Chris Malek is a PeopleTools® Technical Consultant with two decades of experience working on PeopleSoft enterprise software projects. He is available for consulting engagements.
About Chris Work with ChrisIntroducing a small but powerful PeopleSoft bolt-on that makes web services very easy. If you have a SQL statement, you can turn that into a web service in PeopleSoft in a few minutes.
In this article we are going to look at how the REST-based Reporting Web Services works. This allows an HTTP client to run a PeopleSoft query created in “Query Manager” over a web service. I have a blog post on using HTTP Post Running a PeopleSoft Query using the Reporting Web Services which uses the HttpListeningConnector
(HTTP POST). We will see that using the REST services is a little more “clean” and requires less overhead on the client. After all REST is best!
The default return of these web services are XML. We will see in the latter section how to return JSON.
First let’s give some background into what the “Reporting Web Services” are. PeopleTools delivers some functionality called the “Reporting Web Services” which allows a third party to execute a PeopleSoft Query created in PeopleSoft Query Manager over a web service. This query web service can be an easy way for external systems to pull data out of PeopleSoft. You don’t have to create a new web service for each new data extract. You get a functional user to create the query, ensure your query security is setup and communicate the new parameters to the client.
There are several different execution models documented in the Reporting Web Services PeopleBooks. However, we are going to cover only the synchronous method which the query results will be run in real-time and a response will be returned while the client waits on the line. This is the most simple approach and requires the least amount of code for the third party. There are also web services for getting meta-data on queries as well as creating query definitions from outside systems but I don’t think those have common use cases.
HttpListeningConnector
or the PeopleSoftServiceListeningConnector
if you wanted SOAP.RESTListeningConnector
these web services were ported over to the REST model.QAS_QRY_REST
service and the standard/old operations are contained under the QAS_QRY_SERVICE
service definition.In the screenshot below, I searched for any service operations whose service name started with “QAS”, then sorted by the Service Operation name. I also highlighted in yellow the REST versions of the services.
What you will see here is that there are 2 versions of the service operations. The ones that are REST-based typically have “REST_GET” suffix. So for example, you will see the QAS_LISTQUERY_OPR and QAS_LIST_QUERY_REST_GET
which are basically the same service. The former being “standard” and the latter being REST.
You will also notice a few that have a “REST_POST” suffix. These are ones that require an HTTP Post as they are creating something in the system. This is in-line with standard REST design principles.
In order to run these web services we need to create a PeopleSoft account that has access to both the query objects and the web services. The details of REST web service authorization are covered in the REST Security section. We are assuming you have read that article.
In this section we assume:
RESTUSER
$ecretknock1234
Authorization: Basic some-value-that-identifies-you-to-the-server
.First we are going to create a query called USERS_IN_ROLE
. It is a simple query that has no prompts and returns one column. Here is the SQL.
SELECT A.ROLEUSER
FROM PSROLEUSER A
WHERE ( A.ROLENAME = 'Portal Administrator')
Lets call the query web service. Here is the HTTP signature of that web service call.
GET /PSIGW/RESTListeningConnector/PSFT_CHGDEM/ExecuteQuery.v1/PUBLIC/USERS_IN_ROLE/XMLP/NONFILE?isconnectedquery=n&maxrows=3 HTTP/1.1
Host: demo.cedarhillsgroup.com
Authorization: Basic UkVTVFVTRVI6JGVjcmV0a25vY2sxMjM0
The response from the server is:
<?xml version="1.0"?>
<QAS_GETQUERYRESULTS_RESP_MSG xmlns="http://xmlns.oracle.com/Enterprise/Tools/schemas/QAS_GETQUERYRESULTS_RESP_MSG.VERSION_1">
<query numrows="3" queryname="USERS_IN_ROLE" xmlns="http://xmlns.oracle.com/Enterprise/Tools/schemas/QAS_QUERYRESULTS_XMLP_RESP.VERSION_1">
<row rownumber="1">
<ROLEUSER>
<![CDATA[AA_USER]]>
</ROLEUSER>
</row>
<row rownumber="2">
<ROLEUSER>
<![CDATA[ACA_USER]]>
</ROLEUSER>
</row>
<row rownumber="3">
<ROLEUSER>
<![CDATA[ADM_USER]]>
</ROLEUSER>
</row>
</query>
</QAS_GETQUERYRESULTS_RESP_MSG>
There are a few things to note about the URL.
ExecuteQuery.v1/PUBLIC/USERS_IN_ROLE/XMLP/NONFILE?isconnectedquery=n&maxrows=3
. Inside that we see that:
If you contrast this with the “standard”/SOAP method of calling these services you basically get the same result. However, the location of the parameters are completely in the URL path and query string instead of the HTTP Post Body.
How do you know what the parameters are and in what order? When you define a REST service operation in PeopleTools you define a “template” which are the different path and parameters signature combinations. Here is a screenshot of the QAS_EXECUTEQRY_REST_GET
parameters. Note that I had to expand the template sections to make them viewable in the grid. If you look at this in your your PeopleSoft environment, not all the text is visible.
Your requests have to match one of theses templates for it to pass into a handler to be processed. In the first example above, we matched on the number 6 template.
Let’s take this one step farther. Let’s add a query prompt to the equation and see how we can pass that to the web service. First we create a new query called USERS_IN_ROLE_PROMPT
. It has the following SQL.
SELECT A.ROLEUSER
FROM PSROLEUSER A
WHERE ( A.ROLENAME = :1)
The :1
query prompt is defined as “BIND1” which we will need in the HTTP request.
In order for the client to pass the prompts, new query string parameters must be included to pass the bind name and value. We want to pass a value of “Portal Administrator” as the value for “BIND1”. When we map that to the template for the web service, you add in the following query string parameters.
&prompt_uniquepromptname=BIND1,BIND2&prompt_fieldvalue=VALUE1,VALUE2
The request looks like this:
GET /PSIGW/RESTListeningConnector/PSFT_CHGDEM/ExecuteQuery.v1/PUBLIC/USERS_IN_ROLE_PROMPT/XMLP/NONFILE?isconnectedquery=n&maxrows=3&prompt_uniquepromptname=BIND1&prompt_fieldvalue=Portal Administrator HTTP/1.1
Host: demo.cedarhillsgroup.com
Content-Type: application/xml
Authorization: Basic UkVTVFVTRVI6JGVjcmV0a25vY2sxMjM0
Cache-Control: no-cache
The output would be:
<?xml version="1.0"?>
<QAS_GETQUERYRESULTS_RESP_MSG xmlns="http://xmlns.oracle.com/Enterprise/Tools/schemas/QAS_GETQUERYRESULTS_RESP_MSG.VERSION_1">
<query numrows="3" queryname="USERS_IN_ROLE_PROMPT" xmlns="http://xmlns.oracle.com/Enterprise/Tools/schemas/QAS_QUERYRESULTS_XMLP_RESP.VERSION_1">
<row rownumber="1">
<ROLEUSER>
<![CDATA[CMALEK]]>
</ROLEUSER>
</row>
<row rownumber="2">
<ROLEUSER>
<![CDATA[TESTUSER]]>
</ROLEUSER>
</row>
<row rownumber="3">
<ROLEUSER>
<![CDATA[PS]]>
</ROLEUSER>
</row>
</query>
</QAS_GETQUERYRESULTS_RESP_MSG>
If you had more bind variables, you would then just include several key values pairs in this form.
&prompt_uniquepromptname=BIND1&prompt_fieldvalue=VALUE1&prompt_uniquepromptname=BIND2&prompt_fieldvalue=VALUE2
If you look at the templates for the QAS_EXECUTEQRY_REST_GET service operation you will see some of them have a “filterfields” parameter. This looks interesting. Let’s see what it does.
First let’s add a few more columns to the query definition. We add here ROLENAME and DYNAMIC_SW.
SELECT A.ROLEUSER, A.ROLENAME, A.DYNAMIC_SW
FROM PSROLEUSER A
WHERE ( A.ROLENAME = :1)
If you run that query via the web service you now get back this result.
<?xml version="1.0"?>
<QAS_GETQUERYRESULTS_RESP_MSG xmlns="http://xmlns.oracle.com/Enterprise/Tools/schemas/QAS_GETQUERYRESULTS_RESP_MSG.VERSION_1">
<query numrows="3" queryname="USERS_IN_ROLE_PROMPT" xmlns="http://xmlns.oracle.com/Enterprise/Tools/schemas/QAS_QUERYRESULTS_XMLP_RESP.VERSION_1">
<row rownumber="1">
<ROLEUSER>
<![CDATA[CMALEK]]>
</ROLEUSER>
<ROLENAME>
<![CDATA[Portal Administrator]]>
</ROLENAME>
<DYNAMIC_SW>
<![CDATA[N]]>
</DYNAMIC_SW>
</row>
<row rownumber="2">
<ROLEUSER>
<![CDATA[TESTUSER]]>
</ROLEUSER>
<ROLENAME>
<![CDATA[Portal Administrator]]>
</ROLENAME>
<DYNAMIC_SW>
<![CDATA[N]]>
</DYNAMIC_SW>
</row>
<row rownumber="3">
<ROLEUSER>
<![CDATA[PS]]>
</ROLEUSER>
<ROLENAME>
<![CDATA[Portal Administrator]]>
</ROLENAME>
<DYNAMIC_SW>
<![CDATA[N]]>
</DYNAMIC_SW>
</row>
</query>
</QAS_GETQUERYRESULTS_RESP_MSG>
Let’s imagine that we only care about the ROLEUSER and DYNAMIC_SW field and we do NOT want the ROLENAME back because we are passing it as a parameter and we will already know the value. There is a URI in the service operation that has this parameter: &filterfields={FilterFields*}
. It is supposed to allow this this.
PeopleBooks mentions this:
FilterFieldName : List of field names to be returned. This value is case sensitive and must be the unique field name as returned by the service operation QAS_LISTQUERYFIELDS.
You are starting to see this pattern a lot in single page web applications (SPWA) where some javascript will call a REST service that may have different clients. The service may return a lot of data. However, the SPWA may only care about 5 fields out of the 100. Therefore, it asks the server to only return those 5 fields if possible. This filterfields is supposed to follow that same basic approach.
So let’s grant our user RESTUSER access to the QAS_LISTQUERYFIELDS_REST_GET
service operation (an exercise for the reader) and execute that web service for our query.
GET /PSIGW/RESTListeningConnector/PSFT_CHGDEM/QueryFields.v1/USERS_IN_ROLE_PROMPT?isconnectedquery=N HTTP/1.1
Host: demo.cedarhillsgroup.com
Accept: application/xml
Authorization: Basic UkVTVFVTRVI6JGVjcmV0a25vY2sxMjM0
The response is returns the fields that are defined in our query definition.
<?xml version='1.0'?>
<QAS_LISTQUERYFIELDS_RESP_MSG xmlns="http://xmlns.oracle.com/Enterprise/Tools/schemas/QAS_LISTQUERYFIELDS_RESP_MSG.VERSION_1">
<QAS_LISTQUERYFIELDS_RESP>
<PTQASWRK class="R" xmlns="http://xmlns.oracle.com/Enterprise/Tools/schemas/QAS_LISTQUERYFIELDS_RESP.VERSION_1">
<QueryName>USERS_IN_ROLE_PROMPT</QueryName>
<PTQASFIELDWRK class="R">
<ColumnNumber>1</ColumnNumber>
<FieldName>ROLEUSER</FieldName>
<FieldType>string</FieldType>
<FieldLength>30</FieldLength>
<FieldDecimal>0</FieldDecimal>
<HeadingText>User ID</HeadingText>
<UniqueFieldName>A.ROLEUSER</UniqueFieldName>
</PTQASFIELDWRK>
<PTQASFIELDWRK class="R">
<ColumnNumber>2</ColumnNumber>
<FieldName>ROLENAME</FieldName>
<FieldType>string</FieldType>
<FieldLength>30</FieldLength>
<FieldDecimal>0</FieldDecimal>
<HeadingText>Role Name</HeadingText>
<UniqueFieldName>A.ROLENAME</UniqueFieldName>
</PTQASFIELDWRK>
<PTQASFIELDWRK class="R">
<ColumnNumber>3</ColumnNumber>
<FieldName>DYNAMIC_SW</FieldName>
<FieldType>string</FieldType>
<FieldLength>1</FieldLength>
<FieldDecimal>0</FieldDecimal>
<HeadingText>Dynamic</HeadingText>
<UniqueFieldName>A.DYNAMIC_SW</UniqueFieldName>
</PTQASFIELDWRK>
</PTQASWRK>
</QAS_LISTQUERYFIELDS_RESP>
</QAS_LISTQUERYFIELDS_RESP_MSG>
So the UniqueFieldName
for the user ROLEUSER field is “A.ROLEUSER”. Let’s see what happens when we try to tell the query web service to only return that one field even though the query definition has 3 fields.
GET /PSIGW/RESTListeningConnector/PSFT_CHGDEM/ExecuteQuery.v1/PUBLIC/USERS_IN_ROLE_PROMPT/XMLP/NONFILE?isconnectedquery=n&maxrows=3&prompt_uniquepromptname=BIND1&prompt_fieldvalue=Portal Administrator&filterfields=A.ROLEUSER HTTP/1.1
Host: demo.cedarhillsgroup.com
Accpet: application/xml
Authorization: Basic UkVTVFVTRVI6JGVjcmV0a25vY2sxMjM0
The response back from the server is:
<?xml version="1.0"?>
<QAS_GETQUERYRESULTS_RESP_MSG xmlns="http://xmlns.oracle.com/Enterprise/Tools/schemas/QAS_GETQUERYRESULTS_RESP_MSG.VERSION_1">
<query numrows="3" queryname="USERS_IN_ROLE_PROMPT" xmlns="http://xmlns.oracle.com/Enterprise/Tools/schemas/QAS_QUERYRESULTS_XMLP_RESP.VERSION_1">
<row rownumber="1"/>
<row rownumber="2"/>
<row rownumber="3"/>
</query>
</QAS_GETQUERYRESULTS_RESP_MSG>
What the heck! It looks like a big fat fail whale! I am writing this article using an 8.54 tools release.
It looks like there is a bug there with those filterfield
parameters. You can see that 3 rows did come back but there is no data in there. Maybe I used the wrong field name. Just for fun lets try using just “ROLEUSER” instead of “A.ROLESER” to see if maybe we did not use the right field name.
Making that one substitution
GET /PSIGW/RESTListeningConnector/PSFT_CHGDEM/ExecuteQuery.v1/PUBLIC/USERS_IN_ROLE_PROMPT/XMLP/NONFILE?isconnectedquery=n&maxrows=3&prompt_uniquepromptname=BIND1&prompt_fieldvalue=Portal Administrator&filterfields=ROLEUSER HTTP/1.1
Host: demo.cedarhillsgroup.com
Authorization: Basic UkVTVFVTRVI6JGVjcmV0a25vY2sxMjM0
Accept: application/xml
You get this back.
<HTML>
<HEAD>
<TITLE>RESTListeningConnector</TITLE>
</HEAD>
<BODY>The fields received in the fields filter do not match the query (228,5) PT_QAS.PT_QES.QASExecutionService.OnExecute Name:ValidateQuery PCPC:57704 Statement:759
Called from:PT_QAS.PT_QES.QASExecutionService.OnExecute Name:ExecutePSQueryImp Statement:337
Called from:PT_QAS.PT_QES.QASExecutionService.OnExecute Name:ExecuteQuerySyncRequest Statement:272
Called from:PT_QAS.QASRequestHandler.OnExecute Name:ProcessQASRequest Statement:30
Called from:QAS_EXECUTEQRYSYNC.RequestHandler.OnExecute Name:OnR</BODY>
</HTML>
There are a few things to note about this response.
Content-Type: text/html; charset=UTF-8
and you can see the HTML source. The request told the server via the accept
HTTP request header that she only takes XML. However, HTML was sent back. :-(HTTP/1.1 200 OK
which is definitely NOT correct.
400 Bad Request
because the request is malformed.These issues could present a problem with some programmatic access to these web services. As you can see they have some rough edges that Oracle needs to work out.
When these first web services were first delivered they only supported XML encoding. Starting in 8.55, there is support for getting JSON encoding back of the data. To get JSON back, you need ot ask for it using a URL Query string parameter: json_resp=true
. Let’s look at a full example.
First I created a public query called UNLOCKED_USERS
with the following SQL.
SELECT A.OPRID, A.USERIDALIAS, TO_CHAR(CAST((A.LASTUPDDTTM) AS TIMESTAMP),'YYYY-MM-DD-HH24.MI.SS.FF'),
A.LASTUPDOPRID, A.PTALLOWSWITCHUSER
FROM PSOPRDEFN A
WHERE ( A.ACCTLOCK = 0)</pre>
Now let’s call the QAS web service to execute the query and return JSON. Here is the HTTP Syntax.
GET /PSIGW/RESTListeningConnector/PSFT_CHGDEM/ExecuteQuery.v1/PUBLIC/UNLOCKED_USERS/JSON/NONFILE?isconnectedquery=n&maxrows=3&json_resp=true HTTP/1.1
Host: demo.cedarhillsgroup.com
Authorization: Basic UkVTVFVTRVI6JGVjcmV0a25vY2sxMjM0
The JSON response is header has a Content-Type: application/json; charset=UTF-8
. The response body has the following JSON:
{
"status": "success",
"data": {
"query": {
"numrows": 2,
"queryname=": "UNLOCKED_USERS",
"rows": [
{
"attr:rownumber": 1,
"A.OPRID": "PS",
"A.USERIDALIAS": "",
"A.LASTUPDDTTM": "2016-05-24T12:17:29-0700",
"A.LASTUPDOPRID": "PPLSOFT",
"A.PTALLOWSWITCHUSER": 0
},
{
"attr:rownumber": 2,
"A.OPRID": "PTWEBSERVER",
"A.USERIDALIAS": "",
"A.LASTUPDDTTM": "2016-07-05T09:34:22-0700",
"A.LASTUPDOPRID": "PPLSOFT",
"A.PTALLOWSWITCHUSER": 0
}
]
}
}
}
Note that the JSON key reference the field names and table aliases which can be super brittle.