VIVO-1246 Improve the ConfigurationBeanLoader (#52)

* VIVO-1246 improve the ConfigurationBeanLoader:

Add cardinality parameters minOccurs and maxOccurs
Create README.md document in the edu.cornell.mannlib.vitro.webapp.utils.configuration package
Split large class of unit tests into separate classes by functionality

* VIVO-1247, remove duplicate code used with ConfigurationBeanLoader.

Now that the @Property annotation includes cardinality parameters, we can remove a lot of duplicate code.

* VIVO-1246 Move unit tests to the new location.

* VIVO-1246 The documentation was in the wrong place.
This commit is contained in:
Jim Blake 2017-01-03 12:12:11 -05:00 committed by GitHub
parent fc83c9f48d
commit a8ec633f71
22 changed files with 1414 additions and 703 deletions

View file

@ -25,7 +25,6 @@ import edu.cornell.mannlib.vitro.webapp.startup.ComponentStartupStatusImpl;
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;
import edu.cornell.mannlib.vitro.webapp.triplesource.impl.BasicCombinedTripleSource;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.Validation;
/**
* The basic implementation of the Application interface.
@ -69,15 +68,9 @@ public class ApplicationImpl implements Application {
return searchEngine;
}
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasSearchEngine")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasSearchEngine", minOccurs = 1, maxOccurs = 1)
public void setSearchEngine(SearchEngine se) {
if (searchEngine == null) {
searchEngine = se;
} else {
throw new IllegalStateException(
"Configuration includes multiple SearchEngine instances: "
+ searchEngine + ", and " + se);
}
searchEngine = se;
}
@Override
@ -85,15 +78,9 @@ public class ApplicationImpl implements Application {
return searchIndexer;
}
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasSearchIndexer")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasSearchIndexer", minOccurs = 1, maxOccurs = 1)
public void setSearchIndexer(SearchIndexer si) {
if (searchIndexer == null) {
searchIndexer = si;
} else {
throw new IllegalStateException(
"Configuration includes multiple SearchIndexer instances: "
+ searchIndexer + ", and " + si);
}
searchIndexer = si;
}
@Override
@ -101,15 +88,9 @@ public class ApplicationImpl implements Application {
return imageProcessor;
}
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasImageProcessor")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasImageProcessor", minOccurs = 1, maxOccurs = 1)
public void setImageProcessor(ImageProcessor ip) {
if (imageProcessor == null) {
imageProcessor = ip;
} else {
throw new IllegalStateException(
"Configuration includes multiple ImageProcessor instances: "
+ imageProcessor + ", and " + ip);
}
imageProcessor = ip;
}
@Override
@ -117,15 +98,9 @@ public class ApplicationImpl implements Application {
return fileStorage;
}
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasFileStorage")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasFileStorage", minOccurs = 1, maxOccurs = 1)
public void setFileStorage(FileStorage fs) {
if (fileStorage == null) {
fileStorage = fs;
} else {
throw new IllegalStateException(
"Configuration includes multiple FileStorage instances: "
+ fileStorage + ", and " + fs);
}
fileStorage = fs;
}
@Override
@ -133,15 +108,9 @@ public class ApplicationImpl implements Application {
return contentTripleSource;
}
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasContentTripleSource")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasContentTripleSource", minOccurs = 1, maxOccurs = 1)
public void setContentTripleSource(ContentTripleSource source) {
if (contentTripleSource == null) {
contentTripleSource = source;
} else {
throw new IllegalStateException(
"Configuration includes multiple instances of ContentTripleSource: "
+ contentTripleSource + ", and " + source);
}
contentTripleSource = source;
}
@Override
@ -149,15 +118,9 @@ public class ApplicationImpl implements Application {
return configurationTripleSource;
}
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasConfigurationTripleSource")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasConfigurationTripleSource", minOccurs = 1, maxOccurs = 1)
public void setConfigurationTripleSource(ConfigurationTripleSource source) {
if (configurationTripleSource == null) {
configurationTripleSource = source;
} else {
throw new IllegalStateException(
"Configuration includes multiple instances of ConfigurationTripleSource: "
+ configurationTripleSource + ", and " + source);
}
configurationTripleSource = source;
}
@Override
@ -165,47 +128,9 @@ public class ApplicationImpl implements Application {
return tboxReasonerModule;
}
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasTBoxReasonerModule")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasTBoxReasonerModule", minOccurs = 1, maxOccurs = 1)
public void setTBoxReasonerModule(TBoxReasonerModule module) {
if (tboxReasonerModule == null) {
tboxReasonerModule = module;
} else {
throw new IllegalStateException(
"Configuration includes multiple instances of TBoxReasonerModule: "
+ tboxReasonerModule + ", and " + module);
}
}
@Validation
public void validate() throws Exception {
if (searchEngine == null) {
throw new IllegalStateException(
"Configuration did not include a SearchEngine.");
}
if (searchIndexer == null) {
throw new IllegalStateException(
"Configuration did not include a SearchIndexer.");
}
if (imageProcessor == null) {
throw new IllegalStateException(
"Configuration did not include an ImageProcessor.");
}
if (fileStorage == null) {
throw new IllegalStateException(
"Configuration did not include a FileStorage.");
}
if (contentTripleSource == null) {
throw new IllegalStateException(
"Configuration did not include a ContentTripleSource.");
}
if (configurationTripleSource == null) {
throw new IllegalStateException(
"Configuration did not include a ConfigurationTripleSource.");
}
if (tboxReasonerModule == null) {
throw new IllegalStateException(
"Configuration did not include a TBoxReasonerModule.");
}
tboxReasonerModule = module;
}
@Override

View file

@ -26,7 +26,6 @@ import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchResponse;
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchResultDocument;
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchResultDocumentList;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.Validation;
/**
* Manages the life-cycle of the SearchEngine. Adds logging, controlled by
@ -40,26 +39,11 @@ public class InstrumentedSearchEngineWrapper implements SearchEngine {
private volatile LifecycleState lifecycleState = NEW;
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#wraps")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#wraps", minOccurs = 1, maxOccurs = 1)
public void setInnerEngine(SearchEngine inner) {
if (innerEngine == null) {
innerEngine = inner;
} else {
throw new IllegalStateException(
"Configuration includes multiple SearchEngine instancess: "
+ innerEngine + ", and " + inner);
}
innerEngine = inner;
}
@Validation
public void validate() throws Exception {
if (innerEngine == null) {
throw new IllegalStateException(
"Configuration did not include a wrapped SearchEngine.");
}
}
/**
* Complain unless ACTIVE.
*/
@ -222,13 +206,13 @@ public class InstrumentedSearchEngineWrapper implements SearchEngine {
return count;
}
}
// ----------------------------------------------------------------------
// Helper classes
// ----------------------------------------------------------------------
private static class SearchResponseForDocumentCount implements SearchResponse {
private static class SearchResponseForDocumentCount implements
SearchResponse {
private final int count;
public SearchResponseForDocumentCount(int count) {
@ -254,28 +238,29 @@ public class InstrumentedSearchEngineWrapper implements SearchEngine {
public List<SearchFacetField> getFacetFields() {
return Collections.emptyList();
}
private class EmptyDocumentListWithCount implements SearchResultDocumentList {
@Override
public Iterator<SearchResultDocument> iterator() {
return Collections.emptyIterator();
}
@Override
public int size() {
return 0;
}
@Override
public long getNumFound() {
return count;
}
@Override
public SearchResultDocument get(int i) {
throw new ArrayIndexOutOfBoundsException(i);
}
private class EmptyDocumentListWithCount implements
SearchResultDocumentList {
@Override
public Iterator<SearchResultDocument> iterator() {
return Collections.emptyIterator();
}
@Override
public int size() {
return 0;
}
@Override
public long getNumFound() {
return count;
}
@Override
public SearchResultDocument get(int i) {
throw new ArrayIndexOutOfBoundsException(i);
}
}
}
}

View file

@ -99,7 +99,7 @@ public class SearchIndexerImpl implements SearchIndexer {
private Set<IndexingUriFinder> uriFinders;
private WebappDaoFactory wadf;
private boolean rebuildOnUnpause = false;
private boolean rebuildOnUnpause = false;
private volatile int paused = 0;
@ -110,25 +110,14 @@ public class SearchIndexerImpl implements SearchIndexer {
// ConfigurationBeanLoader methods.
// ----------------------------------------------------------------------
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#threadPoolSize")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#threadPoolSize", minOccurs = 1, maxOccurs = 1)
public void setThreadPoolSize(String size) {
if (threadPoolSize == null) {
threadPoolSize = Integer.parseInt(size);
} else {
throw new IllegalStateException(
"Configuration includes multiple values for threadPoolSize: "
+ threadPoolSize + ", and " + size);
}
threadPoolSize = Integer.parseInt(size);
}
@Validation
public void validate() throws Exception {
if (threadPoolSize == null) {
throw new IllegalStateException(
"Configuration did not include a value for threadPoolSize.");
} else {
this.pool = new WorkerThreadPool(threadPoolSize);
}
this.pool = new WorkerThreadPool(threadPoolSize);
}
// ----------------------------------------------------------------------
@ -241,7 +230,7 @@ public class SearchIndexerImpl implements SearchIndexer {
}
}
private synchronized void schedulePendingUris() {
private synchronized void schedulePendingUris() {
if (paused == 0 && pendingUris.size() > 0) {
scheduleUpdatesForUris(pendingUris);
pendingUris = new ArrayList<>();
@ -278,13 +267,14 @@ public class SearchIndexerImpl implements SearchIndexer {
if (changes == null || changes.isEmpty()) {
return;
}
if (paused > 0) {
if (paused > 0) {
if (addToPendingStatements(changes)) {
return;
}
}
}
scheduler.scheduleTask(new UpdateStatementsTask(new IndexerConfigImpl(this), changes));
scheduler.scheduleTask(new UpdateStatementsTask(new IndexerConfigImpl(
this), changes));
log.debug("Scheduled updates for " + changes.size() + " statements.");
}
@ -306,13 +296,14 @@ public class SearchIndexerImpl implements SearchIndexer {
if (uris == null || uris.isEmpty()) {
return;
}
if (paused > 0) {
if (paused > 0) {
if (pendingUris.addAll(uris)) {
return;
}
}
}
scheduler.scheduleTask(new UpdateUrisTask(new IndexerConfigImpl(this), uris));
scheduler.scheduleTask(new UpdateUrisTask(new IndexerConfigImpl(this),
uris));
log.debug("Scheduled updates for " + uris.size() + " uris.");
}
@ -332,13 +323,14 @@ public class SearchIndexerImpl implements SearchIndexer {
return;
}
fireEvent(REBUILD_REQUESTED);
if (paused > 0) {
if (paused > 0) {
// Make sure that we are rebuilding when we unpause
// and don't bother noting any other changes until unpaused
rebuildOnUnpause = true;
return;
}
scheduler.scheduleTask(new RebuildIndexTask(new IndexerConfigImpl(this)));
rebuildOnUnpause = true;
return;
}
scheduler
.scheduleTask(new RebuildIndexTask(new IndexerConfigImpl(this)));
log.debug("Scheduled a full rebuild.");
}
@ -447,13 +439,13 @@ public class SearchIndexerImpl implements SearchIndexer {
}
public synchronized void scheduleTask(Task task) {
if (!started) {
deferredQueue.add(task);
log.debug("added task to deferred queue: " + task);
} else {
if (!started) {
deferredQueue.add(task);
log.debug("added task to deferred queue: " + task);
} else {
taskQueue.scheduleTask(task);
log.debug("added task to task queue: " + task);
}
}
}
public synchronized void start() {
@ -463,8 +455,9 @@ public class SearchIndexerImpl implements SearchIndexer {
private void processDeferredTasks() {
for (Task task : deferredQueue) {
taskQueue.scheduleTask(task);
log.debug("moved task from deferred queue to task queue: " + task);
taskQueue.scheduleTask(task);
log.debug("moved task from deferred queue to task queue: "
+ task);
}
deferredQueue.clear();
}

View file

@ -17,27 +17,18 @@ public class FieldBooster implements DocumentModifier {
private final List<String> fieldNames = new ArrayList<>();
private volatile Float boost;
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasTargetField")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasTargetField", minOccurs = 1)
public void addTargetField(String fieldName) {
fieldNames.add(fieldName);
}
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasBoost")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasBoost", minOccurs = 1)
public void setBoost(float boost) {
this.boost = boost;
}
@Validation
public void validate() {
if (boost == null) {
throw new IllegalStateException(
"Configuration did not include a boost value.");
}
if (fieldNames.isEmpty()) {
throw new IllegalStateException(
"Configuration did not include a target field.");
}
Set<String> uniqueFieldNames = new HashSet<>(fieldNames);
List<String> duplicateFieldNames = new ArrayList<>(fieldNames);
for (String fn : uniqueFieldNames) {

View file

@ -74,7 +74,7 @@ public class SelectQueryUriFinder implements IndexingUriFinder,
label = l;
}
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasSelectQuery")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasSelectQuery", minOccurs = 1)
public void addQuery(String query) {
queries.add(query);
}
@ -89,10 +89,6 @@ public class SelectQueryUriFinder implements IndexingUriFinder,
if (label == null) {
label = this.getClass().getSimpleName() + ":" + this.hashCode();
}
if (queries.isEmpty()) {
throw new IllegalStateException(
"Configuration contains no queries for " + label);
}
}
@Override

View file

@ -19,7 +19,6 @@ import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceFactorySingle;
import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.logging.LoggingRDFServiceFactory;
import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.sparql.RDFServiceSparql;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.Validation;
import edu.cornell.mannlib.vitro.webapp.utils.logging.ToString;
/**
@ -41,34 +40,14 @@ public class ContentTripleSourceSPARQL extends ContentTripleSource {
private Dataset dataset;
private ModelMaker modelMaker;
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasEndpointURI")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasEndpointURI", minOccurs = 1, maxOccurs = 1)
public void setEndpointURI(String eUri) {
if (endpointURI == null) {
endpointURI = eUri;
} else {
throw new IllegalStateException(
"Configuration includes multiple instances of EndpointURI: "
+ endpointURI + ", and " + eUri);
}
endpointURI = eUri;
}
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasUpdateEndpointURI")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasUpdateEndpointURI", maxOccurs = 1)
public void setUpdateEndpointURI(String ueUri) {
if (updateEndpointURI == null) {
updateEndpointURI = ueUri;
} else {
throw new IllegalStateException(
"Configuration includes multiple instances of UpdateEndpointURI: "
+ updateEndpointURI + ", and " + ueUri);
}
}
@Validation
public void validate() throws Exception {
if (endpointURI == null) {
throw new IllegalStateException(
"Configuration did not include an EndpointURI.");
}
updateEndpointURI = ueUri;
}
@Override

View file

@ -24,7 +24,6 @@ import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.tdb.RDFServiceTDB;
import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.logging.LoggingRDFServiceFactory;
import edu.cornell.mannlib.vitro.webapp.servlet.setup.JenaDataSourceSetupBase;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.Validation;
import edu.cornell.mannlib.vitro.webapp.utils.logging.ToString;
/**
@ -49,23 +48,9 @@ public class ContentTripleSourceTDB extends ContentTripleSource {
private Dataset dataset;
private ModelMaker modelMaker;
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasTdbDirectory")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasTdbDirectory", minOccurs = 1, maxOccurs = 1)
public void setTdbPath(String path) {
if (tdbPath == null) {
tdbPath = path;
} else {
throw new IllegalStateException(
"Configuration includes multiple instances of TdbDirectory: "
+ tdbPath + ", and " + path);
}
}
@Validation
public void validate() throws Exception {
if (tdbPath == null) {
throw new IllegalStateException(
"Configuration did not include a TdbDirectory.");
}
tdbPath = path;
}
@Override
@ -106,7 +91,8 @@ public class ContentTripleSourceTDB extends ContentTripleSource {
}
private void checkForFirstTimeStartup() {
if (this.dataset.getNamedModel(ModelNames.TBOX_ASSERTIONS).getGraph().isEmpty()) {
if (this.dataset.getNamedModel(ModelNames.TBOX_ASSERTIONS).getGraph()
.isEmpty()) {
JenaDataSourceSetupBase.thisIsFirstStartup();
}
}

View file

@ -7,7 +7,6 @@ import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.virtuoso.RDFServiceVirtuoso;
import edu.cornell.mannlib.vitro.webapp.triplesource.impl.sparql.ContentTripleSourceSPARQL;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.Validation;
import edu.cornell.mannlib.vitro.webapp.utils.logging.ToString;
/**
@ -19,54 +18,19 @@ public class ContentTripleSourceVirtuoso extends ContentTripleSourceSPARQL {
private String username;
private String password;
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasBaseURI")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasBaseURI", minOccurs = 1, maxOccurs = 1)
public void setBaseUri(String uri) {
if (baseUri == null) {
baseUri = uri;
} else {
throw new IllegalStateException(
"Configuration includes multiple instances of BaseURI: "
+ baseUri + ", and " + uri);
}
baseUri = uri;
}
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasUsername")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasUsername", minOccurs = 1, maxOccurs = 1)
public void setUsername(String user) {
if (username == null) {
username = user;
} else {
throw new IllegalStateException(
"Configuration includes multiple instances of Username: "
+ username + ", and " + user);
}
username = user;
}
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasPassword")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasPassword", minOccurs = 1, maxOccurs = 1)
public void setPassword(String pass) {
if (password == null) {
password = pass;
} else {
throw new IllegalStateException(
"Configuration includes multiple instances of Password: "
+ password + ", and " + pass);
}
}
@Override
@Validation
public void validate() throws Exception {
if (baseUri == null) {
throw new IllegalStateException(
"Configuration did not include a BaseURI.");
}
if (username == null) {
throw new IllegalStateException(
"Configuration did not include a Username.");
}
if (password == null) {
throw new IllegalStateException(
"Configuration did not include a Password.");
}
password = pass;
}
@Override

View file

@ -116,6 +116,7 @@ public class ConfigurationBeanLoader {
WrappedInstance<T> wrapper = InstanceWrapper.wrap(parsedRdf
.getConcreteClass());
wrapper.satisfyInterfaces(ctx, req);
wrapper.checkCardinality(parsedRdf.getPropertyStatements());
wrapper.setProperties(this, parsedRdf.getPropertyStatements());
wrapper.validate();
return wrapper.getInstance();

View file

@ -6,7 +6,9 @@ import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.PropertyMethod;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.PropertyTypeException;
@ -16,11 +18,17 @@ import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.Propert
* instance of the class.
*/
public class InstanceWrapper {
private static final Log log = LogFactory.getLog(InstanceWrapper.class);
public static <T> WrappedInstance<T> wrap(Class<? extends T> concreteClass)
throws InstanceWrapperException {
return new WrappedInstance<T>(createInstance(concreteClass),
parsePropertyAnnotations(concreteClass),
parseValidationAnnotations(concreteClass));
T instance = createInstance(concreteClass);
HashSet<Method> validationMethods = new HashSet<>(
parseValidationAnnotations(concreteClass).values());
Map<String, PropertyMethod> propertyMethods = new PropertyAnnotationsMap(
concreteClass).byUri();
return new WrappedInstance<T>(instance, propertyMethods,
validationMethods);
}
private static <T> T createInstance(Class<? extends T> concreteClass)
@ -33,58 +41,131 @@ public class InstanceWrapper {
}
}
private static Map<String, PropertyMethod> parsePropertyAnnotations(
Class<?> concreteClass) throws InstanceWrapperException {
Map<String, PropertyMethod> map = new HashMap<>();
for (Method method : concreteClass.getDeclaredMethods()) {
Property annotation = method.getAnnotation(Property.class);
if (annotation == null) {
continue;
}
if (!method.getReturnType().equals(Void.TYPE)) {
throw new InstanceWrapperException("Property method '" + method
+ "' should return void.");
}
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1) {
throw new InstanceWrapperException("Property method '" + method
+ "' must accept exactly one parameter.");
}
String uri = annotation.uri();
if (map.containsKey(uri)) {
throw new InstanceWrapperException(
"Two property methods have the same URI value: "
+ map.get(uri).getMethod() + ", and " + method);
}
try {
map.put(uri, PropertyType.createPropertyMethod(method));
} catch (PropertyTypeException e) {
throw new InstanceWrapperException(
"Failed to create the PropertyMethod", e);
private static Map<String, Method> parseValidationAnnotations(Class<?> clazz)
throws InstanceWrapperException {
if (Object.class.equals(clazz)) {
return new HashMap<>();
} else {
Map<String, Method> methods = parseValidationAnnotations(clazz
.getSuperclass());
for (Method method : clazz.getDeclaredMethods()) {
String name = method.getName();
if (methods.containsKey(name)) {
Method m = methods.get(name);
throw new InstanceWrapperException("Method " + name
+ " in " + method.getDeclaringClass().getName()
+ " overrides a validation method in "
+ m.getDeclaringClass().getName());
}
if (method.getAnnotation(Validation.class) == null) {
continue;
}
if (method.getParameterTypes().length > 0) {
throw new InstanceWrapperException("Validation method '"
+ method + "' should not have parameters.");
}
if (!method.getReturnType().equals(Void.TYPE)) {
throw new InstanceWrapperException("Validation method '"
+ method + "' should return void.");
}
methods.put(name, method);
}
return methods;
}
return map;
}
private static Set<Method> parseValidationAnnotations(Class<?> concreteClass)
throws InstanceWrapperException {
Set<Method> methods = new HashSet<>();
for (Method method : concreteClass.getDeclaredMethods()) {
if (method.getAnnotation(Validation.class) == null) {
continue;
private static class PropertyAnnotationsMap {
private Map<String, PropertyMethod> mapByUri = new HashMap<>();
private Map<String, PropertyMethod> mapByName = new HashMap<>();
public PropertyAnnotationsMap(Class<?> clazz)
throws InstanceWrapperException {
if (!Object.class.equals(clazz)) {
populateTheMaps(clazz);
}
if (method.getParameterTypes().length > 0) {
throw new InstanceWrapperException("Validation method '"
+ method + "' should not have parameters.");
}
if (!method.getReturnType().equals(Void.TYPE)) {
throw new InstanceWrapperException("Validation method '"
+ method + "' should return void.");
}
methods.add(method);
}
return methods;
private void populateTheMaps(Class<?> clazz)
throws InstanceWrapperException {
PropertyAnnotationsMap superMap = new PropertyAnnotationsMap(
clazz.getSuperclass());
mapByUri = superMap.byUri();
mapByName = superMap.byName();
for (Method method : clazz.getDeclaredMethods()) {
String name = method.getName();
Method matchByName = methodByName(name);
if (matchByName != null) {
throw new InstanceWrapperException("Method " + name
+ " in " + clazz.getName()
+ " conflicts with a property method in "
+ matchByName.getDeclaringClass().getName());
}
Property annotation = method.getAnnotation(Property.class);
if (annotation == null) {
continue;
}
if (!method.getReturnType().equals(Void.TYPE)) {
throw new InstanceWrapperException("Property method '"
+ method + "' should return void.");
}
if (method.getParameterTypes().length != 1) {
throw new InstanceWrapperException("Property method '"
+ method + "' must accept exactly one parameter.");
}
String uri = annotation.uri();
Method matchByUri = methodByUri(uri);
if (matchByUri != null) {
throw new InstanceWrapperException(
"Two property methods have the same URI value: "
+ matchByUri + ", and " + method);
}
if (annotation.minOccurs() < 0) {
throw new InstanceWrapperException(
"minOccurs must not be negative.");
}
if (annotation.maxOccurs() < annotation.minOccurs()) {
throw new InstanceWrapperException(
"maxOccurs must not be less than minOccurs.");
}
try {
PropertyMethod pm = PropertyType.createPropertyMethod(
method, annotation);
mapByUri.put(uri, pm);
mapByName.put(name, pm);
} catch (PropertyTypeException e) {
throw new InstanceWrapperException(
"Failed to create the PropertyMethod", e);
}
}
}
private Method methodByName(String name) {
PropertyMethod pm = mapByName.get(name);
return (pm == null) ? null : pm.getMethod();
}
private Method methodByUri(String name) {
PropertyMethod pm = mapByUri.get(name);
return (pm == null) ? null : pm.getMethod();
}
public Map<String, PropertyMethod> byUri() {
return mapByUri;
}
public Map<String, PropertyMethod> byName() {
return mapByName;
}
}
public static class InstanceWrapperException extends Exception {

View file

@ -15,4 +15,6 @@ import java.lang.annotation.Target;
@Target(ElementType.METHOD)
public @interface Property {
String uri();
int minOccurs() default 0;
int maxOccurs() default Integer.MAX_VALUE;
}

View file

@ -11,7 +11,6 @@ import java.lang.reflect.Method;
import org.apache.jena.datatypes.RDFDatatype;
import org.apache.jena.datatypes.xsd.impl.RDFLangString;
import org.apache.jena.rdf.model.Literal;
import org.apache.jena.rdf.model.Property;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Statement;
@ -26,38 +25,41 @@ public enum PropertyType {
RESOURCE {
@Override
public PropertyStatement buildPropertyStatement(Statement s) {
return new ResourcePropertyStatement(s.getPredicate(), s
return new ResourcePropertyStatement(s.getPredicate().getURI(), s
.getObject().asResource().getURI());
}
@Override
protected PropertyMethod buildPropertyMethod(Method method) {
return new ResourcePropertyMethod(method);
protected PropertyMethod buildPropertyMethod(Method method,
Property annotation) {
return new ResourcePropertyMethod(method, annotation);
}
},
STRING {
@Override
public PropertyStatement buildPropertyStatement(Statement s) {
return new StringPropertyStatement(s.getPredicate(), s.getObject()
.asLiteral().getString());
return new StringPropertyStatement(s.getPredicate().getURI(), s
.getObject().asLiteral().getString());
}
@Override
protected PropertyMethod buildPropertyMethod(Method method) {
return new StringPropertyMethod(method);
protected PropertyMethod buildPropertyMethod(Method method,
Property annotation) {
return new StringPropertyMethod(method, annotation);
}
},
FLOAT {
@Override
public PropertyStatement buildPropertyStatement(Statement s) {
return new FloatPropertyStatement(s.getPredicate(), s.getObject()
.asLiteral().getFloat());
return new FloatPropertyStatement(s.getPredicate().getURI(), s
.getObject().asLiteral().getFloat());
}
@Override
protected PropertyMethod buildPropertyMethod(Method method) {
return new FloatPropertyMethod(method);
protected PropertyMethod buildPropertyMethod(Method method,
Property annotation) {
return new FloatPropertyMethod(method, annotation);
}
};
@ -101,24 +103,25 @@ public enum PropertyType {
return type.buildPropertyStatement(s);
}
public static PropertyMethod createPropertyMethod(Method method)
throws PropertyTypeException {
public static PropertyMethod createPropertyMethod(Method method,
Property annotation) throws PropertyTypeException {
Class<?> parameterType = method.getParameterTypes()[0];
PropertyType type = PropertyType.typeForParameterType(parameterType);
return type.buildPropertyMethod(method);
return type.buildPropertyMethod(method, annotation);
}
protected abstract PropertyStatement buildPropertyStatement(Statement s);
protected abstract PropertyMethod buildPropertyMethod(Method method);
protected abstract PropertyMethod buildPropertyMethod(Method method,
Property annotation);
public static abstract class PropertyStatement {
private final PropertyType type;
private final String predicateUri;
public PropertyStatement(PropertyType type, Property predicate) {
public PropertyStatement(PropertyType type, String predicateUri) {
this.type = type;
this.predicateUri = predicate.getURI();
this.predicateUri = predicateUri;
}
public PropertyType getType() {
@ -135,8 +138,8 @@ public enum PropertyType {
public static class ResourcePropertyStatement extends PropertyStatement {
private final String objectUri;
public ResourcePropertyStatement(Property predicate, String objectUri) {
super(RESOURCE, predicate);
public ResourcePropertyStatement(String predicateUri, String objectUri) {
super(RESOURCE, predicateUri);
this.objectUri = objectUri;
}
@ -149,8 +152,8 @@ public enum PropertyType {
public static class StringPropertyStatement extends PropertyStatement {
private final String string;
public StringPropertyStatement(Property predicate, String string) {
super(STRING, predicate);
public StringPropertyStatement(String predicateUri, String string) {
super(STRING, predicateUri);
this.string = string;
}
@ -163,8 +166,8 @@ public enum PropertyType {
public static class FloatPropertyStatement extends PropertyStatement {
private final float f;
public FloatPropertyStatement(Property predicate, float f) {
super(FLOAT, predicate);
public FloatPropertyStatement(String predicateUri, float f) {
super(FLOAT, predicateUri);
this.f = f;
}
@ -177,10 +180,23 @@ public enum PropertyType {
public static abstract class PropertyMethod {
protected final PropertyType type;
protected final Method method;
protected final String propertyUri;
protected final int minOccurs;
protected final int maxOccurs;
public PropertyMethod(PropertyType type, Method method) {
// Add cardinality values here! Final, with getters.
public PropertyMethod(PropertyType type, Method method,
Property annotation) {
this.type = type;
this.method = method;
this.propertyUri = annotation.uri();
this.minOccurs = annotation.minOccurs();
this.maxOccurs = annotation.maxOccurs();
checkCardinalityBounds();
}
private void checkCardinalityBounds() {
// This is where we check for negative values or out of order.
}
public Method getMethod() {
@ -191,6 +207,18 @@ public enum PropertyType {
return method.getParameterTypes()[0];
}
public String getPropertyUri() {
return propertyUri;
}
public int getMinOccurs() {
return minOccurs;
}
public int getMaxOccurs() {
return maxOccurs;
}
public void confirmCompatible(PropertyStatement ps)
throws PropertyTypeException {
if (type != ps.getType()) {
@ -213,20 +241,20 @@ public enum PropertyType {
}
public static class ResourcePropertyMethod extends PropertyMethod {
public ResourcePropertyMethod(Method method) {
super(RESOURCE, method);
public ResourcePropertyMethod(Method method, Property annotation) {
super(RESOURCE, method, annotation);
}
}
public static class StringPropertyMethod extends PropertyMethod {
public StringPropertyMethod(Method method) {
super(STRING, method);
public StringPropertyMethod(Method method, Property annotation) {
super(STRING, method, annotation);
}
}
public static class FloatPropertyMethod extends PropertyMethod {
public FloatPropertyMethod(Method method) {
super(FLOAT, method);
public FloatPropertyMethod(Method method, Property annotation) {
super(FLOAT, method, annotation);
}
}

View file

@ -0,0 +1,174 @@
# package edu.cornell.mannlib.vitro.webapp.utils.configuration;
## Overview
### Purpose
This package consists of `ConfigurationBeanLoader` and associated classes.
`ConfigurationBeanLoader` will instantiate and populate objects according to a
description encoded in the triples of an RDF Graph,
and annotations within the Java class of the instantiated object.
The description must include
+ the URI of exactly one concrete Java class, from which the instance will be created.
The description may also include
+ URIs of Java interfaces which the concrete class implements.
The description may be use to satisfy a request
for any of those interfaces, as well as a request for the concrete class.
+ Data properties. These will be passed to "property methods" in the instance
as part of the creation/initialization. The data value must be an untyped
literal (String) or a numeric literal (Float).
+ Object properties. The URI is assumed to be that of another loader description.
The loader will attempt to instantiate the described object, and pass it to
the appropriate property method on the original instance. The result may be a
network of instances, nested to an arbitrary level.
The loader also recognizes two special interfaces: `RequestModelsUser` and `ContextModelsUser`.
If a created instance implements these interfaces,
the loader will provide the appropriate `ModelAccess` object,
allowing the instance to access the Vitro triple-stores.
### Examples of use
#### ApplicationSetup
When Vitro starts up, `ApplicationSetup` uses a `ConfigurationBeanLoader` to instantiate the Vitro's component modules.
The loader creates an RDF Graph from the file `applicationSetup.n3` and instantiates a `SearchEngine` instance,
a `FileStorage` instance, etc.
Here is some RDF that might be used by `ApplicationSetup`:
@prefix : <http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#> .
:application
a <java:edu.cornell.mannlib.vitro.webapp.application.ApplicationImpl> ,
<java:edu.cornell.mannlib.vitro.webapp.modules.Application> ;
:hasSearchEngine :instrumentedSearchEngineWrapper ;
:hasFileStorage :ptiFileStorage .
:ptiFileStorage
a <java:edu.cornell.mannlib.vitro.webapp.filestorage.impl.FileStorageImplWrapper> ,
<java:edu.cornell.mannlib.vitro.webapp.modules.fileStorage.FileStorage> .
:instrumentedSearchEngineWrapper
a <java:edu.cornell.mannlib.vitro.webapp.searchengine.InstrumentedSearchEngineWrapper> ,
<java:edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngine> ;
:wraps :solrSearchEngine .
:solrSearchEngine
a <java:edu.cornell.mannlib.vitro.webapp.searchengine.solr.SolrSearchEngine> ,
<java:edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngine> .
In this case, the `ConfigurationBeanLoader` would be asked to load all instances of
`edu.cornell.mannlib.vitro.webapp.modules.Application`.
The application individual is declared to be both an `Application` and an `ApplicationImpl`.
This is valid because `Application` is an interface, and `ApplicationImpl` implements that interface.
An instance of `ApplicationImpl` will be created.
The application instance has two child objects: a `SearchEngine` and a `FileStorage`.
These objects will also be created, and calls will be made to the application's "property methods" (see below).
The `SearchEngine` in turn has a child object, so that also will be created, and provided to the `SearchEngine`.
#### SearchIndexer
When Vitro's `SearchIndexer` is initialized, it uses a `ConfigurationBeanLoader` to create
lists of `SearchIndexExcluder`s, `DocumentModifier`s, and `IndexingUriFinder`s.
Descriptions of these are taken from Vitro's display model.
## Specifications
### ConfigurationBeanLoader
The principal methods are:
+ `public <T> T loadInstance(String uri, Class<T> resultClass) throws ConfigurationBeanLoaderException`
+ Search the graph for triples that describe the `uri`.
If the description indicates that the individual is of type `resultClass`, create an instance and populate it.
Return a reference to the created instance. Throw an exception if the `uri` does not exist in the graph,
or if the description does not correctly describe an individual.
The `resultClass` may be an interface. In that case, each individual must also have a type statement that refers
to a concrete class that satisfies the interface. An instance of the concrete class will be created.
+ `public <T> Set<T> loadAll(Class<T> resultClass) throws ConfigurationBeanLoaderException`
+ Search the graph for all individuals of type `resultClass`. For each such individual, call `loadInstance`.
Return a set containing the created instances. If no individuals are found, return an empty `Set`.
### Restrictions on instantiated classes.
Each class to be instantiated must have a niladic constructor.
### Property methods
When the loader encounters a data property or an object property in a description,
it will look in the instantiated class for a method tagged with the
`edu.cornell.mannlib.vitro.webapp.utils.configuration.Property` annotation.
For example:
@Property(
uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasConfigurationTripleSource"
minOccurs = 1,
maxOccurs = 3)
public void setTBoxReasonerModule(TBoxReasonerModule module) {
this.module = module;
}
In more detail:
+ A class must contain exactly one method that serves each property URI in the description.
+ The description need not include properies for all of the property methods in the class.
+ Each property method must be public, must have exactly one parameter, and must return null.
+ The name of the property method is immaterial, except that there must not be another method
with the same name in the class.
+ Property methods in superclasses will be recognized and accepted, but they may not be
overridden in a subclass.
+ If `minOccurs` is omitted, the default is `0`. If `minOccurs` is provided, it must be non-negative.
+ If `maxOccurs` is omitted, the default is `MAXINT`. If `maxOccurs` is provided, it must not be less than `minOccurs`.
When instantiating:
+ The parameter on a property method must match the value supplied in the RDF description.
+ If the type of the parameter is `Float`, the object of the triple must be a numeric literal, or
an untyped literal that can be parsed as a number.
+ If the type of the parameter is `String`, the object of the triple must be a String literal or an untyped literal.
+ If the type of the parameter is another class, then the object of the triple must be the URI of
another RDF description, from which the loader can create an instance of the required class.
+ The number of values for a given property URI must not be less than the `minOccurs` value on the corresponding property method.
+ The number of values for a given property URI must not be greater than the `maxOccurs` value on the corresponding property method.
### Validation methods
When the loader has satisfied all of the properties in an instance, it will
look in the instantiated class for any methods tagged with the
`edu.cornell.mannlib.vitro.webapp.utils.configuration.Validation` annotation.
For example:
@Validation
public void validate() throws Exception {
if (baseUri == null) {
throw new IllegalStateException(
"Configuration did not include a BaseURI.");
}
}
Each such method will be called by the loader, and provides a opportunity to
confirm that the bean has been properly initialized.
Again, in detail:
+ Each validation method must be public, must accept no parameters, and must return null.
+ The name of the validation method is immaterial, except that there must not be another
+ method with the same name in the lass.
+ Validation methods in superclasses will be called, but may not be overridden in a subclass.
### Life cycle
For each instance that the loader creates, the loader will:
+ Call the appropriate property method for each property in the description.
For object properties, this includes recursive calls to create subordinate objects.
The order of property method calls is undefined.
+ Call the validation methods on the class. The order of validation method calls is undefined.
If any property method or validation method throws an exception, the process stops,
and the exception is propagated upward.

View file

@ -5,6 +5,7 @@ package edu.cornell.mannlib.vitro.webapp.utils.configuration;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@ -63,6 +64,45 @@ public class WrappedInstance<T> {
}
}
/**
* The loader provides the distilled property statements from the RDF. Check
* that they satisfy the cardinality requested on their methods.
*/
public void checkCardinality(Set<PropertyStatement> propertyStatements)
throws CardinalityException {
Map<String, Integer> statementCounts = countPropertyStatementsByPredicateUri(propertyStatements);
for (PropertyMethod pm : propertyMethods.values()) {
Integer c = statementCounts.get(pm.getPropertyUri());
int count = (c == null) ? 0 : c;
if (count < pm.getMinOccurs()) {
throw new CardinalityException("Expecting at least "
+ pm.getMinOccurs() + " values for '"
+ pm.getPropertyUri() + "', but found " + count + ".");
}
if (count > pm.getMaxOccurs()) {
throw new CardinalityException("Expecting no more than "
+ pm.getMaxOccurs() + " values for '"
+ pm.getPropertyUri() + "', but found " + count + ".");
}
}
statementCounts.hashCode();
}
private Map<String, Integer> countPropertyStatementsByPredicateUri(
Set<PropertyStatement> propertyStatements) {
Map<String, Integer> statementCounts = new HashMap<>();
for (String pmPredicateUri : propertyMethods.keySet()) {
int count = 0;
for (PropertyStatement ps : propertyStatements) {
if (ps.getPredicateUri().equals(pmPredicateUri)) {
count++;
}
}
statementCounts.put(pmPredicateUri, count);
}
return statementCounts;
}
/**
* The loader provides the distilled property statements from the RDF, to
* populate the instance.
@ -76,7 +116,6 @@ public class WrappedInstance<T> {
if (pm == null) {
throw new NoSuchPropertyMethodException(ps);
}
pm.confirmCompatible(ps);
if (ps instanceof ResourcePropertyStatement) {
@ -132,4 +171,10 @@ public class WrappedInstance<T> {
}
}
public static class CardinalityException extends Exception {
public CardinalityException(String message) {
super(message);
}
}
}

View file

@ -43,6 +43,12 @@ public class ModelUtilitiesTestHelper {
createProperty(propertyUri), createPlainLiteral(objectValue));
}
public static Statement dataProperty(String subjectUri, String propertyUri,
Float objectValue) {
return createStatement(createResource(subjectUri),
createProperty(propertyUri), createTypedLiteral(objectValue));
}
public static Statement dataProperty(String subjectUri, String propertyUri,
Object objectValue, XSDDatatype dataType) {
return createStatement(createResource(subjectUri),

View file

@ -5,7 +5,6 @@ package edu.cornell.mannlib.vitro.webapp.utils.configuration;
import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDfloat;
import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDstring;
import static edu.cornell.mannlib.vitro.testing.ModelUtilitiesTestHelper.dataProperty;
import static edu.cornell.mannlib.vitro.testing.ModelUtilitiesTestHelper.model;
import static edu.cornell.mannlib.vitro.testing.ModelUtilitiesTestHelper.objectProperty;
import static edu.cornell.mannlib.vitro.testing.ModelUtilitiesTestHelper.typeStatement;
import static edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoader.toJavaUri;
@ -20,7 +19,6 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@ -32,16 +30,13 @@ import stubs.javax.servlet.http.HttpSessionStub;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.Statement;
import edu.cornell.mannlib.vitro.testing.AbstractTestClass;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ContextModelAccess;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.ModelAccessFactory;
import edu.cornell.mannlib.vitro.webapp.modelaccess.RequestModelAccess;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationRdfParser.InvalidConfigurationRdfException;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.InstanceWrapper.InstanceWrapperException;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.PropertyTypeException;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.WrappedInstance.NoSuchPropertyMethodException;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.WrappedInstance.ResourceUnavailableException;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.WrappedInstance.ValidationFailedException;
/**
* TODO
@ -50,47 +45,8 @@ import edu.cornell.mannlib.vitro.webapp.utils.configuration.WrappedInstance.Vali
* instances by URIs, so if a property refers to a created instance, we just
* pass it in.
*/
public class ConfigurationBeanLoaderTest extends AbstractTestClass {
private static final String GENERIC_INSTANCE_URI = "http://mytest.edu/some_instance";
private static final String GENERIC_PROPERTY_URI = "http://mytest.edu/some_property";
private static final String SIMPLE_SUCCESS_INSTANCE_URI = "http://mytest.edu/simple_success_instance";
private static final String FULL_SUCCESS_INSTANCE_URI = "http://mytest.edu/full_success_instance";
private static final String FULL_SUCCESS_BOOST_PROPERTY = "http://mydomain.edu/hasBoost";
private static final String FULL_SUCCESS_TEXT_PROPERTY = "http://mydomain.edu/hasText";
private static final String FULL_SUCCESS_HELPER_PROPERTY = "http://mydomain.edu/hasHelper";
private static final String FULL_SUCCESS_HELPER_INSTANCE_URI = "http://mytest.edu/full_success_helper_instance";
private ServletContextStub ctx;
private HttpSessionStub session;
private HttpServletRequestStub req;
private Model model;
private ConfigurationBeanLoader loader;
private ConfigurationBeanLoader noRequestLoader;
private ConfigurationBeanLoader noContextLoader;
@Before
public void setup() {
ctx = new ServletContextStub();
session = new HttpSessionStub();
session.setServletContext(ctx);
req = new HttpServletRequestStub();
req.setSession(session);
@SuppressWarnings("unused")
ModelAccessFactory maf = new ModelAccessFactoryStub();
model = model();
loader = new ConfigurationBeanLoader(model, req);
noRequestLoader = new ConfigurationBeanLoader(model, ctx);
noContextLoader = new ConfigurationBeanLoader(model);
}
public class ConfigurationBeanLoaderTest extends
ConfigurationBeanLoaderTestBase {
// ----------------------------------------------------------------------
// Constructor tests
@ -308,276 +264,6 @@ public class ConfigurationBeanLoaderTest extends AbstractTestClass {
// --------------------------------------------
@Test
public void propertyMethodHasNoParameter_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(NoParameterOnPropertyMethod.class)));
expectSimpleFailure(
NoParameterOnPropertyMethod.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class,
"must accept exactly one parameter"));
}
public static class NoParameterOnPropertyMethod {
@Property(uri = GENERIC_PROPERTY_URI)
public void methodTakesNoParameters() {
// Not suitable as a property method.
}
}
// --------------------------------------------
@Test
public void propertyMethodHasMultipleParameters_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(MultipleParametersOnPropertyMethod.class)));
expectSimpleFailure(
MultipleParametersOnPropertyMethod.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class,
"must accept exactly one parameter"));
}
public static class MultipleParametersOnPropertyMethod {
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI)
public void methodTakesMultipleParameters(String s, Float f) {
// Not suitable as a property method.
}
}
// --------------------------------------------
@Test
public void propertyMethodHasInvalidParameter_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(InvalidParameterOnPropertyMethod.class)));
expectSimpleFailure(
InvalidParameterOnPropertyMethod.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class,
"Failed to create the PropertyMethod"));
}
public static class InvalidParameterOnPropertyMethod {
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI)
public void methodTakesInvalidParameters(byte b) {
// Not suitable as a property method.
}
}
// --------------------------------------------
@Test
public void propertyMethodDoesNotReturnVoid_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(PropertyMethodMustReturnVoid.class)));
expectSimpleFailure(
PropertyMethodMustReturnVoid.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class, "should return void"));
}
public static class PropertyMethodMustReturnVoid {
@Property(uri = GENERIC_PROPERTY_URI)
public String methodReturnIsNotVoid(String s) {
// Not suitable as a property method.
return s;
}
}
// --------------------------------------------
@Test
public void propertyMethodNotAccessible_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(PropertyMethodIsPrivate.class)));
model.add(dataProperty(GENERIC_INSTANCE_URI, GENERIC_PROPERTY_URI,
"can't store in a private method."));
expectSimpleFailure(
PropertyMethodIsPrivate.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(PropertyTypeException.class,
"Property method failed."));
}
public static class PropertyMethodIsPrivate {
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI)
private void methodReturnIsNotVoid(String s) {
// Not suitable as a property method.
}
}
// --------------------------------------------
@Test
public void propertyMethodThrowsException_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(PropertyMethodFails.class)));
model.add(dataProperty(GENERIC_INSTANCE_URI, GENERIC_PROPERTY_URI,
"exception while loading."));
expectSimpleFailure(
PropertyMethodFails.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(PropertyTypeException.class,
"Property method failed."));
}
public static class PropertyMethodFails {
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI)
public void methodThrowsException(String s) {
if (true) {
throw new RuntimeException("property method fails.");
}
}
}
// --------------------------------------------
@Test
public void propertyMethodDuplicateUri_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(TwoMethodsWithSameUri.class)));
expectSimpleFailure(
TwoMethodsWithSameUri.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class,
"methods have the same URI"));
}
public static class TwoMethodsWithSameUri {
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI)
public void firstProperty(String s) {
// Nothing to do
}
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI)
public void secondProperty(String s) {
// Nothing to do
}
}
// --------------------------------------------
@Test
public void validationMethodHasParameters_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(ValidationMethodWithParameter.class)));
expectSimpleFailure(
ValidationMethodWithParameter.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class,
"should not have parameters"));
}
public static class ValidationMethodWithParameter {
@SuppressWarnings("unused")
@Validation
public void validateWithParameter(String s) {
// Nothing to do
}
}
// --------------------------------------------
@Test
public void validationMethodDoesNotReturnVoid_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(ValidationMethodShouldReturnVoid.class)));
expectSimpleFailure(
ValidationMethodShouldReturnVoid.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class, "should return void"));
}
public static class ValidationMethodShouldReturnVoid {
@Validation
public String validateWithReturnType() {
return "Hi there!";
}
}
// --------------------------------------------
@Test
public void validationMethodNotAccessible_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(ValidationMethodIsPrivate.class)));
expectSimpleFailure(
ValidationMethodIsPrivate.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(ValidationFailedException.class,
"Error executing validation method"));
}
public static class ValidationMethodIsPrivate {
@Validation
private void validateIsPrivate() {
// private method
}
}
// --------------------------------------------
@Test
public void validationMethodThrowsException_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(ValidationThrowsException.class)));
expectSimpleFailure(
ValidationThrowsException.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(ValidationFailedException.class,
"Error executing validation method"));
}
public static class ValidationThrowsException {
@Validation
public void validateFails() {
throw new RuntimeException("from validation method");
}
}
// --------------------------------------------
@Test
public void loaderCantSatisfyContextModelsUser_throwsException()
throws ConfigurationBeanLoaderException {
@ -1041,6 +727,7 @@ public class ConfigurationBeanLoaderTest extends AbstractTestClass {
// Additional tests
// ----------------------------------------------------------------------
@SuppressWarnings("unused")
@Test
@Ignore
// TODO
@ -1049,6 +736,7 @@ public class ConfigurationBeanLoaderTest extends AbstractTestClass {
fail("circularReferencesAreNotFatal not implemented");
}
@SuppressWarnings("unused")
@Test
@Ignore
// TODO deals with circularity.
@ -1057,6 +745,7 @@ public class ConfigurationBeanLoaderTest extends AbstractTestClass {
fail("subordinateObjectCantBeLoaded_leavesNoAccessibleInstanceOfParent not implemented");
}
@SuppressWarnings("unused")
@Test
@Ignore
// TODO deals with circularity.
@ -1065,42 +754,4 @@ public class ConfigurationBeanLoaderTest extends AbstractTestClass {
fail("parentObjectCantBeLoaded_leavesNoAccessibleInstanceOfSubordinate not implemented");
}
// ----------------------------------------------------------------------
// Helper methods for simple failure
// ----------------------------------------------------------------------
private void expectSimpleFailure(Class<?> failureClass,
ExpectedThrowable expected, ExpectedThrowable cause)
throws ConfigurationBeanLoaderException {
expectException(expected.getClazz(), expected.getMessageSubstring(),
cause.getClazz(), cause.getMessageSubstring());
@SuppressWarnings("unused")
Object unused = loader.loadInstance(GENERIC_INSTANCE_URI, failureClass);
}
private ExpectedThrowable throwable(Class<? extends Throwable> clazz,
String messageSubstring) {
return new ExpectedThrowable(clazz, messageSubstring);
}
private static class ExpectedThrowable {
private final Class<? extends Throwable> clazz;
private final String messageSubstring;
public ExpectedThrowable(Class<? extends Throwable> clazz,
String messageSubstring) {
this.clazz = clazz;
this.messageSubstring = messageSubstring;
}
public Class<? extends Throwable> getClazz() {
return clazz;
}
public String getMessageSubstring() {
return messageSubstring;
}
}
}

View file

@ -0,0 +1,100 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.utils.configuration;
import static edu.cornell.mannlib.vitro.testing.ModelUtilitiesTestHelper.model;
import org.apache.jena.rdf.model.Model;
import org.junit.Before;
import edu.cornell.mannlib.vitro.testing.AbstractTestClass;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.ModelAccessFactory;
import stubs.edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccessFactoryStub;
import stubs.javax.servlet.ServletContextStub;
import stubs.javax.servlet.http.HttpServletRequestStub;
import stubs.javax.servlet.http.HttpSessionStub;
/**
* TODO
*/
public class ConfigurationBeanLoaderTestBase extends AbstractTestClass {
protected static final String GENERIC_INSTANCE_URI = "http://mytest.edu/some_instance";
protected static final String GENERIC_PROPERTY_URI = "http://mytest.edu/some_property";
protected static final String SIMPLE_SUCCESS_INSTANCE_URI = "http://mytest.edu/simple_success_instance";
protected static final String FULL_SUCCESS_INSTANCE_URI = "http://mytest.edu/full_success_instance";
protected static final String FULL_SUCCESS_BOOST_PROPERTY = "http://mydomain.edu/hasBoost";
protected static final String FULL_SUCCESS_TEXT_PROPERTY = "http://mydomain.edu/hasText";
protected static final String FULL_SUCCESS_HELPER_PROPERTY = "http://mydomain.edu/hasHelper";
protected static final String FULL_SUCCESS_HELPER_INSTANCE_URI = "http://mytest.edu/full_success_helper_instance";
private ServletContextStub ctx;
private HttpSessionStub session;
private HttpServletRequestStub req;
protected Model model;
protected ConfigurationBeanLoader loader;
protected ConfigurationBeanLoader noRequestLoader;
protected ConfigurationBeanLoader noContextLoader;
@Before
public void setup() {
ctx = new ServletContextStub();
session = new HttpSessionStub();
session.setServletContext(ctx);
req = new HttpServletRequestStub();
req.setSession(session);
@SuppressWarnings("unused")
ModelAccessFactory maf = new ModelAccessFactoryStub();
model = model();
loader = new ConfigurationBeanLoader(model, req);
noRequestLoader = new ConfigurationBeanLoader(model, ctx);
noContextLoader = new ConfigurationBeanLoader(model);
}
// ----------------------------------------------------------------------
// Helper methods for simple failure
// ----------------------------------------------------------------------
protected void expectSimpleFailure(Class<?> failureClass,
ExpectedThrowable expected, ExpectedThrowable cause)
throws ConfigurationBeanLoaderException {
expectException(expected.getClazz(), expected.getMessageSubstring(),
cause.getClazz(), cause.getMessageSubstring());
@SuppressWarnings("unused")
Object unused = loader.loadInstance(GENERIC_INSTANCE_URI, failureClass);
}
protected ExpectedThrowable throwable(Class<? extends Throwable> clazz,
String messageSubstring) {
return new ExpectedThrowable(clazz, messageSubstring);
}
private static class ExpectedThrowable {
private final Class<? extends Throwable> clazz;
private final String messageSubstring;
public ExpectedThrowable(Class<? extends Throwable> clazz,
String messageSubstring) {
this.clazz = clazz;
this.messageSubstring = messageSubstring;
}
public Class<? extends Throwable> getClazz() {
return clazz;
}
public String getMessageSubstring() {
return messageSubstring;
}
}
}

View file

@ -0,0 +1,251 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.utils.configuration;
import static edu.cornell.mannlib.vitro.testing.ModelUtilitiesTestHelper.dataProperty;
import static edu.cornell.mannlib.vitro.testing.ModelUtilitiesTestHelper.objectProperty;
import static edu.cornell.mannlib.vitro.testing.ModelUtilitiesTestHelper.typeStatement;
import static edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoader.toJavaUri;
import static org.junit.Assert.assertEquals;
import java.util.Set;
import org.apache.jena.rdf.model.Statement;
import org.junit.Test;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.InstanceWrapper.InstanceWrapperException;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.WrappedInstance.CardinalityException;
/**
* Tests for minOccurs, maxOccurs on the @Property annotation.
*/
public class ConfigurationBeanLoader_Cardinality_Test extends
ConfigurationBeanLoaderTestBase {
private static final Statement[] FOUND_NONE = new Statement[0];
private static final Statement[] FOUND_ONE = new Statement[] { dataProperty(
GENERIC_INSTANCE_URI, GENERIC_PROPERTY_URI, "value One") };
private static final Statement[] FOUND_TWO = new Statement[] {
dataProperty(GENERIC_INSTANCE_URI, GENERIC_PROPERTY_URI,
"value One"),
dataProperty(GENERIC_INSTANCE_URI, GENERIC_PROPERTY_URI,
"value Two") };
private static final Statement[] FOUND_THREE = new Statement[] {
dataProperty(GENERIC_INSTANCE_URI, GENERIC_PROPERTY_URI,
"value One"),
dataProperty(GENERIC_INSTANCE_URI, GENERIC_PROPERTY_URI,
"value Two"),
dataProperty(GENERIC_INSTANCE_URI, GENERIC_PROPERTY_URI,
"value Three") };
private static final Statement[] FOUND_FOUR = new Statement[] {
dataProperty(GENERIC_INSTANCE_URI, GENERIC_PROPERTY_URI,
"value One"),
dataProperty(GENERIC_INSTANCE_URI, GENERIC_PROPERTY_URI,
"value Two"),
dataProperty(GENERIC_INSTANCE_URI, GENERIC_PROPERTY_URI,
"value Three"),
dataProperty(GENERIC_INSTANCE_URI, GENERIC_PROPERTY_URI,
"value Four") };
// --------------------------------------------
@Test
public void minOccursIsNegative_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(NegativeMinOccurs.class)));
model.add(objectProperty(GENERIC_INSTANCE_URI, GENERIC_PROPERTY_URI,
"http://some.other/uri"));
expectSimpleFailure(
NegativeMinOccurs.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class,
"must not be negative"));
}
public static class NegativeMinOccurs {
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI, minOccurs = -1)
public void setValue(String value) {
// Nothing to do
}
}
// --------------------------------------------
@Test
public void maxOccursLessThanMinOccurs_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(MaxOccursLessThanMinOccurs.class)));
model.add(objectProperty(GENERIC_INSTANCE_URI, GENERIC_PROPERTY_URI,
"http://some.other/uri"));
expectSimpleFailure(
MaxOccursLessThanMinOccurs.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class,
"not be less than minOccurs"));
}
public static class MaxOccursLessThanMinOccurs {
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI, minOccurs = 2, maxOccurs = 1)
public void setValue(String value) {
// Nothing to do
}
}
// --------------------------------------------
@Test
public void expectingSomeFoundNone_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(ExpectingSome.class)));
model.add(FOUND_NONE);
expectSimpleFailure(
ExpectingSome.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(CardinalityException.class, "Expecting at least 2"));
}
@Test
public void expectingSomeFoundFewer_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(ExpectingSome.class)));
model.add(FOUND_ONE);
expectSimpleFailure(
ExpectingSome.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(CardinalityException.class, "Expecting at least 2"));
}
@Test
public void expectingSomeFoundMore_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(ExpectingSome.class)));
model.add(FOUND_FOUR);
expectSimpleFailure(
ExpectingSome.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(CardinalityException.class,
"Expecting no more than 3"));
}
@Test
public void expectingSomeFoundSome_success()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(ExpectingSome.class)));
model.add(FOUND_THREE);
Set<ExpectingSome> instances = loader.loadAll(ExpectingSome.class);
assertEquals(1, instances.size());
model.removeAll();
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(ExpectingSome.class)));
model.add(FOUND_TWO);
instances = loader.loadAll(ExpectingSome.class);
assertEquals(1, instances.size());
}
public static class ExpectingSome {
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI, minOccurs = 2, maxOccurs = 3)
public void setValue(String value) {
// Nothing to do
}
}
// --------------------------------------------
@Test
public void notSpecifiedFoundNone_success()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(CardinalityNotSpecified.class)));
model.add(FOUND_NONE);
Set<CardinalityNotSpecified> instances = loader
.loadAll(CardinalityNotSpecified.class);
assertEquals(1, instances.size());
}
@Test
public void notSpecifiedFoundSome_success()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(CardinalityNotSpecified.class)));
model.add(FOUND_FOUR);
Set<CardinalityNotSpecified> instances = loader
.loadAll(CardinalityNotSpecified.class);
assertEquals(1, instances.size());
}
public static class CardinalityNotSpecified {
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI)
public void setValue(String value) {
// Nothing to do
}
}
// --------------------------------------------
@Test
public void expectNoneFoundNone_success()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(ExpectNone.class)));
model.add(FOUND_NONE);
Set<ExpectNone> instances = loader.loadAll(ExpectNone.class);
assertEquals(1, instances.size());
}
public static class ExpectNone {
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI, maxOccurs = 0)
public void setValue(String value) {
// Nothing to do
}
}
// --------------------------------------------
@Test
public void expectExactlyFoundExactly_success()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(ExpectTwo.class)));
model.add(FOUND_TWO);
Set<ExpectTwo> instances = loader.loadAll(ExpectTwo.class);
assertEquals(1, instances.size());
}
public static class ExpectTwo {
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI, minOccurs = 2, maxOccurs = 2)
public void setValue(String value) {
// Nothing to do
}
}
}

View file

@ -0,0 +1,338 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.utils.configuration;
import static edu.cornell.mannlib.vitro.testing.ModelUtilitiesTestHelper.dataProperty;
import static edu.cornell.mannlib.vitro.testing.ModelUtilitiesTestHelper.typeStatement;
import static edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoader.toJavaUri;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.apache.jena.rdf.model.Statement;
import org.junit.Test;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.InstanceWrapper.InstanceWrapperException;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.PropertyTypeException;
/**
* Tests of the @Property annotation.
*/
public class ConfigurationBeanLoader_PropertyTest extends
ConfigurationBeanLoaderTestBase {
protected static final String OTHER_PROPERTY_URI = "http://mytest.edu/different_property";
// --------------------------------------------
@Test
public void propertyMethodHasNoParameter_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(NoParameterOnPropertyMethod.class)));
expectSimpleFailure(
NoParameterOnPropertyMethod.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class,
"must accept exactly one parameter"));
}
public static class NoParameterOnPropertyMethod {
@Property(uri = GENERIC_PROPERTY_URI)
public void methodTakesNoParameters() {
// Not suitable as a property method.
}
}
// --------------------------------------------
@Test
public void propertyMethodHasMultipleParameters_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(MultipleParametersOnPropertyMethod.class)));
expectSimpleFailure(
MultipleParametersOnPropertyMethod.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class,
"must accept exactly one parameter"));
}
public static class MultipleParametersOnPropertyMethod {
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI)
public void methodTakesMultipleParameters(String s, Float f) {
// Not suitable as a property method.
}
}
// --------------------------------------------
@Test
public void propertyMethodHasInvalidParameter_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(InvalidParameterOnPropertyMethod.class)));
expectSimpleFailure(
InvalidParameterOnPropertyMethod.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class,
"Failed to create the PropertyMethod"));
}
public static class InvalidParameterOnPropertyMethod {
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI)
public void methodTakesInvalidParameters(byte b) {
// Not suitable as a property method.
}
}
// --------------------------------------------
@Test
public void propertyMethodDoesNotReturnVoid_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(PropertyMethodMustReturnVoid.class)));
expectSimpleFailure(
PropertyMethodMustReturnVoid.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class, "should return void"));
}
public static class PropertyMethodMustReturnVoid {
@Property(uri = GENERIC_PROPERTY_URI)
public String methodReturnIsNotVoid(String s) {
// Not suitable as a property method.
return s;
}
}
// --------------------------------------------
@Test
public void propertyMethodNotAccessible_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(PropertyMethodIsPrivate.class)));
model.add(dataProperty(GENERIC_INSTANCE_URI, GENERIC_PROPERTY_URI,
"can't store in a private method."));
expectSimpleFailure(
PropertyMethodIsPrivate.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(PropertyTypeException.class,
"Property method failed."));
}
public static class PropertyMethodIsPrivate {
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI)
private void methodReturnIsNotVoid(String s) {
// Not suitable as a property method.
}
}
// --------------------------------------------
@Test
public void propertyMethodThrowsException_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(PropertyMethodFails.class)));
model.add(dataProperty(GENERIC_INSTANCE_URI, GENERIC_PROPERTY_URI,
"exception while loading."));
expectSimpleFailure(
PropertyMethodFails.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(PropertyTypeException.class,
"Property method failed."));
}
public static class PropertyMethodFails {
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI)
public void methodThrowsException(String s) {
if (true) {
throw new RuntimeException("property method fails.");
}
}
}
// --------------------------------------------
@Test
public void propertyMethodDuplicateUri_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(TwoMethodsWithSameUri.class)));
expectSimpleFailure(
TwoMethodsWithSameUri.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class,
"methods have the same URI"));
}
public static class TwoMethodsWithSameUri {
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI)
public void firstProperty(String s) {
// Nothing to do
}
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI)
public void secondProperty(String s) {
// Nothing to do
}
}
// --------------------------------------------
@Test
public void superclassContainsPropertyAnnotation_success()
throws ConfigurationBeanLoaderException {
model.add(new Statement[] {
typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(EmptyPropertyMethodSubclass.class)),
dataProperty(GENERIC_INSTANCE_URI, GENERIC_PROPERTY_URI,
"Value") });
EmptyPropertyMethodSubclass instance = loader.loadInstance(
GENERIC_INSTANCE_URI, EmptyPropertyMethodSubclass.class);
assertNotNull(instance);
assertEquals("Value", instance.value);
}
@Test
public void propertyMethodOverridesPropertyMethod_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(PropertyMethodOverPropertyMethodSubclass.class)));
expectSimpleFailure(
PropertyMethodOverPropertyMethodSubclass.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class,
"conflicts with a property method"));
}
@Test
public void plainMethodOverridesPropertyMethod_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(PlainOverPropertyMethodSubclass.class)));
expectSimpleFailure(
PlainOverPropertyMethodSubclass.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class,
"conflicts with a property method"));
}
@Test
public void uriConflictsBetweenSubclassAndSuperclassPropertyMethods_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(ConflictingUriPropertyMethodSubclass.class)));
expectSimpleFailure(
ConflictingUriPropertyMethodSubclass.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class,
"Two property methods have the same URI"));
}
@Test
public void propertyMethodSameNameButDoesNotOverride_throwsException()
throws ConfigurationBeanLoaderException {
model.add(new Statement[] {
typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(DistinctPropertyMethodSubclass.class)),
dataProperty(GENERIC_INSTANCE_URI, GENERIC_PROPERTY_URI,
"Value"),
dataProperty(GENERIC_INSTANCE_URI, OTHER_PROPERTY_URI,
100.0F) });
expectSimpleFailure(
DistinctPropertyMethodSubclass.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class,
"conflicts with a property method"));
}
public static class PropertyMethodSuperclass {
public String value = null;
@Property(uri = GENERIC_PROPERTY_URI)
public void propertySuper(String v) {
if (value != null) {
throw new RuntimeException("propertySuper has already run.");
}
value = v;
}
}
public static class EmptyPropertyMethodSubclass extends
PropertyMethodSuperclass {
// Just want to see that the superclass method is run.
}
public static class DistinctPropertyMethodSubclass extends
PropertyMethodSuperclass {
public float fvalue;
@Property(uri = OTHER_PROPERTY_URI)
public void propertySuper(Float f) {
if (fvalue != 0.0) {
throw new RuntimeException("propertySub has already run.");
}
fvalue = f;
}
}
public static class ConflictingUriPropertyMethodSubclass extends
PropertyMethodSuperclass {
@Property(uri = GENERIC_PROPERTY_URI)
public void propertyConflict(String v) {
// nothing to do.
}
}
public static class PropertyMethodOverPropertyMethodSubclass extends
EmptyPropertyMethodSubclass {
@Override
@SuppressWarnings("unused")
@Property(uri = GENERIC_PROPERTY_URI)
public void propertySuper(String v) {
// Should fail (two levels down)
}
}
public static class PlainOverPropertyMethodSubclass extends
PropertyMethodSuperclass {
@SuppressWarnings("unused")
public void propertySuper(Float f) {
// nothing to do
}
}
}

View file

@ -0,0 +1,215 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.utils.configuration;
import static edu.cornell.mannlib.vitro.testing.ModelUtilitiesTestHelper.typeStatement;
import static edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoader.toJavaUri;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.InstanceWrapper.InstanceWrapperException;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.WrappedInstance.ValidationFailedException;
/**
* Test the @Validation annotation.
*/
public class ConfigurationBeanLoader_ValidationTest extends
ConfigurationBeanLoaderTestBase {
// --------------------------------------------
@Test
public void validationMethodHasParameters_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(ValidationMethodWithParameter.class)));
expectSimpleFailure(
ValidationMethodWithParameter.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class,
"should not have parameters"));
}
public static class ValidationMethodWithParameter {
@SuppressWarnings("unused")
@Validation
public void validateWithParameter(String s) {
// Nothing to do
}
}
// --------------------------------------------
@Test
public void validationMethodDoesNotReturnVoid_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(ValidationMethodShouldReturnVoid.class)));
expectSimpleFailure(
ValidationMethodShouldReturnVoid.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class, "should return void"));
}
public static class ValidationMethodShouldReturnVoid {
@Validation
public String validateWithReturnType() {
return "Hi there!";
}
}
// --------------------------------------------
@Test
public void validationMethodNotAccessible_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(ValidationMethodIsPrivate.class)));
expectSimpleFailure(
ValidationMethodIsPrivate.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(ValidationFailedException.class,
"Error executing validation method"));
}
public static class ValidationMethodIsPrivate {
@Validation
private void validateIsPrivate() {
// private method
}
}
// --------------------------------------------
@Test
public void validationMethodThrowsException_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(ValidationThrowsException.class)));
expectSimpleFailure(
ValidationThrowsException.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(ValidationFailedException.class,
"Error executing validation method"));
}
public static class ValidationThrowsException {
@Validation
public void validateFails() {
throw new RuntimeException("from validation method");
}
}
// --------------------------------------------
@Test
public void superclassContainsValidationMethod_success()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(EmptyValidationSubclass.class)));
EmptyValidationSubclass instance = loader.loadInstance(
GENERIC_INSTANCE_URI, EmptyValidationSubclass.class);
assertNotNull(instance);
assertTrue(instance.validatorSuperHasRun);
}
@Test
public void superclassAndSubclassContainValidationMethods_success()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(AdditionalValidationSubclass.class)));
AdditionalValidationSubclass instance = loader.loadInstance(
GENERIC_INSTANCE_URI, AdditionalValidationSubclass.class);
assertNotNull(instance);
assertTrue(instance.validatorSuperHasRun);
assertTrue(instance.validatorSubHasRun);
}
@Test
public void validationMethodOverridesValidationMethod_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(ValidationOverValidationSubclass.class)));
expectSimpleFailure(
ValidationOverValidationSubclass.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class,
"overrides a validation method"));
}
@Test
public void plainMethodOverridesValidationMethod_throwsException()
throws ConfigurationBeanLoaderException {
model.add(typeStatement(GENERIC_INSTANCE_URI,
toJavaUri(PlainOverValidationSubclass.class)));
expectSimpleFailure(
PlainOverValidationSubclass.class,
throwable(ConfigurationBeanLoaderException.class,
"Failed to load"),
throwable(InstanceWrapperException.class,
"overrides a validation method"));
}
public static class ValidationSuperclass {
public boolean validatorSuperHasRun = false;
@Validation
public void validatorSuper() {
if (validatorSuperHasRun) {
throw new RuntimeException("validatorSuper has already run.");
}
validatorSuperHasRun = true;
}
}
public static class EmptyValidationSubclass extends ValidationSuperclass {
// Just want to see that the superclass validation is run.
}
public static class AdditionalValidationSubclass extends
ValidationSuperclass {
public boolean validatorSubHasRun = false;
@Validation
public void validatorSub() {
if (validatorSubHasRun) {
throw new RuntimeException("validatorSub has already run.");
}
validatorSubHasRun = true;
}
}
public static class ValidationOverValidationSubclass extends
EmptyValidationSubclass {
@Override
@Validation
public void validatorSuper() {
// Should fail (two levels down)
}
}
public static class PlainOverValidationSubclass extends
ValidationSuperclass {
@Override
public void validatorSuper() {
// Should fail
}
}
}