Search code examples
javajerseyjersey-2.0

Close database connections automatically with Jersey CDI?


I would like to inject an object which implements AutoClosable to a jersey resource and have it automatically closed by the framework at the end of the request.
Basically this is a database connection provider and open connections should be closed at the end of the request.
When using the @inject annotation the object is not closed.
Is this possible, and how?


Solution

  • You can use the CloseableService

    The service may be used within the scope of a request to add instances of Closeable that are to be closed when the request goes out of scope, more specifically after the request has been processed and the response has been returned.

    You can inject this service into a Factory where you can create your your provider. Just add the provider to the CloseableService inside the Factory, and it will be closed at the end of the request.

    public class ConnectionProviderFactory implements Factory<ConnectionProvider> {
    
        @Inject
        private CloseableService closeableService;
    
        @Override
        public ConnectionProvider provider() {
            ConnectionProvider provider = new ConnectionProvider();
            closeableService.add(provider);
            return provider;
        }
    
        @Override
        public void dispose(ConnectionProvider provider) {
        }
    }
    

    Then just register the factory in your binder

    bindFactory(ConnectionProviderFactory.class)
            .to(ConnectionProvider.class)
            .in(RequestScoped.class);
    

    Note that the Factory class has a dispose method. I am not 100 percent sure on the details of how it works, as I have had experiences where sometimes it is called, and sometimes it isn't. I've found the CloseableService to be more reliable. Maybe one of these days I'll go digging around to try and solve the mystery of the dispose method for myself.


    UPDATE

    Not quite sure what you're doing differently, but below is a complete test using Jersey Test Framework. Run it like any other JUnit test. Only difference is instead of AutoCloseable, I use Closeable

    import java.io.Closeable;
    import java.util.logging.Logger;
    import javax.inject.Inject;
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.core.Response;
    import org.glassfish.hk2.api.Factory;
    import org.glassfish.hk2.utilities.binding.AbstractBinder;
    import org.glassfish.jersey.filter.LoggingFilter;
    import org.glassfish.jersey.process.internal.RequestScoped;
    import org.glassfish.jersey.server.CloseableService;
    import org.glassfish.jersey.server.ResourceConfig;
    import org.glassfish.jersey.test.JerseyTest;
    import static org.junit.Assert.assertEquals;
    import org.junit.Test;
    
    public class InjectCloseableTest extends JerseyTest {
        
        public static final String CONNECTION_MSG = "Connection";
        
        public static class ConnectionProvider implements Closeable {
            private static final Logger LOGGER
                    = Logger.getLogger(ConnectionProvider.class.getName());
            
            public String getConnection() {
                return CONNECTION_MSG;
            }
    
            @Override
            public void close() {
                LOGGER.info("---- Closing Connection ----");
            }  
        }
        
        @Path("connection")
        public static class ConnectionResource {
            
            @Inject
            ConnectionProvider connectionProvider;
            
            @GET
            public String get() {
                return connectionProvider.getConnection();
            }
        }
        
        public static class ConnectionProviderFactory implements Factory<ConnectionProvider> {
            
            @Inject
            CloseableService closeableService;
    
            @Override
            public ConnectionProvider provide() {
                ConnectionProvider provider = new ConnectionProvider();
                closeableService.add(provider);
                return provider;
            }
    
            @Override
            public void dispose(ConnectionProvider t) {} 
        }
        
        @Override
        public ResourceConfig configure() {
            return new ResourceConfig(ConnectionResource.class)
                    .register(new LoggingFilter(Logger.getAnonymousLogger(), true))
                    .register(new AbstractBinder() {
                        @Override
                        protected void configure() {
                            bindFactory(ConnectionProviderFactory.class)
                                    .to(ConnectionProvider.class)
                                    .in(RequestScoped.class);
                        }
                    });
        }
        
        @Test
        public void doit() {
            Response response = target("connection").request().get();
            assertEquals(200, response.getStatus());
            assertEquals(CONNECTION_MSG, response.readEntity(String.class));
            response.close();
        }
    }
    

    This is the only dependency you need to run it.

    <dependency>
        <groupId>org.glassfish.jersey.test-framework.providers</groupId>
        <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
        <version>${jersey2.version}</version>
        <scope>test</scope>
    </dependency>