Tomcat Context JUnit @Rule

A first draft of a JUnit @Rule that create the test context. This can be used with the Spring context rule for this post to create a complete Spring context for integration tests.

/**
 * Creates an context for tests using an Apache Tomcat server.xml.
 *
 * https://blogs.oracle.com/randystuph/entry/injecting_jndi_datasources_for_junit
 *
 * @author alex.collins
 */
public class TomcatContextRule implements TestRule {
 
    public static final Logger LOGGER = Logger.getLogger(CatalinaContextRule.class);
 
    /**
     * Creates all the sub-contexts for a name.
     */
    public static void createSubContexts(Context ctx, String name) {
        String subContext = "";
        for (String x : name.substring(0, name.lastIndexOf('/')).split("/")) {
            subContext += x;
            try {
                ctx.createSubcontext(subContext);
            } catch (NamingException e) {
                // nop
            }
            subContext += '/';
        }
    }
 
    private final File serverXml;
 
    public TomcatContextRule(File serverXml, Object target) {
        if (serverXml == null || !serverXml.isFile()) {throw new IllegalArgumentException();}
        if (target == null) {throw new IllegalArgumentException();}
        this.serverXml = serverXml;
    }
 
    public Statement apply(final Statement statement, Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                createInitialContext();
                try {
                    statement.evaluate();
                } finally {
                    destroyInitialContext();
                }
            }
        };
    }
 
    private void createInitialContext() throws Exception {
 
        LOGGER.info("creating context");
 
        System.setProperty(Context.INITIAL_CONTEXT_FACTORY, org.apache.naming.java.javaURLContextFactory.class.getName());
        System.setProperty(Context.URL_PKG_PREFIXES, "org.apache.naming");
 
        final InitialContext ic = new InitialContext();
 
        createSubContexts(ic, "java:/comp/env");
 
        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        final DocumentBuilder builder = factory.newDocumentBuilder();
        final Document document = builder.parse(serverXml);
 
        // create Environment
        {
            final NodeList envs = document.getElementsByTagName("Environment");
            for (int i = 0; i < envs.getLength(); i++) {
                final Element env = (Element)envs.item(i); // must be Element
                final String name = "java:comp/env/" + env.getAttribute("name");
                final Object instance = Class.forName(env.getAttribute("type")).getConstructor(String.class)
                        .newInstance(env.getAttribute("value"));
 
                LOGGER.info("binding " + name + " <" + instance + ">");
 
                createSubContexts(ic, name);
 
                ic.bind(name, instance);
            }
        }
 
        // Resource
        {
            final NodeList resources = document.getElementsByTagName("Resource");
            for (int i = 0; i < resources.getLength(); i++) {
                final Element resource = (Element)resources.item(i); // must be Element
                final String name = "java:comp/env/" + resource.getAttribute("name");
                final Class<?> type = Class.forName(resource.getAttribute("type"));
 
                final Object instance;
                if (type.equals(DataSource.class)) {
                    {
                        @SuppressWarnings("unchecked") // this mus be driver?
                        final Class<? extends Driver> driverClass = (Class<? extends Driver>) Class.forName(resource.getAttribute("driverClassName"));
 
                        DriverManager.registerDriver(driverClass.newInstance());
                    }
 
                    final BasicDataSource dataSource = new BasicDataSource();
                    // find all the bean attributes and set them use some reflection
                    for (Method method : dataSource.getClass().getMethods()) {
 
                        if (!method.getName().matches("^set.*")) {continue;}
 
                        final String x = method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4);
 
                        if (!resource.hasAttribute(x)) {continue;}
                        Class<?> y = method.getParameterTypes()[0]; // might be primitive
 
                        if (y.isPrimitive()) {
                            if (y.getName().equals("boolean")) y = Boolean.class;
                            if (y.getName().equals("byte")) y = Byte.class;
                            if (y.getName().equals("char")) y = Character.class;
                            if (y.getName().equals("double")) y = Double.class;
                            if (y.getName().equals("float")) y = Float.class;
                            if (y.getName().equals("int")) y = Integer.class;
                            if (y.getName().equals("long")) y = Long.class;
                            if (y.getName().equals("short")) y = Short.class;
                            if (y.getName().equals("void")) y = Void.class;
                        }
 
                        method.invoke(dataSource, y.getConstructor(String.class).newInstance(resource.getAttribute(x)));
                    }
 
                    instance = dataSource;
                } else {
                    // not supported, yet...
                    throw new AssertionError("type " + type + " not supported");
                }
 
                LOGGER.info("binding " + name + " <" + instance + ">");
 
                createSubContexts(ic, name);
 
                ic.bind(name, instance);
            }
        }
    }
 
    private void destroyInitialContext() {
        System.clearProperty(Context.INITIAL_CONTEXT_FACTORY);
        System.clearProperty(Context.URL_PKG_PREFIXES);
 
        ic.unbind("java:comp/env");
 
        LOGGER.info("context destroyed");
    }
 
}

For example:

 @Rule
    public TestRule rules = RuleChain.outerRule(new CatalinaContextRule(new File(getClass().getResource("/server.xml").getFile()), this))
            .around(new ContextRule(new String[] {"/applicationContext.xml"}, this));

This code is on Github.