Tuesday, December 16, 2008

Using AOP to Make Calls Asynchronous in Java

I am working on a project called ScoreOut that consists of an application that runs on a phone that interacts with a server via a REST API.  The server is written in Java and makes heavy use of the Spring Framework.  I wanted to start tracking some of the actions that a user takes on the phone and recording those actions in our analytics tables.  For example, we want to record when a user logs in or requests a golf course.  However, we do not want to slow down the response to the client since the customer is probably out on a golf course at that time.  Therefore, it seemed to makes sense to perform any auditing asynchronously.

The server is broken up into services and I already had a service that did auditing.  I added two new methods to it recordClientLogin and recordCourseRetrieval.  I wanted these methods to be run asynchronously but I wanted the asychronousness (if this isn't a word it should be) to be reusable and easily enabled or disabled (easier to unit test when disabled).  This immediately let me to use AOP so I wrote an interceptor.


package com.scoreout.service.interceptor;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* Intecepts a call and makes it asynchronous. If there is an error it is
* simply logged.
*/
public class AsynchronizeInterceptor implements MethodInterceptor
{

/**
* Logger
*/
private static final Log LOG = LogFactory.
getLog(AsynchronizeInterceptor.class);

/**
* This method will always return null since the method is
* invoked asynchronously.
*
* @see org.aopalliance.intercept.MethodInterceptor
*          #invoke(org.aopalliance.intercept.MethodInvocation)
*/
public Object invoke(final MethodInvocation invocation) throws Throwable
{
Thread thread = new Thread()
{
public void run()
{
try
{
invocation.proceed();
} catch (Throwable e)
{
LOG.warn("Asynchronous method invocation failed.", e);
}
}
};

thread.start();

return null;
}
}


So this inteceptor will make any method it intercepts asynchronous running the method on a separate thread and returning immediately.  So now I just needed to add the point cut to the spring application context.

<!-- Turns methods calls into asynchronous method calls -->
<bean id="asynchronizeInterceptor"
class="com.scoreout.service.interceptor.AsynchronizeInterceptor">
</bean>

<!-- Intercept caddie event recording method calls -->
<bean id="asynchronizePointCut"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="asynchronizeInterceptor" />
</property>
<property name="patterns">
<list>
<value>com.scoreout.service.Audit.recordCourseRetrieval*</value>
<value>com.scoreout.service.Audit.recordClientLogin*</value>
</list>
</property>
</bean>


Now anytime I call com.scoreout.service.Audit.recordCourseRetrieval or com.scoreout.service.Audit.recordClientLogin the call will return immediately and the method will be run in the background.  For testing I can easily make these methods synchronous by either removing them from the application context or providing a new application context that does not include this pointcut (I generally prefer the latter).

Wednesday, December 10, 2008

IE7 Tricks

Hey, my first post and I already spent an hour setting up wordpress so I'll keep it short.  Recently, we did an overhaul of our video management web application at Episodic.  Like any sane web developer I did all my testing in Firefox and then found myself with one day left before pushing to production and I had yet to look at our site in IE7 (IE6 not supported).

So I RDC'd to our one Windows box and, uh oh, it don't look so good and there are a bunch of JS errors.  Here were a few of the main problems that I had to solve for IE.

Trailing Commas

IE does not like trailing commas in things like object declarations.  For example, you will get a very general JS error that points to a line that has nothing to do with the problem if you have the following:

var myObject = {
foo: 'bar',
baz: anotherObject,
};


Positioning and Z-Index

Our site has a lot of fancy drop-down menus.  The menu is absolutely positioned within some relatively positioned element and then given a z-index so that it appears overtop of other elements on the page.  However, this was a problem on IE when I noticed that my menu would actually appear behind some elements on the page.  In particular, the menu would appear behind other elements that have "position:relative".

This took a while for me to figure out.  Eventually it came down to this: If an absolutely positioned element has a z-index then it's containing relatively positioned element must specify a z-index (z-index:1) in order for the absolutely positioned element to be able to appear on top of other relatively positioned elements.

I did not come up with this on my own but it did take me a while to find this excellent page called "Z-Indexing with position relative and absolute".

Mootools Events Targets

I LOVE Mootools.  Eventually I will do another post about some Mootools tricks I have picked up but for now I'll stay within the context of this post.  I have a lot of JS code that looks like the following:

someElement.addEvent('click', function(event) {
if (event.target.get('tag') == 'li'
event.target.dispose();
});


However, the Mootools documentation says that the event target, is not extended with $ for performance reasons (which actually only seems to be true in IE and would result in JS errors).  Therefore, the above case should be written as:

someElement.addEvent('click', function(event) {
if ($(event.target).get('tag') == 'li'
$(event.target).dispose();
});


To avoid writing code like this everywhere I extended the Event class as follows:

Event.implement({
getTarget: function() {
if (!this.extendedTarget)
this.extendedTarget = $(this.target);
return this.extendedTarget;
});


This left me with:

someElement.addEvent('click', function(event) {
if (event.getTarget().get('tag') == 'li'
event.getTarget().dispose();
});