Showing posts with label REST. Show all posts
Showing posts with label REST. Show all posts

Sunday, August 2, 2020

PeopleSoft Reporting Web Services

While browsing through Peoplebooks for API syntax I ran into this topic related to Reporting web services and I was pleasantly surprised that this feature is available out of the box but I don't think is used frequently. Looks like it has been around since 8.56.x  or maybe even before. So PeopleSoft provides SOAP as well as REST based web services to expose PeopleSoft data based on PS Query or Connected query to external systems. I think this is pretty cool. So as long as the data set can be constructed in PS Query or Connected Query it can be exposed out to a 3rd party service using out of the box infrastructure. This greatly simplifies elaborate design and development time of application engine programs which are generally used for building interfaces.

PeopleSoft provides QAS_QRY_SERVICE which is the service for Query Access Manager.
and PROCESSREQUEST which is the service for Process Scheduler. Via these services once can create and get query and process request type items. I tried out the query service to execute a query via Postman and it works like a charm.

There is lot of good info in PeopleBooks so I won't repeat all of that here. So I currently have two RSS feeds based on PS Query which I use for monitoring the status of process monitor processes and jobs and another one which monitors the asynchronous  services. I used these as the test cases to fetch data using the web services instead of RSS. Also setup a user which has access to the service operation and to the queries and can only run the queries. 

As I have the queries already built I tested out the REST service operation QAS_EXECUTEQRY_REST_GET to execute the queries. 

Example of the URI Template:
{OwnerType}/{QueryName}/{OutResultType}/{OutResultFormat}?isconnectedquery=
{isConnectedQuery}&maxrows={MaxRow}&prompt_psqueryname={Prompt_PSQueryName*}
&prompt_uniquepromptname={Prompt_UniquePromptName*}&prompt_fieldvalue=
{Prompt_FieldValue*}&filterfields={FilterFields*}&json_resp={json_response}
Provided QueryName, OwnerName is PUBLIC as the query I am using is public query, OutResultType is JSON as I want the results back in JSON format, OutResultFormat is NONFILE as I want the response in message object, isconnectedquery = N, maxrows set it to 100 ( I would never get so many rows back), json_resp set it to true, did not set anything for rest of the URI parameters. 

HTTP GET https://myserver/PSIGW/RESTListeningConnector/PSFT_HR/ExecuteQuery.v1/PUBLIC/N_MY_QUERY/JSON/NONFILE?isconnectedquery=N&maxrows=100&prompt_psqueryname=&prompt_uniquepromptname=&prompt_fieldvalue=&filterfields=&json_resp=true 


The response is received as follows:

{
   "status":"success",
   "data":{
      "query":{
         "numrows":3,
         "queryname=":"N_MY_QUERY",
         "rows":[
            {
               "attr:rownumber":1,
               "PRCSINSTANCE":558373,
               "MAINJOBINSTANCE":558373,
               "PRCSJOBNAME":"MYJOB",
               "PRCSTYPE":"PSJob",
               "PRCSNAME":"MYPRCS",
               "OPRID":"PS",
               "RUNCNTLID":"MYRUN",
               "RECURNAME":"Daily",
               "RUNDTTM":"2020-07-04T12:15:00-0400",
               "RUNSTATUS":"Processing",
               "DISTSTATUS":"Scheduled"
            },
            {
               "attr:rownumber":2,
               "PRCSINSTANCE":558374,
               "MAINJOBINSTANCE":558373,
               "PRCSJOBNAME":"MYJOB",
               "PRCSTYPE":"SQR Process",
               "PRCSNAME":"MYSQRPRCS",
               "OPRID":"PS",
               "RUNCNTLID":"MYRUN",
               "RECURNAME":"Daily",
               "RUNDTTM":"2020-07-04T12:15:00-0400",
               "RUNSTATUS":"Processing",
               "DISTSTATUS":"Scheduled"
            },
            {
               "attr:rownumber":3,
               "PRCSINSTANCE":558375,
               "MAINJOBINSTANCE":558373,
               "PRCSJOBNAME":"MYJOB",
               "PRCSTYPE":"SQR Report",
               "PRCSNAME":"MYPROCESS",
               "OPRID":"PS",
               "RUNCNTLID":"MYRUN",
               "RECURNAME":"Daily",
               "RUNDTTM":"2020-07-04T12:15:00-0400",
               "RUNSTATUS":"Pending",
               "DISTSTATUS":"Scheduled"
            }
         ]
      }
   }
}


Following is an example when query does not return any results.

{
   "status":"success",
   "data":{
      "query":{
         "numrows":0,
         "queryname=":"N_MY_QUERY",
         "rows":[

         ]
      }
   }
}

Tuesday, April 21, 2020

Creating JSON request for REST web services

PeopleSoft provides Document technology to be used to generate JSON request messages but in my experience they are very restrictive especially when working on integrating with 3rd party web services. So following is what I did to generate a JSON request message to post to a 3rd party REST web service. 

The request that I have to generate is in the following form.
[
   {
      "attrib1":"value1",
      "attrib2":"value2",
      "attrib3":{
         "attrib3_1":"values3_1",
         "attrib3_2":"values3_2",
         "attrib3_3":"values3_3",
         "attrib3_4":"values3_4"
      }
   }
]

I am running PT 8.57.x and at this time its not possible to build a document with the root node as an array as shown in the example below. Also I have nested compounds which is also a challenge, the parent compound does not have a label where as the child does. 
So to build something like above I am using the CreateJsonBuilder API provided by PeopleSoft.

Local JsonBuilder &jbldr = CreateJsonBuilder();
Local JsonArray &jArray;
Local string &json;
Local message &request, &response;
Local boolean &bRet;

&jbldr.StartArray(""); /* no label */
 &jbldr.StartObject(""); /* no label */
  &jbldr.AddProperty("attrib1", "value1");
  &jbldr.AddProperty("attrib2", "value2");
   &jbldr.StartObject("attrib3"); /* need a label */
    &jbldr.AddProperty("attrib3_1", "value3_1");
    &jbldr.AddProperty("attrib3_2", "value3_2");
    &jbldr.AddProperty("attrib3_3", "value3_3");
    &jbldr.AddProperty("attrib3_4", "value3_4");
   &jbldr.EndObject("attrib3"); /* closing out the compound or JSONObject */
 &jbldr.EndObject("");
&jbldr.EndArray("");


/* this will return the array just like what I want */
&jArray = &jbldr.GetRootNode().GetJsonObject().GetJsonArray("");
&json = &jArray.ToString();

Created a basic non-rowset based message and assigned that as the request message in my service operation. Use this method to set the content for the message segment for a non-rowset-based message only.

&bRet = &request.SetContentString(&json);
&response = %IntBroker.SyncRequest(&request);

That's it, works like a charm.

Sunday, December 23, 2018

JSON Parser

PeopleSoft has undocumented JSON related API and this post covers some of the routines that I have tried to dynamically parse a json response. I think this API calls were made available in PT 8.56.x as part of PeopleSoft's inbuilt integration with ElasticSearch. 

I am making a RESTful web service call to a service hosted by a 3rd party vendor from PeopleSoft; so PeopleSoft is a consumer of the service. Focus here is to parse the response from the service so I am not covering how the service is setup and the request part of the service. 

So I have two types of responses. Response 1 as shown below 


{

    "error": {
        "message": "Some message text",
        "detail": "detail text about the error"
    },
    "status": "failure"
}

and Response 2 as follows.

{
   "import_set":"Import set value",
   "staging_table":"tablename",
   "result":[
      {
         "status":"updated",
         "error_message":"Unable to format 01-01-2019 using format string yyyyMMdd  for field hire_dt"
      }
   ]
}

In Response 1, I has 2 children, viz "error" and "status", whereas Response 2 has 3 children, import_set, staging_table and result.

In Response 1, "error" is a JSonObject which has 2 more children, message and detail. In Response 2, "result" is a JSonArray which has 2 children status and error_message.


Local string &content, &propName, &propValue;
Local JsonParser &parser;
Local JsonObject &jsonRoot, &jsonDetails;
Local JsonArray &jArray;
Local boolean &ret; 
Local number &i, &j, &k, &l;


&parser = CreateJsonParser(); /* this is the undocumented API */
&ret = &parser.Parse(&content); /* &content is the json response as a string */
&jsonRoot = &parser.GetRootObject();

For &i = 1 To &jsonRoot.GetChildCount()
/* for Response 1, following will get status tag and its value */
/* for Response 2, following will get import_set, staging_table and its values */
   &propName = &jsonRoot.GetPropertyNameAt(&i);
   &propValue = &jsonRoot.GetProperty(&propName);
   /* if there is a nested value then its either JsonArray or JsonObject */
   Evaluate &propValue
   When = "JsonArray"
/* this will return status and error_message which are in Response 2 */
      &jArray = &jsonRoot.GetJsonArray(&propName);
      For &j = 1 To &jArray.Length()
         &jsonDetails = &jArray.GetJsonObject(&j);
         For &k = 1 To &jsonDetails.GetChildCount()
            &propName = &jsonDetails.GetPropertyNameAt(&k);
            &propValue = &jsonDetails.GetProperty(&propName);
         End-For;
      End-For;
      Break;
   When = "JsonObject"
/* this will return message and detail which are in Response 1 */
      &jsonDetails = &jsonRoot.GetJsonObject(&propName);
      &numCnt = &jsonDetails.GetChildCount();
      For &l = 1 To &jsonDetails.GetChildCount()
         &propName = &jsonDetails.GetPropertyNameAt(&l);
         &propValue = &jsonDetails.GetProperty(&propName);
      End-For;
      Break;
   When-Other;
      /* when not JsonArray or JsonObject, get prop name and value which is at root level */
      Break;
   End-Evaluate;
End-For;

Hope this helps.

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>