package io.dropwizard.hibernate;
import org.glassfish.jersey.server.ExtendedUriInfo;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.server.model.ResourceMethod;
import org.glassfish.jersey.server.model.ResourceModel;
import org.glassfish.jersey.server.monitoring.ApplicationEvent;
import org.glassfish.jersey.server.monitoring.RequestEvent;
import org.glassfish.jersey.server.monitoring.RequestEventListener;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.context.internal.ManagedSessionContext;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.lang.reflect.Method;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@SuppressWarnings("HibernateResourceOpenedButNotSafelyClosed")
public class UnitOfWorkApplicationListenerTest {
private final SessionFactory sessionFactory = mock(SessionFactory.class);
private final UnitOfWorkApplicationListener listener = new UnitOfWorkApplicationListener(sessionFactory);
private final ApplicationEvent appEvent = mock(ApplicationEvent.class);
private final ExtendedUriInfo uriInfo = mock(ExtendedUriInfo.class);
private final RequestEvent requestStartEvent = mock(RequestEvent.class);
private final RequestEvent requestMethodStartEvent = mock(RequestEvent.class);
private final RequestEvent requestMethodFinishEvent = mock(RequestEvent.class);
private final RequestEvent requestMethodExceptionEvent = mock(RequestEvent.class);
private final Session session = mock(Session.class);
private final Transaction transaction = mock(Transaction.class);
@SuppressWarnings("unchecked")
@Before
public void setUp() throws Exception {
when(sessionFactory.openSession()).thenReturn(session);
when(session.getSessionFactory()).thenReturn(sessionFactory);
when(session.beginTransaction()).thenReturn(transaction);
when(session.getTransaction()).thenReturn(transaction);
when(transaction.isActive()).thenReturn(true);
when(appEvent.getType()).thenReturn(ApplicationEvent.Type.INITIALIZATION_APP_FINISHED);
when(requestMethodStartEvent.getType()).thenReturn(RequestEvent.Type.RESOURCE_METHOD_START);
when(requestMethodFinishEvent.getType()).thenReturn(RequestEvent.Type.RESOURCE_METHOD_FINISHED);
when(requestMethodExceptionEvent.getType()).thenReturn(RequestEvent.Type.ON_EXCEPTION);
when(requestMethodStartEvent.getUriInfo()).thenReturn(uriInfo);
when(requestMethodFinishEvent.getUriInfo()).thenReturn(uriInfo);
when(requestMethodExceptionEvent.getUriInfo()).thenReturn(uriInfo);
prepareAppEvent("methodWithDefaultAnnotation");
}
@Test
public void opensAndClosesASession() throws Exception {
execute();
final InOrder inOrder = inOrder(sessionFactory, session);
inOrder.verify(sessionFactory).openSession();
inOrder.verify(session).close();
}
@Test
public void bindsAndUnbindsTheSessionToTheManagedContext() throws Exception {
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
assertThat(ManagedSessionContext.hasBind(sessionFactory))
.isTrue();
return null;
}
}).when(session).beginTransaction();
execute();
assertThat(ManagedSessionContext.hasBind(sessionFactory)).isFalse();
}
@Test
public void configuresTheSessionsReadOnlyDefault() throws Exception {
prepareAppEvent("methodWithReadOnlyAnnotation");
execute();
verify(session).setDefaultReadOnly(true);
}
@Test
public void configuresTheSessionsCacheMode() throws Exception {
prepareAppEvent("methodWithCacheModeIgnoreAnnotation");
execute();
verify(session).setCacheMode(CacheMode.IGNORE);
}
@Test
public void configuresTheSessionsFlushMode() throws Exception {
prepareAppEvent("methodWithFlushModeAlwaysAnnotation");
execute();
verify(session).setFlushMode(FlushMode.ALWAYS);
}
@Test
public void doesNotBeginATransactionIfNotTransactional() throws Exception {
final String resourceMethodName = "methodWithTransactionalFalseAnnotation";
prepareAppEvent(resourceMethodName);
when(session.getTransaction()).thenReturn(null);
execute();
verify(session, never()).beginTransaction();
verifyZeroInteractions(transaction);
}
@Test
public void beginsAndCommitsATransactionIfTransactional() throws Exception {
execute();
final InOrder inOrder = inOrder(session, transaction);
inOrder.verify(session).beginTransaction();
inOrder.verify(transaction).commit();
inOrder.verify(session).close();
}
@Test
public void rollsBackTheTransactionOnException() throws Exception {
executeWithException();
final InOrder inOrder = inOrder(session, transaction);
inOrder.verify(session).beginTransaction();
inOrder.verify(transaction).rollback();
inOrder.verify(session).close();
}
@Test
public void doesNotCommitAnInactiveTransaction() throws Exception {
when(transaction.isActive()).thenReturn(false);
execute();
verify(transaction, never()).commit();
}
@Test
public void doesNotCommitANullTransaction() throws Exception {
when(session.getTransaction()).thenReturn(null);
execute();
verify(transaction, never()).commit();
}
@Test
public void doesNotRollbackAnInactiveTransaction() throws Exception {
when(transaction.isActive()).thenReturn(false);
executeWithException();
verify(transaction, never()).rollback();
}
@Test
public void doesNotRollbackANullTransaction() throws Exception {
when(session.getTransaction()).thenReturn(null);
executeWithException();
verify(transaction, never()).rollback();
}
private void prepareAppEvent(String resourceMethodName) throws NoSuchMethodException {
final Resource.Builder builder = Resource.builder();
final MockResource mockResource = new MockResource();
final Method method = mockResource.getClass().getMethod(resourceMethodName);
final ResourceMethod resourceMethod = builder.addMethod()
.handlingMethod(method)
.handledBy(mockResource, method).build();
final Resource resource = builder.build();
final ResourceModel model = new ResourceModel.Builder(false).addResource(resource).build();
when(appEvent.getResourceModel()).thenReturn(model);
when(uriInfo.getMatchedResourceMethod()).thenReturn(resourceMethod);
}
private void execute() {
listener.onEvent(appEvent);
RequestEventListener requestListener = listener.onRequest(requestStartEvent);
requestListener.onEvent(requestMethodStartEvent);
requestListener.onEvent(requestMethodFinishEvent);
}
private void executeWithException() {
listener.onEvent(appEvent);
RequestEventListener requestListener = listener.onRequest(requestStartEvent);
requestListener.onEvent(requestMethodStartEvent);
requestListener.onEvent(requestMethodExceptionEvent);
}
public static class MockResource {
@UnitOfWork(readOnly = false, cacheMode = CacheMode.NORMAL, transactional = true, flushMode = FlushMode.AUTO)
public void methodWithDefaultAnnotation() {
}
@UnitOfWork(readOnly = true, cacheMode = CacheMode.NORMAL, transactional = true, flushMode = FlushMode.AUTO)
public void methodWithReadOnlyAnnotation() {
}
@UnitOfWork(readOnly = false, cacheMode = CacheMode.IGNORE, transactional = true, flushMode = FlushMode.AUTO)
public void methodWithCacheModeIgnoreAnnotation() {
}
@UnitOfWork(readOnly = false, cacheMode = CacheMode.NORMAL, transactional = true, flushMode = FlushMode.ALWAYS)
public void methodWithFlushModeAlwaysAnnotation() {
}
@UnitOfWork(readOnly = false, cacheMode = CacheMode.NORMAL, transactional = false, flushMode = FlushMode.AUTO)
public void methodWithTransactionalFalseAnnotation() {
}
}
}