/*
* 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.helpers;
import org.openrdf.annotations.Iri;
import org.openrdf.annotations.Matching;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.repository.object.exceptions.ObjectStoreConfigException;
import org.openrdf.repository.object.managers.RoleMapper;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.*;
/**
* Loads the annotations, concepts and behaviours into memory.
*
* @author James Leigh
*
*/
public class RoleClassLoader {
private static final String CONCEPTS = "META-INF/org.openrdf.concepts";
private static final String BEHAVIOURS = "META-INF/org.openrdf.behaviours";
private static final String ANNOTATIONS = "META-INF/org.openrdf.annotations";
private final Logger logger = LoggerFactory.getLogger(DirectMapper.class);
private RoleMapper roleMapper;
public RoleClassLoader(RoleMapper roleMapper) {
this.roleMapper = roleMapper;
}
/**
* Loads and registers roles listed in resource.
*
* @throws ObjectStoreConfigException
*/
public void loadRoles(ClassLoader cl) throws ObjectStoreConfigException {
try {
ClassLoader first = RoleClassLoader.class.getClassLoader();
Set<URL> loaded;
loaded = load(new CheckForAnnotation(first), first, "annotations", ANNOTATIONS, true, new HashSet<URL>());
loaded = load(new CheckForAnnotation(cl), cl, "annotations", ANNOTATIONS, true, loaded);
loaded = load(new CheckForConcept(first), first, "concepts", CONCEPTS, true, new HashSet<URL>());
loaded = load(new CheckForConcept(cl), cl, "concepts", CONCEPTS, true, loaded);
loaded = load(new CheckForBehaviour(first), first, "behaviours", BEHAVIOURS, false, new HashSet<URL>());
loaded = load(new CheckForBehaviour(cl), cl, "behaviours", BEHAVIOURS, false, loaded);
scanConceptsWithReflections();
Collection<Class<?>> concepts = roleMapper.getConceptClasses();
for(Class<?> conceptClass : concepts) {
logger.debug("Registered concept class " + conceptClass.getCanonicalName());
}
} catch (ObjectStoreConfigException e) {
throw e;
} catch (Exception e) {
throw new ObjectStoreConfigException(e);
}
}
public void scan(URL jar, ClassLoader cl) throws ObjectStoreConfigException {
scan(jar, new CheckForAnnotation(cl), ANNOTATIONS, cl);
scan(jar, new CheckForConcept(cl), CONCEPTS, cl);
scan(jar, new CheckForBehaviour(cl), BEHAVIOURS, cl);
}
private void scan(URL url, CheckForConcept checker, String role, ClassLoader cl)
throws ObjectStoreConfigException {
try {
Scanner scanner = new Scanner(checker);
load(scanner.scan(url, checker.getName(), role), cl, false);
} catch (Exception e) {
throw new ObjectStoreConfigException(e);
}
}
private void scanConceptsWithReflections() throws ObjectStoreConfigException {
logger.debug("Search for concepts with reflections");
Set<URL> classpath = new HashSet<>();
classpath.addAll(ClasspathHelper.forClassLoader());
classpath.addAll(ClasspathHelper.forJavaClassPath());
classpath.addAll(ClasspathHelper.forManifest());
classpath.addAll(ClasspathHelper.forPackage(""));
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setUrls(classpath)
.useParallelExecutor()
.filterInputsBy(FilterBuilder.parsePackages("-java, -javax, -sun, -com.sun"))
.setScanners(new SubTypesScanner(), new TypeAnnotationsScanner()));
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(Iri.class, true);
logger.debug("Search for concepts with reflections resulted in " + annotated.size() + " classes");
for (Class clazz : annotated) {
logger.debug("Found concept class: " + clazz.getCanonicalName());
roleMapper.addConcept(clazz);
}
}
private Set<URL> load(CheckForConcept checker, ClassLoader cl, String forType, String roles, boolean concept, Set<URL> exclude)
throws IOException, ClassNotFoundException, ObjectStoreConfigException {
if (cl == null)
return exclude;
Scanner scanner = new Scanner(checker, roles);
Enumeration<URL> resources = cl.getResources(roles);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
if (!exclude.contains(url)) {
exclude.add(url);
logger.debug("Reading roles from {}", url);
try {
Properties p = new Properties();
p.load(url.openStream());
if (p.isEmpty()) {
// exclude concept scanning because of external reflection scanning
if(!CONCEPTS.equals(roles)) {
load(scanner.scan(url, forType, null), cl, concept);
}
} else {
load(p, cl, concept);
}
} catch (IOException e) {
String msg = e.getMessage() + " in: " + url;
throw new ObjectStoreConfigException(msg, e);
} catch (IllegalArgumentException e) {
String msg = e.getMessage() + " in: " + url;
throw new ObjectStoreConfigException(msg, e);
}
}
}
return exclude;
}
private void load(List<String> roles, ClassLoader cl, boolean concept)
throws IOException, ObjectStoreConfigException {
for (String role : roles) {
try {
Class<?> clazz = forName(role, true, cl);
recordRole(clazz, null, concept);
} catch (ClassNotFoundException exc) {
logger.error(exc.toString());
}
}
}
private void load(Properties p, ClassLoader cl, boolean concept)
throws ClassNotFoundException, IOException, ObjectStoreConfigException {
for (Map.Entry<Object, Object> e : p.entrySet()) {
String role = (String) e.getKey();
String types = (String) e.getValue();
try {
int idx = role.indexOf('#');
if (idx >= 0) {
role = role.substring(0, idx);
}
Class<?> clazz = forName(role, true, cl);
for (String rdf : types.split("\\s+")) {
if (idx < 0) {
recordRole(clazz, rdf, concept);
} else {
String mname = ((String)e.getKey()).substring(idx + 1);
if (mname.endsWith("()")) {
mname = mname.substring(0, mname.length() - 2);
}
if (rdf.length() > 0) {
roleMapper.addAnnotation(clazz.getMethod(mname), new URIImpl(rdf));
} else {
roleMapper.addAnnotation(clazz.getMethod(mname));
}
}
}
} catch (ClassNotFoundException exc) {
logger.error(exc.toString());
} catch (NoSuchMethodException exc) {
logger.error(exc.toString());
}
}
}
private Class<?> forName(String name, boolean init, ClassLoader cl)
throws ClassNotFoundException {
synchronized (cl) {
return Class.forName(name, init, cl);
}
}
private void recordRole(Class<?> clazz, String uri, boolean concept)
throws ObjectStoreConfigException {
if (uri == null || uri.length() == 0) {
if (clazz.isAnnotation()) {
roleMapper.addAnnotation(clazz);
} else if (isAnnotationPresent(clazz) || concept) {
roleMapper.addConcept(clazz);
} else {
roleMapper.addBehaviour(clazz);
}
} else {
if (clazz.isAnnotation()) {
roleMapper.addAnnotation(clazz, new URIImpl(uri));
} else if (isAnnotationPresent(clazz) || concept) {
roleMapper.addConcept(clazz, new URIImpl(uri));
} else {
roleMapper.addBehaviour(clazz, new URIImpl(uri));
}
}
}
private boolean isAnnotationPresent(Class<?> clazz) {
for (Annotation ann : clazz.getAnnotations()) {
String name = ann.annotationType().getName();
if (Iri.class.getName().equals(name))
return true;
if (Matching.class.getName().equals(name))
return true;
}
return false;
}
}