Showing posts with label JSON. Show all posts
Showing posts with label JSON. Show all posts

Monday, September 08, 2025

Parsing JSON Arrays with PeopleCode

PeopleCode includes built-in support for parsing JSON through a native object called the JsonParser. Let's review a couple of JSON strings and then see how the JsonParser interprets them. First, let's review a JsonObject:

{
  "course": "PT1",
  "description": "PeopleTools",
  "courseType": "T",
  "duration": 4
}

Assuming that the JSON text is in the string variable &jsonStr, then we might parse it with code similar to:

Local JsonParser &p = CreateJsonParser();

If (&p.Parse(&jsonStr)) Then
  REM ** Woo Hoo! It parsed!;
End-If;

Parsing means the JsonParser created an in-memory structure. To leverage the JSON, we need to access that in-memory structure. The JsonParser includes one documented method to access the JSON Structure: GetRootObject(). The GetRootObject method returns a JsonObject. According to the documentation, the following PeopleCode should give us the value of the description attribute from the JSON above:

Local JsonObject &course = &p.GetRootObject();
Local string &descr; = &course.GetString("description");

What about a JSON Array? Here is my concern: There is no GetRootArray method. What if the "root object" is not an "object," but an Array? Here is what a valid JSON Array might look like:

[{
  "course": "PT1",
  "description": "PeopleTools",
  "courseType": "T",
  "duration": 4
}, {
  "course": "PC",
  "description": "PeopleCode",
  "courseType": "T",
  "duration": 5
}]

We would parse it using the same code as above. But how would you access the Array? Since the Parser has a GetRootObject method, let's invoke it, and then ToString the root object to see its JSON output. Here is the PeopleCode:

MessageBox(0, "", 0, 0, "%1", &p.GetRootObject().ToString());

... and here is the JSON:

{[
    {
        "course": "PT1",
        "description": "PeopleTools",
        "courseType": "T",
        "duration": 4
    },
    {
        "course": "PC",
        "description": "PeopleCode",
        "courseType": "T",
        "duration": 5
    }
]}

Do you notice anything unusual about that JSON? Notice the extra curly braces ({}). The "root" object is a JSON object. But here is where it gets interesting. The printed JSON is not valid. An object must have an attribute. The array should be assigned to an attribute. Now, does this matter? I think Oracle is allowed to internally represent JSON any way they desire. You might say that what we did was unexpected. We asked PeopleSoft to print an internal representation, not a true, expected JSON Object. But my question is the same: How do you access the Array that is now inside the root object? Here is the answer:

&p.GetRootObject().GetJsonArray("");

The GetJsonArray method expects an attribute name. We don't have one. So we don't give it one. Just use a zero-length string.

Want hands-on experience with REST services and PeopleCode parsing techniques? Join us on Tuesday, September 23, for two hands-on, live virtual workshops focused on integration! Details for the entire September series are available online.

We teach PeopleTools tips like this every week. Check out our website to see what we are offering next. Prefer to learn at your own pace? Our entire catalog is available online.

Tuesday, May 06, 2025

Which Technology Would You Use for JSON?

We asked our LinkedIn audience:

Which Technology Would You Use to Process JSON?

And they replied:


These answers are fantastic! Based on performance testing and research, PeopleCode native JSON objects, such as JsonObject and JsonArray, perform much faster than the Documents module or external Java libraries. However, there are times when I might choose an alternative.

Java Libraries

PeopleCode leverages the Java Native Interface to provide bidirectional access to Java. Java classes can interact with PeopleCode functions and objects, and vice versa. In addition to the delivered Java API, PeopleSoft provides several key Java libraries, including the Jakarta JSON library. Jakarta stream processing enables us to process enormous datasets. This stands in contrast to native DOM-based JSON parsing, which is limited by the amount of system memory available.

Documents

A document structure may generate either XML or JSON, which might simplify solution development when creating APIs that support multiple output types. Likewise, a document structure is required when creating a REST URI template.

Additional Resources


How about you? What technology would you choose? What are some reasons you might choose an alternative to the native JsonObject and JsonArray?

At JSMpros, we teach JSON processing techniques regularly through our Integration Tools Update course. Check out our online schedule to see what we are offering next! Alternatively, subscribe to gain access to all of our on-demand courses!

Monday, April 21, 2025

Consuming Enormous Datasets with PeopleSoft

Integration Broker is built on the DOM concept, which means it parses data into an in-memory structured document before transforming, processing, or transmitting it. This is fantastic for small and incremental integrations designed to keep two systems synchronized on a per-transaction basis. However, it fails when processing datasets that exceed the amount of available system memory.

An alternative is to use a SAX or stream-based parser that emits events. This type of parser only consumes the memory necessary to emit the next event. Stream-based parsers are highly efficient for one-way, one-time reads through large datasets. Unfortunately, Integration Broker does not support stream-based processing.

While reviewing PeopleSoft's Java class path, I noticed the Jakarta JSON stream-processing Java library. You can find a simple PeopleCode example of processing a JSON file using Jakarta JSON streams in our blog post JSON Stream Processing with PeopleCode. Now that we know how to use Jakarta's stream processing, the next challenge is reading an input stream from an external service. Since Integration Broker does not support streams, we need alternatives. Here are a few Java-based alternatives that readily integrate with PeopleCode through delivered APIs:

  • Java Sockets
  • Java HttpURLConnection
  • Apache HttpClient

Although PeopleCode offers incredible support for Java, the real challenge lies with method and constructor overloading. PeopleCode identifies target Java methods and constructors through parameter count, not parameter type. Java overloading doesn't play well with PeopleCode. The solution I use to overcome method overloading is to leverage Java's support for JavaScript as a translation layer or a "glue." PeopleSoft exposes all functions and objects to Java. Therefore, all PeopleCode functions and objects are also available to JavaScript.

The following is a sample JavaScript that can run from PeopleCode to stream load data into a PeopleSoft table. It uses Jakarta for stream processing and Apache HttpClient to connect to the external service. Both of these external libraries are included with PeopleTools. Notice the use of PeopleCode functions, such as CreateSQL, as well as Java objects such as HttpGet.

I use PeopleCode similar to the following to run JavaScript from PeopleCode. You can find several examples of using Java's ScriptEngineManager on our blog.

Local JavaObject &manager = CreateJavaObject("javax.script.ScriptEngineManager");
Local JavaObject &engine = &manager.getEngineByName("JavaScript");
Local String &script = "JavaScript goes here";
Local Any &result;

&engine.eval(&script);

&result = &engine.get("result");

At JSMpros, we teach PeopleTools and PeopleCode tips like this in every class. Check out our online schedule to see what we're offering next. Would you prefer to learn at your own pace? Purchase a subscription to access all of our on-demand content at discounted rates!

Thursday, January 18, 2024

Generating LARGE JSON Files

The PeopleCode native JsonObject and JsonArray classes allow us to create JSON structures as in-memory representations. But what if you need to generate a really LARGE JSON structure? An in-memory JSON Array may consume more memory than you can reasonably allow. Fortunately, PeopleTools includes the Jakarta JSON library, which allows us to write a JSON structure to a stream during construction.

The following code snippet demonstrates creating 10 million JSON objects in an array without any change in memory consumption. The generated file was 2.5 GB in size, but my memory utilization didn't change the entire time the program ran.

Local JavaObject &Json = GetJavaClass("jakarta.json.Json");
Local JavaObject &writer = CreateJavaObject("java.io.FileWriter", "C:\temp\users-big.json");

Local JavaObject &gen = &Json.createGenerator(&writer);

Local number &iteration = 1;

REM ** 10 million iterations;
Local number &maxIterations = 10000000;

&gen.writeStartArray();

For &iteration = 1 To &maxIterations
   
   REM ** start person/user object;
   &gen.writeStartObject();
   &gen.write("id", "" | &iteration);
   &gen.write("firstName", "John");
   &gen.write("lastName", "Smith");
   
   REM ** start child address object;
   &gen.writeStartObject("address");
   &gen.write("streetAddress", "21 2nd Street");
   &gen.write("city", "New York");
   &gen.write("state", "NY");
   &gen.write("postalCode", "10021");
   &gen.writeEnd();
   
   REM ** start phone number array;
   &gen.writeStartArray("phoneNumber");
   
   REM ** start home phone object;
   &gen.writeStartObject();
   &gen.write("type", "home");
   &gen.write("number", "212 555-1234");
   &gen.writeEnd();
   
   REM ** start fax number object;
   &gen.writeStartObject();
   &gen.write("type", "fax");
   &gen.write("number", "646 555-4567");
   &gen.writeEnd();
   
   REM ** end array of phone numbers;
   &gen.writeEnd();
   
   REM ** end person/user object;
   &gen.writeEnd();
End-For;


REM ** end array;
&gen.writeEnd();

REM ** cleanup to flush buffers;
&gen.close();
&writer.close();

The hard-coded values come directly from the Jakarta generator API documentation. In real life, you would replace these values with database data. I converted numbers to strings to simplify the example to avoid Java Reflection.

Are you interested in parsing rather than generating large JSON files? Check out our post on JSON Stream Parsing.

We teach PeopleTools and PeopleCode tips like this every week! Check out our upcoming course schedule to see what we are offering next! We would love to have you join us. Want to learn at your own pace? Check out our subscriptions and on-demand offerings as well. Or do you have a group you would like to train? Contact us for group and quantity discounts.

Monday, December 18, 2023

JSON Stream Processing with PeopleCode

Our Integration Tools courses emphasize the importance of utilizing PeopleSoft's native JSON support to parse and process JSON. This feature provides excellent functionality for most scenarios, as PeopleSoft's native JsonObject and JsonArray offer fast and efficient JSON processing. They are particularly useful for generating small integration responses or handling tasks like JWT (JSON Web Token) generation.

It's important to note that PeopleSoft's native JSON definitions employ a DOM-based parser, which builds an in-memory JSON structure. While this is effective in many cases, it does require your server to have enough memory to accommodate the entire JSON document without impacting its normal workload. Large files, therefore, can be problematic.

Stream-based parsing provides an alternative approach. With a stream-based parser, events are emitted as they occur, enabling immediate processing of data identified by these events. Once processed, the parser discards the data and proceeds to the next event. For instance, let's consider a scenario where you have a massive array of users. A DOM parser would load the entire array into memory, whereas a stream-based parser would only load one user at a time, allowing you to process the user and then discard it. Stream-based parsers are often more efficient in terms of resource utilization, as they process and discard data right away instead of constructing a traversable in-memory document.

Although PeopleCode itself doesn't include a stream-based parser, it does include the Java Jakarta stream-based JSON parser, which we can leverage through PeopleSoft's built-in Java support. Let me share an example with you. For testing purposes, I obtained a sample JSON file from https://jsonplaceholder.typicode.com/users. This file is small and perfect for testing. I then incorporated the following PeopleCode into an App Engine step, which you can run locally through App Designer:

REM ** JavaDoc: https://jakarta.ee/specifications/jsonp/2.0/apidocs/jakarta.json/jakarta/json/stream/jsonparser;
REM ** Data: https://jsonplaceholder.typicode.com/users;
Local JavaObject &Json = GetJavaClass("jakarta.json.Json");
Local JavaObject &reader = CreateJavaObject("java.io.FileReader", "C:\temp\users.json");
Local JavaObject &parser = &Json.createParser(&reader);

Local string &email;

While (&parser.hasNext())
   Local JavaObject &next = &parser.next();
   
   If (&next.equals(&next.START_OBJECT)) Then
      
      Local JavaObject &user = &parser.getObject();
      
      REM ** do something with the object;
      &email = &user.getString("email");
      MessageBox(0, "", 0, 0, "email: %1", &email);
   End-If;
End-While;

Here is some sample output generated by this short App Engine program:

email: Sincere@april.biz (0,0)
 Message Set Number: 0
 Message Number: 0
 Message Reason: email: Sincere@april.biz (0,0) (0,0)

email: Shanna@melissa.tv (0,0)
 Message Set Number: 0
 Message Number: 0
 Message Reason: email: Shanna@melissa.tv (0,0) (0,0)

email: Nathan@yesenia.net (0,0)
 Message Set Number: 0
 Message Number: 0
 Message Reason: email: Nathan@yesenia.net (0,0) (0,0)

email: Julianne.OConner@kory.org (0,0)
 Message Set Number: 0
 Message Number: 0
 Message Reason: email: Julianne.OConner@kory.org (0,0) (0,0)

Are you interested in learning more about PeopleSoft Integration, PeopleTools, Or PeopleCode? If so, check out our subscriptions, on-demand, and upcoming course schedule. We would love to have you join us!

Sunday, May 07, 2023

Eliminate Extra Spacing in JSON

PeopleCode's JsonObject and JsonArray are incredibly important to building a modern PeopleSoft integration, and we teach our students how to leverage them directly. Both have a ToString method that converts the internal object into a String perfect for integration. But the ToString method returns pretty-printed, nicely formatted, human-readable JSON output. This is fantastic for debugging and development! But it wastes valuable bandwidth and disk space with unnecessary characters. Computers ignore those extra spaces. Here is a trick we teach our students to help them reduce their JSON Payloads.

REM ** compress extra space;
Local JsonGenerator &jgen = CreateJsonGenerator();
Local JsonNode &root = CreateJsonNode();
Local string &json;

&root.SetJsonObject(&someJsonObject);
&jgen.SetRootNode(&root);
&jgen.SetPrettyMode( False);
&json = &jgen.ToString();

Got a JSON tip to share? Leave it in the comments. We would love to hear it!

At jsmpros, we teach Integration Tricks like this regularly. Be sure to check our schedule to see what we are offering next! Want to learn more, but at your own pace and in your own time? Check out our on-demand Integration Tools course and All Access Pass!

Tuesday, March 26, 2019

Documented JSON Classes

Looking through the PeopleTools 8.57 Feature Overview document, you may have noticed that 8.57 now includes support for several JSON classes as well as PeopleBooks documentation. As Chris Malek showed us a couple of years ago, the classes listed in the Feature Overview document are not new. What is new is the keyword Support and PeopleBooks documentation. Using the documentation, I was able to generate a sample on PeopleTools 8.57:

Local JsonBuilder &jbldr = CreateJsonBuilder();
Local string &json;

If &jbldr.StartArrayReturnsTrue("Employees") Then
   REM Empl 1;
   If &jbldr.StartObjectReturnsTrue("Employee") Then
      If &jbldr.StartObjectReturnsTrue("Name") Then
         &jbldr.AddProperty("First", "Jim");
         &jbldr.AddProperty("Last", "Marion");
         &jbldr.AddProperty("Middle", "J");
         &jbldr.EndObject("Name");
      End-If;
      &jbldr.AddProperty("ID", 123456);
      &jbldr.EndObject("Employee");
   End-If;
   
   REM Empl 2;
   If &jbldr.StartObjectReturnsTrue("Employee") Then
      If &jbldr.StartObjectReturnsTrue("Name") Then
         &jbldr.AddProperty("First", "Lucy");
         &jbldr.AddProperty("Last", "McGillicuddy");
         &jbldr.AddProperty("Middle", "");
         &jbldr.EndObject("Name");
      End-If;
      &jbldr.AddProperty("ID", 789123);
      &jbldr.EndObject("Employee");
   End-If;
   &jbldr.EndArray("Employees");
End-If;

&json = &jbldr.ToString();

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

Alternatively, we can build JSON structures using JsonObject and JsonArray directly, but I like the way the JsonBuilder structures code so that child items appear indented, etc. Notice the code above begins the JSON structure with an array? Here is the output. Notice the root node is an object, not an Array:


Even though my very first call to JsonBuilder was to start an Array, it started an Object. What if you just want an array as the outer node? We can extract the array from the JsonBuilder RootNode using the following:

&jbldr.GetRootNode().GetJsonObject().GetJsonArray("Employees");
What if we want to format the code? First, I don't recommend formatting code you will transmit to external systems as white-space compressed JSON is preferred for data transmission. But formatting for debugging purposes is perfect. We can format JsonBuilder output using the JsonGenerator class. Here is a fragment that will format the JsonBuilder result:
Local JsonGenerator &jgen = CreateJsonGenerator();
&jgen.SetRootNode(&jbldr.GetRootNode());
&json = &jgen.ToString();

One thing to note is that JsonBuilder will let you generate invalid JSON. The parameter to StartXxxReturnsTrue is the name of the node to create. If we start the first node with a zero-length string: &jbldr.StartArrayReturnsTrue(""), then the generated JSON will include curly brace object notation, but no property name before the Array start.

As I look through the documentation for 8.57, I see every Json class method and property documented, but what about the CreateJsonXxx functions? Anyone find documentation for these functions? Did I miss something?

As Chris pointed out, these JSON Classes have been in PeopleTools since 8.55.11. Assuming that just the documentation is new and not the classes, I ran all of this code on 8.56 and it works without modification.

At jsmpros, we teach JSON strategies through our Integration Broker and PeopleTools Delta courses. Are you interested in learning more? Contact us to schedule your next PeopleTools training session.

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.

Sunday, October 23, 2011

REST-like PeopleSoft Services

As you survey the consumer web service landscape, you will notice a shift: fewer SOAP based services and more REST based services. I will refrain from sharing my true feelings about WSDL and SOAP to share with you the important stuff: how you can make REST-like calls into PeopleSoft. If you are not familiar with REST, then I suggest you read the Wikipedia REpresentational State Tranfer summary and then follow some of the external links for additional details.

While there are implementation differences, at its core, the difference between REST and SOAP is the focus. The focus of SOAP is the operation, not the data. The focus of REST is the data. I find this difference most evident when working with a Component Interface (CI). With a CI, you set key values, call Get (or Create), change values, and then call Save. The entire time you are working with that CI, you are working with a single transaction instance. The focus of the CI is the state of the data. The operations (get, create, save) are secondary. Service Operations are exactly opposite. Service Operations focus on method execution. The data (the transaction in this case) is just a parameter. OK, maybe this isn't the "core" of the REST specification, but as one who has tried working with a CI in a Web Service Data Control, it is enough for me to want to throw out web services. Don't misunderstand me at this point. I'm not blaming web services, the CI WSDL, or the Web Service Data Control. I'm sure they all have their place in development projects. It is my experience, however, that they mix together like chlorine bleach and ammonia (please, oh please don't mix these two chemicals!).

There are several implementation details that differ between REST and SOAP. As a user interface (think Ajax) developer, my preferred implementation detail is the ability to call services with a URL as an HTTP GET or POST. Yes, you can make SOAP calls with JavaScript, but I find it a lot more difficult to package up a SOAP envelope with JavaScript than to just make an HTTP GET or POST with jQuery.

As noted by the Cedar Hills Group PeopleSoft REST Wiki, there is a lot more to REST than just URL's, and a true REST URL doesn't use Query Strings for parameters. If you want more REST, then you will have to wait for PeopleTools 8.52 or build something yourself (stand-alone REST gateway, MyRestListeningConnector, etc). If, like me, your greatest interest is executing Service Operation Handlers from URL's, then review the PeopleBooks HTTP Listening Connector. It contains the URL call specification for PeopleSoft service operations. With an "Any to Local" routing, the basic form looks like this: http(s)://my.peoplesoft.server/PSIGW/HttpListeningConnector?Operation=EXECTHISOPERATION. If you prefer, you can pass transaction keys, etc as query string parameters, and then read those parameters in PeopleCode. Here is how (assuming &MSG is the message parameter to your OnRequest handler):

   Local &connectorInfo = &MSG.IBInfo.IBConnectorInfo;
   Local number &qsIndex = 0;
   Local string &qsValue;
   
   For &qsIndex = 1 To &connectorInfo.GetNumberOfQueryStringArgs()
      If (&connectorInfo.GetQueryStringArgName(&qsIndex) = "THE_QS_PARM_NAME") Then
         &qsValue = &connectorInfo.GetQueryStringArgValue(&qsIndex);
      End-If;
   End-For;

No, I'm not fond of having to iterate over each query string argument either, but that is what the API requires. I packaged this up in a Query String helper class and create an instance of it for each request that uses query string arguments. Here is my Helper class:

class IBQueryStringHelper
   method IBQueryStringHelper(&connectorInfo As IBConnectorInfo);
   method getParameterValue(&parameterName As string) Returns string;
   
private
   instance IBConnectorInfo &m_connectorInfo;
end-class;

method IBQueryStringHelper
   /+ &connectorInfo as IBConnectorInfo +/
   %This.m_connectorInfo = &connectorInfo;
end-method;

method getParameterValue
   /+ &parameterName as String +/
   /+ Returns String +/
   Local number &qsIndex = 0;
   
   For &qsIndex = 1 To &m_connectorInfo.GetNumberOfQueryStringArgs()
      If (&m_connectorInfo.GetQueryStringArgName(&qsIndex) = &parameterName) Then
         Return &m_connectorInfo.GetQueryStringArgValue(&qsIndex);
      End-If;
   End-For;
   Return "";
end-method;

What about the result? Does it have to be XML? No. I have used two ways to create non-XML results from Integration Broker. The first is by creating a JSON response directly in PeopleCode. It is this use case that prompted me to write the PeopleCode JSONEncoder. A service operation handler can return non-XML by wrapping the result in a psnonxml attribute like this:

   Local Message &result_msg = CreateMessage(Operation.MY_SERVICE_OPERATION, %IntBroker_Response);
   Local string &json;
   
   REM ** Do some processing to generate a json response;
   
   Local string &nonXmlData = "<?xml version=""1.0""?><data psnonxml=""yes""><![CDATA[" | &json | "]]></data>";
   Local XmlDoc &doc = CreateXmlDoc(&nonXmlData);
   
   &result_msg.SetXmlDoc(&doc);
   Return &result_msg;

The second method I use to create non-XML results is through a transformation. Using XSL, it is possible to transform an XML document into JSON -- although JSON-safe encoding might be more difficult.

If you use a debugging proxy (such as Fiddler) to inspect the results of an Integration Broker response, you will notice Integration Broker always returns the Content-Type header value text/xml. Unfortunately, this means you have to help jQuery understand the results because it won't be able to determine the response type based on the Content-Type header. When PeopleTools 8.52 arrives at your office, you will be able to specify different MIME types. For now, I find it satisfactory to just set the $.ajax dataType parameter to "json." If you absolutely need to set the Content-Type header and don't have PeopleTools 8.52, then I suggest looking into a reverse proxy with header rewrite capabilities (Apache, for example).

No, unfortunately, this post didn't show you true REST. If you are choosing REST for Ajax because it is easier to make a URL based request to a REST service than to build a SOAP header to send to a Web Service (like me), then this post hopefully offers you enough information to get started. If you require more of the REST specification than I've shown here, then you will probably have to wait for PeopleTools 8.52.

Thursday, September 23, 2010

Going Mobile with PeopleSoft

Chapter 14 of my PeopleTools Tips & Techniques book walks you step-by-step through creating a mobile application. That chapter uses a CI based web service and Oracle ADF to demonstrate some of the simple drag-and-drop tools provided by Oracle. Even though the technique demonstrated appears simple, if you start to dig into the generated code and try to work directly with the Web Service Data Control, you will quickly see that JDeveloper does a very, very good job of hiding the real complexities behind web services. For this year's OpenWorld, I wanted to show just how simple it could be to create a mobile app for PeopleSoft. For my prototype, I chose to build a mobile worklist out of plain HTML, JavaScript, and CSS (it seems to me that plain HTML, JavaScript, and CSS is about as simple as web development gets). Without a server side technology like JDeveloper's ADF and JSF, I knew my mobile app would have to communicate with PeopleSoft using Ajax. As it turns out, most modern mobile browsers support XHR (as of BlackBerry 6, Torch, the BlackBerry browser is now WebKit - YEAH!!!), but I knew having a good mobile JavaScript library like jQuery would certainly help. A quick google search turned up xuijs, which happens to be modeled after jQuery. Using jEdit, my favorite syntax highlighting text editor, I prototyped the user interface, substituting Ajax URL's for local text files. After ironing out the server side requirements, I set about creating the Integration Broker App Class synchronous request handlers that my app would require. To make my HTML and JavaScript as simple as possible, I wrote my handlers to return data in JSON and JSONP format. While my JavaScript and PeopleCode may prove to be of some interest to you, I believe the most important concept from this exercise is the mechanism for calling Integration Broker from Ajax. To execute a web service from an HTTP GET (basic Ajax in REST-like fashion), you use a URL similar to:

http://your.peoplesoft.server/PSIGW/HttpListeningConnector?Operation=YOUR_OPERATION_NAME.v1&OperationType=Sync

Calling any service operation implies, of course, that you have a message, service, service operation, handler, and an any-to-local routing.

My point for sharing this is that we easily forget how simple an application can be. PeopleTools provides the integration architecture. It is up to us to pick a language we are comfortable developing with. If your organization prefers .Net over Java, then write your web based mobile app in .Net. The language doesn't matter. Pretty much any language can make an HTTP request to the Integration Broker and then process the response. The keys are:

  • Knowing how to call Integration Broker
  • Remembering that the mobile device has a much smaller screen

That is about all there is to building mobile applications. Pretty simple... right?

Wednesday, April 14, 2010

JSON Encoding in PeopleCode

I am a big fan of the JSON.simple Java library. JSON.simple integrates well with PeopleCode. It produces flawless JSON without ugly PeopleCode Java Reflection and is compatible with Java 1.2 (for older tools versions). Yes, the object/array to JSON conversion in JSON.simple is nice, but my real reason for using a JSON library is JSON encoding. I can mock up and string together variable values to produce JSON, but my main problem is escaping strings so that they represent safe JSON data (quotes, etc). I thought the PeopleCode EscapeJavascriptString function would handle this for me, but I discovered that JSON != JavaScript. Certain character sequences, such as \' are valid for JavaScript, but invalid for JSON. After my latest tools and app upgrade, I decided to see what it would take to encode strings for JSON from PeopleCode. Here is what I created:

class JSONEncoder
method encode(&input As string) Returns string;

private
instance JavaObject &meta_chars_;
instance JavaObject &unsafe_chars_pattern_;
instance JavaObject &int_;

method init();
end-class;

method encode
/+ &input as String +/
/+ Returns String +/

Local JavaObject &matcher;
Local string &output = &input;
Local string &replacement;
Local string &match;
Local number &offset = 1;

REM ** Run lazy init if needed;
REM ** Protects against stateless PeopleCode/Stateful JVM;
%This.init();

&matcher = &unsafe_chars_pattern_.matcher(CreateJavaObject("java.lang.String", &input));

While &matcher.find()
&match = &matcher.group();

If (&meta_chars_.containsKey(&match)) Then
REM ** replace meta characters first;
&replacement = &meta_chars_.get(&match).toString();
Else
REM ** not meta, so convert to a unicode escape sequence;
&replacement = "\u" | Right("0000" | &int_.toHexString(Code(&match)), 4);
End-If;
&output = Replace(&output, &matcher.start() + &offset, (&matcher.end() - &matcher.start()), &replacement);

REM ** move the starting position based on the size of the string after replacement;
&offset = &offset + Len(&replacement) - (&matcher.end() - &matcher.start());
End-While;

Return &output;
end-method;

method init
REM ** None only works on local vars, so get a pointer;
Local JavaObject &int = &int_;

REM ** if &int has no value, then initialize all JavaObject vars;
/*
* JavaObject vars will have no value in two scenarios:
*
* 1. First use, never initialized
* 2. Think time function, global variable, anything that causes state
* serialization.
*
* The first case is obvious. The second case, however, is not. PeopleSoft
* allows you to make App Classes Global and Component scoped objects, but
* not JavaObject variables. By using JavaObject variables in Component and
* Global scope, you can get into a bit of trouble. Retesting these values
* on each use ensures they are always initialized. The same will happen if
* you use a think-time function like Prompt or a Yes/No/Cancel MessageBox.
*/
If (None(&int)) Then
REM ** Lazy initialize Integer class;
&int_ = GetJavaClass("java.lang.Integer");

REM ** Lazy initialize the regular expression;
REM ** List other unsafe characters;
&unsafe_chars_pattern_ = GetJavaClass("java.util.regex.Pattern").compile("[\\""\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]");

REM ** Lazy initialize the hashtable;
&meta_chars_ = CreateJavaObject("java.util.Hashtable");

REM ** setup meta characters;
&meta_chars_.put(Char(8), "\b");
&meta_chars_.put(Char(9), "\t");
&meta_chars_.put(Char(10), "\n");
&meta_chars_.put(Char(12), "\f");
&meta_chars_.put(Char(13), "\r");
&meta_chars_.put("\", "\\");
&meta_chars_.put("""", "\""");
End-If;

end-method;

I adapted this code from the JavaScript quote function in the json.org JSON2 JavaScript parser. Yes, this solution does still use Java (regular expressions and hexadecimal encoding), but it doesn't require external libraries. See, my real motivation was to eliminate external dependencies. I wanted code I could compile and leave in the database; code that didn't require OS file system modifications; code that would upgrade without impacting PS_HOME, psappsrv.cfg, psconfig.sh, or any other upgraded configuration file.

Why an App Class instead of a FUNCLIB? I originally wrote this code as a FUNCLIB function. Step one of the function would populate the hashtable. This meant for each function call, I would incur the overhead of creating and populating the hashtable. Since I know I will call this function multiple times while constructing a JSON string, I wanted a mechanism to persist the hashtable across function calls. An App Class's private instance variable provides this mechanism. What about Global variables? First, I have NEVER used them. Second, you CAN'T use them with variables of type JavaObject. What about serialization, scoping, and think-time functions with Java? I protect against the "First operand of . is Null" error by lazily initializing the hashtable and the regular expression. A postback will reset the JavaObject to Null, and my lazy initialization code will reinitialize it.

Tuesday, April 13, 2010

Hex Encoding Characters

Does anyone have a PeopleCode algorithm for hex encoding strings? I'm working on an escapeJSON function and would like to come up with a good way to convert unsafe characters to unicode. Here is what I've come up with, but I would like to hear other ideas:

Local JavaObject &int = GetJavaClass("java.lang.Integer");
Local string &unicode;

REM ** I hard coded the source character to A for this example;
&unicode = "\u" | Right("0000" | &int.toHexString(Code("A")), 4);

This converts "A" to \u0041. The actual Hex part is

GetJavaClass("java.lang.Integer").toHexString(Code("A"));

I don't think there is anything wrong with my solution. I am just wondering if I overlooked some PeopleCode function for displaying numbers in Hex.

Thursday, March 19, 2009

Serve JSON from PeopleSoft

Last month a reader asked me for an example of serving JSON from PeopleSoft. The following IScript demonstrates how to serve JSON by printing user and role information in JSON format:

Function IScript_GetJSON
Local SQL &usersCursor = CreateSQL("SELECT OPRID, OPRDEFNDESC, EMAILID FROM PSOPRDEFN WHERE ROWNUM < 6");
Local SQL &rolesCursor;
Local string &oprid;
Local string &oprdefndesc;
Local string &emailid;
Local string &rolename;

Local boolean &isFirstUser = True;
Local boolean &isFirstRole = True;

%Response.Write("[");
While &usersCursor.Fetch(&oprid, &oprdefndesc, &emailid)
REM ** comma logic;
If (&isFirstUser) Then
&isFirstUser = False;
Else
%Response.Write(", ");
End-If;

%Response.Write("{""OPRID"": """ | EscapeJavascriptString(&oprid) | """, ""OPRDEFNDESC"": """ | EscapeJavascriptString(&oprdefndesc) | """, ""EMAILID"": """ | EscapeJavascriptString(&emailid) | """, ""ROLES"": [");

&rolesCursor = CreateSQL("SELECT ROLENAME FROM PSROLEUSER WHERE ROLEUSER = :1 AND ROWNUM < 6", &oprid);
&isFirstRole = True;

While &rolesCursor.Fetch(&rolename);
REM ** comma logic;
If (&isFirstRole) Then
&isFirstRole = False;
Else
%Response.Write(", ");
End-If;

%Response.Write("""" | EscapeJavascriptString(&rolename) | """");
End-While;

&rolesCursor.Close();
%Response.Write("]}");
End-While;

%Response.Write("]");
&usersCursor.Close();

End-Function;

The code above uses embedded SQL. In production, be sure to use App Designer SQL definitions. This code listing also embeds JSON formatting strings. As an alternative, I recommend HTML definitions and HTML bind variables. In this manner, HTML definitions serve as templates for structured JSON data.

Formatted, the output from my demo database looks like:

[
{
"OPRID": "ADRIESSEN",
"OPRDEFNDESC": "Anton Driessen",
"EMAILID": "ADRIESSEN@server.com",
"ROLES": [
"All Processes",
"All Query Access Groups",
"EPM Scorecard Viewer",
"Portal User",
"Query Access - All FSCM"
]
},
{
"OPRID": "ADUPOND",
"OPRDEFNDESC": "Alain Dupond",
"EMAILID": "ADUPOND@server.com",
"ROLES": [
"All Processes",
"All Query Access Groups",
"EPM Scorecard Viewer",
"Portal User",
"Query Access - All FSCM"
]
},
{
"OPRID": "AEGLI",
"OPRDEFNDESC": "Anna Egli",
"EMAILID": "AEGLI@server.com",
"ROLES": [
"All Processes",
"All Query Access Groups",
"EPM Scorecard Viewer",
"Employee Global Payroll",
"Portal User"
]
},
{
"OPRID": "AERICKSON",
"OPRDEFNDESC": "Arthur Erickson",
"EMAILID": "AERICKSON@server.com",
"ROLES": [
"Accounts Payable Manager",
"All Processes",
"All Query Access Groups",
"Application Homepages",
"EP General Options"
]
},
{
"OPRID": "AFAIRCHILD",
"OPRDEFNDESC": "Alison Fairchild",
"EMAILID": "AFAIRCHILD@server.com",
"ROLES": [
"Applicant",
"All Processes",
"All Query Access Groups",
"EPM Scorecard Viewer",
"Employee ELM"
]
}
]

IScripts provide a secure free-form mechanism for serving data. This makes them perfect for serving JSON in response to a logged in user's AJAX request. If you want to serve PeopleSoft data in JSON format for consumption in a page outside PeopleSoft, then try using an Integration Broker synchronous message handler.

If you need help prototyping your JSON, take a look at the JSON homepage and JSONLint, an online JSON validator. I rely heavily on JSONLint when prototyping JSON.