Monday, November 23, 2015

PeopleSoft RESTful Webservice

Here is my initial exposure to creating a RESTful webservice in PeopleSoft. I am running HR 9.1 on PT 8.53.x. 
The requirement is to provide a list of locations based on a region input and then provide a list of employees in that region/location combination.
Here is the flow.


This is accomplished by creating a provider service using the GET method.
I am assuming the basic IB setup is in place, which is essentially making sure the local gateway is up and running and under Service configuration, the REST target location is provided. 

Navigate to PeopleTools > Documents > Document Builder.
First create a document with 2 primitives called as "REGION" and  "LOCATION".
We could have just created a non-rowset based message, but document object provides the flexibility to exchange data in XML or JSON format whereas non-rowset based message would be just XML.
Both primitives will be of the type string, sub-type = None. Adjust the length appropriately, in my case region is just a 1 char code and location is 10 chars.

Now create a response document. This document will have 2 collections one for the location data and the other for the employee data. Under each collection I added a compound child which is essentially a view (record) that I created which will return the appropriate data elements. 
LOC_RESPONSE  -- root element
      LOC_DATA -- collection
            LOC_DTL_VW -- compound
      EMP_DATA -- collection 
            EE_LIST_VW -- compound 

When the compound child is created behind the scenes PeopleSoft creates another document. So make sure the naming of each type is appropriate otherwise it can get pretty confusing. 

Now I created another document which I am going to use for handling fault, this has only one primitive in it of the type text.

After each document is created and saved, verify the document using the "Validate" and "Document Tester" options available on the page.

From the document now create messages, so navigate to PeopleTools > Integration Broker > Integration Setup > Messages. Add new messages of type document. You can keep the document name and message name same. Did this for all the three documents that I created in the earlier step. Once in just click save. The page looks similar to the document builder page, with the addition of the metadata reference section visible at the top of the page. No additional work is required here.

Now navigate to PeopleTools > Integration Broker > Integration Setup > Services and create a REST service. Make sure the "REST Service Type" check-box and the "Is Provider" check-box is checked. Save the service. 
Now select the REST method, in my case "Get", provide a service operation name and click on Add. The REST method is appended to the service operation name.

the REST Base URL will be grayed out and would be auto-populated with the setup info and service name. Under the URI section I have the following indexes.

1. Locations/?region={REGION}
2. Locations/{REGION}/?location={LOCATION}

Document Template - lookup the request document that was created in the first step above. This is the document which has the 2 primitives defined. The primitive name should match what is provided in the curly brackets under the URI section. 

Make the service operation active. Generate any-to-local routing.
Associate the response and fault messages appropriately. In my case the data exchange would be done in JSON format, so under content type I selected "application/json".

Provide service operation security. Verify routing info. Enable logging if required. Doesn't help much in case of JSON messages though.

For the handler section we need to write application package peoplecode for the OnRequest method.  
Following is the basic structure.


import PS_PT:Integration:IRequestHandler;

class Locations implements PS_PT:Integration:IRequestHandler
   method Locations();
   method OnRequest(&MSG As Message) Returns Message;
   method OnError(&request As Message) Returns string;
end-class;

/* constructor */
method Locations
end-method;

method OnRequest
   /+ &MSG as Message +/
   /+ Returns Message +/
   /+ Extends/implements PS_PT:Integration:IRequestHandler.OnRequest +/
   /* Variable Declaration */
  
End-method;

method OnError
   /+ &request as Message +/
   /+ Returns String +/
   /+ Extends/implements PS_PT:Integration:IRequestHandler.OnError +/


End-method;


Once the service operation is saved, a WADL has to be generated. So navigate back to services and click on the "Provide Web Service" link and step through the wizard.

Once this is done you can test it from an utility like SOAPUI (yes can be used to test RESTful web services) or plain browser or via PeopleTools > Integration Broker > Services Utilities > Provider REST Template.

So the call to this service would be like so.
http://servername/PSIGW/RESTListeningConnector/Locations.v1/Locations/?region=A
which will return all locations in region A and,


http://servername/PSIGW/RESTListeningConnector/Locations.v1/Locations/A/?location=XYZ01
which will return employees in region A and Location XYZ01. Location XYZ01 is returned by the first call.

Output 1:

<?xml version="1.0"?> 
<data psnonxml="Yes"> 
  <![CDATA[
{"LOC_RESPONSE": {
"LOC_DATA": [
{
"ADDRESS1": "1 Palace Dr",
"CITY": "",
"DESCR": "My Location",
"DESCRSHORT": "My Location",
"EFFDT": "2010-08-02",
"EFF_STATUS": "A",
"EXTENSION": "",
"FAX": "",
"LOCATION": "XYZ01",
"MESSAGE_TEXT2": "",
"PHONE": "",
"POSTAL": "",
"STATE": ""
},
{
"ADDRESS1": "100 Main Street",
"CITY": "",
"DESCR": "New Location",
"DESCRSHORT": "New Location",
"EFFDT": "2011-08-02",
"EFF_STATUS": "A",
"EXTENSION": "",
"FAX": "",
"LOCATION": "ABC01",
"MESSAGE_TEXT2": "",
"PHONE": "",
"POSTAL": "",
"STATE": ""
}
],"EMP_DATA": {"DEPTNAME": "","EMAIL_ADDR": "","NAME": "","PHONE": "","TITLE": ""}}
} ]]> 
</data>

Output 2:

<?xml version="1.0"?> 
<data psnonxml="Yes"> 
  <![CDATA[{"LOC_RESPONSE": {
"EMP_DATA": [
{
    "DEPTNAME": "My Location Dept",
"EMAIL_ADDR": "jon.doe@company.com",
"NAME": "Doe,Jon",
"PHONE": "123456789",
"TITLE": "Managing Director"
},
{
    "DEPTNAME": "My Location Dept",
"EMAIL_ADDR": "jane.doe@company.com",
"NAME": "Doe,Jane",
"PHONE": "123456789",
"TITLE": "Managing Director"
}
]}
} ]]> 

</data>



29 comments:

  1. Hi, Need some help on PeopleSoft Consuming JSON MQ message.



    We configured JMSTARGET node and were able to establish the connection between MQ series and PS local node as per PS Documentation.



    We are not able to consume the JSON message. We tried to use Document to create the message (Created same JSON structure document) and created the Service operation (not REST)and added handler but when MQ series message published to our queues, MQ message is uncommited and error log says "Unknown JMS message Format".

    Could you please help me on how to consume MQ series JSON message using Document/method? Any Tablespace or naming should I use differently(?)

    ReplyDelete
    Replies
    1. I haven't done a lot of work with JMS queues, but I have done a basic POC using XML messages - check it out and see if that helps you. http://psdips.blogspot.com/2016/12/jms-connectors-and-queues.html

      Delete
  2. Hi ..we are developing a REST restful service, in which we are unable to publish results for more than 1 row.

    ReplyDelete
    Replies
    1. can you provide some additional info. Verify your collection/compound setup. You can always run a trace or use PC Debugger to check the flow of your program.

      Delete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Hi,

    We have designed an Get API where we are providing the client with a list of dates. The current json format is shown below:

    “minimumEffectiveDate”: “2018-03-01”,
    “availableDates”: [
    {
    “date”: “2018-03-02”
    },
    {
    “date”: “2018-03-03”
    },
    {
    “date”: “2018-03-04”
    },
    {
    “date”: “2018-03-07”
    }

    Client requires the dates to show in the following format:

    “minimumEffectiveDate”: “2018-03-01”
    “availableDates”: [
    “2018-03-02”
    “2018-03-03”
    “2018-03-07”
    ]
    Any recommendation how this can be achieved? Thanks in advance

    ReplyDelete
    Replies
    1. Your Json isn't formatted correctly, so I'm guessing following is what you are trying to build.

      {
      "minimumEffectiveDate": "2018-03-01",
      "availableDates": [
      "2018-03-02",
      "2018-03-03",
      "2018-03-07"
      ]
      }

      In this case you cannot use a Document type response message.
      Instead use a Non-Rowset based message for the response message.

      The basic steps are as follows:
      Build the Json, one option is to use JsonBuilder class
      Local JsonBuilder &jbldr = CreateJsonBuilder();

      /* get the JSON generated into a string. */
      Local JsonGenerator &jgen = CreateJsonGenerator();
      &jgen.SetRootNode(&jbldr.GetRootNode());
      &json = &jgen.ToString();

      Then populate the actual response message (where &MSG is a non-rowset based message)..

      &MSG.SetContentString(&json);

      Try this out.

      Delete
    2. Thank you. I was able to generate the response using your recommendation. There is just one issue that I am currently trying to resolve. Here is the current output:

      {
      “minDate”: “2018-03-01”
      “availableDates”: {
      “2018-03-02”
      “2018-03-03”
      “2018-03-07”
      }
      }

      As you can see, the inner section shows a "{" instead of a "[". I am told that this is apparently not standard json. Any suggestions?

      Delete
    3. The square bracket denotes an array in JSON so you need to build that section out using StartArray() and EndArray(). You can also use https://jsonlint.com/ to validate your JSON.

      Delete
    4. Thank you again for your help. I added the array logic, and this is what I am getting now:

      {
      “minDate”: “2018-03-01”
      “availableDates”: [
      {
      “2018-03-02”
      “2018-03-03”
      “2018-03-07”
      }
      ]
      }

      The validation at the https://jsonlint.com site failed, stating the following:

      Error: Parse error on line 5:
      ...": [{ "2019-05-30", "2019-05-31",
      ----------------------^
      Expecting ':', got ','

      It looks like the issue is with the [ and { next to each other. I tried removing the startobject after the startarray, but received an error stating that addproperty needs to be within startobject.

      Please advise.

      Delete
    5. After you initialize StartArray, use AddElement to add the date values to the array.

      Delete
    6. This comment has been removed by the author.

      Delete
    7. Thank you. I was able to utilize your suggestion and generate the result in the required format.

      So far, I have designed APIs where I create request documents that capture the requested parameters, and set them as criteria for SQL Select statements to generate the results.

      We now have a new requirement which is for generating all values from a table. I am not sure how to setup the request where no specific value is being requested.

      Appreciate your help.

      Delete
    8. Hi...just checking to see if you have any suggestions regarding my last inquiry. Running out of ideas :(

      Delete
    9. You can do something like https://{REST_URL}/?data=all and then check if value is "all" then select all data from the table and send it in the response.

      Delete
    10. Thanks. The requirement for the URL is "https://{REST_URL}/all-types". Sorry..I should have mentioned that. Any suggestions?

      Delete
    11. Just add a new URI index as "all-types", lets say this is index #2, then in your peoplecode check the index value of the request &MSG.URIResourceIndex, so if this is 2 then do whatever you have to do to return all data in the response.

      Delete
    12. Thank you again. I will look into it.

      The next item on my list is error handling. Do you have any sample code or resources that you would suggest for handling error codes 400 and 404?

      Delete
    13. Hi...just following up on my earlier post to see if you have any suggestions. Thanks for your help.

      Delete
  5. This comment has been removed by the author.

    ReplyDelete
  6. Hi..I was able to get the error handling code to be generated. However, I am struggling with passing a message based on some conditions. Here is what my code currently looks like:

    Method OnRequest
    .
    .
    .
    If None(&Tbl) Then
    &errorMessage = "The invoice you requested does not exist.";
    throw CreateException(0, 0, &errorMessage);
    Else
    &errorMessage = "Some other message based on additional condition";
    throw CreateException(0, 0, &errorMessage);
    .
    .
    .
    Method OnError

    Local XmlDoc &xmlout;
    Local XmlNode &rootNode;
    Local XmlNode &tempnode, &childnode;



    &xmlout = CreateXmlDoc("");
    Local Exception &exception = &msRequest.IBException

    &childnode = &xmlout.DocumentElement.AddElement("ERROR");
    &tempnode = &childnode.AddText(&exception.ToString());


    Return &xmlout.GenXmlString();
    end-method;

    This is the result I am getting:


    Function OnRequest does not return a result. (180,69)

    It looks like the content of &errormessage is not being passed from the Onrequest method to the OnError method.

    Please advise.....

    ReplyDelete
    Replies
    1. Its difficult for me to provide a solution just based on this information. I would suggest reviewing the following Peoplebooks link for possible solution to your issue.

      https://docs.oracle.com/cd/E41633_01/pt853pbh1/eng/pt/tibr/concept_UnderstandingIntegrationPeopleCode-07642b.html

      Delete
    2. Thank you..I will review it.

      Delete
    3. It looks like the issue is with the "throw CreateException(0, 0, &errorMessage);" No matter what message set and message number I use (also tried hardcoded text in place of &errormessage), the message output is always "Function OnRequest does not return a result. (180,69)"

      Here is the OnError Method code:

      method OnError
      /+ &MSG as Message +/
      /+ Returns String +/
      /+ Extends/implements PS_PT:Integration:IRequestHandler.OnError +/
      /* %This.OnErrorHttpResponseCode = 400;
      /* %This.OnErrorContentType = "application/json";*/

      Local integer &nMsgNumber, &nMsgSetNumber;
      Local string &sText;

      Local XmlDoc &xmlout;
      Local XmlNode &rootNode;
      Local XmlNode &tempnode, &childnode;

      &nMsgNumber = &MSG.IBException.MessageNumber;
      &nMsgSetNumber = &MSG.IBException.MessageSetNumber;

      &xmlout = CreateXmlDoc("");
      Local Exception &exception = &MSG.IBException;

      &childnode = &xmlout.DocumentElement.AddElement("ERROR");
      &tempnode = &childnode.AddText(&exception.ToString());

      Return &xmlout.GenXmlString();
      end-method;

      Here is the output for the above method:

      <--?xml version="1.0"?-->
      <--RESPONSE--><--ERROR-->Function OnRequest does not return a result. (180,69)<--/ERROR--><--/RESPONSE-->

      We are in PT 8.55.02 and I came to learn about a bug that is related to handling multiple fault codes and error content types which is apparently resolved in 8.56. However, I am not sure if this bug (seems unlikely) is the reason behind the issue I am experiencing.

      Do you have any suggestions?

      Appreciate all your help so far. Thanks.

      Delete
    4. Ace,
      I am running PT 8.57.x so not sure about the bug you mentioned related to PT 8.55.02. Give the following a shot.

      class GetTest implements PS_PT:Integration:IRequestHandler
      method OnRequest(&MSG As Message) Returns Message;
      method OnError(&request As Message) Returns string;
      property integer OnErrorHttpResponseCode;
      property string OnErrorContentType;
      end-class;

      method OnRequest
      /+ &MSG as Message +/
      /+ Returns Message +/
      /+ Extends/implements PS_PT:Integration:IRequestHandler.OnRequest +/
      /* Variable Declaration */

      Local Message &response;
      Local string &Tbl, &errorMessage;

      &response = CreateMessage(Operation.GET_SVCOP, %IntBroker_Response);

      If None(&Tbl) Then
      &errorMessage = "no data provided in request";
      throw CreateException(0, 0, &errorMessage);
      end-if;

      Return &response;
      end-method;

      /* the goal of the error method is to generate the error string */
      /* in my service operation definition the fault message is of the type document and content type is application\json and status is 400 */
      method OnError
      /+ &request as Message +/
      /+ Returns String +/
      /+ Extends/implements PS_PT:Integration:IRequestHandler.OnError +/
      Local string &sfault;

      %This.OnErrorHttpResponseCode = 400;
      %This.OnErrorContentType = "application\json";

      /* I can build the following using peoplecode json api but for this test just building the string as shown below */

      &sfault = "{""FAULT_ROOT"": {""FAULT_DATA"": """ | &request.IBException.ToString() | """}}";

      Return &sfault;
      end-method;

      so in case of error I will receive the response as shown below.
      {"FAULT_ROOT": {"FAULT_DATA": "no data provided in request (0,0) N_TEST.GetTest.OnExecute Name:OnRequest PCPC:2872 Statement:41 (0,0)"}}

      Delete
  7. Hi, We need to use Non-Rowset based message If the response message was populated with the PeopleCode. Document type message is only can be used if the response message was populated from the table/view. Please correct me if i wrong. Thanks.

    ReplyDelete
    Replies
    1. That's not true, you can use either non-rowset or document based messages.

      Delete
    2. Well noted. If this is the case, may i know at what situation we're going to use non-rowset and document messages.

      Thanks.

      Delete
    3. It depends. Read my post https://psdips.blogspot.com/2020/04/creating-json-request-for-rest-web.html and maybe that will help answer your question.

      Delete