package com.lordofthejars.nosqlunit.core;
import com.lordofthejars.nosqlunit.annotation.*;
import com.lordofthejars.nosqlunit.util.DefaultClasspathLocationBuilder;
import com.lordofthejars.nosqlunit.util.ReflectionUtil;
import org.junit.rules.MethodRule;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import static ch.lambdaj.Lambda.*;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
public abstract class AbstractNoSqlTestRule implements MethodRule {
private static final String EXPECTED_RESERVED_WORD = "-expected";
/**
* With JUnit 4.10 is impossible to get target from a Rule, it seems that
* future versions will support it. For now constructor is apporach is the
* only way. But JUnit 4.11 undeprecated the TestMethod so for maintaining
* back compatibility target is maintained.
*/
private Object target;
private String identifier;
private DefaultDataSetLocationResolver defaultDataSetLocationResolver;
private LoadStrategyFactory loadStrategyFactory = new ReflectionLoadStrategyFactory();
private InjectAnnotationProcessor injectAnnotationProcessor;
public AbstractNoSqlTestRule(String identifier) {
this.identifier = identifier;
this.injectAnnotationProcessor = new InjectAnnotationProcessor(
this.identifier);
}
public abstract DatabaseOperation getDatabaseOperation();
public abstract String getWorkingExtension();
public abstract void close();
@Override
public Statement apply(final Statement base, final FrameworkMethod method,
final Object testObject) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
target = testObject;
defaultDataSetLocationResolver = new DefaultDataSetLocationResolver(
testObject.getClass());
UsingDataSet usingDataSet = getUsingDataSetAnnotation();
try {
if (isTestAnnotatedWithDataSet(usingDataSet)) {
createCustomInsertationStrategyIfPresent();
loadDataSet(usingDataSet, method);
}
injectAnnotationProcessor.processInjectAnnotation(testObject
.getClass(), target, getDatabaseOperation()
.connectionManager());
base.evaluate();
ShouldMatchDataSet shouldMatchDataSet = getShouldMatchDataSetAnnotation();
if (isTestAnnotatedWithExpectedDataSet(shouldMatchDataSet)) {
createCustomComparisionStrategyIfPresent();
assertExpectation(shouldMatchDataSet);
}
} finally {
close();
}
}
private void createCustomComparisionStrategyIfPresent() {
CustomComparisonStrategy customComparisonStrategy = getCustomComparisionStrategy();
if (isTestAnnotatedWithCustomComparisionStrategy(customComparisonStrategy)) {
DatabaseOperation<?> databaseOperation = getDatabaseOperation();
if (isDatabaseOperationCustomizable(databaseOperation)) {
Class<? extends ComparisonStrategy<?>> comparisionStrategy = customComparisonStrategy
.comparisonStrategy();
ComparisonStrategy<?> comparisionStrategyObject = ReflectionUtil.createInstance(comparisionStrategy);
Class<?> classWithAnnotation = IOUtils.getClassWithAnnotation(
target.getClass(),
IgnorePropertyValue.class);
if (method.getAnnotation(IgnorePropertyValue.class) != null
|| (classWithAnnotation != null && classWithAnnotation
.getAnnotation(
IgnorePropertyValue.class) != null)) {
comparisionStrategyObject
.setIgnoreProperties(getPropertiesToIgnore());
}
overrideComparisionStrategy(databaseOperation,
comparisionStrategyObject);
} else {
throw new IllegalArgumentException(
"Custom Insertation Strategy can only be used in DatabaseOperations that extends from AbstractCustomizableDatabaseOperation");
}
}
}
private void createCustomInsertationStrategyIfPresent() {
CustomInsertionStrategy customInsertionStrategy = getCustomInsertationStrategy();
if (isTestAnnotatedWithCustomInsertationStrategy(customInsertionStrategy)) {
DatabaseOperation<?> databaseOperation = getDatabaseOperation();
if (isDatabaseOperationCustomizable(databaseOperation)) {
Class<? extends InsertionStrategy<?>> insertationStrategy = customInsertionStrategy
.insertionStrategy();
InsertionStrategy<?> insertationStrategyObject = ReflectionUtil.createInstance(insertationStrategy);
overrideInsertationStrategy(databaseOperation,
insertationStrategyObject);
} else {
throw new IllegalArgumentException(
"Custom Insertation Strategy can only be used in DatabaseOperations that extends from AbstractCustomizableDatabaseOperation");
}
}
}
private void overrideComparisionStrategy(
DatabaseOperation<?> databaseOperation,
ComparisonStrategy<?> comparisionStrategyObject) {
AbstractCustomizableDatabaseOperation customizableDatabaseOperation = (AbstractCustomizableDatabaseOperation) databaseOperation;
customizableDatabaseOperation
.setComparisonStrategy(comparisionStrategyObject);
}
private void overrideInsertationStrategy(
DatabaseOperation<?> databaseOperation,
InsertionStrategy<?> insertationStrategyObject) {
AbstractCustomizableDatabaseOperation customizableDatabaseOperation = (AbstractCustomizableDatabaseOperation) databaseOperation;
customizableDatabaseOperation
.setInsertionStrategy(insertationStrategyObject);
}
private boolean isDatabaseOperationCustomizable(
DatabaseOperation databaseOperation) {
return databaseOperation instanceof AbstractCustomizableDatabaseOperation;
}
private ShouldMatchDataSet getShouldMatchDataSetAnnotation() {
ShouldMatchDataSet shouldMatchDataSet = method
.getAnnotation(ShouldMatchDataSet.class);
if (!isTestAnnotatedWithExpectedDataSet(shouldMatchDataSet)) {
Class<?> testClass = target.getClass();
Class<?> annotatedClass = IOUtils.getClassWithAnnotation(
testClass, ShouldMatchDataSet.class);
shouldMatchDataSet = annotatedClass == null ? null
: annotatedClass
.getAnnotation(ShouldMatchDataSet.class);
}
return shouldMatchDataSet;
}
private String[] getPropertiesToIgnore() {
List<String> propertyValuesToIgnore = new ArrayList<String>();
Class<?> annotated = IOUtils.getClassWithAnnotation(
target.getClass(), IgnorePropertyValue.class);
if (annotated != null) {
IgnorePropertyValue annotationIgnore = annotated
.getAnnotation(IgnorePropertyValue.class);
if (annotationIgnore != null) {
String[] properties = annotationIgnore.properties();
for (String property : properties) {
propertyValuesToIgnore.add(property);
}
}
}
IgnorePropertyValue ignorePropertyValue = method
.getAnnotation(IgnorePropertyValue.class);
if (isTestAnnotatedWithIgnoreProperty(ignorePropertyValue)) {
String[] properties = ignorePropertyValue.properties();
for (String property : properties) {
propertyValuesToIgnore.add(property);
}
}
return propertyValuesToIgnore
.toArray(new String[propertyValuesToIgnore.size()]);
}
private UsingDataSet getUsingDataSetAnnotation() {
UsingDataSet usingDataSet = method
.getAnnotation(UsingDataSet.class);
if (!isTestAnnotatedWithDataSet(usingDataSet)) {
Class<?> testClass = target.getClass();
Class<?> annotatedClass = IOUtils.getClassWithAnnotation(
testClass, UsingDataSet.class);
usingDataSet = annotatedClass == null ? null
: annotatedClass.getAnnotation(UsingDataSet.class);
}
return usingDataSet;
}
private CustomComparisonStrategy getCustomComparisionStrategy() {
Class<?> testClass = target.getClass();
Class<?> annotatedClass = IOUtils
.getClassWithAnnotation(
testClass,
com.lordofthejars.nosqlunit.annotation.CustomComparisonStrategy.class);
return annotatedClass == null ? null
: annotatedClass
.getAnnotation(com.lordofthejars.nosqlunit.annotation.CustomComparisonStrategy.class);
}
private com.lordofthejars.nosqlunit.annotation.CustomInsertionStrategy getCustomInsertationStrategy() {
Class<?> testClass = target.getClass();
Class<?> annotatedClass = IOUtils
.getClassWithAnnotation(
testClass,
com.lordofthejars.nosqlunit.annotation.CustomInsertionStrategy.class);
return annotatedClass == null ? null
: annotatedClass
.getAnnotation(com.lordofthejars.nosqlunit.annotation.CustomInsertionStrategy.class);
}
private void assertExpectation(ShouldMatchDataSet shouldMatchDataSet)
throws IOException {
InputStream scriptContent = loadExpectedContentScript(method,
shouldMatchDataSet);
if (isNotEmptyStream(scriptContent)) {
getDatabaseOperation().databaseIs(scriptContent);
} else {
final String suffix = EXPECTED_RESERVED_WORD + "."
+ getWorkingExtension();
final String defaultClassLocation = DefaultClasspathLocationBuilder
.defaultClassAnnotatedClasspathLocation(method);
final String defaultMethodLocation = DefaultClasspathLocationBuilder
.defaultMethodAnnotatedClasspathLocation(method,
defaultClassLocation, suffix);
throw new IllegalArgumentException(
"File specified in location or selective matcher property "
+ " of ShouldMatchDataSet is not present, or no files matching default location. Valid default locations are: "
+ defaultClassLocation + suffix + " or "
+ defaultMethodLocation);
}
}
private InputStream loadExpectedContentScript(
final FrameworkMethod method,
ShouldMatchDataSet shouldMatchDataSet) throws IOException {
String location = shouldMatchDataSet.location();
InputStream scriptContent = null;
if (isNotEmptyString(location)) {
scriptContent = loadExpectedResultFromLocationAttribute(location);
} else {
SelectiveMatcher[] selectiveMatchers = shouldMatchDataSet
.withSelectiveMatcher();
SelectiveMatcher requiredSelectiveMatcher = findSelectiveMatcherByConnectionIdentifier(selectiveMatchers);
if (isSelectiveMatchersDefined(requiredSelectiveMatcher)) {
scriptContent = loadExpectedResultFromLocationAttribute(requiredSelectiveMatcher
.location());
} else {
scriptContent = loadExpectedResultFromDefaultLocation(
method, shouldMatchDataSet);
}
}
return scriptContent;
}
private boolean isSelectiveMatchersDefined(
SelectiveMatcher requiredSelectiveMatcher) {
return requiredSelectiveMatcher != null;
}
private SelectiveMatcher findSelectiveMatcherByConnectionIdentifier(
SelectiveMatcher[] selectiveMatchers) {
return selectFirst(
selectiveMatchers,
having(on(SelectiveMatcher.class).identifier(),
equalTo(identifier)).and(
having(on(SelectiveMatcher.class).location(),
notNullValue())));
}
private InputStream loadExpectedResultFromDefaultLocation(
final FrameworkMethod method,
ShouldMatchDataSet shouldMatchDataSet) throws IOException {
InputStream scriptContent = null;
String defaultLocation = defaultDataSetLocationResolver
.resolveDefaultDataSetLocation(shouldMatchDataSet,
method, EXPECTED_RESERVED_WORD + "."
+ getWorkingExtension());
if (defaultLocation != null) {
scriptContent = loadExpectedResultFromLocationAttribute(defaultLocation);
}
return scriptContent;
}
private InputStream loadExpectedResultFromLocationAttribute(
String location) throws IOException {
InputStream scriptContent;
scriptContent = IOUtils.getStreamFromClasspathBaseResource(
defaultDataSetLocationResolver.getResourceBase(),
location);
return scriptContent;
}
private void loadDataSet(UsingDataSet usingDataSet,
FrameworkMethod method) throws IOException {
List<InputStream> scriptContent = loadDatasets(usingDataSet,
method);
LoadStrategyEnum loadStrategyEnum = usingDataSet.loadStrategy();
if (areDatasetsRequired(loadStrategyEnum)
&& emptyDataset(scriptContent)
&& notSelectiveAnnotation(usingDataSet
.withSelectiveLocations())) {
final String suffix = "." + getWorkingExtension();
final String defaultClassLocation = DefaultClasspathLocationBuilder
.defaultClassAnnotatedClasspathLocation(method);
final String defaultMethodLocation = DefaultClasspathLocationBuilder
.defaultMethodAnnotatedClasspathLocation(method,
defaultClassLocation, suffix);
throw new IllegalArgumentException(
"File specified in locations property are not present in classpath, or no files matching default name are found. Valid default locations are: "
+ defaultClassLocation
+ suffix
+ " or "
+ defaultMethodLocation);
}
LoadStrategyOperation loadStrategyOperation = loadStrategyFactory
.getLoadStrategyInstance(loadStrategyEnum,
getDatabaseOperation());
loadStrategyOperation.executeScripts(scriptContent
.toArray(new InputStream[scriptContent.size()]));
}
private boolean notSelectiveAnnotation(
Selective[] withSelectiveLocations) {
return withSelectiveLocations.length == 0;
}
private boolean emptyDataset(List<InputStream> scriptContent) {
return scriptContent.size() == 0;
}
private boolean areDatasetsRequired(
LoadStrategyEnum loadStrategyEnum) {
return LoadStrategyEnum.DELETE_ALL != loadStrategyEnum;
}
private List<InputStream> loadDatasets(UsingDataSet usingDataSet,
FrameworkMethod method) throws IOException {
String[] locations = usingDataSet.locations();
List<InputStream> scriptContent = new ArrayList<InputStream>();
scriptContent.addAll(loadGlobalDataSets(usingDataSet, method,
locations));
scriptContent.addAll(loadSelectiveDataSets(usingDataSet));
return scriptContent;
}
private List<InputStream> loadSelectiveDataSets(
UsingDataSet usingDataSet) throws IOException {
List<InputStream> scriptContent = new ArrayList<InputStream>();
if (isSelectiveLocationsAttributeSpecified(usingDataSet)) {
Selective[] selectiveLocations = usingDataSet
.withSelectiveLocations();
if (selectiveLocations != null
&& selectiveLocations.length > 0) {
for (Selective selective : selectiveLocations) {
if (identifier
.equals(selective.identifier().trim())
&& isLocationsAttributeSpecified(selective
.locations())) {
scriptContent
.addAll(IOUtils
.getAllStreamsFromClasspathBaseResource(
defaultDataSetLocationResolver
.getResourceBase(),
selective.locations()));
}
}
}
}
return scriptContent;
}
private List<InputStream> loadGlobalDataSets(
UsingDataSet usingDataSet, FrameworkMethod method,
String[] locations) throws IOException {
List<InputStream> scriptContent = new ArrayList<InputStream>();
if (isLocationsAttributeSpecified(locations)) {
scriptContent.addAll(IOUtils
.getAllStreamsFromClasspathBaseResource(
defaultDataSetLocationResolver
.getResourceBase(), locations));
} else {
String location = defaultDataSetLocationResolver
.resolveDefaultDataSetLocation(usingDataSet,
method, "." + getWorkingExtension());
if (location != null) {
scriptContent.add(IOUtils
.getStreamFromClasspathBaseResource(
defaultDataSetLocationResolver
.getResourceBase(), location));
}
}
return scriptContent;
}
private boolean isSelectiveLocationsAttributeSpecified(
UsingDataSet usingDataSet) {
Selective[] selectiveLocations = usingDataSet
.withSelectiveLocations();
if (selectiveLocations != null && selectiveLocations.length > 0) {
for (Selective selective : selectiveLocations) {
if (identifier.equals(selective.identifier().trim())
&& isLocationsAttributeSpecified(selective
.locations())) {
return true;
}
}
}
return false;
}
private boolean isNotEmptyStream(InputStream inputStream) {
return inputStream != null;
}
private boolean isNotEmptyString(String location) {
return location != null && !"".equals(location.trim());
}
private boolean isLocationsAttributeSpecified(String[] locations) {
return locations != null && locations.length > 0;
}
private boolean isTestAnnotatedWithCustomComparisionStrategy(
CustomComparisonStrategy customComparisonStrategy) {
return customComparisonStrategy != null;
}
private boolean isTestAnnotatedWithCustomInsertationStrategy(
CustomInsertionStrategy customInsertionStrategy) {
return customInsertionStrategy != null;
}
private boolean isTestAnnotatedWithExpectedDataSet(
ShouldMatchDataSet shouldMatchDataSet) {
return shouldMatchDataSet != null;
}
private boolean isTestAnnotatedWithDataSet(UsingDataSet usingDataSet) {
return usingDataSet != null;
}
private boolean isTestAnnotatedWithIgnoreProperty(
IgnorePropertyValue ignorePropertyValue) {
return ignorePropertyValue != null;
}
};
}
public void setLoadStrategyFactory(LoadStrategyFactory loadStrategyFactory) {
this.loadStrategyFactory = loadStrategyFactory;
}
public void setInjectAnnotationProcessor(
InjectAnnotationProcessor injectAnnotationProcessor) {
this.injectAnnotationProcessor = injectAnnotationProcessor;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
/*
* With JUnit 4.10 is impossible to get target from a Rule, it seems that
* future versions will support it. For now constructor is approach is the
* only way.
*/
protected void setTarget(Object target) {
this.target = target;
}
}