-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implemented context holder. Bumped version to 0.1.1.
- Loading branch information
Showing
5 changed files
with
191 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
src/main/groovy/com/xinra/nucleus/common/ContextHolder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package com.xinra.nucleus.common; | ||
|
||
/** | ||
* Holds a context object of arbitrary type. A context holds data that is used across different | ||
* parts of an application but should not be passed around as method parameters. Typically, | ||
* a context is a bean with a suiting scope (e.g. 'request'). Instead of autowiring the | ||
* context directly, this wrapper is used to provide a (mock) context if context-dependent | ||
* code is executed outside of the bean's scope, e.g. in tests. | ||
* | ||
* @author Erik Hofer | ||
* @param <T> the context type | ||
*/ | ||
public interface ContextHolder<T> { | ||
|
||
/** | ||
* Returns the current context. Either the context is retrieved from the application context | ||
* (typically as a request-scoped bean) or a mock context is returned if it has been set | ||
* previously by {@link #mock()}. | ||
* | ||
* @throws IllegalStateException if there is neither a context provided by | ||
* the application context nor a mock context. | ||
*/ | ||
T get(); | ||
|
||
/** | ||
* Creates a mock context. This is used if the application context does not hold a context | ||
* (typically if the context is request-scoped but the current thread is not a web request). | ||
* | ||
* <p>IMPORTANT: When the mock context is not needed anymore, it has to be discarded using | ||
* {@link #clearMock()}. The mock context is stored in a {@link ThreadLocal} and unlike | ||
* request-scoped beans it is not cleaned up automatically. To prevent leaking the | ||
* mock context wrap the context-dependent code in a try-finally block. | ||
* | ||
* @return the created context | ||
* @throws IllegalStateException if a context is already provided by the application context. | ||
*/ | ||
T mock(); | ||
|
||
/** | ||
* Clears the mock context that has been created with {@link #mock()}. | ||
*/ | ||
void clearMock(); | ||
|
||
} |
72 changes: 72 additions & 0 deletions
72
src/main/groovy/com/xinra/nucleus/common/GenericContextHolder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package com.xinra.nucleus.common; | ||
|
||
import java.util.Objects; | ||
import java.util.function.Supplier; | ||
import org.springframework.context.ApplicationContext; | ||
import org.springframework.context.ApplicationContextAware; | ||
|
||
/** | ||
* Default implementation of {@link ContextHolder}. Holds context of arbitrary type. | ||
* | ||
* @author Erik Hofer | ||
* @param <T> the context type | ||
*/ | ||
public class GenericContextHolder<T> implements ApplicationContextAware, ContextHolder<T> { | ||
|
||
private final ThreadLocal<T> mock = new ThreadLocal<>(); | ||
private final Class<T> contextType; | ||
private final Supplier<T> contextSupplier; | ||
private final Supplier<Boolean> useApplicationContext; | ||
private ApplicationContext applicationContext; | ||
|
||
/** | ||
* Creates a {@link GenericContextHolder}. If this is not used to create a managed bean, | ||
* make sure to call {@link #setApplicationContext(ApplicationContext)}. | ||
* @param contextType the type used to retrieve the current context from the application context | ||
* @param contextSupplier used to create a new mock context | ||
* @param useApplicationContext used to determine if the application context should be used to | ||
* retrieve the context (true) or if a mock context should be used (false). | ||
*/ | ||
public GenericContextHolder(Class<T> contextType, Supplier<T> contextSupplier, | ||
Supplier<Boolean> useApplicationContext) { | ||
this.contextType = Objects.requireNonNull(contextType); | ||
this.contextSupplier = Objects.requireNonNull(contextSupplier); | ||
this.useApplicationContext = Objects.requireNonNull(useApplicationContext); | ||
} | ||
|
||
@Override | ||
public T get() { | ||
if (useApplicationContext.get()) { | ||
return applicationContext.getBean(contextType); | ||
} else { | ||
T context = mock.get(); | ||
if (context == null) { | ||
mock.remove(); | ||
throw new IllegalStateException("Cannot get current context. This is not a web" | ||
+ " request and there is no mock context!"); | ||
} | ||
return context; | ||
} | ||
} | ||
|
||
@Override | ||
public T mock() { | ||
if (useApplicationContext.get()) { | ||
throw new IllegalStateException("This is a request. Use the request-scoped context" | ||
+ " instead of mocking one!"); | ||
} | ||
final T context = contextSupplier.get(); | ||
mock.set(context); | ||
return context; | ||
} | ||
|
||
@Override | ||
public void clearMock() { | ||
mock.remove(); | ||
} | ||
|
||
@Override | ||
public void setApplicationContext(ApplicationContext applicationContext) { | ||
this.applicationContext = applicationContext; | ||
} | ||
} |
18 changes: 0 additions & 18 deletions
18
src/test/groovy/com/xinra/nucleus/common/test/AllTests.java
This file was deleted.
Oops, something went wrong.
70 changes: 70 additions & 0 deletions
70
src/test/groovy/com/xinra/nucleus/common/test/TestGenericContextHolder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package com.xinra.nucleus.common.test; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.when; | ||
|
||
import com.xinra.nucleus.common.GenericContextHolder; | ||
import org.junit.BeforeClass; | ||
import org.junit.Test; | ||
import org.springframework.context.ApplicationContext; | ||
|
||
public class TestGenericContextHolder { | ||
|
||
private static enum TestContext { | ||
BEAN, | ||
MOCK | ||
} | ||
|
||
private static GenericContextHolder<TestContext> contextHolder; | ||
private static boolean useApplicationContext; | ||
|
||
/** | ||
* Creates the instance of {@link GenericContextHolder} and a mock application | ||
* context. The instance is shared across all tests because it is stateless | ||
* aside from {@link #useApplicationContext}. | ||
*/ | ||
@BeforeClass | ||
public static void setUp() { | ||
contextHolder = new GenericContextHolder<>(TestContext.class, () -> TestContext.MOCK, | ||
() -> useApplicationContext); | ||
|
||
ApplicationContext applicationContext = mock(ApplicationContext.class); | ||
when(applicationContext.getBean(TestContext.class)).thenReturn(TestContext.BEAN); | ||
contextHolder.setApplicationContext(applicationContext); | ||
} | ||
|
||
@Test | ||
public void getContextBean() { | ||
useApplicationContext = true; | ||
assertThat(contextHolder.get()).isEqualTo(TestContext.BEAN); | ||
} | ||
|
||
@Test | ||
public void createAndGetMockContext() { | ||
useApplicationContext = false; | ||
assertThat(contextHolder.mock()).isEqualTo(TestContext.MOCK); | ||
assertThat(contextHolder.get()).isEqualTo(TestContext.MOCK); | ||
} | ||
|
||
@Test | ||
public void createMockContextWhenNotApplicable() { | ||
useApplicationContext = true; | ||
assertThatIllegalStateException().isThrownBy(contextHolder::mock); | ||
} | ||
|
||
@Test | ||
public void getMockContextWihtoutCreatingOne() { | ||
useApplicationContext = false; | ||
assertThatIllegalStateException().isThrownBy(contextHolder::get); | ||
} | ||
|
||
@Test | ||
public void clearMockContext() { | ||
useApplicationContext = false; | ||
contextHolder.mock(); | ||
contextHolder.clearMock(); | ||
assertThatIllegalStateException().isThrownBy(contextHolder::get); | ||
} | ||
} |