Salesforce Apex to VersionOne API

Recently I started doing some work for a client for some integration between Salesforce and VerisonOne.  When I first looked at how they were doing it, it was pretty clear there were some serious struggles which I kind of take personally (I was on the dev team for quite a while).

With the imposed limits of Apex, reusability is a must and for this case specifically, we were doing a lot of writes (POST).  Since Apex now has an XML object that helps with creating those, I came up with a set of objects that handle creating the proper payload for posts to the VersionOne API.

First, I started off with creating a simple interface -- V1ApiXmlSetterNode that demands AddSetterAttributeNode and accepts a Dom.XmlNode.  As of this writing, that's how VersionOne's API takes new content via a post to the rest-1.v1, for instance /rest-1.v1/Data/Story with an XML payload (query.v1 is in the works, so I'm told -- so stay tuned).

From there, a bit of code to handle the basic set action

public class V1AttributeSetNode implements V1ApiXmlSetterNode {
public V1AttributeSetNode(String attributeName, String value)
{
this.AttributeName = attributeName;
this.Value = value;
}
public String AttributeName {get; private set;}
public String Value {get; private set;}
public void AddSetterAttributeNode(Dom.XmlNode assetRoot)
{
if (this.Value == null)
return;
Dom.Xmlnode node = assetRoot.addChildElement('Attribute', null, null);
node.setAttribute('act', 'set');
node.setAttribute('name', AttributeName);
node.addTextNode(Value);
}
 
}

One thing worth noting here is if there's a null value, it's skipped. I did this just to keep junk across the wire and if I remember right, VersionOne cares about null vs empty string. For a multi-relation, such as tasks on a story, and there's nothing to relate, it also skips it -- simply ...

if (this.Value.size() == 0)
return;

It's also done for the relational attributes because of validation problems which could happen. On the lookup, I added in two other checks to determine if there's anything to lookup and if the key contains the value it's expected to set.

if (this.Value == null || lookup.size() == 0)
return;
if (!Lookup.containsKey(Value)) // possible error condition?
return;

I feel like if the key is missing, this is probably some kind of error condition and may need a notification sent. It's also possible to insert that value into VersionOne directly but I found that to be quite dangerous and could end up dumping a bunch of values into a system that really should never be there, so I highly recommend against it.

Inside of a Story for instance, the wiring between these two things look something like this...

public class V1Story {
	
  public V1Story()
  {
    FillMappings();	
  }
	
  private Map<String, String> PriorityLookup;
  private void FillMappings()
  {
    PriorityLookup = new Map<String, String>(); //should this be file-driven
    PriorityLookup.put('High', 'StoryPriority:200');
    PriorityLookup.put('Medium', 'StoryPriority:201');
    PriorityLookup.put('Low', 'StoryPriority:202');
  }

  public String Title {get; set;}
  public String Description {get; set;}  
  public String Scope {get; set;}
  public String Category {get; set;}
  public String Priority {get;set;}
  
	
  public List<V1ApiXmlSetterNode> XmlNodes()
  {
    List<V1ApiXmlSetterNode> nodes = new List<V1ApiXmlSetterNode>();
		
    nodes.add(new V1AttributeSetNode('Name', Title));
    nodes.add(new V1AttributeSetNode('Description', Description));
    nodes.add(new V1AttributeSetRelationNode('Scope', Scope));
    nodes.add(new V1AttributeSetRelationNode('Category', StoryTypeCategoryID));
    nodes.add(new V1AttributeSetRelationLookupNode('Priority', Priority, PriorityLookup));
    nodes.add(new V1AttributeSetNode('Custom_MyCustomField', customField));
		
    return nodes;		
  }
    
}

The method XmlNodes builds up the necessary types for consuming later within the post back to VersionOne. I also put in a custom field example because those are used quite widely across our customers. This one in particular used 7 or so but others will vary.

So what does it look like when the HTTPRequest is ready to go? This is a bit of sudo code, but could easily be adapted to an existing request

public class V1Api {
 
public void SendStory(V1Story story)
{
Dom.Document doc = new Dom.Document();
dom.XmlNode root = doc.createRootElement('Asset', null, null);
for (V1ApiXmlSetterNode node: story.XmlNodes())
{
node.AddSetterAttributeNode(root);
}
 
//build up HTTPRequest
String body = doc.toXmlString();
request.setHeader('Content-Length', String.valueOf(body.length()));
request.setBody(doc);
//send request
}
}

The key on this the root node needs to be created and used as the container. Otherwise its really straight forward.

Next steps would be to create a simple process to build the classes up instead of having to manually write them up by using the meta.v1 and using that to parse out the files, which time permitting, I think its something worth doing and could help others make their integration a lot easier.

I've also created a gist that I will expand on from time to time and you can find that here. https://gist.github.com/jeriley/11230796

Comments are closed