Event Mapping in PeopleTools 8.56

By Chris Heller • September 17, 2017

What it is?

Event Mapping is a new subcomponent of the Related Content Framework. PeopleTools released Event Mapping with version 8.55. What makes it relevant today is enhancements for 8.56.

Imagine this scenario:

A functional user approaches you with changes he or she would like you to make to a PeopleSoft page. These changes include hiding a couple of fields and changing some labels. For the most part, this is a simple request, but you hesitate to accept the request. Why? Because the user just asked you to modify a delivered definition and you know that you will spend the rest of your PeopleSoft life paying for any simple change you make today.

Because there is business value (and you prefer to remain employed), you choose to implement the requested changes. But you ask yourself, how can I make these changes while minimizing upgrade impact? Historically speaking, a good developer, recognizing that these changes are possible through PeopleCode, would create a FUNCLIB or Application Class to hold custom code, and then call that reusable definition from the appropriate location within the delivered event PeopleCode. With the introduction of Event Mapping in PeopleTools 8.55, we now have a new option: we can map an Application Class event handler into a delivered PeopleCode event. This is a configuration that won’t dirty your compare reports. Since your code is no longer merged into Oracle’s code, your changes remain after applying updates.

What is new for 8.56?

PeopleTools 8.56 introduces the following long awaited enhancements to 8.56:

  • You can now configure multiple handlers for the same event,
  • PeopleTools exposed the PageActivate event, which is important for some scenarios to work properly,
  • Event mapping now supports the Component Record Field FieldChange event, and
  • Event mapping works in Component Interfaces (technically arrived in 8.55.09).

How are developers using this new feature?

The most obvious use cases involve changing labels and hiding fields. However, every component customization should be reviewed through the lens of Event Mapping. PS WebSolutions is using Event Mapping with their clients to make the address change effective date display only.

Probably one of the most exciting enhancements to Event Mapping is that it now works with Fluid. Technically, it has been supported on Fluid since its initial release, but demonstrated reliability issues until 8.55.15. What makes Fluid Event Mapping so compelling is that, with Fluid, everything is a component. This includes Homepages, Fluid forms builder, and so on. Logesh Balasubramaniam, from Presence of IT, is using Event Mapping to enhance forms created by the Fluid Forms Builder. Colten Fischer is using Event Mapping to send administrators to a classic homepage from the Fluid homepage. Some GreyHeller customers are using Event Mapping to extend Personal Details to collect additional attributes about employees.

What use cases should I avoid?

Many PeopleCode customizations exist to change business logic. These customizations often require changes to reside somewhere in the middle of an existing event. Event mapping only allows for Pre and Post processing. There are no “inject in the middle” or “replace” options. With that in mind, any PeopleCode change that can’t be categorized as pre or post processing is not a candidate for Event Mapping.

Are there any concerns with Event Mapping?

Yes. One concern is lack of lifecycle management support. When patching or updating a component, there is no indication that an event handler may exist in the event mapping framework. Theoretically speaking, that is the point. Event mapping is supposed to simplify lifecycle management, not create additional tasks. The reality, however, is that any change to a component may impact event mapping PeopleCode. If Oracle removes a field from a page, for example, that field is no longer in the component buffer. Any PeopleCode that references that field will fail at runtime. At this time, Oracle offers no tools to make you aware of this. Nor does it provide any indication that a component has event mapping PeopleCode.

Another concern I have is that code that doesn’t behave as expected. Consider this example:

A user logs a service request because a component is not behaving properly. Based on the timing of the improper behavior, you identify that the errant PeopleCode is in the RowInit event of a level 1 rowset. Armed with that knowledge, you open the RowInit PeopleCode of the component, review the code, and everything looks normal. In fact, there is nothing in that code that would cause the behavior you are seeing in the component. Now what?

If you are like me, you will scratch your head, probably burn a few more hours on it, and then spend even longer digging through a PeopleCode trace, only to discover that some App Class is being invoked even though there is no indication of that App Class in the RowInit event. That App Class is configured into the event through event mapping. Users of dynamic languages are quite familiar with this scenario as the impact is akin to something known as Monkey Patching or Duck Punching. We expect a single source for the truth, but what we discover is there are multiple versions, and it is the last version that wins.

Even with these concerns, do I still use and recommend event mapping? You bet! Event mapping is one of the best features introduced to PeopleTools.

Put the Appsian Security Platform to the Test

Schedule Your Demonstration and see how the Appsian Security Platform can be tailored to your organization’s unique objectives

Your security options are set improperly. Please contact your security administrator.

By Chris Heller • September 14, 2010

Hit this error message earlier and noticed that no search engines had the answer so I wanted to share.

When running App Engine from a command prompt or from within App Designer, you get a dialog popping up with

Your security options are set improperly. Please contact your security administrator.

The same error does not popup when you login to App Designer or Query or other 2 tier tool though.

The answer is that somehow you ended up with the PS_SERVER_CFG variable set in your current environment and psae.exe is getting confused as to whether it should be running under the process scheduler or not. Tools like Application Designer don’t get confused because they never run under the process scheduler.

Labels: 2010, PeopleTools, Support

Put the Appsian Security Platform to the Test

Schedule Your Demonstration and see how the Appsian Security Platform can be tailored to your organization’s unique objectives

Query Tip of the day: Creating a Query from a PeopleSoft Component Name

By Chris Heller • June 24, 2010

One of the biggest challenges of end-users is to figure out where the data they want to query against resides. This is yet another tip from our “Advanced Query Tips and Techniques” webinar. By combining the PeopleCode Query Classes with knowledge of the PeopleTools tables underlying the component definition, you can provide a page that allows queries to be easily created.

Getting the Records to put in the Query

Probably the most important part of this is to figure out what records are in a component (and then subsequently weeding out the extraneous records from the list). This information is stored in the PSPNLFIELD and PSPNLGROUP records (the PSPNLGROUP record tells you the list of pages you need to look at, and the PSPNLFIELD record tells you what records and fields are on those pages.

Here is sample peoplecode that will log the list of records from a component.

Now, let’s get to the good stuff

Instead of extending our code to get the records to include in the query, we will start with some code to create a simple query definition, and then extend it accordingly. Here is the code to create a “Hello World” query against the GL_ACCOUNT_TBL record.

Add multiple fields to Query

The next step is to extend our code to add multiple fields to the query definition. For the purposes of this step, we are hard-coding the record alias to be “A”, and passing in the field name for both the query fielname and query field heading.

Here are the modifications to accomplish this.

Determine what fields to add the query

The next step is to add the logic to determine which fields we should add to the query from the current record. As the goal is to identify the records for a starting point, we are taking the approach of adding the key fields of the current record to the query.

This information is stored in the PSRECFIELD table, in the USEEDIT field. This field is a bitmap integer where each base-2 position is a switch for a different attribute.

Here are the modifications to accomplish this.

Dynamically add records to Query

Now that we’ve got a single record and its key fields being added to our query, the next step is to add more than one record by selecting against the PSPNLFIELD and PSPNLGROUP records.

In addition to putting in the appropriate selection and looping logic, it is important to keep track of the alias by which the record will be referred (this ensures that logic for adding the selected fields puts the fields in the proper place). To accomplish this, we are using the Char peopletools function and adding 64 to the index value (65 is the ascii value of “A”).

Here are the modifications to accomplish this.

Expanding to handle more records

Because most components contain a lot of records and fields that do not match the key structure of the component, we limited our logic in the prior step to only bring in the GL_ACCOUNT_TBL. At this point, we will expand it to determine what keys are required from the search record, and then only include records that match at least one field from the search record.

To accomplish this, we will be looking at the PSPNLGRPDEFN record, in the SEARCHRECNAME field. The code creates an array of fields, and then a separate function will compare this array to an array that contains the list of fields of the current record to be added to the query.

Here are the modifications to accomplish this.

Expand to support more complex components

The last step is to address some complexities that come from more complex component definitions. These issues include the following:

  • Components with search records that don’t have any keys (e.g. INSTALLATION table)
  • Related Displays

The related display information is also stored in the FIELDUSE field on the PSPNLFIELD record, but in a different position in the bitmap. For the search records that don’t have any keys, we took the shortcut of taking the first record in the component and using its keys to drive what gets included in the query.

Here are the modifications to accomplish this.

Last Step – Create a Page

The last step is to create a page to allow users to create the queries by picking a component name.

You can go to the project home page to pull down the whole project for your own use. When doing this, you will want to open up the component definition in application designer and use the wizard to add it to the portal registry and add it to one of your permission lists for testing.

Potential Enhancements

The code discussed here does have a number of places where it should be enhanced as part of deploying it widely. Those can be viewed here.

Labels: PeopleCode, Query

Put the Appsian Security Platform to the Test

Schedule Your Demonstration and see how the Appsian Security Platform can be tailored to your organization’s unique objectives

Functional Testing of PeopleSoft Applications

By Chris Heller • May 10, 2009

In our previous blog post we introduced a Continuous Integration server called Hudson to run automated tests against a PeopleSoft system for us. We didn’t really get into any test content itself though; we just ran some dummy Application Engine code to show how Hudson can work with PeopleSoft.

Now that the framework piece is in place though, we can begin creating tests. For this blog post we’re going to focus on testing functional aspects of PeopleSoft, not performance testing.

Functional Testing Webinar

Before getting into the details, I wanted to point out that we have an upcoming webinar on this exact topic on May 20th, so if you’re interested be sure to register for that.

Functional Testing Options

There’s a variety of different ways for doing functional testing. Dave Bain from Oracle recently announced the availability of a PeopleSoft specific unit testing tool called PS/Unit. We’ll look at PS/Unit in an upcoming post, but if you’re not familiar with unit testing you can think of it as testing the lowest level “units” of your code.

Another approach is doing browser-based testing. Browser based testing means having automated tests that drive an actual web browser instance and simulate an actual user’s behavior. The primary benefit of browser based testing is that you can be reasonably sure that you are testing PeopleSoft as your users will be seeing and working with it.

In fact, browser based testing can be thought of as automating the way that most people test their PeopleSoft implementations today; which, sadly enough, is by having some of their functional users run through a series of manual tests in their browsers. Expensive, error-prone, incomplete are some of the adjectives that spring to mind when describing manual testing. Not to mention just the hassle of trying to get the functional users to participate in testing. They may be willing to participate once or twice, but if you need to incorporate any additional testing beyond your original project plan, it is a major hassle.

Browser based testing

Back in the old client/server days, PeopleSoft used to bundle SQA Robot (later purchased by Rational/IBM) because it understood something about the Windows controls used PeopleTools for creating the UI. These days the PeopleSoft UI is HTML/Javascript and there are lots of freely available tools that we can use for testing.

One tool that we like for functional testing via the browser is called Selenium. Selenium can run tests across multiple different browsers, and there is a plugin for running Selenium tests via Hudson so we can automate the execution and management of our automated tests.

Getting Started with Selenium

Before we get into running our Selenium tests in Hudson, we need to create some tests. The easiest way to do this is with the Selenium IDE, which is a Firefox plugin and can record, edit and debug tests. The tests themselves can be executed in other browsers, so we’re only dependent on Firefox for our initial test development.

So if you’re not already in Firefox right now, switch over to Firefox and launch the Selenium IDE plugin installer. Here is the current link, which is for version 1.0 beta 2, but you can check their download page as well to see if there are updated versions available.

Your First Test

After installing Selenium IDE, you can launch it from the Tools -> Selenium IDE menu in Firefo

When Selenium IDE launches it automatically starts in recording mode, so you can switch back to your main Firefox window and begin working. For this first example, I

  • Entered the URL for the PeopleSoft signon page
  • Typed in the password (the user ID was already defaulted for me)
  • Clicked “Sign In”
  • Waited for the portal home page to load
  • Clicked the “Logout” link

Then I switched back to the Selenium IDE window and stopped recording by clicking the Red button in the recording toolbar (unfortunately there are not keyboard equivalents for the Selenium recording toolbar). Let’s see what Selenium captured for us.

The first thing to notice is that Selenium has captured http://www.gsdemo.com as the base URL. All URL commands will be relative to this (there are some extra things to know if you plan to write tests that go across more than one base URL).

The first actual command that Selenium has captured is the open command. All Selenium commands take the form of Command, Target, Value. Here the target is the URL /psp/ps/?cmd=login, which gets combined with the base URL. This opens the login page.

Next is the type command, where we entered the password. In this case Selenium realized that the pwd DOM ID for the password field was the simplest choice that would work. You’ll notice that the Target editbox has become a dropdown box which shows some alternate methods of referencing the password field.

The next command is the clickAndWait command. Selenium recognized the Sign In button via it’s DOM ID of Submit and used that.

The last command is also the clickAndWait command, but when we logged out, there was no unique DOM ID that was associated with the Logout link, so Selenium recognized the click by noting that the link text was Logout (eagle-eyed readers will note that the logout text in this environment has actually been customized from an earlier blog entry.

At this point

we can repeatedly click the play button in the Selenium recording toolbar and watch the browser login and logout. There are actually two play buttons, one is for the current test case and one is for playing back an entire test suite (which is many test cases grouped together). In this instance it doesn’t matter, but once you actually create test suites, then you’ll want keep them straight in your head.

If you have any problems at this point playing back the test, you can try adjusting slider control in the Selenium IDE toolbar towards the “Slow” end. This causes Selenium IDE to wait a bit longer for commands to show some results.

Modifying the Test

Each of the commands that Selenium captured for us is modifiable, including being able to delete it. So to make our test a bit more exciting, we highlight the last command (the clickAndWait command that logged out) and delete it, since we don’t want to logout just yet.

Then play the script again. This time when the script finishes you will be left on the portal home page (since we’re not logging out). Pressing the Record button will start recording again at the end of the script.

This time we will navigate via the portal menu navigation to PeopleTools -> Security -> User Profiles -> User Profiles. Once the User Profiles component search page is up, we type in PTDMO and then press the Search button.

Before playing back this script, I made one manual edit. I changed the very top command from navigating to cmd=login to cmd=logout. PeopleTools will send back the signon page in both cases, but by starting with the logout command you clear server resources from the previous script run. Your system administrators will thank you for this small act of kindness 🙂

When playing back the script, I have a small problem; the script does not playback successfully. It tells me that it can’t find a frame named “NAV”.

The source of the problem is that Selenium does not quite understand the way that PeopleTools is doing it’s navigation. When we click on the first navigation link from the Portal home page Selenium records the following two commands.

  • Command: clickAndWait, Target: link=(the name of the link)
  • Command: waitForPopup, Target: _parent, Value: 30000

After we leave the portal home page though, we are getting into PeopleTools generated HTML frames. The next command that Selenium IDE records is Command: selectFrame, Target: name=NAV. The NAV is the name that PeopleTools gives to the menu frame once we leave the portal home page. What we want at this point is for Selenium to wait for the NAV frame to load before we select it, but the commands that Selenium is generating are only waiting for our top level page to load.

Rather than run the entire test at a slower rate so that we’re sure that the navigation frame is loaded, we’ll insert an extra command before the selectFrame command that will tell Selenium to wait for that frame to load. To do that we

  • Right click on the selectFrame
  • Select “Insert New Command” from the popup menu
  • Type waitForFrameToLoad in the Command edit box
  • Type TargetContent in the Target edit box
  • Type 30000 in the Value edit box

One cool thing about Selenium IDE is that as you enter commands, you get type-ahead support for what commands are available. Once you select a command, then you get the help text for it automatically displayed at the bottom of the IDE window. Very nice! Here’s a full list of all of the different commands that Selenium supports.

The other nice thing about the IDE is that we can copy and paste our waitForFrameToLoad command instead of typing it in everywhere. There are a few more places in our navigation script where the selectFrame command is used, so we’ll paste it before each one. That allows us to run our script at full speed, but have it wait for the navigation to finish loading if the webserver is slow for some reason. Note that after we finish navigating we are then waiting for the frame called “TargetContent”, so we use that name instead.

Skipping Navigation

In some cases you may want to include the navigation portion in your tests, but in many cases you’ll want to just test a certain functional area. A good trick for doing this is just have Selenium go straight to the page that you want instead of navigating to it.

The open command in Selenium can be used in the middle of your test scripts, not just at the beginning, so we can replace all of the navigation commands with the open command and the value “/psc/ps/EMPLOYEE/PT_LOCAL/c/MAINTAIN_SECURITY.USERMAINT.GBL”. Note that we used the psc servlet instead of the default psp. That will completely bypass the portal and just load the underlying component itself.

Making Assertions

Part of testing is making assertions about the behavior that is seen. Selenium supports making assertions about things as simple as text being on the page to whether specific cookies are present (a good way of checking that you are logged in is asserting that you have a PS_TOKEN cookie) and even as detailed as making assertions about the HTML source code that has been generated for a page.

There are somewhere in the neighborhood of around 100 different things that Selenium can make assertions about, so you should be able to come up with something that helps you validate the desired functional behavior that you are testing. For the example that we have been going through I added a the assertTextPresent command with the Target set to “Confirm Password” since that is the label for one of the fields on the User Profile page. This assertion will return true if “Confirm Password” is present anywhere on the entire page.

We can make some tighter assertions by narrowing the focus from the entire page to specific elements. The above assertion could also be implemented as the assertText, which takes an element locator in addition to the text that is being checked. We saw the use of element locators when working with the login form above; an element locator is just some way of identifying a single element on the page.

PeopleTools will typically generate unique IDs for each form field on page. These are normally the underlying record name and field name for the underlying value in the database (with row numbers if working with multiple rows). In the case of checking a label, PeopleTools will generate a “label” element, with the “for” attribute referencing which form field the label is for.

In this case, we end up with our element locator as //label[@for='PSUSRPRFL_WRK_OPERPSWDCONF'] because we want the label for the OPERPSWD_CONF field from the PSUSRPRFL_WRK record.

In the above screenshot we can see that our assertTextPresent assertion passed, but our assertText assertion failed. Selenium IDE provides the reason for the failure in the log though. The assertText assertion is making an exact check of the element’s text, while the assertTextPresent assertion merely checks that the text exists at all across the entire page. Since PeopleTools is generating a colon at the end of the label, we would need to incorporate that in our assertion.

Saving Our Tests

Up to this point we haven’t saved our test logic at all. In the Selenium IDE window, select File -> Save Test Case (or press Ctrl-S for a keyboard equivalent). You’ll be prompted for a file name. I selected TestUserProfile.html. By default Selenium stores it’s list of commands in a special HTML format, which is why we used that extension.

You can also have Selenium export a test case to various other programming languages (Java, C#, Perl, PHP, Python, and Ruby are all supported currently). The benefit to using a programming language for controlling Selenium is that you can then do things like have conditional logic, common subroutines, etc. The drawback is that you lose the ability to use Selenium IDE to work with them at that point. We’ll stick with the plain HTML format for now while we’re still getting familiar with Selenium, but future blog posts will get into using the programming language integration.

In the above screenshot you’ll notice that I expanded out one section of the Selenium IDE window. That shows us the current test case being worked (TestUserProfile). If we click File -> New Test Case, we’ll get an “Untitled” entry added in the list of test cases and an empty list of commands. We would then build up our test case just like we did above and save it.

Once you have a few test cases, then you can select File -> Save Test Suite to group the test cases together. You can copy and paste commands between different test cases, so if you find one test case is starting to cover too much functionality, you can break it into multiple test cases this way. Selenium lets you run either single test cases or run an entire test suite together, so you can break things in manageable chunks without losing the ability to group things appropriately.

I did notice a bug in the test case re-ordering within the Selenium IDE window though, so after you save your test suite, you’ll probably want to open to the open the test file and re-order the tests listed there so they match the order that you want to run things in.

Wrapping Up

Now that we can start coming up with some meaningful tests, the next steps from here are to incorporate the execution of our tests into our Hudson environment so that they can be run automatically for us.

We’ll also want to start integrating all of this with version control. We not only want to start keeping track of the different versions of our tests, but we also want to tie the execution of our tests to changes that are happening with the underlying PeopleSoft objects.

That way if a working test starts throwing assertion errors at some point, we’ll be able to see exactly what changes were implemented in PeopleSoft that caused the problem, along with who did it and the underlying reason that the changes were implemented. And if necessary, revert those changes to put things back the way they should be.

Labels: 2009, Browser, Sysadmin, Testing, VersionControl

Put the Appsian Security Platform to the Test

Schedule Your Demonstration and see how the Appsian Security Platform can be tailored to your organization’s unique objectives

Overlapping Text in PeopleSoft and Safari 3.x

By Chris Heller • November 14, 2008

A little while back we had a request for help from Genentech to solve an issue that was causing them fits. They have several Macs used for ESS and MSS that run Safari 3.x, the latest browser from Apple. The problem is that when Apple released Safari 3.x, they changed a tiny behavior of the DIV tag. The height attribute is now absolute instead of the minimum height. The result is that certain pages now display overlapping text.

So, the question was how to solve this problem without rewritting every page that has this issue.

The solution is extremely simple; add a style that defaults all DIV tags to set the display style to inline. In order to accomplish this on all pages, you need to add a small set of JavaScript to one of the PeopleSoft delivered scripts, for example PT_SAVEWARNINGSCRIPT. Simply open App Designer and the HTML object PT_SAVEWARNINGSCRIPT. At the head of the object add the following lines:


if ( navigator.appVersion.indexOf( "Version/3" ) > 0 &&
navigator.appVersion.indexOf( "WebKit" ) > 0 ) {
document.write('<style> div { display:inline; } </style>');
}



The code checks the version string for two things, the start of the version number (Version/3) and the browser rendering engine (WebKit). This allow us to only modify the HTML when the browser is Version 3 of the WebKit engine. That means both Safari and Google Chrome will get the extra style tag.

One caveat is that if you upgrade PeopleSoft, you will need to add this script back to the delivered HTML object.

That’s it.

Labels: DIV, Mac, PeopleSoft, Safari

Put the Appsian Security Platform to the Test

Schedule Your Demonstration and see how the Appsian Security Platform can be tailored to your organization’s unique objectives

PeopleSoft telephony two factor authentication

By Chris Heller • July 7, 2008

(update : check out demo 4 in the Flash demo for our ERP Firewall to see some additional real world examples of multi-factor authentication for PeopleSoft)

At the end of the previous blog entry on PeopleSoft telephony integration, we were able to initiate telephone calls from within PeopleCode to an end user and prompt them to enter a PIN code so that we could authenticate them. We could even see in the logs whether the user typed the correct PIN on their phone or not.

What we didn’t cover was how we can figure out in PeopleCode whether the user typed the correct PIN or not. If we can’t do that, then there’s not much point to the whole exercise, eh? 🙂

To make a long story less long, you can’t (easily) find out the answer with just PeopleCode. There are a couple of reasons for this. One was hinted at yesterday; the Asterisk Manager API call origination only allows you to get the call going. Once the call has been successfully initiated, then the call origination reports success. Even if the user does not answer the phone, the call itself was successfully originated.

If you want to find out what happened, then you have to use another part of the Asterisk Manager API, the events API. That allows you to listen to various events happening within Asterisk. So after we originate the call, there will be a series of events triggered (Dial events, Hangup events, Callerid events, etc.) that we can use.

The good news is that the Asterisk-Java library that we’re using has great support for the events API. There are Java classes for all of the different events that occur within the Asterisk server. For example, here’s someone else’s sample Java code of catching the Dial event and using that for screenpops.

In order to register which events that you are interested in though, the Asterisk-Java library require you to implement a Java interface called ManagerEventListener. Implementing interfaces is no big deal when you’re writing Java code; it’s just a list of methods that you have to provide the actual code for. However PeopleCode can’t implement Java interfaces, so we’re going to need to write some Java code ourselves.

In order to make this work, I needed to create two separate Java classes. The first one is called PSoftLoginManager. In addition to implementing the ManagerEventListener interface, I moved the PeopleCode logic for doing the call origination into this class as well.

The other class that I needed to create is called PSoftLoginEvent and it extends the delivered UserEvent class. The Authenticate command that we are using in Asterisk does not actually create any events for us to listen to. However, Asterisk supports the notion of user defined events, so we can use that. At the moment the PSoftLoginEvent includes the PeopleSoft user ID and whether the login attempt was successful or not.

The Java code wasn’t too bad. 100+ lines or so, but let’s look at how the PeopleCode looks now first. All of the variable declarations at the top stay the same, but the rest of the code is now just

Local JavaObject &loginMgr = CreateJavaObject("com.greysparling.asterisk.PSoftLoginManager", &host, &user, &pswd);
Local JavaObject &loginEvt = &loginMgr.challengeUser(&userID, &phone, &pinCode, &channel, &context, &exten);
If &loginEvt.isSuccess() Then
Warning ("Successfully validated user " | &userID);
Else
Warning ("Off with " | &userID | "'s head!");
End-If;



We create our PSoftLoginManager object with the Asterisk server information, and then call challengeUser to initiate the call and get the result back. Instead of having challengeUser return a binary result of success or not, I return the actual PSoftLoginEvent object itself which can be queried for success or failure. That keeps things easy later if we need to expose any additional data back from the Asterisk side.

On the Asterisk configuration side, we need a small change in order to trigger our login event.

[challenge-psft-user]
exten = 7189,1,Answer()
exten = 7189,2,Playback(vm-intro)
exten = 7189,3,NoOp(Authenticating user ${PSFTUSERID})
exten = 7189,4,Authenticate(${PIN},j)
exten = 7189,5,NoOp(Successful login ${PSFTUSERID})
exten = 7189,6,UserEvent(PSoftLogin|userId: ${PSFTUSERID}|result: SUCCESS)
exten = 7189,7,Hangup()
exten = 7189,105,NoOp(Unsuccessful login ${PSFTUSERID})
exten = 7189,106,UserEvent(PSoftLogin|userId: ${PSFTUSERID}|result: FAILURE)
exten = 7189,107,Hangup()



The main difference is the use of the Asterisk UserEvent command. That takes the name of our login event as it’s first parameter. The additional parameters are the ones that we have defined for our event. The Asterisk-Java library will automatically map these extra parameters that we have defined into the appropriate setter calls on the Java PSoftLoginEvent object (e.g. userId maps to setUserId() ).

Now when we run the App Engine program, here’s what the Asterisk console looks like.

    -- Executing [7189@challenge-psft-user:1] Answer("IAX2/123456789-2", "") in new stack
-- Executing [7189@challenge-psft-user:2] Playback("IAX2/123456789-2", "gs-demo-login") in new stack
-- Playing 'gs-demo-login' (language 'en')
-- Executing [7189@challenge-psft-user:3] NoOp("IAX2/123456789-2", "Authenticating user PTDMO") in new stack
-- Executing [7189@challenge-psft-user:4] Authenticate("IAX2/123456789-2", "4554|j") in new sta ck
-- Playing 'agent-pass' (language 'en')
-- Playing 'auth-thankyou' (language 'en')
-- Executing [7189@challenge-psft-user:5] NoOp("IAX2/123456789-2", "Successful login PTDMO") in new stack
-- Executing [7189@challenge-psft-user:6] UserEvent("IAX2/123456789-2", "PSoftLogin|userId: PTDMO|result: SUCCESS") in new stack
-- Executing [7189@challenge-psft-user:7] Hangup("IAX2/123456789-2", "") in new stack
== Spawn extension (challenge-psft-user, 7189, 7) exited non-zero on 'IAX2/123456789-2'
-- Hungup 'IAX2/123456789-2'



You can see the addition of the UserEvent here, plugged in with the name of our event, the PTDMO user and the result value of SUCCESS. Since this event is what our challengeUser function returns to PeopleCode, we can now properly do the two factor authentication from PeopleSoft.

Here’s the Java code for the PSoftLoginEvent class. The things that are worth noting is that it extends the org.asteriskjava.manager.event.UserEvent class, and that it has setters for the values that we want to receive. It appears as though you can only get strings from the Asterisk side, so I adopted the convention that result has to be the string SUCCESS for a successful login.

package com.greysparling.asterisk;

import org.asteriskjava.manager.event.UserEvent;

public class PSoftLoginEvent extends UserEvent {

private String userId;
private String result;

public PSoftLoginEvent(Object source) {
super(source);
}

public String toString() {
return "PSoftLogin for " + getUserId() + " (" + result + "),"
+ super.toString();
}

public boolean isSuccess() {
return "SUCCESS".equals(result);
}

public void setResult(String result) {
this.result = result;
}

public String getUserId() {
return userId;
}

public void setUserId(String userId) {
this.userId = userId;
}

private static final long serialVersionUID = 1L;

}


Here is the Java code for the PSoftLoginManager class. This is a little bit more complex, but not too bad. We implement the ManagerEventListener class so that the Asterisk-Java library knows we want to receive events. That means that we have to provide an “onManagerEvent” method.

Our implementation of onManagerEvent is pretty simplistic – we just check if the event is a PSoftLoginEvent, and whether the user ID in that event matches the one that we just sent. A more robust implementation would use some unique IDs for matching up the exact logins; not just check the user ID. We’d also want to check for other related events such as the user hanging up without even trying to enter the PIN code.

Another thing worth pointing out here is that in the Asterisk-Java library, event notifications come in on a different thread. PeopleCode only runs single threaded, but when you are calling Java from within PeopleCode, there may be multiple threads running from within the Java Virtual Machine.

In this demo implementation, we’re cheating a bit by just sleeping on the main thread while we wait for the login event to come back. It works just fine for our purposes here, but it’s definitely not production ready code. We’d probably also want to have some sort of a connection pool instead of logging in to the Asterisk server on every request.

package com.greysparling.asterisk;

import java.io.IOException;
import java.util.HashMap;

import org.asteriskjava.manager.AuthenticationFailedException;

import org.asteriskjava.manager.ManagerConnection;
import org.asteriskjava.manager.ManagerConnectionFactory;
import org.asteriskjava.manager.ManagerEventListener;
import org.asteriskjava.manager.TimeoutException;

import org.asteriskjava.manager.action.OriginateAction;
import org.asteriskjava.manager.event.ManagerEvent;
import org.asteriskjava.manager.response.ManagerResponse;

public class PSoftLoginManager implements ManagerEventListener {

private ManagerConnection connection;
private boolean keep_running = true;

private String userId;
private PSoftLoginEvent event;

static final String USERID = "PSFTUSERID";

static final String PIN = "PIN";

public PSoftLoginManager(String host, String user, String pswd) throws
AuthenticationFailedException, TimeoutException, IOException {

ManagerConnectionFactory factory =
new ManagerConnectionFactory(host, user, pswd);

connection = factory.createManagerConnection();
connection.addEventListener(this);

connection.registerUserEventClass(PSoftLoginEvent.class);
connection.login();
}

public PSoftLoginEvent challengeUser(String userId, String phone,
String pinCode, String channel, String context, String exten)

throws InterruptedException, TimeoutException, IOException {

this.userId = userId;
this.event = null;

HashMap vars = new HashMap();

vars.put(USERID, userId);
vars.put(PIN, pinCode);

OriginateAction action = new OriginateAction();
action.setChannel(channel + "/" + phone);

action.setContext(context);
action.setVariables(vars);
action.setExten(exten);

action.setPriority(new Integer(1));

ManagerResponse response = connection.sendAction(action, 30000);

System.out.println(response.getResponse());

while (keep_running)
Thread.sleep(100);

return this.event; // caller can check isSuccess()

}

public void onManagerEvent(ManagerEvent event) {
if (event instanceof PSoftLoginEvent) {

PSoftLoginEvent login_event = (PSoftLoginEvent)event;
System.out.println("Login event: " + login_event);

if (userId.equals(login_event.getUserId())) {
System.out.println("Matched user");
this.event = login_event;
this.stop();
}

else {
System.out.println("User was " + login_event.getUserId());
}
}
else {
System.out.println("Event: " + event);
}
}

public void stop() {
keep_running = false;
}

}



So there you have it. We’ve successfully made phone calls from within PeopleSoft, challenged user for a PIN code, and take action accordingly. Everything that we need for two factor authentication of PeopleSoft users, and our total cost is still under one dollar.

(updated with syntax highlighting)

Labels: , , , , ,

Put the Appsian Security Platform to the Test

Schedule Your Demonstration and see how the Appsian Security Platform can be tailored to your organization’s unique objectives

PeopleSoft IVR Integration the easy way

By Chris Heller • July 6, 2008
Yesterday’s blog post on Java 5 and PeopleTools 8.49 (and my high hopes that it would have fixed something that been annoying me) didn’t have a happy ending. PeopleTools doesn’t (yet) take advantage of the some of the Java 5 features, so we have to stick with some workarounds for the time being. PeopleTools 8.49 using Java 5 does provide me another good new thing to work on though. The open source Asterisk-Java library requires Java 5 at a minimum, but with PeopleTools 8.49, that’s OK. What the library provides is a good Java level interface into the open source Asterisk telephony platform. There’s tons of material about Asterisk available, both online and printed books, so this blog post won’t go into too much background on it. If you want to follow along though, you can’t go wrong with the different downloadable Asterisk appliances out there (PBX in a Flash, AsteriskNOW, etc.). This blog post is actually sort of a lead-in for one of the sessions that I submitted for this year via Mix was called “Telephony Integration with PeopleSoft on the cheap”. I don’t know whether that session will actually make it in for OpenWorld (there are an astounding number of sessions that have been submitted via Mix), but it’s a good topic, so I thought I’d start blogging it. For this blog post, what we want to do is be able to initiate a phone call to the end user from PeopleSoft, prompt them for a PIN code, and take action in PeopleSoft depending on whether they were successful or not. We may want to do this as part of the initial signon process for two-factor authentication of your PeopleSoft users, or we might tie this in with some business logic (e.g. be really sure who is sending off a large wire transfer). For the business logic use case, we recommend that you utilize an application firewall to simplify the configuration and maintenance of it. Our own ERP Firewall for PeopleSoft product has a demonstration on our product page of doing 2-factor authentication using Instant Messaging technology (a similar technique to that used here). For testing, I always like to whip up an App Engine test program for playing around. Let’s take a look at our first attempt at the code, then we’ll look at the Asterisk configuration.
REM Connectivity to Asterisk server. ;
REM Assumes that &user has been granted access to Asterisk Manager API;
Local string &host = "192.168.1.200";
Local string &user = "asterisk_user_id";
Local string &pswd = "asterisk_password";

REM Asterisk information for how we will be originating phone calls;
Local String &voip_provider_acct = "123456789</code><code>";
Local string &channel = "IAX2/" | </code><code>&voip_provider_acct</code><code>;
Local string &context = "challenge-psft-user";
Local string &exten = "7189";

REM The phone number that we will be calling;
REM This would normally come out of the user's profile;
Local string &phone = "15105551212";

REM The PeopleSoft userID of the person to call;
Local string &userID = "PTDMO";

REM This PIN could be autogenerated or stored in the DB;
Local string &pinCode = "4554";


REM ******** No more user variables below here *********** ;
Local string &base = "org.asteriskjava.manager.";
Local JavaObject &connectFactory = CreateJavaObject(&base | "ManagerConnectionFactory", &host, &user, &pswd);

Local JavaObject &connection = &connectFactory.createManagerConnection();
&connection.login();

Local JavaObject &originateAction = CreateJavaObject(&base | "action.OriginateAction");
Local JavaObject &vars = CreateJavaObject("java.util.HashMap");
&vars.put("PIN", &pinCode);
&vars.put("PSFTUSERID", &userID);
&originateAction.setChannel(&channel | "/" | &phone);
&originateAction.setContext(&context);
&originateAction.setVariables(&vars);
&originateAction.setExten(&exten);
&originateAction.setPriority(CreateJavaObject("java.lang.Integer", 1));

Local JavaObject &originateResponse = &connection.sendAction(&originateAction, 30000);
Warning (&originateResponse.getResponse());
After setting up a bunch of variables that we’ll need, the code begins by connecting to the Manager API of our Asterisk server. We’ll need a user/password here that has appropriate access to the Manager API; there probably won’t be any in your default install of Asterisk so you’ll need to add one. This is not an end-user account though; it’s a service account that should be treated with the appropriate security. The other configuration note here is that the Manager API lets you limit access with an IP address range for each user, so in addition to having a strong password, you should limit the user to only be able to connect from your PeopleSoft servers. After that, the code creates a call origination object. The Asterisk Manager API allows us to originate a call between a channel and an extension that is the Asterisk dialplan. A channel can be a lot of things (different Voice over IP protocols, regular phone network, etc.). In this case, I’m using a IAX2 channel that I’ve previously defined in Asterisk to use a VoIP (Voice over Internet Protocol) connection to the outside world. IAX2 is an Asterisk specific protocol similar to the more commonly known standard SIP (for example, both Oracle and BEA have SIP servers). The nice thing is that you don’t need to know too much about the channel details for experimentation purposes though. For example, for this test, I use a pay-by-the-drip VoIP provider called CallWithUs. There’s other providers out there (and we use some of those also), but CallWithUs have a nice web based provisioning system. Sign up through their webpage, send ’em some money, and you’re all set. You then supply the info that they give you to Asterisk, so when you ask Asterisk to make a call, it can send it along through CallWithUs, who then connect the call. If you’re using one of the Asterisk appliances, then you can probably get your first call happening in 30-60 minutes or so. The nice thing about doing it this way is that this can all be running on standard computer equipment. Everything that we’re doing is software based, and CallWithUs deal with the part of actually connecting to “real” phone system. As part of the channel definition for the Asterisk Manager API, we add the end-user’s phone numb er to our “dial string”, which Asterisk sends off to CallWithUs, who make the phone call to the end user. That’s one half of the call equation; my mobile phone is now ringing! Here’s what we see in the Asterisk console with the logging turned up.
  == Parsing '/etc/asterisk/manager.conf': Found
== Manager 'asterisk_user_id' logged on from 192.168.1.11
-- Call accepted by 64.85.163.184 (format ulaw)
-- Format for call is ulaw
As soon as I answered the phone, then the Asterisk console shows
      > Channel IAX2/123456789-2 was answered.
If you’ll remember our original requirements, we needed some programmatic validation of a PIN code through the phone. The cool thing about Asterisk is that there are tons of built-in options for how you handle a call coming in. Here’s a snippet from the Asterisk configuration. This defines what Asterisk calls a “context” and what to do with a call into that context for extension 7189. The name of the context and the extension don’t really matter; we just need for our PeopleCode to match up with Asterisk has.
[challenge-psft-user]
exten => 7189,1,Answer()
exten => 7189,2,Playback(gs-demo-login)
exten => 7189,3,NoOp(Authenticating user ${PSFTUSERID})
exten => 7189,4,Authenticate(${PIN},j)
exten => 7189,5,NoOp(Successful login ${PSFTUSERID})
exten => 7189,6,Hangup()
exten => 7189,105,NoOp(Unsuccessful login ${PSFTUSERID})
exten => 7189,106,Hangup()
One thing that may be a little confusing here is that we’re using Asterisk from two sides. One is our code that is calling the Asterisk Manager API that is initiating the call between my mobile phone and this context/extension definition within Asterisk. However, the Manager API is just initiating the call and then it’s done. The context/extension definition within Asterisk then says what to do with the call to this context/extension. Line 1 of the context/extension definition says to Answer the call. Line 2 plays a message that explains what is happening. On my mobile phone, I hear “This is the Grey Sparling demo login. “. Line 3 just logs what is happening on the Asterisk console. We use one of the variables that was set in the PeopleCode side so that we can match up calls with the PeopleSoft user accounts. Line 4 is a builtin Asterisk command to challenge the user to type in a PIN code. Here we’re using the PIN code that was set from the PeopleCode side. There are a series of voice prompts already delivered in Asterisk that get played as part of this as well as Asterisk “listening” for the DTMF tones from the buttons on the phone being pushed. You can roll your own handling of this within Asterisk (speech recognition anyone?), but the Authenticate command has a lot of built-in functionality for free so we make use of that. One strange thing worth mentioning here is the “j” parameter after the PIN code. That tells Asterisk to jump “+101” if the command fails (it continues on the next line if successful). It’s a bizarre form of GOTO (note that there are other ways of adding logic to Asterisk though). Then we log what happened on the console (depending on whether we get the right PIN code or not) and then hangup the phone. Here’s what the Asterisk console looks like with an invalid login.
    -- Executing [7189@challenge-psft-user:1] Answer("IAX2/123456789-2", "") in new stack
 -- Executing [7189@challenge-psft-user:2] Playback("IAX2/123456789-2", "gs-demo-login") in new stack
 --  Playing 'gs-demo-login' (language 'en')
== Manager 'asterisk_user_id' logged off from 192.168.1.11
 -- Executing [7189@challenge-psft-user:3] NoOp("IAX2/123456789-2", "Authenticating user PTDMO") in new stack
 -- Executing [7189@challenge-psft-user:4] Authenticate("IAX2/123456789-2", "4554|j") in new stack
 --  Playing 'agent-pass' (language 'en')
 --  Playing 'auth-incorrect' (language 'en')
 --  Playing 'auth-incorrect' (language 'en')
 -- Executing [7189@challenge-psft-user:105] NoOp("IAX2/123456789-2", "Unsuccessful login PTDMO") in new stack
 -- Executing [7189@challenge-psft-user:106] Hangup("IAX2/123456789-2", "") in new stack
== Spawn extension (challenge-psft-user, 7189, 106) exited non-zero on 'IAX2/123456789-2'
 -- Hungup 'IAX2/123456789-2'
We can see that in this instance I didn’t type in the PIN code correctly, so I wouldn’t have been granted access. Here’s the relevant bits from the Asterisk console of a successful login.
    --  Playing 'agent-pass' (language 'en')
  --  Playing 'auth-thankyou' (language 'en')
  -- Executing [7189@challenge-psft-user:5] NoOp("IAX2/123456789-3", "Successful login PTDMO") in new stack
  -- Executing [7189@challenge-psft-user:6] Hangup("IAX2/123456789-3", "") in new stack
But how do we find that out within the PeopleCode side so that we can actually take action? That will have to wait until part 2 One final note here. I mentioned that CallWithUs is pay by the drip. One reason that is important is because the pay by the drip VoIP providers are typically more open to initiating multiple calls at once (since you’re not paying a flat fee), which is something that you’d need for doing this for authenticating users. Once you’re comfortable with doing this sort of thing, then you might want to get your own internal phone folks involved, but since this sort of thing is fairly uncommon at this point, you’ll probably be on your own for your initial experiments in this. The nice thing is that it’s fairly cheap though. For each call to my mobile phone that was initiated while testing this I paid US$0.0125. So you get 80 login attempts for a buck 🙂

Put the Appsian Security Platform to the Test

Schedule Your Demonstration and see how the Appsian Security Platform can be tailored to your organization’s unique objectives

A Brief History of PeopleTools 9

By Chris Heller • May 24, 2007

Preface

Readers should note that this post is about PeopleTools 9, not PeopleSoft 9 applications. PeopleSoft 9 has shipped to customers. PeopleSoft 9 applications are based on the PeopleTools 8.4x codeline.

Take One

When I was reading Larry’s post about the history of Tree Manager I said

“Hey Larry, late 1998 wasn’t PeopleTools 8. “

“It wasn’t?”

“No, it was PeopleTools 9. “

When work first started in late 1998 on what is now known as PeopleTools 8, the code base was originally named PeopleTools 9, but was later renamed to PeopleTools 8.

Part of the problem was that the PeopleTools group was ready to start working on that release before there was much of an applications plan in place. The general thinking at the time was that the release would be too large to have it just be a “.5” release (1).

There were already designs / proof of concept code in place for things like

* Application Messaging (using this new fangled thing called XML)
* Component Interfaces (these were originally known as Business Components, which is why all of the Component Interface tables are prefaced with BC)
* Business Interlinks
* Unicode
* Java in PeopleCode
* LDAP / Roles / Signon PeopleCode

That’s quite a bit of stuff for one release, and this is even before the whole “no code on the client” stuff appeared.

As proof of how much work went into the release prior to going completely browser-based, look no further than the “Insert” menu in Application Designer.

Notice that option for inserting ActiveX controls? That was actually built for the Windows client, not the browser. Technically it is still supported with the 8.1x tools, but I would strongly recommend against using it. The menu option should have been removed when 8.4 came out (code cruft is a topic for a separate blog post some day).

I don’t remember how the decision to rename it as PeopleTools 8 happened though. There was lots of turmoil inside PeopleSoft then (falling sales, layoffs, new CEO, Vantive acquisition, etc), so it’s hard to say. I do know that build managers just love writing scripts to rename everything though. Not!

Take Two

So when did PeopleTools 9 next show up? Believe it or not, in late 2001. We were planning on having a small new version of PeopleTools to clean up a few things in the 8.1x codeline that we wished we had been smart enough to think about before shipping it. This would have been similar in scope to 7.5 PeopleTools; a follow on release after a major technology shift that served to be a long term stable base, while new development continued on. There was even talk of just calling it PeopleTools 8.2 and making it a mandatory release for customers. While that was going on we began working on the Next Big Thing for PeopleTools 9.

A couple of things conspired against that go around for PeopleTools 9. One was that the name “PeopleSoft 8” had some off the chart brand recognition (at least according to our marketing folks), so there was some senior management push to not lose that name right away.

The other was that customers were still wrestling with a lot of the details of running large scale PeopleSoft 8 deployments and there was quite a bit of pressure to make the follow on release deal with a lot of those issues.

So work on PeopleTools 9 was (mostly) halted to put all hands onto what ended up as PeopleTools 8.4. Why 8.4? Mainly to leave room for a potential 8.8 release. That ended up happening for the applications, along with 8.9 releases. Heck, the CRM group almost did an 8.95 release. Talk about asymptotically approaching 9 🙂

Take Three

I’m not sure which customer managed to finally convince PeopleSoft senior management that doing some releases that were focused on making current stuff work instead of The Next Big Thing, but I remember being in several corporate visits where (due to scheduling issues) I ended up sitting through Craig Conway or Ram Gupta’s sessions and get to hear the same earful that they were getting.

It’s wasn’t unusual to hear from people in charge of actually running PeopleSoft at a customer site about things that we could do to improve operational efficiencies. That’s one of the main reasons people used to come to Pleasanton for a corporate visit – to get a chance to talk with product strategy and development about their needs. Historically though you wouldn’t see the “C-level” discussions get into this sort of thing though.

So that’s what led to Total Ownership Experience (TOE). PeopleTools 8.44 was the big TOE release for PeopleTools. Performance Monitor, Change Assistant, etc were all big features, but not really “sexy”. Of course, they took a fair amount of development effort, which meant that there wasn’t really enough folks left to work on PeopleTools 9.

This was the go-around where there were actual PeopleTools 9 baseball hats created. I’ll have to talk Larry into posting a picture of his. I gave mine to a friend at a PeopleSoft customer in Germany awhile ago.

Take Four

The last stab at doing PeopleTools 9 was in the middle of the Oracle acquisition, so it was a little strange to be doing long term project planning. SAP was scoring sex and sizzle points with their NetWeaver story, which shaped some of the internal scoping debates.

This time around, PeopleTools 9 was not being called 9, but rather “X”. No one was ready to venture a guess as to what version number it would finally ship as. Which turned out to be rather smart since it didn’t ship 🙂

Postscript

As it turns out a lot of the ideas for PeopleTools 9 are things that Oracle had already been working on (or would soon acquire).

One was the whole idea of radically extending/re-writing the component processor to make things that are common customizations today into personalizations. Imagine the tab order personalization feature from PeopleTools 8.44 being extended to moving and hiding fields (while still honoring the business logic of course). Check out some of the ADF Faces work for some ideas on what Fusion applications will be capable of.

Another big feature was the idea of re-usable data objects. Oracle got that with the acquisition of Siebel, who in turn had acquired a company called nQuire. This now has the awkward name of Oracle Business Intelligence Enterprise Edition, but it is some really slick stuff. As Larry mentioned in a previous posting, we had brought nQuire in before Siebel bought them, but we didn’t move quick enough (historically PeopleSoft didn’t do acquisitions that well).

Applications Unlimited

One of the things that we did right after the acquisition was to survey some PeopleSoft customers about what they wanted to see from Oracle in relationship to PeopleTools. To a customer, every single one said in no uncertain terms that they didn’t want to see any major new things from PeopleTools – just focus on quality, performance, operational excellence, integration, etc.

This was prior to the announcement of Applications Unlimited,
but the sentiment remains.

As it turns out, folks that are looking for something radically new are starting to adopt Fusion Middleware. Oracle seems to be having a decent uptake of that within PeopleSoft customers.

(1) Funnily enough, the comedian that PeopleSoft hired for the PeopleSoft 8 launch party, Greg Proops, actually did some research before the event and made a comment to the effect of “All of this for going from 7.5 to 8? .5? “. Of course, he actually made it sound funny.

On a sad note, that launch party was held in the summer in 2000 at the Windows on the World restaurant, which was destroyed on September 11, 2001.

Labels: 2007, Fusion, History

Put the Appsian Security Platform to the Test

Schedule Your Demonstration and see how the Appsian Security Platform can be tailored to your organization’s unique objectives

Java and PeopleCode Tips and Tricks – Part 3

By Chris Heller • August 30, 2006

I haven’t written anything on the Java and PeopleCode series (part 1, part 2) recently, so I thought I’d whip something together this evening.

As previously discussed in the series, there are a few, um, quirks in the bindings between Java and PeopleCode. One typical workaround when you can’t cross between Java and PeopleCode successfully is to write some additional glue code on the Java side to provide an easier “target” to work with. This post will discuss a few tips and techniques for doing it all from the PeopleCode side.

Why would you want to avoid writing the Java glue code to simplify things? Well, it’s certainly not to avoid the complexity of Java (as the rest of this post will show). A more common reason is to avoid needing to distribute the compiled Java code out to each application server (which can be the source of various logistical difficulties).

On with the code. The use case here is to take an image and modify it so that we can stamp some text on it. The example comes from an article that shows how to use the Java Advanced Imaging libraries that are part of the standard Java environment as of Java 1.4.

The actual working code is below. Let’s start by looking at the first line of code that causes problems.

&jBufImage = &jImageIO.read(CreateJavaObject("java.io.File", &sSourceFileName));

This line of code will trigger the infamous “more than 1 overload matches” PeopleCode error. If you look at the relevant Javadoc, you’ll see that there are indeed multiple versions of the read method. Java can tell these apart by the type of the parameters being sent in, but PeopleCode only uses the number of parameters to differentiate among methods in a Java class with the same name.

In order to call this method from PeopleCode, we’ll need to use reflection. Reflection is how Java lets you determine class metadata (such as what methods it has and what parameters they take) at runtime.

Here’s what it looks like in action. This is broken into separate lines for clarity, but as you’ll see in the code, you can combine these where it makes sense.

&jReadArgTypes = CreateJavaObject("java.lang.Class[]", &jIOFileClass);
&jReadMethod = &jImageIOClass.getDeclaredMethod("read", &jReadArgTypes);
&jReadArgs = CreateJavaObject("java.lang.Object[]", CreateJavaObject("java.io.File", &sSourceFileName));
&jBufImage = &amp;amp;jReadMethod.invoke(&jImageIO, &jReadArgs);

This is easier to explain working from the bottom up. In order to call a method via reflection, we need to have the correct Method classinstance and use it’s invoke method. That’s what the 4th line is doing. The first parameter, &jImageIO, is the same object that we were trying to use before, and the second parameter is an array of parameters that invoke() will pass along to the “real” method.

Getting that parameter array is what line 3 does. When we have all of the values that are ever going to be in the array, then using CreateJavaObject with the braces, [], at the end of the class name is nicer than using the CreateJavaArray PeopleCode function. Mainly because we can pass all of the values in at once instead of setting them one by one as CreateJavaArray forces you to do.

We also needed to have the actual Method object. That’s what line 2 is doing. We call the getDeclaredMethod() method of the underlying Class object (this is the actual Java class that defines what a Java class is; chicken, meet egg) and pass it the name of the method that we want, along with array of the parameter types (not the parameter values!) that the method expects.

You can get the underlying Class object for any Java object by calling the getClass() method (there are examples in the code below). But when you have a JavaObject in PeopleCode that you obtained via GetJavaClass (instead of CreateJavaObject), then you actually have a PeopleCode reference to the class and not an instance of java.lang.Class. The PeopleCode reference allows you to call static methods on the class, but if you call getClass() on it, you’ll get an error. The secret to getting to a java.lang.Class instance for a particular class when you don’t have any instances of that class is to do something like this.

&jImageIOClass = GetJavaClass("java.lang.Class").forName("javax.imageio.ImageIO");

Now &jImageIOClass is an actual java.lang.Class instance, suitable for the reflection work that we’re doing.

Finishing things off, in line 1, we created the array of parameter types that we needed for the call to getDeclaredMethod(). The parameter types are specified by using their underlying java class, so you definitely want to be sure that you understand the difference between a java class and the java.lang.Class object which describes that java class.

Whew! That’s a lot of explaining to do just because PeopleCode doesn’t resolve all Java methods properly. What’s worse is that we’re not done yet. We now have another problem.

In the original line of PeopleCode, we called a method (“read”) that returns a Java object. Specifically an object of type java.awt.image.Bufferedimage. But we can’t use it as a BufferedImage object, because PeopleTools looks at the return type for invoke() and sees that it returns java.lang.Object, which is the base class for everything in Java. If you try to do something useful with &jBufImage (like get the dimensions of the image), PeopleTools will give you a “method not found” error.

Thankfully the underlying JVM still understands that this is a BufferedImage object and not just a java.lang.Object. So we can (read “have to”) use reflection again in order to use our BufferedImage. Of course, since we’re using reflection with BufferedImage, that means that any Java objects that we get back from reflected method calls are also misunderstood by PeopleTools (it will think that they are instances of java.lang.Object rather than whatever they really are).

So, once you fall into needing to use reflection within PeopleCode, you end up using a lot of it.
Believe it or not, it’s not so bad once you wrap your head around it. It took me longer to write this post than it took to write the code below since the extra work is essentially just some extra typing.

Obviously if you are doing a lot of Java/PeopleCode integration, then you’d be better off just writing a little bit of glue code on the Java side to avoid all of this, but when you’re just doing something quick (like using Java hashmaps instead of faking it with 2 dimensional arrays in PeopleCode), then this technique works well.

Finally, here is the actual code, along with the starting image (found in your PeopleTools directory) and the altered image.

Scroll box

Labels: PeopleCode, User

Put the Appsian Security Platform to the Test

Schedule Your Demonstration and see how the Appsian Security Platform can be tailored to your organization’s unique objectives