Using SOAP to terminate a running workflow - Part 2

0 Likes
over 10 years ago

Novell Identity Manager with the Role Based Provisioning Module support an interesting notion called workflows. You can read more about where all the associated bits and pieces for Workflows reside, in this series of articles:

The user interface is somewhat hard to use, as there are important things and widgets you need, here there, and everywhere. I wish the UI could be cleaned up, but darned if I know how you would do it. It is a very complex product that needs a lot of stuff shown.

With the addition of the Roles Based Provisioning Model (well ok, it started with the Provisioning version of the User Application, way back in Identity Manager 3, but thats just a detail in the distant past now), there are really two sides of the house in Identity Manager now. There is the engine side with DirXML Script and Policy, then there is the Provisioning side. They are quite disconnected, with a few inter-connect points. There is the Start Workflow token and some Role management (Add or Remove tokens) on the engine side, along with Roles and Resources being enforced by the Roles and Resources driver. On the Provisioning side there is the ability to read and write to eDirectory through the DAL (Directory Abstraction Layer). There are a number of activities that can do other 'stuff' of interest. Additionally there is a SOAP interface into the various Provisioning actions. New in Identity Manager 4.0 which includes RBPM 3.7, if you link them to the User Application driver object, you can reference ECMA Script functions and Global Configuration Variable values inside the provisioning view.

While there is a Start Workflow token, at times it would be pretty useful to have a Stop Workflow token. Well, can't have everything built in, but there is enough functionality to do what is needed. Consider this notion, you call a Start Workflow, passing in the request ID of a workflow, and it makes a SOAP call to cancel that running workflow. Thus you have a Stop workflow event, using the Start Workflow token. The same can be considered for Approving a workflow. It would be nice to have such a token, but until that is available it is pretty straightforward to make a workflow that when passed the request ID will approve the workflow.

In part 1 of this series I talked through some of the issues involved, in terms of where in a Provisioning Request Definition (PRD) you would start to configure this (Add a SOAP Integration Activity into the workflow view), where you would get the WSDL (Web Services Description Language) file from (login to User App as a User App Admin user, and it is in the bottom left corner, get the Provisioning WSDL). Then how to open up the Integration View (right click on the SOAP Integration Activity dropped in your workflow and select Edit Integration Activity and a new tab appears along the bottom).

Finally we discussed some of what you see in the Integration view. Here is a screen shot to make it easier to understand as I talk about it.

That shows the four windows across the top, each with different representative XML documents that are involved. You have the Input, and Output windows. Those make sense. With SOAP, you generate and submit an Input document, and then the SOAP endpoint returns an Output document. Then there are _SystemFault and AdminException windows, for various error cases that occur. Each of these windows is an XML browser that shows a tree view by default, with a tab to see the XML source available.

When I first started working on this I started adding XPATH tokens to add in my data, into the document. You can see a couple that come in by default with the WSDL definition. Well more correctly, Designer figures based on the WSDL and presents what is needed to get it working. But it turns out not to be neccasary, as I will explain in a moment, but first a digresion about XPATH.

The XPATH here is different than usual XPATH in IDM. The command syntax is built using the ECMA expression builder, and has a browser to select functions for setAttribute and to pick the node in the XML document and what not.

Input.createXPath("terminateInput/terminateRequest/TerminationType").setAttribute("xmlns:tns", "http://www.novell.com/provisioning/service")

In Identity Manager Policy, (DirXML Script) when you use XPATH the current context is the current operation node. That is, for an <add> event, the XPATH is not nds/input/add/add-value to select the <add-value> node, rather it is just plain add-value since the current context is already at the operation node, which is nds/input/add and we are selecting, relative to the current context. Thus the XPATH of -dn selects the src-dn XML attribute from the current operation node. (Which might look like <add src-dn="\TREE\O\OU\User"> )

However here in the Integration Activity XPATH, you need to start at the root of the document. So when you look at the Input window, you will see the document begins with a node <terminateInput> which is interesting since in soapUI it shows differently.

How soapUI displays the SOAP function:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://www.novell.com/provisioning/service">
<soapenv:Header/>
<soapenv:Body>
<ser:terminateRequest>
<!--Optional:-->
<ser:arg0> 30f1e2ae98ce4f91829df1af5bac6cb4</ser:arg0>
<ser:TerminationType>RETRACT</ser:TerminationType>
<!--Optional:-->
</ser:terminateRequest>
</soapenv:Body>
</soapenv:Envelope>

How the Integration Activity Input window displays the SOAP function:

<terminateInput>
<terminateRequest xmlns="http://www.novell.com/provisioning/service" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!--Element 'arg0' is optional-->
<!--The value of the attribute 'nillable' is 'true' The value of the attribute 'type' is 'string'-->
<arg0 xsi:type="xsd:string">sample</arg0>
<!-- Choice is RETRACT-->
<!-- Choice is ERROR-->
<TerminationType xmlns:tns="http://www.novell.com/provisioning/service" xsi:type="xsd:string">RETRACT</TerminationType>
<!--Element 'arg2' is optional-->
<!--The value of the attribute 'nillable' is 'true' The value of the attribute 'type' is 'string'-->
<arg2 xsi:type="xsd:string">sample</arg2>
</terminateRequest>
</terminateInput>

I think the difference is how the SOAP tool (soapUI vs Integration Activity) repackages the document, into a SOAP envelope. I think soapUI just shows you it with the envelope intact, but the Integration Activity bundles it internally in a <terminateInput> node that it will fix that up after the fact to be inside a proper <SOAP-ENVELOPE> node

Anyway, the XPATH to specify the request ID is easy enough in that case, knowing you have to start at the root of the document:

terminateInput/terminateRequest/arg0

Now, as I said above, I started fiddling with the function browser, to add the arg0 node:

Input.createXPath("terminateInput/terminateRequest").setAttributeNode("arg0")

Then set the value as a text string:

Input.createXPath("terminateInput/terminateRequest/arg0").setText("30f1e2ae98ce4f91829df1af5bac6cb4")

but it turns out all that work is not needed, since once you explore the user interface in PRD's and get used to where things might be, you learn where to look, if you suspect you are missing something. For example, if you are trying to configure something, try right clicking on it, and see if you can show any additional menus. This is how I found the Edit Integration Activity.

Amusingly enough, have you ever tried to configure the email token in a PRD workflow? Its not in Properties, where you might expect to find such settings. Check out the Edit Data Item Mappings just in case as well. Nope, however if you right click on it, and there in the menu there is a Show Email settings option that then opens a new window pane with the Email page, where you can specify the email template object and the string tokens that will be replaced into the email template. As usual, you can use ECMA, flowdata, and built in functions to generate the data being sent in the tokens.

Thus the first thing to do is look at the right click options. Well there is the usual Data Item Mapping option, so that worth a look. Well would you look at that? Yet another custom view in Data Item Mappings, where you have three columns. Source Expression, Web Service Input Field, and Data Type.

So what you do is specify where the data value is in the flowdata, with say a flowdata.get("start/request_form/processID") that is mapped to the Web Services Input Field, as the XPATH expression of: terminateInput/terminateRequest/arg0

Now, since there are optional data field in the WSDL (specifically arg2) we do not want to send them neither empty (<arg2></arg2>) nor null since that will almost certainly throw an error.

Therefore in the Data Item Mappings view, there is a Mapping button, which shows the XML <terminateInput> document in tree view that you can expand, and unselect arg2, so that it is not shown in the list of data to be sent in.

In this example, you also need to do TerminationType and give it a value. The WSDL in the comments actually explains the valid values, as you can see in the Integration Activity view. (My soapUI example is cleaned up after I played around a bit). Anyway, your options are RETRACT or ERROR so either pass it in as a field in your request form, and then you can use it with the flowdata.get() command or else you can specify it as a constant, and just set a string value. I chose to pass it in, leaving me with a bit more flexibility in terms of the use of this workflow. This way, should I later need to do a Stop Workflow as a RETRACT or Stop Workflow because its an ERROR case, I just use the same workflow, but call it with the value I care about in one of the fields.

Have I mentioned that the UI for this part of the product could clearly use some clean up? I wish I knew how to clean it up so I could suggest it to the Designer folk, but honestly I have no clue. The more I use it, the more I understand where things are, and sort of get a feel for why they are there. However, as a new user, it is ridiculously daunting. Though having played with a few other similar applications now it starts to make a little more sense, nonetheless the learning curve is way too high for the current user interface in my opinion.

Anyway, what is also really cool about the Integration Activity is that you can click on the Play button that shows up in the toolbar (sorry not in my screen shot). You can insert break points, so it runs to the next break point, then you can step through the Action Model. (The window in which you specify the XPATH calls, the actual call to the WS Interchange function (which actually makes the SOAP call)) This is actually more obvious if you had ever used the Composer product from SilverStream that used to be available to build IDM drivers, then this was great, as these function calls could make screen scraper calls, and try to get data off a green screen application or even an HTML web page. Alas that product is deprecated and no more, but this is about the most obvious piece that remains.

Anyway, you can then see in the Output window what you get, which is you intend to read the response out of, you need to use the Data Item Mapping view, to select how the XML from the output document should map back into the flowdata. In other words, the Data Item Mapping here is used to copy flowdata (where data that came in via the request or approval form is stored) into the XML for the SOAP call. That is the Pre Activity mapping. Then there is a Post-Activity mapping option, which is where you would copy data OUT of the XML Output document back into the flowdata. This is basically how most of the items in the PRD Palette work, so it is nice and consistent.

Now on to some errors you might see when working on this.

So I used the soapUI example above, (no need to repeat it here), and it returned the following error:

Returns the error:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>Client</faultcode>
<faultstring>Server Error</faultstring>
<detail>
<ns1:AdminException xmlns="http://www.novell.com/provisioning/service" xmlns:ns1="http://www.novell.com/provisioning/service">
<reason>No such request: [ 30f1e2ae98ce4f91829df1af5bac6cb4]</reason>
</ns1:AdminException>
<stackTrace xsi:type="xsd:string">com.novell.soa.af.impl.soap.AdminException={_Reason=No such request: [ 30f1e2ae98ce4f91829df1af5bac6cb4]}
at com.novell.soa.af.impl.soap.ProvisioningImpl.terminate(ProvisioningImpl.java:992)
at com.novell.soa.af.impl.soap.Provisioning_ServiceSkeleton._invoke(Provisioning_ServiceSkeleton.java:576)
at com.novell.soa.ws.server.ServletSkeleton.invokeEndPoint(ServletSkeleton.java:208)
at com.novell.soa.ws.impl.soap.MessageHandlerInvoker.invokeServerMessageHandlers(MessageHandlerInvoker.java:348)
at com.novell.soa.ws.impl.soap.SOAPHandler.handleServerRequest(SOAPHandler.java:84)
at com.novell.soa.ws.impl.rpc.ServerDelegateImpl.handleServerRequest(ServerDelegateImpl.java:92)
at com.novell.soa.ws.server.ServletSkeleton.handleRequest(ServletSkeleton.java:107)
at com.novell.soa.ws.server.ServletSkeleton.doPost(ServletSkeleton.java:317)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at com.novell.common.auth.JAASFilter.doFilter(JAASFilter.java:108)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at com.novell.common.auth.saml.AuthTokenGeneratorFilter.doFilter(AuthTokenGeneratorFilter.java:90)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at com.novell.common.auth.sso.SSOFilter.doFilter(SSOFilter.java:91)
at com.novell.common.auth.sso.SAPFilter.doFilter(SAPFilter.java:37)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at com.novell.common.auth.sso.SSOFilter.doFilter(SSOFilter.java:91)
at com.novell.common.auth.sso.KerberosFilter.doFilter(KerberosFilter.java:49)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at com.novell.soa.common.i18n.BestLocaleServletFilter.doFilter(BestLocaleServletFilter.java:233)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:235)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:190)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:433)
at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:92)
at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.process(SecurityContextEstablishmentValve.java:126)
at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.invoke(SecurityContextEstablishmentValve.java:70)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:158)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:330)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:829)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:601)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
at java.lang.Thread.run(Thread.java:619)</stackTrace>
</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

That was an easy one. Since I was copying and pasting text in, I had left a leading space in the RequestID. Which you can see both in the Input document and the error messages if you read it carefully. These are some of the harder errors to catch, as depending on the font you are using, and your resolution, a leading or trailing space might be pretty subtle.

However, this really was an easy one, and I quickly hit a success, so I can show what it is SUPPOSED to look like! Which is always good to know, since sometimes you just do not know what success is meant to look like. After all, how can you be sure you got a success if you are not sure what success even looks like? And this specific success message is really not very informative. I found that the Salesforce.com response documents were much more descriptive, as were their error messages, but whatcha gonna do?

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Body>
<ns1:terminateResponse xmlns="http://www.novell.com/provisioning/service" xmlns:ns1="http://www.novell.com/provisioning/service"/>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Now that I understand the SOAP call itself, and know it works, lets see if the PRD we designed, with the SOAP integration activity works, and then lets see what that trace looks like in the server.log from the User Application server. (Assuming you installed with pretty much the defaults, then the path will be something like /opt/novell/idm/jboss/server/IDMProv/logs/server.log if not, the idm and IDMProv parts will likely be the differing part, and I have no idea where the log would be if you used WebLogic or WebSphere as your application server instead of JBoss, read the docs in that case). The server.log file has lots of data, and even more so, the options of what you can trace are pretty much a per class process. That is, almost every Java class used by the User Application seems like it has its own debug code path, and if you enabled everything it would be crazy! You might beed to enable a logging class, since I totally lost track of which I enabled outside the defaults over the course of this work. The good news is that you can see the class you need to enable debugging for in my sample trace, it is visible at the beginning of the line as: com.novell.soa.af.impl and there might be a number of subclasses as well you might need logging as well.

Here is an attempt that failed, lets see how easy it is to figure what is happening, and what went wrong.

2010-08-10 16:59:37,193 INFO  [com.novell.soa.af.impl.LogEvent] (pool-10-thread-3) [Workflow_Started] Initiated by cn=admin,ou=admins,o=ACME,dc=com, Process ID: 4ca1dcb4fa8e4ea79625ee85c4993b8b, Process Name: cn=CancelWorkflow,cn=RequestDefs,cn=AppConfig,cn=User Application,cn=IDM,ou=Drivers,o=ACME,dc=com:17, Activity: start, Recipient: cn=admin,ou=admins,o=ACME,dc=com, Secondary User: null

There is the start workflow event.

2010-08-10 16:59:37,215 INFO  [com.novell.soa.af.impl.LogEvent] (pool-10-thread-3) [Workflow_Forwarded] Initiated by System, Process ID: 4ca1dcb4fa8e4ea79625ee85c4993b8b, Process Name: cn=CancelWorkflow,cn=RequestDefs,cn=AppConfig,cn=User Application,cn=IDM,ou=Drivers,o=ACME,dc=com:17, Activity: start, Recipient: cn=admin,ou=admins,o=ACME,dc=com

This is getting through the Start activity.

2010-08-10 16:59:39,533 INFO  [STDOUT] (pool-10-thread-3) DEBUG [com.novell.soa.ws.impl.xml.OutputStreamImpl:getBytes] <SOAP-ENV:Envelope xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'><SOAP-ENV:Body><terminateRequest xmlns="http://www.novell.com/provisioning/service" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><arg0>f1403236ba01400b974021294c41ed5a</arg0><TerminationType xmlns:tns="http://www.novell.com/provisioning/service">RETRACT</TerminationType></terminateRequest></SOAP-ENV:Body></SOAP-ENV:Envelope>
2010-08-10 16:59:39,532 DEBUG [com.novell.soa.ws.impl.xml.OutputStreamImpl] (pool-10-thread-3) <SOAP-ENV:Envelope xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'><SOAP-ENV:Body><terminateRequest xmlns="http://www.novell.com/provisioning/service" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><arg0>f1403236ba01400b974021294c41ed5a</arg0><TerminationType xmlns:tns="http://www.novell.com/provisioning/service">RETRACT</TerminationType></terminateRequest></SOAP-ENV:Body></SOAP-ENV:Envelope>

Here is the SOAP integration activity actual SOAP call.

2010-08-10 16:59:39,631 INFO  [org.apache.commons.httpclient.auth.AuthChallengeProcessor] (pool-10-thread-3) basic authentication scheme selected

Tries to login in with basic authentication on the HTTP connection to the endpoint specified.

2010-08-10 16:59:39,633 INFO  [org.apache.commons.httpclient.HttpMethodDirector] (pool-10-thread-3) No credentials available for BASIC 'MetricsWebService'@10.255.4.62:8080

Oops. forgot to set the credentails. That won't work.

2010-08-10 16:59:39,654 INFO  [STDOUT] (pool-10-thread-3)        Tue Aug 10 16:59:39 EDT 2010 USER LOG FROM IDVAULT-LAB_User Application_C. Cancel a running workflow_Activity
------ com.sssw.b2b.rt.GNVException: rt007005:Error encountered executing WSDL Action:;
---> nested java.rmi.RemoteException: HTTP 401 Unauthorized2010-08-10 16:59:39,658 ERROR [com.novell.soa.af.impl.LogEvent] (pool-10-thread-3) [Workflow_Error] Initiated by cn=admin,ou=admins,o=ACME,dc=com, Error Message: Process requestId [4ca1dcb4fa8e4ea79625ee85c4993b8b], Id [cn=CancelWorkflow,cn=RequestDefs,cn=AppConfig,cn=User Application,cn=IDM,ou=Drivers,o=ACME,dc=com], Integration activity [Activity]: faulted [<?xml version="1.0" encoding="UTF-8"?>
<m:FaultInfo xmlns:m="http://novell/extendComposer/SystemFault">
<m:DateTime>Tue Aug 10 16:59:39 EDT 2010</m:DateTime>
<m:ComponentName>IDVAULT-LAB_User Application_C. Cancel a running workflow_Activity</m:ComponentName>
<m:MainCode>-1</m:MainCode>
<m:SubCode>1</m:SubCode>
<m:Message>com.sssw.b2b.rt.GNVException: rt007005:Error encountered executing WSDL Action:;
---> nested java.rmi.RemoteException: HTTP 401 Unauthorized</m:Message>
</m:FaultInfo>]., Process ID: 4ca1dcb4fa8e4ea79625ee85c4993b8b, Process Name: cn=CancelWorkflow,cn=RequestDefs,cn=AppConfig,cn=User Application,cn=IDM,ou=Drivers,o=ACME,dc=com:17, Activity: Activity, Recipient: cn=admin,ou=admins,o=ACME,dc=com

There you go, no credentails, so you get a 401 Unathorized error.

As you can see, reading the log is a pain, as it is a LOT of text, but the data you need is in there. (Sort of describes much of the Provisioning side... There is a lot of information which makes it hard to use, but is actually useful to have all that detail.).

Hope this helps walk you through what is needed to do this sort of thing. We can easily extend this to handle Approving a running workflow in much the same way, though it requires you to understand that the WSDL requires a call to getWorkEntries to get the process ID from the requestID and then you can call the forwardRequest with that data).

Tags:

Labels:

How To-Best Practice
Comment List
Anonymous
Related Discussions
Recommended