Being a mathematician, equations like those in the title usually make me wanna poke someone in the eye.. but in this case it’s just too true to not use. My aim over the next two posts is to provide you with the knowledge to,
- Consume web services from within the Force.com platform(without WSDLs).
- Use web services(we’ll be using the SOAP-based variety), or other agents, as data sources for integration with JavaScript, and in particular, jQuery.
I will cover point 1 in this post, and then bring it all together in a second post which will cover point 2. It’s gonna be a rocky ride, but I’m sure all you cowboys/girls/others can handle it. Just so we know where we want to end up, our final product will look something like this,
So what exactly are we looking at here? Well our aim is to build a gallery viewer using images sourced from Flickr. We will be using their webservices API, and one of my favourite jQuery gallery viewer libraries. I’ve used something similar to build the image carousel in my Force.com Sites Developer Challenge entry, The Cookbook.
Before we start let’s make sure we know the basics i.e.
- What the heck is Flickr? Answer.
- What are web services? Another Answer.
Now that we have an exhaustive knowledge on each of those topics we’ll need to signup for a Flickr account(you can login if you already have a Yahoo! account), and you’ll need to obtain an API key.
You’ll see on the page that details the Flickr API that there are a number of libraries for the common languages e.g. C# and Java, although there isn’t any for Apex. Noticing that, I thought I should write a very basic Apex library wrapping a few of the web services calls to that API, and open that up to the community. For the large part I’ve tried to keep things simple, modular and as OOP as possible, but I’d be glad to hear your opinions.
Since Apex lacks the ability to put code into Java like packages, I’ve included all Flickr-related code into a class creatively named ‘Flickr’
[sourcecode language=”java”]
public with sharing class Flickr {
private final String USER_ID = ‘Your_flickr_userid_here’;
[/sourcecode]
The USER_ID constant can be used to simplify some of the method calls and I would suggest setting a value whilst you’re learning your way around the code. If you’re logged in you can find out your user Id here.
Next I’ve defined a class called Packet. This class is responsible for building any SOAP request ‘envelopes’,
[sourcecode language=”java”]
public class Packet{
/** Your Api Key goes here. **/
private String key = ‘aaaabbbbccccdddd111122223333’;
private String header = ‘<s:Envelope’ +
‘ xmlns:s="http://www.w3.org/2003/05/soap-envelope"’ +
‘ xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"’+
‘ xmlns:xsd="http://www.w3.org/1999/XMLSchema">’+
‘<s:Body>’+
‘ <x:FlickrRequest xmlns:x="urn:flickr">’;
private String footer = ‘ <api_key>’+key+'</api_key>’+
‘ </x:FlickrRequest>’+
‘</s:Body>’+
‘</s:Envelope>’;
public Map<String,String> argumentMap{get;set;}
public Packet(){
argumentMap = new Map<String,String>();
}
private String buildPacket(){
String packet = header;
for(String argument: argumentMap.keyset()){
packet += ‘<‘+argument+’>’;
packet += argumentMap.get(argument);
packet += ‘</’+argument+’>’;
}
packet += footer;
return packet;
}
public String toXmlString(){
return buildPacket();
}
}
[/sourcecode]
The parts to note here are,
- The ‘key’ variable which should be set to the value of your API key.
- The ‘argumentMap’ variable which will be used to set request-specific tags.
- The toXmlString() method which returns the packet as a XML string(why is it that I want to type ‘an XML’ as opposed to ‘a XML’?).
Next we need some way to make requests to the Flickr servers,
[sourcecode language=”java”]
public String makeRequest(Map<String,String> argumentMap){
Httprequest request = new HttpRequest();
request.setMethod(‘POST’);
request.setEndpoint(‘http://api.flickr.com/services/soap/’);
Packet p = new Packet();
p.argumentMap = argumentMap;
request.setBody(p.toXmlString());
Http http = new Http();
HttpResponse response = http.send(request);
System.debug(response.getBody());
XmlStreamReader reader = response.getXmlStreamReader();
reader.setCoalescing(true);
while(reader.hasNext()){
if(reader.isStartElement()&&reader.getLocalName()==’FlickrResponse’){
reader.next();
/** I’ve had to implement a bit of a hack below **/
if(reader.isCharacters()){
return reader.getText();
}
}
reader.next();
}
return null;
}
[/sourcecode]
This method is dirty and I’ll tell you why. For some reason when parsing the XML tags inside of the ‘FlickrResponse’ node the XmlStreamReader fails to recognise children nodes and instead breaks everything into text elements e.g. <photoset> will be read as three elements namely ‘<‘, ‘photoset’, and ‘>’. For this reason I’ve had to return all the entire contents of the ‘FlickrResponse’ node as a single String(to be parsed later). I really, really don’t like this sort of inconsistency and if there are any XML gurus out there who know why, please drop me a line.
Next up, we need some structures to store results, namely a User, a Photoset, and a Photo. The code follows,
[sourcecode language=”java”]
public class FlickrUser{
public String email{get;set;}
public String id{get;set;}
public String username{get;set;}
}
[/sourcecode]
[sourcecode language=”java”]
private class PhotoSet{
public String id{get;set;}
public String numPhotos{get;set;}
public String title{get;set;}
public String description{get;set;}
public PhotoSet(){}
}
[/sourcecode]
[sourcecode language=”java”]
public class Photo{
public String id{get;set;}
public String title{get;set;}
public String secret{get;set;}
public String server{get;set;}
public Map<String,String> urls{get;set;}
public Photo(){
urls = new Map<String,String>();
}
public String getSquareUrl(){
return urls.get(‘url_sq’);
}
public String getTinyUrl(){
return urls.get(‘url_t’);
}
public String getSmallUrl(){
return urls.get(‘url_s’);
}
public String getMedUrl(){
return urls.get(‘url_m’);
}
public String getLargeUrl(){
return urls.get(‘url_l’);
}
}
[/sourcecode]
I’ve coded a number of methods to fetch data into each of the aforementioned structures but I’m going to concentrate on one in particular, Flickr search. The method itself is quite straightforward,
[sourcecode language=”java”]
public List<Photo> photoSearch(String keywords){
List<Photo> photos = new List<Photo>();
Map<String,String> arguments = new Map<String,String>();
arguments.put(‘method’, ‘flickr.photos.search’);
arguments.put(‘text’, keywords);
arguments.put(‘per_page’,’20’);
arguments.put(‘extras’,’url_sq, url_t, url_s, url_m, url_o’);
String response = makeRequest(arguments);
System.debug(response);
XmlStreamReader reader = new XmlStreamReader(response);
while(reader.hasNext()){
if(reader.isStartElement()){
Integer numAttributes = reader.getAttributeCount();
if(reader.getLocalName()==’photo’){
Photo p = new Photo();
for(Integer i = 0; i<numAttributes;i++){
String attributeName = reader.getAttributeLocalName(i);
String attributeValue = reader.getAttributeValueAt(i);
if(attributeName==’id’)
p.id = attributeValue;
else if(attributeName==’title’)
p.title = attributeValue;
else if(attributename==’server’)
p.server = attributeValue;
else if(attributeName==’secret’)
p.secret = attributeValue;
else if(attributeName.startsWith(‘url’))
p.urls.put(attributeName,attributeValue);
}
photos.add(p);
}
}
reader.next();
}
return photos;
}
[/sourcecode]
One thing to note is that I’ve restricted the search to 20 results. You may increase this number but I’ve noticed that if the SOAP response packets are too large the XmlStreamReader variable seems to FAIL i.e. it throws an XML parsing exception part of the way through the traversal even though(IMO) the packets aren’t thhhaaattt big. Once again, if you can figure out why let me know.
Of course it’s nice to see these things in action, so I’ve drafted a bit of demo code. First we have the page controller,
[sourcecode language=”java”]
public with sharing class FlickrDemoController {
private String wallpaperPsId = ‘72157605600725406’;
public String keywords{get;set;}
public Flickr fc{get;set;}
public List<Flickr.Photo> photos{get;set;}
public FlickrDemoController(){
fc = new Flickr();
photos = fc.getPhotosByPhotoSet(wallpaperPsId);
}
public void search(){
photos = fc.photoSearch(keywords);
}
}
[/sourcecode]
And the demo page,
[sourcecode language=”java”]
<apex:page controller="FlickrDemoController" showHeader="false" standardStylesheets="false">
<apex:form >
<apex:inputText value="{!keywords}"/>
<apex:commandButton value="Search" action="{!search}" rerender="results"/>
<apex:outputPanel layout="block" id="results">
<apex:repeat value="{!photos}" var="photo">
<apex:image url="{!photo.squareUrl}" style="background:gray;padding:3px;margin:3px"/>
</apex:repeat>
</apex:outputPanel>
</apex:form>
</apex:page>
[/sourcecode]
Together this controller and page give us a basic, but neat interface,
And if you’d like to see this badboy in action, you can find it here.
In summary, this post has covered the concepts of connecting to a third party using a SOAP-based request packet, as well as parsing the response packet to pull out the bits we might find useful. We’ve also created types(Packet, User, Photo, PhotoSet) which are given semantic meaning by the data they contain, and the operations they can perform on that data, and in doing so attempted to adhere to the object-oriented principle of delegation.
We now know how to consume web services from within the Force.com platform, and provide the user with a basic interface to access these services. Next post we’ll focus on creating a more elegant user interface.
By the way, this is my first post from my brand new MacBook Pro! My first laptop ever. Thank you Salesforce:)
I am getting one error in FlickrDemoController.
that error is list invalid Data Type…..
Please give me correct code
Hey, can you post the entire error please. The code is working on my side so I’m not sure what part you’re having an issue with.
I’m also getting the same error : Compile Error: Invalid type: Flickr.Photo
Hi. Nice Post..!
Also is it possible for me to upload photos to Flickr from Salesforce..? I tried to, but it seems that there is a problem doing multi-part posts from Salesforce. Any thoughts on that.?
Thanks.
Hey. Multi-part as in the content type header? What issue are you having exactly?
Hey Wes ,
Great Article Indeed and cool writing style .
Are you still following this post . Because , I am also reading out this example and getting this error Compile Error: Invalid type: Flickr.Photo while saving class FlickrDemoController .
Warm Regards,
Mayank Joshi