diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/filters/VitroFilterUtils.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/filters/VitroFilterUtils.java index 169a7e09b4..33ee73f5c5 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/filters/VitroFilterUtils.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/filters/VitroFilterUtils.java @@ -6,8 +6,6 @@ import java.util.Comparator; import java.util.List; -import javax.servlet.ServletContext; - import net.sf.jga.fn.UnaryFunctor; import net.sf.jga.fn.adaptor.ChainUnary; import net.sf.jga.fn.property.GetProperty; @@ -16,12 +14,12 @@ /** * Static methods to help create commonly used filters. */ -public class VitroFilterUtils { +public class VitroFilterUtils { /** * Gets a filter that hides any property or resource that is restricted from * public view. */ - public static VitroFilters getPublicFilter(ServletContext ctx) { + public static VitroFilters getPublicFilter() { return new FilterByDisplayPermission(); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassGroupCache.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassGroupCache.java index 96c125f04c..82cff3bda0 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassGroupCache.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/VClassGroupCache.java @@ -89,14 +89,7 @@ public class VClassGroupCache implements SearchIndexer.Listener { private final RebuildGroupCacheThread _cacheRebuildThread; - /** - * Need a pointer to the context to get DAOs and models. - */ - private final ServletContext context; - - private VClassGroupCache(ServletContext context) { - this.context = context; this._groupList = null; if (StartupStatus.getBean(context).isStartupAborted()) { @@ -199,7 +192,7 @@ protected void requestStop() { } protected VClassGroupDao getVCGDao() { - return ModelAccess.on(context).getWebappDaoFactory().getVClassGroupDao(); + return ModelAccess.getInstance().getWebappDaoFactory().getVClassGroupDao(); } public void doSynchronousRebuild(){ @@ -263,11 +256,11 @@ public static VClassGroupsForRequest getVClassGroups(HttpServletRequest req) { */ protected static void rebuildCacheUsingSearch( VClassGroupCache cache ) throws SearchEngineException{ long start = System.currentTimeMillis(); - WebappDaoFactory wdFactory = ModelAccess.on(cache.context).getWebappDaoFactory(); + WebappDaoFactory wdFactory = ModelAccess.getInstance().getWebappDaoFactory(); SearchEngine searchEngine = ApplicationUtils.instance().getSearchEngine(); - VitroFilters vFilters = VitroFilterUtils.getPublicFilter(cache.context); + VitroFilters vFilters = VitroFilterUtils.getPublicFilter(); VClassGroupDao vcgDao = new WebappDaoFactoryFiltering(wdFactory, vFilters).getVClassGroupDao(); List groups = vcgDao.getPublicGroupsWithVClasses(ORDER_BY_DISPLAYRANK, @@ -488,7 +481,7 @@ protected void checkAndDoUpdate(Statement stmt) { } else if(VitroVocabulary.DISPLAY_RANK.equals(stmt.getPredicate().getURI())){ requestCacheUpdate(); } else if (RDFS.label.equals(stmt.getPredicate())){ - OntModel jenaOntModel = ModelAccess.on(context).getOntModelSelector().getTBoxModel(); + OntModel jenaOntModel = ModelAccess.getInstance().getOntModelSelector().getTBoxModel(); if( isClassNameChange(stmt, jenaOntModel) ) { requestCacheUpdate(); } 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 c1f1f0a611..78cef9ac62 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 @@ -27,7 +27,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import javax.servlet.ServletContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -93,7 +92,6 @@ public class SearchIndexerImpl implements SearchIndexer { private Integer threadPoolSize; private WorkerThreadPool pool; - private ServletContext ctx; private List excluders; private List modifiers; private Set uriFinders; @@ -134,7 +132,6 @@ public void startup(Application application, ComponentStartupStatus ss) { "startup() called after shutdown()."); } try { - this.ctx = application.getServletContext(); this.wadf = getFilteredWebappDaoFactory(); loadConfiguration(); @@ -150,14 +147,14 @@ public void startup(Application application, ComponentStartupStatus ss) { /** With a filtered factory, only public data goes into the search index. */ private WebappDaoFactory getFilteredWebappDaoFactory() { - WebappDaoFactory rawWadf = ModelAccess.on(ctx).getWebappDaoFactory(); - VitroFilters vf = VitroFilterUtils.getPublicFilter(ctx); + WebappDaoFactory rawWadf = ModelAccess.getInstance().getWebappDaoFactory(); + VitroFilters vf = VitroFilterUtils.getPublicFilter(); return new WebappDaoFactoryFiltering(rawWadf, vf); } private void loadConfiguration() throws ConfigurationBeanLoaderException { ConfigurationBeanLoader beanLoader = new ConfigurationBeanLoader( - ModelAccess.on(ctx).getOntModel(DISPLAY), ctx); + ModelAccess.getInstance().getOntModel(DISPLAY)); uriFinders = beanLoader.loadAll(IndexingUriFinder.class); excluders = new ArrayList<>(); 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 8c45a2fca4..f84c509695 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 @@ -2,21 +2,26 @@ package edu.cornell.mannlib.vitro.webapp.utils.configuration; -import static org.apache.jena.rdf.model.ResourceFactory.createResource; +import java.util.HashMap; import java.util.HashSet; -import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.TreeSet; -import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jena.query.ParameterizedSparqlString; +import org.apache.jena.query.Query; +import org.apache.jena.query.QueryExecution; +import org.apache.jena.query.QueryExecutionFactory; +import org.apache.jena.query.QueryFactory; +import org.apache.jena.query.QuerySolution; +import org.apache.jena.query.ResultSet; import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.Resource; -import org.apache.jena.vocabulary.RDF; - import edu.cornell.mannlib.vitro.webapp.utils.jena.criticalsection.LockableModel; import edu.cornell.mannlib.vitro.webapp.utils.jena.criticalsection.LockedModel; @@ -24,9 +29,31 @@ * Load one or more Configuration beans from a specified model. */ public class ConfigurationBeanLoader { + + private static final String FIND_URI_QUERY = "" + + "PREFIX rdfs: \n" + + "PREFIX dynapi: \n" + + "SELECT ?uri\n" + + "WHERE {\n" + + " {\n" + + " ?uri a ?type .\n" + + " ?type dynapi:implementedBy ?java .\n" + + " }\n" + + " UNION\n" + + " {\n" + + " ?uri a ?java .\n" + + " }\n" + + "} "; + + + private static final Log log = LogFactory.getLog(ConfigurationBeanLoader.class); + private static final String JAVA_URI_PREFIX = "java:"; + Map instancesMap = new HashMap(); + + // ---------------------------------------------------------------------- // utility methods // ---------------------------------------------------------------------- @@ -80,12 +107,6 @@ public static boolean isMatchingJavaUri(String uri1, String uri2) { /** Must not be null. */ private final LockableModel locking; - /** - * 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. @@ -93,44 +114,33 @@ public static boolean isMatchingJavaUri(String uri1, String uri2) { private final HttpServletRequest req; public ConfigurationBeanLoader(Model model) { - this(new LockableModel(model), null, null); + this(new LockableModel(model), null); } public ConfigurationBeanLoader(LockableModel locking) { - this(locking, null, null); - } - - public ConfigurationBeanLoader(Model model, ServletContext ctx) { - this(new LockableModel(model), ctx, null); - } - - public ConfigurationBeanLoader(LockableModel locking, ServletContext ctx) { - this(locking, ctx, null); + this(locking, null); } public ConfigurationBeanLoader(Model model, HttpServletRequest req) { this(new LockableModel(model), req); } - public ConfigurationBeanLoader(LockableModel locking, - HttpServletRequest req) { - this(locking, - (req == null) ? null : req.getSession().getServletContext(), - req); - } - - private ConfigurationBeanLoader(LockableModel locking, ServletContext ctx, - HttpServletRequest req) { - this.locking = Objects.requireNonNull(locking, - "locking may not be null."); - this.req = req; - this.ctx = ctx; - } + public ConfigurationBeanLoader(LockableModel locking, HttpServletRequest req) { + this.locking = Objects.requireNonNull(locking, "locking may not be null."); + this.req = req; + } /** * Load the instance with this URI, if it is assignable to this class. */ - public T loadInstance(String uri, Class resultClass) + public T loadInstance(String uri, Class resultClass) throws ConfigurationBeanLoaderException { + instancesMap.clear(); + T result = loadSubordinateInstance(uri, resultClass); + instancesMap.clear(); + return result; + } + + protected T loadSubordinateInstance(String uri, Class resultClass) throws ConfigurationBeanLoaderException { if (uri == null) { throw new NullPointerException("uri may not be null."); @@ -138,14 +148,22 @@ public T loadInstance(String uri, Class resultClass) if (resultClass == null) { throw new NullPointerException("resultClass may not be null."); } - + if (instancesMap.containsKey(uri)) { + try { + T t = (T) instancesMap.get(uri); + return t; + } catch (ClassCastException e) { + throw new ConfigurationBeanLoaderException(uri, e); + } + } try { ConfigurationRdf parsedRdf = ConfigurationRdfParser .parse(locking, uri, resultClass); WrappedInstance wrapper = InstanceWrapper .wrap(parsedRdf.getConcreteClass()); - wrapper.satisfyInterfaces(ctx, req); + wrapper.satisfyInterfaces(req); wrapper.checkCardinality(parsedRdf.getPropertyStatements()); + instancesMap.put(uri, wrapper.getInstance()); wrapper.setProperties(this, parsedRdf.getPropertyStatements()); wrapper.validate(); return wrapper.getInstance(); @@ -161,22 +179,48 @@ public T loadInstance(String uri, Class resultClass) public Set loadAll(Class resultClass) throws ConfigurationBeanLoaderException { Set uris = new HashSet<>(); - try (LockedModel m = locking.read()) { - 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()); - } - } - } - } - + findUris(resultClass, uris); Set instances = new HashSet<>(); for (String uri : uris) { instances.add(loadInstance(uri, resultClass)); } return instances; } + + /** + * Find all of the resources with the specified class, and instantiate them. + */ + public Map loadEach(Class resultClass){ + Set uris = new HashSet<>(); + findUris(resultClass, uris); + HashMap instances = new HashMap<>(); + for (String uri : uris) { + try { + instances.put(uri, loadInstance(uri, resultClass)); + } catch (ConfigurationBeanLoaderException e) { + log.error(e, e); + } + } + return instances; + } + + private void findUris(Class resultClass, Set uris) { + try (LockedModel m = locking.read()) { + for (String typeUri : toPossibleJavaUris(resultClass)) { + ParameterizedSparqlString pss = new ParameterizedSparqlString(FIND_URI_QUERY); + pss.setIri("java", typeUri); + Query query = QueryFactory.create(pss.toString()); + QueryExecution qexec = QueryExecutionFactory.create(query, m); + try { + ResultSet results = qexec.execSelect(); + while (results.hasNext()) { + QuerySolution solution = results.nextSolution(); + uris.add(solution.getResource("uri").getURI()); + } + } finally { + qexec.close(); + } + } + } + } } 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 4153d22953..17e8b876ed 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 @@ -4,18 +4,21 @@ 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 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.query.ParameterizedSparqlString; +import org.apache.jena.query.Query; +import org.apache.jena.query.QueryExecution; +import org.apache.jena.query.QueryExecutionFactory; +import org.apache.jena.query.QueryFactory; +import org.apache.jena.query.QuerySolution; +import org.apache.jena.query.ResultSet; import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.RDFNode; import org.apache.jena.rdf.model.Selector; @@ -33,6 +36,28 @@ * ConfigurationRdf object. */ public class ConfigurationRdfParser { + + private static final String implementationClassesQuery = "" + + "PREFIX rdfs: \n" + + "PREFIX dynapi: \n" + + "SELECT DISTINCT \n" + + "?class \n" + + "?java \n" + + "(COUNT(DISTINCT(?intermediateClass)) as ?distance)\n" + + "WHERE {\n" + + " {\n" + + " ?uri a ?java .\n" + + " FILTER(STRSTARTS(STR(?java), 'java:'))\n" + + " }\n" + + " UNION\n" + + " {\n" + + " ?uri a ?type .\n" + + " ?type rdfs:subClassOf* ?intermediateClass .\n" + + " ?intermediateClass rdfs:subClassOf* ?class .\n" + + " ?class dynapi:implementedBy ?java .\n" + + " }\n" + + "} GROUP BY ?class ?java ORDER BY ?distance\n"; + public static ConfigurationRdf parse(LockableModel locking, String uri, Class resultClass) throws InvalidConfigurationRdfException { @@ -42,8 +67,6 @@ public static ConfigurationRdf parse(LockableModel locking, confirmExistenceInModel(locking, uri); - confirmEligibilityForResultClass(locking, uri, resultClass); - Set properties = loadProperties(locking, uri); Class concreteClass = determineConcreteClass(locking, uri, @@ -57,32 +80,13 @@ private static void confirmExistenceInModel(LockableModel locking, Selector s = new SimpleSelector(createResource(uri), null, (RDFNode) null); try (LockedModel m = locking.read()) { - if (m.listStatements(s).toList().isEmpty()) { + final List subjectStatements = m.listStatements(s).toList(); + if (subjectStatements.isEmpty()) { throw individualDoesNotAppearInModel(uri); } } } - private static void confirmEligibilityForResultClass(LockableModel locking, - String uri, Class resultClass) - throws InvalidConfigurationRdfException { - String resultClassUri = toJavaUri(resultClass); - try (LockedModel m = locking.read()) { - 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); - } - } - private static Set loadProperties(LockableModel locking, String uri) throws InvalidConfigurationRdfException { Set set = new HashSet<>(); @@ -111,26 +115,39 @@ private static Set loadProperties(LockableModel locking, } } - private static Class determineConcreteClass( - LockableModel locking, String uri, Class resultClass) - throws InvalidConfigurationRdfException { + private static Class determineConcreteClass(LockableModel locking, String uri, + Class resultClass) throws InvalidConfigurationRdfException { Set> concreteClasses = new HashSet<>(); try (LockedModel m = locking.read()) { - for (RDFNode node : m - .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)); - } + ParameterizedSparqlString pss = new ParameterizedSparqlString(implementationClassesQuery); + pss.setIri("uri", uri); + Query query = QueryFactory.create(pss.toString()); + QueryExecution qexec = QueryExecutionFactory.create(query, m); + try { + ResultSet results = qexec.execSelect(); + int distance = -1; + while (results.hasNext()) { + QuerySolution solution = results.nextSolution(); + String typeUri = solution.getResource("java").toString(); + int solDistance = solution.getLiteral("distance").getInt(); + //set to distance of first solution + if(distance < 0) { + distance = solDistance; + } + //process only solutions with the same distance + if (distance != solDistance) { + break; + } + + if (!isConcreteClass(typeUri)) { + continue; + } + concreteClasses.add(processTypeUri(typeUri, resultClass)); + } + } finally { + qexec.close(); + } } if (concreteClasses.isEmpty()) { @@ -227,20 +244,6 @@ private static InvalidConfigurationRdfException failedToLoadClass( "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( - String uri, String resultClassUri) { - return new InvalidConfigurationRdfException( - "A type statement is required: '" - + createStatement(createResource(uri), RDF.type, - createResource(resultClassUri))); - } - private static InvalidConfigurationRdfException noRdfStatements( String uri) { return new InvalidConfigurationRdfException( diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/Property.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/Property.java index da531f5984..32eb339136 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/Property.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/Property.java @@ -17,4 +17,5 @@ String uri(); int minOccurs() default 0; int maxOccurs() default Integer.MAX_VALUE; + boolean asString() default false; } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/PropertyType.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/PropertyType.java index 7bae32b366..7dfa8ac553 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/PropertyType.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/PropertyType.java @@ -2,7 +2,12 @@ package edu.cornell.mannlib.vitro.webapp.utils.configuration; +import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDboolean; +import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDdateTime; +import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDdecimal; import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDfloat; +import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDint; +import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDinteger; import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDstring; import java.lang.reflect.InvocationTargetException; @@ -30,8 +35,7 @@ public PropertyStatement buildPropertyStatement(Statement s) { } @Override - protected PropertyMethod buildPropertyMethod(Method method, - Property annotation) { + protected PropertyMethod buildPropertyMethod(Method method, Property annotation) { return new ResourcePropertyMethod(method, annotation); } @@ -44,8 +48,7 @@ public PropertyStatement buildPropertyStatement(Statement s) { } @Override - protected PropertyMethod buildPropertyMethod(Method method, - Property annotation) { + protected PropertyMethod buildPropertyMethod(Method method, Property annotation) { return new StringPropertyMethod(method, annotation); } }, @@ -57,10 +60,33 @@ public PropertyStatement buildPropertyStatement(Statement s) { } @Override - protected PropertyMethod buildPropertyMethod(Method method, - Property annotation) { + protected PropertyMethod buildPropertyMethod(Method method, Property annotation) { return new FloatPropertyMethod(method, annotation); } + }, + INTEGER { + @Override + public PropertyStatement buildPropertyStatement(Statement s) { + return new IntegerPropertyStatement(s.getPredicate().getURI(), s + .getObject().asLiteral().getInt()); + } + + @Override + protected PropertyMethod buildPropertyMethod(Method method, Property annotation) { + return new IntegerPropertyMethod(method, annotation); + } + }, + BOOLEAN{ + @Override + public PropertyStatement buildPropertyStatement(Statement s) { + return new BooleanPropertyStatement(s.getPredicate().getURI(), s + .getObject().asLiteral().getBoolean()); + } + + @Override + protected PropertyMethod buildPropertyMethod(Method method, Property annotation) { + return new BooleanPropertyMethod(method, annotation); + } }; public static PropertyType typeForObject(RDFNode object) @@ -71,12 +97,24 @@ public static PropertyType typeForObject(RDFNode object) if (object.isLiteral()) { Literal literal = object.asLiteral(); RDFDatatype datatype = literal.getDatatype(); - if (datatype == null || datatype.equals(XSDstring) || datatype.equals(RDFLangString.rdfLangString)) { + if (datatype == null || + datatype.equals(XSDstring) || + datatype.equals(RDFLangString.rdfLangString) || + //TODO: Until more suitable type defined + datatype.equals(XSDdateTime)) { return STRING; } - if (datatype.equals(XSDfloat)) { + if (datatype.equals(XSDfloat) || + datatype.equals(XSDdecimal)){ return FLOAT; } + if (datatype.equals(XSDint) || + datatype.equals(XSDinteger)) { + return INTEGER; + } + if (datatype.equals(XSDboolean)) { + return BOOLEAN; + } } throw new PropertyTypeException("Unsupported datatype on object: " + object); @@ -87,6 +125,12 @@ public static PropertyType typeForParameterType(Class parameterType) if (Float.TYPE.equals(parameterType)) { return FLOAT; } + if (Integer.TYPE.equals(parameterType)) { + return INTEGER; + } + if (Boolean.TYPE.equals(parameterType)) { + return BOOLEAN; + } if (String.class.equals(parameterType)) { return STRING; } @@ -103,8 +147,7 @@ public static PropertyStatement createPropertyStatement(Statement s) return type.buildPropertyStatement(s); } - public static PropertyMethod createPropertyMethod(Method method, - Property annotation) 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, annotation); @@ -177,10 +220,39 @@ public Float getValue() { } } + public static class IntegerPropertyStatement extends PropertyStatement { + private final int i; + + public IntegerPropertyStatement(String predicateUri, int i) { + super(INTEGER, predicateUri); + this.i = i; + } + + @Override + public Integer getValue() { + return i; + } + } + + public static class BooleanPropertyStatement extends PropertyStatement { + private final Boolean bool; + + public BooleanPropertyStatement(String predicateUri, Boolean b) { + super(BOOLEAN, predicateUri); + this.bool = b; + } + + @Override + public Boolean getValue() { + return bool; + } + } + public static abstract class PropertyMethod { protected final PropertyType type; protected final Method method; protected final String propertyUri; + protected final boolean asString; protected final int minOccurs; protected final int maxOccurs; @@ -192,6 +264,7 @@ public PropertyMethod(PropertyType type, Method method, this.propertyUri = annotation.uri(); this.minOccurs = annotation.minOccurs(); this.maxOccurs = annotation.maxOccurs(); + this.asString = annotation.asString(); checkCardinalityBounds(); } @@ -218,14 +291,26 @@ public int getMinOccurs() { public int getMaxOccurs() { return maxOccurs; } + + public boolean getAsString() { + return asString; + } - 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 confirmCompatible(PropertyStatement ps) throws PropertyTypeException { + final PropertyType psType = ps.getType(); + if (asString && psType.equals(PropertyType.RESOURCE) && type.equals(PropertyType.STRING)) { + return; + } + if (type != psType && !(isSubtype(psType, type))) { + throw new PropertyTypeException( + "Can't apply statement of type " + psType + " to a method of type " + type + ".\n Class " + method.getDeclaringClass().getCanonicalName() + ". Method:" + method.getName()); + } + } + + private boolean isSubtype(PropertyType subType, PropertyType superType){ + if (subType.equals(INTEGER) && superType.equals(FLOAT)) + return true; + return false; } public void invoke(Object instance, Object value) @@ -258,6 +343,18 @@ public FloatPropertyMethod(Method method, Property annotation) { } } + public static class IntegerPropertyMethod extends PropertyMethod { + public IntegerPropertyMethod(Method method, Property annotation) { + super(INTEGER, method, annotation); + } + } + + public static class BooleanPropertyMethod extends PropertyMethod { + public BooleanPropertyMethod(Method method, Property annotation) { + super(BOOLEAN, method, annotation); + } + } + public static class PropertyTypeException extends Exception { public PropertyTypeException(String message) { super(message); 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 9e889426ce..3bd0b89e95 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 @@ -9,7 +9,6 @@ import java.util.Map; import java.util.Set; -import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; @@ -43,16 +42,11 @@ public WrappedInstance(T instance, * null. If the instance expects request models, an exception will be * thrown. */ - public void satisfyInterfaces(ServletContext ctx, HttpServletRequest req) + public void satisfyInterfaces(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)); - } + ContextModelsUser cmu = (ContextModelsUser) instance; + cmu.setContextModels(ModelAccess.getInstance()); } if (instance instanceof RequestModelsUser) { if (req == null) { @@ -64,13 +58,8 @@ public void satisfyInterfaces(ServletContext ctx, HttpServletRequest req) } } if (instance instanceof ConfigurationReader) { - if (ctx == null) { - throw new ResourceUnavailableException("Cannot satisfy " - + "ConfigurationReader interface: context not available."); - } else { - ConfigurationReader cr = (ConfigurationReader) instance; - cr.setConfigurationProperties(ConfigurationProperties.getBean(ctx)); - } + ConfigurationReader cr = (ConfigurationReader) instance; + cr.setConfigurationProperties(ConfigurationProperties.getInstance()); } } @@ -128,10 +117,9 @@ public void setProperties(ConfigurationBeanLoader loader, } pm.confirmCompatible(ps); - if (ps instanceof ResourcePropertyStatement) { + if (!pm.asString && ps instanceof ResourcePropertyStatement) { ResourcePropertyStatement rps = (ResourcePropertyStatement) ps; - Object subordinate = loader.loadInstance(rps.getValue(), - pm.getParameterType()); + Object subordinate = loader.loadSubordinateInstance(rps.getValue(), pm.getParameterType()); pm.invoke(instance, subordinate); } else { pm.invoke(instance, ps.getValue()); 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 bfd88db4a4..3bef516981 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 @@ -6,9 +6,12 @@ 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.XSDboolean; import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDfloat; +import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDinteger; import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDstring; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -31,13 +34,6 @@ import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.PropertyTypeException; import edu.cornell.mannlib.vitro.webapp.utils.configuration.WrappedInstance.ResourceUnavailableException; -/** - * TODO - * - * Circularity prevention. Before setting properties, create a WeakMap of - * instances by URIs, so if a property refers to a created instance, we just - * pass it in. - */ public class ConfigurationBeanLoaderTest extends ConfigurationBeanLoaderTestBase { @@ -87,6 +83,7 @@ public void noStatementsAboutUri_throwsException() "The model contains no statements about")); } + @Ignore @Test public void uriDoesNotDeclareResultClassAsType_throwsException() throws ConfigurationBeanLoaderException { @@ -257,31 +254,6 @@ public ConstructorFails() { // -------------------------------------------- - @Test - public void loaderCantSatisfyContextModelsUser_throwsException() - throws ConfigurationBeanLoaderException { - model.add(typeStatement(GENERIC_INSTANCE_URI, - toJavaUri(NeedsContextModels.class))); - - loader = noContextLoader; - - expectSimpleFailure( - NeedsContextModels.class, - throwable(ConfigurationBeanLoaderException.class, - "Failed to load"), - throwable(ResourceUnavailableException.class, - "Cannot satisfy ContextModelsUser")); - } - - public static class NeedsContextModels implements ContextModelsUser { - @Override - public void setContextModels(ContextModelAccess models) { - // Nothing to do - } - } - - // -------------------------------------------- - @Test public void loaderCantSatisfyRequestModelsUser_throwsException() throws ConfigurationBeanLoaderException { @@ -368,10 +340,8 @@ public void setHelper(SimpleDateFormat sdf) { public void simpleSuccess() throws ConfigurationBeanLoaderException { model.add(typeStatement(SIMPLE_SUCCESS_INSTANCE_URI, toJavaUri(SimpleSuccess.class))); - SimpleSuccess instance = loader.loadInstance( SIMPLE_SUCCESS_INSTANCE_URI, SimpleSuccess.class); - assertNotNull(instance); } @@ -394,6 +364,37 @@ public void simpleSuccessIgnoringExtraProperties() throws ConfigurationBeanLoade public static class SimpleSuccess { // Nothing of interest. } + + public static class Friend { + + public Friend() { + + } + Friend friend; + String uri; + Integer intNumber; + boolean booleanValue; + + @Property(uri = "http://set.friend/property") + public void setFriend(Friend friend) { + this.friend = friend; + } + + @Property(uri = "http://set.friend/asString", asString = true) + public void setUri(String uri) { + this.uri = uri; + } + + @Property(uri = "http://set.friend/setInteger") + public void setIntNumber(int intNumber) { + this.intNumber = intNumber; + } + + @Property(uri = "http://set.friend/setBoolean") + public void setBooleanValue(boolean booleanValue) { + this.booleanValue = booleanValue; + } + } // -------------------------------------------- @@ -673,7 +674,53 @@ public void loadAll_oneResult_success() Set instances = loader.loadAll(SimpleSuccess.class); assertEquals(1, instances.size()); } - + + // -------------------------------------------- + + @Test + public void loop_test() throws ConfigurationBeanLoaderException { + model.add(new Statement[] { + typeStatement("http://friend.instance/one", toJavaUri(Friend.class)), + typeStatement("http://friend.instance/two", toJavaUri(Friend.class)), + objectProperty("http://friend.instance/one", "http://set.friend/property", "http://friend.instance/two"), + objectProperty("http://friend.instance/two", "http://set.friend/property", "http://friend.instance/one") }); + + Friend friend = loader.loadInstance("http://friend.instance/one", Friend.class); + assertNotEquals(friend, friend.friend); + assertEquals(friend, friend.friend.friend); + } + + @Test + public void loadUriAsString() throws ConfigurationBeanLoaderException { + model.add(new Statement[] { + typeStatement("http://friend.instance/one", toJavaUri(Friend.class)), + typeStatement("http://friend.instance/two", toJavaUri(Friend.class)), + objectProperty("http://friend.instance/one", "http://set.friend/asString", + "http://friend.instance/two"), }); + + Friend friend = loader.loadInstance("http://friend.instance/one", Friend.class); + assertEquals(friend.uri, "http://friend.instance/two"); + } + + @Test + public void loadInteger() throws ConfigurationBeanLoaderException { + model.add(new Statement[] { + typeStatement("http://friend.instance/one", toJavaUri(Friend.class)), + dataProperty("http://friend.instance/one", "http://set.friend/setInteger", 42, XSDinteger), }); + + Friend friend = loader.loadInstance("http://friend.instance/one", Friend.class); + assertEquals(new Integer(42), friend.intNumber); + } + + @Test + public void loadBoolean() throws ConfigurationBeanLoaderException { + model.add(new Statement[] { + typeStatement("http://friend.instance/one", toJavaUri(Friend.class)), + dataProperty("http://friend.instance/one", "http://set.friend/setBoolean", true, XSDboolean), }); + + Friend friend = loader.loadInstance("http://friend.instance/one", Friend.class); + assertEquals(Boolean.TRUE, friend.booleanValue); + } // -------------------------------------------- @Test diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTestBase.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTestBase.java index 66db7d49f7..c46780127d 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTestBase.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationBeanLoaderTestBase.java @@ -55,7 +55,7 @@ public void setup() { model = model(); loader = new ConfigurationBeanLoader(model, req); - noRequestLoader = new ConfigurationBeanLoader(model, ctx); + noRequestLoader = new ConfigurationBeanLoader(model); noContextLoader = new ConfigurationBeanLoader(model); } @@ -63,15 +63,13 @@ public void setup() { // 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()); + 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); - } + @SuppressWarnings("unused") + Object unused = loader.loadInstance(GENERIC_INSTANCE_URI, failureClass); + } protected ExpectedThrowable throwable(Class clazz, String messageSubstring) {