Wednesday, October 13, 2010

Google Checkout and JAXBContext vs. Google AppEngine

 

I am using the Google Checkout Java SDK v2.5 with my app running on Google App Engine. When sending a new order to Google Checkout, the 30 second threshold has been frequently exceeded and stopping people from being able to buy anything.

I spent a number of hours debugging this problem, eventually pinpointing the cause in the Google Checkout code:   a call to JAXBContext.newInstance("com.google.checkout.sdk.domain") in com.google.checkout.sdk.util.Utils.

Sure enough after pinpointing this, googling for JAXBContext and App Engine revealed that this is not an unusual problem for App Engine users. The killer for me is that I want to use the Google Checkout Java SDK, out of the box.

Here is my workaround.  I checked out the source code for the Google Checkout SDK and changed the Utils.java class. First I commented out the context variable, and the code that initialized it. For my strategy, I plan to initialize the JAXB context variable “in advance”, so that it is ready prior to sending out a new order to google checkout.

//  private static final JAXBContext context = makeJaxbContext();
//  private static JAXBContext makeJaxbContext() {
//    try {
//      return JAXBContext.newInstance("com.google.checkout.sdk.domain");
//    } catch (JAXBException e) {
//      throw new RuntimeException(e);
//    }
//  }

 

Now, for the places in Utils.java where “context” is now broken, I replaced it with a “getter” to a singleton that has the JAXB context prepared for me. For example, the fromXML() method now looks like:

public static <T> JAXBElement<T> fromXML(InputStream is) throws JAXBException {
  JAXBContext context = GaeGoogleCheckoutJaxbHelper.INSTANCE.getJaxbContext();
  return (JAXBElement<T>) context.createUnmarshaller().unmarshal(is);
}

 

And here’s the singleton that prepares the JAXB context:

import java.util.logging.Logger;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
public enum GaeGoogleCheckoutJaxbHelper {
  INSTANCE;
  
  private JAXBContext jaxbContext;
  private GaeGoogleCheckoutJaxbHelper() {
    try {
      Logger logger = Logger.getLogger(GaeGoogleCheckoutJaxbHelper.class.getSimpleName());
      logger.info("creating new JAXB context.");
      this.jaxbContext = JAXBContext.newInstance("com.google.checkout.sdk.domain");
      logger.info("done creating new JAXB context.");
    }
    catch (JAXBException e) {
      throw new RuntimeException(e);
    }
  }
  
  public JAXBContext getJaxbContext() {
    return this.jaxbContext;
  }
  
}

 

In my case, I have three steps leading up to a purchase, with the third page being a review page with the Google Checkout button present. When I reach my “step 2” page, I queue a task that calls GaeGoogleCheckoutJaxbHelper.INSTANCE.getJaxbContext() and ignores the result. By the time the user gets to the end of step 3, the JAXB context is awaiting in memory.

This by no means is foolproof. If the user waits *too* long, then App Engine will kick out our app, and we are no better off.

So this will get me by for now while I seek a better solution. I am sad that I may have to roll my own non-JAXB google checkout client. This would be really bad.

Furthermore, I cannot understand why loading that JAXB context takes so long on appengine?! It only took 4 seconds on my wimpy 2GHz machine, yet App Engine consistently takes 20 seconds…!?!?!

This topic relates to being able to control what parts of your app “stay warm” in the JVM, which is a topic circulated elsewhere. I would clearly put my JAXB context stuff there. Google, can we have a “startup” hook for our app’s lifecycle, and keep a few things “warm”?  And can we have a Checkout SDK that integrates with App Engine nicely?

UPDATE 11/2/2010:  I am seeing timeouts on AppEngine when GoogleCheckout is calling me. Unlike the case where the client is about to contact GoogleCheckout, I can’t anticipate when to “warm up” JAXB. I have filed a bug report to the AppEngine folks, though I don’t know what they can do without giving “special treatment” to some classes or libs. I suppose they could just increase the timeout. On the other hand, it really should not take so long for JAXBContext.newInstance() to execute.