gandalfml
gandalfml

Reputation: 908

HttpSession CDI injection in servlet project

I have problem with my project for studies. I have simple project with one servlet and I need to have some CDI beans with diferent scopes. This part was realy simple, but I have to be able to inject HttpSession into each of me CDI beans. To resolve this problem I made ServletRequestListener to get HttpServletRequest object, I store this object in application scoped bean in ThreadLocal object and in this bean I have producer method for HttpSession object from stored HttpServletRequest. After that I'm able to inject HttpSession in any CDI bean except session scoped beans. Session is properly injected to that bean after session initialization, but for second request in the same session I have null pointer exception, because session bean is created(or deserialized) before RequestInitialized method and my producer return null value, which is illegal accoprding to stacktrace.

Here is the stacktrace from second request in one session:

    org.jboss.weld.exceptions.IllegalProductException: WELD-000052 Cannot return null from a non-dependent producer method:  [method] @Produces @RequestScoped public pl.lab2.cdi.producers.SessionObjectsProducer.getSession()
    org.jboss.weld.bean.AbstractProducerBean.checkReturnValue(AbstractProducerBean.java:217)
    org.jboss.weld.bean.AbstractProducerBean.create(AbstractProducerBean.java:300)
    org.jboss.weld.context.AbstractContext.get(AbstractContext.java:107)
    org.jboss.weld.bean.proxy.ContextBeanInstance.getInstance(ContextBeanInstance.java:90)
    org.jboss.weld.bean.proxy.ProxyMethodHandler.invoke(ProxyMethodHandler.java:104)
    org.jboss.weld.proxies.HttpSession$776413422$Proxy$_$$_WeldClientProxy.getId(HttpSession$776413422$Proxy$_$$_WeldClientProxy.java)
    pl.lab2.bean.SessionBean.toString(SessionBean.java:31)
    java.lang.String.valueOf(String.java:2854)
    java.lang.StringBuilder.append(StringBuilder.java:128)
    org.jboss.weld.context.SerializableContextualInstanceImpl.toString(SerializableContextualInstanceImpl.java:60)
    java.lang.String.valueOf(String.java:2854)
    java.lang.StringBuilder.append(StringBuilder.java:128)
    org.jboss.weld.context.beanstore.AttributeBeanStore.attach(AttributeBeanStore.java:109)
    org.jboss.weld.context.AbstractBoundContext.activate(AbstractBoundContext.java:66)
    org.jboss.weld.servlet.WeldListener.requestInitialized(WeldListener.java:141)
    org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
    org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:368)
    org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:877)
    org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:671)
    org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:930)
    java.lang.Thread.run(Thread.java:724)

And sources:

Listener

package pl.lab2.servlet;

import org.apache.log4j.Logger;
import pl.lab2.cdi.BeanManagerHelper;
import pl.lab2.servlet.events.literal.*;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;

public class ServletListener implements ServletRequestListener {
    private static final Logger log = Logger.getLogger(ServletListener.class);

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        log.info("request destroyed event");
        BeanManagerHelper.getBeanManagerByJNDI().fireEvent((HttpServletRequest) sre.getServletRequest(), DestroyedLiteral.INSTANCE);
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        log.info("request initialized event");
        BeanManagerHelper.getBeanManagerByJNDI().fireEvent((HttpServletRequest) sre.getServletRequest(), InitializedLiteral.INSTANCE);
    }
}

Holder

package pl.lab2.servlet;

import org.apache.log4j.Logger;
import pl.lab2.servlet.events.Destroyed;
import pl.lab2.servlet.events.Initialized;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@ApplicationScoped
public class ServletObjectHolder {
    private static final Logger log = Logger.getLogger(ServletObjectHolder.class);
    private final ThreadLocal<HttpServletRequest> threadRequest = new ThreadLocal<HttpServletRequest>();

    public HttpSession getSession() {
        log.info("get session");
        if (threadRequest.get() != null) {
            return threadRequest.get().getSession();
        }
        return null;
    }

    public void servletRequestInitialized(@Observes @Initialized final HttpServletRequest request) {
        log.info("receive request initialization");
        threadRequest.set(request);
    }

    public void servletRequestDestroyed(@Observes @Destroyed final HttpServletRequest request) {
        log.info("receive request destroyed");
        threadRequest.set(null);
    }
}

Producer

package pl.lab2.cdi.producers;

import org.apache.log4j.Logger;
import pl.lab2.servlet.ServletObjectHolder;

import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
import javax.servlet.http.HttpSession;
import java.io.Serializable;

public class SessionObjectsProducer implements Serializable {
    private static final Logger log = Logger.getLogger(SessionObjectsProducer.class);

    @Inject
    private ServletObjectHolder servletObjectHolder;

    @Produces
    @RequestScoped
    public HttpSession getSession() {
        log.info("get session");
        return servletObjectHolder.getSession();
    }
}

Session bean

package pl.lab2.bean;

import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpSession;
import java.io.Serializable;

@SessionScoped
@Named
public class SessionBean implements Serializable {
    private String name;
    @Inject
    private HttpSession session;

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "SessionBean{" +
                "name='" + name + "', " +
                "sessionId='" + session.getId() + "'" +
                '}';
    }
}

And servlet:

package pl.lab2.servlet;

import org.apache.log4j.Logger;
import org.jboss.weld.context.ConversationContext;
import org.jboss.weld.context.http.Http;
import pl.lab2.bean.ApplicationBean;
import pl.lab2.bean.ConversationBean;
import pl.lab2.bean.RequestBean;
import pl.lab2.bean.SessionBean;
import pl.lab2.cdi.producers.SessionObjectsProducer;

import javax.enterprise.context.Conversation;
import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class ServletDispatcher extends HttpServlet {
    private static final Logger log = Logger.getLogger(ServletDispatcher.class);
    @Inject
    private ApplicationBean applicationBean;
    @Inject
    private SessionBean sessionBean;
    @Inject
    private ConversationBean conversationBean;
    @Inject
    private RequestBean requestBean;
    @Inject
    private Conversation conversation;
    @Inject
    @Http
    private ConversationContext conversationContext;
    @Inject
    private SessionObjectsProducer sessionObjectsProducer;

    @Override
    public void init() throws ServletException {
        super.init();
        conversationContext.setParameterName("cId");
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {
        this.request(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {
        this.request(request, response);
    }

    private void request(HttpServletRequest request, HttpServletResponse response) throws IOException {
        log.info("request started in session " + request.getSession().getId());
        String cid = request.getParameter(conversationContext.getParameterName());
        if (cid != null) {
            conversationContext.activate(cid);
        } else {
            conversationContext.activate();
        }

        takeActions(request);
        updateData(request);
        printState(response.getWriter(), request);
    }

    private void printState(PrintWriter writer, HttpServletRequest request) {
        writer.print("<div>");
        writer.print("<div>Beans:</div>");
        writer.print(applicationBean.toString() + "<br />");
        writer.print(sessionBean.toString() + "<br />");
        writer.print(conversationBean.toString() + "<br />");
        writer.print(requestBean.toString() + "<br />");
        writer.print("</div>");
        writer.print("<div>");
        writer.print("<div>Data:</div>");
        writer.print("session id: " + request.getSession().getId() + "<br />");
        writer.print("conversation id: " + conversation.getId() + "<br />");
        writer.print("</div>");
    }

    private void takeActions(HttpServletRequest request) {
        if ("begin".equals(request.getParameter("conversationState"))) conversation.begin();
        else if ("end".equals(request.getParameter("conversationState"))) conversation.end();
    }

    private void updateData(HttpServletRequest request) {
        if (request.getParameter("application") != null) {
            applicationBean.setName(request.getParameter("application"));
        }
        if (request.getParameter("session") != null) {
            sessionBean.setName(request.getParameter("session"));
        }
        if (request.getParameter("conversation") != null) {
            conversationBean.setName(request.getParameter("conversation"));
        }
        if (request.getParameter("request") != null) {
            requestBean.setName(request.getParameter("request"));
        }
    }
}

To done it I use seam/servlet sources form github as example.

I've uploaded my current codes to dropbox just build, deploy on JBoss as 7.1.1.Final, go to localhost:8080/lab2, hit F5 twice and you will see the problem.

Upvotes: 0

Views: 6201

Answers (3)

cyril
cyril

Reputation: 1005

Use JSF, indeed you will have acces to your httpsession with FacesContext...

we have not all your configuration and here i don't see the purpose of this configuration no one use servlet like that and you can acess to the http session via the request parameter not with injection...

Upvotes: 0

gandalfml
gandalfml

Reputation: 908

I figured out that the problem was in my toString method in session scoped bean. In this method I tried to get session id, but for some reasons(logging I think) cdi calls toString method before request initialization. When I removed access to session object from toString method, everything works fine.

Upvotes: 1

rdcrng
rdcrng

Reputation: 3443

Something as simple the following will do what you want and work like a charm:

import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Produces;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

// NOTE: this produces a request scoped session object because that's what the OP seems to want
@WebListener
public class SessionProducer implements ServletRequestListener {

    private static ThreadLocal<HttpSession> SESSIONS = new ThreadLocal<>();

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        SESSIONS.remove();
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        SESSIONS.set(HttpServletRequest.class.cast(sre.getServletRequest()).getSession());
    }

    @Produces @RequestScoped
    protected HttpSession getSession() {
        return SESSIONS.get();
    }

}

Enjoy!

Upvotes: 1

Related Questions