Tags

,

I was recently faced with a problem that encouraged me to look into CGLIB proxies. What I needed to do was to slightly modify the way a RichFaces component rendered a partial response (an AJAX response). The change was very small, yet the only way to do it with traditional methods would be to override a RichFaces class, copy-and-paste a large method and make the necessary changes there.

I did not want to go in that direction, b/c that would make my code very sensitive to changes in future versions of RichFaces. Instead I decided to explore the possibility of somehow intercepting a call to PartialResponseWriter.startUpdate() method, b/c that was the behaviour I needed to change.

The obvious strategy here would be to create a wrapper around the PartialResponseWriter and make the necessary changes in the method I need. The problem with this approach is that my wrapper would have to implement about 20 methods, all of which except for one would be useless pass-throughs. That’s a lot of boilerplate there. A Lazy Programmer such as myself would never actually spend the time writing all that useless code. So, I decided to see if I could achieve my goal by utilizing CGLIB proxies.

CGLIB

CGLIB is a powerful and performant bytecode generation library, that relies on the low-level ASM framework (http://asm.ow2.org/). If you ever used Spring AOP or Hibernate, than you are probably used to seeing CGLIB proxied objects all the time.

CGLIB codebase is small, but not well documented, so it took me some time to figure out how to create some simple proxies.

Creating a Simple Proxy

Unlike the JDK dynamic proxy, we cannot proxy an existing object. The target object must be created by the CGLIB library, so all we get to do is specify the superclass for our proxy, as well as which constructor to use, if any.

The core of the CGLIB proxying API is the net.sf.cglib.proxy.Enhancer class. It has a lot of helper method for different situations. You can easily explore these options by looking at the source code for that class. In my example here I’ll show how to create a proxy for PartialResponseWriter, which has no default constructor:

private PartialResponseWriter proxyResponseWriter(PartialResponseWriter origWriter) {
	Enhancer enhancer = new Enhancer();
	enhancer.setSuperclass(PartialResponseWriter.class);
	enhancer.setCallback(new ResponseWriterInterceptor(origWriter));
		
	PartialResponseWriter proxy = (PartialResponseWriter) enhancer.create(
			new Class[] {ResponseWriter.class}, 
			new Object[] {origWriter.getWrapped()});
	
	return proxy;
}

private class ResponseWriterInterceptor implements MethodInterceptor {
	private PartialResponseWriter originalWriter;
	public ResponseWriterInterceptor(PartialResponseWriter originalWriter) {
		this.originalWriter = originalWriter;
	}

	public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		if ( "startUpdate".equals(method.getName()) ) {
			String clientId = args[0].toString();
			
			if ( FixedUICollapsibleSubTable.this.getClientId().equals(clientId) ) { // Make sure the update is for our table
				clientId += ":c";
				return method.invoke(originalWriter, new Object[] {clientId});
			}
		}

		return method.invoke(originalWriter, args);
	}
}


This code creates a new instance of PartialResponseWriter and provides us with a way to examine and/or modify all calls to it via the provided MethodInterceptor. In this case I made the interceptor act as a pass-through for all methods, except startUpdate, which does additional processing before calling the proxied method.

The Final Hack

The final implementation of this hack was a bit more complex than proxying just the PartialResponseWriter, since RichFaces did not make it easy to “install” our proxied version of the ResponseWriter into the PartialViewContext, nevertheless this strategy worked out very well for me.

The two additional things I needed to do was CGLIB proxy ExtendedPartialViewContext as well, in order to get at the right spot to proxy PartialResponseWriter.

I also had to use reflection to set the resulting PartialResponseWriter proxy on the current FacesContext.

Here is the full code for the hack:

/**
* The ajax response modification allows us to re-render only the subtable we want on ajax requests, rather than
* the whole master table.
*
* @author Val Blant
*/
public class FixedUICollapsibleSubTable extends UICollapsibleSubTable {

/**
 * This method installs hooks into the <code>PartialResponseWriter</code> used to put together an ajax 
 * response that involves our Collapsible Sub Table. We use CGLIB proxies to intercept the startUpdate() 
 * call and manipulate the clientId used in writing the response.
 */
@Override
public boolean visitTree(VisitContext visitContext, VisitCallback callback) {
	FacesContext facesContext = FacesContext.getCurrentInstance();
	PartialViewContext viewContext = facesContext.getPartialViewContext();
	
	boolean magicEnabled = false;
	if ( visitContext instanceof RenderExtendedVisitContext ) { // Only do this for render phase
		if ( viewContext instanceof ExtendedPartialViewContext ) { // Should always be true unless RF4 changes something
			magicEnabled = true;

			// Proxy the View Context and set it into the FacesContext
			//
			ExtendedPartialViewContext proxy = proxyViewContext(viewContext);
			replaceViewContext(facesContext, proxy);
		}
		
	}
	
	// Visit the subtable with proxied PartialResponseWriter
	//
	boolean result = super.visitTree(visitContext, callback);
	
	// Restore the original objects
	//
	if ( magicEnabled ) {
		replaceViewContext(facesContext, viewContext);
	}
	
	return result;
}

/**
 * @param viewContext
 * @return CGLIB proxy of the passed in <code>PartialViewContext</code>
 * @see PartialViewContextInterceptor
 */
private ExtendedPartialViewContext proxyViewContext(PartialViewContext viewContext) {
	Enhancer enhancer = new Enhancer();
	enhancer.setSuperclass(ExtendedPartialViewContext.class);
	enhancer.setCallback(new PartialViewContextInterceptor((ExtendedPartialViewContext)viewContext));
	
	ExtendedPartialViewContext proxy = (ExtendedPartialViewContext) enhancer.create(
			new Class[] {FacesContext.class}, 
			new Object[] {FacesContext.getCurrentInstance()});
	
	return proxy;
}

/**
 * Replaces the <code>PartialViewContext</code> inside the given facesContext
 * 
 * @param facesContext
 * @param viewContext
 */
private void replaceViewContext(FacesContext facesContext, PartialViewContext viewContext) {
	try {
		Field f = facesContext.getClass().getDeclaredField("partialViewContext");
		f.setAccessible(true);
		f.set(facesContext, viewContext);
	} catch (Exception e) {
		throw new IllegalStateException(e);
	}
}


/**
 * Intercepts calls to getPartialResponseWriter() in order to return a proxied <code>PartialResponseWriter</code>
 * with our customizations.
 */
private class PartialViewContextInterceptor implements MethodInterceptor {
	private ExtendedPartialViewContext originalContext;
	public PartialViewContextInterceptor(ExtendedPartialViewContext originalContext) {
		this.originalContext = originalContext;
	}

	public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		if ( "getPartialResponseWriter".equals(method.getName()) ) {
			return proxyResponseWriter();
		}
		else {
			return method.invoke(originalContext, args);
		}
	}
	
	/**
	 * @return CGLIB proxy of the <code>PartialResponseWriter</code>
	 * @see ResponseWriterInterceptor
	 */
	private PartialResponseWriter proxyResponseWriter() {
		PartialResponseWriter origWriter = originalContext.getPartialResponseWriter();
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(PartialResponseWriter.class);
		enhancer.setCallback(new ResponseWriterInterceptor(origWriter));
		
		PartialResponseWriter proxy = (PartialResponseWriter) enhancer.create(
				new Class[] {ResponseWriter.class}, 
				new Object[] {origWriter.getWrapped()});
		
		return proxy;
	}
	
}

/**
 * Intercepts calls to startUpdate() method in order to modify the clientId.
 * 
 * This is necessary, b/c we are no longer using the stock version of the Collapsible Sub Table from RF4.
 * We made a lot of modifications which change the HTML structure of the table. One change involves not rendering
 * the 'tbody' tag with id that matches the clientId of our subtable component. We are only rendering the content 
 * 'tbody', which has the id with form: "{clientId}:c".
 *  
 * The purpose of this interceptor is to make sure that the ajax update response targets the content 'tbody', rather than
 * now missing 'tbody'.
 */
private class ResponseWriterInterceptor implements MethodInterceptor {
	private PartialResponseWriter originalWriter;
	public ResponseWriterInterceptor(PartialResponseWriter originalWriter) {
		this.originalWriter = originalWriter;
	}

	public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		if ( "startUpdate".equals(method.getName()) ) {
			String clientId = args[0].toString();
			
			if ( FixedUICollapsibleSubTable.this.getClientId().equals(clientId) ) { // Make sure the update is for our table
				clientId += ":c";
				return method.invoke(originalWriter, new Object[] {clientId});
			}
		}

		return method.invoke(originalWriter, args);
	}
}


}