Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Thursday, March 11, 2021

SignOn PeopleCode: Base64 Decode + Inflate

This is a follow-up to my previous post where we saw how we can Deflate + Base64 Encode a SAML Request. 

Refer: https://pe0ples0ft.blogspot.com/2021/03/sso-deflate-base64-encode-saml.html

Here is an example of the reverse - Base64 Decode + Inflate:

Wednesday, March 10, 2021

SignOn PeopleCode: Deflate + Base64 Encode (SAML Request)

Often, I hear questions about PeopleSoft's support for SAML, OAuth or other types of SSO. I have also seen some bolt-on solutions that are available for purchase. My thoughts on this age old question is as follows:

Signon PeopleCode + SetAuthenticationResult Function + PeopleCode Java Functions

Signon PeopleCode is incredibly flexible and allows us to build any custom SSO integration that might be unique to our requirements. Couple this with the built in SetAuthenticationResult function (to manage redirects and authentication) and PeopleCode Java functions, we have everything that we need to develop an integration with any SSO solution.

Working on PeopleSoft SSO implementations is interesting. No matter what standard or tailored solution we choose, there is always something unique with each environment. I plan to write more follow-up posts on this topic! Stay tuned.

This brings my focus to the purpose of this particular post. A SignOn PeopleCode requirement that was brought to my attention by Diego De Boni - a colleague (and a new friend) in the PeopleSoft community!

The requirement is to Deflate and Base64 encode a string (in this case a SAML Request) in SignOn PeopleCode. This is where PeopleCode Java functions come in handy!

Useful Resources:
What is SAML and how does it work?
SAML Developer Tools - Deflate + Base64 Encode SAML Message

Here is an example of a simple string "ABCDE" that is deflated and Base64 encoded. This string could be replaced with a SAML Request for real use cases.

Before I could come up with a solution, Diego beat me to it and found a way to achieve the Deflate + Base64 encoding using Java in Signon PeopleCode. I asked his permission to share the following sample code as it will help many others in the community. More importantly, it shows the power of this formula:

Signon PeopleCode + SetAuthenticationResult Function + PeopleCode Java Functions

Signon PeopleCode Snippet

I look forward to your comments and feedback (please use the comments section).

Friday, October 23, 2015

Oracle OpenWorld 2015 Conference

Oracle OpenWorld is starting on Sunday, October 25th!!

I will be presenting the following session on Sunday, Oct 25, 9:00 a.m. | Moscone West—3009:
When Integration Broker Is Not Enough [UGF1786] 

I look forward to catching up with any fellow bloggers/readers who are also attending/presenting!

Wednesday, November 5, 2014

Using log4j in PeopleCode - Implementation and Troubleshooting Tips

Many of you in the PeopleSoft space already know that the use of log4j as a logging framework in PeopleCode has been very well detailed and demonstrated in Jim Marion's book.

This post is not intended to explain how to implement log4j (there are several resources on this topic already), but it is intended to share my implementation experience and troubleshooting tips that might be handy.

For my requirement, I built a log4j API (using App Classes) very similar and based on PeopleSoft PeopleTools - Tips and Techniques - Chapter 10. It works great and it is very easy to use in desired locations in PeopleCode.

During my unit testing, I found that the log file rolls (as expected) based on the custom configuration properties but only if I reload the configuration every time I invoke the logging.

If the configuration is loaded once initially (using reloadConfig method in LogManager class), and then subsequently continue logging (using getLogger method in LogManager class) without reloading the configuration every call (until it is necessary... i.e.: until the logger cannot be found by getLogger), the log file does not roll as expected (RollingFileAppender). The logging functionality still works as expected.

It is almost as if the custom appender for maximumFileSize is not taking effect.

#File appender configuration
log4j.appender.cust_file=org.apache.log4j.RollingFileAppender
log4j.appender.cust_file.maximumFileSize=100000
log4j.appender.cust_file.maxBackupIndex=5
log4j.appender.cust_file.File=./LOGS/cust_log4j.log
log4j.appender.cust_file.layout=org.apache.log4j.PatternLayout
log4j.appender.cust_file.layout.ConversionPattern=%d %-5p [%c] - %m%n

While troubleshooting this issue I found a few handy tips. The environment details at the time of writing this post are as follows:
PeopleTools: 8.52.22
Application: Campus Solutions 9.0 Bundle 34
Database: Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
App Server: Linux - oel-5-x86_64

Issues Found with RollingFileAppender:

  • The RollingFileAppender does not roll if we use the getLogger method (even after I verified that all the necessary log4j configuration is in memory).

  • Another issue with the RollingFileAppender is that it randomly picks old files and starts writing into it. Previously rolled files get activated again. This is very similar to the issue discussed here: log4j file appender.

Handy Tips to Troubleshoot log4j Issues:

  • We can use the following code to determine the current log4j properties in the JVM. This proved to be very useful for my troubleshooting (and identify differences between invoking the reloadConfig and the getLogger methods).

    /* Get current log4j properties */

    Local JavaObject &bos = CreateJavaObject("java.io.ByteArrayOutputStream");
    Local JavaObject &pw = CreateJavaObject("java.io.PrintWriter", &bos);
    Local JavaObject &pp = CreateJavaObject("org.apache.log4j.config.PropertyPrinter", &pw);
    &pp.print(&pw);
    Local string
    &currentProperties = &bos.toString();

    Note:
    We can either print &currentProperties to some other file or any other preferred logging mechanism (as we cannot rely on log4j while troubleshooting issues with log4j).

    Sample of printing &currentProperties string: 
log4j.rootCategory=DEBUG
log4j.appender.cust_file=org.apache.log4j.RollingFileAppender
log4j.appender.cust_file.append=true
log4j.appender.cust_file.bufferSize=8192
log4j.appender.cust_file.bufferedIO=false
log4j.appender.cust_file.file=./LOGS/cust_log4j.log
log4j.appender.cust_file.immediateFlush=true
log4j.appender.cust_file.maxBackupIndex=5
log4j.appender.cust_file.maximumFileSize=100000
log4j.appender.cust_file.layout=org.apache.log4j.PatternLayout
log4j.appender.cust_file.layout.contentType=text/plain
log4j.appender.cust_file.layout.conversionPattern=%d %-5p [%c] - %m%n
log4j.category.TEST_API_LOG4J=ALL, cust_file
log4j.category.TEST_API=OFF
log4j.appender.A1=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A1.append=true
log4j.appender.A1.bufferSize=8192
log4j.appender.A1.bufferedIO=false
log4j.appender.A1.datePattern=.yyyy-MM-dd
log4j.appender.A1.file=./LOGS/PSJChart.log
log4j.appender.A1.immediateFlush=true
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.contentType=text/plain
log4j.appender.A1.layout.conversionPattern=%d{DATE} [%t] %-5p %c %x - %m%n
log4j.category.com.peoplesoft.pt.charting=WARN, A1 
log4j.category.TEST_API_LOG4J.oprid.PS=OFF

  • We can use the following code to clear all log4j properties from the JVM. Again, this is handy while unit testing to clear any loaded properties and starting over again without having to restart the app server.

    Local JavaObject &manager = GetJavaClass("org.apache.log4j.LogManager");
    &manager.resetConfiguration();

    The result of invoking the above code would reset the current JVM log4j properties as follows:

    log4j.rootCategory=DEBUG

Findings and Workarounds:

Note: The findings and workarounds are completely based on my experience and environment. This might not be reproducible and appropriate for all cases. The intention is to share and provide ideas if others run into similar or related issues.

  • I found that there are several different threads that are in play at the app server level (although there was only app server domain in the environment). And it appears that each of these threads have its own JVM (and associated log4j configuration) of sorts. Basically, the first time I do a configuration load (I might be on one thread where the custom log4j configuration is loaded), then every subsequent time I call the getLogger (I might be on other threads which does not have the custom log4j configuration).

  • To work around the above issue, I wrote some code to compare the custom configuration in the message catalog with the current log4j properties in memory. If any differences were found, then configuration would be reloaded.
    Here is my addition to the getLogger method of LogManager class:

    This ensures that all threads on the app server domain are properly initialized with the custom log4j properties.


    method getLogger
       /+ &name as String +/
      
       If (%This.isNotInitialized()) Then
          %This.initialize();  
       End-If;
      
       If (%This.isNotInMemory()) Then
          %This.initialize();
       End-If;
       /* Note: %This.isNotInMemory() method implementation is detailed in the Appendix Code below */
       Return create TEST_API:UTIL:Logger(&name);
      
    end-method;

  • While the above item solved my problem partially, I still have not found a way to work around the issue of RollingFileAppender randomly picking old files to write into.

  • As a work around to the above issues identified, I decided to reload the configuration every time. Additionally, I am not sure if the juice is worth the squeeze in trying to avoid the configuration reload (if it is already loaded) and rather just reload every time? I am guessing that the cost in trying to do the determination might be the same as the actual configuration reload itself. At least, with the configuration reload every time we can be guaranteed that the rolling works as expected.
    Here is my work around getLogger method of LogManager class.

    method getLogger
       /+ &name as String +/
      
          %This.initialize(); 
      
       Return create TEST_API:UTIL:Logger(&name);
      
    end-method;

Appendix Code:

%This.isNotInMemory() Method Implementation in LogManager Class (if anyone is interested):


method isNotInMemory

   /+ Returns Boolean +/
   Local JavaObject &baos, &pw, &pp;
   Local string &inMemoryProps, &fileName, &customConfig, &temp;
   Local number &prev, &index, &i;
  
   Local array of string &arrProps = CreateArrayRept("", 0);
  
   /* Get Current In Memory Properties */
   &baos = CreateJavaObject("java.io.ByteArrayOutputStream");
   &pw = CreateJavaObject("java.io.PrintWriter", &baos);
   &pp = CreateJavaObject("org.apache.log4j.config.PropertyPrinter", &pw);
   &pp.print(&pw);
  
   &inMemoryProps = &baos.toString();
  
   /* Get Custom Properties from Message Catalog Entry */
   &fileName = GetEnv("PS_SERVDIR") | "/LOGS/test_log4j.log";
   &customConfig = MsgGetExplainText(27000, 1, "", &fileName);
  
   /******** Split Message Catalog Text into Property Array ********/
   /* Find first line in Configuration */
   &prev = 0;
   &index = Find(Char(10), &customConfig);
  
   While &index > 0
     
      &temp = Substring(&customConfig, (&prev + 1), (&index - &prev - 1));
      If All(&temp) And
            Substring(&temp, 1, 1) <> "#" Then
         &arrProps.Push(&temp);
      End-If;
     
      &prev = &index;
      &index = Find(Char(10), &customConfig, &index + 1);
     
      /* Check if last line (because carriage return might not exist at the end) */
      If &index = 0 And
            &prev < Len(&customConfig) Then
         &temp = Substring(&customConfig, (&prev + 1), (Len(&customConfig) - &prev));
         If All(&temp) And
               Substring(&temp, 1, 1) <> "#" Then
            &arrProps.Push(&temp);
         End-If;
      End-If;
     
   End-While;
    /******** Split Message Catalog Text into Property Array ********/
  
   /* Find Custom Properties in JVM (In Memory) */
   For &i = 1 To &arrProps.Len
     
      If Find(&arrProps [&i], &inMemoryProps) = 0 Then
         /* Configuration needs to be re-loaded */
         Return True;
      End-If;
   End-For;
  
   Return False;
  
end-method;

Wednesday, October 29, 2014

Apache Commons HttpClient for REST in PeopleCode - Part II - More Examples

This is a follow up to my previous post where we discussed how to use Apache Commons HttpClient for REST in PeopleSoft. Here are some more examples.

GetMethod - Adding Cookie (Header) to the Request:

Local JavaObject &jHttp, &jMethod;

/* Initialize HttpClient and set parameters */
&jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
&jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);

/* Initialize GetMethod */
&sURL = "https://www.test.com/name.v1/get?emplid=12345";
&jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.GetMethod", &sURL);
&jMethod.setFollowRedirects( False);

/* Adding Cookie to the Request */
&jMethod.setRequestHeader("Cookie", "JSESSIONID=64497D7D587637EBF17E128881C04016";
/* Adding Cookie to the Request */

/* Invoke GetMethod */
&return = &jHttp.executeMethod(&jMethod);
&responseStatus = &jMethod.getStatusLine().getStatusCode();

MessageBox(0, "", 0, 0, "Response Status: " | &responseStatus); 
MessageBox(0, "", 0, 0, "Response Message: " | &jMethod.getResponseBodyAsString());

&jMethod.releaseConnection();

PostMethod - Plain Text Content:

Local JavaObject &jHttp, jMethod;

/* Initialize HttpClient and set parameters */

&jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
&jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);

/* Initialize PostMethod and Set Request Details */

Local string &sURL = "https://cas.test.com/cas/v1/tickets";
&jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.PostMethod", &sURL);
&jMethod.setFollowRedirects( False);
&jMethod.setRequestHeader("Content-Type", "text/plain");
&jMethod.setRequestBody("username=testuser&password=testpassword");

/* Invoke PostMethod */

&jHttp.executeMethod(&jMethod);

Local integer &responseStatus = &jMethod.getStatusLine().getStatusCode();

Local string &responseBody = &jMethod.getResponseBodyAsString();

MessageBox(0, "", 0, 0, "&responseBody " | &responseBody);

MessageBox(0, "", 0, 0, "&responseStatus " | &responseStatus);

&jMethod.releaseConnection();

PostMethod - Multi-Part Message:

In this example, we will be posting a file on the App Server file system as a multi-part message.

Local JavaObject &jHttp,
&jMethod, &filePart, &partArray, &mPartReqEntity;

/* Initialize HttpClient and set parameters */

&jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
&jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);

/* Initialize PostMethod */

Local string &sURL = "https://doc.mgmt.com/upload/v1/filename=test.pdf";
&jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.PostMethod", &sURL);
&jMethod.setFollowRedirects( False);

/* Add Multi-Part Message */


/* Create File Object from App Server */

Local JavaObject &file = CreateJavaObject("java.io.File", "/tmp/test.pdf");

/* Create File Part */
&filePart = CreateJavaObject("org.apache.commons.httpclient.methods.multipart.FilePart", "test.pdf", &file);
/* Add File Part to Part Array */
&partArray = CreateJavaObject("org.apache.commons.httpclient.methods.multipart.Part[]", &filePart);
/* Create Multi-Part Request Entity */
&mPartReqEntity = CreateJavaObject("org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity", &partArray, &jMethod.getParams());
/* Add Multi-Part Request Entity to the Post Method */
&jMethod.setRequestEntity(&mPartReqEntity);

/* Add Multi-Part Message */

/* Invoke PostMethod */
Local integer &return = &jHttp.executeMethod(&jMethod);
Local integer &responseStatus = &jMethod.getStatusLine().getStatusCode();
Local string &responseBody = &jMethod.getResponseBodyAsString();

MessageBox(0, "", 0, 0, "&responseBody " | &responseBody);
MessageBox(0, "", 0, 0, "&responseStatus " | &responseStatus);

&jMethod.releaseConnection();


DeleteMethod:

Local JavaObject &jHttp, jMethod;

/* Initialize HttpClient and set parameters */
&jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
&jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);

/* Initialize DeleteMethod */
Local string &sURL = "https://doc.mgmt.com/delete/v1/filename=test.pdf";
Local JavaObject &jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.DeleteMethod", &sURL);
&jMethod.setFollowRedirects( False);

/* Invoke DeleteMethod */

Local integer &return = &jHttp.executeMethod(&jMethod);
Local integer &responseStatus = &jMethod.getStatusLine().getStatusCode();
Local string &responseBody = &jMethod.getResponseBodyAsString();

MessageBox(0, "", 0, 0, "&responseBody " | &responseBody);
MessageBox(0, "", 0, 0, "&responseStatus " | &responseStatus);

&jMethod.releaseConnection();

Tuesday, October 28, 2014

Apache Commons HttpClient for REST in PeopleCode - Part I - GetMethod and Basic Authentication Examples

Recently, I used Apache Commons HttpClient for making REST calls in PeopleCode for a project (interacting with a RESTful API of a Document Management System).

I found that using PeopleSoft Integration Broker for REST had the following limitations for my project:
1. Cookies (intended to be part of response messages) were getting lost and not coming through.
2. Dealing with, reading and processing response messages containing raw binary data (document).

Looking for workarounds, I then started exploring Apache Commons HttpClient after reading Chris Malek's blog and some posts in Jim Marion's blog.

The advantage of using Java is that it gives us a lot of flexibility to interact directly at the http layer. The disadvantage is that we would be bypassing Integration Broker (Gateway) which means we need to consider and implement functionality like logging, error handling and other built-in IB features such as authentication, etc.

There is also a distinction between the IB approach versus the Java approach.

IB Approach has the following flow:
App Server -> Web Server (IB Gateway) -> Third Party System

Java Approach has the following flow:
App Server -> Third Party System

I mention the above to point out that if there are any firewalls to be opened or if there are certificates/keys to be loaded then it needs to be done at the App Server level (if we are going with the Java Approach).

Stating the obvious, but another point to note is that this Java Approach is only an option for outbound REST calls.

That said, I would like to share my experiments and implementation experiences using Apache Commons HttpClient with some examples.

In this post, let us explore how to invoke the Get Method and use Basic Authentication. There are two options that I found to use Basic Authentication (Note: Based on OTN discussion).

Option 1 - Adding Authorization Header:

Local JavaObject &jHttp, &jMethod;

/* Initialize HttpClient and set parameters */
&jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
&jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);

/* Initialize GetMethod */
&sURL = "https://www.test.com/name.v1/get?emplid=12345";
&jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.GetMethod", &sURL);
&jMethod.setFollowRedirects( False);

/* Basic Auth - Adding Authorization Header */
/* &base64EncodeString = username:password encoded in base64 */
/* Note: In Production it is advised to store the base64 encoded string in a configuration table rather than hardcoding */
&base64EncodeString = "ABCD12345TESTBASE64";
&jMethod.setRequestHeader("Authorization", "Basic " | &base64EncodeString);
/* Basic Auth - Adding Authorization Header */

/* Invoke GetMethod */
&return = &jHttp.executeMethod(&jMethod);
&responseStatus = &jMethod.getStatusLine().getStatusCode();

MessageBox(0, "", 0, 0, "Response Status: " | &responseStatus); 
MessageBox(0, "", 0, 0, "Response Message: " | &jMethod.getResponseBodyAsString());

&jMethod.releaseConnection();

Option 2 - Using UsernamePasswordCredentials Class:


Local JavaObject &jHttp, &jMethod, &jCred, &jAuth;

/* Initialize HttpClient and set parameters */
&jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
&jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);

/* Initialize GetMethod */
&sURL = "https://www.test.com/name.v1/get?emplid=12345";
&jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.GetMethod", &sURL);
&jMethod.setFollowRedirects( False);

/* Basic Auth - Using UsernamePasswordCredentials Class */
&jCred = CreateJavaObject("org.apache.commons.httpclient.UsernamePasswordCredentials", "userid", "password");
&jAuth = CreateJavaObject("org.apache.commons.httpclient.auth.AuthScope", "www.test.com", 443);
/* Note: Use 443 if your host is using https or 80 if your host is using http */

&jHttp.getState().setCredentials(&jAuth, &jCred);
&jMethod.setDoAuthentication( True);
/* Basic Auth - Using UsernamePasswordCredentials Class */

/* Invoke GetMethod */
&return = &jHttp.executeMethod(&jMethod);
&responseStatus = &jMethod.getStatusLine().getStatusCode();

MessageBox(0, "", 0, 0, "Response Status: " | &responseStatus); 
MessageBox(0, "", 0, 0, "Response Message: " | &jMethod.getResponseBodyAsString());

&jMethod.releaseConnection();

Note: Here are the jars that I downloaded and placed on my app server classpath. An app server bounce/restart is required for the new jars to take effect.
- commons-httpclient-3.1.zip
- commons-codec-1.6.zip
- commons-logging-1.1.3.zip
- commons-io-2.4.zip (optional)

Few more examples of "Apache Commons HttpClient for REST in PeopleCode" to follow in Part II.

Tuesday, October 21, 2014

Reading .properties file on app server file system using Java in PeopleCode

Here is an example of how we can read .properties files on the app server file system easily using Java in PeopleCode.

Why: There might be some useful information in the .properties files on the app server that might not be accessible to developers directly in PeopleCode. Some examples: OS Type and Version Details in peopletools.properties.

In this example, let us assume we want to access the OS Type and Version properties that is set on the peopletools.properties file (located either in PS_CFG_HOME or PS_HOME).

Here is an example of the contents in the peopletools.properties file:

#Peopletools Install Settings
#Fri Oct 17 10:19:17 EDT 2014
installlocation=<PS_HOME>
dbcodessuffix=PT
unicodedb=0
licensegroupname=PeopleTools
hstplt=oel-5-x86_64
psplatformregname=ORACLE
psserver=App
psdbbin=
psenv=App
dbtypedescr=Oracle
dbtype=ORA
licensegroup=06
tuxedodir=
productversion=8.52.07
psplatform=Linux

Sample PeopleCode:

/* Get the file (peopletools.properties) */
Local JavaObject &fis = CreateJavaObject("java.io.FileInputStream", GetEnv("PS_CFG_HOME") | "/peopletools.properties");
/* Note: If PS_CFG_HOME is not configured in your environment, then try PS_HOME. */

/* Create Properties JavaObject */
Local JavaObject &props = CreateJavaObject("java.util.Properties");


/* Load file to Properties Class */
&props.load(&fis);

/* Use Reflection to call the appropriate getProperty (overloaded) method of the Properties Class */

/******* REFLECTION ********/
/* Get a reference to a Java class instance for the String Class */
Local JavaObject &stringClass = GetJavaClass("java.lang.String");

/* Get a reference to the method we want to call */
Local JavaObject &methodArgTypes = CreateJavaObject("java.lang.Class[]", &stringClass);
Local JavaObject &methodReference = &props.getClass().getMethod("getProperty", &methodArgTypes);

/* Call the method */
Local JavaObject &psplatform = &methodReference.invoke(&props, CreateJavaObject("java.lang.Object[]", "psplatform"));
Local JavaObject &hstplt = &methodReference.invoke(&props, CreateJavaObject("java.lang.Object[]", "hstplt"));
/******* REFLECTION ********/

/* psplatform returns OS Type */
MessageBox(0, "", 0, 0, "psplatform: " | &psplatform.toString());
/* hstplt returns OS Details (including version) */
MessageBox(0, "", 0, 0, "hstplt: " | &hstplt.toString());

&fis.close();

Granted that the same can also be achieved without using Java and parsing through the .properties file using PeopleCode File Object. This post is just to show an alternative approach which might be applicable in certain scenarios.

This approach could be easily extended to any .properties file on the application server or process scheduler server.

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();