org.openrdf.repository.object.managers.RoleMapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of alibaba-composition-object Show documentation
Show all versions of alibaba-composition-object Show documentation
The Object Composition library merges multiple Java objects into a single multi-subject object.
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright (c) 2011 Talis Inc., Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package org.openrdf.repository.object.managers;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.openrdf.annotations.Classpath;
import org.openrdf.annotations.Iri;
import org.openrdf.annotations.Mixin;
import org.openrdf.model.URI;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.model.impl.ValueFactoryImpl;
import org.openrdf.model.vocabulary.OWL;
import org.openrdf.repository.object.exceptions.ObjectStoreConfigException;
import org.openrdf.repository.object.managers.helpers.HierarchicalRoleMapper;
import org.openrdf.repository.object.managers.helpers.RoleMatcher;
import org.openrdf.repository.object.managers.helpers.WeakValueMap;
import org.openrdf.repository.object.vocabulary.MSG;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Tracks the annotation, concept, and behaviour classes and what rdf:type they
* should be used with.
*
* @author James Leigh
*
*/
public class RoleMapper implements Cloneable {
private static final WeakHashMap,ClassLoader>> classloaders = new WeakHashMap,ClassLoader>>();
private ValueFactory vf;
private Logger logger = LoggerFactory.getLogger(RoleMapper.class);
private HierarchicalRoleMapper roleMapper = new HierarchicalRoleMapper();
private Map>> instances = new ConcurrentHashMap>>(
256);
private RoleMatcher matches = new RoleMatcher();
private Map annotations = new HashMap();
private Map annotationURIs = new HashMap();
private Map, String> complementIDs;
private Map, Class>> complementClasses;
private Map, List>> intersections;
private Set> conceptClasses = new HashSet>();
public RoleMapper() {
this(ValueFactoryImpl.getInstance());
}
public RoleMapper(ValueFactory vf) {
this.vf = vf;
roleMapper.setURIFactory(vf);
complementIDs = new ConcurrentHashMap, String>();
complementClasses = new ConcurrentHashMap, Class>>();
intersections = new ConcurrentHashMap, List>>();
}
public RoleMapper clone() {
try {
RoleMapper cloned = (RoleMapper) super.clone();
cloned.roleMapper = roleMapper.clone();
cloned.instances = clone(instances);
cloned.matches = matches.clone();
cloned.annotations = new HashMap(annotations);
cloned.annotationURIs = new HashMap(annotationURIs);
cloned.complementIDs = new ConcurrentHashMap, String>(complementIDs);
cloned.complementClasses = new ConcurrentHashMap, Class>>(complementClasses);
cloned.intersections = clone(intersections);
cloned.conceptClasses = new HashSet>(conceptClasses);
return cloned;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
private Map> clone(Map> map) {
Map> cloned = new ConcurrentHashMap>(map);
for (Map.Entry> e : cloned.entrySet()) {
e.setValue(new CopyOnWriteArrayList(e.getValue()));
}
return cloned;
}
public Collection> getConceptClasses() {
return conceptClasses;
}
public Collection> findIndividualRoles(URI instance,
Collection> classes) {
List> list = instances.get(instance);
if (list != null) {
classes.addAll(list);
addImpliedRoles(list, classes);
}
list = new ArrayList>();
matches.findRoles(instance.stringValue(), list);
classes.addAll(list);
addImpliedRoles(list, classes);
return classes;
}
public boolean isRecordedConcept(URI type, ClassLoader cl) {
if (roleMapper.isTypeRecorded(type)) {
for (Class> role : findAllRoles(type)) {
if (findType(role) != null)
return true;
}
}
if ("java:".equals(type.getNamespace())) {
try {
synchronized (cl) {
java.lang.Class.forName(type.getLocalName(), true, cl);
}
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
return false;
}
public Class> findInterfaceConcept(URI uri) {
Class> concept = null;
Class> mapped = null;
Collection> rs = findAllRoles(uri);
for (Class> r : rs) {
URI type = findType(r);
if (r.isInterface() && type != null) {
concept = r;
if (uri.equals(type)) {
mapped = r;
if (r.getSimpleName().equals(uri.getLocalName())) {
return r;
}
}
}
}
if (mapped != null)
return mapped;
if (concept != null)
return concept;
return null;
}
public Class> findConcept(URI uri, ClassLoader cl) {
if (roleMapper.isTypeRecorded(uri)) {
Class> concept = null;
Class> mapped = null;
Class> face = null;
Collection> rs = findAllRoles(uri);
for (Class> r : rs) {
URI type = findType(r);
if (type != null && r.isInterface()) {
concept = r;
}
if (uri.equals(type)) {
mapped = r;
if (r.getSimpleName().equals(uri.getLocalName())) {
return r;
} else if (r.isInterface()) {
face = r;
}
}
}
if (face != null)
return face;
if (mapped != null)
return mapped;
if (concept != null)
return concept;
}
if ("java:".equals(uri.getNamespace())) {
try {
if (cl == null) {
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
return java.lang.Class.forName(uri.getLocalName(), true, ccl);
}
return java.lang.Class.forName(uri.getLocalName(), true, cl);
} catch (ClassNotFoundException e) {
return null;
}
}
return null;
}
public Collection> findRoles(URI type) {
return findAdditionalRoles(roleMapper.findRoles(type));
}
public Collection> findRoles(Collection types,
Collection> roles) {
return findAdditionalRoles(roleMapper.findRoles(types, roles));
}
public Collection> findAdditionalRoles(Collection> classes) {
if (intersections.isEmpty() && complementIDs.isEmpty() && complementClasses.isEmpty())
return classes;
List> result;
result = new ArrayList>(classes.size() * 2 + 2);
result.addAll(classes);
int before = result.size();
addIntersectionsAndComplements(result);
int after = result.size();
if (before != after) {
ArrayList> anonymous;
anonymous = new ArrayList>(result.subList(before, after));
addImpliedRoles(anonymous, result);
}
return result;
}
public Collection findSubTypes(Class> role, Collection rdfTypes) {
return roleMapper.findSubTypes(role, rdfTypes);
}
public URI findType(Class> concept) {
return roleMapper.findType(concept);
}
public boolean isNamedTypePresent() {
return roleMapper.isNamedTypePresent();
}
public boolean isIndividualRolesPresent(URI instance) {
return !matches.isEmpty() || !instances.isEmpty() && instances.containsKey(instance);
}
public URI findAnnotation(Method ann) {
return annotations.get(ann);
}
public Method findAnnotationMethod(URI uri) {
return annotationURIs.get(uri);
}
public boolean isRecordedAnnotation(URI uri) {
return findAnnotationMethod(uri) != null;
}
public void addAnnotation(Class> annotation) {
for (Method m : annotation.getDeclaredMethods()) {
if (!m.isAnnotationPresent(Iri.class)) {
String msg = "@" + Iri.class.getSimpleName()
+ " annotation required in " + m.toGenericString();
throw new IllegalArgumentException(msg);
}
String uri = m.getAnnotation(Iri.class).value();
addAnnotation(m, new URIImpl(uri));
}
}
public void addAnnotation(Class> annotation, URI uri) {
if (annotation.getDeclaredMethods().length != 1)
throw new IllegalArgumentException(
"Must specify annotation method if multiple methods exist: "
+ annotation);
addAnnotation(annotation.getDeclaredMethods()[0], uri);
}
public void addAnnotation(Method annotation) {
if (!annotation.isAnnotationPresent(Iri.class))
throw new IllegalArgumentException("@" + Iri.class.getSimpleName()
+ " annotation required in " + annotation.toGenericString());
String uri = annotation.getAnnotation(Iri.class).value();
addAnnotation(annotation, new URIImpl(uri));
}
public void addAnnotation(Method annotation, URI uri) {
annotations.put(annotation, uri);
annotationURIs.put(uri, annotation);
if (annotation.isAnnotationPresent(Iri.class)) {
String iri = annotation.getAnnotation(Iri.class).value();
if (!uri.stringValue().equals(iri)) {
addAnnotation(annotation);
}
}
}
public void addConcept(Class> role) throws ObjectStoreConfigException {
recordRole(role, role, null, true, true, true);
}
public void addConcept(Class> role, URI type)
throws ObjectStoreConfigException {
recordRole(role, role, type, true, true, true);
}
public void addBehaviour(Class> role) throws ObjectStoreConfigException {
assertBehaviour(role);
boolean hasType = false;
for (Class> face : role.getInterfaces()) {
boolean recorded = recordRole(role, face, null, true, false, false);
if (recorded && hasType) {
throw new ObjectStoreConfigException(role.getSimpleName()
+ " can only implement one concept");
} else {
hasType |= recorded;
}
}
if (!hasType)
throw new ObjectStoreConfigException(role.getSimpleName()
+ " must implement a concept or mapped explicitly");
}
public void addBehaviour(Class> role, URI type)
throws ObjectStoreConfigException {
assertBehaviour(role);
recordRole(role, null, type, true, false, false);
}
private void assertBehaviour(Class> role)
throws ObjectStoreConfigException {
if (isAnnotationPresent(role))
throw new ObjectStoreConfigException(role.getSimpleName()
+ " cannot have a concept annotation");
if (role.isInterface())
throw new ObjectStoreConfigException(role.getSimpleName()
+ " is an interface and not a behaviour");
for (Method method : role.getDeclaredMethods()) {
if (isAnnotationPresent(method)
&& method.getName().startsWith("get"))
throw new ObjectStoreConfigException(role.getSimpleName()
+ " cannot have a property annotation");
}
}
private Collection> findAllRoles(URI type) {
Set> set = new HashSet>();
for (Class> role : findRoles(type)) {
if (set.add(role)) {
addInterfaces(set, role.getSuperclass());
addInterfaces(set, role.getInterfaces());
}
}
return set;
}
private void addInterfaces(Set> set, Class>... list) {
for (Class> c : list) {
if (c != null && set.add(c)) {
addInterfaces(set, c.getSuperclass());
addInterfaces(set, c.getInterfaces());
}
}
}
private boolean isAnnotationPresent(AnnotatedElement role)
throws ObjectStoreConfigException {
return role.isAnnotationPresent(Iri.class);
}
private boolean recordRole(Class> role, Class> elm, URI rdfType,
boolean equiv, boolean concept, boolean primary)
throws ObjectStoreConfigException {
boolean hasType = false;
if (rdfType != null) {
if (concept) {
roleMapper.recordConcept(role, rdfType, equiv, primary);
} else {
roleMapper.recordBehaviour(role, rdfType, equiv);
}
hasType = true;
} else if (elm != null) {
URI defType = findDefaultType(elm);
if (defType != null) {
if (concept) {
roleMapper.recordConcept(role, defType, equiv, role.equals(elm));
} else {
roleMapper.recordBehaviour(role, defType, equiv);
}
hasType = true;
}
hasType |= recordAnonymous(role, elm, concept);
}
if (!hasType && elm != null) {
for (Class> face : elm.getInterfaces()) {
hasType |= recordRole(role, face, null, equiv, concept, false);
}
}
if (!hasType && primary) {
throw new ObjectStoreConfigException(role.getSimpleName()
+ " does not have an RDF type mapping");
}
recordMixins(role, elm, rdfType);
if (concept && !role.isInterface()) {
conceptClasses.add(role);
}
return hasType;
}
private void recordMixins(Class> role, Class> elm, URI rdfType)
throws ObjectStoreConfigException {
Mixin mixin = role.getAnnotation(Mixin.class);
if (mixin != null) {
for (Class> mix : mixin.value()) {
assertBehaviour(mix);
recordRole(mix, elm, rdfType, true, false, false);
}
for (String name : mixin.name()) {
Classpath cp = role.getAnnotation(Classpath.class);
String[] jars = cp == null ? new String[0] : cp.value();
Class> mix = findClass(name, role.getClassLoader(), jars,
role);
if (mix != null) {
assertBehaviour(mix);
recordRole(mix, elm, rdfType, true, false, false);
} else {
logger.error("Could not find {} in {}", name, jars);
}
}
}
}
private Class> findClass(String name, ClassLoader cl, String[] jars,
Class> base) {
try {
return Class.forName(name, true, findClassLoader(cl, jars, base));
} catch (Throwable e) {
logger.error(e.getMessage(), e);
return null;
}
}
private ClassLoader findClassLoader(ClassLoader parent, String[] jars,
Class> base) {
if (jars.length == 0)
return parent; // no children
Set urls = resolve(jars, base);
if (urls.isEmpty())
return parent; // no valid children
return createClassLoader(parent, urls);
}
private static synchronized ClassLoader createClassLoader(ClassLoader parent, Set urls) {
WeakValueMap, ClassLoader> map = classloaders.get(parent);
if (map == null) {
map = new WeakValueMap, ClassLoader>();
classloaders.put(parent, map);
}
ClassLoader direct = map.get(urls);
if (direct != null)
return direct; // already loaded
for (Map.Entry, ClassLoader> e : map.entrySet()) {
ClassLoader p = e.getValue();
if (p != null && urls.containsAll(e.getKey())) {
Set set = new HashSet(urls);
set.removeAll(e.getKey());
URL[] array = set.toArray(new URL[set.size()]);
ClassLoader cl = new URLClassLoader(array, p);
map.put(urls, cl);
return cl; // reuse an exiting cl as parent
}
}
URL[] array = urls.toArray(new URL[urls.size()]);
ClassLoader cl = new URLClassLoader(array, parent);
map.put(urls, cl);
return cl; // new cl
}
private Set resolve(String[] jars, Class> base) {
Set urls = new HashSet(jars.length);
for (int i = 0; i < jars.length; i++) {
try {
urls.add(resolve(jars[i], base));
} catch (MalformedURLException e) {
logger.error(e.getMessage(), e);
}
}
return urls;
}
private URL resolve(String jar, Class> base) throws MalformedURLException {
java.net.URI jurl = java.net.URI.create(jar);
if (jurl.isAbsolute())
return jurl.toURL();
URL url = base.getResource(jar);
if (url != null)
return url;
java.net.URI uri = java.net.URI.create(getSystemId(base));
return uri.resolve(jar).toURL();
}
private String getSystemId(Class> base) {
if (base.isAnnotationPresent(Iri.class))
return base.getAnnotation(Iri.class).value();
String name = base.getSimpleName() + ".class";
URL url = base.getResource(name);
if (url != null)
return url.toExternalForm();
return "java:" + base.getName();
}
private boolean recordAnonymous(Class> role, Class> elm,
boolean isConcept) throws ObjectStoreConfigException {
boolean recorded = false;
for (Annotation ann : elm.getAnnotations()) {
for (Method m : ann.annotationType().getDeclaredMethods()) {
try {
URI name = findAnnotation(m);
if (name == null && m.isAnnotationPresent(Iri.class)) {
addAnnotation(m);
name = findAnnotation(m);
}
if (name == null)
continue;
Object value = m.invoke(ann);
recorded |= recordAnonymous(role, isConcept, name, value);
} catch (Exception e) {
logger.error(e.getMessage(), e);
continue;
}
}
}
return recorded;
}
private boolean recordAnonymous(Class> role, boolean isConcept, URI name,
Object value) throws ObjectStoreConfigException {
boolean recorded = false;
if (OWL.EQUIVALENTCLASS.equals(name)) {
Object[] values = (Object[]) value;
for (Object v : values) {
if (v instanceof Class>) {
Class> concept = (Class>) v;
URI uri = findDefaultType(concept);
if (uri != null) {
// only equivalent named concepts are supported here
recorded |= recordRole(role, concept, uri, true, isConcept, false);
}
} else if (v instanceof String) {
URI uri = vf.createURI((String) v);
recorded |= recordRole(role, role, uri, true, isConcept, false);
} else {
logger.error("{} must have a value of type Class[] or String[]",
name);
}
}
}
if (MSG.MATCHING.equals(name)) {
String[] values = (String[]) value;
for (String pattern : values) {
matches.addRoles(pattern, role);
recorded = true;
}
}
if (OWL.ONEOF.equals(name)) {
String[] values = (String[]) value;
for (String instance : values) {
URI uri = vf.createURI(instance);
List> list = instances.get(uri);
if (list == null) {
list = new CopyOnWriteArrayList>();
instances.put(uri, list);
}
list.add(role);
recorded = true;
}
}
if (OWL.COMPLEMENTOF.equals(name)) {
if (value instanceof Object[]) {
Object[] ar = (Object[]) value;
if (ar.length != 1) {
logger.error("{} must have exactly one value",
name);
}
value = ar[0];
}
if (value instanceof Class>) {
Class> concept = (Class>) value;
recordRole(concept, concept, null, true, true, true);
complementClasses.put(role, concept);
recorded = true;
} else if (value instanceof String) {
complementIDs.put(role, (String) value);
recorded = true;
} else {
logger.error("{} must have a value of type Class or String",
name);
}
}
if (OWL.INTERSECTIONOF.equals(name)) {
List> ofs = new ArrayList>();
loop: for (Object v : (Object[]) value) {
if (v instanceof Class>) {
Class> concept = (Class>) v;
recordRole(concept, concept, null, true, true, true);
ofs.add(concept);
} else if (v instanceof String) {
Class> superclass = role.getSuperclass();
if (superclass != null
&& superclass.isAnnotationPresent(Iri.class)) {
if (v.equals(superclass.getAnnotation(Iri.class)
.value())) {
recordRole(superclass, superclass, null, true, true, true);
ofs.add(superclass);
continue loop;
}
}
for (Class> sp : role.getInterfaces()) {
if (sp.isAnnotationPresent(Iri.class)) {
if (v.equals(sp.getAnnotation(Iri.class).value())) {
recordRole(sp, sp, null, true, true, true);
ofs.add(sp);
continue loop;
}
}
}
logger.error("{} can only reference super classes", name);
} else {
logger.error(
"{} must have a value of type Class[] or String[]",
name);
}
}
intersections.put(role, ofs);
recorded = true;
}
if (OWL.UNIONOF.equals(name)) {
for (Object v : (Object[]) value) {
if (v instanceof Class>) {
Class> concept = (Class>) v;
recordRole(concept, concept, null, true, true, true);
if (role.isAssignableFrom(concept)) {
recorded = true; // implied
} else {
recorded |= recordRole(role, concept, null, false,
isConcept, false);
}
} else if (v instanceof String) {
recorded |= recordRole(role, null,
vf.createURI((String) v), false, isConcept, false);
} else {
logger.error(
"{} must have a value of type Class[] or String[]",
name);
}
}
}
return recorded;
}
private URI findDefaultType(AnnotatedElement elm) {
if (elm.isAnnotationPresent(Iri.class)) {
String value = elm.getAnnotation(Iri.class).value();
if (value != null) {
return vf.createURI(value);
}
}
return null;
}
private void addIntersectionsAndComplements(Collection> roles) {
for (Map.Entry, List>> e : intersections.entrySet()) {
Class> inter = e.getKey();
List> of = e.getValue();
if (!roles.contains(inter) && intersects(roles, of)) {
roles.add(inter);
}
}
if (complementIDs.isEmpty() && complementClasses.isEmpty())
return;
boolean complementAdded = false;
for (Map.Entry, String> e : complementIDs.entrySet()) {
Class> comp = e.getKey();
String of = e.getValue();
if (!roles.contains(comp) && !contains(roles, of)) {
complementAdded = true;
roles.add(comp);
}
}
for (Map.Entry, Class>> e : complementClasses.entrySet()) {
Class> comp = e.getKey();
Class> of = e.getValue();
if (!roles.contains(comp) && !contains(roles, of)) {
complementAdded = true;
roles.add(comp);
}
}
if (!complementAdded)
return;
for (Map.Entry, List>> e : intersections.entrySet()) {
Class> inter = e.getKey();
List> of = e.getValue();
if (!roles.contains(inter) && intersects(roles, of)) {
roles.add(inter);
}
}
}
private void addImpliedRoles(Collection> anonymous,
Collection> result) {
for (Class> r : anonymous) {
Class> sc = r.getSuperclass();
if (sc != null) {
URI uri = findType(sc);
if (uri != null) {
result.addAll(roleMapper.findRoles(uri));
}
}
for (Class> c : r.getInterfaces()) {
URI uri = findType(c);
if (uri != null) {
result.addAll(roleMapper.findRoles(uri));
}
}
}
}
private boolean intersects(Collection> roles, Collection> ofs) {
for (Class> of : ofs) {
if (!contains(roles, of))
return false;
}
return true;
}
private boolean contains(Collection> roles, Class> of) {
for (Class> type : roles) {
if (of.isAssignableFrom(type))
return true;
}
return false;
}
private boolean contains(Collection> roles, String iri) {
Set> set = new HashSet>();
for (Class> type : roles) {
if (isImplementationPresent(type, iri, set))
return true;
}
return false;
}
private boolean isImplementationPresent(Class> type, String iri, Set> ignore) {
if (type == null || ignore.contains(type))
return false;
Iri id = type.getAnnotation(Iri.class);
if (id != null && iri.equals(id.value()))
return true;
for (Class> face : type.getInterfaces()) {
if (isImplementationPresent(face, iri, ignore))
return true;
}
return isImplementationPresent(type.getSuperclass(), iri, ignore);
}
}