Inject beans into JBoss 7 Modules

It seems like JBoss 7.0 CDI has an issue with injecting beans that are located in shared libraries, and are not part of an EAR file. Irritating (but solved in the next version). However, I didn’t want to upgrade, so I decided to solve it instead.
Here’s a short step-by-step:

Deltaspike

Deltaspike is an apache library that has several useful CDI extensions. Add it as a module to your jboss-as. You can use the following module.xml file:

<?xml version="1.0" encoding="UTF-8"?>

<module xmlns="urn:jboss:module:1.1" name="org.deltaspike">

    <resources>
        <resource-root path="deltaspike-core-api.jar" />
        <resource-root path="deltaspike-core-impl.jar" />
    </resources>

    <dependencies>
        <module name="com.google.guava" />
        <module name="javax.enterprise.api" />
        <module name="javax.inject.api" />
    </dependencies>
</module>

Reflections

Reflections is a very useful library that allows you to find classes that has an annotation in runtime.
Add it as a module to your jboss-as.

<?xml version="1.0" encoding="UTF-8"?>

<module xmlns="urn:jboss:module:1.1" name="org.reflections">

    <resources>
        <resource-root path="reflections.jar" />
    </resources>

    <dependencies>
        <module name="com.google.guava" />
        <module name="org.javassist" />
        <module name="org.slf4j" />
    </dependencies>
</module>

Module extensions

This is the heart of the solution. This module runs when the CDI container starts, and adds relevant beans to the CDI. Make sure this class is part of a JAR file that is inside your EAR file!

This code is greatly influenced from https://rmannibucau.wordpress.com/2013/08/19/adding-legacy-beans-to-cdi-context-a-cdi-extension-sample/

package com.tona.cdi;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.enterprise.event.Observes;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.BeforeBeanDiscovery;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.util.AnnotationLiteral;
import javax.inject.Named;
import javax.inject.Singleton;

import org.apache.deltaspike.core.util.bean.BeanBuilder;
import org.apache.deltaspike.core.util.metadata.builder.AnnotatedTypeBuilder;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Strings;

public class ModuleConfigurationExtension implements Extension {
    private static final Logger log = LoggerFactory.getLogger(ModuleConfigurationExtension.class);
    private final Map<String, AnnotatedType<Object>> beans = new HashMap<>();

    /**
     * This method is automatically activated by CDI, and loads all classes in the com.tona package that has NAMED or
     * SINGLETON annotations.
     * @param bdd
     */
    void readAllConfigurations(final @Observes BeforeBeanDiscovery bdd, BeanManager bm) {
        log.info("Starting to load beans from modules");
        addBeansFromPackage(bdd, bm, "com.tona");
    }

    private void addBeansFromPackage(final BeforeBeanDiscovery bdd, BeanManager bm, String packageName) {
        Reflections reflections = new Reflections(packageName);
        Set<Class<?>> beanClasses = reflections.getTypesAnnotatedWith(Named.class);
        beanClasses.addAll(reflections.getTypesAnnotatedWith(Singleton.class));

        for (Class<?> bean : beanClasses) {
            @SuppressWarnings({ "unchecked", "rawtypes" })
            AnnotatedType<Object> annotatedType = new AnnotatedTypeBuilder().readFromType(bean).create();
            Set<Bean<?>> foundBeans = bm.getBeans(annotatedType.getBaseType(), new AnnotationLiteral<Any>() {
            });

            if (foundBeans.size() == 0) {
                bdd.addAnnotatedType(annotatedType);
                String name;
                Named named = bean.getAnnotation(Named.class);
                if (named == null || Strings.isNullOrEmpty(named.value())) {
                    name = bean.getSimpleName();
                } else {
                    name = named.value();
                }
                beans.put(name, annotatedType);
            }
        }
    }

    /**
     * This method actually initializes the beans we discovered in <code>readAllConfigurations</code>. Again - this
     * method is automatically activated by CDI
     * @param abd
     * @param bm
     * @throws Exception
     */
    public void addCdiBeans(final @Observes AfterBeanDiscovery abd, final BeanManager bm) throws Exception {
        log.info("Starting to initialize beans from modules");

        for (Map.Entry<String, AnnotatedType<Object>> bean : beans.entrySet()) {
            Set<Bean<?>> foundBeans = bm.getBeans(bean.getValue().getBaseType(), new AnnotationLiteral<Any>() {
            });

            if (foundBeans.size() == 0) {
                final Bean<Object> cdiBean = createBean(bm, bean.getKey(), bean.getValue());
                abd.addBean(cdiBean);
                log.debug("Added bean " + cdiBean.getName());
            }
        }
    }

    private static Bean<Object> createBean(final BeanManager bm,
            final String name,
            final AnnotatedType<Object> annotatedType)
            throws Exception {
        final BeanBuilder<Object> beanBuilder = new BeanBuilder<Object>(bm).
                readFromType(annotatedType).
                name(name);

        return beanBuilder.create();
    }
}

Configuring the extension

Create a file called META-INF/services/javax.enterprise.inject.spi.Extension. It should only have the following line:

com.tona.cdi.ModuleConfigurationExtension

Updating your EAR file

The EAR file should have dependencies on the org.deltaspike and org.reflections module. Add it in the MANIFEST.MF file.

Monitoring seda queues with JBoss JMX console

In my current project we’re using camel, and depend heavily on it’s seda technology.
Since we didn’t monitor our queues at first, we encountered OutOfMemory exceptions constantly (usually after ~48 hours of heavy use).
We overcome this by limiting the size of the seda queue (using the size attribute – see here for more info).
But now we face QueueFull exceptions, and need to constantly monitor the queues for their size. Since our application runs on top of JBoss, we can use their JMX API for that. And since I’m a bit lazy – I’ve decided to access it through their HTTP jmx-console.

package com.tona.monitor;

import java.io.ByteArrayOutputStream;
import java.io.FileWriter;
import java.io.InputStream;
import java.net.URL;

public class Main {
	public static void main(String[] args) throws Exception {
		String[] urls = new String[] {
				"http://192.168.155.101:8080/jmx-console/HtmlAdaptor?action=invokeOpByName&name=org.apache.camel%3Acontext%3DacsAdapterCamelContext%2Ctype%3Dendpoints%2Cname%3D%22seda%3A%2F%2FworkflowTriggerManager%5C%3Fsize%3D50000%22&methodName=queueSize",
				"http://192.168.155.101:8080/jmx-console/HtmlAdaptor?action=invokeOpByName&name=org.apache.camel%3Acontext%3DacsAdapterCamelContext%2Ctype%3Dendpoints%2Cname%3D%22seda%3A%2F%2FsyslogAppender%5C%3FconcurrentConsumers%3D4%26timeout%3D5000%22&methodName=queueSize",
		};
		FileWriter fos = new FileWriter("/tmp/queue_log" + System.currentTimeMillis() + ".csv");

		for (String url : urls) {
			System.out.print(getQueueName(url) + ",");
			fos.write(getQueueName(url) + ",");
		}
		System.out.println();
		fos.write("\n");
		
		boolean flag = true;
		
		while (flag) {
			for (String url : urls) {
				URL u = new URL(url);
				InputStream is = u.openStream();
				int i = 0;
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				while ((i = is.read()) > 0) {
					baos.write(i);
				}

				// System.out.println(baos.toString());
				String body = baos.toString();
				int start = body.indexOf("<pre>");
				int end = body.indexOf("</pre>");
				String numOfMessages = body.substring(start + 5, end).trim();
				System.out.print(numOfMessages + ",");
				fos.write(numOfMessages + ",");

			}
			System.out.println();
			fos.write("\n");
			fos.flush();
			Thread.sleep(1000);			
		}
		
		fos.close();

	}
	
	private static String getQueueName(String url) {
		String queueNameStart = "seda%3A%2F%2F";
		String queueNameEnd = "%5C%3";
		
		int queueNameStartPos = url.indexOf(queueNameStart) + queueNameStart.length();
		int queueNameEndPos = url.indexOf(queueNameEnd);
		
		if (queueNameEndPos == -1)
			queueNameEndPos = url.length();
		
		return url.substring(queueNameStartPos,queueNameEndPos);
	}

}

FileDescriptor.sync()

First and foremost – a week ago, I never even knew this method existed in Java. Basically – it let you force the file writing to the disk. Turns out Arjuna (JBoss transactions) is using it in its ShadowStore class, to ensure transaction data is stored to disk. It makes sense – as they want to recover transactions in case of a server crash.
Now, if you read my last post, on the inflation of EJBs, you know that 200 EJBs working together is a mess. And I’ve reached a point where 15% of CPU time of a single transaction is spent on this FileDescriptor.sync() method. Since I couldn’t refactor the whole code – I had to think of another solution. Here goes.

I’ve written a new class, that extends ShadowStore.

public class TonaStore extends ShadowingStore {
    public TonaStore(ObjectStoreEnvironmentBean objectStoreEnvironmentBean) throws ObjectStoreException
    {
        super(objectStoreEnvironmentBean);
    	syncWrites = false;
    }
}

I deployed it to a JAR file, and placed it in the server/all/lib directory.

Now, I opened the /server/all/deploy/transactions-jboss-beans.xml file, and changed ActionStore section to the following:

    <bean name="ActionStoreObjectStoreEnvironmentBean" class="com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean">

        <annotation>@org.jboss.aop.microcontainer.aspects.jmx.JMX(name="jboss.jta:name=ActionStoreObjectStoreEnvironmentBean", exposedInterface=com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBeanMBean.class, registerDirectly=true)</annotation>
        
        <constructor factoryClass="com.arjuna.common.internal.util.propertyservice.BeanPopulator" factoryMethod="getNamedInstance">
            <parameter>com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean</parameter>
            <parameter>default</parameter>
        </constructor>
        <property name="objectStoreDir">${jboss.server.data.dir}/tx-object-store</property>
	<property name="objectStoreType">com.tona.ts.common.TonaStore</property>
    </bean>

I got almost a 100% increase in hits/second. Sweet.