9/10/2015

Simple Tip to refresh structure & content

One thing I always find annoying is when I have to add a new portal object in the structure and content.  Especially went migrated to another environment, if the server caches are not cleared, the new link might only appear after few days.  Of course, Business Analysts would never understand this limitation.

My collegues and me found an easy way to make a link appear.  Here is the procedure :

- Navigate to Structure & Content
- Within Structure & Content, navigate to the parent folder of the object (link or folder) which is not displayed
- Modify a "brother" (other child of the parent) of the not displayed object
- Add a space at the end of the description (the space will be removed when saving, which will avoid us to put back the original value of the description and save again)
- Save

The link will appear.

If the new object has no brothers, I guess that doing the same procedure on the father might have the same effect.

7/16/2015

Component Interface for dummies

Any Peoplesoft Developer who used and debugged code calling component interfaces might understand what really is a CI, but, from my experiments, a lot of people do not understand the CI concept at all, especially non-thecnical (BAs, PMs) resources.  They think it is something coded, which emulates online and executes ALL peoplecode such as if a user would navigate throught the Component and Pages within Peoplesoft.

I found it complicated to explain to those people the scope of a Component Interface.  It is simply a LAYER around a Component to be able to call it from Peoplecode so online rules apply ALMOST such as when a user navigates online.

I was trying to explain it to my PM who thought she understood the concept but the message would definetely not pass.  Back home, I got a revelation.  I saw my Polaroid Cube (kind of GO PRO camera) inside its camera case.  See below



The Polaroid Cube camera is very primitive.  It has only one button; 3 seconds press for on/off, 1 click for a picture, 2 quick cliks to start or stop recording a video.  It also has a powerful magnet under, to be fixed easily on an ironned surface.

The case is as primitive as the camera.  It is transparent so the camera can film/take pictures.  It has one push button that transmits the push action to the camera.  It can be locked so water does not access to the camera, and has a mounting adaptor, to be fixed to mounts (bike mount, helmet mount, suction mount).

If we think about it, the case allow the user to use some of the camera's functionalities underwater : turn on, turn off, start recording, stop recording, take a picture.  Some functionalities do not work with the case, such as the magnet mount, charge the camera or remove the SD card.  However, new functionalities are available, such as the mounting adaptor.

So, my conclusion is : the Component Interface is at the Component what the Case is at the Camera.


6/16/2015

Component Interface - Calling Find() method

It doesn't seem to be a big deal, calling the Find method should be a piece of cake, but I didn't find any example on the net.  So, here is my explanation :

I. Generate the code to call a CI - simply drag a CI into a blank Peoplecode window.

II. Declare ApiObjects for results of the find :
Local ApiObject &FindKeyCollection, &FindKeyRec;

III. In the code calling the component interface, enter values in keys (optional)
&oCiSsrSsenrlList.STRM = "2151";

IV. Call the Find method; it returns a collection of rows in the search record
&FindKeyCollection = &oCiSsrSsenrlList.Find();

V. Travel through the collection of rows and process each row as needed (stock in an array, select a row, call Get method)
   Local integer &ixyz;
   For &ixyz = 1 To &FindKeyCollection.Count;
      &FindKeyRec = &FindKeyCollection.Item(&ixyz);
      MessageBox(0, "", 0, 0, "INSTITUTION=[" | &FindKeyRec.INSTITUTION | "];ACAD_CAREER=[" | &FindKeyRec.ACAD_CAREER | "];STRM=[" | &FindKeyRec.STRM | "]");
   End-For;


Code : 

[...]  

   &fileLog.WriteLine("Begin");
   rem ***** Get current PeopleSoft Session *****;
   &oSession = %Session;
   
   rem ***** Set the PeopleSoft Session Error Message Mode *****;
   rem ***** 0 - None *****;
   rem ***** 1 - PSMessage Collection only (default) *****;
   rem ***** 2 - Message Box only *****;
   rem ***** 3 - Both collection and message box *****;
   &oSession.PSMessagesMode = 1;
   
   rem ***** Get the Component Interface *****;
   &oCiSsrSsenrlList = &oSession.GetCompIntfc(CompIntfc.CI_SSR_SSENRL_LIST);
   If &oCiSsrSsenrlList = Null Then
      errorHandler();
      throw CreateException(0, 0, "GetCompIntfc failed");
   End-If;
   
   rem ***** Set the Component Interface Mode *****;
   &oCiSsrSsenrlList.InteractiveMode = False;
   &oCiSsrSsenrlList.GetHistoryItems = True;
   &oCiSsrSsenrlList.EditHistoryItems = False;
   

   &oCiSsrSsenrlList.STRM = "2151";
   
   &FindKeyCollection = &oCiSsrSsenrlList.Find();
   
   Local integer &ixyz;
   For &ixyz = 1 To &FindKeyCollection.Count;
      &FindKeyRec = &FindKeyCollection.Item(&ixyz);
      MessageBox(0, "", 0, 0, "INSTITUTION=[" | &FindKeyRec.INSTITUTION | "];ACAD_CAREER=[" | &FindKeyRec.ACAD_CAREER | "];STRM=[" | &FindKeyRec.STRM | "]");
   End-For;

[...]  
 

5/29/2012

Password Encryption

I found a bit non secured to have cleared passwords in the code, such as when we connect to a FTP using PutAttachement() function.  Here is a piece of code that shows how to encrypt and decrypt a password, the Integration Broker way (as available when configuring nodes, on the Connectors tab)

Local JavaObject &pscipher = CreateJavaObject("com.peoplesoft.pt.integrationgateway.common.EncryptPassword"); &pwd = "XXXX"; MessageBox(0, "", 0, 0, "Password before encryption " | &pwd); &encPassword = &pscipher.encryptPassword(&pwd); &yo = &pscipher.isPasswordEncrypted(&encPassword); &pscipher = Null; MessageBox(0, "", 0, 0, "Encrypted Password " | &encPassword | " " | &yo); Local JavaObject &psCYPHER = CreateJavaObject("psft.pt8.pshttp.PSCipher"); &DecPwd = &psCYPHER.decodePassword(&encPassword); MessageBox(0, "", 0, 0, "&DecPwd " | &DecPwd);

5/10/2012

Integration Broker Again - FTPTARGET Connector

For my current client, I had de develop an interface between Peoplesoft and a provider.  A huge XML file containing benefit data must be generated, validated with a XSD Schema and sent using the SFTP protocol.

This process is a little bit more complicated that a simple XML generated and sent using PutAttachement global function.  The first option I though about was to develop a Java Library to be able to validate XML with XSD Schema, to be used as a JavaObject, and then try to send the file using PutAttachment.  I think this solution is too hard to implement and to maintain.

I asked Oracle for a XSD validation global function, and they redirected me to Integration Broker.  I also remembered it was possible to attach (or generate) a schema on a message.  I also knew that we could use other protocol than PSFTTARGET.

 So I started by adding a new external node.  I chose to use the FTPTARGET connector; there is a tool I find useful : the password is encrypted and it is not displayed in clear, so it is a little bit more secure.



Then, I created a Non-Rowset message, because the XML generated is not based on Peoplesoft Tables; there is also some information included to Rowset Messages that we do not want, that are used when transferring to another Peoplesoft instance.  The message definition is almost empty, it only contains the XSD to validate the XML.



I also had to create a queue, a service and a service operation, and configure the routing, but this is trivial, so I will not describe that part.

After the configuration was done, I tested it to be sure that messages are going trough.

One of my requirements was to be able to rename the filename.  I tried several ways, to add a connector property and try to modify it using Peoplecode, before sending the file, but I was not able.  I finally found a workaround, by selecting all FTPTARGET properties and add them back to the IBConnectorInfo object associated to the object using the AddConnectorProperties method (&MSG.IBInfo.IBConnectorInfo.AddConnectorProperties); I was then able to add the Filename property with the correct value (filename).

&MSG = CreateMessage(Operation.JB_XSD_MSG_3);
&MSG.SetXmlDoc(&TransformedxmlRequestDoc);

/* Set ConnectorName and Connector ClassName */
&MSG.IBInfo.IBConnectorInfo.ConnectorName = "FTPTARGET";
&MSG.IBInfo.IBConnectorInfo.ConnectorClassName = "FTPTargetConnector";

&strQuery = " select PROPID, PROPNAME, PROPVALUE from  PSNODECONPROP where MSGNODENAME = 'JB_FTP_xxxEVMGR'";

&sql1 = CreateSQL(&strQuery);
&aany = CreateArrayAny();

While &sql1.Fetch(&aany)
   If &aany [1] = "HEADER" Then
      &nRet = &MSG.IBInfo.IBConnectorInfo.AddConnectorProperties(&aany [2], &aany [3], %Header);
   Else
      &nRet = &MSG.IBInfo.IBConnectorInfo.AddConnectorProperties(&aany [2], &aany [3], %Property);
   End-If;
End-While;

&nRet = &MSG.IBInfo.IBConnectorInfo.AddConnectorProperties("FILENAME", "xsd_ok_" | DateTimeToLocalizedString(%Datetime, "yyyyMMddhhmmss") | ".XML", %Property);

To generate easily XML, I wanted to use a table structure, such as File Layouts or Rowset.  I finally opted for Rowsets, because it seemed to be not to complicated and enough flexible for me.  Each element (nodes) of the XML file will correspond to a Record (Table or View) and its attributes (leafs) will be associated to a Field.

First I had to create the rowset structure.  I followed this article : 


&rs0 = CreateRowset(Record.BEN_LOADER);
&rs1 = CreateRowset(Record.BEN_MEMBER);
&rs2 = CreateRowset(Record.ADDRESSES);
&rs12 = CreateRowset(&rs1, &rs2);
&rs = CreateRowset(&rs0, &rs12);
&rs.Fill();

For &i = 1 To &rs.ActiveRowCount
   &rs(&i).GetRowset(Scroll.BEN_MEMBER).Fill("WHERE POLICY_NBR = :1", &rs(&i).GetRecord(Record.BEN_LOADER).GetField(Field.POLICY_NBR).Value);
   For &j = 1 To &rs(&i).GetRowset(Scroll.BEN_MEMBER).ActiveRowCount
      &rs(&i).GetRowset(Scroll.BEN_MEMBER).GetRow(&j).GetRowset(Scroll.ADDRESSES).Fill("Where EMPLID = :1", &rs(&i).GetRowset(Scroll.BEN_MEMBER).GetRow(&j).GetRecord(Record.BEN_MEMBER).GetField(Field.EMPLID).Value);
   End-For;
End-For;

I tried to find a simple way to generate XML from a rowset to a view.  I thought about creating a function to generate one, but I found that we can create a XMLDoc object from a rowset using the CopyRowset method.

&xmlRequestDoc = CreateXmlDoc("");
&ret = &xmlRequestDoc.CopyRowset(&rs, "JB_MSG_XSD_3", "VERSION_2");
JB_TST_IB_FTP.HTMLAREA = &xmlRequestDoc.GenFormattedXmlString();



The generated XMLDocument tries to match the structure of the message in parameters, but it seems it is not able when the message is of type "non-rowset".  So the result was not the expected XML, it looked like a Rowset-based message, with unwated tags (see highlighted below)

<?xml version="1.0"?>
<JB_MSG_XSD_3>
  <FieldTypes>
    <PSCAMA class="R">
      <LANGUAGE_CD type="CHAR"/>
      <AUDIT_ACTN type="CHAR"/>
      <BASE_LANGUAGE_CD type="CHAR"/>
      <MSG_SEQ_FLG type="CHAR"/>
      <PROCESS_INSTANCE type="NUMBER"/>
      <PUBLISH_RULE_ID type="CHAR"/>
      <MSGNODENAME type="CHAR"/>
    </PSCAMA>
  </FieldTypes>
  <MsgData>
    <Transaction>
      <RECORD1 class="R">
        <FIELD1>121212</FIELD1>
        <FIELD2>Test</FIELD2>
        <RECORD2 class="R">
          <FIELD3>12asdd2</FIELD3>
          <FIELD4>T122est</FIELD4>
        </RECORD2>
        <RECORD2 class="R">
          <FIELD3>12as122dedddd2</FIELD3>
          <FIELD4>T1122322est</FIELD4>
        </RECORD2>       
      </RECORD1>       
    </Transaction>
  </MsgData>
</JB_MSG_XSD_3>


Also, the records and fields names are not the tags we want to be present into the file when sending it; anyway, it would not pass the XSD validation step.  I tried to find a delivered way to modify the generated XML so it would be "sendable".  I found the TransformEx and TransformExCache global functions.  Both function are used to apply a XSL to a XML, in order to transform it into a XML with a different structure.  The only difference wetween the two functions is the nature of the parameters.

try
     &TransformedxmlRequestDoc = TransformExCache(&xmlRequestDoc, "/home/devmgr/xsl1.xsl", "xsl1");
catch Exception &E
   MessageBox(0, "", 0, 0, "Caught exception: " | &E.ToString());
end-try;

The result is a XMLDoc with the wanted structure.  It is ready to be validated by the XSD :

Local string &retValidateMessage;
&retValidateMessage = %IntBroker.ValidateMsgData("JB_MSG_XSD_3", "VERSION_2", &TransformedxmlRequestDoc.GenFormattedXmlString());


If the mesage is valid, we can publish it.  To be able to modify conector properties, I had to ConnectorRequest method instead of Publish.

If &retValidateMessage = "Validation Successful" Then
   &MSG2 = %IntBroker.ConnectorRequest(&MSG);
End-If;

4/02/2012

My Integration Broker Experience


At my former customer, I had to setup, configure and customize integration broker to exchange data between HR and Financial environments, both on PSFT 9.0 Tools 8.49, but didn't know from where to start.  I finally understood the mechanism and decided to share it, because I didn't find any clear, quick and sweet guide to start with this monster.

Introduction

Integration Broker is a mechansim used by Peoplesoft to exchange with itself, with another instance or with external web services.

Data is sent as an XML File, the message, which is generated by the source environment and read by the destination environment.  The message is sent from destination into a queue and received from the same queue by the source.

There are two types of Peoplesoft messages: one row message (SYNC) and all rows messages (FULL_SYNC).  SYNC message are used to synchronize a row, when created or modified (when triggered).  The FULL_SYNC messages are launched via an Application Engine executed by the user.

Once received, the message is interpreted by an Application Class: the Handler.  For SYNC messages, the handler usually inserts or updates only 1 line of data.  For FULL_SYNC messages, the handler deletes the data and replaced it with the one contained into the message; exception can be made, such as the person data created in FS (non-HR) which are not deleted when a FULL_SYNC is received from HR.

For some messages, the table structure differs between environments.  To be able to synchronize the data, the message must be transformed to reflect the target environment.  This is done by a transformation, which is an Application Engine of type “Transfrom Only”

Routing is used to specify source and target nodes for a given message.  Nodes represent the target and source environments.  Addresses of servers associated to nodes are defined into the gateway, which represents the connexion with outside.

All those elements (Message, Queue, Routing, Handlers) are grouped into a Service Operation.

Configuration
Gateway
PeopleTools > Integration Broker > Configuration > Gateways

PSFTTARGET connector is used to exchange between two instances of Peoplesoft.  Gateway Setup Properties links to the nodes configuration page, to associate them to servers.

Nodes
PeopleTools > Integration Broker > Integration Setup >  Nodes
When we work with two distinct Peoplesoft environments, it is important to configure and activate the corresponding nodes, in each environment.  Peoplesoft nodes type is PIA; in some, we need to set a node type to External even if it represents a Peoplesoft environment (see in Security for more information).

Messages
PeopleTools > Integration Broker > Integration Setup >  Messages
Queues
PeopleTools > Integration Broker > Integration Setup >  Queues
The queue status must be set at Run to let messages go threw.  It is possible to split a queue into sub queues, using a list of fieds to split on.

Service Operations
PeopleTools > Integration Broker > Integration Setup >  Service Operations
A service operation is the object that links all elements.  It links the message and the queue, specifies the code to execute at the reception (handler), the routing (target node, destination node), and the transformation to apply if necessary.
 
Messages FullSync
Rules
Enterprise Components > Integration Definitions > Full Data Publish Rules

The language tab is not used for all messages; mainly for setup tables (countries, languages, diploma, business unit, …)

Exécution
Enterprise Components > Integration Definitions > Initiate Processes > Full Data Publish

The process to execute is EOP_PUBLISHT

Security
For a successful exchange from HR to FN, the user must be present in both environment and have the security to use the Services Operations; it is possible in Peopletoos >= 8.50 to specify a default userid which is used in the source environment to manipulate the Service Operation.  For version below 8.50, workaround exists : the « non-local » node  (PSFT_EP in HR, PSFT_HR in FN) must be of type External the default userid must be set (see below).

More information is available here:
The security must be set to give access to Service Operations to the default user (and any other users allowed to transfer messages).  This must be done in both environments :

Code

Transformation
The transformation is used to translate a message into another version.  PERSON_BASIC and WORKFORCE messages are using such transformation, because datamodel is different between FN and HR.

A transformation is an Application Engine (AE) of type Transform Only.  This AE has only one Peoplecode step, which does the transformation.  %TransformData variable refers to the message (rowset) to transform.
import XXX_HCR_PUBLICATION_RULES:XXXTransformPersonFullToV1;
[…]
Local TransformData &tdata = %TransformData;
/* Set a temp object to contain the incoming document */
Local XmlDoc &tempDoc = &tdata.XmlDoc;
&PERS_MSG = CreateMessage(Message. XXX_PERSON_BASIC_FULLSYNC);
&RS_CURR = &PERS_MSG.GetRowset();
&XMLDOC_TO_MSG = &tempDoc.CopyToRowset(&RS_CURR, "XXX_PERSON_BASIC_FULLSYNC", "VERSION_3");
&RS_V1 = &PERS_MSG.GetRowset("VERSION_1");
[…]
 Local XXX_HCR_PUBLICATION_RULES: XXXTransformPersonFullToV1 &TransV1;
&TransV1 = create XXX_HCR_PUBLICATION_RULES: XXXTransformPersonFullToV1(&RS_CURR, &RS_V1);
%TransformData.XmlDoc.CopyRowset(&RS_V1, "XXX_PERSON_BASIC_FULLSYNC", "VERSION_1");
[…]

Handler

The handler is code which is executed as a certain ; it can be of type OnNotify, OnReceive, OnRoute and OnSend.  OnNotify handlers are called when the message is recieved.

A handler is coded an application classe.  It can be set by clicking on Details.
 
The handler implemets an interface contained in package PS_PT:Integration.  For a onNotify handler, the class implements PS_PT:Integration:INotificationHandler  as followed :

class XXXPersonBasicSync implements PS_PT:Integration:INotificationHandler
   method XXXPersonBasicSync();
   method OnNotify(&_MSG As Message);
end-class;

The OnNotify method is executed when the message is received.  The code can modify the data in the message and inserts / updates the records.

method OnNotify
   /+ &_MSG as Message +/
   /+ Extends/implements PS_PT:Integration:INotificationHandler.OnNotify +/
   /* Variable Declaration */
     
   Local Message &MSG;
     
   /**For CAFM integration**/
   Local boolean &bHaveCAFM;
   Local RE_UTILITIES:CAFMUtil &objCAFMLib;

   Local Rowset &MSG_ROWSET, &LEVEL1_ROWSET;
   Local number &A0, &B1, &A1;
  
   /* &PersonalData = GetLevel0().GetRow(1).GetRowset(Scroll.PERSONAL_DATA);*/
   &MSG = &_MSG;
   If &MSG.IsActive Then
      /*Make Custom Code to remove the zeros in front of emplid*/
      &MSG_ROWSET = &MSG.GetRowset();
      &MSG_ROWSET(1).PERSONAL_DATA.EMPLID.Value = Substring(&MSG_ROWSET(1).PERSONAL_DATA.EMPLID.Value, 6, 6);
     
      /*Bypass Subscribe_IncrReplication to continue working with the Rowset (the following code is the content of Subscribe_IncrReplication) */
      &MSG_LANG_CD = &MSG_ROWSET(1).PSCAMA.LANGUAGE_CD.Value;
      &MSG_BASE_LANG_CD = &MSG_ROWSET(1).PSCAMA.BASE_LANGUAGE_CD.Value;
      &FULL_INCR = "INCR";
      Proc_Sub_Rowset(&MSG_ROWSET);

   End-If;
[…]

The object of type Message derives from the Rowset type; the method GetRowset() is used to retrieve the message as a rowset, to manipulate it easily.