a little madness

A man needs a little madness, or else he never dares cut the rope and be free -Nikos Kazantzakis

Zutubi

Archive for July, 2008

Ajax vs Caching vs Firefox 3

In an earlier post, many moons ago, I related some struggles with Ajax page updates and browser caching behaviour. Today I have had to revisit the same problem again, thanks to Firefox 3.

The good news is that this time it was very easy to spot via the “Net” tab of Firebug. I could clearly see that no request was being made when I expected an Ajax-based panel update. I could also see that the response headers on the previous request for the same panel, whilst including some Cache-Control parameters, didn’t seem to include every header I expected. A bit of searching around led me to a useful comment on a Mozilla bug report. The bug report suggests there may be some issues when specifying multiple Cache-Control headers, with a specific header ordering to fix the problem. So I updated the headers set to:

Cache-Control: no-store, no-cache, must-revalidate
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Pragma: no-cache

So far this update appears to work: at the very least it solves the panel update problem I was seeing. Hopefully it can save someone else wasted debugging time!

Java 6: Using Python Via The New Scripting Engine

SUMMARY

Do you ever find yourself writing Java code that interacts with external processes and systems, but wish you could use a scripting language more suited to the task? If you have Java 6 available to you, then you are in luck.

MOTIVATION

Interacting with the file system and external processes, whilst possible, is not what I would consider to be one of Javas’ strong points. So, when thinking about acceptance testing for a product such as Pulse, it makes sense that much of the deployment / starting / stopping is handled by a scripting language (Python in this case).

This has served us well up until recently, when we added features such as database migration and automated backups to Pulse.

Let me explain why:

Each time we ran our acceptance tests on a new Pulse build, we took the deliverable artifact, deployed and started it using the above mentioned scripts. We then triggered our Java test suit which ran through the setup tests first (effectively setting up a fresh installation) followed by all of the tests that require a running installation.

This works well so long as the context of the acceptance tests is a running Pulse server. But now we need tests that run in the context of multiple invocations of the server (start pulse – back it up – make changes – stop pulse, restart pulse with the restore flag etc). One option would be to write these tests in Python (as we have done with our automated agent upgrade tests) which works well, but means that tests are now split amongst multiple languages. The other option is to continue to drive all the tests from Java and drive our external scripts from Java as well.

SOLUTION

The new Java 6 scripting engine support provides a very clean solution to the problem we faced by running our scripts directly within the JVM and allowing our Java code to access the script components.

Using our situation for the example, with the help of jython, we were able to wrap our Python script components behind Java interfaces, and using a simple factory method, we are able to run our scripts as if they were plain Java objects.

Here is the code.

Firstly, we have the python scripts, that define a PulsePackage and Pulse classes, with the functionality to extract a package and then start / stop an installation.

class PulsePackage:
   
  def __init__(self, packageFile):
    self.packageFile = packageFile
   
  def extractTo(self, destDir):
    if not os.path.exists(destDir):
      os.makedirs(destDir)
       
    var = {‘destDir’:destDir, ‘packageFile’:self.packageFile}
      if self.packageFile.endswith(‘.zip’):
        os.system("unzip -qd %(destDir)s %(packageFile)s" % var)
      else:
        os.system("tar -zxC %(destDir)s -f %(packageFile)s" % var)
       
    return Pulse(os.path.join(destDir, self.getBasename()))
       
class Pulse:

  def __init__(self, baseDir, port=8080):
    """ snipped """
       
  def start(self, wait=True, service=False):
    """ snipped """
       
  def stop(self, timeout=60, service=False):
    """ snipped """

Then we define the Java interfaces that represent these python classes.

public interface PulsePackage
{
  Pulse extractTo(String target);
}

public interface Pulse
{
  void start();
 
  void stop();
}

Next, we define the factory that gives us access to the Python objects from within our Java tests.

public class JythonPulsePackageFactory
  implements PulsePackageFactory
{
  private File scripts = null;
   
  private Invocable invocableEngine;

  public JythonPulsePackageFactory()
  {
    this.scripts = new File("scripts");

    ScriptEngine jythonEngine =
        new ScriptEngineManager().
            getEngineByName("python");
       
    // the packageFactory.py script
    // contains our python class
    // definitions, and is loaded
    // here into the script engine.
    Reader script = new FileReader(
      new File(scripts, "packageFactory.py"));
    jythonEngine.eval(script);

    // this invocable engine now
    // allows us access to execute
    // python defined methods.
    invocableEngine =
      (Invocable) jythonEngine;
  }

  public PulsePackage createPackage(File pkg)
    throws Exception
  {
    // the createPackage method is
    // implemented in python, and
    // returns an instance (handle)
    // to python implemented extension
    // of our PulsePackage interface.
    return (PulsePackage)
      invocableEngine.invokeFunction(
          "createPackage", pkg.getCanonicalPath());
  }
}

And finally, where the magic happens, we bridge the gap between the Java and the python instances by defining the create package method in the python script.

def createPackage(packageFile):
    return PulsePackage(packageFile)

and update the python class definitions, extending the java interfaces:

class Pulse(com.zutubi.pulse.acceptance.Pulse):
  """ <snipped> """

class PulsePackage(com.zutubi.pulse.acceptance.PulsePackage):
  """ <snipped> """

And this is how the resulting java code looks for deploying and starting a new pulse installation:

File pkg =  new File("testing-packages/pulse-2.0.0.zip");

JythonPulsePackageFactory factory =
    new JythonPulsePackageFactory();

PulsePackage pulsePackage = factory.createPackage(pkg);

File destDir = new File("tmp");
Pulse pulse = pulsePackage.extractTo(destDir.getCanonicalPath());
pulse.start();