Showing posts with label PeopleCode. Show all posts
Showing posts with label PeopleCode. 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!

Wednesday, February 26, 2025

Function Library or Application Class?

When we discover redundancies while writing code, the DRY principle (Don't Repeat Yourself) encourages us to refactor our code into reusable definitions. Reasons cited for writing DRY code include:

  • Code that is easier to maintain,
  • Code that scales better as system load increases, and
  • Reduced redundancies in process logic.

With PeopleCode, we have two reusable options:

  • Function Libraries
  • Application Classes

We asked our LinkedIn audience which option they would choose.


The poll results show that 76% would choose Application Classes! Wow! 76% will defer to Object-oriented programming. This is one of those questions that has no right or wrong answer. Both are effective. But is there a more correct answer? Is there a time when using one over the other is more appropriate?

Let's consider App Classes first. Every App Class involves object creation overhead, which means using objects has a cost. But there are two features App Classes contain that set them apart from Function Libraries:

  • State: App Classes have an internal state. App Classes have private, internal variables that persist beyond method invocation.
  • Dynamic Execution: At runtime, we can create objects, set properties, and invoke methods that didn't exist during design time. Function libraries can't do this.

If I know that my solution will require internal state or dynamic execution, I choose App Classes; function libraries are not an option.

Here are a few other reasons people choose Application Classes over Function Libraries:

  • To leverage dynamic testing frameworks such as PSUnit.
  • Extensible framework development through inheritance and composition.

Is there a place for function libraries in modern PeopleCode? This is an interesting question that I think Oracle answers for us. If you look through Oracle's Fluid-specific PeopleCode, you will see a lot of references to Fluid-specific Function Libraries. Why? If you don't need internal state or dynamic execution, then a Function Library may be the fastest, lightweight option.

Consider the following example (adapted from Base64 Encoding with Emoji):

/****
 * You may find a subset of supported character sets in the Javadoc:
 * https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html
 */
Function base64_encode(&textToEncode as string, &charSet as string) returns string
   Local JavaObject &encoder = GetJavaClass("java.util.Base64").getEncoder();
   Local JavaObject &bytes = CreateJavaObject("java.lang.String", &textToEncode).getBytes(&charSet);

   return &encoder.encodeToString(&bytes);
End-Function;

Does it make more sense to expose this reusable algorithm as a Function Library or Application Class method? What do you think? If you choose App Classes, why? If you prefer Function Libraries, we would love to hear your reasons!

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!

Tuesday, January 07, 2025

Five Ways to Consume a REST Service with PeopleSoft

PeopleSoft includes facilities for providing and consuming REST. A service provider is a listener. A provider waits for requests and responds accordingly. To provide web services, we must register metadata with Integration Broker. The process is pretty well fixed. Consumption, on the other hand, is much more flexible. Consuming a REST service means invoking (or calling) someone else's listener. The consumer initiates the conversation. In a consumption scenario, we might be sending data, receiving data, or both.

Here are five ways a PeopleSoft developer might invoke a REST service:

1. Metadata

This is the traditional PeopleSoft approach and follows the same pattern as providing services through Integration Broker. Here are a few of the metadata items a developer would create:

  • Documents,
  • Messages,
  • Service, and
  • Service Operation.
After creating and registering somewhat reusable metadata, the developer would then write PeopleCode to:
  • Create Document structures,
  • Invoke the service, and
  • Parse the response.

Even though this approach requires the most effort and offers the least reuse, it takes full advantage of Integration Broker's capabilities, including logging, security, etc.

Metadata, in general, offers the following benefits:

  • Reuse (ability to use the same definition multiple times),
  • Documentation (describes an object),
  • Reporting (query and analysis), and
  • Transformation (ability to upgrade and transform based on architecture changes).

Pure code solutions are much harder to query and transform. Metadata is what allowed PeopleSoft to switch from a Windows-based Client/Server solution to its Pure Internet Architecture in (mostly) one release. Metadata is what allowed Integration Broker to transform from an XML-focused solution into an SOA solution in one release (the 8.48 transform). If these benefits sound appealing, then metadata may be the right choice for you.

2. Application Services Framework

Created initially as a REST provider to support digital assistants, the Application Services Framework can now consume REST services. The Application Services Framework is a developer-friendly configuration layer tightly integrated with Integration Broker metadata. Rather than directly working with SOA-oriented metadata, the Application Services Framework allows developers to create solutions using REST terminology and REST patterns.

To use the Application Services Framework for consumption, a developer would:

  • Register the target service with the Application Services Framework, which includes describing the URL, parameters, headers, and payload (both request and response).
  • Write PeopleCode to invoke the endpoint service.
This is one of the most important chapters in our updated Integration Tools curriculum. You may find basic information (including code samples) on consuming a REST service in PeopleBooks.

3. %IntBroker.ConnectorRequestURL

If your request is a simple URL with no special headers (request or response headers) and any parameters may be passed through the URL, then this is the most straightforward approach:

Local string &response = %IntBroker.ConnectorRequestURL("http://rest.example.com/endpoint");

Unfortunately, this solution offers no logging, header control, or any typical Integration Broker or integration features. It is strictly for the simplest GET requests. Nevertheless, by using URL definitions, this alternative may be one of the better alternatives for cross-environment migrations.

4. Code-only

The challenge with the first option, Metadata, is that it requires creating, migrating, and maintaining a significant number of definitions that provide little (if any) value for REST. For example, the URL of a PeopleSoft REST consumer service becomes part of the metadata. If you have different targets between DEV, TEST, and PROD, then you must modify the metadata after every migration. Implementing a true PeopleSoft Metadata-focused solution (such as option 1) may require you to violate development best practices by making changes after each migration.

Since all approaches require PeopleCode, why not skip the Integration Broker metadata in favor of your own reusable best practice-based metadata? You may find an example of this approach in our blog post Simple Code-only REST Request. With this approach, you are responsible for managing URLs, Credentials, etc., but you may store them in a manner that allows for reuse and better migration management. For example, you might create URL definitions that point to different endpoints in DEV, TEST, and PROD.

It is important to note that this approach DOES use metadata, but it reuses generic PeopleTools-delivered definitions, so we don't have to create new definitions.

As mentioned earlier, code doesn't report or transform well, so this alternative may miss out on future automated transforms, such as the 8.48 automated transform. Likewise, integrations launched in this manner won't log details in the Synchronous Service Operation Monitor. Nevertheless, this approach is becoming one of our favorite alternatives to overly complicated metadata.

5. Java

Using Java allows a PeopleSoft developer to bypass Integration Broker altogether. We put this option last because it should be the choice of last resort. However, it is probably the most flexible option available, allowing for everything from simple low-level sockets to convenient libraries, such as Apache's HttpClient (now included with PeopleTools).

There are many reasons to choose this option, but here are the two most common:

  • Processing binary data: Integration Broker is pretty much an XML workhorse. Everything passing through Integration Broker is wrapped in XML at some point. Unfortunately, that might not work well with binary data. Therefore, skipping Integration Broker may be appropriate.
  • Processing large amounts of data: Integration Broker works with DOM. It requires all data to be loaded in memory at one time. This makes it a poor choice for handling large datasets. An alternative is to use stream processing with an event-based parser, such as a SAX parser. With Java, you can read bytes, process them through an event-based processor, and then discard the processed bytes.
The challenge with using Java directly from PeopleCode is that many Java methods and constructors use overloading. As a workaround, we devised a strategy for invoking Java through JavaScript from PeopleCode. With a small PeopleCode fragment, we can invoke a JavaScript, and JavaScript handles Java really well. Details about this strategy are available in our Blog Post JavaScript on the App Server: Scripting PeopleCode.

The following is a sample JavaScript we used at Reconnect 2024 to stream-processed a large amount of JSON data from a REST service.


At JSMpros, we teach all five options in our Integration Tools and Integration Tools Update courses. Check out our website for on-demand and live course offerings. Alternatively, subscribe to gain access to all of our on-demand courses.

Thursday, December 19, 2024

How Well Do You Know the PeopleCode Language?

 I was reading through some forum threads and saw an interesting question. Let me summarize:

How do I convert from a number to a string so that I get the integer portion of the number? I have 1234 in a number, but when I put it in a string field, I get 1234.00.

This is a great question! There are many PeopleCode "casting" functions, such as Number and String. Reading through the thread, I saw some interesting answers. One of them that I liked used Java to do the conversion. What I liked about this idea was the out-of-the-box thinking. But is it necessary? Can you do this with PeopleCode? The answer is YES using the functions NumberToString and NumberToDisplayString. Is there anything wrong with using Java instead? No. I love PeopleCode's ability to leverage the JRE. But there is a cost in context switching, JRE overhead, etc.

Keeping with the number and string scenario, do you really need to "cast" a number to a string? Consider the following code listing:

Local number &anInteger = 1234;

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

This code would fail because the fifth parameter to MessageBox must be a string. Therefore, it seems we need casting functions. Or do we? Consider the following alternative (notice the string concatenation in the MessageBox function):

Local number &anInteger = 1234;

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

When we concatenate a number to a string, PeopleCode applies an implicit cast. Interestingly, it only applies an implicit cast if we trick it with a zero-length string. As a side note, the above implicit cast returns the integer portion 1234 without decimals because 1234 has no decimals (which was the original request).

What about conditional logic? Here is one of my favorite examples:

If(PER_ORG_ASGN.POI_TYPE = "00002") Then
   JOB.COMPRATE.Visible = False;
Else
   JOB.COMPRATE.Visible = True;
End-If;

The above conditional code could be rewritten as:

JOB.COMPRATE.Visible = (PER_ORG_ASGN.POI_TYPE = "00002");

With an understanding of the PeopleCode language, we have reduced five lines of code into a single line. This might be considered the Spartan Programming approach. While I love the concept, Spartan Programming claims it "is not directly concerned with readability." Therefore, I prefer a slightly modified, possibly more human-readable alternative:

Local boolean &isVisible = (PER_ORG_ASGN.POI_TYPE = "00002");

JOB.COMPRATE.Visible = &isVisible;

Here is another piece of code I found several years ago:

Local Array of String &arr;

...

REM ** delete all elements from an array;
For &i = 1 to &arr.Len
   &arr.pop();
End-For;

If you look through PeopleBooks, you will see the Array has no clearemptydeleteAll, or any other method for emptying all elements from an array. So I can see why the author wrote the code above. But the reason the Array object has no method for emptying an array is because it doesn't need one. The Array length is writable. The easiest way to empty an array is:

&arr.Len = 0;

How well do you know the PeopleCode language? Time for a refresher? Check out our on-demand PeopleCode course and advanced PeopleCode Application Classes courses and experience a new side of PeopleCode!

Tuesday, November 19, 2024

Generating Activity Guide URLs

Creating Activity Guide navigation is challenging for two reasons:

  1. Simple Fluid Activity Guides all use the same component. This means we can't use a simple content reference. To use security to show or hide an Activity Guide content reference, we must use the URL type of PeopleSoft Generic URL. We must type a PeopleSoft URL fragment rather than leverage the traditional Content Reference fields (menu, component, market).
  2. Activity Guides with Runtime Context require dynamically generated URLs. These Activity Guides cannot leverage simple content references.

My PeopleCode for launching Activity Guides with Runtime Context usually starts with a GenerateComponentPortalURL to generate the base Activity Guide framework URL and then a bit of URL concatenation to assemble the Runtime context attributes. Here is an App Class I put together to make this easier.



Here is how you would use it:

import JSM_URL_UTIL:ActivityGuideURL;   
   
Local JSM_URL_UTIL:ActivityGuideURL &urlBuilder = create JSM_URL_UTIL:ActivityGuideURL("JSM_AWE_AG");
Local string &url;

&urlBuilder.addContextItem(Field.EOAWPRCS_ID, "FacilityAccessRequest");
&urlBuilder.addContextItem(Field.DESCR, "Facility Access Request");

&url = &urlBuilder.generateFluidURL();

The addContextItem method takes a key/value pair, both of which are strings (they will become part of the URL string). Since context IDs (keys) are fields, then using Field.FIELDNAME syntax is preferred so Edit | Find Definition References will locate your field usage.

We added one more convenience method: generateGenericPeopleSoftFluidURL(). Use this method to help you craft a PeopleSoft Generic URL to a static Activity Guide. This is a one-time-use method you would call at design time to create that static URL fragment required by a Content Reference. If you have a simple, static Activity Guide with no context, then you may want to create a Content Reference. However, typing all of the parameters correctly can be a challenge. Use this helper method to generate the full Generic PeopleSoft URL for you. We invoke this method from a design-time App Engine, but you may want to create a page for it instead.

Are you interested in learning more about PeopleCode Application Classes? Check out our two-day course available live virtual or on-demand!

Thursday, June 27, 2024

PeopleSoft Data Masking Options

PeopleSoft customers have several Data Masking options:

Let's compare the various options.

Event Mapping

Event Mapping is the most flexible option, allowing us to use any masking character, from alpha-numeric to emoji. In fact, you may even combine the space character with custom CSS to leverage any character, including custom Fonts, such as FontAwesome. Relevant PeopleCode functions and methods include:

Field.SetDisplayMask
Field.AddFFClass
AddStylesheet

Check out this video to learn more about Event Mapping for Data Masking:


Page and Field Configurator

Page and Field Configurator is less flexible but easier to apply than Event Mapping. Page and Field Configurator offers a point-click interface to configure masking against page fields. Masking characters are limited to * and x (although this is configurable through masking profiles).

App-specific masking

The HCM team built its own registry of sensitive fields with a masking utility. You can learn more about this feature in the Quest blog post Maintaining Data Privacy in PeopleSoft HCM. What makes this option compelling is:

  1. The HCM team wrote all the code. All we have to do is choose our sensitive fields, components, and roles.
  2. If anything breaks, we file a ticket for the HCM team to fix. This is in contrast to Page and Field Configurator and Event Mapping, which are site-specific isolated customizations and, therefore, the customer's responsibility to fix.
  3. This solution has broad coverage. If we choose to mask a sensitive field, such as birth date, then all HCM pages and components should mask that field. Event Mapping and Page and Field Configurator, on the other hand, only mask one component. If we used either of those solutions to mask the birth date field, we would need to apply that masking to all components ourselves.

Data Privacy Framework

The Data Privacy Framework allows us to apply masking to query results. This is not mutually exclusive. You may choose to apply the Data Privacy Framework along with any of the other options.

Bolt-on Solutions

My favorite data masking solution is Pathlock's Security Solution for PeopleSoft. Besides the basics of masking, Pathlock's solution also allows us to unmask using a variety of techniques, including:

  • Click-to-view (a loggable event) and
  • MFA-to-view (also loggable but requiring a second factor to confirm your identity).

Interested in learning more? We teach PeopleTools Tips like this every week at JSMpros! Check out our online schedule to see what we are offering next! Or do you have a specific topic you want to study? Subscribe to gain access to all of our on-demand content at a fraction of the cost!

Thursday, April 25, 2024

HOWTO Override Fluid Component Event Handlers

Oracle's delivered Fluid components use an interesting pattern: App Class Event Handlers. This isn't required. It's just a design decision. Here is how it works: a Fluid page's Component PreBuild usually initializes a component-scoped App Class variable and every subsequent event delegates to a custom App Class method. If done properly, this design decision has the following potential benefits:

  • Reusable,
  • Unit testable,
  • Extensible, and
  • It eliminates the "Data Integrity Error" when making changes to Component-specific PeopleCode while a component transaction is in progress.

Unfortunately, to be reusable and testable, App Class code must be context-agnostic. That means it can't leverage component buffer-specific functions, such as GetLevel0, GetRow, and GetRowset; it can't use context-specific variables, such as %Component; and it can't use bare references, such as RECORD.FIELD references.

We discuss these design concepts regularly in our PeopleCode Application Classes two-day course, and we wrote about the extensibility idea in this blog post. In the blog post, we noted that Oracle would need to change the way they load their App Classes in Component PreBuild to make event handlers extensible. But do we need to wait? We came up with an idea that allows us to implement this idea now: we can use Event Mapping to replace Component PreBuild so we can load our own App Class. As long as our new App Class extends the Oracle-delivered App class, all other event PeopleCode will delegate properly. In other words, PeopleSoft will use our code in all other events. What's interesting about this idea is that it may allow you to apply just one Event Mapping service to a component rather than one per event.

As an example, let's extend Direct Deposit by subclassing (overriding) one of its App Classes. Components that apply the event delegation pattern instantiate App Classes in PreBuild. Within the PreBuild of the Direct Deposit component (PY_IC_DIR_DEP_FL), we see the App Class PY_DD_SELFSERVICE:Utilities. That class includes several important methods, including one appropriately named PageActivate. Our goal is to use the PageActivate event to hide the Pay Statement Print Options box. We will accomplish this goal by using Event Mapping to replace PY_DD_SELFSERVICE:Utilities with our own Utilities subclass. Here is the code for the Utilities subclass:

import PY_DD_SELFSERVICE:Utilities;

class CustomUtilities extends PY_DD_SELFSERVICE:Utilities;
   method pageactivate();
   REM ** Add more methods to override mor functionality;
end-class;

method pageactivate
   /+ Extends/implements PY_DD_SELFSERVICE:Utilities.pageactivate +/
   REM ** invoke original pageactivate method since we are just extending, not replacing;
   %Super.pageactivate();
   REM ** The following line doesn't work because later Oracle-delivered code overrides it;
   REM PY_IC_WRK2.PRINT_OPTN.Visible = False
   PY_IC_WRK2.PRINT_OPTN.AddFFClass("psc_hidden");
end-method;

The next step is to apply Event Mapping to override Component PreBuild. Here is our sample code:

import PT_RCF:ServiceInterface;
import PY_DD_SELFSERVICE:Utilities;
import TRN_PY_IC_DIR_DEP_FL_OVRD:CustomUtilities;

class PreBuild extends PT_RCF:ServiceInterface
   method execute();
end-class;

Component PY_DD_SELFSERVICE:Utilities &DDUltities;

method execute
   /+ Extends/implements PT_RCF:ServiceInterface.execute +/
   
   &DDUltities = create TRN_PY_IC_DIR_DEP_FL_OVRD:CustomUtilities();
end-method;



After applying Event Mapping, the delivered Direct Deposit component will now pass all Utilities requests through our subclass. The standard, delivered &DDUtilities.pageactivate call that is in the middle of the delivered PageActiviate PeopleCode will now invoke our pageactivate method instead.

Summary

This was an interesting academic exercise with some benefits over Event Mapping:

  • This approach allowed me to indirectly inject code into the middle of an Oracle-delivered code listing. The delivered PageActivate event invokes &DDUtilities.pageactivate in the middle. Event Mapping would have required my change to appear at the end or beginning, but not in the middle.
  • I only had to configure one Event Mapping, not one for each event I desired to extend.

I also found some challenges that this approach could not solve:

  • I wanted to run code at the end of PageActivate, not in the middle. My CustomUtilities code triggers too soon. As you can see from the first code listing, I run Oracle's code through %Super, and then mine. But the actual event code listing has more code that overrides my code. Event Mapping is the only way to make sure your code runs last.
  • I would like to mask the routing number within the rows of the Direct Deposit grid. I would use RowInit to apply this masking. The delivered Direct Deposit component does not have code in RowInit. I would, therefore, have to use Event Mapping to apply RowInit code.

This is a pattern I'm going to keep in my toolbox. For this scenario specifically, Event Mapping without overriding was a better solution. But there are times where subclassing a backing App Class may make more sense.

Are you interested in learning more about PeopleTools and PeopleCode? Check out our live virtual and on-demand courses. Or even better, subscribe and get access to all of our content for a full year!

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!

Wednesday, November 08, 2023

Base64 Encoding with Emoji

PeopleSoft's Pluggable Encryption Technology (PET) is used to apply base64 encoding. The first step of the base64 algorithm chain is to convert from PeopleSoft Unicode to ASCII. The conversion makes sense since PeopleCode is a Unicode language, and the base64 algorithm is not. But what if you have Unicode characters you want to base64 encode? So I tried the following "Hello World" example with emoji:

&crypto.UpdateData("Hello World 🤔");

Unfortunately, the PET algorithm dropped the Unicode Emoji. Understandable because Emoji is Unicode. So, what alternatives do we have? The good news is my older PL/SQL approach still works. However, my older Java example no longer works. So, for those who want a cross-platform solution, here is an updated PeopleCode/Java code listing. The good news is this version is documented and much simpler!

REM ** What would you like to encode?;
Local string &textToEncode = "Hello World 🤔";

REM ** Pointer to Java encoder;
Local JavaObject &encoder = GetJavaClass("java.util.Base64").getEncoder();

REM ** Be sure to change the character set to match your source;
Local JavaObject &bytes = CreateJavaObject("java.lang.String", &textToEncode).getBytes("UTF-8");

Local string &result = &encoder.encodeToString(&bytes);

REM ** print the result;
MessageBox(0, "", 0, 0, &result);

At JSMpros, we teach advanced PeopleTools concepts such as this all the time! Check out our events page to see what we are offering next, and become a subscriber to get 24x7 access to all of our on-demand videos, activity guides, and code samples!

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!

Thursday, February 09, 2023

Did Event Mapping Break Your Update?

I love Event Mapping. But I have a concern:

The benefit of Event Mapping is that your customizations no longer appear on a compare report, and the problem with Event Mapping is that your customizations no longer appear on a comapre report.

I know what you are thinking: "Wait, didn't he just contradict himself?" Yes! Let me explain with a scenario:

Let's say you move a customization into Event Mapping. Later you apply an update. You run a compare report and see the beautiful "change/change" with no asterisks. Perfect! You have no customized code, and therefore nothing to retrofit. And then you test the upgraded system. And you find the system is broken. Since you have no customizations identified in the compare report, you should be fine, right? If this happened to me, my first thought would be that something is wrong with the update, and I would file a support ticket. But unfortunately, Oracle support can't replicate the issue. After escalation and further analysis, Oracle discovers that custom Event Mapping is causing the problem.

I share this scenario because it is possible, but it seems like a worst-case scenario. Does it really happen? Do custom, invisible event mapping "configurations" ever break an update/get current? It turns out they do! MOS doc 2798164.1 was posted in 2021 and demonstrates this scenario.

The problem with broken Event Mapping code is that it fails just like any other code, so we can't tell that the failure was caused by Event Mapping. Event Mapping is not a configuration. It is an isolated customization.

Event Mapping is amazing! But until Oracle provides us with LCM tools that identify potential Event Mapping issues, we must perform our own analysis. Here are some options to help you catch troublesome Event Mapping:

  1. Use SQL in this blog post to create your own Event Mapping analysis.
  2. Use PTF to create Event Mapping and Page and Field Configurator Regression tests.
  3. Wrap Event Mapping in a try/catch block to log and notify.
We teach Event Mapping and PeopleSoft Test Framework regularly. Check out our website to see what we are offering next!

Monday, October 17, 2022

What if Component App Classes Were Configurable?

With Fluid, PeopleSoft implemented an interesting design pattern: App Classes as Event Handlers. Properly implemented, there are some fantastic reasons to choose this pattern:

You can see Oracle's latest pattern on just about every Fluid component. A component that uses this pattern might have PreBuild PeopleCode that looks something like this:

import SOME_PACKAGE:SomeClass;
Component SOME_PACKAGE:SomeClass &handler;

&handler = create SOME_PACKAGE:SomeClass();
&handler.PreBuild();

But I had this idea... What if the code looked more like this:

import SOME_PACKAGE:SomeBaseClass;
Component SOME_PACKAGE:SomeBaseClass &handler;

Local string &className;

REM ** Select actual implementation of SomeBaseClass from a configuration table;
SQLExec("...", %Component, &className);

&handler = CreateObject(&className);
&handler.PreBuild();

Then we could override delivered behavior by subclassing and configuring our own handlers to override the delivered handlers. And I think this is the best reason to use App Classes as component event handlers. What would it take to implement this solution? Oracle would need to select the implementation class from SQL rather than hard-code its implementation into component PreBuild. But is it worth it? You know that is a fantastic question! You might say Event Mapping offers the same result but is more flexible. Let us know your thoughts in the comments!

At JSMpros, we teach PeopleTools and PeopleCode concepts like this regularly check out our website to see what we are offering next!

Wednesday, September 14, 2022

Simple Code-only REST Request

PeopleSoft is a metadata-driven application. It stands to reason, therefore, that the solutions we create require metadata. Sometimes metadata, which is supposed to be configurable, forces us down an immutable (non-configurable) path. Integration Broker is a fantastic example of semi-mutable, bordering on immutable metadata. As we prototype, we often make changes. But the testing associated with prototyping locks various metadata pieces. Sometimes we just want to test without metadata and backfill after we choose the proper path.

Here is a very simple metadata-free REST request example I run from a local App Engine program for prototyping and testing purposes.

Now, an important question:

Once you have a working "simple" solution, do you backfill and update with proper Integration Broker metadata?

Let us know your thoughts in the comments below!

At JSMpros, we teach Integration Tools concepts regularly. Check out our website to see what we are offering next!

Wednesday, August 31, 2022

Metadata-less Integration (Easy REST)

 As PeopleSoft customers move to REST, I've noticed an interesting trend: choosing code over metadata. Most of the examples I've seen online are labeled Easy or Simple. Some of the examples use fully supported documented approaches, whereas others use undocumented internal testing methods. Here are some of my favorites:

Based on those examples, REST PeopleCode really is easy! But what does that say about the metadata approach? Is it hard? Is it complex? To answer that, let's review the metadata definitions necessary to craft a REST request:

  • UR Document (if the target has parameterized URLs)
  • URI Message (if the target has parameterized URLs)
  • Request Document (if POST, PUT, or PATCH)
  • Request Message (if POST, PUT, or PATCH)
  • Response Document
  • Response Message
  • Service
  • Service Operation
  • Service Operation Security

And when you are done with the metadata configuration, you must still write code to invoke the REST request. Interestingly, invoking a REST metadata-based request requires roughly the same amount of PeopleCode as a "simple" or "easy" PeopleCode request.

So what does all that Metadata buy us? In other words, why choose metadata over pure code?

  • Logging: We can set the log level in the routing.
  • Security: OAuth and Basic Auth options can be set at the routing level.
  • Parsing: Document-based messages will parse JSON, XML, and Rowsets for us.
  • Maintenance: We can update/change URLs online rather than through PeopleCode (although a URL definition is a very simple way to maintain URLs across instances).

What if you don't require these features? Perhaps choosing the simple PeopleCode approach is better? Let us know your thoughts in the comments!

We teach PeopleTools REST and Integration regularly through our three-day Integration Tools Update course. Check out our website to see what we are offering next!

Wednesday, June 02, 2021

App Classes or Function Libraries: Which is Better?

As programmers, we look for patterns. And when we find them, we refactor them into reusable code. Keep it DRY: Don't Repeat Yourself. PeopleCode offers two reusable code containers:

  • Function Libraries
  • Application Classes

And this brings us to the great debate. Which is better? Object-oriented App Classes or procedure-based Function Libraries? I often ask my students this question. And if they prefer one over the other, why? Let's review the benefits of each:

Function Library:

  • Stateless
  • Simple
  • Must be declared

Application Class:

  • Stateful
  • Dynamic execution

There are other differences, but they are irrelevant for this comparison. For example, Application Classes have inheritance (some would say inheritance is NOT a benefit). The value of inheritance is reuse, but then a function can call a function, which is also reuse.

When asking students about their preference, here are a few of my favorite answers:

  • Choose App Classes because they are new.
  • Choose App Classes because they are object-oriented.
  • Choose App Classes because we are supposed to.
  • Choose App Classes because everyone is doing it.
  • Choose Function Libraries because they work!

One of the most valuable features of App Classes is dynamic PeopleCode. Unlike Function Libraries, App Classes do not have to be imported/declared before use. This is how every PeopleTools framework works. As a developer, we create an App Class and register it with the framework. At runtime, the framework reads the metadata and invokes the App Class. A framework such as AWE, for example, knows nothing about my custom App Class. But it invokes my code nevertheless through functions such as CreateObject, ObjectDoMethod, ObjectGetProperty, and ObjectSetProperty.

Which one do I choose? Here are my decision criteria:

  • If I need dynamic PeopleCode, meaning no declaration, then the only solution is an App Class. Another way to think about it is if I'm writing code that invokes another code block and I don't know that code block at design time, but will fetch the name from the database, then I have to use an App Class.
  • If I'm writing code to plug into another framework, such as Event Mapping or Integration Broker, I will use an App Class.
  • If I want testable code to test through PSUnit, I will create an App Class (because PSUnit is a framework that will invoke dynamic code).
  • If I need to maintain state between method invocations, I'll choose an App Class.
  • Otherwise, I choose a Function Library.

Which do you prefer and why? What are other benefits of one over the other? Leave a comment to let us know what you think!

At JSMpros, we recognize the value of Application Classes and offer a two-day course dedicated to teaching object-oriented best practices. In fact, our next session starts on August 9, 2021. Register now!

Wednesday, February 24, 2021

Setting IB Request Content Type

In our Integration Day 2021 One-day Webinar, we showed how to send SMS from PeopleSoft through Twilio. What makes Twilio interesting is that Twilio expects URL encoded form data. As you may know from using Integration Broker, PeopleSoft is quite happy sending and receiving JSON or XML. But what about other content types, such as application/x-www-form-urlencoded? You may know that you can set most HTTP headers using something like:

&request.IBInfo.IBConnectorInfo.AddConnectorProperties("Content-Type", "application/x-www-form-urlencoded", %Header);

That's a mouth full! But the weirdest thing happens when trying that trick with URL encoded form data. PeopleSoft URL encodes the entire body! Wait, isn't that correct? No, actually. When you and I create the body, we create the proper key=value&key2=value2 pairs. Proper URL encoded form data would look something like this:

key=This+is+some+encoded+data&key2=value2

Keys, ampersands, and equals signs should not be encoded (unless part of the value). But PeopleSoft URL encodes everything as if there were no keys. Here is what that same string above looks like when PeopleSoft finishes with it:

key%3DThis%2Bis%2Bsome%2Bdata%2Bto%2Bencode%26key2%3Dvalue

It is just supposed to URL encode the values. Or better yet, just let us URL encode the values. What's the workaround?

&request.SegmentContentType = "application/x-www-form-urlencoded";

This solution is a little strange because we aren't using a multi-part, segmented message, but it works!

While performing research for this post, I came across MOS Doc ID 2187249.1, which asks for an enhancement to allow additional, hopefully even custom, Content Types, so it appears URL encoded form data isn't the only issue. But lucky for us, we have SegmentContentType as a workaround!

Are you interested in learning more about PeopleSoft Integration Broker and integration strategies? Join one of our live virtual top-rated Integration Tools Update courses!

Friday, February 05, 2021

PeopleCode to Move a File... sort of

You ever need to move a file through PeopleCode? Perhaps an App Engine program that needs to move generated content into the process output directory? I found myself in that position today. Surely there is a PeopleCode function for that... right? If you have one, please share it, because it seems the Internet doesn't know about it. A quick Internet search turned up several examples of using Exec with mv. I'm always weary of Exec. If I have to use the command line, I prefer to control it through the Java Runtime (see Exec Processes while Controlling stdin and stdout). Speaking of Java, can we borrow anything from the JRE? I did find my old ITToolbox response showing how to use java.io.File.renameTo to move files. It is still a great solution, but with multiple mounted file systems... well... it actually might fail. So I got to thinking... what about using java.nio? Here is an example:

Local JavaObject &jSource = CreateJavaObject("java.io.File", "c:\temp\JSM_TEST.log").toPath();
Local JavaObject &jTarget = CreateJavaObject("java.io.File", "c:\temp\JSM_TEST_moved.log").toPath();

Local JavaObject &jFiles = GetJavaClass("java.nio.file.Files");
Local JavaObject &jOptions = CreateJavaObject("java.nio.file.CopyOption[]");

&jFiles.move(&jSource, &jTarget, &jOptions);

And, what if you want to overrwrite the destination? Try it with copy options:

Local JavaObject &jSource = CreateJavaObject("java.io.File", "c:\temp\JSM_TEST.log").toPath();
Local JavaObject &jTarget = CreateJavaObject("java.io.File", "c:\temp\JSM_TEST_moved.log").toPath();

Local JavaObject &jFiles = GetJavaClass("java.nio.file.Files");
Local JavaObject &jStandardCopyOptions = GetJavaClass("java.nio.file.StandardCopyOption");
Local JavaObject &jOptions = CreateJavaObject("java.nio.file.CopyOption[]", &jStandardCopyOptions.REPLACE_EXISTING);

&jFiles.move(&jSource, &jTarget, &jamp;Options);

For more details about copy options, take a look at the Java Docs.

Are you ready to take your PeopleSoft development skills to the next level? Check out our latest course offerings at jsmpros.com. Prefer a custom agenda? Contact us for details.