diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java index 89170713ac..33477e04b8 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java @@ -35,12 +35,16 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- -import com.google.inject.Inject; -import com.google.inject.Provider; +import com.google.common.annotations.VisibleForTesting; +import com.google.inject.OutOfScopeException; +import com.google.inject.Provider; +import com.google.inject.ProvisionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.inject.Inject; + /** * Injection provider for {@link HgContext}. * This provider returns an instance {@link HgContext} from request scope, if no {@link HgContext} could be found in @@ -52,31 +56,50 @@ public class HgContextProvider implements Provider { /** - * the logger for HgContextProvider + * the LOG for HgContextProvider */ - private static final Logger logger = + private static final Logger LOG = LoggerFactory.getLogger(HgContextProvider.class); //~--- get methods ---------------------------------------------------------- - /** - * Method description - * - * - * @return - */ - @Override - public HgContext get() { - if (contextRequestStore == null) { - logger.trace("context is null, we are probably out of request scope"); - return new HgContext(); - } - logger.trace("return HgContext from request store"); - return contextRequestStore.get(); + private Provider requestStoreProvider; + + @Inject + public HgContextProvider(Provider requestStoreProvider) { + this.requestStoreProvider = requestStoreProvider; } - //~--- fields --------------------------------------------------------------- + @VisibleForTesting + public HgContextProvider() { + } - @Inject(optional = true) - private HgContextRequestStore contextRequestStore; + @Override + public HgContext get() { + HgContext context = fetchContextFromRequest(); + if (context != null) { + LOG.trace("return HgContext from request store"); + return context; + } + LOG.trace("could not find context in request scope, returning new instance"); + return new HgContext(); + } + + private HgContext fetchContextFromRequest() { + try { + if (requestStoreProvider != null) { + return requestStoreProvider.get().get(); + } else { + LOG.trace("no request store provider defined, could not return context from request"); + return null; + } + } catch (ProvisionException ex) { + if (ex.getCause() instanceof OutOfScopeException) { + LOG.trace("we are currently out of request scope, failed to retrieve context"); + return null; + } else { + throw ex; + } + } + } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgContextProviderTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgContextProviderTest.java new file mode 100644 index 0000000000..de31e2b11e --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgContextProviderTest.java @@ -0,0 +1,87 @@ +package sonia.scm.repository; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.OutOfScopeException; +import com.google.inject.Provider; +import com.google.inject.ProvisionException; +import com.google.inject.Scope; +import com.google.inject.servlet.RequestScoped; +import com.google.inject.util.Providers; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class HgContextProviderTest { + + @Mock + private Scope scope; + + @Test + void shouldThrowNonOutOfScopeProvisionExceptions() { + Provider provider = () -> { + throw new RuntimeException("something different"); + }; + + when(scope.scope(any(Key.class), any(Provider.class))).thenReturn(provider); + + Injector injector = Guice.createInjector(new HgContextModule(scope)); + + assertThrows(ProvisionException.class, () -> injector.getInstance(HgContext.class)); + } + + @Test + void shouldCreateANewInstanceIfOutOfRequestScope() { + Provider provider = () -> { + throw new OutOfScopeException("no request"); + }; + when(scope.scope(any(Key.class), any(Provider.class))).thenReturn(provider); + + Injector injector = Guice.createInjector(new HgContextModule(scope)); + + HgContext contextOne = injector.getInstance(HgContext.class); + HgContext contextTwo = injector.getInstance(HgContext.class); + + assertThat(contextOne).isNotSameAs(contextTwo); + } + + @Test + void shouldInjectFromRequestScope() { + HgContextRequestStore requestStore = new HgContextRequestStore(); + Provider provider = Providers.of(requestStore); + + when(scope.scope(any(Key.class), any(Provider.class))).thenReturn(provider); + + Injector injector = Guice.createInjector(new HgContextModule(scope)); + + HgContext contextOne = injector.getInstance(HgContext.class); + HgContext contextTwo = injector.getInstance(HgContext.class); + + assertThat(contextOne).isSameAs(contextTwo); + } + + private static class HgContextModule extends AbstractModule { + + private Scope scope; + + private HgContextModule(Scope scope) { + this.scope = scope; + } + + @Override + protected void configure() { + bindScope(RequestScoped.class, scope); + bind(HgContextRequestStore.class); + bind(HgContext.class).toProvider(HgContextProvider.class); + } + } +}