Tuesday, October 14, 2014

Java Reflection in PeopleCode

Here is a simple example of how we can overcome the "more than one overload matches" error which occurs when calling an overloaded Java Class Method in PeopleCode.

Note: My understanding and learning is primarily based on Jim Marion's blog. This example is specific to my requirement and it can be easily extended as required.

Let us take an example of FileOutputStream class which has a write method which is overloaded.

Let us assume that we want to call the write method with the following parameters: public void write(byte[] b)

/* Use Reflection to call the appropriate method of the Java Class (which is an overloaded method) */

/* Create an instance of the Class */
Local JavaObject &fos = CreateJavaObject("java.io.FileOutputStream", "/tmp/" | &filename);

/******* REFLECTION ********/

/* Get a reference to a Java class instance of the method parameter(s): In this case the primitive byte array */
Local JavaObject &arrayClass = GetJavaClass("java.lang.reflect.Array");
Local JavaObject &bytArrClass = &arrayClass.newInstance(GetJavaClass("java.lang.Byte").TYPE, 0);

/* Get a reference to the method we want to call using Reflection */
Local JavaObject &methodArgTypes = CreateJavaObject("java.lang.Class[]", &bytArrClass.getClass());
Local JavaObject &methodReference = &fos.getClass().getMethod("write", &methodArgTypes);

/* Call the method */
Local any &result = &methodReference.invoke(&fos, CreateJavaObject("java.lang.Object[]", &byteArray));
/******* REFLECTION ********/

/* Close File Object */
&fos.close();

7 comments:

  1. Sasank I have similar issue but with fileinputstream. The issue is that fileinputstream has 3 mehtods and the one I specifically need is the one with input type file ANy help?
    Thank you

    Local JavaObject &jFile = CreateJavaObject("java.io.File", &1094C_FullPath);
    Local JavaObject &jFileReader = CreateJavaObject("java.io.FileInputStream", &jFile);
    Local JavaObject &messageDigest = GetJavaClass("java.security.MessageDigest").getInstance("MD5");
    Local JavaObject &jByteArray = CreateJavaArray("byte[]", 1024);
    /* Read Input File Byte by Byte */
    Local number &jByteCount = &jFileReader.read(&jByteArray);
    While &jByteCount > 0
    &messageDigest.update(&jByteArray, 0, &jByteCount);
    &jByteCount = &jFileReader.read(&jByteArray);
    End-While;
    /* Perform MD5 Digest */
    Local JavaObject &jMD5Array = &messageDigest.digest();
    /* Convert to Hexstring */
    Local JavaObject &DatatypeConverter = GetJavaClass("javax.xml.bind.DatatypeConverter");
    Local string &encodehex = &DatatypeConverter.printHexBinary(&jMD5Array);
    Local number &filesize = &jFile.length();

    ReplyDelete
    Replies
    1. Sorry for the delay in responding. What is your error message exactly?

      When I tried your code on a PT 8.54.08 environment, I did not receive any overloaded method errors.

      Delete
    2. Also, you could refer Jim Marion's approach to using server side scripting to workaround the java overloaded method errors. It is a much neater solution if it works for you instead of dealing with the reflection.

      http://jjmpsj.blogspot.com/2015/09/javascript-on-app-server-scripting.html

      Delete
  2. Hi Sasank, after reading a lot of blogs, I finaly decide to ask your help !
    I have to load a file (pdf) from a NAS, put it in a HashMap and pass the HashMap to a java method to send it via xmlrpc protocol to a service.
    The problem is that the file has to be load in byte in the map, that I'm trying to do, but the service doesn't recognize the file format.
    Could you please tell me how should i modify my code to do that ?

    Local array of JavaObject &a_docs = CreateArrayRept(CreateJavaObject("java.util.HashMap"), 0);

    Local JavaObject &jDocArray = CreateJavaArray("java.lang.Object[]", &rsDoc.ActiveRowCount);

    &jDoc = CreateJavaObject("java.util.HashMap");

    &SysFileName = &rs(1).PV_ATTACHMENTS.ATTACHSYSFILENAME.Value;
    &UsrFileName = &rs(1).PV_ATTACHMENTS.ATTACHUSERFILE.Value;

    rem here read file into buffer byte array;
    Local JavaObject &buf = CreateJavaArray("byte[]", 1024);
    Local JavaObject &bytes = CreateJavaObject("java.io.ByteArrayOutputStream");
    Local number &byteCount;
    Local JavaObject &in = CreateJavaObject("java.io.FileInputStream", &pathFTP | &SysFileName);

    &byteCount = &in.read(&buf);

    While &byteCount > 0
    &bytes.write(&buf, 0, &byteCount);
    &byteCount = &in.read(&buf);
    End-While;
    &in.close();

    &jDoc.put("content", &bytes.toByteArray());
    &a_docs.Push(&jDoc);

    Thanks in advance.
    Regards

    ReplyDelete
    Replies
    1. Hi Franck - Sorry for the delay. To be honest I have not worked much with Hash Maps let alone documents with Hash Maps. That said, I have a couple of questions:
      - Not sure why you are adding the hash map into an array?
      - Also, does the service provider have any sample java code that they/you can share? It would be a lot easier to try to replicate the code in PeopleCode if we have a sample to work with!

      Delete
  3. We are experiencing a problem with reflection during response.
    &jgetResponseCode.invoke(&jconn, CreateJavaObject("java.lang.Object[]"));

    Error log shows the following:
    Java Exception: java.lang.reflect.InvocationTargetException: during call of java.lang.reflect.Method.invoke. (2,763) AHPY_I037_AE.DOWNLOAD.GBL.default.1900-01-01.HTTPSv2.OnExecute PCPC:2835 Statement:21 (0,0)
    GetNextStateRecord [68] Exception logged: RC=3.
    AePcdExecutePeopleCode [235] Exception logged: RC=100.

    Any help is appreciated

    ReplyDelete
  4. Hello Sasank,

    Is there a way to convert Java code to Peoplecode ? or use the java code in Peoplesoft?. The below piece of java code i want to implement in Peoplesoft.

    package com.amazon.associates.sample;

    import java.io.UnsupportedEncodingException;

    import java.net.URLDecoder;
    import java.net.URLEncoder;

    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;

    import java.text.DateFormat;
    import java.text.SimpleDateFormat;

    import java.util.Calendar;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.Map;
    import java.util.SortedMap;
    import java.util.TimeZone;
    import java.util.TreeMap;

    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;

    import org.apache.commons.codec.binary.Base64;

    public class SignedRequestsHelper {
    private static final String UTF8_CHARSET = "UTF-8";
    private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
    private static final String REQUEST_URI = "/onca/xml";
    private static final String REQUEST_METHOD = "GET";

    private String endpoint = "webservices.amazon.com"; // must be lowercase
    private String awsAccessKeyId = "YOUR AWS ACCESS KEY";
    private String awsSecretKey = "YOUR AWS SECRET KEY";

    private SecretKeySpec secretKeySpec = null;
    private Mac mac = null;

    public SignedRequestsHelper() {
    byte[] secretyKeyBytes = awsSecretKey.getBytes(UTF8_CHARSET);
    secretKeySpec =
    new SecretKeySpec(secretyKeyBytes, HMAC_SHA256_ALGORITHM);
    mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
    mac.init(secretKeySpec);
    }

    public String sign(Map params) {
    params.put("AWSAccessKeyId", awsAccessKeyId);
    params.put("Timestamp", timestamp());

    SortedMap sortedParamMap =
    new TreeMap(params);
    String canonicalQS = canonicalize(sortedParamMap);
    String toSign =
    REQUEST_METHOD + "\n"
    + endpoint + "\n"
    + REQUEST_URI + "\n"
    + canonicalQS;

    String hmac = hmac(toSign);
    String sig = percentEncodeRfc3986(hmac);
    String url = "http://" + endpoint + REQUEST_URI + "?" +
    canonicalQS + "&Signature=" + sig;

    return url;
    }

    private String hmac(String stringToSign) {
    String signature = null;
    byte[] data;
    byte[] rawHmac;
    try {
    data = stringToSign.getBytes(UTF8_CHARSET);
    rawHmac = mac.doFinal(data);
    Base64 encoder = new Base64();
    signature = new String(encoder.encode(rawHmac));
    } catch (UnsupportedEncodingException e) {
    throw new RuntimeException(UTF8_CHARSET + " is unsupported!", e);
    }
    return signature;
    }

    private String timestamp() {
    String timestamp = null;
    Calendar cal = Calendar.getInstance();
    DateFormat dfm = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
    dfm.setTimeZone(TimeZone.getTimeZone("GMT"));
    timestamp = dfm.format(cal.getTime());
    return timestamp;
    }

    private String canonicalize(SortedMap sortedParamMap)
    {
    if (sortedParamMap.isEmpty()) {
    return "";
    }

    StringBuffer buffer = new StringBuffer();
    Iterator> iter =
    sortedParamMap.entrySet().iterator();

    while (iter.hasNext()) {
    Map.Entry kvpair = iter.next();
    buffer.append(percentEncodeRfc3986(kvpair.getKey()));
    buffer.append("=");
    buffer.append(percentEncodeRfc3986(kvpair.getValue()));
    if (iter.hasNext()) {
    buffer.append("&");
    }
    }
    String canonical = buffer.toString();
    return canonical;
    }

    private String percentEncodeRfc3986(String s) {
    String out;
    try {
    out = URLEncoder.encode(s, UTF8_CHARSET)
    .replace("+", "%20")
    .replace("*", "%2A")
    .replace("%7E", "~");
    } catch (UnsupportedEncodingException e) {
    out = s;
    }
    return out;
    }

    }

    ReplyDelete