Wednesday, September 29, 2010

OOW 2010 Presentations Available

Oracle OpenWorld OnDemand is now available and includes slides and audio for each of the sessions in which I presented. Here are the OnDemand links:

If you were not able to attend OpenWorld 2010, you can purchase OnDemand here.

Thursday, September 23, 2010

Going Mobile with PeopleSoft

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

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

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

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

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

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

Posting Data to IScripts

If you are a regular reader, you already know that I am a big fan of Ajax. Most of my PeopleSoft Ajax requests use HTTP GET operations to send query string parameters to iScripts. I have considered using POST to send structured data to iScripts (XML, JSON, etc), but have not found reason to do so. Considering my background in other web based languages, I just assumed the %Request object provided direct access to posted content. I didn't really look until I saw an IT Toolbox forum question from KCWeaver asking how to post data to an iScript. The Request object does have a GetContentBody() method that will return POST'd data. What PeopleBooks doesn't tell you is how to activate the GetContentBody method (Note: I don't think this is an oversight. I think it is because GetContentBody is designed for Business Interlinks, not for iScripts). Special thanks to Kevin for digging through the documentation and figuring out how to POST to an iScript. The trick is to add postDataBin=y to the end of your query string.

View the full IT Toolbox thread here: AJAX to iScript

PeopleTools Tips Sample Chapter Available

Are you still trying to decide whether or not to buy my new PeopleTools book? Would a sample chapter help? The McGraw Hill page for this book allows you to download chapter 3 for free. Chapter 3 contains step by step instructions for workflow enabling a transaction using the relatively new Approval Workflow Engine (new in PeopleTools 8.48). If you have ever had trouble configuring AWE and wondered if it was possible to trace the stage, step, path, approver selection information, you will want to take a look at the Tracing AWE sidebar on page 125 (page 35 of the PDF).

Saturday, September 18, 2010

PeopleTools Tips at OpenWorld

I have checked in at my local airport and am en route to OpenWorld, the largest tech conference of the year. I'm pretty well finished preparing my demos for this year's session, and am using this time to finish my slides. I am very pleased with my demos this year as I think they demonstrate some very powerful ways to enhance PeopleSoft applications. Two of my primary topics for this year are Mashups and Mobile. I see Mashups as an alternative to Integration. Of course, you still need integrations, but whenever possible, I look for a Mashup alternative because Mashups general don't require modifications. In this session I will present some Mashup ideas and ways to ensure security.

Mobile... I find mobile to be one of the most fascinating ideas. I work remote (no office), and, therefore, am 100% mobile (at least in theory). PeopleTools has been relatively silent in regards to mobile. PeopleSoft Applications have built some very exciting mobile apps (see Theresa's blog post and video), but PeopleTools is silent. After reviewing a handful of mobile development strategies, I am actually quite pleased with the mobile development solutions available to PeopleSoft customers. I'm finding that even though PeopleTools is silent in regards to mobile, the PeopleTools architecture lends itself very well to mobile development. In my PeopleTools Tips session on Monday I will demonstrate two separate mobile applications. The first is a mobile employee directory built using ADF and the web service data control. The second application uses plain old JavaScript and HTML to display a mobile worklist. This second application excites me the most because it shows that mobile development can be simple - No SOAP, no WSDL, no frameworks, no data bindings... just plain JavaScript, CSS, HTML, and PeopleCode.

Besides mobile and mashups, I also included:

  • Monkeypatching - what is it and how can I/why would I use it?
  • Debugging integrations - tools that facilitate debugging.
  • Pagelet Wizard - what is it, how can I use it, how can I extend it?

See you Monday at 5:00 PM in the Marriott, Golden Gate A (session id S317016). You won't be disappointed!

Friday, September 10, 2010

PeopleTools 8.51 Now Generally Available

PeopleTools 8.51 is now Generally Available (meaning, you can download it from eDelivery). You can find the hosted PeopleBooks for 8.51 here. The PeopleTools 8.51 documentation home is here.

Tuesday, September 07, 2010

URL Administration (Nodes and URL Definitions)

I use URL's quite extensively for Ajax and other non-Integration Broker integrations (NEVER HARD CODE URL'S!). PeopleTools provides a couple of ways to store URL information. The most well-known of these features is the URL definition (PeopleTools > Utilities > Administration > URLs). URL Definitions are great for relative URL's, but I'm not fond of storing fully qualified URL's in this manner. Here is why...

Imagine having 5 URL's that point to different resources on the same server and then one day the server's host name changes. Because of this change I have to modify 5 URL definitions. What if I forget one?

An alternative is to store the base URL in a node definition and then the relative portion of the URL in a URL definition. Creating a fully qualified URL in this manner requires concatenating two definitions: the node URI and the URL definition. Next Question: How do I access these Meta-data objects from PeopleCode? Most of us are familiar with the GetURL PeopleCode function, but what mechanism does PeopleCode offer for retrieving a node's Content and Portal URI?

A PeopleSoft instance's node definitions are accessible through the %Session object. The Session object contains a method named GetNodes which returns a collection of the instance's node definitions. A call to the collections FindItemByName method returns a reference to a single node, which, of course, has properties of its own. Putting this all together, returning the Portal URI of a node named UCM would require PeopleCode that looks something like:

Local string &serverUrl = %Session.GetNodes().ItemByName("UCM").PortalURI;

By centralizing the base portion of the URL in a node definition, we save some administration overhead.

Tuesday, August 31, 2010

OpenWorld in Two Weeks!

OpenWorld is almost here! In less than 3 weeks, we will all be together again for the biggest Oracle apps and technology reunion of the year... and possibly, the biggest ever, with JavaOne and Oracle Develop co-located with OpenWorld.

This year I am teaming up with my good friend Graham Smith to deliver the "best of" PeopleTools Tips for 2010. Expect to see more PeopleTools 8.50 content in our presentation this year. Graham and I will be on stage Monday evening from 5:00 PM to 6:00 PM at the Marriott Golden Gate A (session id S317016). You will NOT want to miss this session!

On Thursday you can see Matthew, Pramod, and myself present Monster Mashups, a session about creating mashups using the PeopleTools 8.50 related content framework. That session will be held at Moscone West room 2014 from 12:00 PM to 1:00 PM (session ID S317448).

Besides these sessions, I'll be working the PeopleTools Integration Tools demo pod Monday morning and all of Tuesday. Later during the week, however, I hope to spend some time in the Fusion Apps UI demo pod.

On Wednesday I plan to spend a half hour at the Oracle Bookstore signing copies of my new book. I will be there from 10:00 AM to 10:30 AM during the Meet the Authors time slot. If you have a copy of my book, bring it with you so I can sign it! If you don't have a copy, I'm sure the Oracle bookstore will be more than happy to sell you a copy. Meet the Authors actually runs Monday, Tuesday, and Wednesday from 10:00 AM to 10:30 AM, but I have demo grounds responsibilities Monday and Tuesday, so I won't be able to attend the first two days.

Friday, August 20, 2010

Get Your Kindle Copy

The Kindle edition of my PeopleTools Tips and Techniques book is now available. Download a copy from Amazon's site here.

Thursday, July 22, 2010

Monday, July 19, 2010

Comment Moderation is now On

Sigh... I really didn't want to enable comment moderation. When I post to a blog with comment moderation, I always wonder if my comment will appear. I also like to see my comments appear immediately. What if the moderator is on vacation? Unfortunately, I find myself in a position where I HAVE to enable comment moderation. Some organization has been posting pornographic links as comments on my blog and has been doing this for about six months. I have been diligent in deleting those comments, but then it occurred to me that each subscriber to a post was receiving these links as unsolicited e-mail. I WILL NOT ALLOW ANYONE TO MOLEST MY READERS IN THIS MANNER! I find it utterly distasteful and disgraceful. If someone wants to view pornography, that is their business, but I will not allow my blog to be used to tempt/lure people into pornography. The temptation is too much for some to handle. Just as alcoholism, gambling addiction, or many other social ills start as harmless entertainment, pornography can get way out of hand.

If you have been trapped by one of the e-mail comments sent from my blog and need help, I want to provide you with some resources. I think it is the least I can do. I have no experience in this issue, so I'm just listing what I googled on the topic: Abuse and Addiction: Pornography and Cybersex and Dads.org.

Yes, you can still post comments on my blog. I really, really enjoy reading and responding to comments. I learn a lot from my readers. The unfortunate side affect of this parasite is that you will have to wait for me to read and approve your comments before they appear on this site. I really apologize for this. I wish there was something else I could do.

Wednesday, July 14, 2010

The Book is Shipping, but Where is the Code?

Amazon has been shipping my new PeopleTools book for about a week now... but where is the source code? The honest truth? It is on my laptop. I am in the process of exporting the code from my test PeopleSoft instance. I am up to chapter 9. I am averaging about 2 chapters a night, so I expect to finish this week. Once I finish, I'll send it to Oracle Press and they will post it on their web site at http://www.oraclepressbooks.com/.

Update 15-July-2010: I sent the code to the publisher last night. It is now in their production department awaiting posting. I will update again when it is available.

Update: The code is now available. You can find information in this blog post.

Monday, June 21, 2010

At UKOUG With a Copy of My New Book

I am presenting at the PeopleSoft UKOUG conference this week. It is always a pleasure to catch up with my English and European colleagues. I was able to acquire an advanced copy of my new book PeopleSoft PeopleTools Tips & Techniques. I will have the book with me during the conference, and will gladly show it to anyone who asks.

Tuesday, June 08, 2010

Marketing Flyer for PeopleTools Tips Book

The marketing flyer for my PeopleSoft PeopleTools Tips and Techniques book is now available. If you are unable to see the embedded flyer below, you can download a copy from my box.net account: PeopleTools Tips and Techniques flyer.

Saturday, May 22, 2010

Enable View Source in Online HTML Editor

If you use the Pagelet Wizard or Enterprise Portal's Managed Content features, then you have likely seen the PeopleTools online rich text editor. With PeopleTools 8.50, PeopleSoft switched to the CKEditor and added rich text editor configuration options to App Designer. This allows you to turn any long text field into a rich text field (although I don't recommend doing so, as it can have a negative impact on reporting).

The former rich text editor had a view source button. For security reasons, the PeopleTools team removed the view source button from this release. If you trust the users that have access to rich text editor pages (like the pagelet wizard) and would like to re-enable the view source button, then add 'Source','-', to the config.toolbar array in your rich text editor configuration. Here is a fragment of the configuration file:

        config.toolbar =
[
['Source','-','Maximize','

Where do you find your rich text editor's configuration? The PeopleBooks appendix Creating Custom Plug-in Files for the Rich Text Editor describes how to configure rich text editors on a per-page, per-editor basis. To change the default configuration, open the ckeditor/config.js file in your webserver's domain directory. For example, if your web server domain is named portal, open $PS_HOME/webserv/portal/applications/peoplesoft/PORTAL.war/portal/ckeditor/config.js.

Accessing PeopleCode Rowsets from Java

A reader recently asked how to create instances of the Rowset class from Java. I believe the question was more about IDE and classpath setup than it was about actual Java code. But, since it can be difficult to figure out how to use PeopleCode objects in Java, I thought I would post an example:

package test.peoplecode;

import PeopleSoft.PeopleCode.Func;
import PeopleSoft.PeopleCode.Name;
import PeopleSoft.PeopleCode.Rowset;

public static String getOprDescr(String oprid) {
Name recName = new Name("RECORD", "PSOPRDEFN");
Name fieldName = new Name("FIELD", "OPRDEFNDESC");
Rowset r = Func.CreateRowset(recName, new Object[] { });

r.Fill(new Object[] { "WHERE OPRID = :1", oprid });

return (String)r.GetRow(1).GetRecord(recName).GetField(fieldName).getValue();
}
}

Notice that the first parameter to CreateRowset is a Name object and the second is an empty array. If I were creating a hierarchical Rowset (similar to a component buffer), then I would fill the array with additional Rowset objects, as described by the CreateRowset PeopleBooks entry. Another important difference between PeopleCode and Java is that the "RECORD" and "FIELD" parameters to the Name constructor must be upper case.

Here is some PeopleCode to test this example:

MessageBox(0, "", 0, 0, GetJavaClass("test.peoplecode.RowsetTest").getOprDescr(%OperatorId));

What about the IDE's Java project classpath? If your IDE supports library definitions (like JDeveloper), then add the JAR %PS_HOME%\class\peoplecode.jar as a new library and then add the library to your project.

Tuesday, April 20, 2010

FUNCLIB's and Event Scoped Variables

While writing code for my post JSON Encoding in PeopleCode, I discovered a need for transient variable persistence (acknowledged in that post). Since I originally wrote that code in a FUNCLIB, I thought I could reuse/persist my JavaObject variables by moving those two variable declarations above the function declaration. What I found was that this had no impact on the behavior of my code. The FUNCLIB function continued to initialize a new instance of my JavaObject variables on each call. Now, PeopleBooks says that JavaObject variables are treated a little differently than other variables so we should test to see if this behavior exists for regular variables, like String variables. To test this, create a FUNCLIB that contains this code:

Local string &test;

Function testval() Returns string
If (None(&test)) Then
&test = "new value";
Return "Not initialized";
Else
Return "Initialized";
End-If;
End-Function;

You can then test this code with a PSUnit test case defined as follows:

import TTS_UNITTEST:TestBase;

class Test extends TTS_UNITTEST:TestBase
method Test();
method Run();
end-class;

Declare Function testval PeopleCode JJM_SCOPE_FUNC.FUNCLIB FieldFormula;

method Test
%Super = create TTS_UNITTEST:TestBase("Test");
end-method;

method Run
/+ Extends/implements TTS_UNITTEST:TestBase.Run +/
Local number &idx;
For &idx = 1 To 10
%This.Msg(&idx | ": " | testval());
End-For;
end-method;

What does PeopleBooks say about this? What should I expect to see? Summarized, any variable declared within an event is available to all functions within that event (take me to the PeopleBooks reference for this). Given this information, the code does work... as described. The variable is accessible by the FUNCLIB function. PeopleBooks does not say the value will persist after a FUNCLIB function returns. It is important to make this distinction. Event scoped variables are accessible by all functions within an event, but they do not persist after leaving the scope of an event. In other words, once a FUNCLIB returns, the event scoped variables are discarded. Of course, if you call a FUNCLIB function from the same event that defines the FUNCLIB function, then the variable value will persist for the duration of the calling function. But if you did that, then the FUNCLIB function wouldn't really be a FUNCLIB function. It would just be a function. By definition, a FUNCLIB function is a function defined in a different event.

My conclusion: Locally scoped variables are really locally scoped. They don't maintain state when a FUNCLIB function returns. For a FUNCLIB, locally scoped variables are only relevant if you plan to call other functions within the FUNCLIB function's event from the FUNCLIB function.

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.