Spring

CXF, JAXRS, XML and CDATA

I was in a need to export some XML tags as CDATA in my CXF XML @WebService that generates some XML for our partner.

We had a field that contained an URL and in that we have ampersand characters. By default these get turned into “&” but our specific XML partner was not OK with that so we had to adjust to CDATA. That turned out to be rather complex as I was hoping for something simple like @CDATA annotation – but as always, life is full of suprises.

Anyway, the trick is rather simple to make it work – We need to define our own XML Stream Writer and overwrite the default write method from “writeCharacters” to “writeCData”.

We had a very specific tags in mind only so we specified those as static string array.

You can do this like this:

import java.util.Arrays;

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import org.apache.cxf.staxutils.DelegatingXMLStreamWriter;

/**
 * Simple CDATA XML Stream Writer that exports some items as CDATA
 */
public class CDataXMLStreamWriter extends DelegatingXMLStreamWriter {

	// All elements with these names will be turned into CDATA
	private static String[] CDATA_ELEMENTS = { "infoUrl", "description" };

	private String currentElementName;

	public CDataXMLStreamWriter(XMLStreamWriter del) {
		super(del);
	}

	@Override
	public void writeCharacters(String text) throws XMLStreamException {
		if (Arrays.asList(CDATA_ELEMENTS).contains(currentElementName)) {
			super.writeCData(text);
		} else {
			super.writeCharacters(text);
		}
	}

	public void writeStartElement(String prefix, String local, String uri) throws XMLStreamException {
		currentElementName = local;
		super.writeStartElement(prefix, local, uri);
	}
}

OK; So now we have this nice writer, but how do you use it? Well, we are running it all inside a CXF that’s running with Spring container so as it turns out, we need an “Interceptor” that would help us write stuff. You can make one easily like this.


import java.io.OutputStream;

import javax.xml.stream.XMLStreamWriter;

import org.apache.cxf.interceptor.AttachmentOutInterceptor;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.staxutils.StaxUtils;

public class CDataWriterInterceptor extends AbstractPhaseInterceptor {

	public CDataWriterInterceptor() {
		super(Phase.PRE_STREAM);
		addAfter(AttachmentOutInterceptor.class.getName());
	}

	@Override
	public void handleMessage(Message message) {
		// Required for CDATA to working
		message.put("disable.outputstream.optimization", Boolean.TRUE);
		XMLStreamWriter writer = StaxUtils.createXMLStreamWriter(message.getContent(OutputStream.class));
		message.setContent(XMLStreamWriter.class, new CDataXMLStreamWriter(writer));
	}
}

And now, how do you use this interceptor? In your applicationContext.xml or similar Spring context file, define the interceptor and use it!
Note that the LoggingFeature is not required but I’ve left it in here so that you could easily enable logging to see the incoming and outgoing messages better.


	<bean id="CDataWriterInterceptor" class="com.amivarius.cxf.util.CDataWriterInterceptor" />

	<jaxrs:server .... >

		<jaxrs:features>
			<bean class="org.apache.cxf.feature.LoggingFeature" />
		</jaxrs:features>

		<jaxrs:outInterceptors>
			<ref bean="CDataWriterInterceptor" />
		</jaxrs:outInterceptors>

	</jaxrs:server>

And that’s it folks!

The output will be <![CDATA[]]> nicely 🙂

Struts2 Convention & Spring Junit4

The problem

You normally would use StrutsSpringJunit4TestCase as the base class to unit-test your actions. But this class does not support Struts2 Convention plugin Annotations. So if you are like me and you like to keep your actions and results defined in the Action classes by annotations you are s***-out-of-luck. Most likely on your first attempt you will see exceptions like this:

WARN org.springframework.mock.web.MockServletContext.getResourcePaths:212 – Couldn’t get resource paths for class path resource [WEB-INF/content/] java.io.FileNotFoundException: class path resource [WEB-INF/content/] cannot be resolved to URL because it does not exist. This effectively means that the conventions plugin is attempting to locate the resources etc.. Now the simple solution to resolve all those warnings is to modify your struts.xml file and add this setting in there:

<constantname=“struts.convention.result.path”value=“/”/>

Ok, so the warnings are gone but non of your annotated struts actions have actually been loaded into the configuration and you can’t unit-test them. So what gives?

The Solution – Almost

Now, here’s a nice solution almost: http://depressedprogrammer.wordpress.com/2007/06/18/unit-testing-struts-2-actions-spring-junit/

I do want to credit Zarar Siddiqi for that class mostly. It’s a good starting base, but needs a bit of customization for the latest Struts / Spring and Junit 4 environment.

The Solution – Finally

Here’s the source code for the class i created based on the above posting:

import java.util.HashMap;

import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.mapper.ActionMapper;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import org.apache.struts2.views.JspSupportServlet;
import org.junit.Before;
import org.junit.BeforeClass;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.ContextLoader;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.ActionProxyFactory;

/**
 * Improved the sample from originally
 * http://depressedprogrammer.wordpress.com/2007/06/18/unit-testing
 * -struts-2-actions-spring-junit/
 * 
 * - Added support for Struts Convention plugin annotations, also made it run
 * with JUnit 4.
 * 
 * @author Zarar Siddiqi
 * @author codeyouneed.com
 * 
 */
public abstract class StrutsConventionSpringJUnit4TestCase {

	/**
	 * Where are the Spring configurations?
	 */
	private static final String CONFIG_LOCATIONS = "applicationContext-test.xml,"
			+ "applicationContext-test-security.xml";

	private static ApplicationContext applicationContext;
	protected Dispatcher dispatcher;
	protected ActionProxy proxy;
	protected static MockServletContext servletContext;
	protected static MockServletConfig servletConfig;
	protected MockHttpServletRequest request;
	protected MockHttpServletResponse response;

	/**
	 * Mimic the getActionProxy method the usual StrutsSpringJuni4TestCase uses.
	 * 
	 * @param clazz
	 *          Class for which to create Action
	 * @param namespace
	 *          Namespace of action
	 * @param name
	 *          Action name
	 * @return Action class
	 * @throws Exception
	 *           Catch-all exception
	 */
	@SuppressWarnings("unchecked")
	protected ActionProxy getActionProxy(String uri) {
		request.setRequestURI(uri);
		ActionMapping mapping = Dispatcher
				.getInstance()
				.getContainer()
				.getInstance(ActionMapper.class)
				.getMapping(request, Dispatcher.getInstance().getConfigurationManager());
		String namespace = mapping.getNamespace();
		String name = mapping.getName();
		String method = mapping.getMethod();

		// create a proxy class which is just a wrapper around the action call.
		// The proxy is created by checking the namespace and name against the
		// struts.xml configuration
		proxy = dispatcher
				.getContainer()
				.getInstance(ActionProxyFactory.class)
				.createActionProxy(namespace, name, method,
						new HashMap&lt;String, Object&gt;(), true, false);

		// Add all request parameters to our action
		ActionContext invocationContext = proxy.getInvocation()
				.getInvocationContext();
		invocationContext.setParameters(new HashMap&lt;String, Object&gt;(request
				.getParameterMap()));

		// set the action context to the one used by the proxy
		ActionContext.setContext(invocationContext);

		// By default, have a clean session.
		proxy.getInvocation().getInvocationContext()
				.setSession(new HashMap&lt;String, Object&gt;());

		// Important - unit testing JSP's is just not very helpful.
		// do not execute the result after executing the action.
		proxy.setExecuteResult(false);

		// set the actions context to the one which the proxy is using
		ServletActionContext.setContext(proxy.getInvocation()
				.getInvocationContext());
		ServletActionContext.setRequest(request);
		ServletActionContext.setResponse(response);
		ServletActionContext.setServletContext(servletContext);
		return proxy;
	}

	/**
	 * Create a new dispatcher before every test.
	 */
	@Before
	public void initDispatcher() {
		// Dispatcher is the guy that actually handles all requests. Pass in
		// an empty. Map as the parameters but if you want to change stuff like
		// what config files to read, you need to specify them here. Here's how to
		// scan packages for actions. Thanks to Hardy Ferentschik, for action
		// packages ideas. (see Dispatcher's source code)
		dispatcher = new Dispatcher(servletContext, new HashMap&lt;String, String&gt;());
		dispatcher.init();
		Dispatcher.setInstance(dispatcher);
		request = new MockHttpServletRequest();
		response = new MockHttpServletResponse();
	}

	/**
	 * Initialize Spring context before the unit tests.
	 * 
	 * @throws Exception
	 *           When something bad happens.
	 */
	@BeforeClass
	public static void setUpContext() throws Exception {
		// Only initialize Spring context once - speeds up the tests a lot.
		if (applicationContext == null) {
			// this is the first time so initialize Spring context
			servletContext = new MockServletContext();
			servletContext.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM,
					CONFIG_LOCATIONS);
			applicationContext = (new ContextLoader())
					.initWebApplicationContext(servletContext);

			// Struts JSP support servlet (for Freemarker)
			new JspSupportServlet().init(new MockServletConfig(servletContext));
		}
	}
}

 

So what’s going on – what do I need to know?

Rather simply but, I needed the unit-testing class to support the newer versions of the used libraries, so the above class is made to work with the following environment:

  • JUnit 4.10
  • Spring 3.1.0
  • Struts 2.3.1.2

Warning about Tiles and other results

There’s no tiles support in the above class, and executing the results have been disabled. If you want to enable executing the results you have to modify this line:

proxy.setExecuteResult(false);

You can hack it in there if you like but I couldn’t get it to work because of lack of time and because I really didn’t wanted to unit-test my JSP’s and tiles. Here’s the code you need to add to the setUpContext() method to make it initialize the tiles.

// Goes way in the top of the class as a static configuration.
private static final String TILES_CONFIG = "WEB-INF/tiles.xml";

// Goes into setUpContext() method in the end of the class file...
servletContext.addInitParameter(BasicTilesContainer.DEFINITIONS_CONFIG, TILES_CONFIG);
final StrutsTilesListener tilesListener = new StrutsTilesListener();
final ServletContextEvent event = new ServletContextEvent(servletContext);
tilesListener.contextInitialized(event);