4

I'm developing little app and have some issues with DI.

I have a repository class for persisting my entities which I inject to my service. And I'd like to inject Session object to it using H2K. For this purpose I try do something similar described in following SO posts:

  1. Jersey + HK2 + Grizzly: Proper way to inject EntityManager?
  2. Using Jersey 2.0, how do you register a bindable instance per request?
  3. How do I properly configure an EntityManager in a jersey / hk2 application?

So I created SFFactory class and register it in ApplicationConfig.

SFFactory.java

import org.glassfish.hk2.api.Factory;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;

public class SFFactory implements Factory<Session> {

private SessionFactory factory;

public SFFactory() {
    Configuration configuration = new Configuration();
    configuration.configure("hibernate.cfg.xml");
    StandardServiceRegistryBuilder srBuilder = new StandardServiceRegistryBuilder();
    srBuilder.applySettings(configuration.getProperties());
    factory = configuration.buildSessionFactory(srBuilder.build());
    }

@Override
public Session provide() {
    return factory.openSession();
    }

@Override
public void dispose(Session session) {
    if (session.isOpen()) {
        session.close();
        }
    }
}

ApplicationConfig.java

import org.alexdzot.phonettesttask.repository.MessageRepository;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import org.hibernate.Session;

import javax.inject.Singleton;
import javax.ws.rs.ApplicationPath;

@ApplicationPath("/rest/*")
public class ApplicationConfig extends ResourceConfig {

public ApplicationConfig() {
    packages("org.alexdzot.phonettesttask");
    register(new AbstractBinder() {
        @Override
        protected void configure() {
            bindFactory(SFFactory.class).to(Session.class);
            bindFactory(MessageRepositoryFactory.class).to(MessageRepository.class).in(Singleton.class);
            }
        });
    }
}

DefaultMessageRepository

import org.alexdzot.phonettesttask.model.Message;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.jvnet.hk2.annotations.Service;

import javax.inject.Inject;
import java.util.List;

@Service
public class DefaultMessageRepository implements MessageRepository {

@Inject
private Session session;

public void saveMessage(Message message) {
    Session session = null;
    Transaction tx = null;
    try {
        tx = session.beginTransaction();
        session.save(message);
        tx.commit();
        } catch (Exception e) {
        tx.rollback();
        } finally {
        session.close();
        }
    }

public List<Message> getAllMessages() {
    Session session = null;
    Transaction tx = null;
    List<Message> messages = null;
    try {
        tx = session.beginTransaction();
        messages = session.createCriteria(Message.class).list();

        } catch (Exception e) {
        tx.rollback();
        } finally {
        session.close();
        return messages;
        }
    }
}

But when I run the app and trying to call repository methods I get NullPoinerException. I can't understand what am I doing wrong. This is what Tomcat log is saying:

*08-Aug-2015 02:42:18.232 SEVERE [http-nio-8080-exec-10] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [org.alexdzot.phonettesttask.config.ApplicationConfig] in context with path [] threw exception [java.lang.NullPointerException] with root cause
 java.lang.NullPointerException
    at org.alexdzot.phonettesttask.repository.DefaultMessageRepository.getAllMessages(DefaultMessageRepository.java:42)
    at org.alexdzot.phonettesttask.service.MessageResource.viewSentMessages(MessageResource.java:38)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory$1.invoke(ResourceMethodInvocationHandlerFactory.java:81)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:144)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:161)
    at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$TypeOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:205)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:99)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:389)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:347)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:102)
    at org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:308)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317)
    at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:291)
    at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1140)
    at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:403)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:386)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:334)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:221)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:617)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:668)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1521)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1478)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)*

Any ideas what wrong with my solution and what causes that problem?

Community
  • 1
  • 1
alexdzot
  • 175
  • 14

1 Answers1

5

Main problem:

[java.lang.NullPointerException]

Look at your repository class

@Service
public class DefaultMessageRepository implements MessageRepository {

    @Inject
    private Session session; <-------------------+
                                                 | // You shadow session
    public void saveMessage(Message message) {   |
        Session session = null;   <--------------+
        Transaction tx = null;
        try {
            tx = session.beginTransaction();
        ...
    }
    ...
}

You are shadowing the injected field session. So you are just using the local session, which is null. So in your method, just get rid of Session session = null;. The injection should work fine, and you should be able to just use the session field.

Another Problem:

The same Session is being used for all request. Since DefaultMessagFactory is a singleton, HK2 asserts that Session should also be a singleton, since it is contained within a singleton. So instead of making the SFFactory the default @PerLookup scope, you get a factory that is a singleton, and so only one Session will be created. This is not the behavior you should want. You can test this by putting some print statements inside your factory class.

One way to make sure that a new Session is created for each request is to:

  1. Use javax.inject.Provider<Session>, to lazily load the session, which will allow use to keep it in the scope we configured.

    @Inject
    private javax.inject.Provider<Session> session;
    
    @Override
    public void saveEvent(Event event) {
        Session s = session.get();
        Transaction tx = s.beginTransaction(); 
    
  2. Configure the SFFactory in a request scope

    @Override
    protected void configure() {
        bindFactory(SFFactory.class)
                .to(Session.class)
                .in(RequestScoped.class);
    }
    

The other thing to fix is that SessionFactory should be a single instance through the application. For that we can create a single factory for it, and inject it into the SFFactory. For example

public class SessionFactoryFactory implements Factory<SessionFactory> {

    private final SessionFactory factory;

    public SessionFactoryFactory() {
        Configuration configuration = new Configuration();
        configuration.configure("hibernate.cfg.xml");
        StandardServiceRegistryBuilder srBuilder = new StandardServiceRegistryBuilder();
        srBuilder.applySettings(configuration.getProperties());
        factory = configuration.buildSessionFactory(srBuilder.build());
        System.out.println("--- SessionFactory Created ---");
    }

    @Override
    public SessionFactory provide() {
        System.out.println("--- SessionFactory Provided ---");
        return factory;
    }

    @Override
    public void dispose(SessionFactory factory) {
        factory.close();
    }
}

The in your SFFactory

public class SFFactory implements Factory<Session> {

    private final SessionFactory factory;

    @Inject
    public SFFactory(SessionFactory factory) {
        this.factory = factory;
    }

    @Override
    public Session provide() {
        System.out.println("--- Session Created ---");
        return factory.openSession();
    }

    @Override
    public void dispose(Session session) {
        if (session.isOpen()) {
            session.close();
        }
    }
}

You configuration would change to look like

@Override
protected void configure() {
    bindFactory(SessionFactoryFactory.class)
            .to(SessionFactory.class)
            .in(Singleton.class);
    bindFactory(SFFactory.class)
            .to(Session.class)
            .in(RequestScoped.class);
}

UPDATE

So it seems aside from the shadowing, another main problem was in the MessageRepositoryFactory that the OP didn't show (in github project from comments).

public class MessageRepositoryFactory implements Factory<MessageRepository> {
    @Override
    public MessageRepository provider() {
        return new DefaultMessageRepository();
    }
    ...
}

When you instantiate the object your self, by default, the framework doesn't handle the injection. So that's why the injection of the Session didn't happen.

Instead of using the a Factory like this, you could simply bind like this

bind(DefaultMessageRepository.class).to(MessageRepository.class).in(..);

This way the framework creates the instance. The Factory should really only be used where it is not possible to bind the above way, for instance you have some initializations to do. We could have even not created the Factory for the SessionFactoryFactory and instead did all the session factory configuration elsewhere and just binded the instance. For example

SessionFactory sessionFactory =...
...
bind(sessionFactory).to(SessionFactory.class);

We just bind a SessionFactory singleton.

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • Thank you for you time and help! But when I did what you suggested I get the same result - NullPointerException. – alexdzot Aug 08 '15 at 07:20
  • What line (piece of code) is the NullPointerException pointing to? I've tested this and it works fine. Just removing the method local `Session session` declaration allows it to work fine. The request of this answer is an improvement. Dd you try it with just removing the Session declaration inside the method? – Paul Samsotha Aug 08 '15 at 07:24
  • I added System.out.println() statements to SFFactroy and SessionFactoryFactory and it seems that provide method is never called. – alexdzot Aug 08 '15 at 07:29
  • Check that your Hibernate config is good (no exceptions). Test on a standalone main method. If all is good, Making the all the fixes mentioned above has been tested to work. I don't know what else to tell you from there. I removed the repository binding for brevity, but it should still be there. Maybe that's missing. I don't know – Paul Samsotha Aug 08 '15 at 07:36
  • If I inject Provider to get Session inside repository it points to line with provider.get() . If I inject Session object directly it points to line where I call save() or create criteria for getting all results from database. – alexdzot Aug 08 '15 at 07:41
  • I did not remove repository binding. hibernate config file looks good. If I call utility class to get Session inside repository everything is OK. – alexdzot Aug 08 '15 at 07:45
  • Give me a few minutes and I will post a working project to github. I'm eating right now, so give me maybe 30 minutes – Paul Samsotha Aug 08 '15 at 07:46
  • [Here's mine](https://github.com/psamsotha/stackoverflow-jersey-hibernate). I'll check your out a bit later. – Paul Samsotha Aug 08 '15 at 08:32
  • Ahh I think I spot the problem. I didn't notice you were using a factory for the repository. When you instantiate the repository yourself, the injection doesn't occur. You don't need to factory, just use `bind().to().in(..)`. Notice It's not using your factory. You just let the framework create the instance – Paul Samsotha Aug 08 '15 at 08:40
  • 1
    Thank you! That's was the real problem. Now everything works as I expect. – alexdzot Aug 08 '15 at 08:51
  • One thing I wasn't quite sure about during my testing was the fact that `dispose` is never called. I don't know if this leads to a resource leak or what. I just thought about it, reading one of the posts you linked to. You may want to do some profiling. Also maybe look into using `getCurrentSession` instead of `openSession` AFAIK, using `getCurrentSession` uses thread locals and you may not need to explicitly close them. I am not 100% sure. You may want to look into this further. – Paul Samsotha Aug 08 '15 at 09:26