VIVO-1408 Allow the use of a #-terminated prefix on JAVA class URIs
This commit is contained in:
parent
8fbfc6d4ff
commit
7f8b16bc1b
5 changed files with 253 additions and 72 deletions
|
@ -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<String> toPossibleJavaUris(Class<?> clazz) {
|
||||
Set<String> 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<T> parsedRdf = ConfigurationRdfParser.parse(
|
||||
locking, uri, resultClass);
|
||||
WrappedInstance<T> wrapper = InstanceWrapper.wrap(parsedRdf
|
||||
.getConcreteClass());
|
||||
ConfigurationRdf<T> parsedRdf = ConfigurationRdfParser
|
||||
.parse(locking, uri, resultClass);
|
||||
WrappedInstance<T> wrapper = InstanceWrapper
|
||||
.wrap(parsedRdf.getConcreteClass());
|
||||
wrapper.satisfyInterfaces(ctx, req);
|
||||
wrapper.checkCardinality(parsedRdf.getPropertyStatements());
|
||||
wrapper.setProperties(this, parsedRdf.getPropertyStatements());
|
||||
wrapper.validate();
|
||||
return wrapper.getInstance();
|
||||
} 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<String> uris = new HashSet<>();
|
||||
try (LockedModel m = locking.read()) {
|
||||
List<Resource> 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<Resource> resources = m.listResourcesWithProperty(RDF.type,
|
||||
createResource(typeUri)).toList();
|
||||
for (Resource r : resources) {
|
||||
if (r.isURIResource()) {
|
||||
uris.add(r.getURI());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<RDFNode> 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<PropertyStatement> set = new HashSet<>();
|
||||
|
||||
try (LockedModel m = locking.read()) {
|
||||
List<Statement> rawStatements = m.listStatements(
|
||||
m.getResource(uri), (Property) null, (RDFNode) null)
|
||||
.toList();
|
||||
List<Statement> 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<Class<? extends T>> 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 <T> Class<? extends T> processTypeUri(String typeUri,
|
||||
Class<T> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <java:edu.cornell.mannlib.vitro.webapp.application.ApplicationImpl> .
|
||||
```
|
||||
|
||||
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 <java:edu.cornell.mannlib.vitro.webapp#application.ApplicationImpl> .
|
||||
```
|
||||
|
||||
which implies that this is equivalent also:
|
||||
|
||||
```
|
||||
@prefix javaWebapp: <java:edu.cornell.mannlib.vitro.webapp#>
|
||||
: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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue