Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dropwizard & Hibernate - No session currently bound to execution context

I think I have a fair idea what my problem is here, but have absolutely NO idea how I might fix it...

Here is how I am starting my application in dropwizard :

@Override
public void run(ServerConfiguration configuration, Environment environment)
{

    // Setting up the database.
    final DBIFactory factory = new DBIFactory();
    final DBI jdbi = factory.build(environment, configuration.getDataSourceFactory(), "mysql");

    //Hibernate
    final UserDAO dao = new UserDAO(hibernate.getSessionFactory());
    environment.jersey().register(new UserResource(dao));

    final TemplateHealthCheck healthCheck = new TemplateHealthCheck(configuration.getTemplate());

    environment.healthChecks().register("template", healthCheck);

    // security
    //****** Dropwizard security - custom classes ***********/
    environment.jersey().register(new AuthDynamicFeature(new BasicCredentialAuthFilter.Builder<User>()
            .setAuthenticator(new BasicAuth(dao))
            .setAuthorizer(new BasicAuthorizer())
            .setRealm("BASIC-AUTH-REALM")
            .buildAuthFilter()));
    environment.jersey().register(RolesAllowedDynamicFeature.class);
    environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
}

Now, as you can see here, I am passing my User dao into my authenticator... No tutorial I have seen online does this, and this is because every tutorial online uses hardcoded values instead of showing how to query the DB.

That said, here is how I am trying to authenticate...

public class BasicAuth implements Authenticator<BasicCredentials, User> {

UserDAO _userDAO;
final Encryption enc = new Encryption();

public BasicAuth(UserDAO dao)
{
    this._userDAO = dao;
}

@Override
public Optional<User> authenticate(BasicCredentials credentials)
        throws AuthenticationException {

    // Get the user record.
    User requestedUser = _userDAO.findOneByUsername(credentials.getUsername());

    if (requestedUser != null)
    {
        // check pw.
        if(enc.compare(credentials.getPassword(), requestedUser.getPassword())) {
            return Optional.of(requestedUser);
        }
        else {
            return Optional.empty();
        }
    }
    return Optional.empty();
}
}

please excuse the terrible indentation above, I pasted my code here from intelliJ and it just isn't behaving well - anyways, when I try to run this application, the authenticator tells me :

No session currently bound to execution context

Here is the kicker, I know this is only for the security aspect of this that I am getting this error, because if I remove the security lines from the Application class, and run it up, I can still hit my create user endpoints (which also use this DAO) and that works well.

So my question here really, is - Am I meant to use that dao in the authenticator? If not, how the hell am I meant to query the database?

If I am, then where am I going wrong?

Thanks in advance.

like image 1000
MickeyThreeSheds Avatar asked Feb 22 '17 06:02

MickeyThreeSheds


People also ask

What is Dropwizard used for?

What is Dropwizard? Dropwizard is an open source Java framework for developing ops-friendly, high-performance RESTful backends. It was developed by Yammer to power their JVM based backend. Dropwizard provides best-of-breed Java libraries into one embedded application package.

Which is better spring boot vs Dropwizard?

The bare bone spring boot app starts in 1.64 seconds whereas the bare bone Dropwizard app took 1.526 seconds to startup. Spring Boot consumes much more memory. This was true. Spring Boot loaded 7591 classes whereas Dropwizard loaded 6255 classes.

What is Dropwizard in Maven?

Dropwizard is an open-source Java framework used for the fast development of high-performance RESTful web services. It gathers some popular libraries to create the light-weight package. The main libraries that it uses are Jetty, Jersey, Jackson, JUnit, and Guava. Furthermore, it uses its own library called Metrics.

Does Dropwizard use Tomcat?

Instead of deploying your applications to an application server or web server, Dropwizard defines a main method that invokes the Jetty server as a standalone process. As of now, Dropwizard recommends only running the application with Jetty; other web services like Tomcat are not officially supported.


1 Answers

First, your issue:

From the DW documentation:

Currently creating transactions with the @UnitOfWork annotation works out-of-box only for resources managed by Jersey. If you want to use it outside Jersey resources, e.g. in authenticators, you should instantiate your class with UnitOfWorkAwareProxyFactory.

With your code, you create an Authenticator but you never hook that back to the Hibernate Session. How would the Authenticator know when to open a new Session for the DAO to operate on? This is done by the UnitOfWork mechanism. This however only currently works for the Jersey resources and needs to be enabled for any other class that wants to participate in this.

So, fortunately the Docs gives us an exact Authenticator example on here: http://www.dropwizard.io/1.0.6/docs/manual/hibernate.html

I am not going to copy their code as I have a standalone example for you to play with:

 public class HibernateTest extends io.dropwizard.Application<DBConfiguration> {

    private final HibernateBundle<DBConfiguration> hibernate = new HibernateBundle<DBConfiguration>(Object.class) {
        @Override
        public DataSourceFactory getDataSourceFactory(DBConfiguration configuration) {
            return configuration.getDataSourceFactory();
        }
    };

    @Override
    public void initialize(Bootstrap<DBConfiguration> bootstrap) {
        super.initialize(bootstrap);
        bootstrap.addBundle(hibernate);
    }

    @Override
    public void run(DBConfiguration configuration, Environment environment) throws Exception {
        MyDao dao = new MyDao(hibernate.getSessionFactory());
        environment.jersey().register(new MyHelloResource(dao));


        // THIS IS ABSOLUTELY CRITICAL
        MyAuthenticator proxyAuth = new UnitOfWorkAwareProxyFactory(hibernate).create(MyAuthenticator.class, MyDao.class, dao);

        AuthDynamicFeature authDynamicFeature = new AuthDynamicFeature(
                new BasicCredentialAuthFilter.Builder<Principal>()
                    .setAuthenticator(proxyAuth)
                    .setRealm("SUPER SECRET STUFF")
                    .buildAuthFilter());

        environment.jersey().register(authDynamicFeature);
    }

    public static void main(String[] args) throws Exception {
        new HibernateTest().run("server", "/home/artur/dev/repo/sandbox/src/main/resources/config/db.yaml");
    }

    @Path("test")
    @Produces(MediaType.APPLICATION_JSON)
    public static class MyHelloResource {

        private MyDao dao;

        public MyHelloResource(MyDao dao) {
            this.dao = dao;
        }

        @GET
        @Path("/test")
        @UnitOfWork
        @PermitAll
        public Response downloadFile() throws Exception {
            dao.get();
            return Response.ok().build();
        }

    }

    public static class MyAuthenticator implements Authenticator<BasicCredentials, Principal> {
        private MyDao dao;

        MyAuthenticator(MyDao dao) {
            this.dao = dao;
        }

        @Override
        @UnitOfWork
        public Optional<Principal> authenticate(BasicCredentials credentials) throws AuthenticationException {
            dao.get(); 
            return Optional.empty();
        }
    }

    public static class MyDao extends AbstractDAO<Object> {

        public MyDao(SessionFactory sessionFactory) {
            super(sessionFactory);
        }

        public Object get() {
            // if not bridged to Jersey this will throw an exception due to session
            currentSession().createSQLQuery("SELECT 1;").uniqueResult();
            return new Object();
        }
    }

}

The above code runs a minimal DW application with an h2 db setup in memory. You will have to apply your configuration changes so that it starts (and change the server configuration file)

What this does is:

  1. Create the hibernate bundle supplying the DataSourceFactory
  2. Create the DAO
  3. Create the Authenticator as a proxy
  4. Wire all of it together

The important bits are:

MyAuthenticator proxyAuth = new UnitOfWorkAwareProxyFactory(hibernate).create(MyAuthenticator.class, MyDao.class, dao);

This creates a proxy for you that is aware of the UnitOfWork annotation. It enables the Authenticator to hook into the (I believe) event system that will open and close sessions on request.

You then use that proxy in your AuthDynamicFeature

Finally, in your Authenticator you must tell it to open a new Session when the authentication is executed, such as:

@Override
        @UnitOfWork
        public Optional<Principal> authenticate(BasicCredentials credentials) throws AuthenticationException {
            dao.get(); 
            return Optional.empty();
        }

Now all of this will work without an exception:

curl user:pass@localhost:9085/api/test/test 
Credentials are required to access this resource.

As for your last question:

I'm actually more fluent with spring do you think it would be a good idea to switch instead of dealing with this constantly?

I think this is mostly opinion based, but: Spring DI != Jersey DI. You essentially do the same thing with Spring, you bridge Jersey DI with Spring DI such that jersey can access these beans in Spring. However, all the session logic is still handled the same way. Spring simply takes the explicit abstraction away afaik - it creates the proxies for you already when creating the bean. So I don't think you will have many advantages with this and from personal experience, bridging Spring and Jersey is not all that easy. The dependency (spring-jersey-bridge) jersey advertises does not work with embedded Jetties (such as DW setups) but rather by hooking into a Servlet at startup (which you don't have). This can still work, however it requires a bit of hacking to get that setup. In my experience, guice (e.g. https://github.com/xvik/dropwizard-guicey) integrates much easier and nicer into DW and will give you the same advantages. Guice obviously does not do all the things that Spring does (neither does Spring do all the things that Guice does) so you may need to do your own research.

I hope this clears things up and gets you started :)

Regards,

Artur

like image 111
pandaadb Avatar answered Sep 26 '22 22:09

pandaadb