Wednesday, October 29, 2014

Apache Commons HttpClient for REST in PeopleCode - Part II - More Examples

This is a follow up to my previous post where we discussed how to use Apache Commons HttpClient for REST in PeopleSoft. Here are some more examples.

GetMethod - Adding Cookie (Header) to the Request:

Local JavaObject &jHttp, &jMethod;

/* Initialize HttpClient and set parameters */
&jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
&jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);

/* Initialize GetMethod */
&sURL = "https://www.test.com/name.v1/get?emplid=12345";
&jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.GetMethod", &sURL);
&jMethod.setFollowRedirects( False);

/* Adding Cookie to the Request */
&jMethod.setRequestHeader("Cookie", "JSESSIONID=64497D7D587637EBF17E128881C04016";
/* Adding Cookie to the Request */

/* Invoke GetMethod */
&return = &jHttp.executeMethod(&jMethod);
&responseStatus = &jMethod.getStatusLine().getStatusCode();

MessageBox(0, "", 0, 0, "Response Status: " | &responseStatus); 
MessageBox(0, "", 0, 0, "Response Message: " | &jMethod.getResponseBodyAsString());

&jMethod.releaseConnection();

PostMethod - Plain Text Content:

Local JavaObject &jHttp, jMethod;

/* Initialize HttpClient and set parameters */

&jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
&jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);

/* Initialize PostMethod and Set Request Details */

Local string &sURL = "https://cas.test.com/cas/v1/tickets";
&jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.PostMethod", &sURL);
&jMethod.setFollowRedirects( False);
&jMethod.setRequestHeader("Content-Type", "text/plain");
&jMethod.setRequestBody("username=testuser&password=testpassword");

/* Invoke PostMethod */

&jHttp.executeMethod(&jMethod);

Local integer &responseStatus = &jMethod.getStatusLine().getStatusCode();

Local string &responseBody = &jMethod.getResponseBodyAsString();

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

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

&jMethod.releaseConnection();

PostMethod - Multi-Part Message:

In this example, we will be posting a file on the App Server file system as a multi-part message.

Local JavaObject &jHttp,
&jMethod, &filePart, &partArray, &mPartReqEntity;

/* Initialize HttpClient and set parameters */

&jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
&jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);

/* Initialize PostMethod */

Local string &sURL = "https://doc.mgmt.com/upload/v1/filename=test.pdf";
&jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.PostMethod", &sURL);
&jMethod.setFollowRedirects( False);

/* Add Multi-Part Message */


/* Create File Object from App Server */

Local JavaObject &file = CreateJavaObject("java.io.File", "/tmp/test.pdf");

/* Create File Part */
&filePart = CreateJavaObject("org.apache.commons.httpclient.methods.multipart.FilePart", "test.pdf", &file);
/* Add File Part to Part Array */
&partArray = CreateJavaObject("org.apache.commons.httpclient.methods.multipart.Part[]", &filePart);
/* Create Multi-Part Request Entity */
&mPartReqEntity = CreateJavaObject("org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity", &partArray, &jMethod.getParams());
/* Add Multi-Part Request Entity to the Post Method */
&jMethod.setRequestEntity(&mPartReqEntity);

/* Add Multi-Part Message */

/* Invoke PostMethod */
Local integer &return = &jHttp.executeMethod(&jMethod);
Local integer &responseStatus = &jMethod.getStatusLine().getStatusCode();
Local string &responseBody = &jMethod.getResponseBodyAsString();

MessageBox(0, "", 0, 0, "&responseBody " | &responseBody);
MessageBox(0, "", 0, 0, "&responseStatus " | &responseStatus);

&jMethod.releaseConnection();


DeleteMethod:

Local JavaObject &jHttp, jMethod;

/* Initialize HttpClient and set parameters */
&jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
&jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);

/* Initialize DeleteMethod */
Local string &sURL = "https://doc.mgmt.com/delete/v1/filename=test.pdf";
Local JavaObject &jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.DeleteMethod", &sURL);
&jMethod.setFollowRedirects( False);

/* Invoke DeleteMethod */

Local integer &return = &jHttp.executeMethod(&jMethod);
Local integer &responseStatus = &jMethod.getStatusLine().getStatusCode();
Local string &responseBody = &jMethod.getResponseBodyAsString();

MessageBox(0, "", 0, 0, "&responseBody " | &responseBody);
MessageBox(0, "", 0, 0, "&responseStatus " | &responseStatus);

&jMethod.releaseConnection();

Tuesday, October 28, 2014

Apache Commons HttpClient for REST in PeopleCode - Part I - GetMethod and Basic Authentication Examples

Recently, I used Apache Commons HttpClient for making REST calls in PeopleCode for a project (interacting with a RESTful API of a Document Management System).

I found that using PeopleSoft Integration Broker for REST had the following limitations for my project:
1. Cookies (intended to be part of response messages) were getting lost and not coming through.
2. Dealing with, reading and processing response messages containing raw binary data (document).

Looking for workarounds, I then started exploring Apache Commons HttpClient after reading Chris Malek's blog and some posts in Jim Marion's blog.

The advantage of using Java is that it gives us a lot of flexibility to interact directly at the http layer. The disadvantage is that we would be bypassing Integration Broker (Gateway) which means we need to consider and implement functionality like logging, error handling and other built-in IB features such as authentication, etc.

There is also a distinction between the IB approach versus the Java approach.

IB Approach has the following flow:
App Server -> Web Server (IB Gateway) -> Third Party System

Java Approach has the following flow:
App Server -> Third Party System

I mention the above to point out that if there are any firewalls to be opened or if there are certificates/keys to be loaded then it needs to be done at the App Server level (if we are going with the Java Approach).

Stating the obvious, but another point to note is that this Java Approach is only an option for outbound REST calls.

That said, I would like to share my experiments and implementation experiences using Apache Commons HttpClient with some examples.

In this post, let us explore how to invoke the Get Method and use Basic Authentication. There are two options that I found to use Basic Authentication (Note: Based on OTN discussion).

Option 1 - Adding Authorization Header:

Local JavaObject &jHttp, &jMethod;

/* Initialize HttpClient and set parameters */
&jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
&jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);

/* Initialize GetMethod */
&sURL = "https://www.test.com/name.v1/get?emplid=12345";
&jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.GetMethod", &sURL);
&jMethod.setFollowRedirects( False);

/* Basic Auth - Adding Authorization Header */
/* &base64EncodeString = username:password encoded in base64 */
/* Note: In Production it is advised to store the base64 encoded string in a configuration table rather than hardcoding */
&base64EncodeString = "ABCD12345TESTBASE64";
&jMethod.setRequestHeader("Authorization", "Basic " | &base64EncodeString);
/* Basic Auth - Adding Authorization Header */

/* Invoke GetMethod */
&return = &jHttp.executeMethod(&jMethod);
&responseStatus = &jMethod.getStatusLine().getStatusCode();

MessageBox(0, "", 0, 0, "Response Status: " | &responseStatus); 
MessageBox(0, "", 0, 0, "Response Message: " | &jMethod.getResponseBodyAsString());

&jMethod.releaseConnection();

Option 2 - Using UsernamePasswordCredentials Class:


Local JavaObject &jHttp, &jMethod, &jCred, &jAuth;

/* Initialize HttpClient and set parameters */
&jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
&jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);

/* Initialize GetMethod */
&sURL = "https://www.test.com/name.v1/get?emplid=12345";
&jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.GetMethod", &sURL);
&jMethod.setFollowRedirects( False);

/* Basic Auth - Using UsernamePasswordCredentials Class */
&jCred = CreateJavaObject("org.apache.commons.httpclient.UsernamePasswordCredentials", "userid", "password");
&jAuth = CreateJavaObject("org.apache.commons.httpclient.auth.AuthScope", "www.test.com", 443);
/* Note: Use 443 if your host is using https or 80 if your host is using http */

&jHttp.getState().setCredentials(&jAuth, &jCred);
&jMethod.setDoAuthentication( True);
/* Basic Auth - Using UsernamePasswordCredentials Class */

/* Invoke GetMethod */
&return = &jHttp.executeMethod(&jMethod);
&responseStatus = &jMethod.getStatusLine().getStatusCode();

MessageBox(0, "", 0, 0, "Response Status: " | &responseStatus); 
MessageBox(0, "", 0, 0, "Response Message: " | &jMethod.getResponseBodyAsString());

&jMethod.releaseConnection();

Note: Here are the jars that I downloaded and placed on my app server classpath. An app server bounce/restart is required for the new jars to take effect.
- commons-httpclient-3.1.zip
- commons-codec-1.6.zip
- commons-logging-1.1.3.zip
- commons-io-2.4.zip (optional)

Few more examples of "Apache Commons HttpClient for REST in PeopleCode" to follow in Part II.

Tuesday, October 21, 2014

Reading .properties file on app server file system using Java in PeopleCode

Here is an example of how we can read .properties files on the app server file system easily using Java in PeopleCode.

Why: There might be some useful information in the .properties files on the app server that might not be accessible to developers directly in PeopleCode. Some examples: OS Type and Version Details in peopletools.properties.

In this example, let us assume we want to access the OS Type and Version properties that is set on the peopletools.properties file (located either in PS_CFG_HOME or PS_HOME).

Here is an example of the contents in the peopletools.properties file:

#Peopletools Install Settings
#Fri Oct 17 10:19:17 EDT 2014
installlocation=<PS_HOME>
dbcodessuffix=PT
unicodedb=0
licensegroupname=PeopleTools
hstplt=oel-5-x86_64
psplatformregname=ORACLE
psserver=App
psdbbin=
psenv=App
dbtypedescr=Oracle
dbtype=ORA
licensegroup=06
tuxedodir=
productversion=8.52.07
psplatform=Linux

Sample PeopleCode:

/* Get the file (peopletools.properties) */
Local JavaObject &fis = CreateJavaObject("java.io.FileInputStream", GetEnv("PS_CFG_HOME") | "/peopletools.properties");
/* Note: If PS_CFG_HOME is not configured in your environment, then try PS_HOME. */

/* Create Properties JavaObject */
Local JavaObject &props = CreateJavaObject("java.util.Properties");


/* Load file to Properties Class */
&props.load(&fis);

/* Use Reflection to call the appropriate getProperty (overloaded) method of the Properties Class */

/******* REFLECTION ********/
/* Get a reference to a Java class instance for the String Class */
Local JavaObject &stringClass = GetJavaClass("java.lang.String");

/* Get a reference to the method we want to call */
Local JavaObject &methodArgTypes = CreateJavaObject("java.lang.Class[]", &stringClass);
Local JavaObject &methodReference = &props.getClass().getMethod("getProperty", &methodArgTypes);

/* Call the method */
Local JavaObject &psplatform = &methodReference.invoke(&props, CreateJavaObject("java.lang.Object[]", "psplatform"));
Local JavaObject &hstplt = &methodReference.invoke(&props, CreateJavaObject("java.lang.Object[]", "hstplt"));
/******* REFLECTION ********/

/* psplatform returns OS Type */
MessageBox(0, "", 0, 0, "psplatform: " | &psplatform.toString());
/* hstplt returns OS Details (including version) */
MessageBox(0, "", 0, 0, "hstplt: " | &hstplt.toString());

&fis.close();

Granted that the same can also be achieved without using Java and parsing through the .properties file using PeopleCode File Object. This post is just to show an alternative approach which might be applicable in certain scenarios.

This approach could be easily extended to any .properties file on the application server or process scheduler server.

Tuesday, October 14, 2014

PeopleSoft - Mining Linux based Web/App Server Logs Using find, egrep, awk commands

This post is based on information provided to me by an ex-colleague, friend and the best PS Admin I have worked with Danny Kile. I use this regularly and find it very effective when we need to quickly investigate issues particularly in a Production Environment (with several instances of the web/app server domains).

# Change Directory to where the logs reside
# Note: I changed the directory to the appserv folder so the search would go through
# multiple domain folders (if they exist)
cd /%PS_CFG_HOME%/appserv/

# If you want all the output printed
find . -name 'APPSRV_1014.LOG' | xargs egrep '*Search Text*'

# If you just want the number of times the 'Search Text' was found
find . -name 'APPSRV_1014.LOG' | xargs egrep '*Search Text*' | wc -l

# If you want multiple days worth
find . -name 'APPSRV_101*.LOG' | xargs egrep '*Search Text*' | wc -l

#If you want to aggregate based on day or hour, etc.
find . -name 'APPSRV_101*.LOG' | xargs awk '/*Search Text*/{freq[$3]++} END {printf "\n";for(day in freq){printf "%s\t%d\n",day,freq[day];}printf "\n";printf "\n";}'

Hope this is useful. Please share any other commands/scripts that might come in handy for PeopleSoft Developers.

APPMSGARCH Process - Performance Tuning

I recently worked on a requirement to tune the performance of the delivered APPMSGARCH process (batch approach used to archive service operation data). The process was taking longer to run everyday and got to a point where it would run for over 12 hours.

While investigating the problem in production, it was found that the following SQL was the main cause for our performance issue.

SqlExec is located in APPMSGARCH.MAIN.GBL.default.1900-01-01.ARCHASYN.OnExecute:

DELETE
FROM PSAPMSGPUBDATA
WHERE EXISTS
  (SELECT '*'
  FROM PSAPMSGARCHTMP B,
    PSAPMSGSUBCON C
  WHERE (PSAPMSGPUBDATA.IBTRANSACTIONID = C.CANONICALTRSFRMID
  OR PSAPMSGPUBDATA.IBTRANSACTIONID     = C.IBTRANSACTIONID)
  AND B.IBTRANSACTIONID                 = C.IBPUBTRANSACTID
  );

Note: This is not to imply that all performance problems with this process is directly related to this SQL. There could be other issues depending on each individual environment. But I do find that the structure of all the SQLs particularly the DELETEs follow a similar theme (with the usage of EXISTS clause). So it could be a common problem for which the following solution could be applied.

Once it was identified that this SQL was the main issue in our environment, I tried to look in My Oracle Support for potential solutions (the first place I would look to research a problem with anything delivered). I found this document E-IB: APPMSGARCH Performance Issue (Doc ID 754437.1).

Amongst other things in the document, it was recommended to replace the SQL (mentioned above) with two different SQL statements to separate the OR clause.

SQL 1:
DELETE
FROM PSAPMSGPUBDATA
WHERE EXISTS
  (SELECT '*'
  FROM PSAPMSGARCHTMP B,
    PSAPMSGSUBCON C
  WHERE PSAPMSGPUBDATA.IBTRANSACTIONID = C.CANONICALTRSFRMID
  AND B.IBTRANSACTIONID                = C.IBPUBTRANSACTID
  );

SQL 2:
DELETE
FROM PSAPMSGPUBDATA
WHERE EXISTS
  (SELECT '*'
  FROM PSAPMSGARCHTMP B,
    PSAPMSGSUBCON C
  WHERE PSAPMSGPUBDATA.IBTRANSACTIONID = C.IBTRANSACTIONID
  AND B.IBTRANSACTIONID                = C.IBPUBTRANSACTID
  );

Since it was a recommendation of potential value (and one that was pertinent to our problem), I went ahead and applied this change and tested again. Unfortunately, it did not help with the performance at all.

At this point, I started looking into the data in the tables as well as the SQL statements to identify any tuning opportunities. I found that PSAPMSGPUBDATA had around 800,000 rows of data in it. The way the SQL statements are written, it appears that the process would go over each and every row in the table (PSAPMSGPUBDATA) and check for the EXISTS clause before deleting.

Here is how I re-wrote the SQL statements which helped considerably.

SQL1 (Re-write):
  DELETE
FROM PSAPMSGPUBDATA
WHERE IBTRANSACTIONID IN
  (SELECT C.CANONICALTRSFRMID
  FROM PSAPMSGARCHTMP B,
    PSAPMSGSUBCON C
  WHERE B.IBTRANSACTIONID = C.IBPUBTRANSACTID
  );

SQL2 (Re-write):
DELETE
FROM PSAPMSGPUBDATA
WHERE IBTRANSACTIONID IN
  (SELECT C.IBTRANSACTIONID
  FROM PSAPMSGARCHTMP B,
    PSAPMSGSUBCON C
  WHERE B.IBTRANSACTIONID = C.IBPUBTRANSACTID
  );

If you notice the new SQL statements, you will find that the main difference was the replacement of the EXISTS clause with a IN clause.

Now, instead of looping through each row in PSAPMSGPUBDATA and checking the EXISTS clause, the SQL would just look for the rows that are in the results of the IN clause.

Here is a good article that details the usage and difference between the EXISTS and IN clause:
Usage of EXISTS and IN clause

If you are having issues with other DELETE sql statements (using EXISTS) in the APPMSGARCH app engine then you could try a similar approach. As always test the changes in your environment to see if you achieve the desired performance gains.

Note: Alternate solutions that I know have helped others in improving performance of APPMSGARCH :

  1. Indexing affected tables appropriately.
  2. Staging transaction ids that need to be deleted in a custom table (temporarily) to avoid complex where clauses in the DELETE statements.

Java Reflection in PeopleCode

Here is a simple example of how we can overcome the "more than one overload matches" error which occurs when calling an overloaded Java Class Method in PeopleCode.

Note: My understanding and learning is primarily based on Jim Marion's blog. This example is specific to my requirement and it can be easily extended as required.

Let us take an example of FileOutputStream class which has a write method which is overloaded.

Let us assume that we want to call the write method with the following parameters: public void write(byte[] b)

/* Use Reflection to call the appropriate method of the Java Class (which is an overloaded method) */

/* Create an instance of the Class */
Local JavaObject &fos = CreateJavaObject("java.io.FileOutputStream", "/tmp/" | &filename);

/******* REFLECTION ********/

/* Get a reference to a Java class instance of the method parameter(s): In this case the primitive byte array */
Local JavaObject &arrayClass = GetJavaClass("java.lang.reflect.Array");
Local JavaObject &bytArrClass = &arrayClass.newInstance(GetJavaClass("java.lang.Byte").TYPE, 0);

/* Get a reference to the method we want to call using Reflection */
Local JavaObject &methodArgTypes = CreateJavaObject("java.lang.Class[]", &bytArrClass.getClass());
Local JavaObject &methodReference = &fos.getClass().getMethod("write", &methodArgTypes);

/* Call the method */
Local any &result = &methodReference.invoke(&fos, CreateJavaObject("java.lang.Object[]", &byteArray));
/******* REFLECTION ********/

/* Close File Object */
&fos.close();

AAWS - Targeted Error Handling

While troubleshooting AAWS issues (Admission Applications Web Services delivered in Campus Solutions) in a Production Environment, I found the following options that might come in handy.

Let us take AAWS - Submit Application (SAD_SUBMITAPPL) service operation as an example for this blog. Note: The same approach could be extended to any other AAWS service operation.

The problem or difficulty with troubleshooting this service operation is that the faults generated are very generic in nature and does not help with identifying the root cause of the problem.

Here is an example of a fault message generated by SAD_SUBMITAPPPL:

<SOAP-ENV:Envelope xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Body>
    <SOAP-ENV:Fault>
      <faultcode>Client</faultcode>
      <faultstring>An Error occurred processing this request (14098,286)</faultstring>
      <detail>
        <MSGS>
          <MSG>
            <ID>14098-206</ID>
            <DESCR>Error during Application submit, Contact the Administrator</DESCR>
            <MESSAGE_SEVERITY>I</MESSAGE_SEVERITY>
            <PROPS/>
          </MSG>
        </MSGS>
      </detail>
    </SOAP-ENV:Fault>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

While I can understand why AAWS faults are generic, potentially to mask the nasty error messages from being displayed to the end user, it is my personal opinion that this type of fault message is unacceptable especially for a high volume web service such as AAWS (from a service maintenance point of view). Let us explore how we can put controls in place to be able to identify the root cause for this error in real-time.

Using Delivered Campus Service Oriented Architecture Logging (Doc ID 1511543.1):

This document details couple of options.
  1. Setting up the regular logging at the routing level. This does not really add much value in the case of AAWS.

    Logging:



    Results:



    You will notice that there are no errors logged for the synchronous transaction.

  2. Setting Log Type and Threshold using delivered Campus Service Oriented Architecture (SOA) Logging.



    We can set the Logging Type to Database or File. For this example I chose the Database option. For the Log Threshold we have many options. The 'All' option would provide a lot of detail and something like 'Severe' option would probably give us what we want (in terms of errors).

    The result of this logging should be available in the PS_SCC_INTEG_LOG table. Use the following SQL to get the most recent entries first:

    select * from PS_SCC_INTEG_LOG order by 1 desc;

    You will notice that SCC_INT_LOG_MSG (where SCC_INT_LOG_LEVEL=2) would contain the error message that we are after. In my case, I found that my error message was as follows:

    "Transaction Manager Exception: Error Saving: XXX_CUSTOM_STG tempid: 579 (0,0) SCC_COMMON.ENTITY.StagedEntity.OnExecute  Name:save  PCPC:16872  Statement:322 Called from:SCC_COMMON.ENTITY.ChildEntity.OnExecute  Name:save  Statement:101 Called from:SCC_C"

    Now, this would tell us exactly why and where (peoplecode statement) the error occurred. This helps greatly in comparison to the Generic Error Message (Error during Application submit, Contact the Administrator).

    While this is very useful for short term/unexpected troubleshooting, one downside of the Campus SOA Logging is that it is not specific to a service operation. So if we turn on logging it would take effect for all delivered service operations that are represented using the Campus SOA. You will notice that if we have this logging turned on in a Production Environment then the table PS_SCC_INTEG_LOG would quickly fill up. Another scenario where this logging might fall short is if we want to send back real-time meaningful response/fault messages for the consumer to process and take action.
Now that we have exhausted delivered options (please correct me if I missed anything!), let us look at a targeted error handling approach (returning appropriate exceptions in the fault message) involving a minor customization. Note: While customizations would involve cost and maintenance considerations, it is generally the case of whether the benefits outweigh the effort and maintenance. I will let you make the decision based on the circumstances in your organization.

AAWS Targeted Error Handling - Customizing the Handler:

The OnRequest Handler code for the AAWS - SAD_SUBMITAPPL is implemented in the following class and method:
Class: SCC_OLA.HANDLERS.Admissions.OnExecute
Method: submitApplication

Let us take a look at a snippet of the delivered code that deals with part of the error handling which I customized:


Here is the same snippet of delivered code with my customization:


Let us examine my customization in detail:

         /* XXX - SV - 20141013 - Test Start */

         Local SCC_COMMON:ENTITY:LOG:MessageEntry &msgEntryCust = create SCC_COMMON:ENTITY:LOG:MessageEntry();
         Local array of string &parms = CreateArrayRept("", 0);
         &parms [1] = &e2.ToString();
         &msgEntryCust.DataPopulateV1(14098, 181, &msgEntryCust.Severity_Error, &parms, "", "", "");
         &returnError.writeEntry(&msgEntryCust);

         /* XXX - SV - 20141013 - Test End */

I made use of the MessageEntry class to create another message detail (<MSG> section), populate the message detail with the exception information and append it to the &returnError object which is the fault message.

Note: I used the same message set number and message number (14098, 181) that is referenced in the logError method right at the start of this exception handling section. 
&p_logger.logError(MsgGet(14098, 181, "", &e2.ToString()));

Let us see the results of this customization.

<SOAP-ENV:Envelope xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> 
  <SOAP-ENV:Body> 
    <SOAP-ENV:Fault> 
      <faultcode>Client</faultcode> 
      <faultstring>An Error occurred processing this request (14098,286)</faultstring> 
      <detail> 
        <MSGS> 
          <MSG> 
            <ID>14098-206</ID> 
            <DESCR>Error during Application submit, Contact the Administrator</DESCR> 
            <MESSAGE_SEVERITY>I</MESSAGE_SEVERITY> 
            <PROPS/> 
          </MSG> 
          <MSG> 
            <ID>14098-181</ID> 
            <DESCR>Transaction Manager Exception: Error Saving: XXX_CUSTOM_STG tempid: 583 (0,0) SCC_COMMON.ENTITY.StagedEntity.OnExecute  Name:save  PCPC:16872  Statement:322
Called from:SCC_COMMON.ENTITY.ChildEntity.OnExecute  Name:save  Statement:101
Called from:SCC_COMMON.ENTITY.StagedEntity.OnExecute  Name:saveChildren  Statement:374
Called from:SCC_COMMON.ENTITY.StagedEntity.OnExecute  Name:save  Statement:303
Called from:SAD_ADM_APPL.ApplicationManager.OnExecute  Name:saveApplication  Statement:70
Called from:SCC_OLA.HANDLERS.Admissions.OnExecute  Name:submitApplication  Statement:595
Call
</DESCR> 
            <MESSAGE_SEVERITY>E</MESSAGE_SEVERITY> 
            <PROPS/> 
          </MSG> 
        </MSGS> 
      </detail> 
    </SOAP-ENV:Fault> 
  </SOAP-ENV:Body> 
</SOAP-ENV:Envelope>

Highlighted in orange is the marker for the custom message that was added and highlighted in green are the details of the custom message.

You can see how we can improve the details in the fault messages enabling both technical and functional staff to quickly identify the problem with the application and take corrective action. This is very effective especially when there could be application submission peaks where we can expect thousands of application requests in a day.

Note:
  • Please test (test and test!) this thoroughly before applying the suggested customization to your Production Environment (if you think this might be useful).
  • In the submitApplication method used in this example there are a 4 occurrences of &returnError.writeEntry method being invoked. It is recommended that each of those are appropriately extended using the approach in the sample code and example provided in this blog.
  • Customization Environment Details: Campus Solutions 9.0 (Bundle 34) and PeopleTools 8.52.22.

Sunday, October 12, 2014

PeopleSoft Copy Project To File... - Node Password Bug

I noticed a bug (or hack?) with the Copy Project to File feature.

Let us take an example of any Integration Broker Node that uses Password as the Authentication Option (E.g.: I have found that nodes like PSFT_HR, PSFT_EP, PSFT_CR, etc, especically the local node would most likely have this setup).


If you look at the password field value for this node in the database (PSMSGNODEDEFN), you will find that it is encrypted.

select USERID, IBPASSWORD from PSMSGNODEDEFN where MSGNODENAME = 'PSFT_HR';

Another place the password for such nodes would be stored is the integrationGateway.properties file. 

You can find this file in the following path in the PeopleSoft home directory of your gateway web server:
<PIA_HOME>\webserv\<DOMAIN>\applications\peoplesoft\PSIGW.war\WEB-INF\integrationGateway.properties
ig.isc.$NODENAME.serverURL=//<machine name>:<jolt port>
ig.isc.$NODENAME.userid=<application server user id>
ig.isc.$NODENAME.password=<application server password>
ig.isc.$NODENAME.toolsRel=<peopletools release version>
You can also access the contents of the properties file in the following navigation:
Main Menu > PeopleTools > Integration Broker > Configuration > Gateways (Gateway Setup Properties Link)


You will notice that the password for the node in the gateway properties file would be encrypted as well.

So far so good.

Now let us find an App Designer Project that contains this node we are investigating (hacking?!?):
select distinct PROJECTNAME from PSPROJECTITEM where objectvalue1 = 'PSFT_HR';

Note: This is to demonstrate that it is not necessary to create a NEW Project with the node concerned. I have found that most nodes (especially the local node in an environment) would have been part of a project already.

Now let us open one such project and copy the project to file using App Designer > Tools > Copy Project > To File...

Once the project is copied to file, let us open the project folder in the local directory. You will find the following files in the project folder:
<PROJECTNAME>.ini
<PROJECTNAME>.xml

Let us open the <PROJECTNAME>.xml file and search for <szIbPassword>. You will find the node password for the Default User ID in clear text!

Here are the challenges:
  1. More often than not, the Default User ID that is used in Integration Broker nodes would be a power/super user like PS or equivalent.
  2. Most developers have access to App Designer to copy project to file option.
  3. Not all developers that are employed for projects are permanent employees of the organization (think consultants who are engaged for a brief period of time).
    Note: I have nothing against consultants! I have been one myself. :)
  4. There is no way to selectively provide/revoke access to the copy project to file option using security. If anyone knows of a way to do this then please let me know.
If this is a matter of concern from a security point of view, the only workaround I see right now is to completely remove upgrade (migration) access from anyone who should not have access to this Userid/Password.

Please let me know if you have any other ideas/thoughts!