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.

Sunday, February 21, 2010

Page Assembler Strips Empty Elements -- This is Good!

With the new release of PeopleTools and Enterprise Portal, I find myself modifying my old branding themes to incorporate new features. While testing a header, I noticed that it appeared correctly on homepages, but not on transaction pages. Upon further inspection, I noticed that certain HTML elements I included in my HTML definition appeared on homepages, but not on transaction pages. Specifically, if an HTML element had a bind variable as its only content and that bind variable was only relevant on a homepage, then the page assembler would strip my hard-coded empty element from the transaction page. This caused me a bit of concern because I was actually using those elements to provide layout and styling. Consider the "Personalize Content | Layout" links that usually appear underneath the tabs in a standard Enterprise Portal implementation. Those links only appear on homepages, not on transaction pages. The PeopleSoft branding/assembly code uses designated bind variables to insert those links into a header HTML definition. If you wrap a block element, such as an HTML div around that bind variable, then your div will appear on a homepage, but not on a transaction page.

When I saw this behavior the other day I was quite surprised... and then I remembered I had seen it before. Several years ago while working with page level HTML Areas and Ajax, I noticed the same behavior. It is quite common to insert empty, hidden div elements and other structural elements into HTML to act as containers for dynamic content. The only problem with this approach is that the page assembler seems to eliminate these empty elements. Here is the workaround I contrived for this issue: Add an HTML comment inside an empty HTML element as follows:

<div id="jjm_dynamicContent" style="height: 10px; background-color: blue;">
<!-- This comment will force the page assembler to render this element -->
</div>

The page assembler will see content (the comment) inside the element and will allow it to pass through to the browser. Since the content is a comment, the browser will ignore the content and treat the element as if it were empty, giving us our much desired empty element.

Now that we have a solution for creating empty elements, let's consider how to turn this seemingly annoying behavior into a positive feature. The PeopleTools branding makes extensive use of HTML definitions. These HTML definitions contain bind variables that may or may not have values, depending on the execution context. I've already given the example of the Personalize links on a homepage. Wrapping items like these in HTML containers, such as div elements, provides us with conditionals that otherwise might not exist. For example, by wrapping the Personalize bind variable in a named div, you create a div that will exist on homepages, but not on transaction pages. Using JavaScript, you can test for the existence of this named element and execute code accordingly. Likewise, you can use CSS to attach layout and design instructions to these elements that the browser will only apply if the element exists (conditions are met).

There was a time when I thought this was a bug that should be fixed. But, now that I am enlightened to the possibilities of conditionals, I see the benefits of this feature and would be very sorry to see this behavior change.

HEUG Alliance 2010

I can't believe that Alliance is next week! This is one of my favorite conferences - the people, the sessions, the locations... Alliance is one of the best opportunities to learn and share PeopleTools ideas.

I hope you can get into San Antonio early and get a good night's sleep Sunday night because my session, 27202 - PeopleTools Tips and Tricks is the first breakout session Monday morning. The fun run and golf tournament are great ways to get some exercise to acclimate yourself to the timezone differences, or just so you can sleep well in your hotel room.

The Alliance Agenda builder doesn't show sessions by vendor. Since I have a list of PeopleTools sessions offered by Oracle, I thought I would list them here. I will attend some of these sessions, but I am much more interested in attending your (the customer) sessions and seeing what you are doing.

TitleSessionTimeLocation
Monday
PeopleTools Tips and Tricks272029:30 AMRoom 103A
PeopleSoft and WebCenter: The New World of Enterprsie 2.02721710:45 AMRoom 103A
Building Web Services with PeopleTools Integration Broker
2721512:45 PMRoom 103B
PeopleTools Roadmap271973:15 PMGrand Ballroom C3
PeopleTools Performance Tips and Tricks272014:30 PMRoom 103B
Maintenance Strategies for a PeopleSoft Enterprise Application272064:30 PMRoom 007A
Tuesday
PeopleTools 8.50 Highlights - For The Application Developer2720512:45 PMRoom 103A
PeopleTools 8.50 Highlights - SOA and Integration272162:00 PMRoom 103B
PeopleTools 8.50 Highlights - Application and Web Servers272003:15 PMRoom 103A
PeopleSoft PeopleTools Enterprise: A Panel Discussion272044:30 PMRoom 103A
Wednesday
PeopleTools 8.50 Highlights - Reporting and BI271999:30 AMRiver Room 001B
Securing your PeopleSoft Application2719810:45 AMRoom 103B
Web 2.0 in Your Enterprise: Collaboration by Example2720710:45 AMRoom 103A

Besides great sessions, be sure to stop by the Oracle demo grounds to see our new 8.50 and 9.1 applications! I haven't seen a final schedule, but Meet the Experts will be in the demo grounds some time during the normal exhibit all hours. Be sure to look for the Meet the Experts bistro tables in Oracle booth.

See you next week in San Antonio!

Exec Processes while Controlling stdin and stdout

A few months ago I read a question on the ITToolbox PeopleTools-I forum asking if it was possible to read the output of a spawned process. Unfortunately this is not possible with the delivered PeopleCode Exec function. I got to thinking about this though, and I wondered if it was possible to accomplish this by using Java's Runtime object from PeopleCode. The answer is yes. To test this, create a batch file in your c:\temp directory named sayHello.bat and then add the following batch file commands to this file:

@echo off
echo Hello %1
echo How are you?

Next, open App Designer and create a new App Engine program. Add a new PeopleCode action to this program and insert the following PeopleCode:

Local JavaObject &runtime = GetJavaClass("java.lang.Runtime").getRuntime();
Local JavaObject &process = &runtime.exec("c:\temp\sayHello.bat """ | %OperatorId | """");

Local JavaObject &inputStreamReader = CreateJavaObject("java.io.InputStreamReader", &process.getInputStream());
Local JavaObject &bufferedReader = CreateJavaObject("java.io.BufferedReader", &inputStreamReader);
Local any &inputLine;

While True
&inputLine = &bufferedReader.readLine();
If (&inputLine <> Null) Then
MessageBox(0, "", 0, 0, &inputLine);
Else
Break;
End-If;
End-While;

Open the new App Engine's properties and disable restart. From the App Designer menu bar, choose Edit | Run Program. When the Run Request dialog appears, select the Output Log to File checkbox and then activate the Run button. When the App Engine finishes, open the log file (usually c:\temp\NAME_OF_AE.log) and review its contents. Here is the contents of my log file. Notice that I was logged in as user PS and named my App Engine JJM_JAVAEXEC. I expect your results to differ slightly based on your tools version, operator ID, and program name.

PeopleTools 8.49 - Application Engine
Copyright (c) 1988-2010 PeopleSoft, Inc.
All Rights Reserved


Hello "PS" (0,0)
Message Set Number: 0
Message Number: 0
Message Reason: Hello "PS" (0,0) (0,0)

How are you? (0,0)
Message Set Number: 0
Message Number: 0
Message Reason: How are you? (0,0) (0,0)
Application Engine program JJM_JAVAEXEC ended normally

The following example is similar to the previous, but writes to stdin as well as reading from stdout. To run this example, you need a copy of grep. I use the version that comes with UnxUtils (Note: You do not need grep or UnxUtils to write to stdin. Grep is a common command line tool, so I used it here for example purposes only). The following code writes two lines to the grep program's standard input (stdin): EMPLID and OPRID. It then asks grep to find the line containing OPRID. The PeopleCode then reads the grep program's output and writes it to the App Engine log.

Local JavaObject &runtime = GetJavaClass("java.lang.Runtime").getRuntime();
Local JavaObject &process = &runtime.exec("grep ""OPRID""");

Local JavaObject &inputStreamReader = CreateJavaObject("java.io.InputStreamReader", &process.getInputStream());
Local JavaObject &bufferedReader = CreateJavaObject("java.io.BufferedReader", &inputStreamReader);
Local JavaObject &outputStreamWriter = CreateJavaObject("java.io.OutputStreamWriter", &process.getOutputStream());
Local JavaObject &outputBuffer = CreateJavaObject("java.io.BufferedWriter", &outputStreamWriter);
Local any &inputLine;

&outputBuffer.write("EMPLID: " | %EmployeeId);
&outputBuffer.newLine();
&outputBuffer.write("OPRID: " | %OperatorId);
&outputBuffer.close();

Repeat
&inputLine = &bufferedReader.readLine();
If (All(&inputLine)) Then
MessageBox(0, "", 0, 0, &inputLine);
End-If;
Until None(&inputLine);

If you want to run this example and have a copy of grep in your %PATH% environment variable, then add a new step and PeopleCode action to your App Engine, deactivate the previously created step, and then run the program. Your output should look something like:

PeopleTools 8.49 - Application Engine
Copyright (c) 1988-2010 PeopleSoft, Inc.
All Rights Reserved


OPRID: PS (0,0)
Message Set Number: 0
Message Number: 0
Message Reason: OPRID: PS (0,0) (0,0)
Application Engine program JJM_JAVAEXEC ended normally

Wednesday, January 20, 2010

Base64 Encoding Binary Files

Several months ago I posted a handful of methods for base64 encoding strings. At that time I suggested that the next important step was base64 encoding binary files. This represents a challenge because PeopleCode does not offer methods for reading binary files. The following PeopleCode uses the Java API and the Apache Commons Codec library to show how to base64 encode a binary file. To run this example, download the commons-codec library and place the jar file in your local %PS_HOME\class directory. Next, create an App Engine with a PeopleCode step and insert the following PeopleCode:

Local JavaObject &f_in = CreateJavaObject("java.io.FileInputStream", "c:\temp\binaryfile.gif");
Local JavaObject &coder_in = CreateJavaObject("org.apache.commons.codec.binary.Base64InputStream", &f_in, True);
Local JavaObject &reader = CreateJavaObject("java.io.BufferedReader", CreateJavaObject("java.io.InputStreamReader", &coder_in));

Local string &b64Data = "";
Local any &line;

While True
&line = &reader.readLine();
If (&line <> Null) Then
&b64Data = &b64Data | &line | Char(13) | Char(10);
Else
Break;
End-If;

End-While;

Local File &b64File = GetFile("c:\temp\base64_encoded.txt", "A", "A", %FilePath_Absolute);
&b64File.WriteLine(&b64Data);
&b64File.Close();

The Java objects at the top of this listing read binary data from a file and transform that data into a base64 text listing, which is then stored in the variable &b64Data. For demonstration purposes, I wrote the contents of &b64Data to a file. In real life, you would add this contents to a CDATA node prior to sending a message through the Integration Broker.

Before running this code, replace c:\temp\binaryfile.gif with the full path to a real binary file on your workstation. After adding the PeopleCode listed above, disable restart and run your App Engine. When the App Engine completes, look in your local c:\temp directory for a file named base64_encoded.txt.

Tuesday, January 12, 2010

Pre-order My PeopleTools Tips Book

My PeopleSoft PeopleTools Tips and Techniques book is now available for Pre-order through popular book resellers:

Expected availability is July 22, 2010. This book took a lot of effort and I especially want to thank my wife who performed the first edit and rewrite (the biggest chunk). Without her, there wouldn't be an intelligible sentence or complete thought in the book. Secondly, I would like to thank my technical editors Tim Burns and Graham Smith, two very well known faces in the PeopleSoft community. These guys tested every example prior to publication.

Monday, October 26, 2009

OpenWorld Presentations Available

The 2009 PeopleTools Tips and Tricks session I presented is available (audio and slides) on Oracle OpenWorld On Demand. Unfortunately there are no screenshots for the demos.

Thursday, October 15, 2009

Change the Advanced Search Page Default Search Operator

In my OOW presentation yesterday I showed that it is possible to change the default search operator for a specific advanced search page. The JavaScript required to implement this behavior follows:

<script type="text/javascript">
$(document).ready(function() {
var newValue = 9;
var coll = $("select[name='APT_UI_SCRIPTS_MENUNAME$op']");
if(coll.val() != newValue) {
coll.val(newValue).change();
}
});
</script>

Note: The code above uses jQuery for event handling and therefore requires that you inject jQuery along with the JavaScript above. I demonstrate how to accomplish this in the injection blog post referenced below.

In my PeopleTools book I provide full details for implementing this behavior, but if you want to get a head start, I'll give you some hints. First, you need a mechanism for injecting this JavaScript into a search page. To learn more about injection, read my post Injecting JavaScript Libraries into PeopleSoft Pages. Next, you need a way to target a specific component. For this, see my post JavaScript complement of PeopleCode Global Vars. Your final step is to write some JavaScript to determine if the open page is a search page. I determine this by testing for the existence of an object named #ICSearch.

To determine the numerical code for a search page operator (9 = between), open the HTML source of any advanced search page and search for the option child nodes of PSDROPDOWNLIST.

Update (April 15, 2010)

The code above works great with PeopleTools 8.49 and lower. PeopleTools 8.50 uses Ajax to switch from the basic to the advanced search page. The 8.50 Ajax does not trigger the $(document).ready code. I have another solution for PeopleTools 8.50 (documented in my book). I'll post it soon.

Wednesday, October 07, 2009

Learn Development Tips and Techniques at OpenWorld 2009

Next Wednesday, October 14th, I will be sharing PeopleTools Tips and Tricks from 11:45 to 12:45 in Moscone West room 2002/2004 (level 2). In this session I will share with you some very practical development tips (TDD, design patterns, best practices, etc) as well as demonstrate exotic user interface customizations using AJAX and Flex. Time permitting, I will share with you some real-time integration solutions for legacy (non web service) applications and I won't use DB links or SQR.

What type of AJAX demonstrations do I have this year?

  • Using Thickbox/lightbox with PeopleSoft
  • Creating a custom toolbar
  • Change the search page default search operator

Best of all, these AJAX examples are configurable, except for 10 lines of JavaScript added to one PeopleSoft delivered definition. Yes, that is right! By adding 10 lines of JavaScript to a single delivered definition, I can add code to any PeopleSoft page without modifying the page in AppDesigner. Furthermore, using Meta-data, I can configure page specific customizations or global customizations.

If you have room in your OpenWorld schedule, I highly recommend this Tips and Tricks session.

On Thursday at 10:30, come by the Create a Rich Internet UI for Oracle Applications with Oracle Application Development Framework hands on session at the Marriott Golden Gate A3 to see a demonstration of how to build mobile applications for PeopleSoft using Oracle ADF.

Here is a list of other sessions I recommend:

Friday, September 18, 2009

PeopleTools 8.50 Released!

The PeopleTools team just announced the general availability of PeopleTools 8.50. I have had the fortunate opportunity to work with PeopleTools 8.50 for a couple of months and I am very pleased with the enhancements provided in this new release. For more information, please read the PeopleTools Team's blog post.

Wednesday, September 09, 2009

Cast your Vote for the next PeopleTools Features

Greg Kelley from PeopleTools Product Strategy just posted a request for PeopleTools enhancement ideas and votes. If you have an idea, post it in the PeopleTools Strategy Mix Group. What's that you say? You don't have any ideas? Log in to Mix anyway and vote for ideas submitted by other developers. The best ideas will be discussed at an OOW session on Wednesday, October 14th from 1:00 - 1:30 PM.

Tuesday, September 01, 2009

Monday, August 31, 2009

JavaScript complement of PeopleCode Global Vars

While writing some JavaScript for my new PeopleTools book, I came up with a JavaScript complement to some of the common PeopleCode global variables. Here is the code:

jjmpsj.sysVars = (function(url) {
var matches = url.match(
/ps[pc]\/(.+?)(?:_\d)*?\/(.+?)\/(.+?)\/c\/(.+?)\.(.+?)\.(.+?)$/);
return {
getSite: function() {
return matches[1];
},
getPortal: function() {
return matches[2];
},
getNode: function() {
return matches[3];
},
getMenu: function() {
return matches[4];
},
getComponent: function() {
return matches[5];
},
getMarket: function() {
return matches[6];
}
}
})(window.location.pathname);

Now, wouldn't you just love to know how I intend to use this code... to learn that you will have to wait for the book.

Monday, August 17, 2009

OpenWorld 2009

OpenWorld 2009 is only two months away. If you are looking for an interesting PeopleTools session, be sure to show up in Moscone West L2 Room 2002/2004 on Wednesday 10/14/2009 at 11:40 (session time is 11:45 - 12:45). I will be presenting another exciting series of PeopleTools Advanced Tips and Techniques. Whether you are a functional user interested in seeing what's possible, or an expert looking for something new, I can guarantee you will hear and see something of interest. As always, I plan to pack three days worth of content into one hour.

Besides the PeopleTools 8.50 sessions that every developer will attend, I recommend the session How to Customize PeopleSoft Applications Safely led by Graham Smith. Graham is a personal friend of mine from UKOUG.

I will also be working the demo grounds. I'll post my schedule once it is finalized.

Sunday, August 02, 2009

Using jEdit to Edit PeopleSoft Files

A couple of years ago, Chris Heller wrote a great article on editing PeopleSoft files with the Notepad++ text editor. Since I prefer jEdit over Notepad++ (my jEdit fascination dates back to my Java programming roots and, more recently, the jEdit Ruby plugin). I have a collection of custom jEdit syntax files in my box.net jEdit shared folder. In this folder, you will find DMS and PeopleCode syntax files (unlike Notepad++, jEdit includes SQR syntax files out of the box). Feel free to download and use these files. Caveat: these files are far from complete. I add to them on an as-needed basis. If you find errors and/or omissions, please leave a comment here, and I'll do my best to update my shared syntax files. See the jEdit help file for how to add these syntax files to jEdit (hint: jEdit calls syntax files "Edit Modes").

By the way, jEdit has a Code2HTML plugin. I use the syntax files in my shared folder along with the Code2HTML plugin to generate the syntax highlighting used on this blog.

Friday, July 31, 2009

HOWTO: Generate GUID from PeopleCode

A few months ago I demonstrated a handful of ways to Base64 encode strings — none of them were delivered. That post generated some outstanding feedback. Customers, consultants, and Oracle employees pointed me at several other alternatives, including the delivered Pluggable Encryption technique documented in PeopleBooks. I hope this post generates the same amount of discussion.

First, why would a PeopleSoft developer generate a GUID? My motivation is a database cache. I have a transaction that inserts information into a cache Record and an IScript that reads values from that cache. Rather than pass transaction keys to the IScript, I just pass a GUID that identifies the transaction's row in the cache. Besides decoupling the IScript from a transaction, it provides a bit of security through obfuscation (see OWASP Top 10 2007-Insecure Direct Object Reference)

Now, how to generate a GUID from PeopleCode... PeopleBooks does not list any GUID generation functions, so the next place to look for this functionality is in related technologies accessible to PeopleCode. For example, the Oracle database provides the SYS_GUID function for generating GUID's in the RAW. Here is the SYS_GUID function in action:

Local string &guid;
SQLExec("SELECT RAWTOHEX(SYS_GUID()) FROM PS_INSTALLATION", &guid);
MessageBox(0, "", 0, 0, "GUID from DB: " | &guid);

If you are just looking for a random string, then read no further. If you want a formatted GUID, then you will need to add the dashes yourself. Microsoft SQL Server has a similar function that actually returns a fully formatted plain text GUID.

The problem with these SQL alternatives is that they are database specific. I'm not going to complain about a user taking full advantage of the database's features. But, if there is an alternative that may reduce future maintenance costs (like the cost of swtiching from one database to another), then I'll consider the low cost alternative.

Since all PeopleSoft application servers run Java, we can use the JRE's GUID methods to generate a GUID. If you are on PeopleTools 8.49 with Java 1.5, then this short PeopleCode snippet will give you a fully formatted GUID:

GetJavaClass("java.util.UUID").randomUUID().toString();

On my laptop, the code above generated d65ca460-fc93-4420-a889-8b36311ee4a0.

What if you are using an older version of PeopleTools (prior to 8.49)? The Apache commons id project has a UUID Java class that is similar to the delivered Java 1.5 UUID class. For more information about Java GUID generation, see this java.util.UUID mini-FAQ.

Who knows, if you dig through your application's PeopleCode, you might find an undocumented method for generating GUID's ;)

Friday, July 10, 2009

New PeopleTools Book

Pre order copies of my new book PeopleSoft PeopleTools Tips & Techniques. The publication date is set for July, 2010, about one year from now.

Updated Oct 29, 2009: Unfortunately, you cannot pre order yet. McGraw Hill removed the placeholder page from their site. McGraw Hill will repost and begin taking pre orders as we get closer to the release date. I will post an update at that time.

Wednesday, May 06, 2009

Ajax Experience '08 JavaScript Presentation

I was researching some JavaScript techniques and ran across the video Advanced JavaScript: Closures, Prototypes, Demystified. If you read this blog and like the JavaScript customization ideas that you see, but aren't very comfortable with JavaScript, then watch this presentation. It does a great job of explaining some of the features of the JavaScript language.

Monday, May 04, 2009

Base64 Encoding with Pluggable Encryption

I dedicate this post to Chris Heller since he is the person that pointed me at PeopleSoft's pluggable encryption in his comments to my last post on Base64 Encoding for PeopleSoft. I had not considered PeopleSoft's pluggable encryption for base64 encoding. Thanks Chris!

Before we can base64 encode strings with PeopleSoft's Pluggable Encryption, we need to create an Algorithm Chain and an Encryption Profile. PeopleBooks does an excellent job of explaining each of these terms as well as telling how to create each of these components. Since PeopleSoft delivers the base64 encryption algorithm with the PSPETSSL encryption library, we can move right into defining the Algorithm Chain. To define the chain, navigate to PeopleTools > Security > Encryption > Algorithm Chain. Add the new value BASE64_ENCODE and add the following Algorithms in order:

  1. PSUnicodeToAscii
  2. base64_encode
  3. PSAsciiToUnicode

Be sure to set the sequence number for each row (1-3). Save and navigate to PeopleTools > Security > Encryption > Encryption Profile. Add the new value BASE64_ENCODE. Specify the algorithm chain BASE64_ENCODE and save. We can now test this pluggable encryption profile with a little PeopleCode:

Local object &crypto = CreateObject("Crypt");
&crypto.Open("BASE64_ENCODE");
&crypto.UpdateData("Hello World");
MessageBox(0, "", 0, 0, "Encrypted: " | &crypto.Result);

And, the result should be: SGVsbG8gV29ybGQ=. I am sure you will all agree that this solution is much simpler than the ugly Java Reflection I previously demonstrated. Furthermore, this method is well documented in PeopleBooks and supported by PeopleSoft.

If I am reading PeopleBooks right, Pluggable Encryption only works with ASCII text. That is why we have to use the PSUnicodeToAscii and PSAsciiToUnicode algorithms in our algorithm chain. What this means is that we still need to use an alternative for base64 encoding binary data, a requirement when embedding binary data in XML documents.

Sunday, May 03, 2009

Base64 Encoding for PeopleSoft

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

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

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

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

In fact, rewrite that as:

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

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

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

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

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

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

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

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

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

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

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

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

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

package com.blogspot.jjmpsj;

import java.io.IOException;

import sun.misc.BASE64Encoder;

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

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

... and call it like so:

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

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

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

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

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

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

Productivity gains through Meta-SQL

I hope the designer of PeopleSoft's Meta-SQL is extremely wealthy. I believe Meta-SQL usage is one of the most under utilized PeopleTools development practices. Do you want to improve your productivity as a PeopleSoft developer? Learn Meta-SQL. Consider the %InsertSelect Meta-SQL statement... A PeopleSoft SQL insert into/select from statement may contain 100 or more fields. Maintaining the mappings between the insert clause and the select clause can be quite daunting. %InsertSelect eliminates this mapping nightmare through convention: source fields with the same name are automatically mapped to destinations with the same name. If your scenario deviates from this convention, then a minor configuration in the Meta-SQL statement allows you to override the default behavior.

Likewise, using Meta-SQL, it is possible to write generic SQL that works regardless of the target table. Consider the following PeopleCode:

Local Record &rec = CreateRecord(Record.xxx);
Local string &exists;

REM set &rec key field values, copy from component buffer, etc;

SQLExec("SELECT 'X' FROM %Table(:1) WHERE %KeyEqual(:1)", &rec, &exists);

No matter what table I specify, this same SQL statement will tell me if a matching row exists in that table. PeopleSoft includes many of these Meta-SQL shortcuts. By combining a couple of Meta-SQL statements into a FUNCLIB, I can create a generic PeopleCode compliment to the Oracle merge statement (some call it UPSERT):

Function Merge(&rec As Record)
Local string &exists;

SQLExec("SELECT 'X' FROM %Table(:1) WHERE %KeyEqual(:1)", &rec, &exists);

If (All(&exists)) Then
SQLExec("%Update(:1)", &rec);
Else
SQLExec("%Insert(:1)", &rec);
End-If;
End-Function;

Where would I use a merge function like this? Let's say you need to clone data in the current buffer, but change a key field (EFFDT perhaps?). Using the Copy methods of stand-alone Rowsets and Records, I can copy the component buffer into stand-alone rowsets and records, change the key fields, and then, with a generic loop, iterate over the rows and records, inserting or updating database values using the Merge function above.

There are several Meta-SQL routines. Some exist to provide database independence, but many exist to provide meta-data driven shortcuts for repetitive tasks. I encourage you to take some time to review the PeopleBooks Meta-SQL documentation. I trust you will be pleasantly surprised with the productivity gains you can achieve through Meta-SQL. Now, if I could just get someone to create an open source port of PeopleSoft's Meta-SQL that I can use with JDBC...

Monday, April 13, 2009

"Introducing PSUnit" now in Wiki

I just moved the "Introducing PSUnit" document to Oracle's Wiki: Introducing PSUnit. Please feel free to update, discuss, and contribute using the tools provided by Oracle's Wiki.