/*
* (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Anahide Tchertchian
* Florent Guillaume
* Remi Cattiau
*/
package org.nuxeo.ecm.platform.relations.services;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.transaction.Transaction;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.repository.RepositoryService;
import org.nuxeo.ecm.platform.relations.api.DocumentRelationManager;
import org.nuxeo.ecm.platform.relations.api.Graph;
import org.nuxeo.ecm.platform.relations.api.GraphDescription;
import org.nuxeo.ecm.platform.relations.api.GraphFactory;
import org.nuxeo.ecm.platform.relations.api.RelationManager;
import org.nuxeo.ecm.platform.relations.api.Resource;
import org.nuxeo.ecm.platform.relations.api.ResourceAdapter;
import org.nuxeo.ecm.platform.relations.descriptors.GraphTypeDescriptor;
import org.nuxeo.ecm.platform.relations.descriptors.ResourceAdapterDescriptor;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.model.ComponentContext;
import org.nuxeo.runtime.model.ComponentInstance;
import org.nuxeo.runtime.model.ComponentName;
import org.nuxeo.runtime.model.DefaultComponent;
import org.nuxeo.runtime.transaction.TransactionHelper;
/**
* Relation service.
* <p>
* It handles a registry of graph instances through extension points.
*/
public class RelationService extends DefaultComponent implements RelationManager {
public static final ComponentName NAME = new ComponentName(
"org.nuxeo.ecm.platform.relations.services.RelationService");
private static final long serialVersionUID = -4778456059717447736L;
private static final Log log = LogFactory.getLog(RelationService.class);
/** Graph type -> class. */
protected final Map<String, Class<?>> graphTypes;
/** Graph name -> description */
protected final Map<String, GraphDescription> graphDescriptions;
/** Graph name -> factory. */
public final Map<String, GraphFactory> graphFactories;
/** Graph name -> graph instance. */
public final Map<String, Graph> graphRegistry;
protected final Map<String, String> resourceAdapterRegistry;
public RelationService() {
// Hashtable to get implicit synchronization
graphTypes = new Hashtable<>();
graphDescriptions = new Hashtable<>();
graphRegistry = new Hashtable<>();
graphFactories = new Hashtable<>();
resourceAdapterRegistry = new Hashtable<>();
}
@Override
public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
if (extensionPoint.equals("graphtypes")) {
registerGraphType(contribution);
} else if (extensionPoint.equals("graphs")) {
registerGraph(contribution);
} else if (extensionPoint.equals("resourceadapters")) {
registerResourceAdapter(contribution);
} else {
log.error(String.format("Unknown extension point %s, can't register !", extensionPoint));
}
}
@Override
public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
if (extensionPoint.equals("graphtypes")) {
unregisterGraphType(contribution);
} else if (extensionPoint.equals("graphs")) {
unregisterGraph(contribution);
} else if (extensionPoint.equals("resourceadapters")) {
unregisterResourceAdapter(contribution);
} else {
log.error(String.format("Unknown extension point %s, can't unregister !", extensionPoint));
}
}
@Override
@SuppressWarnings("unchecked")
public <T> T getAdapter(Class<T> adapter) {
if (adapter.isAssignableFrom(RelationManager.class)) {
return (T) this;
} else if (adapter.isAssignableFrom(DocumentRelationManager.class)) {
return (T) new DocumentRelationService();
}
return null;
}
// Graph types
/**
* Registers a graph type, saving a name and a class name.
* <p>
* The name will be used when registering graphs.
*/
private void registerGraphType(Object contribution) {
GraphTypeDescriptor graphTypeDescriptor = (GraphTypeDescriptor) contribution;
String graphType = graphTypeDescriptor.getName();
String className = graphTypeDescriptor.getClassName();
if (graphTypes.containsKey(graphType)) {
log.error(String.format("Graph type %s already registered using %s", graphType, graphTypes.get(graphType)));
return;
}
Class<?> klass;
try {
klass = getClass().getClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException(
String.format("Cannot register unknown class for graph type %s: %s", graphType, className), e);
}
if (!Graph.class.isAssignableFrom(klass) && !GraphFactory.class.isAssignableFrom(klass)) {
throw new RuntimeException("Invalid graph class/factory type: " + className);
}
graphTypes.put(graphType, klass);
log.info(String.format("Registered graph type: %s (%s)", graphType, className));
}
/**
* Unregisters a graph type.
*/
private void unregisterGraphType(Object contrib) {
GraphTypeDescriptor graphTypeExtension = (GraphTypeDescriptor) contrib;
String graphType = graphTypeExtension.getName();
List<GraphDescription> list = new ArrayList<>(graphDescriptions.values()); // copy
for (GraphDescription graphDescription : list) {
if (graphType.equals(graphDescription.getGraphType())) {
String name = graphDescription.getName();
graphFactories.remove(name);
graphRegistry.remove(name);
graphDescriptions.remove(name);
log.info("Unregistered graph: " + name);
}
}
graphTypes.remove(graphType);
log.info("Unregistered graph type: " + graphType);
}
public List<String> getGraphTypes() {
return new ArrayList<>(graphTypes.keySet());
}
/**
* Registers a graph instance.
* <p>
* The graph has to be declared as using a type already registered in the graph type registry.
*/
protected void registerGraph(Object contribution) {
GraphDescription graphDescription = (GraphDescription) contribution;
String name = graphDescription.getName();
if (graphDescriptions.containsKey(name)) {
log.info(String.format("Overriding graph %s definition", name));
graphDescriptions.remove(name);
}
graphDescriptions.put(name, graphDescription);
log.info("Registered graph: " + name);
// remove any existing graph instance in case its definition changed
graphRegistry.remove(name);
}
/**
* Unregisters a graph.
*/
protected void unregisterGraph(Object contribution) {
GraphDescription graphDescription = (GraphDescription) contribution;
String name = graphDescription.getName();
if (graphDescriptions.containsKey(name)) {
graphFactories.remove(name);
graphRegistry.remove(name);
graphDescriptions.remove(name);
log.info("Unregistered graph: " + name);
}
}
// Resource adapters
private void registerResourceAdapter(Object contribution) {
ResourceAdapterDescriptor adapter = (ResourceAdapterDescriptor) contribution;
String ns = adapter.getNamespace();
String adapterClassName = adapter.getClassName();
if (resourceAdapterRegistry.containsKey(ns)) {
log.info("Overriding resource adapter config for namespace " + ns);
}
resourceAdapterRegistry.put(ns, adapterClassName);
log.info(String.format("%s namespace registered using adapter %s", ns, adapterClassName));
}
private void unregisterResourceAdapter(Object contribution) {
ResourceAdapterDescriptor adapter = (ResourceAdapterDescriptor) contribution;
String ns = adapter.getNamespace();
String adapterClassName = adapter.getClassName();
String registered = resourceAdapterRegistry.get(ns);
if (registered == null) {
log.error(String.format("Namespace %s not found", ns));
} else if (!registered.equals(adapterClassName)) {
log.error(String.format("Namespace %s: wrong class %s", ns, registered));
} else {
resourceAdapterRegistry.remove(ns);
log.info(String.format("%s unregistered, was using %s", ns, adapterClassName));
}
}
private ResourceAdapter getResourceAdapterForNamespace(String namespace) {
String adapterClassName = resourceAdapterRegistry.get(namespace);
if (adapterClassName == null) {
log.error(String.format("Cannot find adapter for namespace: %s", namespace));
return null;
} else {
try {
// Thread context loader is not working in isolated EARs
ResourceAdapter adapter = (ResourceAdapter) RelationService.class.getClassLoader()
.loadClass(adapterClassName)
.newInstance();
adapter.setNamespace(namespace);
return adapter;
} catch (ReflectiveOperationException e) {
String msg = String.format("Cannot instantiate generator with namespace '%s': %s", namespace, e);
log.error(msg);
return null;
}
}
}
// RelationManager interface
@Override
public Graph getGraphByName(String name) {
return getGraph(name, null);
}
@Override
public Graph getGraph(String name, CoreSession session) {
GraphDescription graphDescription = graphDescriptions.get(name);
if (graphDescription == null) {
throw new RuntimeException("No such graph: " + name);
}
Graph graph = getGraphFromRegistries(graphDescription, session);
if (graph != null) {
return graph;
}
// check what we have for the graph type
Class<?> klass = graphTypes.get(graphDescription.getGraphType());
if (Graph.class.isAssignableFrom(klass)) {
// instance
try {
graph = (Graph) klass.newInstance();
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
graphRegistry.put(name, graph);
} else { // GraphFactory.class.isAssignableFrom(klass)
// factory
GraphFactory factory;
try {
factory = (GraphFactory) klass.newInstance();
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
graphFactories.put(name, factory);
}
return getGraphFromRegistries(graphDescription, session);
}
/** Gets the graph from the registries. */
protected Graph getGraphFromRegistries(GraphDescription graphDescription, CoreSession session) {
String name = graphDescription.getName();
// check instances
Graph graph = graphRegistry.get(name);
if (graph != null) {
graph.setDescription(graphDescription);
return graph;
}
// check factories
GraphFactory factory = graphFactories.get(name);
if (factory != null) {
return factory.createGraph(graphDescription, session);
}
return null;
}
protected Graph newGraph(String className) {
try {
Class<?> klass = getClass().getClassLoader().loadClass(className);
return (Graph) klass.newInstance();
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
public Graph getTransientGraph(String type) {
Class<?> klass = graphTypes.get(type);
if (Graph.class.isAssignableFrom(klass)) {
try {
return (Graph) klass.newInstance();
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
throw new RuntimeException("Graph type cannot be transient: " + type);
}
@Override
public Resource getResource(String namespace, Serializable object, Map<String, Object> context) {
ResourceAdapter adapter = getResourceAdapterForNamespace(namespace);
if (adapter == null) {
log.error("Cannot find adapter for namespace: " + namespace);
return null;
} else {
return adapter.getResource(object, context);
}
}
@Override
public Set<Resource> getAllResources(Serializable object, Map<String, Object> context) {
// TODO OPTIM implement reverse map in registerContribution
Set<Resource> res = new HashSet<>();
for (String ns : resourceAdapterRegistry.keySet()) {
ResourceAdapter adapter = getResourceAdapterForNamespace(ns);
if (adapter == null) {
continue;
}
Class<?> klass = adapter.getKlass();
if (klass == null) {
continue;
}
if (klass.isAssignableFrom(object.getClass())) {
res.add(adapter.getResource(object, context));
}
}
return res;
}
@Override
public Serializable getResourceRepresentation(String namespace, Resource resource, Map<String, Object> context) {
ResourceAdapter adapter = getResourceAdapterForNamespace(namespace);
if (adapter == null) {
log.error("Cannot find adapter for namespace: " + namespace);
return null;
} else {
return adapter.getResourceRepresentation(resource, context);
}
}
@Override
public List<String> getGraphNames() {
return new ArrayList<>(graphDescriptions.keySet());
}
@Override
public void applicationStarted(ComponentContext context) {
RepositoryService repositoryService = Framework.getService(RepositoryService.class);
if (repositoryService == null) {
// RepositoryService failed to start, no need to go further
return;
}
Transaction tx = TransactionHelper.suspendTransaction();
try {
log.info("Relation Service initialization");
// init jena Graph outside of Tx
for (String graphName : graphDescriptions.keySet()) {
GraphDescription desc = graphDescriptions.get(graphName);
if (desc.getGraphType().equalsIgnoreCase("jena")) {
log.info("create RDF Graph " + graphName);
Graph graph = getGraphByName(graphName);
graph.size();
}
}
} finally {
TransactionHelper.resumeTransaction(tx);
}
// init non jena Graph inside a Tx
if (tx == null) {
TransactionHelper.startTransaction();
}
boolean txErrors = true;
try {
for (String graphName : graphDescriptions.keySet()) {
GraphDescription desc = graphDescriptions.get(graphName);
if (!desc.getGraphType().equalsIgnoreCase("jena")) {
log.info("create RDF Graph " + graphName);
Graph graph = getGraphByName(graphName);
graph.size();
}
}
txErrors = false;
} finally {
if (txErrors) {
TransactionHelper.setTransactionRollbackOnly();
}
if (tx == null) {
TransactionHelper.commitOrRollbackTransaction();
}
}
}
}