/*
* Copyright (c) 2007-2009, James Leigh All 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.config;
import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap;
import static org.openrdf.repository.object.config.ObjectRepositorySchema.BEHAVIOUR;
import static org.openrdf.repository.object.config.ObjectRepositorySchema.BEHAVIOUR_JAR;
import static org.openrdf.repository.object.config.ObjectRepositorySchema.BLOB_STORE;
import static org.openrdf.repository.object.config.ObjectRepositorySchema.BLOB_STORE_PARAMETER;
import static org.openrdf.repository.object.config.ObjectRepositorySchema.CONCEPT;
import static org.openrdf.repository.object.config.ObjectRepositorySchema.CONCEPT_JAR;
import static org.openrdf.repository.object.config.ObjectRepositorySchema.DATATYPE;
import static org.openrdf.repository.object.config.ObjectRepositorySchema.KNOWN_AS;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.openrdf.model.Graph;
import org.openrdf.model.Model;
import org.openrdf.model.Resource;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.impl.GraphImpl;
import org.openrdf.model.impl.LinkedHashModel;
import org.openrdf.model.impl.ValueFactoryImpl;
import org.openrdf.model.util.ModelException;
import org.openrdf.repository.config.RepositoryConfigException;
import org.openrdf.repository.contextaware.config.ContextAwareConfig;
import org.openrdf.repository.object.ObjectRepository;
import org.openrdf.repository.object.exceptions.ObjectStoreConfigException;
/**
* Defines the Scope of an {@link ObjectRepository} and its factory. This
* includes roles, literals, factories, datasets, and contexts.
*
* @author James Leigh
*
*/
public class ObjectRepositoryConfig extends ContextAwareConfig implements
Cloneable {
private static final String JAVA_NS = "java:";
private ValueFactory vf = ValueFactoryImpl.getInstance();
private ClassLoader cl;
private Map<Class<?>, List<URI>> datatypes = new HashMap<Class<?>, List<URI>>();
private Map<Method, List<URI>> annotations = new HashMap<Method, List<URI>>();
private Map<Class<?>, List<URI>> concepts = new HashMap<Class<?>, List<URI>>();
private Map<Class<?>, List<URI>> behaviours = new HashMap<Class<?>, List<URI>>();
private List<URL> conceptJars = new ArrayList<URL>();
private List<URL> behaviourJars = new ArrayList<URL>();
private Value blobStore;
private Set<Value> blobStoreParameters = new HashSet<Value>();
public ObjectRepositoryConfig() {
super();
cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
cl = getClass().getClassLoader();
}
}
public ObjectRepositoryConfig(ClassLoader cl) {
this.cl = cl;
}
public void setClassLoader(ClassLoader cl) {
this.cl = cl;
}
public ClassLoader getClassLoader() {
return cl;
}
public Map<Class<?>, List<URI>> getDatatypes() {
return unmodifiableMap(datatypes);
}
/**
* Associates this class with the given datatype.
*
* @param type
* serializable class
* @param datatype
* URI
* @throws ObjectStoreConfigException
*/
public void addDatatype(Class<?> type, URI datatype)
throws ObjectStoreConfigException {
List<URI> list = datatypes.get(type);
if (list == null && datatypes.containsKey(type))
throw new ObjectStoreConfigException(type.getSimpleName()
+ " can only be added once");
if (list == null) {
datatypes.put(type, list = new LinkedList<URI>());
}
list.add(datatype);
}
/**
* Associates this class with the given datatype.
*
* @param type
* serializable class
* @param datatype
* uri
* @throws ObjectStoreConfigException
*/
public void addDatatype(Class<?> type, String datatype)
throws ObjectStoreConfigException {
addDatatype(type, vf.createURI(datatype));
}
public Map<Method, List<URI>> getAnnotations() {
return unmodifiableMap(annotations);
}
/**
* Associates this annotation with its annotated type.
*
* @param ann
* @throws ObjectStoreConfigException
*/
public void addAnnotation(Class<?> ann) throws ObjectStoreConfigException {
if (ann.getDeclaredMethods().length != 1)
throw new ObjectStoreConfigException(
"Annotation class must have exactly one method: " + ann);
addAnnotation(ann.getDeclaredMethods()[0]);
}
/**
* Associates this annotation with the given type.
*
* @param ann
* @param type
* @throws ObjectStoreConfigException
*/
public void addAnnotation(Class<?> ann, URI type)
throws ObjectStoreConfigException {
if (ann.getDeclaredMethods().length != 1)
throw new ObjectStoreConfigException(
"Annotation class must have exactly one method: " + ann);
addAnnotation(ann.getDeclaredMethods()[0], type);
}
/**
* Associates this annotation with the given type.
*
* @param ann
* @param type
* @throws ObjectStoreConfigException
*/
public void addAnnotation(Class<?> ann, String type)
throws ObjectStoreConfigException {
if (ann.getDeclaredMethods().length != 1)
throw new ObjectStoreConfigException(
"Annotation class must have exactly one method: " + ann);
addAnnotation(ann.getDeclaredMethods()[0], type);
}
/**
* Associates this annotation with its annotated type.
*
* @param ann
* @throws ObjectStoreConfigException
*/
public void addAnnotation(Method ann) throws ObjectStoreConfigException {
if (annotations.containsKey(ann))
throw new ObjectStoreConfigException(ann.toString()
+ " can only be added once");
annotations.put(ann, null);
}
/**
* Associates this annotation with the given type.
*
* @param ann
* @param type
* @throws ObjectStoreConfigException
*/
public void addAnnotation(Method ann, URI type)
throws ObjectStoreConfigException {
List<URI> list = annotations.get(ann);
if (list == null && annotations.containsKey(ann))
throw new ObjectStoreConfigException(ann.toString()
+ " can only be added once");
if (list == null) {
annotations.put(ann, list = new LinkedList<URI>());
}
list.add(type);
}
/**
* Associates this annotation with the given type.
*
* @param ann
* @param type
* @throws ObjectStoreConfigException
*/
public void addAnnotation(Method ann, String type)
throws ObjectStoreConfigException {
addAnnotation(ann, vf.createURI(type));
}
public Map<Class<?>, List<URI>> getConcepts() {
return unmodifiableMap(concepts);
}
/**
* Associates this concept with its annotated type.
*
* @param concept
* interface or class
* @throws ObjectStoreConfigException
*/
public void addConcept(Class<?> concept) throws ObjectStoreConfigException {
if (concepts.containsKey(concept))
throw new ObjectStoreConfigException(concept.getSimpleName()
+ " can only be added once");
concepts.put(concept, null);
}
/**
* Associates this concept with the given type.
*
* @param concept
* interface or class
* @param type
* URI
* @throws ObjectStoreConfigException
*/
public void addConcept(Class<?> concept, URI type)
throws ObjectStoreConfigException {
List<URI> list = concepts.get(concept);
if (list == null && concepts.containsKey(concept))
throw new ObjectStoreConfigException(concept.getSimpleName()
+ " can only be added once");
if (list == null) {
concepts.put(concept, list = new LinkedList<URI>());
}
list.add(type);
}
/**
* Associates this concept with the given type.
*
* @param concept
* interface or class
* @param type
* uri
* @throws ObjectStoreConfigException
*/
public void addConcept(Class<?> concept, String type)
throws ObjectStoreConfigException {
addConcept(concept, vf.createURI(type));
}
public Map<Class<?>, List<URI>> getBehaviours() {
return unmodifiableMap(behaviours);
}
/**
* Associates this behaviour with its implemented type.
*
* @param behaviour
* class
* @throws ObjectStoreConfigException
*/
public void addBehaviour(Class<?> behaviour)
throws ObjectStoreConfigException {
if (behaviours.containsKey(behaviour))
throw new ObjectStoreConfigException(behaviour.getSimpleName()
+ " can only be added once");
try {
behaviour.getConstructor();
} catch (NoSuchMethodException e) {
throw new ObjectStoreConfigException(behaviour.getSimpleName()
+ " must have a default constructor");
}
behaviours.put(behaviour, null);
}
/**
* Associates this behaviour with the given type.
*
* @param behaviour
* class
* @param type
* URI
* @throws ObjectStoreConfigException
*/
public void addBehaviour(Class<?> behaviour, URI type)
throws ObjectStoreConfigException {
List<URI> list = behaviours.get(behaviour);
if (list == null && behaviours.containsKey(behaviour))
throw new ObjectStoreConfigException(behaviour.getSimpleName()
+ " can only be added once");
if (list == null) {
behaviours.put(behaviour, list = new LinkedList<URI>());
}
list.add(type);
}
/**
* Associates this behaviour with the given type.
*
* @param behaviour
* class
* @param type
* uri
* @throws ObjectStoreConfigException
*/
public void addBehaviour(Class<?> behaviour, String type)
throws ObjectStoreConfigException {
addBehaviour(behaviour, vf.createURI(type));
}
public List<URL> getConceptJars() {
return unmodifiableList(conceptJars);
}
public void addConceptJar(URL jarFile) {
conceptJars.add(jarFile);
}
public List<URL> getBehaviourJars() {
return unmodifiableList(behaviourJars);
}
public void addBehaviourJar(URL jarFile) {
behaviourJars.add(jarFile);
}
public String getBlobStore() {
if (blobStore == null)
return null;
return blobStore.stringValue();
}
public void setBlobStore(String blobStore) {
this.blobStore = vf.createLiteral(blobStore);
}
public Map<String, String> getBlobStoreParameters() {
Map<String, String> result = new HashMap<String, String>();
for (Value v : blobStoreParameters) {
String[] split = v.stringValue().split(":", 2);
result.put(split[0], split[1]);
}
return result;
}
public void setBlobStoreParameters(Map<String, String> blobStoreParameters) {
this.blobStoreParameters.clear();
for (Map.Entry<String, String> e : blobStoreParameters.entrySet()) {
String label = e.getKey() + ":" + e.getValue();
this.blobStoreParameters.add(vf.createLiteral(label));
}
}
public ObjectRepositoryConfig clone() {
try {
Object o = super.clone();
ObjectRepositoryConfig clone = (ObjectRepositoryConfig) o;
clone.setReadContexts(copy(clone.getReadContexts()));
clone.setAddContexts(copy(clone.getAddContexts()));
clone.setRemoveContexts(copy(clone.getRemoveContexts()));
clone.setArchiveContexts(copy(clone.getArchiveContexts()));
clone.datatypes = copy(datatypes);
clone.concepts = copy(concepts);
clone.behaviours = copy(behaviours);
clone.conceptJars = new ArrayList<URL>(conceptJars);
clone.behaviourJars = new ArrayList<URL>(behaviourJars);
clone.blobStoreParameters = new HashSet<Value>(blobStoreParameters);
Graph model = new GraphImpl();
Resource subj = clone.export(model);
clone.parse(model, subj);
return clone;
} catch (RepositoryConfigException e) {
throw new AssertionError(e);
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
@Override
public Resource export(Graph model) {
ValueFactory vf = ValueFactoryImpl.getInstance();
Resource subj = super.export(model);
exportAssocation(subj, datatypes, DATATYPE, model);
exportAssocation(subj, concepts, CONCEPT, model);
exportAssocation(subj, behaviours, BEHAVIOUR, model);
for (URL jar : conceptJars) {
model.add(subj, CONCEPT_JAR, vf.createURI(jar.toExternalForm()));
}
for (URL jar : behaviourJars) {
model.add(subj, BEHAVIOUR_JAR, vf.createURI(jar.toExternalForm()));
}
if (blobStore != null) {
model.add(subj, BLOB_STORE, blobStore);
}
for (Value v : blobStoreParameters) {
model.add(subj, BLOB_STORE_PARAMETER, v);
}
return subj;
}
@Override
public void parse(Graph graph, Resource subj)
throws RepositoryConfigException {
super.parse(graph, subj);
try {
Model model = new LinkedHashModel(graph);
parseAssocation(subj, datatypes, DATATYPE, model);
parseAssocation(subj, concepts, CONCEPT, model);
parseAssocation(subj, behaviours, BEHAVIOUR, model);
conceptJars.clear();
for (Value obj : model.filter(subj, CONCEPT_JAR, null).objects()) {
conceptJars.add(new URL(obj.stringValue()));
}
behaviourJars.clear();
for (Value obj : model.filter(subj, BEHAVIOUR_JAR, null).objects()) {
behaviourJars.add(new URL(obj.stringValue()));
}
blobStore = model.filter(subj, BLOB_STORE, null).objectValue();
blobStoreParameters.clear();
blobStoreParameters.addAll(model.filter(subj, BLOB_STORE_PARAMETER, null).objects());
} catch (MalformedURLException e) {
throw new ObjectStoreConfigException(e);
} catch (ModelException e) {
throw new ObjectStoreConfigException(e);
}
}
private Map<Class<?>, List<URI>> copy(Map<Class<?>, List<URI>> map) {
Map<Class<?>, List<URI>> result = new HashMap<Class<?>, List<URI>>();
for (Map.Entry<Class<?>, List<URI>> e : map.entrySet()) {
if (e.getValue() == null) {
result.put(e.getKey(), null);
} else {
result.put(e.getKey(), new LinkedList<URI>(e.getValue()));
}
}
return result;
}
private URI[] copy(URI[] ar) {
URI[] result = new URI[ar.length];
System.arraycopy(ar, 0, result, 0, ar.length);
return result;
}
private void exportAssocation(Resource subj, Map<Class<?>, List<URI>> assocation,
URI relation, Graph model) {
ValueFactory vf = ValueFactoryImpl.getInstance();
for (Map.Entry<Class<?>, List<URI>> e : assocation.entrySet()) {
URI name = vf.createURI(JAVA_NS, e.getKey().getName());
model.add(subj, relation, name);
if (e.getValue() != null) {
for (URI value : e.getValue()) {
model.add(name, KNOWN_AS, value);
}
}
}
}
private void parseAssocation(Resource subj, Map<Class<?>, List<URI>> assocation,
URI relation, Model model) throws ObjectStoreConfigException {
assocation.clear();
for (Value obj : model.filter(subj, relation, null).objects()) {
Class<?> role = loadClass(obj);
Set<Value> objects = model.filter((Resource) obj, KNOWN_AS, null)
.objects();
if (objects.isEmpty()) {
assocation.put(role, null);
}
for (Value uri : objects) {
List<URI> list = assocation.get(role);
if (list == null) {
assocation.put(role, list = new LinkedList<URI>());
}
list.add((URI) uri);
}
}
}
private Class<?> loadClass(Value base) throws ObjectStoreConfigException {
if (base instanceof URI) {
URI uri = (URI) base;
if (JAVA_NS.equals(uri.getNamespace())) {
String name = uri.getLocalName();
try {
synchronized (cl) {
return Class.forName(name, true, cl);
}
} catch (ClassNotFoundException e) {
throw new ObjectStoreConfigException(e);
}
}
}
throw new ObjectStoreConfigException("Invalid java URI: " + base);
}
}