From 481a2152065e24697446fae6cf9c0e9fe6d59fe4 Mon Sep 17 00:00:00 2001 From: Jim Blake Date: Sun, 29 May 2016 10:25:23 -0400 Subject: [PATCH 1/9] 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 --- .../ConfigurationBeanLoader.java | 1 + .../utils/configuration/InstanceWrapper.java | 181 ++++++--- .../webapp/utils/configuration/Property.java | 2 + .../utils/configuration/PropertyType.java | 90 +++-- .../webapp/utils/configuration/README.md | 174 +++++++++ .../utils/configuration/WrappedInstance.java | 47 ++- .../testing/ModelUtilitiesTestHelper.java | 6 + .../ConfigurationBeanLoaderTest.java | 364 +----------------- .../ConfigurationBeanLoaderTestBase.java | 102 +++++ ...figurationBeanLoader_Cardinality_Test.java | 252 ++++++++++++ .../ConfigurationBeanLoader_PropertyTest.java | 339 ++++++++++++++++ ...onfigurationBeanLoader_ValidationTest.java | 215 +++++++++++ 12 files changed, 1332 insertions(+), 441 deletions(-) create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/README.md create mode 100644 webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTestBase.java create mode 100644 webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_Cardinality_Test.java create mode 100644 webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_PropertyTest.java create mode 100644 webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_ValidationTest.java diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader.java index fa7249297..36d399224 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader.java @@ -116,6 +116,7 @@ public class ConfigurationBeanLoader { WrappedInstance wrapper = InstanceWrapper.wrap(parsedRdf .getConcreteClass()); wrapper.satisfyInterfaces(ctx, req); + wrapper.checkCardinality(parsedRdf.getPropertyStatements()); wrapper.setProperties(this, parsedRdf.getPropertyStatements()); wrapper.validate(); return wrapper.getInstance(); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/InstanceWrapper.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/InstanceWrapper.java index c882ccfd5..45db851ad 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/InstanceWrapper.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/InstanceWrapper.java @@ -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 WrappedInstance wrap(Class concreteClass) throws InstanceWrapperException { - return new WrappedInstance(createInstance(concreteClass), - parsePropertyAnnotations(concreteClass), - parseValidationAnnotations(concreteClass)); + T instance = createInstance(concreteClass); + HashSet validationMethods = new HashSet<>( + parseValidationAnnotations(concreteClass).values()); + Map propertyMethods = new PropertyAnnotationsMap( + concreteClass).byUri(); + return new WrappedInstance(instance, propertyMethods, + validationMethods); } private static T createInstance(Class concreteClass) @@ -33,58 +41,131 @@ public class InstanceWrapper { } } - private static Map parsePropertyAnnotations( - Class concreteClass) throws InstanceWrapperException { - Map 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 parseValidationAnnotations(Class clazz) + throws InstanceWrapperException { + if (Object.class.equals(clazz)) { + return new HashMap<>(); + } else { + Map 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 parseValidationAnnotations(Class concreteClass) - throws InstanceWrapperException { - Set methods = new HashSet<>(); - for (Method method : concreteClass.getDeclaredMethods()) { - if (method.getAnnotation(Validation.class) == null) { - continue; + private static class PropertyAnnotationsMap { + private Map mapByUri = new HashMap<>(); + private Map 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 byUri() { + return mapByUri; + } + + public Map byName() { + return mapByName; + } + } public static class InstanceWrapperException extends Exception { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/Property.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/Property.java index 0eecb23e8..bcc60bba3 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/Property.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/Property.java @@ -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; } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/PropertyType.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/PropertyType.java index 397008548..9560fd5f8 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/PropertyType.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/PropertyType.java @@ -10,7 +10,6 @@ import java.lang.reflect.Method; import com.hp.hpl.jena.datatypes.RDFDatatype; import com.hp.hpl.jena.rdf.model.Literal; -import com.hp.hpl.jena.rdf.model.Property; import com.hp.hpl.jena.rdf.model.RDFNode; import com.hp.hpl.jena.rdf.model.Statement; @@ -25,38 +24,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); } }; @@ -100,24 +102,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() { @@ -134,8 +137,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; } @@ -148,8 +151,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; } @@ -162,8 +165,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; } @@ -176,10 +179,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() { @@ -190,6 +206,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()) { @@ -212,20 +240,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); } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/README.md b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/README.md new file mode 100644 index 000000000..68da421fa --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/README.md @@ -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 : . + :application + a , + ; + :hasSearchEngine :instrumentedSearchEngineWrapper ; + :hasFileStorage :ptiFileStorage . + + :ptiFileStorage + a , + . + + :instrumentedSearchEngineWrapper + a , + ; + :wraps :solrSearchEngine . + + :solrSearchEngine + a , + . + +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 loadInstance(String uri, Class 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 Set loadAll(Class 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. \ No newline at end of file diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/WrappedInstance.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/WrappedInstance.java index ced8285bb..4b739c6ec 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/WrappedInstance.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/WrappedInstance.java @@ -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 { } } + /** + * The loader provides the distilled property statements from the RDF. Check + * that they satisfy the cardinality requested on their methods. + */ + public void checkCardinality(Set propertyStatements) + throws CardinalityException { + Map 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 countPropertyStatementsByPredicateUri( + Set propertyStatements) { + Map 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 { if (pm == null) { throw new NoSuchPropertyMethodException(ps); } - pm.confirmCompatible(ps); if (ps instanceof ResourcePropertyStatement) { @@ -132,4 +171,10 @@ public class WrappedInstance { } } + public static class CardinalityException extends Exception { + public CardinalityException(String message) { + super(message); + } + } + } \ No newline at end of file diff --git a/webapp/test/edu/cornell/mannlib/vitro/testing/ModelUtilitiesTestHelper.java b/webapp/test/edu/cornell/mannlib/vitro/testing/ModelUtilitiesTestHelper.java index 69c29537f..e38a0e3b7 100644 --- a/webapp/test/edu/cornell/mannlib/vitro/testing/ModelUtilitiesTestHelper.java +++ b/webapp/test/edu/cornell/mannlib/vitro/testing/ModelUtilitiesTestHelper.java @@ -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), diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTest.java index da924567a..3d2ff1fff 100644 --- a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTest.java +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTest.java @@ -5,7 +5,6 @@ package edu.cornell.mannlib.vitro.webapp.utils.configuration; import static com.hp.hpl.jena.datatypes.xsd.XSDDatatype.XSDfloat; import static com.hp.hpl.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,28 +19,19 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; -import org.junit.Before; import org.junit.Ignore; import org.junit.Test; -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; - import com.hp.hpl.jena.rdf.model.Model; import com.hp.hpl.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 +40,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 +259,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 +722,7 @@ public class ConfigurationBeanLoaderTest extends AbstractTestClass { // Additional tests // ---------------------------------------------------------------------- + @SuppressWarnings("unused") @Test @Ignore // TODO @@ -1049,6 +731,7 @@ public class ConfigurationBeanLoaderTest extends AbstractTestClass { fail("circularReferencesAreNotFatal not implemented"); } + @SuppressWarnings("unused") @Test @Ignore // TODO deals with circularity. @@ -1057,6 +740,7 @@ public class ConfigurationBeanLoaderTest extends AbstractTestClass { fail("subordinateObjectCantBeLoaded_leavesNoAccessibleInstanceOfParent not implemented"); } + @SuppressWarnings("unused") @Test @Ignore // TODO deals with circularity. @@ -1065,42 +749,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 clazz, - String messageSubstring) { - return new ExpectedThrowable(clazz, messageSubstring); - } - - private static class ExpectedThrowable { - private final Class clazz; - private final String messageSubstring; - - public ExpectedThrowable(Class clazz, - String messageSubstring) { - this.clazz = clazz; - this.messageSubstring = messageSubstring; - } - - public Class getClazz() { - return clazz; - } - - public String getMessageSubstring() { - return messageSubstring; - } - } - } diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTestBase.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTestBase.java new file mode 100644 index 000000000..0725f9a6c --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTestBase.java @@ -0,0 +1,102 @@ +/* $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.junit.Before; + +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; + +import com.hp.hpl.jena.rdf.model.Model; + +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.ModelAccessFactory; + +/** + * 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 clazz, + String messageSubstring) { + return new ExpectedThrowable(clazz, messageSubstring); + } + + private static class ExpectedThrowable { + private final Class clazz; + private final String messageSubstring; + + public ExpectedThrowable(Class clazz, + String messageSubstring) { + this.clazz = clazz; + this.messageSubstring = messageSubstring; + } + + public Class getClazz() { + return clazz; + } + + public String getMessageSubstring() { + return messageSubstring; + } + } + +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_Cardinality_Test.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_Cardinality_Test.java new file mode 100644 index 000000000..13e767265 --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_Cardinality_Test.java @@ -0,0 +1,252 @@ +/* $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.junit.Test; + +import com.hp.hpl.jena.rdf.model.Statement; + +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 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 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 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 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 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 + } + } +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_PropertyTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_PropertyTest.java new file mode 100644 index 000000000..52996bd0e --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_PropertyTest.java @@ -0,0 +1,339 @@ +/* $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.junit.Test; + +import com.hp.hpl.jena.rdf.model.Statement; + +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 + } + } + +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_ValidationTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_ValidationTest.java new file mode 100644 index 000000000..8e3498a56 --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_ValidationTest.java @@ -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 + } + } + +} From d752989003a5f6c06fb025c165bdf643e79cac34 Mon Sep 17 00:00:00 2001 From: Jim Blake Date: Sun, 29 May 2016 16:12:11 -0400 Subject: [PATCH 2/9] VIVO-1247, remove duplicate code used with ConfigurationBeanLoader. Now that the @Property annotation includes cardinality parameters, we can remove a lot of duplicate code. --- .../webapp/application/ApplicationImpl.java | 103 +++--------------- .../InstrumentedSearchEngineWrapper.java | 71 +++++------- .../webapp/searchindex/SearchIndexerImpl.java | 61 +++++------ .../documentBuilding/FieldBooster.java | 13 +-- .../indexing/SelectQueryUriFinder.java | 6 +- .../sparql/ContentTripleSourceSPARQL.java | 29 +---- .../impl/tdb/ContentTripleSourceTDB.java | 22 +--- .../virtuoso/ContentTripleSourceVirtuoso.java | 48 +------- 8 files changed, 86 insertions(+), 267 deletions(-) diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/application/ApplicationImpl.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/application/ApplicationImpl.java index 36e230466..1f840ae37 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/application/ApplicationImpl.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/application/ApplicationImpl.java @@ -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 diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/InstrumentedSearchEngineWrapper.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/InstrumentedSearchEngineWrapper.java index 72228d940..d4ea2382e 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/InstrumentedSearchEngineWrapper.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/InstrumentedSearchEngineWrapper.java @@ -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 getFacetFields() { return Collections.emptyList(); } - - private class EmptyDocumentListWithCount implements SearchResultDocumentList { - @Override - public Iterator 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 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); + } } } - + } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/SearchIndexerImpl.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/SearchIndexerImpl.java index e9a6a9a81..2b63123bc 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/SearchIndexerImpl.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/SearchIndexerImpl.java @@ -99,7 +99,7 @@ public class SearchIndexerImpl implements SearchIndexer { private Set 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(); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/FieldBooster.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/FieldBooster.java index 5a86e1f65..7a3f0dad9 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/FieldBooster.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/FieldBooster.java @@ -17,27 +17,18 @@ public class FieldBooster implements DocumentModifier { private final List 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 uniqueFieldNames = new HashSet<>(fieldNames); List duplicateFieldNames = new ArrayList<>(fieldNames); for (String fn : uniqueFieldNames) { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/SelectQueryUriFinder.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/SelectQueryUriFinder.java index a0e50797a..efaf41964 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/SelectQueryUriFinder.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/SelectQueryUriFinder.java @@ -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 diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/triplesource/impl/sparql/ContentTripleSourceSPARQL.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/triplesource/impl/sparql/ContentTripleSourceSPARQL.java index cc19fac1e..044492036 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/triplesource/impl/sparql/ContentTripleSourceSPARQL.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/triplesource/impl/sparql/ContentTripleSourceSPARQL.java @@ -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 diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/triplesource/impl/tdb/ContentTripleSourceTDB.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/triplesource/impl/tdb/ContentTripleSourceTDB.java index 4591559b0..595633252 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/triplesource/impl/tdb/ContentTripleSourceTDB.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/triplesource/impl/tdb/ContentTripleSourceTDB.java @@ -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(); } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/triplesource/impl/virtuoso/ContentTripleSourceVirtuoso.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/triplesource/impl/virtuoso/ContentTripleSourceVirtuoso.java index 441279fb3..5cb4c0ce0 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/triplesource/impl/virtuoso/ContentTripleSourceVirtuoso.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/triplesource/impl/virtuoso/ContentTripleSourceVirtuoso.java @@ -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 From ac6f8722d7ae8ee852ca3c48900c6f0bd1af8e4b Mon Sep 17 00:00:00 2001 From: Jim Blake Date: Tue, 31 May 2016 12:25:52 -0400 Subject: [PATCH 3/9] VIVO-1246 Move unit tests to the new location. --- .../utils/configuration/ConfigurationBeanLoaderTestBase.java | 0 .../configuration/ConfigurationBeanLoader_Cardinality_Test.java | 0 .../utils/configuration/ConfigurationBeanLoader_PropertyTest.java | 0 .../configuration/ConfigurationBeanLoader_ValidationTest.java | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {webapp/test => api/src/test/java}/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTestBase.java (100%) rename {webapp/test => api/src/test/java}/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_Cardinality_Test.java (100%) rename {webapp/test => api/src/test/java}/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_PropertyTest.java (100%) rename {webapp/test => api/src/test/java}/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_ValidationTest.java (100%) diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTestBase.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTestBase.java similarity index 100% rename from webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTestBase.java rename to api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTestBase.java diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_Cardinality_Test.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_Cardinality_Test.java similarity index 100% rename from webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_Cardinality_Test.java rename to api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_Cardinality_Test.java diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_PropertyTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_PropertyTest.java similarity index 100% rename from webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_PropertyTest.java rename to api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_PropertyTest.java diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_ValidationTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_ValidationTest.java similarity index 100% rename from webapp/test/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_ValidationTest.java rename to api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_ValidationTest.java From 41ff46e771b1da456817cf8f2eadf26c1ceaacf6 Mon Sep 17 00:00:00 2001 From: Jim Blake Date: Tue, 21 Jun 2016 14:06:04 -0400 Subject: [PATCH 4/9] VIVO-1246 The documentation was in the wrong place. --- .../cornell/mannlib/vitro/webapp/utils/configuration/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {webapp/src => api/src/main/java}/edu/cornell/mannlib/vitro/webapp/utils/configuration/README.md (100%) diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/README.md b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/README.md similarity index 100% rename from webapp/src/edu/cornell/mannlib/vitro/webapp/utils/configuration/README.md rename to api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/README.md From b00633326e2c7ccb871952a157ba4661333742d4 Mon Sep 17 00:00:00 2001 From: Jim Blake Date: Fri, 1 Dec 2017 11:41:04 -0500 Subject: [PATCH 5/9] VIVO-1405 Add pseudo-version to requests for scripts and stylesheets. The result is that the user does not need to refresh the browser cache when a new version of VIVO is deployed on the server. --- .../vitro/webapp/web/templatemodels/Tags.java | 313 ++++++++++++++---- .../webapp/web/templatemodels/TagsTest.java | 248 ++++++++++++++ 2 files changed, 497 insertions(+), 64 deletions(-) create mode 100644 api/src/test/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/TagsTest.java diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/Tags.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/Tags.java index a51e0c541..d1bfa5bd6 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/Tags.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/Tags.java @@ -2,82 +2,267 @@ package edu.cornell.mannlib.vitro.webapp.web.templatemodels; +import java.io.File; +import java.lang.reflect.Method; import java.util.LinkedHashSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.ServletContext; -import freemarker.ext.beans.MethodAppearanceFineTuner; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder; import freemarker.ext.beans.BeansWrapper; import freemarker.template.TemplateModel; import freemarker.template.TemplateModelException; +/** + * Provides a mechanism for Freemarker templates (main or included, parent or + * child) to add to the lists of scripts and style sheets for the current page. + * + * Each page uses 3 instances of Tags, exposed as ${scripts}, ${headScripts} and + * ${stylesheets}. A template may add a complete <script/$gt; element (for + * scripts or headScripts) or a <link> tag (for stylesheets), and these + * elements will appear at the proper location in the rendered HTML for the + * page. + * + * VIVO-1405: This process is augmented by the TagVersionInfo inner class, which + * attempts to add a "version=" query string to the URL in the supplied element. + * The version number is derived from the last-modified date of the specified + * script or stylesheet on the server. The effect is that a user's browser cache + * is effectively invalidated each time a new version of the script or + * stylesheet is deployed. + */ public class Tags extends BaseTemplateModel { - - private static final Log log = LogFactory.getLog(Tags.class); - - protected final LinkedHashSet tags; + private static final Log log = LogFactory.getLog(Tags.class); - public Tags() { - this.tags = new LinkedHashSet(); - } - - public Tags(LinkedHashSet tags) { - this.tags = tags; - } - - public TemplateModel wrap() { - try { - return new TagsWrapper().wrap(this); - } catch (TemplateModelException e) { - log.error("Error creating Tags template model"); - return null; - } - } - - /** Script and stylesheet lists are wrapped with a specialized BeansWrapper - * that exposes certain write methods, instead of the configuration's object wrapper, - * which doesn't. The templates can then add stylesheets and scripts to the lists - * by calling their add() methods. - */ - static public class TagsWrapper extends BeansWrapper { - - public TagsWrapper() { - // Start by exposing all safe methods. - setExposureLevel(EXPOSE_SAFE); - setMethodAppearanceFineTuner(new MethodAppearanceFineTuner() { - @Override - public void process(MethodAppearanceDecisionInput methodAppearanceDecisionInput, MethodAppearanceDecision methodAppearanceDecision) { - try { - String methodName = methodAppearanceDecisionInput.getMethod().getName(); - if ( ! ( methodName.equals("add") || methodName.equals("list")) ) { - methodAppearanceDecision.setExposeMethodAs(null); - } - } catch (Exception e) { - log.error(e, e); - } - } - }); - } - } - - - /* Template methods */ + protected final LinkedHashSet tags; - public void add(String... tags) { - for (String tag : tags) { - add(tag); - } - } - - public void add(String tag) { - tags.add(tag); - } - - public String list() { - return StringUtils.join(tags, "\n"); - } - + public Tags() { + this.tags = new LinkedHashSet(); + } + public Tags(LinkedHashSet tags) { + this.tags = tags; + } + + public TemplateModel wrap() { + try { + return new TagsWrapper().wrap(this); + } catch (TemplateModelException e) { + log.error("Error creating Tags template model"); + return null; + } + } + + /** + * Script and stylesheet lists are wrapped with a specialized BeansWrapper + * that exposes certain write methods, instead of the configuration's object + * wrapper, which doesn't. The templates can then add stylesheets and + * scripts to the lists by calling their add() methods. + * + * @param Tags + * tags + * @return TemplateModel + */ + static public class TagsWrapper extends BeansWrapper { + + public TagsWrapper() { + // Start by exposing all safe methods. + setExposureLevel(EXPOSE_SAFE); + } + + @SuppressWarnings("rawtypes") + @Override + protected void finetuneMethodAppearance(Class cls, Method method, + MethodAppearanceDecision decision) { + + try { + String methodName = method.getName(); + if (!(methodName.equals("add") || methodName.equals("list"))) { + decision.setExposeMethodAs(null); + } + } catch (Exception e) { + log.error(e, e); + } + } + } + + /* Template methods */ + + @SuppressWarnings("hiding") + public void add(String... tags) { + for (String tag : tags) { + add(tag); + } + } + + public void add(String tag) { + TagVersionInfo info = new TagVersionInfo(tag); + if (info.hasVersion()) { + tags.add(TagVersionInfo.addVersionNumber(tag, info)); + } else { + tags.add(tag); + } + } + + public String list() { + return StringUtils.join(tags, "\n"); + } + + /** + * Find the value of "href" or "src". + * + * If there is such a value, and it doesn't have a query string already, and + * it represents a local URL, and we can locate the file that is served by + * the URL and get the last modified date, then we have found a "version + * number" that we can add to the attribute value. + * + * Reference for parsing attributes: + * https://www.w3.org/TR/html/syntax.html#elements-attributes + */ + protected static class TagVersionInfo { + private static final Pattern PATTERN_DOUBLE_QUOTES = Pattern + .compile("(href|src)\\s*=\\s*\"([^\"]+)\"[\\s|>]"); + private static final int GROUP_INDEX_DOUBLE_QUOTES = 2; + + private static final Pattern PATTERN_SINGLE_QUOTES = Pattern + .compile("(href|src)\\s*=\\s*'([^']+)'[\\s|>]"); + private static final int GROUP_INDEX_SINGLE_QUOTES = 2; + + private static final Pattern PATTERN_NO_QUOTES = Pattern + .compile("(href|src)\\s*=\\s*([^\"'<=>\\s]+)[\\s|>]"); + private static final int GROUP_INDEX_NO_QUOTES = 2; + + public static String addVersionNumber(String rawTag, + TagVersionInfo info) { + String versionString = (info.match.style == MatchResult.Style.NO_QUOTES) + ? "?version&eq;" + : "?version="; + return rawTag.substring(0, info.match.start) + info.match.group + + versionString + smushTimeStamp(info) + + rawTag.substring(info.match.end); + } + + private static String smushTimeStamp(TagVersionInfo info) { + int smushed = (((char) (info.timestamp >> 48)) + ^ ((char) (info.timestamp >> 32)) + ^ ((char) (info.timestamp >> 16)) + ^ ((char) info.timestamp)); + return String.format("%04x", smushed); + } + + private MatchResult match; + private long timestamp = 0L; + + public TagVersionInfo(String rawTag) { + try { + match = findUrlValue(rawTag); + + if (match != null && !hasQueryString(match.group)) { + String stripped = stripContextPath(match.group); + + if (stripped != null) { + String realPath = locateRealPath(stripped); + + if (realPath != null) { + timestamp = getLastModified(realPath); + } + } + } + } catch (Exception e) { + log.debug("Failed to add version info to tag: " + rawTag, e); + timestamp = 0L; + } + } + + public boolean hasVersion() { + return timestamp != 0L; + } + + private static MatchResult findUrlValue(String rawTag) { + Matcher mDouble = PATTERN_DOUBLE_QUOTES.matcher(rawTag); + if (mDouble.find()) { + return new MatchResult(mDouble, GROUP_INDEX_DOUBLE_QUOTES, + MatchResult.Style.DOUBLE_QUOTES); + } + + Matcher mSingle = PATTERN_SINGLE_QUOTES.matcher(rawTag); + if (mSingle.find()) { + return new MatchResult(mSingle, GROUP_INDEX_SINGLE_QUOTES, + MatchResult.Style.SINGLE_QUOTES); + } + + Matcher mNo = PATTERN_NO_QUOTES.matcher(rawTag); + if (mNo.find()) { + return new MatchResult(mNo, GROUP_INDEX_NO_QUOTES, + MatchResult.Style.NO_QUOTES); + } + + log.debug(rawTag + " no match"); + return null; + } + + private static boolean hasQueryString(String group) { + if (group.indexOf('?') > -1) { + log.debug(group + " has query string already"); + return true; + } else { + return false; + } + } + + private static String stripContextPath(String group) { + String contextPath = UrlBuilder.getBaseUrl(); + if (contextPath.isEmpty() || group.startsWith(contextPath)) { + return group.substring(contextPath.length()); + } else { + log.debug(group + " doesn't match context path"); + return null; + } + } + + private static String locateRealPath(String stripped) { + ServletContext ctx = ApplicationUtils.instance() + .getServletContext(); + String realPath = ctx.getRealPath(stripped); + if (realPath == null) { + log.debug(stripped + " has no real path"); + } + return realPath; + } + + private static long getLastModified(String realPath) { + return new File(realPath).lastModified(); + } + + protected static class MatchResult { + public enum Style { + SINGLE_QUOTES, DOUBLE_QUOTES, NO_QUOTES + } + + public final String group; + public final int start; + public final int end; + public final Style style; + + public MatchResult(Matcher matcher, int group, Style style) { + this.group = matcher.group(group); + this.start = matcher.start(group); + this.end = matcher.end(group); + this.style = style; + log.debug(this); + } + + @Override + public String toString() { + return "MatchResult[start=" + start + ", end=" + end + + ", group=" + group + ", style=" + style + "]"; + } + } + } } diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/TagsTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/TagsTest.java new file mode 100644 index 000000000..11fadb585 --- /dev/null +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/TagsTest.java @@ -0,0 +1,248 @@ +package edu.cornell.mannlib.vitro.webapp.web.templatemodels; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.lang.reflect.Field; + +import org.apache.log4j.Level; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder; +import edu.cornell.mannlib.vitro.webapp.web.templatemodels.Tags.TagVersionInfo; +import edu.cornell.mannlib.vitro.webapp.web.templatemodels.Tags.TagVersionInfo.MatchResult; +import stubs.edu.cornell.mannlib.vitro.webapp.modules.ApplicationStub; +import stubs.javax.servlet.ServletContextStub; + +public class TagsTest extends AbstractTestClass { + private ServletContextStub ctx; + private File resource; + + @Before + public void setup() throws IOException { + resource = File.createTempFile("resource", ""); + + ctx = new ServletContextStub(); + ctx.setRealPath("/base/sub/file.js", resource.getPath()); + ctx.setRealPath("/base/sub/file.css", resource.getPath()); + + ApplicationStub.setup(ctx, null); + + setContextPath("/context"); + } + + // ---------------------------------------------------------------------- + // Parsing tests + // + // Reference for parsing attributes: + // https://www.w3.org/TR/html/syntax.html#elements-attributes + // ---------------------------------------------------------------------- + + @Test + public void noAttribute_failure() { + assertNoMatch("
"); + } + + @Test + public void singleQuote_noTerminator_failure() { + assertNoMatch(""); + } + + @Test + public void doubleQuotes_embeddedDoubleQuote_failure() { + assertNoMatch(""); + } + + @Test + public void doubleQuotes_embeddedSingleQuote_success() { + assertMatch("", + "value'noproblem"); + } + + @Test + public void unquotedBadTerminator_failure() { + assertNoMatch("", "value"); + } + + @Test + public void noSpacesAroundEquals_success() { + assertMatch("", "value"); + } + + // ---------------------------------------------------------------------- + // Substitution tests + // ---------------------------------------------------------------------- + + @Test + public void noMatch_noChange() { + assertVersionNotAdded( + "", + "no match"); + } + + @Test + public void alreadyHasQueryString_noChange() { + assertVersionNotAdded( + "", + "has query"); + } + + @Test + public void doesntStartWithContextPath_noChange() { + assertVersionNotAdded( + "", + "context path"); + } + + @Test + public void noRealPath_noChange() { + assertVersionNotAdded( + "", + "real path"); + } + + @Test + @Ignore + public void exception_noChange() { + fail("exception_noChange not implemented"); + } + + @Test + public void doubleQuotes_substitution() { + assertVersionAdded( // + "", // + ""); + } + + @Test + public void singleQuotes_substitution() { + assertVersionAdded( // + "", // + ""); + } + + @Test + public void unquoted_substitution() { + assertVersionAdded( // + "", // + ""); + } + + // ---------------------------------------------------------------------- + // Helper methods + // ---------------------------------------------------------------------- + + private void setContextPath(String contextPath) { + try { + Field f = UrlBuilder.class.getDeclaredField("contextPath"); + f.setAccessible(true); + f.set(null, contextPath); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void assertMatch(String tag, String expected) { + TagVersionInfo info = new TagVersionInfo(tag); + + try { + Field f = TagVersionInfo.class.getDeclaredField("match"); + f.setAccessible(true); + MatchResult match = (MatchResult) f.get(info); + + assertEquals(expected, match.group); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + private void assertNoMatch(String tag) { + TagVersionInfo info = new TagVersionInfo(tag); + assertFalse(info.hasVersion()); + } + + private void assertVersionAdded(String rawTag, String expected) { + String actual = createTag(rawTag); + String canonicalActual = actual.replaceAll("=[0-9a-f]{4}", "=9999") + .replaceAll("&eq;[0-9a-f]{4}", "&eq;9999"); + assertEquals(expected, canonicalActual); + } + + private void assertVersionNotAdded(String rawTag, String debugMessage) { + StringWriter writer = new StringWriter(); + captureLogOutput(Tags.class, writer, true); + setLoggerLevel(Tags.class, Level.DEBUG); + + String actual = createTag(rawTag); + assertEquals(rawTag, actual); + assertThat(writer.toString(), containsString(debugMessage)); + } + + private String createTag(String rawTag) { + Tags t = new Tags(); + t.add(rawTag); + return t.list(); + } + +} \ No newline at end of file From 9e3ac4fb54e442d2f51250c3733e273e7b0286d5 Mon Sep 17 00:00:00 2001 From: Jim Blake Date: Fri, 5 Jan 2018 09:50:42 -0500 Subject: [PATCH 6/9] Remove implicit call to deprecated constructor. --- .../cornell/mannlib/vitro/webapp/web/templatemodels/Tags.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/Tags.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/Tags.java index d1bfa5bd6..d89ed61ef 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/Tags.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/web/templatemodels/Tags.java @@ -17,6 +17,7 @@ import org.apache.commons.logging.LogFactory; import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder; import freemarker.ext.beans.BeansWrapper; +import freemarker.template.Configuration; import freemarker.template.TemplateModel; import freemarker.template.TemplateModelException; @@ -72,6 +73,8 @@ public class Tags extends BaseTemplateModel { static public class TagsWrapper extends BeansWrapper { public TagsWrapper() { + super(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); + // Start by exposing all safe methods. setExposureLevel(EXPOSE_SAFE); } From 8fbfc6d4ffbe690738a267a202fec3d4d7b31e74 Mon Sep 17 00:00:00 2001 From: Jim Blake Date: Sat, 6 Jan 2018 14:09:03 -0500 Subject: [PATCH 7/9] ConfigurationBeanLoader will ignore unexpected properties on instances. If no method has been annotated to accept that property, don't complain, don't throw an exception, just continue. --- .../utils/configuration/WrappedInstance.java | 17 +++---- .../ConfigurationBeanLoaderTest.java | 46 +++++++++---------- 2 files changed, 27 insertions(+), 36 deletions(-) diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/WrappedInstance.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/WrappedInstance.java index 344016863..ba4a2ab75 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/WrappedInstance.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/WrappedInstance.java @@ -70,7 +70,8 @@ public class WrappedInstance { */ public void checkCardinality(Set propertyStatements) throws CardinalityException { - Map statementCounts = countPropertyStatementsByPredicateUri(propertyStatements); + Map statementCounts = countPropertyStatementsByPredicateUri( + propertyStatements); for (PropertyMethod pm : propertyMethods.values()) { Integer c = statementCounts.get(pm.getPropertyUri()); int count = (c == null) ? 0 : c; @@ -109,12 +110,11 @@ public class WrappedInstance { */ public void setProperties(ConfigurationBeanLoader loader, Collection propertyStatements) - throws PropertyTypeException, NoSuchPropertyMethodException, - ConfigurationBeanLoaderException { + throws PropertyTypeException, ConfigurationBeanLoaderException { for (PropertyStatement ps : propertyStatements) { PropertyMethod pm = propertyMethods.get(ps.getPredicateUri()); if (pm == null) { - throw new NoSuchPropertyMethodException(ps); + continue; // No method for this property? Ignore it. } pm.confirmCompatible(ps); @@ -141,7 +141,8 @@ public class WrappedInstance { } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new ValidationFailedException( - "Error executing validation method '" + method + "'", e); + "Error executing validation method '" + method + "'", + e); } } } @@ -165,12 +166,6 @@ public class WrappedInstance { } } - public static class NoSuchPropertyMethodException extends Exception { - public NoSuchPropertyMethodException(PropertyStatement ps) { - super("No property method for '" + ps.getPredicateUri() + "'"); - } - } - public static class CardinalityException extends Exception { public CardinalityException(String message) { super(message); diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTest.java index 17e9f2751..a0ebfa672 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTest.java @@ -2,12 +2,12 @@ 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.objectProperty; import static edu.cornell.mannlib.vitro.testing.ModelUtilitiesTestHelper.typeStatement; import static edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoader.toJavaUri; +import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDfloat; +import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDstring; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -19,18 +19,16 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; -import org.junit.Ignore; -import org.junit.Test; - import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.Statement; +import org.junit.Ignore; +import org.junit.Test; import edu.cornell.mannlib.vitro.webapp.modelaccess.ContextModelAccess; 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; /** @@ -309,24 +307,6 @@ public class ConfigurationBeanLoaderTest extends // -------------------------------------------- - @Test - public void tripleHasUnrecognizedProperty_throwsException() - throws ConfigurationBeanLoaderException { - model.add(typeStatement(GENERIC_INSTANCE_URI, - toJavaUri(SimpleSuccess.class))); - model.add(dataProperty(GENERIC_INSTANCE_URI, - "http://bogus.property/name", "No place to put it.")); - - expectSimpleFailure( - SimpleSuccess.class, - throwable(ConfigurationBeanLoaderException.class, - "Failed to load"), - throwable(NoSuchPropertyMethodException.class, - "No property method")); - } - - // -------------------------------------------- - @Test public void valueTypeDoesNotMatchArgumentOfPropertyMethod_throwsException() throws ConfigurationBeanLoaderException { @@ -395,10 +375,26 @@ public class ConfigurationBeanLoaderTest extends assertNotNull(instance); } + /** + * Ignores unexpected properties + */ + @Test + public void simpleSuccessIgnoringExtraProperties() throws ConfigurationBeanLoaderException { + model.add(typeStatement(SIMPLE_SUCCESS_INSTANCE_URI, + toJavaUri(SimpleSuccess.class))); + model.add(dataProperty(SIMPLE_SUCCESS_INSTANCE_URI, + "http://surprise.property/name", "No matching method.")); + + SimpleSuccess instance = loader.loadInstance( + SIMPLE_SUCCESS_INSTANCE_URI, SimpleSuccess.class); + + assertNotNull(instance); + } + public static class SimpleSuccess { // Nothing of interest. } - + // -------------------------------------------- /** From 7f8b16bc1b51cf61f228fa31ac36bf11f401a86b Mon Sep 17 00:00:00 2001 From: Jim Blake Date: Mon, 8 Jan 2018 11:19:53 -0500 Subject: [PATCH 8/9] VIVO-1408 Allow the use of a #-terminated prefix on JAVA class URIs --- .../ConfigurationBeanLoader.java | 69 ++++++++--- .../configuration/ConfigurationRdfParser.java | 73 +++++++----- .../webapp/utils/configuration/README.md | 35 +++++- ...onfigurationBeanLoader_NamespacesTest.java | 111 ++++++++++++++++++ ...onfigurationBeanLoader_ValidationTest.java | 37 +++--- 5 files changed, 253 insertions(+), 72 deletions(-) create mode 100644 api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_NamespacesTest.java diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader.java index 9f7f603aa..8c45a2fca 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader.java @@ -8,6 +8,7 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.TreeSet; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; @@ -34,16 +35,42 @@ public class ConfigurationBeanLoader { return JAVA_URI_PREFIX + clazz.getName(); } + public static String toCanonicalJavaUri(String uri) { + return uri.replace("#", "."); + } + public static boolean isJavaUri(String uri) { return uri.startsWith(JAVA_URI_PREFIX); } - public static String fromJavaUri(String uri) { - if (!isJavaUri(uri)) { - throw new IllegalArgumentException("Not a java class URI: '" + uri - + "'"); + public static Set toPossibleJavaUris(Class clazz) { + Set set = new TreeSet<>(); + String[] uriPieces = toJavaUri(clazz).split("\\."); + for (int hashIndex = 0; hashIndex < uriPieces.length; hashIndex++) { + set.add(joinWithPeriodsAndAHash(uriPieces, hashIndex)); } - return uri.substring(JAVA_URI_PREFIX.length()); + return set; + } + + private static String joinWithPeriodsAndAHash(String[] pieces, + int hashIndex) { + StringBuilder buffer = new StringBuilder(pieces[0]); + for (int i = 1; i < pieces.length; i++) { + buffer.append(i == hashIndex ? '#' : '.').append(pieces[i]); + } + return buffer.toString(); + } + + public static String classnameFromJavaUri(String uri) { + if (!isJavaUri(uri)) { + throw new IllegalArgumentException( + "Not a java class URI: '" + uri + "'"); + } + return toCanonicalJavaUri(uri).substring(JAVA_URI_PREFIX.length()); + } + + public static boolean isMatchingJavaUri(String uri1, String uri2) { + return toCanonicalJavaUri(uri1).equals(toCanonicalJavaUri(uri2)); } // ---------------------------------------------------------------------- @@ -85,9 +112,11 @@ public class ConfigurationBeanLoader { this(new LockableModel(model), req); } - public ConfigurationBeanLoader(LockableModel locking, HttpServletRequest req) { - this(locking, (req == null) ? null : req.getSession() - .getServletContext(), req); + public ConfigurationBeanLoader(LockableModel locking, + HttpServletRequest req) { + this(locking, + (req == null) ? null : req.getSession().getServletContext(), + req); } private ConfigurationBeanLoader(LockableModel locking, ServletContext ctx, @@ -111,18 +140,18 @@ public class ConfigurationBeanLoader { } try { - ConfigurationRdf parsedRdf = ConfigurationRdfParser.parse( - locking, uri, resultClass); - WrappedInstance wrapper = InstanceWrapper.wrap(parsedRdf - .getConcreteClass()); + ConfigurationRdf parsedRdf = ConfigurationRdfParser + .parse(locking, uri, resultClass); + WrappedInstance wrapper = InstanceWrapper + .wrap(parsedRdf.getConcreteClass()); wrapper.satisfyInterfaces(ctx, req); wrapper.checkCardinality(parsedRdf.getPropertyStatements()); wrapper.setProperties(this, parsedRdf.getPropertyStatements()); wrapper.validate(); return wrapper.getInstance(); } catch (Exception e) { - throw new ConfigurationBeanLoaderException("Failed to load '" + uri - + "'", e); + throw new ConfigurationBeanLoaderException( + "Failed to load '" + uri + "'", e); } } @@ -133,11 +162,13 @@ public class ConfigurationBeanLoader { throws ConfigurationBeanLoaderException { Set uris = new HashSet<>(); try (LockedModel m = locking.read()) { - List resources = m.listResourcesWithProperty(RDF.type, - createResource(toJavaUri(resultClass))).toList(); - for (Resource r : resources) { - if (r.isURIResource()) { - uris.add(r.getURI()); + for (String typeUri : toPossibleJavaUris(resultClass)) { + List resources = m.listResourcesWithProperty(RDF.type, + createResource(typeUri)).toList(); + for (Resource r : resources) { + if (r.isURIResource()) { + uris.add(r.getURI()); + } } } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationRdfParser.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationRdfParser.java index e96488f3a..4153d2295 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationRdfParser.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationRdfParser.java @@ -2,16 +2,18 @@ package edu.cornell.mannlib.vitro.webapp.utils.configuration; +import static edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoader.classnameFromJavaUri; +import static edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoader.isJavaUri; +import static edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoader.isMatchingJavaUri; +import static edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoader.toJavaUri; import static org.apache.jena.rdf.model.ResourceFactory.createResource; import static org.apache.jena.rdf.model.ResourceFactory.createStatement; -import static edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoader.fromJavaUri; -import static edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoader.isJavaUri; -import static edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoader.toJavaUri; import java.lang.reflect.Modifier; import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; import org.apache.jena.rdf.model.Property; @@ -64,12 +66,20 @@ public class ConfigurationRdfParser { private static void confirmEligibilityForResultClass(LockableModel locking, String uri, Class resultClass) throws InvalidConfigurationRdfException { - Statement s = createStatement(createResource(uri), RDF.type, - createResource(toJavaUri(resultClass))); + String resultClassUri = toJavaUri(resultClass); try (LockedModel m = locking.read()) { - if (!m.contains(s)) { - throw noTypeStatementForResultClass(s); + Set types = // + m.listObjectsOfProperty(createResource(uri), RDF.type) + .toSet(); + for (RDFNode typeNode : types) { + if (typeNode.isURIResource()) { + String typeUri = typeNode.asResource().getURI(); + if (isMatchingJavaUri(resultClassUri, typeUri)) { + return; + } + } } + throw noTypeStatementForResultClass(uri, resultClassUri); } } @@ -78,9 +88,8 @@ public class ConfigurationRdfParser { Set set = new HashSet<>(); try (LockedModel m = locking.read()) { - List rawStatements = m.listStatements( - m.getResource(uri), (Property) null, (RDFNode) null) - .toList(); + List rawStatements = m.listStatements(m.getResource(uri), + (Property) null, (RDFNode) null).toList(); if (rawStatements.isEmpty()) { throw noRdfStatements(uri); } @@ -108,8 +117,9 @@ public class ConfigurationRdfParser { Set> concreteClasses = new HashSet<>(); try (LockedModel m = locking.read()) { - for (RDFNode node : m.listObjectsOfProperty(createResource(uri), - RDF.type).toSet()) { + for (RDFNode node : m + .listObjectsOfProperty(createResource(uri), RDF.type) + .toSet()) { if (!node.isURIResource()) { throw typeMustBeUriResource(node); } @@ -140,7 +150,7 @@ public class ConfigurationRdfParser { if (!isJavaUri(typeUri)) { return false; } - Class clazz = Class.forName(fromJavaUri(typeUri)); + Class clazz = Class.forName(classnameFromJavaUri(typeUri)); if (clazz.isInterface()) { return false; } @@ -157,7 +167,7 @@ public class ConfigurationRdfParser { private static Class processTypeUri(String typeUri, Class resultClass) throws InvalidConfigurationRdfException { try { - Class clazz = Class.forName(fromJavaUri(typeUri)); + Class clazz = Class.forName(classnameFromJavaUri(typeUri)); if (!resultClass.isAssignableFrom(clazz)) { throw notAssignable(resultClass, clazz); } @@ -180,22 +190,23 @@ public class ConfigurationRdfParser { "The model contains no statements about '" + uri + "'"); } - private static InvalidConfigurationRdfException noConcreteClasses(String uri) { + private static InvalidConfigurationRdfException noConcreteClasses( + String uri) { return new InvalidConfigurationRdfException( "No concrete class is declared for '" + uri + "'"); } private static InvalidConfigurationRdfException tooManyConcreteClasses( String uri, Set concreteClasses) { - return new InvalidConfigurationRdfException("'" + uri - + "' is declared with more than one " + "concrete class: " - + concreteClasses); + return new InvalidConfigurationRdfException( + "'" + uri + "' is declared with more than one " + + "concrete class: " + concreteClasses); } private static InvalidConfigurationRdfException notAssignable( Class resultClass, Class clazz) { - return new InvalidConfigurationRdfException(clazz - + " cannot be assigned to " + resultClass); + return new InvalidConfigurationRdfException( + clazz + " cannot be assigned to " + resultClass); } private static InvalidConfigurationRdfException noZeroArgumentConstructor( @@ -212,8 +223,8 @@ public class ConfigurationRdfParser { private static InvalidConfigurationRdfException failedToLoadClass( String typeUri, Throwable e) { - return new InvalidConfigurationRdfException("Can't load this type: '" - + typeUri + "'", e); + return new InvalidConfigurationRdfException( + "Can't load this type: '" + typeUri + "'", e); } private static InvalidConfigurationRdfException typeMustBeUriResource( @@ -223,15 +234,18 @@ public class ConfigurationRdfParser { } private static InvalidConfigurationRdfException noTypeStatementForResultClass( - Statement s) { + String uri, String resultClassUri) { return new InvalidConfigurationRdfException( - "A type statement is required: '" + s); + "A type statement is required: '" + + createStatement(createResource(uri), RDF.type, + createResource(resultClassUri))); } - private static InvalidConfigurationRdfException noRdfStatements(String uri) { - return new InvalidConfigurationRdfException("'" + uri - + "' does not appear as the subject of any " - + "statements in the model."); + private static InvalidConfigurationRdfException noRdfStatements( + String uri) { + return new InvalidConfigurationRdfException( + "'" + uri + "' does not appear as the subject of any " + + "statements in the model."); } public static class InvalidConfigurationRdfException extends Exception { @@ -239,7 +253,8 @@ public class ConfigurationRdfParser { super(message); } - public InvalidConfigurationRdfException(String message, Throwable cause) { + public InvalidConfigurationRdfException(String message, + Throwable cause) { super(message, cause); } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/README.md b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/README.md index 68da421fa..0527158e4 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/README.md +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/README.md @@ -95,8 +95,36 @@ The principal methods are: + 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`. +### Specifying Java class URIs + +Java classes are specified as types in the configurations. The type URIs consist of `java:` and the fully-qualified class path and name. For example, + +``` +:application + a . +``` + +It would be nice to use prefixes to make URIs more readable. This doesn't +work with the scheme above, since none of the characters in the URI are valid +as delimiters of a prefix. + +For this reason, the loader will also recognize a type URI if one of the periods is replaced by a hash (`#`). So, this is equivalent to the previous example (note the `#` after `webapp`): + +``` +:application + a . +``` + +which implies that this is equivalent also: + +``` +@prefix javaWebapp: +:application a javaWebapp:application.ApplicationImpl . + +``` + ### Restrictions on instantiated classes. -Each class to be instantiated must have a niladic constructor. +Each class to be instantiated must have a public niladic constructor. ### Property methods When the loader encounters a data property or an object property in a description, @@ -116,7 +144,8 @@ For example: In more detail: -+ A class must contain exactly one method that serves each property URI in the description. ++ Each property URI in the description may be served by only one method in the class. ++ If a property URI in the description is not served by any method in the class, the loader will ignore that property. + 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 @@ -159,7 +188,7 @@ 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. ++ method with the same name in the class. + Validation methods in superclasses will be called, but may not be overridden in a subclass. ### Life cycle diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_NamespacesTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_NamespacesTest.java new file mode 100644 index 000000000..61e2b5916 --- /dev/null +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_NamespacesTest.java @@ -0,0 +1,111 @@ +/* $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.toPossibleJavaUris; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import java.util.HashSet; +import java.util.Random; +import java.util.Set; + +import org.junit.Test; + +/** + * Assure that we can use "namespaces" for Java URIs. The namespace must end + * with a '#'. + */ +public class ConfigurationBeanLoader_NamespacesTest + extends ConfigurationBeanLoaderTestBase { + + // ---------------------------------------------------------------------- + // toPossibleJavaUris() + // ---------------------------------------------------------------------- + + @Test + public void possibleForJavaLangString() { + Set expected = new HashSet<>(); + expected.add("java:java.lang.String"); + expected.add("java:java#lang.String"); + expected.add("java:java.lang#String"); + assertEquals(expected, toPossibleJavaUris(String.class)); + } + + // ---------------------------------------------------------------------- + // loadAll() + // ---------------------------------------------------------------------- + + @Test + public void loadAllForJavaUtilRandom() + throws ConfigurationBeanLoaderException { + model.add(typeStatement("http://noPound", "java:java.util.Random")); + model.add(typeStatement("http://firstPound", "java:java#util.Random")); + model.add(typeStatement("http://secondPound", "java:java.util#Random")); + model.add(typeStatement("http://notARandom", "java:java.util.Set")); + Set instances = loader.loadAll(Random.class); + assertEquals(3, instances.size()); + } + + @Test + public void loadAlForCustomInnerClass() + throws ConfigurationBeanLoaderException { + Set typeUris = toPossibleJavaUris(ExampleClassForLoadAll.class); + for (String typeUri : typeUris) { + model.add(typeStatement("http://testUri" + model.size(), typeUri)); + } + Set instances = loader + .loadAll(ExampleClassForLoadAll.class); + assertEquals(typeUris.size(), instances.size()); + } + + public static class ExampleClassForLoadAll { + // Nothing of interest + } + + // ---------------------------------------------------------------------- + // loadInstance() + // ---------------------------------------------------------------------- + + @Test + public void loadInstanceVariationsForJavaUtilRandom() + throws ConfigurationBeanLoaderException { + model.add(typeStatement("http://noPound", "java:java.util.Random")); + model.add(typeStatement("http://firstPound", "java:java#util.Random")); + model.add(typeStatement("http://secondPound", "java:java.util#Random")); + model.add(typeStatement("http://notARandom", "java:java.util.Set")); + + assertNotNull(loader.loadInstance("http://noPound", Random.class)); + assertNotNull(loader.loadInstance("http://firstPound", Random.class)); + assertNotNull(loader.loadInstance("http://secondPound", Random.class)); + + try { + loader.loadInstance("http://notARandom", Random.class); + fail("Should not be a Random"); + } catch (Exception e) { + // Expected it + } + } + + @Test + public void loadInstanceVariationsForCustomInnerClass() + throws ConfigurationBeanLoaderException { + Set typeUris = toPossibleJavaUris( + ExampleClassForLoadInstance.class); + for (String typeUri : typeUris) { + model.add(typeStatement("http://testUri" + model.size(), typeUri)); + } + for (int i = 0; i < model.size(); i++) { + String instanceUri = "http://testUri" + i; + assertNotNull("No instance for " + instanceUri, loader.loadInstance( + instanceUri, ExampleClassForLoadInstance.class)); + } + } + + public static class ExampleClassForLoadInstance { + // Nothing of interest + } + +} diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_ValidationTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_ValidationTest.java index 59a57a0b0..60e66a5d2 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_ValidationTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoader_ValidationTest.java @@ -15,8 +15,8 @@ import edu.cornell.mannlib.vitro.webapp.utils.configuration.WrappedInstance.Vali /** * Test the @Validation annotation. */ -public class ConfigurationBeanLoader_ValidationTest extends - ConfigurationBeanLoaderTestBase { +public class ConfigurationBeanLoader_ValidationTest + extends ConfigurationBeanLoaderTestBase { // -------------------------------------------- @Test @@ -25,8 +25,7 @@ public class ConfigurationBeanLoader_ValidationTest extends model.add(typeStatement(GENERIC_INSTANCE_URI, toJavaUri(ValidationMethodWithParameter.class))); - expectSimpleFailure( - ValidationMethodWithParameter.class, + expectSimpleFailure(ValidationMethodWithParameter.class, throwable(ConfigurationBeanLoaderException.class, "Failed to load"), throwable(InstanceWrapperException.class, @@ -49,11 +48,11 @@ public class ConfigurationBeanLoader_ValidationTest extends model.add(typeStatement(GENERIC_INSTANCE_URI, toJavaUri(ValidationMethodShouldReturnVoid.class))); - expectSimpleFailure( - ValidationMethodShouldReturnVoid.class, + expectSimpleFailure(ValidationMethodShouldReturnVoid.class, throwable(ConfigurationBeanLoaderException.class, "Failed to load"), - throwable(InstanceWrapperException.class, "should return void")); + throwable(InstanceWrapperException.class, + "should return void")); } public static class ValidationMethodShouldReturnVoid { @@ -71,8 +70,7 @@ public class ConfigurationBeanLoader_ValidationTest extends model.add(typeStatement(GENERIC_INSTANCE_URI, toJavaUri(ValidationMethodIsPrivate.class))); - expectSimpleFailure( - ValidationMethodIsPrivate.class, + expectSimpleFailure(ValidationMethodIsPrivate.class, throwable(ConfigurationBeanLoaderException.class, "Failed to load"), throwable(ValidationFailedException.class, @@ -94,8 +92,7 @@ public class ConfigurationBeanLoader_ValidationTest extends model.add(typeStatement(GENERIC_INSTANCE_URI, toJavaUri(ValidationThrowsException.class))); - expectSimpleFailure( - ValidationThrowsException.class, + expectSimpleFailure(ValidationThrowsException.class, throwable(ConfigurationBeanLoaderException.class, "Failed to load"), throwable(ValidationFailedException.class, @@ -144,8 +141,7 @@ public class ConfigurationBeanLoader_ValidationTest extends model.add(typeStatement(GENERIC_INSTANCE_URI, toJavaUri(ValidationOverValidationSubclass.class))); - expectSimpleFailure( - ValidationOverValidationSubclass.class, + expectSimpleFailure(ValidationOverValidationSubclass.class, throwable(ConfigurationBeanLoaderException.class, "Failed to load"), throwable(InstanceWrapperException.class, @@ -158,8 +154,7 @@ public class ConfigurationBeanLoader_ValidationTest extends model.add(typeStatement(GENERIC_INSTANCE_URI, toJavaUri(PlainOverValidationSubclass.class))); - expectSimpleFailure( - PlainOverValidationSubclass.class, + expectSimpleFailure(PlainOverValidationSubclass.class, throwable(ConfigurationBeanLoaderException.class, "Failed to load"), throwable(InstanceWrapperException.class, @@ -182,8 +177,8 @@ public class ConfigurationBeanLoader_ValidationTest extends // Just want to see that the superclass validation is run. } - public static class AdditionalValidationSubclass extends - ValidationSuperclass { + public static class AdditionalValidationSubclass + extends ValidationSuperclass { public boolean validatorSubHasRun = false; @Validation @@ -195,8 +190,8 @@ public class ConfigurationBeanLoader_ValidationTest extends } } - public static class ValidationOverValidationSubclass extends - EmptyValidationSubclass { + public static class ValidationOverValidationSubclass + extends EmptyValidationSubclass { @Override @Validation public void validatorSuper() { @@ -204,8 +199,8 @@ public class ConfigurationBeanLoader_ValidationTest extends } } - public static class PlainOverValidationSubclass extends - ValidationSuperclass { + public static class PlainOverValidationSubclass + extends ValidationSuperclass { @Override public void validatorSuper() { // Should fail From 8cba661a0e5d242d033bffa84facda43181be0de Mon Sep 17 00:00:00 2001 From: Jim Blake Date: Wed, 10 Jan 2018 08:51:41 -0500 Subject: [PATCH 9/9] VIVO-1408 Use prefix to simplify JAVA class URIs --- .../config/example.applicationSetup.n3 | 49 ++++++++++--------- .../searchIndexerConfigurationVitro.n3 | 46 ++++++++--------- 2 files changed, 49 insertions(+), 46 deletions(-) diff --git a/home/src/main/resources/config/example.applicationSetup.n3 b/home/src/main/resources/config/example.applicationSetup.n3 index eeb8678a9..6ead57a93 100644 --- a/home/src/main/resources/config/example.applicationSetup.n3 +++ b/home/src/main/resources/config/example.applicationSetup.n3 @@ -11,6 +11,7 @@ # ------------------------------------------------------------------------------ @prefix : . +@prefix vitroWebapp: . # ---------------------------- # @@ -19,8 +20,8 @@ # :application - a , - ; + a vitroWebapp:application.ApplicationImpl , + vitroWebapp:modules.Application ; :hasSearchEngine :instrumentedSearchEngineWrapper ; :hasSearchIndexer :basicSearchIndexer ; :hasImageProcessor :iioImageProcessor ; @@ -35,8 +36,8 @@ # :iioImageProcessor - a , - . + a vitroWebapp:imageprocessor.imageio.IIOImageProcessor , + vitroWebapp:modules.imageProcessor.ImageProcessor . # ---------------------------- # @@ -46,8 +47,8 @@ # :ptiFileStorage - a , - . + a vitroWebapp:filestorage.impl.FileStorageImplWrapper , + vitroWebapp:modules.fileStorage.FileStorage . # ---------------------------- # @@ -58,13 +59,13 @@ # :instrumentedSearchEngineWrapper - a , - ; + a vitroWebapp:searchengine.InstrumentedSearchEngineWrapper , + vitroWebapp:modules.searchEngine.SearchEngine ; :wraps :solrSearchEngine . :solrSearchEngine - a , - . + a vitroWebapp:searchengine.solr.SolrSearchEngine , + vitroWebapp:modules.searchEngine.SearchEngine . # ---------------------------- # @@ -74,8 +75,8 @@ # :basicSearchIndexer - a , - ; + a vitroWebapp:searchindex.SearchIndexerImpl , + vitroWebapp:modules.searchIndexer.SearchIndexer ; :threadPoolSize "10" . # ---------------------------- @@ -89,26 +90,26 @@ # :sdbContentTripleSource - a , - . + a vitroWebapp:triplesource.impl.sdb.ContentTripleSourceSDB , + vitroWebapp:modules.tripleSource.ContentTripleSource . #:tdbContentTripleSource -# a , -# ; +# a vitroWebapp:triplesource.impl.tdb.ContentTripleSourceTDB , +# vitroWebapp:modules.tripleSource.ContentTripleSource ; # # May be an absolute path, or relative to the Vitro home directory. # :hasTdbDirectory "tdbContentModels" . #:sparqlContentTripleSource -# a , -# ; +# a vitroWebapp:triplesource.impl.virtuoso.ContentTripleSourceSPARQL , +# vitroWebapp:modules.tripleSource.ContentTripleSource ; # # The URI of the SPARQL endpoint for your triple-store. # :hasEndpointURI "PUT_YOUR_SPARQL_ENDPOINT_URI_HERE" ; # # The URI to use for SPARQL UPDATE calls against your triple-store. # :hasUpdateEndpointURI "PUT_THE UPDATE_URI_HERE" . #:virtuosoContentTripleSource -# a , -# ; +# a vitroWebapp:triplesource.impl.virtuoso.ContentTripleSourceVirtuoso , +# vitroWebapp:modules.tripleSource.ContentTripleSource ; # # The URI of Virtuoso's SPARQL endpoint. # :hasEndpointURI "PUT_YOUR_VIRTUOSO_URI_HERE" ; # # The URI to use for SPARQL UPDATE calls against Virtuoso. @@ -123,8 +124,8 @@ # :tdbConfigurationTripleSource - a , - . + a vitroWebapp:triplesource.impl.tdb.ConfigurationTripleSourceTDB , + vitroWebapp:modules.tripleSource.ConfigurationTripleSource . # ---------------------------- # @@ -134,5 +135,5 @@ # :jfactTBoxReasonerModule - a , - . + a vitroWebapp:tboxreasoner.impl.jfact.JFactTBoxReasonerModule , + vitroWebapp:modules.tboxreasoner.TBoxReasonerModule . diff --git a/home/src/main/resources/rdf/display/everytime/searchIndexerConfigurationVitro.n3 b/home/src/main/resources/rdf/display/everytime/searchIndexerConfigurationVitro.n3 index 528157150..4c5a384c8 100644 --- a/home/src/main/resources/rdf/display/everytime/searchIndexerConfigurationVitro.n3 +++ b/home/src/main/resources/rdf/display/everytime/searchIndexerConfigurationVitro.n3 @@ -1,6 +1,8 @@ @prefix : . @prefix rdfs: . @prefix xsd: . +@prefix searchIndex: . + # # configure the SearchIndexer @@ -8,8 +10,8 @@ # Individuals with these types will be excluded from the search index :searchExcluder_typeExcluder - a , - ; + a searchIndex:exclusions.ExcludeBasedOnType , + searchIndex:exclusions.SearchIndexExcluder ; :excludes "http://www.w3.org/2002/07/owl#AnnotationProperty" , "http://www.w3.org/2002/07/owl#DatatypeProperty" , @@ -17,8 +19,8 @@ # Individuals with types from these namespaces will be excluded from the search index. :searchExcluder_namespaceExcluder - a , - ; + a searchIndex:exclusions.ExcludeBasedOnNamespace , + searchIndex:exclusions.SearchIndexExcluder ; :excludes "http://vitro.mannlib.cornell.edu/ns/vitro/0.7#" , "http://vitro.mannlib.cornell.edu/ns/vitro/public#" , @@ -27,38 +29,38 @@ # Individuals with URIs in these namespaces will be excluded from the search index. :searchExcluder_typeNamespaceExcluder - a , - ; + a searchIndex:exclusions.ExcludeBasedOnTypeNamespace , + searchIndex:exclusions.SearchIndexExcluder ; :excludes "http://vitro.mannlib.cornell.edu/ns/vitro/role#public" . :searchExcluder_syncingTypeExcluder - a , - . + a searchIndex:exclusions.SyncingExcludeBasedOnType , + searchIndex:exclusions.SearchIndexExcluder . # ------------------------------------ :uriFinder_forDataProperties - a , - . + a searchIndex:indexing.IndexingUriFinder , + searchIndex:indexing.AdditionalURIsForDataProperties . :uriFinder_forObjectProperties - a , - . + a searchIndex:indexing.IndexingUriFinder , + searchIndex:indexing.AdditionalURIsForObjectProperties . :uriFinder_forTypeStatements - a , - . + a searchIndex:indexing.IndexingUriFinder , + searchIndex:indexing.AdditionalURIsForTypeStatements . :uriFinder_forClassGroupChange - a , - . + a searchIndex:indexing.IndexingUriFinder , + searchIndex:indexing.URIsForClassGroupChange . # ------------------------------------ :documentModifier_AllNames - a , - ; + a searchIndex:documentBuilding.SelectQueryDocumentModifier , + searchIndex:documentBuilding.DocumentModifier ; rdfs:label "All labels are added to name fields." ; :hasTargetField "nameRaw" ; :hasSelectQuery """ @@ -70,8 +72,8 @@ """ . :documentModifier_NameFieldBooster - a , - ; + a searchIndex:documentBuilding.FieldBooster , + searchIndex:documentBuilding.DocumentModifier ; :hasTargetField "nameRaw" ; :hasTargetField "nameLowercase" ; :hasTargetField "nameUnstemmed" ; @@ -79,5 +81,5 @@ :hasBoost "1.2"^^xsd:float . :documentModifier_thumbnailImageUrl - a , - . + a searchIndex:documentBuilding.ThumbnailImageURL , + searchIndex:documentBuilding.DocumentModifier .