Monday, March 31, 2014

Seam 2 Gotchas - Part 1

A few common mistakes I've seen made with Seam 2:

Referencing EJB Components Directly

I think it's pretty easy to know that referencing a Seam component that happens to be an EJB directly (with @EJB or with a JNDI lookup) is probably not going to end well, but a novice Seam developer might make this mistake.  This mistake is more likely when writing code in a non-JSF part of the system (e.g. a Servlet).

Here's what you can do about it:
  1. Make sure EJB Seam components are injected using @In, or look them up using Seam.   Make sure you have a clear distinction between 'regular' EJBs and Seam component EJBs.  Here are the two main things to avoid:    
    • INJECTING SEAM COMPONENTS WITH @EJB - Use @In instead!
    • LOOKING UP SEAM COMPONENTS WITH JNDI - Use Component.getInstance(name) or  Contexts.lookupInStatefulContexts(name) instead!
  2. In a non-JSF environment, use ContextualHttpServletRequest, for example, in a Servlet:
        @Override
        protected void service(HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
        {
            //... do some stuff...
            new ContextualHttpServletRequest(request) 
            {
                @Override
                public void process() throws Exception
                {
                    // Access the components, do work.   The contexts will be properly set up here.
                    MyComponent component = (MyComponent)Component.getInstance("myComponent");
                }
            }.run();    // Run the request.
     
        }
    
    See this JBoss community post.   Personally, I strongly prefer using ContextFilter.
  3. In a non-JSF environment, apply the ContextFilter.   This will automatically wrap all requests in ContextualHttpServletRequest().

    For example:  <web:context-filter url-pattern="/servlet/*"/> in components.xml.
Note that when using ContextFilter or ContextualHttpServletRequest, exceptions may be handled differently than you might expect!

If anything inside the ContextFilter/ContextualHttpServletRequest throws an exception, then all the contexts will be torn down.   You may get other filters throwing java.lang.IllegalStateException: No active event context! after the ContextFilter/ContextualHttpServletRequest has finished!

Component Lookup - Component is not automatically created?

While injecting Seam components with @In is the simplest way to access another component, there are cases where a lookup is needed (e.g. in a Servlet).   The problem is, there is more than one way to look up components, and the method used to look up the component will determine the behavior:

  1. Contexts.lookupInStatefulContexts(name) - This is similar to @In : It will not create components automatically!
  2. Component.getInstance(name) - This is similar to @In(autocreate=true) : Components will be created automatically if they don't exist.
 Make sure you use the appropriate method for your use case.

Injected values are null?!?

If you are used to other DI frameworks, you may be expecting injected values to stick around.   Not always the case with Seam :

Seam will set injected fields to null when the request is finished.  Injection / uninjection happen before and after every method invocation.

So, if you access an instance outside of Seam's control, then the injected values might be null.


Tuesday, March 25, 2014

JBoss AS 7 and SLF4J

As side project at work, I'm porting a Java Enterprise 5 Seam 2 application to JBoss AS 7 (7.2, to be precise).   This application uses SLF4J for logging, and I quickly realized that without some careful configuration, the SLF4J log messages can get discarded.   That's not so great when trying to troubleshoot deployment problems!

(Side note: to post XML inside a <pre> tag on Blogger, use an HTML Encoder like this one)

Anyway, here's what I ended up doing to get it to work:
  1. Don't use the provided SLF4J module from the container.   This will allow the application to use it's own version of SLF4J and logging implementation (e.g. Log4J).   I did this by adding the following exclusions to jboss-deployment-structure.xml (like this):
            <exclusions>
                ... other modules ...
                <module name="org.apache.log4j" />
                <module name="org.slf4j" />
                <module name="org.slf4j.impl" />
            </exclusions>
    
    • Make sure to exclude the implementation org.slf4j.impl as well, otherwise the app server will supply it's own. 
    • For EAR deployments, this needs to be repeated in the sub-deployment for the WAR as well.   See this JBoss community post.

  2. Include the slf4j-api, and slf4j implementation jars (e.g. slf4j-log4j12 and log4j) in the lib directory of the EAR. In my case, this is just making sure that the Maven module for the EAR doesn't exclude these. Verify by locating the files in the target EAR.

    In the EAR pom.xml, I added the following dependencies:

           <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
                <scope>runtime</scope> 
            </dependency>
    
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
            </dependency>
    

    In this case, the versions are specified in a dependency management pom.xml.   Also, you may need to change the scope to 'runtime' if the scope is set in the dependency management (to something else, like 'test').
  3. Put your log implementation config files where the implementation can see them.   For Lo4J, you can make a jar with log4j.xml in it, and put this in the lib directory of the ear.

Troubleshooting

Various things I encountered while setting this up...

No SLF4J Implementation


If you manage to exclude the SLF4J implementation, but the EAR doesn't contain one you may get this:

ERROR [stderr] (ServerService Thread Pool -- 65) SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
ERROR [stderr] (ServerService Thread Pool -- 65) SLF4J: Defaulting to no-operation (NOP) logger implementation
ERROR [stderr] (ServerService Thread Pool -- 65) SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

In this case, just make sure the desired SLF4J implementation class is in the EAR lib directory. For example, add it as a dependency in your pom.xml.

JBoss-Specific Logging Configuration


I had an old log4j.xml that had some references to some older JBoss-specific logging features like this:

    <category name="javax">
        <priority value="INFO" class="org.jboss.logging.log4j.JDKLevel"/>
    </category>

These references caused ClassNotFoundExeptions when Log4J was initializing. To resolve this, I simply commented out these elements.

Also, I replaced org.jboss.logging.appender.RollingFileAppender with org.apache.log4j.RollingFileAppender.