Schabby's Blog
Reserve Orbital Defence Commander

In this post I am going to provide some example code to connect to Zanox revised API via Java and SOAP.

The updated API got more coherent and consistent. Its changes mainly address the new authentication model and the application store in which you can feature your own applications based on Zanox data.

Now for the purpose of this post, I sum up how to connect to Zanox SOAP API using Java. There are several steps to do, mostly administrative in nature.

Get the keys

Zanox implements a shared key authentication model. Therefore, you need to get yourself the right keys (public and private) to get started. Here's the page that describes how to get them. Once you created your own application, you will receive the following keys that are necessary prerequisites for your code

  • zanox Application ID
  • Connect ID
  • Public key
  • zanox secret key (aka. private key)

Generate Zanox SOAP Beans From WSDL

Once you have the keys, you need to generate the classes from the WSDL. For the purpose of this post, we used the (currently latest) WSDL on

http://api.zanox.com/wsdl/2009-07-01

with the endpoint at

http://api.zanox.com/soap/2009-07-01

We used wsimport to generate the classes as follows

wsimport -d bin -s src -p com.zanox.api.namespace._2009_07_01 http://api.zanox.com/wsdl/2009-07-01

This should generate you a bunch of class in in the src folder.

Example Code to Connect to Zanox SOAP API

In order to run our example code, you need to setup your classpath with the following libraries (actually I am not sure if they are really all necessary)

  • XmlSchema-1.4.3.jar
  • asm-2.2.3.jar
  • cxf-2.1.4.jar
  • geronimo-activation_1.1_spec-1.0.2.jar
  • geronimo-annotation_1.0_spec-1.1.1.jar
  • geronimo-jaxws_2.1_spec-1.0.jar
  • geronimo-stax-api_1.0_spec-1.0.1.jar
  • geronimo-ws-metadata_2.0_spec-1.1.2.jar
  • jaxb-api-2.1.jar
  • jaxb-impl-2.1.9.jar
  • jaxb-xjc-2.1.9.jar
  • saaj-api-1.3.jar
  • saaj-impl-1.3.2.jar
  • wsdl4j-1.6.2.jar
  • wstx-asl-3.2.6.jar
  • xml-resolver-1.2.jar

Alrighty, we got all we need to fill in the last bricks. The example code conncets to the API and retrieves the sales of the specified date. The API holds far more methods for different purposes, but they are quit similar in use and once you got the sale-example working (even though you might not generate any sales yet) you can easily infere the correct usage and parameter sets for all other methods.

Create two classes: ZanoxAuthenticationDto and SoapZanoxFacade. The first DTO is just to hold the authentication data and looks like this:

public class ZanoxAuthenticationDto
{
	private String applicationId;
	private String connectId;
	private String sharedKey;
	private String privateKey;
 
 // ... according getter and setter methods
 //  are left out for simplicity reasons
}

In the SoapZanoxFacade class we do the actual work and contain all following code snippets.

We first define a couple of helper methods. The most important one is certainly the one to compute the correct signature that is sent to Zanox for every request.

    public static String createSoapSignature(final String serviceName, final String serviceMethod, final String timestamp, String nonce, String secreyKey) 
    {
        final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
 
        // Acquire an HMAC/SHA1 from the raw key bytes.
        SecretKeySpec signingKey = new SecretKeySpec(secreyKey.getBytes(), HMAC_SHA1_ALGORITHM);
 
        // Acquire the MAC instance and initialize with the signing key.
        Mac mac = null;
        try 
        {
            mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
            mac.init(signingKey);
        } 
        catch (Exception e) 
        {
            throw new RuntimeException("trouble while initiating/signing", e);
        }
 
        String input = serviceName.toLowerCase() + serviceMethod.toLowerCase() + timestamp + nonce;
 
        // Compute the HMAC on the digest, and set it.
        String signature = new BASE64Encoder().encode(mac.doFinal(input.getBytes()));
        return signature;
    }

Another one to format the timestamp

    public static String createSoapTimestamp() 
    {
        String fmt = "yyyy-MM-dd'T'HH:mm:ss.000'Z'";
        SimpleDateFormat df = new SimpleDateFormat(fmt, Locale.US);
        df.setTimeZone(TimeZone.getTimeZone("UTC"));
        String date = df.format(new Date());
        return date;
    }

one to converte java.util.Calendar to XmlGregorianCalendar

    public static XMLGregorianCalendar toXmlGregorianCalendar(Calendar c)
    {
    	if( !(c instanceof GregorianCalendar) )
    	{
    		throw new ZanoxWsException("Calendar instance must be of type GregorianCalendar");
    	}
 
    	try
		{
			return DatatypeFactory.newInstance().newXMLGregorianCalendar((GregorianCalendar)c);
		} 
    	catch (DatatypeConfigurationException e)
		{
    		throw new ZanoxWsException(e);
		}    	
    }

and the last one to generate a random string of specified length (aka. nonce)

    public static String createNonce(int length)
    {
    	final String CHARS = "01234567890abcdefghijkmlmnopqrstuvwxyz";
    	StringBuilder sb = new StringBuilder();
    	for(int i=0; i < length; i++)
    		sb.append(CHARS.charAt((int)(Math.random()*CHARS.length())));
 
    	return sb.toString();
    }

The final code snipped finally retrieves the sales:

	/**
	 * Retrieve the first 10K sales for day <code>calTime</code>. 
	 * Paging is hard-wired to first page (in other words: no paging)
	 */
    public void getSales(Calendar calTime, ZanoxAuthenticationDto auth)  
    {
        String timestamp = createSoapTimestamp();
        String nonce     = createNonce(20);
        String signature = createSoapSignature("PublisherService", "GetSales", timestamp, nonce, auth.getPrivateKey());
        XMLGregorianCalendar date = toXmlGregorianCalendar(calTime);
 
        GetSalesRequest getSalesRequest = new GetSalesRequest();
        getSalesRequest.setConnectId(auth.getConnectId());
        getSalesRequest.setDate(date);
        getSalesRequest.setTimestamp(timestamp);
        getSalesRequest.setPage(0);
        getSalesRequest.setItems(10000);
        getSalesRequest.setSignature(signature);
        getSalesRequest.setNonce(nonce);
 
        GetSalesResponse response = null;
 
        try 
        {
            response = getPublisher().getSales(getSalesRequest);
        } 
        catch (Exception e)
        {
            throw new ZanoxWsException("error while fetching sales from Zanox SOAP API", e);
        } 
 
        // TODO: do something sensible with the data...
        for( SaleItem item: response.getSaleItems().getSaleItem())
        {
        	System.out.println(item.getProgram().getValue()+"\t "+item.getSubPublisher().getId()+"\t"+item.getClickId()+" "+item.getAmount()+" "+item.getLptId()+" "+item.getCommission());
        }
    }
 
    private PublisherSoapPortType getPublisher() 
    {
        final PublisherService pubs = new PublisherService();
        return pubs.getPublisherSoapPort();
    }

To run the code, create a main methode where you set up you authentication data and call getSales on SoapZanoxFacade.

    public static void main(String[] args) 
    {
    	ZanoxAuthenticationDto dto = new ZanoxAuthenticationDto();
    	dto.setConnectId("ASL22DKAS223SKD55KAgD");
    	dto.setSharedKey("JY89AFLLA(RKJZZ");
    	dto.setPrivateKey("asdkljah893qisdnfsyniweoi8ynw893nw");
    	dto.setApplicationId("sdfs4w9p4uao8oadf9fusm9d");
 
    	Calendar c = Calendar.getInstance();
    	c.set(2009, 9, 2);
    	SoapZanoxFacade l = new SoapZanoxFacade();
    	l.getSales(c, dto);
    }

This should get you going with the API. All other SOAP requests are built in a similar fashion.


Eine Antwort

  1. Chris Loonan says:

    Great post ! You've got everyone in the zanox Bay Area Campus talking/curious to know more about you. What do you know about our App Store, the $2million competition or a potential spotlight from Twitter?

    Shoot me an email, I'd love to talk.

Post Comment

Please notice: Comments are moderated by an Admin.