Getting Started Building a SOAP Driver for IDM - Part 6

1 Likes
Starting A SOAP Driver for IDM Part 6:

Novell Identity Manager comes with a bunch of prebuilt and out of the box drivers that mostly do what is needed for most cases. However some drivers allow for so much flexibility that no out of the box configuration will ever be complete. The JDBC driver, which can connect to many different databases comes with some of the big ones (Oracle, Microsoft SQL Server) configured, but the rest are sort of up to you. Mostly because almost everyone uses databases differently. Novell has talked about a fan out configuration for the JDBC driver that will come out after the formal release of Identity Manager 4.0. That will probably manage the case of out of the box database models, like using Oracle with 'Oracle Users' in which case, you can imagine a model fairly easily where the driver could be set up to push users into many dozens of such Oracle databases that all use the same basic model of users, really differing only in host information (DB server, port, database name, etc) and using entitlements to specify which database the user gets access too.



But the SOAP driver is even harder to provide useful default configurations. Well it ships with a DSML and SPML 1.0 configuration, since those are the only really mature standards for SOAP operations involving users. But SOAP is basically as open ended as you want it to be, and everyone does whatever they want.



For example, the User Application is more about Provisioning than user events directly, but if you really wanted too, you could use the SOAP driver to talk to the User Application. (Actually, it is almost as much fun to use a Workflow, to use a SOAP integration activity, to talk to User Application to do stuff.)



With that said, there are some big targets for SOAP that you could develop configurations for. I started this series to try and provide notions on how you might do that, using Salesforce.com as the target, since it is a pretty big target to aim at. However the concepts involved would be much the same that would be used to try and target any other SOAP system. Heck I used the ideas I developed here in my SOAP integration activity in a Workflow to call User Application web services.



In part 1 of this series, Getting Started Building a SOAP Driver for IDM - Part 1 I discussed some of the things you need to get started building a SOAP driver. I was using the example of Salesforce.com (henceforth known as SFDC, since typing the full name is too much of a pain each time). In Part 1 I focused on how you might start connecting via SOAP to get a session ID.



In Part 2 of this series Getting Started Building a SOAP Driver for IDM - Part 2 I discussed how you might process the results from SFDC after you submit a login request, and converting it into an <instance> document.



In Part 3 of this series Getting Started Building a SOAP Driver for IDM - Part 3 I discussed how you might handle query events and their responses.



In Part 4 of this series Getting Started Building a SOAP Driver for IDM - Part 4 I talked about some of the background stuff you need to manage, like attribute syntaxes, and left hanging two more concepts. Subscriber channel write events, like <add> or <modify> events, that need to be sent to SFDC, and the ability to get events onto the Publisher channel.



In Part 5 of this series Getting Started Building a SOAP Driver for IDM - Part 5 I started talking about how you would map add and modify events from Identity Manager into SFDC events. This would allow you to write changes (modify events) back to SFDC, or add new users to SFDC.

I started talking about how you would handle modify events, and left add events as an exercise. However I did not finish the modify discussion. I showed some sample code, to manage it, but I would like to discuss the actual process that the code sample uses.



As always, I find it much easier to work in these cases if I have a sample input doc, a sample output doc as a goal, and then just need to get from here to there (funny things are everywhere... (Thanks Dr. Suess, Red Fish, Blue Fish book... My three year old loves that book!)). Also, this allows you to build the rule in Designer, and when it is time to test, paste the sample input doc into the Simulator view, and let it run your rule, and you can then compare with your target output document and see if you got it right.



This approach is so much faster than the older model of make a change in Designer and push it to the directory, restart the driver, generate a test event, watch trace. see a problem, fix it, rinse and repeat. Doing it with Simulator is so much faster and better. However, if you have external queries in your rule it is sort of hard to do, so I often disable rules that need that external query, and put in fake data for developing and testing instead.



So here is the sample input doc I am working with:



<?xml version="1.0" encoding="UTF-8"?><nds dtdversion="3.5" ndsversion="8.x">
<source>
<product version="?.?.?.?">DirXML</product>
<contact>Novell, Inc.</contact>
</source>
<input>
<modify cached-time="20100426155034.125Z" class-name="Organizational Unit" event-id="nycaidv01m-ebsd#20100426155034#1#1" qualified-src-dn="dc=com\O=ACME\OU=Service" src-dn="\IDVAULT-LAB\com\ACME\Service" src-entry-id="32946" timestamp="1272297034#1">
<association>IDstringGoesHere</association>
<modify-attr attr-name="L">
<add-value>
<value timestamp="1272297034#1" type="string">46L</value>
</add-value>
</modify-attr>
<modify-attr attr-name="M">
<remove-value>
<value timestamp="1272297034#1" type="string">46M</value>
</remove-value>
</modify-attr>
<modify-attr attr-name="N">
<remove-all-values/>
<add-value>
<value timestamp="1272297034#1" type="string">46N</value>
</add-value>
</modify-attr>
</modify>
</input>
</nds>



Next we need to know what that should look like in the output. So here is a sample doc for the SFDC SOAP document:



<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:enterprise.soap.sforce.com" xmlns:urn1="urn:sobject.enterprise.soap.sforce.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header>
<urn:SessionHeader>
<urn:sessionId>QwWsHJyTPW.1pd0_jXlNKOSU</urn:sessionId>
</urn:SessionHeader>
</soapenv:Header>
<soapenv:Body>
<urn:upsert>
<urn:externalIDFieldName>Id</urn:externalIDFieldName>
<urn:sObjects xsi:type="Account">
<Id>IDStringGoesHere</Id>
<L>46L</L>
<N>46N</N>
<fieldsToNull>M</fieldsToNull>
</urn:sObjects>
</urn:upsert>
</soapenv:Body>
</soapenv:Envelope>



Now that we have our sample event documents, lets start picking apart the rule I use to get from one to the other. First off here is total rule, then I will pick it apart item by item.



<rule>
<description>[acme] Convert Modify to upsert() SOAP doc</description>
<comment name="author" xml:space="preserve">Geoffrey Carman</comment>
<comment name="version" xml:space="preserve">5</comment>
<comment name="lastchanged" xml:space="preserve">Aug 6, 2010</comment>
<conditions>
<and>
<if-operation mode="case" op="equal">modify</if-operation>
</and>
</conditions>
<actions>
<do-append-xml-element expression=".." name="soapenv:Envelope"/>
<do-append-xml-element expression="../soapenv:Envelope" name="soapenv:Header"/>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Header" name="urn:SessionHeader"/>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Header/urn:SessionHeader" name="urn:sessionId"/>
<do-append-xml-text expression="../soapenv:Envelope/soapenv:Header/urn:SessionHeader/urn:sessionId">
<arg-string>
<token-local-variable name="SessionID"/>
</arg-string>
</do-append-xml-text>
<do-append-xml-element expression="../soapenv:Envelope" name="soapenv:Body"/>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body" name="urn:upsert"/>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body/urn:upsert" name="urn:externalIDFieldName"/>
<do-append-xml-text expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:externalIDFieldName">
<arg-string>
<token-text xml:space="preserve">Id</token-text>
</arg-string>
</do-append-xml-text>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body/urn:upsert" name="urn:sObjects"/>
<do-if>
<arg-conditions>
<and>
<if-xpath op="true">@class-name</if-xpath>
</and>
</arg-conditions>
<arg-actions>
<do-set-local-variable name="CLASS-NAME" scope="policy">
<arg-string>
<token-xpath expression="@class-name"/>
</arg-string>
</do-set-local-variable>
</arg-actions>
<arg-actions/>
</do-if>
<do-set-xml-attr expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects" name="xsi:type">
<arg-string>
<token-local-variable name="CLASS-NAME"/>
</arg-string>
</do-set-xml-attr>
<do-if>
<arg-conditions>
<and>
<if-xpath op="true">@dest-dn</if-xpath>
</and>
</arg-conditions>
<arg-actions>
<do-set-local-variable name="DEST-ID" scope="policy">
<arg-string>
<token-xpath expression="@dest-dn"/>
</arg-string>
</do-set-local-variable>
</arg-actions>
<arg-actions>
<do-if>
<arg-conditions>
<and>
<if-association op="available"/>
</and>
</arg-conditions>
<arg-actions>
<do-set-local-variable name="DEST-ID" scope="policy">
<arg-string>
<token-association/>
</arg-string>
</do-set-local-variable>
</arg-actions>
<arg-actions>
<do-if>
<arg-conditions>
<and>
<if-src-attr name="acmeId" op="available"/>
</and>
</arg-conditions>
<arg-actions>
<do-set-local-variable name="DEST-ID" scope="policy">
<arg-string>
<token-src-attr name="acmeId"/>
</arg-string>
</do-set-local-variable>
</arg-actions>
<arg-actions>
<do-status level="warning">
<arg-string>
<token-text xml:space="preserve">No acmeId available, and cannot process the change.</token-text>
</arg-string>
</do-status>
<do-veto/>
</arg-actions>
</do-if>
</arg-actions>
</do-if>
</arg-actions>
</do-if>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects" name="Id"/>
<do-append-xml-text expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects/Id">
<arg-string>
<token-local-variable name="DEST-ID"/>
</arg-string>
</do-append-xml-text>
<do-for-each>
<arg-node-set>
<token-xpath expression="modify-attr"/>
</arg-node-set>
<arg-actions>
<do-set-local-variable name="ATTR-NAME" scope="policy">
<arg-string>
<token-xpath expression="$current-node/@attr-name"/>
</arg-string>
</do-set-local-variable>
<do-if>
<arg-conditions>
<or>
<if-xpath op="true">$current-node/@attr-name=$SCHEMA-MAP/attr-name-map/attr-name[@class-name=$EDIR-CLASS]/app-name</if-xpath>
<if-xpath op="true">$current-node/@attr-name=$SCHEMA-MAP/attr-name-map/attr-name[not(@class-name)]/app-name</if-xpath>
</or>
</arg-conditions>
<arg-actions>
<do-if>
<arg-conditions>
<or>
<if-xpath op="true">$current-node/remove-value</if-xpath>
<if-xpath op="true">$current-node/remove-all-values</if-xpath>
</or>
</arg-conditions>
<arg-actions>
<do-if>
<arg-conditions>
<and>
<if-xpath op="true">$current-node/add-value</if-xpath>
</and>
</arg-conditions>
<arg-actions>
<do-set-local-variable name="VALUE" scope="policy">
<arg-string>
<token-xpath expression="$current-node/add-value"/>
</arg-string>
</do-set-local-variable>
<do-if>
<arg-conditions>
<and>
<if-local-variable mode="regex" name="VALUE" op="equal">. </if-local-variable>
</and>
</arg-conditions>
<arg-actions>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects" name="$ATTR-NAME$"/>
<do-append-xml-text expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects/*[last()]">
<arg-string>
<token-xpath expression="$current-node/add-value/value"/>
</arg-string>
</do-append-xml-text>
</arg-actions>
<arg-actions>
</arg-actions>
</do-if>
</arg-actions>
<arg-actions>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects" name="urn1:fieldsToNull"/>
<do-append-xml-text expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects/urn1:fieldsToNull">
<arg-string>
<token-local-variable name="ATTR-NAME"/>
</arg-string>
</do-append-xml-text>
</arg-actions>
</do-if>
</arg-actions>
<arg-actions>
<do-set-local-variable name="VALUE" scope="policy">
<arg-string>
<token-xpath expression="$current-node/add-value"/>
</arg-string>
</do-set-local-variable>
<do-if>
<arg-conditions>
<and>
<if-local-variable mode="regex" name="VALUE" op="equal">. </if-local-variable>
</and>
</arg-conditions>
<arg-actions>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects" name="$ATTR-NAME$"/>
<do-append-xml-text expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects/*[last()]">
<arg-string>
<token-xpath expression="$current-node/add-value/value"/>
</arg-string>
</do-append-xml-text>
</arg-actions>
<arg-actions>
</arg-actions>
</do-if>
</arg-actions>
</do-if>
</arg-actions>
<arg-actions/>
</do-if>
</arg-actions>
</do-for-each>
<do-if>
<arg-conditions>
<and>
<if-xpath op="not-true">count(../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects/*) > 1</if-xpath>
</and>
</arg-conditions>
<arg-actions>
<do-strip-xpath expression="self::soapenv:Envelope"/>
<do-veto/>
</arg-actions>
<arg-actions/>
</do-if>
<do-strip-xpath expression="self::modify"/>
</actions>
</rule>



First we need to build some of the basic document structure. This code manages most of it:



<do-append-xml-element expression=".." name="soapenv:Envelope"/>
<do-append-xml-element expression="../soapenv:Envelope" name="soapenv:Header"/>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Header" name="urn:SessionHeader"/>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Header/urn:SessionHeader" name="urn:sessionId"/>
<do-append-xml-text expression="../soapenv:Envelope/soapenv:Header/urn:SessionHeader/urn:sessionId">
<arg-string>
<token-local-variable name="SessionID"/>
</arg-string>
</do-append-xml-text>
<do-append-xml-element expression="../soapenv:Envelope" name="soapenv:Body"/>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body" name="urn:upsert"/>


This builds us the following chunk of the document:



<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:enterprise.soap.sforce.com" xmlns:urn1="urn:sobject.enterprise.soap.sforce.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header>
<urn:SessionHeader>
<urn:sessionId>QwWsHJyTPW.1pd0_jXlNKOSU</urn:sessionId>
</urn:SessionHeader>
</soapenv:Header>
<soapenv:Body>
<urn:upsert>
</urn:upsert>
</soapenv:Body>
</soapenv:Envelope>



Up till now this is basically the same as the previous rules we have worked on in this series. Nothing magical. Just build your nodeset bit by bit. Add nodes (append XML element), add some XML attributes into those nodes if needed (set xml attribute), and some value text (append XML text). In this case SessionId is a driver scoped local variable that we talked about in Part 2 of this series when we discussed the Login process. And should just be floating around defined. By the time this rule gets called, a number of other places would have been sure to have set the value. If not, the event fails.



Next up is externalIDFieldName. This is where you tell SFDC, how to find your user. In the upsert version of an add event I am not sure exactly how that is supposed to be sent, since we do not know the Id yet, but I imagine we would specify the Id as the external ID field name and then it would return the Id in the results.



<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body/urn:upsert" name="urn:externalIDFieldName"/>
<do-append-xml-text expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:externalIDFieldName">
<arg-string>
<token-text xml:space="preserve">Id</token-text>
</arg-string>
</do-append-xml-text>



So we add the node, (<urn:externalIDFieldName>) and then we set Id as the text in the node. Then we add an <urn:sObjects> node with this token.



<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body/urn:upsert" name="urn:sObjects"/>
<do-if>
<arg-conditions>
<and>
<if-xpath op="true">@class-name</if-xpath>
</and>
</arg-conditions>
<arg-actions>
<do-set-local-variable name="CLASS-NAME" scope="policy">
<arg-string>
<token-xpath expression="@class-name"/>
</arg-string>
</do-set-local-variable>
</arg-actions>
<arg-actions/>
</do-if>
<do-set-xml-attr expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects" name="xsi:type">
<arg-string>
<token-local-variable name="CLASS-NAME"/>
</arg-string>
</do-set-xml-attr>



To correctly specify which object class it is, we first look for an @class-name XML attribute in the input document. Normally that is there, however, the various Add and Set Attribute (Source and Destination) tokens do not require you to specify an Object Class. They allow you too, but do not require it. Initially when I implemented this I just said, you will be required. Later I realized that I kept forgetting to do it, and sometimes the engine sends merge updates without an object class, so it was better to just infer the class.



That is a snippet of clever code I kept out of this example. :) But it is not hard. You have an association value, look back at the source object to figure out its class. Also I added a test to be sure we did not somehow get the class in eDirectory name space, in which case we convert it back to SFDC naming.



At this point we should have an output document that looks mostly like:



<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:enterprise.soap.sforce.com" xmlns:urn1="urn:sobject.enterprise.soap.sforce.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header>
<urn:SessionHeader>
<urn:sessionId>QwWsHJyTPW.1pd0_jXlNKOSU</urn:sessionId>
</urn:SessionHeader>
</soapenv:Header>
<soapenv:Body>
<urn:upsert>
<urn:externalIDFieldName>Id</urn:externalIDFieldName>
<urn:sObjects xsi:type="Account">
</urn:sObjects>
</urn:upsert>
</soapenv:Body>
</soapenv:Envelope>




Next up that is critical is getting a destination value on a modify. If this is not an associated object, we need a way to specify the destination object.



<do-if>
<arg-conditions>
<and>
<if-xpath op="true">@dest-dn</if-xpath>
</and>
</arg-conditions>
<arg-actions>
<do-set-local-variable name="DEST-ID" scope="policy">
<arg-string>
<token-xpath expression="@dest-dn"/>
</arg-string>
</do-set-local-variable>
</arg-actions>
<arg-actions>
<do-if>
<arg-conditions>
<and>
<if-association op="available"/>
</and>
</arg-conditions>
<arg-actions>
<do-set-local-variable name="DEST-ID" scope="policy">
<arg-string>
<token-association/>
</arg-string>
</do-set-local-variable>
</arg-actions>
<arg-actions>
<do-if>
<arg-conditions>
<and>
<if-src-attr name="acmeId" op="available"/>
</and>
</arg-conditions>
<arg-actions>
<do-set-local-variable name="DEST-ID" scope="policy">
<arg-string>
<token-src-attr name="acmeId"/>
</arg-string>
</do-set-local-variable>
</arg-actions>
<arg-actions>
<do-status level="warning">
<arg-string>
<token-text xml:space="preserve">No acmeId available, and cannot process the change.</token-text>
</arg-string>
</do-status>
<do-veto/>
</arg-actions>
</do-if>
</arg-actions>
</do-if>
/arg-actions>
</do-if>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects" name="Id"/>
<do-append-xml-text expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects/Id">
<arg-string>
<token-local-variable name="DEST-ID"/>
</arg-string>
</do-append-xml-text>



I have been storing my database ID from SFDC in an attribute called acmeId, you of course can call it whatever you like. This is in the schema map of course. So first we look for a dest-dn in the event document, and if not, look for an association value, and try to use that. If not we try and read back to the source attribute acmeId from the current object.



Finally if all else fails, we send back a Status level of warning, since this just isn't going to work and veto the modify event.



Next question is, how do we add all the nodes for attributes, with the nodes named for the attribute. Unlike IDM's XDS format where there is a @attr-name XML attribute in each node when needed. SFDC instead uses the name of the attribute as the name of the node. For example, the attribute Last_Login__c in SFDC would be added as a node that might look like:



	<Last_Login__c>Some Time stamp</Last_Login__c>


So here is the basic rule that does all the work. Let me show it one pastable chunk, and then I will work through it element by element.



<do-for-each>
<arg-node-set>
<token-xpath expression="modify-attr"/>
</arg-node-set>
<arg-actions>
<do-set-local-variable name="ATTR-NAME" scope="policy">
<arg-string>
<token-xpath expression="$current-node/@attr-name"/>
</arg-string>
</do-set-local-variable>
<do-if>
<arg-conditions>
<or>
<if-xpath op="true">$current-node/@attr-name=$SCHEMA-MAP/attr-name-map/attr-name[@class-name=$EDIR-CLASS]/app-name</if-xpath>
<if-xpath op="true">$current-node/@attr-name=$SCHEMA-MAP/attr-name-map/attr-name[not(@class-name)]/app-name</if-xpath>
</or>
</arg-conditions>
<arg-actions>
<do-if>
<arg-conditions>
<or>
<if-xpath op="true">$current-node/remove-value</if-xpath>
<if-xpath op="true">$current-node/remove-all-values</if-xpath>
</or>
</arg-conditions>
<arg-actions>
<do-if>
<arg-conditions>
<and>
<if-xpath op="true">$current-node/add-value</if-xpath>
</and>
</arg-conditions>
<arg-actions>
<do-set-local-variable name="VALUE" scope="policy">
<arg-string>
<token-xpath expression="$current-node/add-value"/>
</arg-string>
</do-set-local-variable>
<do-if>
<arg-conditions>
<and>
<if-local-variable mode="regex" name="VALUE" op="equal">. </if-local-variable>
</and>
</arg-conditions>
<arg-actions>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects" name="$ATTR-NAME$"/>
<do-append-xml-text expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects/*[last()]">
<arg-string>
<token-xpath expression="$current-node/add-value/value"/>
</arg-string>
</do-append-xml-text>
</arg-actions>
<arg-actions>
</arg-actions>
</do-if>
</arg-actions>
<arg-actions>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects" name="urn1:fieldsToNull"/>
<do-append-xml-text expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects/urn1:fieldsToNull">
<arg-string>
<token-local-variable name="ATTR-NAME"/>
</arg-string>
</do-append-xml-text>
</arg-actions>
</do-if>
</arg-actions>
<arg-actions>
<do-set-local-variable name="VALUE" scope="policy">
<arg-string>
<token-xpath expression="$current-node/add-value"/>
</arg-string>
</do-set-local-variable>
<do-if>
<arg-conditions>
<and>
<if-local-variable mode="regex" name="VALUE" op="equal">. </if-local-variable>
</and>
</arg-conditions>
<arg-actions>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects" name="$ATTR-NAME$"/>
<do-append-xml-text expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects/*[last()]">
<arg-string>
<token-xpath expression="$current-node/add-value/value"/>
</arg-string>
</do-append-xml-text>
</arg-actions>
<arg-actions>
</arg-actions>
</do-if>
</arg-actions>
</do-if>
</arg-actions>
<arg-actions/>
</do-if>
</arg-actions>
</do-for-each>




We loop through the modify-attr nodeset, which is nice and easy. This would have to be handled different for <add> events, instead of <modify> events.



<do-set-local-variable name="ATTR-NAME" scope="policy">
<arg-string>
<token-xpath expression="$current-node/@attr-name"/>
</arg-string>
</do-set-local-variable>



We need the ATTR-NAME, and cannot be sure we will not overwrite current-node by being inside another loop, so store it now.



<do-if>
<arg-conditions>
<or>
<if-xpath op="true">$current-node/@attr-name=$SCHEMA-MAP/attr-name-map/attr-name[@class-name=$EDIR-CLASS]/app-name</if-xpath>
<if-xpath op="true">$current-node/@attr-name=$SCHEMA-MAP/attr-name-map/attr-name[not(@class-name)]/app-name</if-xpath>
</or>



Now lets make sure the attribute is in the Schema Map, if it is not, we need to drop it, as it will just cause an error with SFDC. The XPATH that does this is nicely explained in the article I wrote on the topic: XPATH to do schema mapping rule.



Next we get into a nested set of IF-THEN statements to handle the various possible cases of how we get a modify-attr event.



<do-if>
<arg-conditions>
<or>
<if-xpath op="true">$current-node/remove-value</if-xpath>
<if-xpath op="true">$current-node/remove-all-values</if-xpath>
</or>
</arg-conditions>
<arg-actions>
<do-if>
<arg-conditions>
<and>
<if-xpath op="true">$current-node/add-value</if-xpath>
</and>
</arg-conditions>
<arg-actions>
<do-set-local-variable name="VALUE" scope="policy">
<arg-string>
<token-xpath expression="$current-node/add-value"/>
</arg-string>
</do-set-local-variable>
<do-if>
<arg-conditions>
<and>
<if-local-variable mode="regex" name="VALUE" op="equal">. </if-local-variable>
</and>
</arg-conditions>
<arg-actions>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects" name="$ATTR-NAME$"/>
<do-append-xml-text expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects/*[last()]">
<arg-string>
<token-xpath expression="$current-node/add-value/value"/>
</arg-string>
</do-append-xml-text>
</arg-actions>
<arg-actions>
</arg-actions>
</do-if>
</arg-actions>
<arg-actions>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects" name="urn1:fieldsToNull"/>
<do-append-xml-text expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects/urn1:fieldsToNull">
<arg-string>
<token-local-variable name="ATTR-NAME"/>
</arg-string>
</do-append-xml-text>
</arg-actions>
</do-if>
</arg-actions>
<arg-actions>
<do-set-local-variable name="VALUE" scope="policy">
<arg-string>
<token-xpath expression="$current-node/add-value"/>
</arg-string>
</do-set-local-variable>
<do-if>
<arg-conditions>
<and>
<if-local-variable mode="regex" name="VALUE" op="equal">. </if-local-variable>
</and>
</arg-conditions>
<arg-actions>
<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects" name="$ATTR-NAME$"/>
<do-append-xml-text expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects/*[last()]">
<arg-string>
<token-xpath expression="$current-node/add-value/value"/>
</arg-string>
</do-append-xml-text>
</arg-actions>
<arg-actions>
</arg-actions>
</do-if>
</arg-actions>
</do-if>



First we need to handle the possible cases for an <remove-value> or <remove-all-values> event. First lets check if there is an <add-value> in there.



<if-xpath op="true">$current-node/add-value</if-xpath>


Now this could be a <remove-value> and <add-value> or a <remove-all-values> and <add-value> event or it could be an empty <add-value> which is annoying and needs to be dropped as well. Thus we test with:



<if-local-variable mode="regex" name="VALUE" op="equal">. </if-local-variable>


This makes sure we have some data to set, avoiding a possible error.



If we have a value to add, lets add it in:



<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects" name="$ATTR-NAME$"/>


In this first token, we create the <$ATTR-NAME$> node (Based on the variable).



<do-append-xml-text expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects/*[last()]">
<arg-string>
<token-xpath expression="$current-node/add-value/value"/>
</arg-string>
</do-append-xml-text>



Now to append to it a value, we XPATH the value, with $current-node/add-value/value, and then add the text to the ../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects/*[last()] node. Now we probably could have used the fact we know the ATTR-NAME and done something like:



../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects/$ATTR-NAME


and that ought to work fine.



Now if we have <modify-attr> but no <add-value> node, then we must have a <remove-value> node, which we handle with these tokens:



<do-append-xml-element expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects" name="urn1:fieldsToNull"/>
<do-append-xml-text expression="../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects/urn1:fieldsToNull">
<arg-string>
<token-local-variable name="ATTR-NAME"/>
</arg-string>
</do-append-xml-text>


What we need to do is create a node called <fieldsToNull> and the text should be the attribute name that we wish to Null.



This works because there are no multi valued attributes in SFDC. Therefore, as long as we know there was a removal node, and an add node, then we want to add something, and if there was a removal node, but no add, then we need to Null the field.



Next lets handle the case of just an <add-value> node, and then we just repeat the code from above.



At the end of this we have basically built up our document and are ready to send it on to SFDC. You can see that resulting document at the beginning of this article. We have reached our goal.



However, lets add some sanity checking to be sure we actually have any data, since we could have gotten a single <add-value> node with no text contents, in which case we would have stripped it out, and have an empty modify.



<do-if>
<arg-conditions>
<and>
<if-xpath op="not-true">count(../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects/*) > 1</if-xpath>
</and>
</arg-conditions>
<arg-actions>
<do-strip-xpath expression="self::soapenv:Envelope"/>
<do-veto/>
</arg-actions>
<arg-actions/>
</do-if>
<do-strip-xpath expression="self::modify"/>



Use the count() function in XPATH to make sure we actually have a single or more values to change:



count(../soapenv:Envelope/soapenv:Body/urn:upsert/urn:sObjects/*)


If not, veto this event.



Finally, strip off the originating <modify> event so that we have just a bare SOAP document to send on.



There you have it. A mostly working example.


Comment List
Related
Recommended