Create ConfigurationBeansLoader.

Also dependent classes and unit tests.
This commit is contained in:
Jim Blake 2014-10-24 10:29:59 -04:00
parent 645bc92d99
commit 73b200aac1
13 changed files with 2151 additions and 0 deletions

View file

@ -0,0 +1,139 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.utils.configuration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static com.hp.hpl.jena.rdf.model.ResourceFactory.*;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.ResourceFactory;
import com.hp.hpl.jena.vocabulary.RDF;
import edu.cornell.mannlib.vitro.webapp.utils.jena.Critical;
/**
* Load one or more Configuration beans from a specified model.
*/
public class ConfigurationBeanLoader {
private static final String JAVA_URI_PREFIX = "java:";
// ----------------------------------------------------------------------
// utility methods
// ----------------------------------------------------------------------
public static String toJavaUri(Class<?> clazz) {
return JAVA_URI_PREFIX + clazz.getName();
}
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
+ "'");
}
return uri.substring(JAVA_URI_PREFIX.length());
}
// ----------------------------------------------------------------------
// the instance
// ----------------------------------------------------------------------
/** Must not be null. */
private final Model model;
/**
* May be null, but the loader will be unable to satisfy instances of
* ContextModelUser.
*/
private final ServletContext ctx;
/**
* May be null, but the loader will be unable to satisfy instances of
* RequestModelUser.
*/
private final HttpServletRequest req;
public ConfigurationBeanLoader(Model model) {
this(model, null, null);
}
public ConfigurationBeanLoader(Model model, ServletContext ctx) {
this(model, ctx, null);
}
public ConfigurationBeanLoader(Model model, HttpServletRequest req) {
this(model,
(req == null) ? null : req.getSession().getServletContext(),
req);
}
private ConfigurationBeanLoader(Model model, ServletContext ctx,
HttpServletRequest req) {
if (model == null) {
throw new NullPointerException("model may not be null.");
}
this.model = model;
this.req = req;
this.ctx = ctx;
}
/**
* Load the instance with this URI, if it is assignable to this class.
*/
public <T> T loadInstance(String uri, Class<T> resultClass)
throws ConfigurationBeanLoaderException {
if (uri == null) {
throw new NullPointerException("uri may not be null.");
}
if (resultClass == null) {
throw new NullPointerException("resultClass may not be null.");
}
try {
ConfigurationRdf<T> parsedRdf = ConfigurationRdfParser.parse(model,
uri, resultClass);
WrappedInstance<T> wrapper = InstanceWrapper.wrap(parsedRdf
.getConcreteClass());
wrapper.satisfyInterfaces(ctx, req);
wrapper.setProperties(this, parsedRdf.getPropertyStatements());
wrapper.validate();
return wrapper.getInstance();
} catch (Exception e) {
throw new ConfigurationBeanLoaderException("Failed to load '" + uri
+ "'", e);
}
}
/**
* Find all of the resources with the specified class, and instantiate them.
*/
public <T> Set<T> loadAll(Class<T> resultClass)
throws ConfigurationBeanLoaderException {
Set<String> uris = new HashSet<>();
try (Critical section = Critical.read(model)) {
List<Resource> resources = model.listResourcesWithProperty(
RDF.type, createResource(toJavaUri(resultClass))).toList();
for (Resource r : resources) {
if (r.isURIResource()) {
uris.add(r.getURI());
}
}
}
Set<T> instances = new HashSet<>();
for (String uri : uris) {
instances.add(loadInstance(uri, resultClass));
}
return instances;
}
}

View file

@ -0,0 +1,16 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.utils.configuration;
/**
* Indicates that the loading of configuration beans did not succeed.
*/
public class ConfigurationBeanLoaderException extends Exception {
public ConfigurationBeanLoaderException(String message) {
super(message);
}
public ConfigurationBeanLoaderException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,29 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.utils.configuration;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.PropertyStatement;
public class ConfigurationRdf<T> {
private final Class<? extends T> concreteClass;
private final Set<PropertyStatement> properties;
public ConfigurationRdf(Class<? extends T> concreteClass,
Set<PropertyStatement> properties) {
this.concreteClass = concreteClass;
this.properties = Collections
.unmodifiableSet(new HashSet<>(properties));
}
public Class<? extends T> getConcreteClass() {
return concreteClass;
}
public Set<PropertyStatement> getPropertyStatements() {
return new HashSet<>(properties);
}
}

View file

@ -0,0 +1,251 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.utils.configuration;
import static com.hp.hpl.jena.rdf.model.ResourceFactory.createResource;
import static com.hp.hpl.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.Set;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Selector;
import com.hp.hpl.jena.rdf.model.SimpleSelector;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.vocabulary.RDF;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.PropertyStatement;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.PropertyTypeException;
import edu.cornell.mannlib.vitro.webapp.utils.jena.Critical;
/**
* Parse the RDF for a single individual in the model to create a
* ConfigurationRdf object.
*/
public class ConfigurationRdfParser {
public static <T> ConfigurationRdf<T> parse(Model model, String uri,
Class<T> resultClass) throws InvalidConfigurationRdfException {
if (model == null) {
throw new NullPointerException("model may not be null.");
}
if (uri == null) {
throw new NullPointerException("uri may not be null.");
}
if (resultClass == null) {
throw new NullPointerException("resultClass may not be null.");
}
confirmExistenceInModel(model, uri);
confirmEligibilityForResultClass(model, uri, resultClass);
Set<PropertyStatement> properties = loadProperties(model, uri);
Class<? extends T> concreteClass = determineConcreteClass(model, uri,
resultClass);
return new ConfigurationRdf<T>(concreteClass, properties);
}
private static void confirmExistenceInModel(Model model, String uri)
throws InvalidConfigurationRdfException {
Selector s = new SimpleSelector(createResource(uri), null,
(RDFNode) null);
try (Critical section = Critical.read(model)) {
if (model.listStatements(s).toList().isEmpty()) {
throw individualDoesNotAppearInModel(uri);
}
}
}
private static void confirmEligibilityForResultClass(Model model,
String uri, Class<?> resultClass)
throws InvalidConfigurationRdfException {
Statement s = createStatement(createResource(uri), RDF.type,
createResource(toJavaUri(resultClass)));
try (Critical section = Critical.read(model)) {
if (!model.contains(s)) {
throw noTypeStatementForResultClass(s);
}
}
}
private static Set<PropertyStatement> loadProperties(Model model, String uri)
throws InvalidConfigurationRdfException {
Set<PropertyStatement> set = new HashSet<>();
try (Critical section = Critical.read(model)) {
List<Statement> rawStatements = model.listStatements(
model.getResource(uri), (Property) null, (RDFNode) null)
.toList();
if (rawStatements.isEmpty()) {
throw noRdfStatements(uri);
}
for (Statement s : rawStatements) {
if (s.getPredicate().equals(RDF.type)) {
continue;
} else {
try {
set.add(PropertyType.createPropertyStatement(s));
} catch (PropertyTypeException e) {
throw new InvalidConfigurationRdfException(
"Invalid property statement on '" + uri + "'",
e);
}
}
}
return set;
}
}
private static <T> Class<? extends T> determineConcreteClass(Model model,
String uri, Class<T> resultClass)
throws InvalidConfigurationRdfException {
Set<Class<? extends T>> concreteClasses = new HashSet<>();
try (Critical section = Critical.read(model)) {
for (RDFNode node : model.listObjectsOfProperty(
createResource(uri), RDF.type).toSet()) {
if (!node.isURIResource()) {
throw typeMustBeUriResource(node);
}
String typeUri = node.asResource().getURI();
if (!isConcreteClass(typeUri)) {
continue;
}
concreteClasses.add(processTypeUri(typeUri, resultClass));
}
}
if (concreteClasses.isEmpty()) {
throw noConcreteClasses(uri);
}
if (concreteClasses.size() > 1) {
throw tooManyConcreteClasses(uri, concreteClasses);
}
return concreteClasses.iterator().next();
}
private static boolean isConcreteClass(String typeUri)
throws InvalidConfigurationRdfException {
try {
if (!isJavaUri(typeUri)) {
return false;
}
Class<?> clazz = Class.forName(fromJavaUri(typeUri));
if (clazz.isInterface()) {
return false;
}
if (Modifier.isAbstract(clazz.getModifiers())) {
return false;
}
return true;
} catch (ClassNotFoundException | ExceptionInInitializerError e) {
throw failedToLoadClass(typeUri, e);
}
}
@SuppressWarnings("unchecked")
private static <T> Class<? extends T> processTypeUri(String typeUri,
Class<T> resultClass) throws InvalidConfigurationRdfException {
try {
Class<?> clazz = Class.forName(fromJavaUri(typeUri));
if (!resultClass.isAssignableFrom(clazz)) {
throw notAssignable(resultClass, clazz);
}
try {
clazz.getConstructor();
} catch (NoSuchMethodException e) {
throw noZeroArgumentConstructor(clazz);
} catch (SecurityException e) {
throw constructorNotPublic(clazz);
}
return (Class<? extends T>) clazz;
} catch (ClassNotFoundException e) {
throw failedToLoadClass(typeUri, e);
}
}
private static InvalidConfigurationRdfException individualDoesNotAppearInModel(
String uri) {
return new InvalidConfigurationRdfException(
"The model contains no statements about '" + 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);
}
private static InvalidConfigurationRdfException notAssignable(
Class<?> resultClass, Class<?> clazz) {
return new InvalidConfigurationRdfException(clazz
+ " cannot be assigned to " + resultClass);
}
private static InvalidConfigurationRdfException noZeroArgumentConstructor(
Class<?> clazz) {
return new InvalidConfigurationRdfException("Can't instantiate '"
+ clazz + "': no zero-argument constructor.");
}
private static InvalidConfigurationRdfException constructorNotPublic(
Class<?> clazz) {
return new InvalidConfigurationRdfException("Can't instantiate '"
+ clazz + "': zero-argument constructor is not public.");
}
private static InvalidConfigurationRdfException failedToLoadClass(
String typeUri, Throwable e) {
return new InvalidConfigurationRdfException("Can't load this type: '"
+ typeUri + "'", e);
}
private static InvalidConfigurationRdfException typeMustBeUriResource(
RDFNode node) {
return new InvalidConfigurationRdfException(
"Type must be a URI Resource: " + node);
}
private static InvalidConfigurationRdfException noTypeStatementForResultClass(
Statement s) {
return new InvalidConfigurationRdfException(
"A type statement is required: '" + s);
}
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 {
public InvalidConfigurationRdfException(String message) {
super(message);
}
public InvalidConfigurationRdfException(String message, Throwable cause) {
super(message, cause);
}
}
}

View file

@ -0,0 +1,13 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.utils.configuration;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ContextModelAccess;
/**
* When the ConfigurationBeanLoader creates an instance of this class, it will
* call this method, supplying the RDF models from the context.
*/
public interface ContextModelsUser {
void setContextModels(ContextModelAccess models);
}

View file

@ -0,0 +1,100 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.utils.configuration;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.PropertyMethod;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.PropertyTypeException;
/**
* Parse the annotations on this class and package them with a newly-created
* instance of the class.
*/
public class InstanceWrapper {
public static <T> WrappedInstance<T> wrap(Class<? extends T> concreteClass)
throws InstanceWrapperException {
return new WrappedInstance<T>(createInstance(concreteClass),
parsePropertyAnnotations(concreteClass),
parseValidationAnnotations(concreteClass));
}
private static <T> T createInstance(Class<? extends T> concreteClass)
throws InstanceWrapperException {
try {
return concreteClass.newInstance();
} catch (Exception e) {
throw new InstanceWrapperException("Failed to create an instance.",
e);
}
}
private static Map<String, PropertyMethod> parsePropertyAnnotations(
Class<?> concreteClass) throws InstanceWrapperException {
Map<String, PropertyMethod> map = new HashMap<>();
for (Method method : concreteClass.getDeclaredMethods()) {
Property annotation = method.getAnnotation(Property.class);
if (annotation == null) {
continue;
}
if (!method.getReturnType().equals(Void.TYPE)) {
throw new InstanceWrapperException("Property method '" + method
+ "' should return void.");
}
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1) {
throw new InstanceWrapperException("Property method '" + method
+ "' must accept exactly one parameter.");
}
String uri = annotation.uri();
if (map.containsKey(uri)) {
throw new InstanceWrapperException(
"Two property methods have the same URI value: "
+ map.get(uri).getMethod() + ", and " + method);
}
try {
map.put(uri, PropertyType.createPropertyMethod(method));
} catch (PropertyTypeException e) {
throw new InstanceWrapperException(
"Failed to create the PropertyMethod", e);
}
}
return map;
}
private static Set<Method> parseValidationAnnotations(Class<?> concreteClass)
throws InstanceWrapperException {
Set<Method> methods = new HashSet<>();
for (Method method : concreteClass.getDeclaredMethods()) {
if (method.getAnnotation(Validation.class) == null) {
continue;
}
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;
}
public static class InstanceWrapperException extends Exception {
public InstanceWrapperException(String message) {
super(message);
}
public InstanceWrapperException(String message, Throwable cause) {
super(message, cause);
}
}
}

View file

@ -0,0 +1,18 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.utils.configuration;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* The annotated method should be called each time a property with a matching
* URI is found on the bean.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Property {
String uri();
}

View file

@ -0,0 +1,243 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
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 java.lang.reflect.InvocationTargetException;
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;
/**
* An enumeration of the types of properties that the ConfigurationBeanLoader
* will support.
*
* Also, classes that represent the Java methods and RDF statements associated
* with those types.
*/
public enum PropertyType {
RESOURCE {
@Override
public PropertyStatement buildPropertyStatement(Statement s) {
return new ResourcePropertyStatement(s.getPredicate(), s
.getObject().asResource().getURI());
}
@Override
protected PropertyMethod buildPropertyMethod(Method method) {
return new ResourcePropertyMethod(method);
}
},
STRING {
@Override
public PropertyStatement buildPropertyStatement(Statement s) {
return new StringPropertyStatement(s.getPredicate(), s.getObject()
.asLiteral().getString());
}
@Override
protected PropertyMethod buildPropertyMethod(Method method) {
return new StringPropertyMethod(method);
}
},
FLOAT {
@Override
public PropertyStatement buildPropertyStatement(Statement s) {
return new FloatPropertyStatement(s.getPredicate(), s.getObject()
.asLiteral().getFloat());
}
@Override
protected PropertyMethod buildPropertyMethod(Method method) {
return new FloatPropertyMethod(method);
}
};
public static PropertyType typeForObject(RDFNode object)
throws PropertyTypeException {
if (object.isURIResource()) {
return RESOURCE;
}
if (object.isLiteral()) {
Literal literal = object.asLiteral();
RDFDatatype datatype = literal.getDatatype();
if (datatype == null || datatype.equals(XSDstring)) {
return STRING;
}
if (datatype.equals(XSDfloat)) {
return FLOAT;
}
}
throw new PropertyTypeException("Unsupported datatype on object: "
+ object);
}
public static PropertyType typeForParameterType(Class<?> parameterType)
throws PropertyTypeException {
if (Float.TYPE.equals(parameterType)) {
return FLOAT;
}
if (String.class.equals(parameterType)) {
return STRING;
}
if (!parameterType.isPrimitive()) {
return RESOURCE;
}
throw new PropertyTypeException(
"Unsupported parameter type on method: " + parameterType);
}
public static PropertyStatement createPropertyStatement(Statement s)
throws PropertyTypeException {
PropertyType type = PropertyType.typeForObject(s.getObject());
return type.buildPropertyStatement(s);
}
public static PropertyMethod createPropertyMethod(Method method)
throws PropertyTypeException {
Class<?> parameterType = method.getParameterTypes()[0];
PropertyType type = PropertyType.typeForParameterType(parameterType);
return type.buildPropertyMethod(method);
}
protected abstract PropertyStatement buildPropertyStatement(Statement s);
protected abstract PropertyMethod buildPropertyMethod(Method method);
public static abstract class PropertyStatement {
private final PropertyType type;
private final String predicateUri;
public PropertyStatement(PropertyType type, Property predicate) {
this.type = type;
this.predicateUri = predicate.getURI();
}
public PropertyType getType() {
return type;
}
public String getPredicateUri() {
return predicateUri;
}
public abstract Object getValue();
}
public static class ResourcePropertyStatement extends PropertyStatement {
private final String objectUri;
public ResourcePropertyStatement(Property predicate, String objectUri) {
super(RESOURCE, predicate);
this.objectUri = objectUri;
}
@Override
public String getValue() {
return objectUri;
}
}
public static class StringPropertyStatement extends PropertyStatement {
private final String string;
public StringPropertyStatement(Property predicate, String string) {
super(STRING, predicate);
this.string = string;
}
@Override
public String getValue() {
return string;
}
}
public static class FloatPropertyStatement extends PropertyStatement {
private final float f;
public FloatPropertyStatement(Property predicate, float f) {
super(FLOAT, predicate);
this.f = f;
}
@Override
public Float getValue() {
return f;
}
}
public static abstract class PropertyMethod {
protected final PropertyType type;
protected final Method method;
public PropertyMethod(PropertyType type, Method method) {
this.type = type;
this.method = method;
}
public Method getMethod() {
return method;
}
public Class<?> getParameterType() {
return method.getParameterTypes()[0];
}
public void confirmCompatible(PropertyStatement ps)
throws PropertyTypeException {
if (type != ps.getType()) {
throw new PropertyTypeException(
"Can't apply statement of type " + ps.getType()
+ " to a method of type " + type);
}
}
public void invoke(Object instance, Object value)
throws PropertyTypeException {
try {
method.invoke(instance, value);
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
throw new PropertyTypeException("Property method failed.", e);
}
}
}
public static class ResourcePropertyMethod extends PropertyMethod {
public ResourcePropertyMethod(Method method) {
super(RESOURCE, method);
}
}
public static class StringPropertyMethod extends PropertyMethod {
public StringPropertyMethod(Method method) {
super(STRING, method);
}
}
public static class FloatPropertyMethod extends PropertyMethod {
public FloatPropertyMethod(Method method) {
super(FLOAT, method);
}
}
public static class PropertyTypeException extends Exception {
public PropertyTypeException(String message) {
super(message);
}
public PropertyTypeException(String message, Throwable cause) {
super(message, cause);
}
}
}

View file

@ -0,0 +1,13 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.utils.configuration;
import edu.cornell.mannlib.vitro.webapp.modelaccess.RequestModelAccess;
/**
* When the ConfigurationBeanLoader creates an instance of this class, it will
* call this method, supplying the RDF models for the current HTTP request.
*/
public interface RequestModelsUser {
void setRequestModels(RequestModelAccess models);
}

View file

@ -0,0 +1,20 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.utils.configuration;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* The annotated method should be called after the bean is instantiated, to
* confirm that the bean is correctly formed.
*
* If the bean is not correctly formed, throw a runtime exception.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Validation {
// No elements
}

View file

@ -0,0 +1,135 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
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.Map;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.PropertyMethod;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.PropertyStatement;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.PropertyTypeException;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.ResourcePropertyStatement;
/**
* An instance of a ConfigurationBean, packaged with the distilled information
* about the annotated methods on the class.
*/
public class WrappedInstance<T> {
private final T instance;
private final Map<String, PropertyMethod> propertyMethods;
private final Set<Method> validationMethods;
public WrappedInstance(T instance,
Map<String, PropertyMethod> propertyMethods,
Set<Method> validationMethods) {
this.instance = instance;
this.propertyMethods = propertyMethods;
this.validationMethods = validationMethods;
}
/**
* The loader calls this as soon as the instance is created.
*
* If the loader did not have access to a request object, then req will be
* null. If the instance expects request models, an exception will be
* thrown.
*/
public void satisfyInterfaces(ServletContext ctx, HttpServletRequest req)
throws ResourceUnavailableException {
if (instance instanceof ContextModelsUser) {
if (ctx == null) {
throw new ResourceUnavailableException("Cannot satisfy "
+ "ContextModelsUser interface: context not available.");
} else {
ContextModelsUser cmu = (ContextModelsUser) instance;
cmu.setContextModels(ModelAccess.on(ctx));
}
}
if (instance instanceof RequestModelsUser) {
if (req == null) {
throw new ResourceUnavailableException("Cannot satisfy "
+ "RequestModelsUser interface: request not available.");
} else {
RequestModelsUser rmu = (RequestModelsUser) instance;
rmu.setRequestModels(ModelAccess.on(req));
}
}
}
/**
* The loader provides the distilled property statements from the RDF, to
* populate the instance.
*/
public void setProperties(ConfigurationBeanLoader loader,
Collection<PropertyStatement> propertyStatements)
throws PropertyTypeException, NoSuchPropertyMethodException,
ConfigurationBeanLoaderException {
for (PropertyStatement ps : propertyStatements) {
PropertyMethod pm = propertyMethods.get(ps.getPredicateUri());
if (pm == null) {
throw new NoSuchPropertyMethodException(ps);
}
pm.confirmCompatible(ps);
if (ps instanceof ResourcePropertyStatement) {
ResourcePropertyStatement rps = (ResourcePropertyStatement) ps;
Object subordinate = loader.loadInstance(rps.getValue(),
pm.getParameterType());
pm.invoke(instance, subordinate);
} else {
pm.invoke(instance, ps.getValue());
}
}
}
/**
* After the interfaces have been satisfied and the instance has been
* populated, call any validation methods to see whether the instance is
* viable.
*/
public void validate() throws ValidationFailedException {
for (Method method : validationMethods) {
try {
method.invoke(instance);
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
throw new ValidationFailedException(
"Error executing validation method '" + method + "'", e);
}
}
}
/**
* Once satisfied, populated, and validated, the instance is ready to go.
*/
public T getInstance() {
return instance;
}
public static class ResourceUnavailableException extends Exception {
public ResourceUnavailableException(String message) {
super(message);
}
}
public static class ValidationFailedException extends Exception {
public ValidationFailedException(String message, Throwable cause) {
super(message, cause);
}
}
public static class NoSuchPropertyMethodException extends Exception {
public NoSuchPropertyMethodException(PropertyStatement ps) {
super("No property method for '" + ps.getPredicateUri() + "'");
}
}
}

View file

@ -0,0 +1,68 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.testing;
import static com.hp.hpl.jena.rdf.model.ResourceFactory.createLangLiteral;
import static com.hp.hpl.jena.rdf.model.ResourceFactory.createPlainLiteral;
import static com.hp.hpl.jena.rdf.model.ResourceFactory.createProperty;
import static com.hp.hpl.jena.rdf.model.ResourceFactory.createResource;
import static com.hp.hpl.jena.rdf.model.ResourceFactory.createStatement;
import static com.hp.hpl.jena.rdf.model.ResourceFactory.createTypedLiteral;
import java.util.SortedSet;
import java.util.TreeSet;
import com.hp.hpl.jena.datatypes.xsd.XSDDatatype;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.vocabulary.RDF;
/**
* Just some helper methods for Test classes that work with models.
*/
public class ModelUtilitiesTestHelper {
public static Model model(Statement... stmts) {
return ModelFactory.createDefaultModel().add(stmts);
}
public static Statement typeStatement(String subjectUri, String classUri) {
return createStatement(createResource(subjectUri), RDF.type,
createResource(classUri));
}
public static Statement objectProperty(String subjectUri,
String propertyUri, String objectUri) {
return createStatement(createResource(subjectUri),
createProperty(propertyUri), createResource(objectUri));
}
public static Statement dataProperty(String subjectUri, String propertyUri,
String objectValue) {
return createStatement(createResource(subjectUri),
createProperty(propertyUri), createPlainLiteral(objectValue));
}
public static Statement dataProperty(String subjectUri, String propertyUri,
Object objectValue, XSDDatatype dataType) {
return createStatement(createResource(subjectUri),
createProperty(propertyUri),
createTypedLiteral(String.valueOf(objectValue), dataType));
}
public static Statement dataProperty(String subjectUri, String propertyUri,
String objectValue, String language) {
return createStatement(createResource(subjectUri),
createProperty(propertyUri),
createLangLiteral(objectValue, language));
}
public static SortedSet<String> modelToStrings(Model m) {
SortedSet<String> set = new TreeSet<>();
for (Statement stmt : m.listStatements().toList()) {
set.add(stmt.toString());
}
return set;
}
}