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

Friday, June 29, 2018

101 Ways to Process JSON with PeopleCode

... well... maybe not 101 ways, but there are several!

There is a lot of justified buzz around JSON. Many of us want to (or must) generate and parse JSON with PeopleSoft. Does PeopleSoft support JSON? Yes, actually. The Documents module can generate and parse JSON. Unfortunately, many of us find the Documents module's structure too restrictive. The following is a list of several alternatives available to PeopleSoft developers:

  • Documents module
  • Undocumented JSON objects delivered by PeopleTools
  • JSON.org Java implementation (now included with PeopleTools)
  • JavaScript via Java's ScriptEngineManager

We will skip the first two options as there are many examples and references available on the internet. In this post, we will focus on the last two options in the list: JSON.org and JavaScript. Our scenario involves generating a JSON object containing a role and a list of the role's permission lists.

PeopleCode can do a lot of things, but it can't do everything. When I find a task unfit for PeopleCode, I reach out to the Java API. PeopleCode has outstanding support for Java. I regularly scan the class and classes directories of PS_HOME, looking for new libraries I can leverage from PeopleCode. One of the files in my App Server's class path is json.jar. As a person interested in JSON, how could I resist inspecting the file's contents? Upon investigation, I realized that json.jar contains the json.org Java JSON implementation. This is good news as I used to have to add this library myself. So how might we use json.jar to generate a JSON file? Here is an example

JSON.org has this really cool fluent design class named JSONStringer. If the PeopleCode editor supported custom formatting, fluent design would be really, really cool. For now, it is just cool. Here is an example of creating the same JSON using the JSONStringer:

What about reading JSON using json.org? The following example starts from the JSON string generated by JSONStringer. It is a little ugly because it requires Java Reflection to invoke the JSONObject constructor. On the positive side, though, this example demonstrates Java Class casting in PeopleCode (hat tip to tslater2006 for helping me with Java Class casting in PeopleCode)

What is that you say? Your PeopleTools installation doesn't have the json.jar (or jsimple.jar) files? If you like this approach, then I suggest working with your system administrator to deploy the JSON.org jar file to your app and/or process scheduler's Java class path

But do we really need a special library to handle JSON? By definition, JSON describes a JavaScript object. Using Java's embedded JavaScript script engine, we have full access to JavaScript. Here is a sample JavaScript file that generates the exact same JSON as the prior two examples:

... and the PeopleCode to invoke this JavaScript:

Did you see something in this post that interests you? Are you ready to take your PeopleTools skills to the next level? We offer a full line of PeopleTools training courses. Learn more at jsmpros.com.

Tuesday, November 10, 2015

JavaScript and PeopleCode Array Parameters

I have been experimenting with scripting PeopleCode using JavaScript. This is possible because Java includes Mozilla's Rhino JavaScript engine. I took one of my experiments to OpenWorld 2015 which shows creating a Microsoft Excel Spreadsheet using POI and JavaScript. Here we are, a couple of weeks later, and I see this in the PeopleSoft OTN Discussion Forum: Java Exception: java.lang.reflect.InvocationTargetException: during call of java.lang.reflect.Method .invoke. Perfect! That is my exact use case from OpenWorld. I just happen to have a code sample to share on the forum. The developer's scenario was a bit more complicated. As you will note from the forum post, the developer needed to invoke SQL.Fetch from JavaScript. The JavaScript version of SQL.Fetch, which uses the PeopleCode Java interface, requires an array of selected columns. My first thought was just to use a standard JavaScript array. Since the SQL only has one column, I just needed an array with one item. This didn't work. JavaScript Arrays clearly are not Java Arrays. Here is an example:

var result = (function() {  
    var ReflectiveArray = java.lang.reflect.Array;  
    var CreateSQL = Packages.PeopleSoft.PeopleCode.Func.CreateSQL;  
    var columns = ReflectiveArray.newInstance(java.lang.Object,
        1 /* number of selected columns */);  
    var results = [];  
      
    SQL = CreateSQL("SELECT OPRDEFNDESC FROM PSOPRDEFN WHERE ROWNUM < 10");  
    while (SQL.Fetch(columns)) {  
        results.push(columns[0]);  
    }  
      
    return results.join();  
      
}());  

Sunday, May 03, 2009

Base64 Encoding for PeopleSoft

This week on the ittoolbox peopeltools-I forum, I ran across a question on base64 encoding binary data using PeopleCode. There are a couple of ways to base64 encode data, none of which are delivered. Before deciding which method to use, we first have to answer two questions:

  1. Where is the data (file, database, text string)?
  2. Is the data in binary or plain text format?

If the data resides in the database, then we can select it out using the following SQL:

SELECT UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(UTL_RAW.CAST_TO_RAW('Hello World'))) FROM DUAL;

In fact, rewrite that as:

SELECT UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(UTL_RAW.CAST_TO_RAW(%TextIn(:1)))) FROM DUAL;

And, if you are an Oracle database user, then you have a generic SQL statement you can use to base64 encode any plain text. If you name the SQL definition BASE_64_ENCODE, then you can call that SQL definition as follows:

Local string &source = "Hello World";
Local string &encoded;
Local number &i;

SQLExec(SQL.BASE_64_ENCODE, &source, &encoded);
MessageBox(0, "", 0, 0, "base64: " | &encoded);

The PeopleCode and SQL above will work for any text string as long as you are using an Oracle database. Now, what if you want to base64 encode binary data? If the binary data is in the database, then modify the SQL accordingly.

Did you notice the %TextIn Meta-SQL I sneaked into the SQL above? Use it whenever you are sending large amounts of text to the database. For a string as short as "Hello World," you don't need it, but if you are trying to base64 encode an entire XML document, then you might.

What if you are not using Oracle database? Perhaps T-SQL has a base64 encoding routine? If so, great. If not, then the following code provides a Java/PeopleCode solution. Unfortunately, the 2 common Java base64 encoding algorithms use method and constructor overloads in a manner that requires some ugly Java reflection PeopleCode.

REM ** create an instance of the Java base64 encoder;
Local JavaObject &encoder = CreateJavaObject("sun.misc.BASE64Encoder");

REM ** get a reference to a Java class instance for the primitive byte array;
Local JavaObject &arrayClass = GetJavaClass("java.lang.reflect.Array");
Local JavaObject &bytArrClass = &arrayClass.newInstance(GetJavaClass("java.lang.Byte").TYPE, 0);

REM ** use reflection to get a reference to the method we want to call;
Local JavaObject &encodeArgTypes = CreateJavaObject("java.lang.Class[]", &bytArrClass.getClass());
Local JavaObject &encodeMethod = &encoder.getClass().getMethod("encode", &encodeArgTypes);

REM ** call the method;
Local JavaObject &bytes = CreateJavaObject("java.lang.String", "Hello World").getBytes();
Local JavaObject &result = &encodeMethod.invoke(&encoder, CreateJavaObject("java.lang.Object[]", &bytes));

REM ** print the result;
MessageBox(0, "", 0, 0, "Result: " | &result.toString());

If you know enough about Java to compile a wrapper class and you have access to your app server's class directory, then you can eliminate the Java reflection code in favor of a wrapper:

package com.blogspot.jjmpsj;

import java.io.IOException;

import sun.misc.BASE64Encoder;

public class Base64Coder {
public static String encodeString(String data) {
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(data.getBytes());
}

public static String encodeBytes(byte[] data) {
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(data);
}
}

... and call it like so:

Local JavaObject &encoder = GetJavaClass("com.blogspot.jjmpsj.Base64Coder");
Local string &result = &encoder.encodeString("Hello World");

MessageBox(0, "", 0, 0, &result);

As you can see from this final example, when working with overloaded constructors and methods, Java wrappers dramatically simplify PeopleCode.

Each of the examples above uses "Hello World" as the text string. What is "Hello World" in base64? SGVsbG8gV29ybGQ=. You can find an online base64 encoder/decoder here.

I will have to leave the question of base64 encoding binary files for another day. I am on the East coast attending Collaborate '09 and I need to get some rest so I can be right as rain for the conference tomorrow morning.

Update May 4th, 2009 at 4:09 AM: In the comments to this post, devwfb suggests that developers use org.apache.commons.codec.binary.Base64
rather than sun.misc.BASE64Encoder because classes in the sun package are undocumented and unsupported. devwfb is right and I should have mentioned this fact in my original post. I chose to use sun's base64 encoder because it is delivered and doesn't require customers to add more jars to the $PS_HOME/class directory. I will post the commons-codec example and link to it from here. See Sun's FAQ for more information.

Wednesday, September 17, 2008

Parsing JSON with PeopleCode

A lot of web services return results in JSON format rather than XML. Is it possible to parse JSON in PeopleCode? Can you consume JSON web services uisng PeopleCode? Absolutely. My first attempt at parsing JSON in PeopleCode used eval and the Rhino JavaScript scripting engine as documented in my post Scripting PeopleSoft. Because the Bean Scripting Framework's BSFEngine.eval method returns a java.lang.Object, I was left in a state of painful Java Reflection (executing each call using Java Reflection). Looking over the json.org website, I took note of the collection of Java JSON parsers. After choosing the org.json parser. I again found myself having to deal with the pain of Java Reflection (and, most definitely, I was left wishing PeopleCode had a JavaCast function). Rather than deal with the Java reflection required to create an instance of a JSONObject or JSONArray, I chose an easier route: write a helper class to construct JSON objects. Here is the source:

package yourcompany.json;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class ParseHelper {
private ParseHelper() {
}

public static JSONObject objectFromString(String json) throws JSONException {
return new JSONObject(json);
}

public static JSONArray arrayFromString(String json) throws JSONException {
return new JSONArray(json);
}
}

If my JSON looks like

{
"EMPLID": "E1234",
"NAME": "Marion,Jim",
"DIRECTS": [
{
"EMPLID": "E5678",
"NAME": "Doe,John"
},
{
"EMPLID": "E2468",
"NAME": "Doe,Jane"
}
]
}

Then I can enumerate the directs array using PeopleCode like:

Local string &json_data = "my JSON string...";

REM ** use static helper class to avoid ugly Java reflection;
Local JavaObject &json = GetJavaClass("yourcompany.json.ParseHelper").objectFromString(&json_data);
Local JavaObject &directsArr = &json.getJSONArray("DIRECTS");
Local number &length = &directsArr.length();
Local number &directsIdx = 0;

For &directsIdx = 0 To &length - 1
Local JavaObject &direct = &directsArr.getJSONObject(&directsIdx);
&logger.debug("DIRECTS [" | &directsIdx | "] " | &direct.get("NAME").toString());
End-For;

Friday, September 05, 2008

Calling log4j's Logger.error from PeopleCode

A couple of years have passed since I first posted about using log4j as a logging framework for PeopleCode. In my post log4j and PeopleCode Part II, I noted that it is not possible to directly call the Logger.error method because error is a keyword in PeopleCode. I also mentioned that it would be possible to use reflection to call this method. Here is the PeopleCode required to call the error method using reflection:

Local JavaObject &logger = GetJavaClass("org.apache.log4j.Logger").getLogger("my.custom.logger");
Local JavaObject &jErrorArgTypes = CreateJavaObject("java.lang.Class[]", GetJavaClass("java.lang.Object"));
Local JavaObject &jErrorMethod = &logger.getClass().getMethod("error", &jErrorArgTypes);

&jErrorMethod.invoke(&logger, CreateJavaObject("java.lang.Object[]", "This is an error message"));

Want it all on one line?

Local JavaObject &logger = GetJavaClass("org.apache.log4j.Logger").getLogger("my.custom.logger");
&logger.getClass().getMethod("error", CreateJavaObject("java.lang.Class[]", GetJavaClass("java.lang.Object"))).invoke(&logger, CreateJavaObject("java.lang.Object[]", "This is another error message"));

Using reflection from PeopleCode can get ugly. If you are going to use Logger.error, then you may want to hide the Java implementation details in an app class.

Thursday, July 24, 2008

Using XQuery with PeopleSoft

Like XSLT 1.0, XQuery provides a method for transforming XML. Besides the obvious differences in syntax, XQuery provides additional functionality that doesn't exist in the XSLT 1.0 specification. For example, XQuery not only provides the ability to transform a single XML document into a single output document, like XSLT 1.0, but also adds the ability to merge and/or join multiple input documents into a single result document. I'll let you look up the rest of the differences between the 2 languages. I'm not going to say either is better. They are different and each has its place. The main question I want to answer is, "How can I use XQuery with PeopleSoft?"

To use XQuery from PeopleSoft, you will need to download an XQuery library, install it, and configure your PeopleSoft app server to use it. The example code that follows uses the Saxon XQuery processor. Which version of Saxon you download and how you configure your app server to support Saxon will depend on which version of PeopleTools you are using. This difference is the result of changes made to the Java JAXP API between Java 1.4.2 (PT 8.48 and earlier) and Java 1.5 (PT 8.49). Below, you will find separate configuration sections for the Java 1.4.2 PeopleTools versions and the Java 1.5 PeopleTools version. To find out which Java version your app server uses, execute the following command:

%PS_HOME%\jre\bin\java.exe -version.

When you add the Saxon jars to your classpath, you will be adding a second implementation of the JAXP interfaces to your app server's Java runtime environment. PeopleSoft uses the Apache (Xalan/Xerces) JAXP implementation. Both of these implementations, Saxon and Apache, will register themselves as the default JAXP factory implementation. To ensure that PeopleSoft works correctly after installing the Saxon jars, you need to explicitly set the default JAXP implementation. There are 2 ways to do this: the jaxp.properties file or JVM system properties. In this example, I will give the steps for modifying your app server's JVM system properties, ignoring the jaxp.properties alternative.

To ensure that you configure our app server correctly, you need a way to determine your current JAXP settings. I wrote the following PeopleCode to assist you in configuring your JAXP settings. When run from an IScript, this code will give you the JAXP settings used by your online app server, formatted so that you can copy and paste it into your psappsrv.cfg file, as described in a later step. If you will be using XQuery in your process scheduler server, then you can replace the text %Response.WriteLine with MessageBox, and run this same PeopleCode from an AppEngine program. To run this code online, you will need to create a WEBLIB and IScript and paste this code into your record field PeopleCode:

Function IScript_GetJAXPSystemProperties()
Local string &XPathFactorySetting = "";
Local string &XPathFactoryName = "";

try
&XPathFactoryName = GetJavaClass("javax.xml.xpath.XPathFactory").newInstance().getClass().getName();
If (All(&XPathFactoryName)) Then
&XPathFactorySetting = " -Djavax.xml.xpath.XPathFactory=" | &XPathFactoryName | " -Djavax.xml.xpath.XPathFactory:http://java.sun.com/jaxp/xpath/dom=" | &XPathFactoryName;
End-If;
catch Exception &e1
end-try;

%Response.SetContentType("text/plain");
%Response.WriteLine("-Djavax.xml.transform.TransformerFactory=" | GetJavaClass("javax.xml.transform.TransformerFactory").newInstance().getClass().getName() | &XPathFactorySetting);
End-Function;

After creating your IScript, you can run this code from a URL similar to:

http://<server>:<port>/psc/<site>/EMPLOYEE/<node>/s/WEBLIB_CSS_SAXN.ISCRIPT1.FieldFormula.IScript_GetJAXPSystemProperties

Just replace the parts in < > with your site specific values.

Running this code after installing Saxon should give you the exact same result. If it doesn't, then something in the Saxon jars is overriding the PeopleSoft delivered value. Check your Java VM options to ensure that you have them set correctly. Besides your initial run prior to installing Saxon, I suggest you run this again after you install the Saxon jars and BEFORE you update the psappsrv.cfg file to see how the Factory class implementations change with the presence of the Saxon jars. Then, after you modify your psappsrv.cfg file, you can be sure that the JVM is set correctly.

PeopleTools version specific installation steps:

PT 8.48/Java 1.4.2

  1. Download Saxon version 8.9.04 from the Saxon SourceForge file repository
  2. Extract saxon8.jar, saxon8-xqj.jar, and saxon8-xpath.jar from the downloaded archive and place them in your %PS_HOME%/class directory.
  3. Open your psappsrv.cfg file and find the line that starts with JavaVM Options= and append the value given to you when you ran the function IScript_GetJAXPSystemProperties. It should look something like: -Djavax.xml.transform.TransformerFactory=org.apache.xalan.processor.TransformerFactoryImpl.
  4. Restart your app server

PT 8.49/Java 1.5

  1. Download Saxon version 9.1.0.1 from the Saxon SourceForge file repository
  2. Extract saxon9.jar and saxon9-xpath.jar from the downloaded archive and place them in your %PS_HOME%/class directory.
  3. Open your psappsrv.cfg file and find the line that starts with JavaVM Options= and append the value given to you when you ran the function IScript_GetJAXPSystemProperties. It should look something like: -Djavax.xml.transform.TransformerFactory=org.apache.xalan.processor.TransformerFactoryImpl -Djavax.xml.xpath.XPathFactory=com.sun.org.apache.xpath.internal.jaxp.XPathFactoryImpl -Djavax.xml.xpath.XPathFactory:http://java.sun.com/jaxp/xpath/dom=com.sun.org.apache.xpath.internal.jaxp.XPathFactoryImpl.
  4. Restart your app server

Downloading jars and placing them in the class path is standard practice when adding new Java libraries to a PeopleSoft implementation. Step 3 above, however, is unique. By specifically setting the JAXP system properties in the psappsrv.cfg file, we force the JVM to use the correct JAXP factories regardless of the order in which the JVM loads our Saxon/Apache jar files.

The following PeopleCode demonstrates how to execute an XQuery from PeopleCode. You will notice that the following PeopleCode references an HTML object named RSS_XQ. You can download the HTML for this HTML object (really, XQuery source, not HTML) from my online repository: rss2_html_obj.xq

Function ExecXQuery(&xquery As string) Returns string
Local JavaObject &jConfig = CreateJavaObject("net.sf.saxon.Configuration");
Local JavaObject &jClass = GetJavaClass("java.lang.Class");

Local JavaObject &jStaticContext = CreateJavaObject("net.sf.saxon.query.StaticQueryContext", &jConfig);

rem ** use Java reflection to call the compile method;
rem Local JavaObject &jExp = &jStaticContext.compileQuery(GetHTMLText(HTML.RSS_NYTIMES_BUSINESS_XQ));
Local JavaObject &jCompileArgTypes = CreateJavaObject("java.lang.Class[]", &jClass.forName("java.lang.String"));
Local JavaObject &jCompileMethod = &jStaticContext.getClass().getDeclaredMethod("compileQuery", &jCompileArgTypes);
Local JavaObject &jExp = &jCompileMethod.invoke(&jStaticContext, CreateJavaObject("java.lang.Object[]", &xquery));

Local JavaObject &jOutputProperties = CreateJavaObject("java.util.Properties");
rem ** set any output properties like encoding, method, etc;

Local JavaObject &jResultWriter = CreateJavaObject("java.io.StringWriter");
Local JavaObject &jStreamResult = CreateJavaObject("javax.xml.transform.stream.StreamResult");
Local JavaObject &jDynamicContext = CreateJavaObject("net.sf.saxon.query.DynamicQueryContext", &jConfig);

&jStreamResult.setWriter(&jResultWriter);

rem PeopleCode engine thinks &jExp is java.lang.Object, not net.sf.saxon.query.XQueryExpression so we need to continue to use reflection;
rem &jExp.run(CreateJavaObject("net.sf.saxon.query.DynamicQueryContext", &jConfig), &jStreamResult, &jOutputProperties);
Local JavaObject &jExpressionRunMethod = &jExp.getClass().getDeclaredMethod("run", CreateJavaObject("java.lang.Class[]", &jDynamicContext.getClass(), &jClass.forName("javax.xml.transform.Result"), &jOutputProperties.getClass()));
&jExpressionRunMethod.invoke(&jExp, CreateJavaObject("java.lang.Object[]", &jDynamicContext, &jStreamResult, &jOutputProperties));

Return &jResultWriter.toString();
End-Function;

Function IScript_ExecXQuery()
%Response.Write(ExecXQuery(GetHTMLText(HTML.RSS_XQ, "http://www.nytimes.com/services/xml/rss/nyt/Business.xml")));
End-Function;

As you can see, I wrapped the XQuery transformation in a function called ExecXQuery. The Saxon XQuery classes use overloads that can't be interpreted by the PeopleCode interpreter. To work around this, I had to use some Java reflection. Chris Heller did an excellent job of explaining Java reflection and PeopleCode in his post Java and PeopleCode Tips and Tricks - Part 2.

In this PeopleCode example, I've only scratched the surface of what you can do with Saxon and XQuery. Saxon includes methods for running queries to return lists, methods for dynamically setting input documents, etc. I'll let you investigate the power of Saxon. If you just want to execute xqueries as described in this post, then I suggest you add the ExecXQuery function to a FUNCLIB. Once you have the function in a FUNCLIB, you can call it from Integration Broker PeopleCode transformations. Likewise, you can use it to create a custom Pagelet Wizard data source or transformer. Using a delivered data source like HTML, you could place an XQuery in the HTML text box, and then apply an XQuery display type to that HTML data source to execute the XQuery. Creating a Pagelet Wizard XQuery transformer would allow you to execute XQueries against data sources like content management, news publications, integration broker, PeopleSoft queries, HTML, etc. Unlike an XSL transform, using XQuery, you could merge content from a news publication with other online documents.

If you plan to use other Saxon features, then you may want to create an app package with app classes to encapsulate PeopleCode interfaces to Saxon's XQuery Java API.

Friday, May 23, 2008

AppEngine Output Tricks, Reporting, Logging, Etc

Reporting

When I took the AppEngine course several years ago, my instructor made sure his students knew that AppEngine was a batch processing tool, not a reporting tool, unlike SQR, which could do both. At that time, PeopleSoft offered Crystal Reports, PS/nVision, PS Query, and SQR as reporting options, and he encouraged us to use those tools for reporting. AppEngine was strictly labeled a batch processing tool. While debugging some jobs containing AppEngines (dunning letters, training letters, etc), I noticed that these AppEngines created and/or read files from the process output directory. Looking at the process monitor, I knew that those same files were available from the View Log/Trace link on the process details page. This got me thinking... if I could create a Microsoft Word file from AppEngine, I could place it in that process output directory and not have to run the WINWORD process on a headless server. Now, the trick, creating a Microsoft Word file from an AppEngine... Here are a couple of options

  • Word HTML format
  • Word XML format
  • RTF

By using various methods, I can convert my mail merge source data into XML format and transform it into either of these three Microsoft Word recognized formats using XSL. Of course, as of PeopleTools 8.48, we can use XMLPublisher to generate the same result. Nevertheless, if you need to process your data prior to generating a report, then a multi-step AppEngine reporting solution might be easier for you to manage than a multi-step job.

The same options are available for creating Microsoft Excel and OpenOffice documents. If you want to create Microsoft Excel binary files from AppEngine, then you can use Apache's POI Java libraries from PeopleCode. If you are interested in creating OpenOffice documents, you can generate the appropriate XML files, and then use Java to zip them into a single file. In fact, you could use this same approach to generate OpenOffice Impress presentations or Microsoft PowerPoint 2007 presentations.

For reporting, why choose AppEngine over SQR? AppEngine components (PeopleCode, SQL, etc) are managed objects. PeopleTools managed objects participate in the change management features available in PeopleTools. SQR text files do not.

Can I create a PDF from an AppEngine? Yes. Using an XSL-FO processor, you can trasform XML into PDF using a user defined XSL template. Likewise, you can use one of the PDF Java libraries to print text to a PDF file using PeopleCode similar to the way you would print output to a PDF in SQR, but with rich text features. Other reporting options: Any reporting/output tool that has a Java API can be called from AppEngine PeopleCode. For example, JasperReports, BIRT, JFreeReport, FOP, etc.

File Output Location

Suppose I want to create a file (printable report, log file, etc), where should I create the file? If you want the file available from the View Log/Trace link, then use the following SQL to determine the process's output directory:

SELECT PRCSOUTPUTDIR FROM PSPRCSPARMS WHERE PRCSINSTANCE = %ProcessInstance

Logging

I've already mentioned using log4j from PeopleCode. You can read about that in my posts: Logging PeopleCode Using log4j to debug applications and log4j and PeopleCode Part II. Other options include the Peoplecode MessageBox function, the PeopleCode File object, and the Java System.out/System.err methods. I prefer the Java System output methods over the PeopleCode MessageBox function because Java gives me complete control over the output. Unfortunately, you can't call the Java System output methods directly because the PrintStream output methods are overloaded. Instead, we need to use reflection to call the print methods. Here are some functions you can place in a FUNCLIB that allow you to print to stdout and stderr from PeopleCode:

/*
* Print a line of text to stdout
*/
Function println_to_stdout(&message As string)
Local JavaObject &jSystem = GetJavaClass("java.lang.System");
Local JavaObject &jOutStream = &jSystem.out;
Local JavaObject &jCls = GetJavaClass("java.lang.Class");
Local JavaObject &jStringClass = &jCls.forName("java.lang.String");
Local JavaObject &jPrintStreamCls = &jOutStream.getClass();
Local JavaObject &jPrintlnArgTypes = CreateJavaObject("java.lang.Class[]", &jStringClass);

Local JavaObject &jPrintlnMethod = &jPrintStreamCls.getDeclaredMethod("println", &jPrintlnArgTypes);

&jPrintlnMethod.invoke(&jOutStream, CreateJavaObject("java.lang.Object[]", &message));
rem ** I didn't find flushing necessary, but here is where you would flush the buffer if desired;
rem &jOutStream.flush();
End-Function;

/*
* Print a line of text to stderr
*/
Function println_to_stderr(&message As string)
Local JavaObject &jSystem = GetJavaClass("java.lang.System");
Local JavaObject &jOutStream = &jSystem.err;
Local JavaObject &jCls = GetJavaClass("java.lang.Class");
Local JavaObject &jStringClass = &jCls.forName("java.lang.String");
Local JavaObject &jPrintStreamCls = &jOutStream.getClass();
Local JavaObject &jPrintlnArgTypes = CreateJavaObject("java.lang.Class[]", &jStringClass);

Local JavaObject &jPrintlnMethod = &jPrintStreamCls.getDeclaredMethod("println", &jPrintlnArgTypes);

&jPrintlnMethod.invoke(&jOutStream, CreateJavaObject("java.lang.Object[]", &message));
rem ** I didn't find flushing necessary, but here is where you would flush the buffer if desired;
rem &jOutStream.flush();
End-Function;

If you want to use the PrintStream.print method instead of the println method, copy the code above, rename the function, and change the &jPrintlnMethod assignment from "println" to "print".

If you've worked with Java, then you know that you can redirect stdout and stderr to another PrintStream. For example, you can redirect stdout to a file or a network socket connection. Here is some code demonstrating how to redirect stdout and stderr to a different file:

/*
* Redirect stdout to file
*/
Function redirect_stdout(&fileName as string)
Local JavaObject &jSystem = GetJavaClass("java.lang.System");
Local JavaObject &jfos_out = CreateJavaObject("java.io.FileOutputStream", &fileName, True);
Local JavaObject &jps_out = CreateJavaObject("java.io.PrintStream", &jfos_out, True);
&jSystem.setOut(&jps_out);
End-Function;

/*
* Redirect stderr to file
*/
Function redirect_stderr(&fileName as string)
Local JavaObject &jSystem = GetJavaClass("java.lang.System");
Local JavaObject &jfos_out = CreateJavaObject("java.io.FileOutputStream", &fileName, True);
Local JavaObject &jps_out = CreateJavaObject("java.io.PrintStream", &jfos_out, True);
&jSystem.setErr(&jps_out);
End-Function;

By redirecting stdout and stderr, you could actually create 3 separate output files without using the File object. The benefit of using a redirected stdout over a File object is that you can setup your stdout location in one step of your program and write to that same file from anywhere else in the program without having to open/close a File object on every step.

The App Server

Just a side note: Many of the techniques demonstrated in this post can be used online. Using System.out.println, you could print to the app server's stdout file. Likewise, the reporting solutions above could be used from an online PeopleCode event to generate reports online.