0

I've built an EJB that will manage my database access. I'm building a web app around it that uses Struts 2.

The problem I'm having is when I deploy the ear, the EntityManager doesn't get injected into my service class (and winds up null and results in NullPointerExceptions).

The weird thing is, it works on JBoss 7.1.1 but not on WebSphere 7.

You'll notice that Struts doesn't inject the EJB, so I've got some intercepter code that does that. My current working theory right now is that the WS7 container can't inject the EntityManager because of Struts for some unknown reason. My next step is to try Spring next, but I'd really like to get this to work if possible.

I've spent a few days searching and trying various things and haven't had any luck. I figured I'd give this a shot. Let me know if I can provide additional information.

<?xml version="1.0" encoding="UTF-8"?>
<persistence    xmlns="http://java.sun.com/xml/ns/persistence"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"
                xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
    <persistence-unit name="JPATestPU" transaction-type="JTA">
        <description>JPATest Persistence Unit</description>
        <jta-data-source>jdbc/Test-DS</jta-data-source>
        <class>org.jaredstevens.jpatest.db.entities.User</class>
        <properties>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
        </properties>
    </persistence-unit>
</persistence>
package org.jaredstevens.jpatest.db.entities;

import java.io.Serializable;
import javax.persistence.*;

@Entity
@Table
public class User implements Serializable {
    private static final long serialVersionUID = -2643583108587251245L;

    private long id;
    private String name;
    private String email;

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE)
    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    @Column(nullable=false)
    public String getName() {
        return this.name;
    }

    public void setName( String name ) {
        this.name = name;
    }

    @Column(nullable=false)
    public String getEmail() {
        return this.email;
    }

    @Column(nullable=false)
    public void setEmail( String email ) {
        this.email= email;
    }
}
package org.jaredstevens.jpatest.db.services;
import java.util.List;

import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.Query;

import org.jaredstevens.jpatest.db.entities.User;
import org.jaredstevens.jpatest.db.interfaces.IUserService;

@Stateless(name="UserService",mappedName="UserService")
@Remote
public class UserService implements IUserService {
    @PersistenceContext(unitName="JPATestPU",type=PersistenceContextType.TRANSACTION)
    private EntityManager em;

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public User getUserById(long userId) {
        User retVal = null;
        if(userId > 0) {
            retVal = (User)this.getEm().find(User.class, userId);
        }
        return retVal;
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public List<User> getUsers() {
        List<User> retVal = null;
        String sql;
        sql = "SELECT u FROM User u ORDER BY u.id ASC";
        Query q = this.getEm().createQuery(sql);
        retVal = (List<User>)q.getResultList();
        return retVal;
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void save(User user) {
        this.getEm().persist(user);
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public boolean remove(long userId) {
        boolean retVal = false;
        if(userId > 0) {
            User user = null;
            user = (User)this.getEm().find(User.class, userId);
            if(user != null) this.getEm().remove(user);
            if(this.getEm().find(User.class, userId) == null) retVal = true;
        }
        return retVal;
    }

    public EntityManager getEm() {
        return em;
    }

    public void setEm(EntityManager em) {
        this.em = em;
    }
}
package org.jaredstevens.jpatest.actions.user;

import javax.ejb.EJB;
import org.jaredstevens.jpatest.db.entities.User;
import org.jaredstevens.jpatest.db.interfaces.IUserService;

import com.opensymphony.xwork2.ActionSupport;

public class UserAction extends ActionSupport {
    @EJB(mappedName="UserService")
    private IUserService userService;

    private static final long serialVersionUID = 1L;
    private String userId;
    private String name;
    private String email;

    private User user;

    public String getUserById() {
        String retVal = ActionSupport.SUCCESS;
        this.setUser(userService.getUserById(Long.parseLong(this.userId)));
        return retVal;
    }

    public String save() {
        String retVal = ActionSupport.SUCCESS;
        User user = new User();
        if(this.getUserId() != null && Long.parseLong(this.getUserId()) > 0) user.setId(Long.parseLong(this.getUserId()));
        user.setName(this.getName());
        user.setEmail(this.getEmail());
        userService.save(user);
        this.setUser(user);
        return retVal;
    }

    public String getUserId() {
        return this.userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getName() {
        return this.name;
    }

    public void setName( String name ) {
        this.name = name;
    }

    public String getEmail() {
        return this.email;
    }

    public void setEmail( String email ) {
        this.email = email;
    }

    public User getUser() {
        return this.user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}
package org.jaredstevens.jpatest.utils;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;

public class EJBAnnotationProcessorInterceptor implements Interceptor {
    private static final long serialVersionUID = 1L;

    public void destroy() {
    }

    public void init() {
    }

    public String intercept(ActionInvocation ai) throws Exception {
        EJBAnnotationProcessor.process(ai.getAction());
        return ai.invoke();
    }
}
package org.jaredstevens.jpatest.utils; 

import java.lang.reflect.Field;

import javax.ejb.EJB;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class EJBAnnotationProcessor {
    public static void process(Object instance)throws Exception{
        Field[] fields = instance.getClass().getDeclaredFields();
        if(fields != null && fields.length > 0){
            EJB ejb;
            for(Field field : fields){
                ejb = field.getAnnotation(EJB.class);
                if(ejb != null){
                    field.setAccessible(true);
                    field.set(instance, EJBAnnotationProcessor.getEJB(ejb.mappedName()));
                }
            }
        }
    }

    private static Object getEJB(String mappedName) {
        Object retVal = null;
        String path = "";
        Context cxt = null;
        String[] paths = {"cell/nodes/virgoNode01/servers/server1/","java:module/"};
        for( int i=0; i < paths.length; ++i )
        {
            try {
                path = paths[i]+mappedName;
                cxt = new InitialContext();
                retVal = cxt.lookup(path);
                if(retVal != null) break;
            } catch (NamingException e) {
                retVal = null;
            }
        }
        return retVal;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
    <constant name="struts.devMode" value="true" />

    <package name="basicstruts2" namespace="/diagnostics" extends="struts-default">
        <interceptors>
            <interceptor name="ejbAnnotationProcessor"
                class="org.jaredstevens.jpatest.utils.EJBAnnotationProcessorInterceptor"/>
            <interceptor-stack name="baseStack">
            <interceptor-ref name="defaultStack"/>
            <interceptor-ref name="ejbAnnotationProcessor"/>
            </interceptor-stack>
        </interceptors>
        <default-interceptor-ref name="baseStack"/>
    </package>

    <package name="restAPI" namespace="/conduit" extends="json-default">
        <interceptors>
            <interceptor name="ejbAnnotationProcessor"
                class="org.jaredstevens.jpatest.utils.EJBAnnotationProcessorInterceptor" />
            <interceptor-stack name="baseStack">
                <interceptor-ref name="defaultStack" />
                <interceptor-ref name="ejbAnnotationProcessor" />
            </interceptor-stack>
        </interceptors>
        <default-interceptor-ref name="baseStack" />

        <action name="UserAction.getUserById"
            class="org.jaredstevens.jpatest.actions.user.UserAction" method="getUserById">
            <result type="json">
                <param name="ignoreHierarchy">false</param>
                <param name="includeProperties">
                    ^user\.id,
                    ^user\.name,
                    ^user\.email
                </param>
            </result>
            <result name="error" type="json" />
        </action>

        <action name="UserAction.save"
            class="org.jaredstevens.jpatest.actions.user.UserAction" method="save">
            <result type="json">
                <param name="ignoreHierarchy">false</param>
                <param name="includeProperties">
                    ^user\.id,
                    ^user\.name,
                    ^user\.email
                </param>
            </result>
            <result name="error" type="json" />
        </action>
    </package>
</struts>

Stack Trace

java.lang.NullPointerException
    org.jaredstevens.jpatest.actions.user.UserAction.save(UserAction.java:38)
    sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:60)
    sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:37)
    java.lang.reflect.Method.invoke(Method.java:611)
    com.opensymphony.xwork2.DefaultActionInvocation.invokeAction(DefaultActionInvocation.java:453)
    com.opensymphony.xwork2.DefaultActionInvocation.invokeActionOnly(DefaultActionInvocation.java:292)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:255)
    org.jaredstevens.jpatest.utils.EJBAnnotationProcessorInterceptor.intercept(EJBAnnotationProcessorInterceptor.java:21)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    org.apache.struts2.interceptor.debugging.DebuggingInterceptor.intercept(DebuggingInterceptor.java:256)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor.doIntercept(DefaultWorkflowInterceptor.java:176)
    com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    com.opensymphony.xwork2.validator.ValidationInterceptor.doIntercept(ValidationInterceptor.java:265)
    org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor.doIntercept(AnnotationValidationInterceptor.java:68)
    com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    com.opensymphony.xwork2.interceptor.ConversionErrorInterceptor.intercept(ConversionErrorInterceptor.java:138)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:211)
    com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:211)
    com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    com.opensymphony.xwork2.interceptor.StaticParametersInterceptor.intercept(StaticParametersInterceptor.java:190)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    org.apache.struts2.interceptor.MultiselectInterceptor.intercept(MultiselectInterceptor.java:75)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    org.apache.struts2.interceptor.CheckboxInterceptor.intercept(CheckboxInterceptor.java:90)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    org.apache.struts2.interceptor.FileUploadInterceptor.intercept(FileUploadInterceptor.java:243)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor.intercept(ModelDrivenInterceptor.java:100)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    com.opensymphony.xwork2.interceptor.ScopedModelDrivenInterceptor.intercept(ScopedModelDrivenInterceptor.java:141)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    com.opensymphony.xwork2.interceptor.ChainingInterceptor.intercept(ChainingInterceptor.java:145)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    com.opensymphony.xwork2.interceptor.PrepareInterceptor.doIntercept(PrepareInterceptor.java:171)
    com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    com.opensymphony.xwork2.interceptor.I18nInterceptor.intercept(I18nInterceptor.java:176)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    org.apache.struts2.interceptor.ServletConfigInterceptor.intercept(ServletConfigInterceptor.java:164)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    com.opensymphony.xwork2.interceptor.AliasInterceptor.intercept(AliasInterceptor.java:192)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor.intercept(ExceptionMappingInterceptor.java:187)
    com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    org.apache.struts2.impl.StrutsActionProxy.execute(StrutsActionProxy.java:54)
    org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:511)
    org.apache.struts2.dispatcher.ng.ExecuteOperations.executeAction(ExecuteOperations.java:77)
    org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:91)
    com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:188)
    com.ibm.ws.webcontainer.filter.WebAppFilterChain.doFilter(WebAppFilterChain.java:116)
    com.ibm.ws.webcontainer.filter.WebAppFilterChain._doFilter(WebAppFilterChain.java:77)
    com.ibm.ws.webcontainer.filter.WebAppFilterManager.doFilter(WebAppFilterManager.java:908)
    com.ibm.ws.webcontainer.filter.WebAppFilterManager.invokeFilters(WebAppFilterManager.java:997)
    com.ibm.ws.webcontainer.extension.DefaultExtensionProcessor.invokeFilters(DefaultExtensionProcessor.java:1062)
    com.ibm.ws.webcontainer.extension.DefaultExtensionProcessor.handleRequest(DefaultExtensionProcessor.java:982)
    com.ibm.ws.webcontainer.webapp.WebApp.handleRequest(WebApp.java:3935)
    com.ibm.ws.webcontainer.webapp.WebGroup.handleRequest(WebGroup.java:276)
    com.ibm.ws.webcontainer.WebContainer.handleRequest(WebContainer.java:931)
    com.ibm.ws.webcontainer.WSWebContainer.handleRequest(WSWebContainer.java:1583)
    com.ibm.ws.webcontainer.channel.WCChannelLink.ready(WCChannelLink.java:186)
    com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.handleDiscrimination(HttpInboundLink.java:452)
    com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.handleNewRequest(HttpInboundLink.java:511)
    com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.processRequest(HttpInboundLink.java:305)
    com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.ready(HttpInboundLink.java:276)
    com.ibm.ws.tcp.channel.impl.NewConnectionInitialReadCallback.sendToDiscriminators(NewConnectionInitialReadCallback.java:214)
    com.ibm.ws.tcp.channel.impl.NewConnectionInitialReadCallback.complete(NewConnectionInitialReadCallback.java:113)
    com.ibm.ws.tcp.channel.impl.AioReadCompletionListener.futureCompleted(AioReadCompletionListener.java:165)
    com.ibm.io.async.AbstractAsyncFuture.invokeCallback(AbstractAsyncFuture.java:217)
    com.ibm.io.async.AsyncChannelFuture.fireCompletionActions(AsyncChannelFuture.java:161)
    com.ibm.io.async.AsyncFuture.completed(AsyncFuture.java:138)
    com.ibm.io.async.ResultHandler.complete(ResultHandler.java:204)
    com.ibm.io.async.ResultHandler.runEventProcessingLoop(ResultHandler.java:775)
    com.ibm.io.async.ResultHandler$2.run(ResultHandler.java:905)
    com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1604)
BikerJared
  • 111
  • 2
  • 8
  • Is the EJB class located in the WAR or in a separate EJB module? Where is persistence.xml located (in the EJB module, in the WAR module, in a jar in ear/lib/)? Can you show a stack trace of the failure? Are there any CWWJP errors in the logs prior to the failure? – Brett Kail Nov 02 '12 at 13:28
  • I have the same exception. NullPointer. But i can say that the entitymanager injection is working. Yet when the persist method is called get a NullPointer. Raised issue in the forum. – siddhesh jog Nov 02 '12 at 13:50
  • I couldn't tell you what the problem is, but I will mention that JBoss AS7 is JEE6 and WebSphere 7 is JEE5, which might be part of the issue. Also, unfortunately, just because something works fine in one app server doesn't mean it will work fine in another. – Steven Benitez Nov 02 '12 at 17:20
  • @StevenBenitez I agree. That's a good place to look. For what its worth, I've got another app that I've put together that doesn't use struts and its injecting properly from what I can tell. – BikerJared Nov 02 '12 at 20:13
  • @bkail The EJB class is located in a separate JAR file that is part of the EAR. When maven builds it, the EJB jar is in the root of the package. The persistence.xml file is located in the EJB in META-INF/persistence.xml. As far as a stack trace - I'm assuming you want the null pointer stack trace? When the app deploys, there aren't any failures. – BikerJared Nov 02 '12 at 20:15
  • @bkail Added the NPE stack trace. – BikerJared Nov 02 '12 at 20:38
  • @bkail Also added Struts interceptor code that injects the EJB for me. The part that does the JNDI lookup has multiple strings that I try so that I can use it on both JBoss and WS7. – BikerJared Nov 02 '12 at 20:49
  • 1
    The top of the stack trace shows that UserAction.save is NPE'ing (i.e., the `@EJB` failed? line numbers not matching up). If the EM were failing to be injected, I would expect a stack trace with an NPE in UserService.save. Am I misreading? – Brett Kail Nov 02 '12 at 21:25
  • You're right! The EJB didn't inject properly. The line numbers don't match up because somebody edited the code to remove some comments, (both I and someone else). I'll check that and see if it magically starts working. I swear though, it seems like I got past this point and was getting a NPE on the entity manager. I'll check it out. – BikerJared Nov 03 '12 at 05:49
  • @bkail Its worth noting, the code I've listed here was created to illustrate the problem I'm having on a project who's code I can't provide. That being said, I configured the EJB in WS7 for this example project so that the JNDI lookup would succeed and all the sudden I'm getting MySQL query errors from OpenJPA. This is encouraging and may have solved my problem. I'll keep you posted. – BikerJared Nov 03 '12 at 19:58
  • @bkail That's what it was. Basically the EJB wasn't injecting for two reasons. 1. I'm testing this on a different machine than production and the server name is hard coded into the JNDI lookup there. I need to find a reliable way of doing the lookup that's server name agnostic. 2. The WS7 server needed me to set up aliases for the EJBs in the application configuration (something that doesn't need to happen on JBoss 7). Once I had taken care of these two problems, it started working. I got stuck on this because the methods in the EJB and in my Service classes are the same for saving (save()). – BikerJared Nov 03 '12 at 20:18

2 Answers2

2

The real problem here was the EJB wasn't injecting. My interceptor code couldn't successfully lookup the EJB in JNDI and was setting my local reference to it to null. Once this was fixed, the EntityManager gets injected and everything is happy again.

The solution to this was two-fold:

  1. The JNDI lookup in my interceptor class was failing because the path I hardcoded was wrong for the server I'm on.
  2. The WS7 configuration needed an alias in the EJB settings for the application so that my interceptor class could find the correct EJB.

To fix #1 - I modified my code. I need a better solution for this though that is server agnostic.

To fix #2:

  1. Log into the console. (http://servername:9080/admin)
  2. Applications->Application Types->Websphere Enterprise Applications.
  3. Click on your application name (in my case, JPATestEAR).
  4. On the right hand side there are links. Click 'EJB JNDI Names' (It should list all of your EJBs).
  5. On the right side, there are radio buttons and form fields where you can enter an alias for each EJB. I used 'JNDI Name For All Interfaces' and entered my EJB name in the field 'UserService'.
  6. Click 'Save' and then save the settings again.

Thanks @bkail for getting me on the right track.

BikerJared
  • 111
  • 2
  • 8
0

If your application is package as WAR this might explain why it does not work on WS7.

As bkail pointed out it might be packaging issue.

Since EE6 you can bundle your local EJB beans as part of your war application but pre-EE6 you need to deploy this as ear and have ejb's packed in ejb-jar.

You might try with Websphere 8+ and it should probably work or repackage your application as ear with ejb-jar module & war module.

Tomaz Cerar
  • 5,761
  • 25
  • 32
  • The EJB and WAR file are packaged inside of the same EAR. My production environment is WS7, so I'm trying to be as close to that as possible. I'll look into the ejb-jar plugin. – BikerJared Nov 03 '12 at 19:56