001/* 002 * (C) Copyright 2006-2017 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Anahide Tchertchian 018 * Florent Guillaume 019 * Remi Cattiau 020 */ 021package org.nuxeo.ecm.platform.relations.services; 022 023import java.io.Serializable; 024import java.util.ArrayList; 025import java.util.HashSet; 026import java.util.Hashtable; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030 031import javax.transaction.Transaction; 032 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.nuxeo.ecm.core.api.CoreSession; 036import org.nuxeo.ecm.core.repository.RepositoryService; 037import org.nuxeo.ecm.platform.relations.api.DocumentRelationManager; 038import org.nuxeo.ecm.platform.relations.api.Graph; 039import org.nuxeo.ecm.platform.relations.api.GraphDescription; 040import org.nuxeo.ecm.platform.relations.api.GraphFactory; 041import org.nuxeo.ecm.platform.relations.api.RelationManager; 042import org.nuxeo.ecm.platform.relations.api.Resource; 043import org.nuxeo.ecm.platform.relations.api.ResourceAdapter; 044import org.nuxeo.ecm.platform.relations.descriptors.GraphTypeDescriptor; 045import org.nuxeo.ecm.platform.relations.descriptors.ResourceAdapterDescriptor; 046import org.nuxeo.runtime.api.Framework; 047import org.nuxeo.runtime.model.ComponentContext; 048import org.nuxeo.runtime.model.ComponentInstance; 049import org.nuxeo.runtime.model.ComponentName; 050import org.nuxeo.runtime.model.DefaultComponent; 051import org.nuxeo.runtime.transaction.TransactionHelper; 052 053/** 054 * Relation service. 055 * <p> 056 * It handles a registry of graph instances through extension points. 057 */ 058public class RelationService extends DefaultComponent implements RelationManager { 059 060 public static final ComponentName NAME = new ComponentName( 061 "org.nuxeo.ecm.platform.relations.services.RelationService"); 062 063 private static final long serialVersionUID = -4778456059717447736L; 064 065 private static final Log log = LogFactory.getLog(RelationService.class); 066 067 /** Graph type -> class. */ 068 protected final Map<String, Class<?>> graphTypes; 069 070 /** Graph name -> description */ 071 protected final Map<String, GraphDescription> graphDescriptions; 072 073 /** Graph name -> factory. */ 074 public final Map<String, GraphFactory> graphFactories; 075 076 /** Graph name -> graph instance. */ 077 public final Map<String, Graph> graphRegistry; 078 079 protected final Map<String, String> resourceAdapterRegistry; 080 081 public RelationService() { 082 // Hashtable to get implicit synchronization 083 graphTypes = new Hashtable<>(); 084 graphDescriptions = new Hashtable<>(); 085 graphRegistry = new Hashtable<>(); 086 graphFactories = new Hashtable<>(); 087 resourceAdapterRegistry = new Hashtable<>(); 088 } 089 090 @Override 091 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 092 if (extensionPoint.equals("graphtypes")) { 093 registerGraphType(contribution); 094 } else if (extensionPoint.equals("graphs")) { 095 registerGraph(contribution); 096 } else if (extensionPoint.equals("resourceadapters")) { 097 registerResourceAdapter(contribution); 098 } else { 099 log.error(String.format("Unknown extension point %s, can't register !", extensionPoint)); 100 } 101 } 102 103 @Override 104 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 105 if (extensionPoint.equals("graphtypes")) { 106 unregisterGraphType(contribution); 107 } else if (extensionPoint.equals("graphs")) { 108 unregisterGraph(contribution); 109 } else if (extensionPoint.equals("resourceadapters")) { 110 unregisterResourceAdapter(contribution); 111 } else { 112 log.error(String.format("Unknown extension point %s, can't unregister !", extensionPoint)); 113 } 114 } 115 116 @Override 117 @SuppressWarnings("unchecked") 118 public <T> T getAdapter(Class<T> adapter) { 119 if (adapter.isAssignableFrom(RelationManager.class)) { 120 return (T) this; 121 } else if (adapter.isAssignableFrom(DocumentRelationManager.class)) { 122 return (T) new DocumentRelationService(); 123 } 124 return null; 125 } 126 127 // Graph types 128 129 /** 130 * Registers a graph type, saving a name and a class name. 131 * <p> 132 * The name will be used when registering graphs. 133 */ 134 private void registerGraphType(Object contribution) { 135 GraphTypeDescriptor graphTypeDescriptor = (GraphTypeDescriptor) contribution; 136 String graphType = graphTypeDescriptor.getName(); 137 String className = graphTypeDescriptor.getClassName(); 138 139 if (graphTypes.containsKey(graphType)) { 140 log.error(String.format("Graph type %s already registered using %s", graphType, graphTypes.get(graphType))); 141 return; 142 } 143 Class<?> klass; 144 try { 145 klass = getClass().getClassLoader().loadClass(className); 146 } catch (ClassNotFoundException e) { 147 throw new RuntimeException( 148 String.format("Cannot register unknown class for graph type %s: %s", graphType, className), e); 149 } 150 if (!Graph.class.isAssignableFrom(klass) && !GraphFactory.class.isAssignableFrom(klass)) { 151 throw new RuntimeException("Invalid graph class/factory type: " + className); 152 } 153 graphTypes.put(graphType, klass); 154 log.info(String.format("Registered graph type: %s (%s)", graphType, className)); 155 } 156 157 /** 158 * Unregisters a graph type. 159 */ 160 private void unregisterGraphType(Object contrib) { 161 GraphTypeDescriptor graphTypeExtension = (GraphTypeDescriptor) contrib; 162 String graphType = graphTypeExtension.getName(); 163 List<GraphDescription> list = new ArrayList<>(graphDescriptions.values()); // copy 164 for (GraphDescription graphDescription : list) { 165 if (graphType.equals(graphDescription.getGraphType())) { 166 String name = graphDescription.getName(); 167 graphFactories.remove(name); 168 graphRegistry.remove(name); 169 graphDescriptions.remove(name); 170 log.info("Unregistered graph: " + name); 171 } 172 } 173 graphTypes.remove(graphType); 174 log.info("Unregistered graph type: " + graphType); 175 } 176 177 public List<String> getGraphTypes() { 178 return new ArrayList<>(graphTypes.keySet()); 179 } 180 181 /** 182 * Registers a graph instance. 183 * <p> 184 * The graph has to be declared as using a type already registered in the graph type registry. 185 */ 186 protected void registerGraph(Object contribution) { 187 GraphDescription graphDescription = (GraphDescription) contribution; 188 String name = graphDescription.getName(); 189 if (graphDescriptions.containsKey(name)) { 190 log.info(String.format("Overriding graph %s definition", name)); 191 graphDescriptions.remove(name); 192 } 193 graphDescriptions.put(name, graphDescription); 194 log.info("Registered graph: " + name); 195 196 // remove any existing graph instance in case its definition changed 197 graphRegistry.remove(name); 198 } 199 200 /** 201 * Unregisters a graph. 202 */ 203 protected void unregisterGraph(Object contribution) { 204 GraphDescription graphDescription = (GraphDescription) contribution; 205 String name = graphDescription.getName(); 206 if (graphDescriptions.containsKey(name)) { 207 graphFactories.remove(name); 208 graphRegistry.remove(name); 209 graphDescriptions.remove(name); 210 log.info("Unregistered graph: " + name); 211 } 212 } 213 214 // Resource adapters 215 216 private void registerResourceAdapter(Object contribution) { 217 ResourceAdapterDescriptor adapter = (ResourceAdapterDescriptor) contribution; 218 String ns = adapter.getNamespace(); 219 String adapterClassName = adapter.getClassName(); 220 if (resourceAdapterRegistry.containsKey(ns)) { 221 log.info("Overriding resource adapter config for namespace " + ns); 222 } 223 resourceAdapterRegistry.put(ns, adapterClassName); 224 log.info(String.format("%s namespace registered using adapter %s", ns, adapterClassName)); 225 } 226 227 private void unregisterResourceAdapter(Object contribution) { 228 ResourceAdapterDescriptor adapter = (ResourceAdapterDescriptor) contribution; 229 String ns = adapter.getNamespace(); 230 String adapterClassName = adapter.getClassName(); 231 String registered = resourceAdapterRegistry.get(ns); 232 if (registered == null) { 233 log.error(String.format("Namespace %s not found", ns)); 234 } else if (!registered.equals(adapterClassName)) { 235 log.error(String.format("Namespace %s: wrong class %s", ns, registered)); 236 } else { 237 resourceAdapterRegistry.remove(ns); 238 log.info(String.format("%s unregistered, was using %s", ns, adapterClassName)); 239 } 240 } 241 242 private ResourceAdapter getResourceAdapterForNamespace(String namespace) { 243 String adapterClassName = resourceAdapterRegistry.get(namespace); 244 if (adapterClassName == null) { 245 log.error(String.format("Cannot find adapter for namespace: %s", namespace)); 246 return null; 247 } else { 248 try { 249 // Thread context loader is not working in isolated EARs 250 ResourceAdapter adapter = (ResourceAdapter) RelationService.class.getClassLoader() 251 .loadClass(adapterClassName) 252 .newInstance(); 253 adapter.setNamespace(namespace); 254 return adapter; 255 } catch (ReflectiveOperationException e) { 256 String msg = String.format("Cannot instantiate generator with namespace '%s': %s", namespace, e); 257 log.error(msg); 258 return null; 259 } 260 } 261 } 262 263 // RelationManager interface 264 265 @Override 266 public Graph getGraphByName(String name) { 267 return getGraph(name, null); 268 } 269 270 @Override 271 public Graph getGraph(String name, CoreSession session) { 272 GraphDescription graphDescription = graphDescriptions.get(name); 273 if (graphDescription == null) { 274 throw new RuntimeException("No such graph: " + name); 275 } 276 277 Graph graph = getGraphFromRegistries(graphDescription, session); 278 if (graph != null) { 279 return graph; 280 } 281 282 // check what we have for the graph type 283 Class<?> klass = graphTypes.get(graphDescription.getGraphType()); 284 if (Graph.class.isAssignableFrom(klass)) { 285 // instance 286 try { 287 graph = (Graph) klass.newInstance(); 288 } catch (ReflectiveOperationException e) { 289 throw new RuntimeException(e); 290 } 291 graphRegistry.put(name, graph); 292 } else { // GraphFactory.class.isAssignableFrom(klass) 293 // factory 294 GraphFactory factory; 295 try { 296 factory = (GraphFactory) klass.newInstance(); 297 } catch (ReflectiveOperationException e) { 298 throw new RuntimeException(e); 299 } 300 graphFactories.put(name, factory); 301 } 302 303 return getGraphFromRegistries(graphDescription, session); 304 } 305 306 /** Gets the graph from the registries. */ 307 protected Graph getGraphFromRegistries(GraphDescription graphDescription, CoreSession session) { 308 String name = graphDescription.getName(); 309 // check instances 310 Graph graph = graphRegistry.get(name); 311 if (graph != null) { 312 graph.setDescription(graphDescription); 313 return graph; 314 } 315 316 // check factories 317 GraphFactory factory = graphFactories.get(name); 318 if (factory != null) { 319 return factory.createGraph(graphDescription, session); 320 } 321 322 return null; 323 } 324 325 protected Graph newGraph(String className) { 326 try { 327 Class<?> klass = getClass().getClassLoader().loadClass(className); 328 return (Graph) klass.newInstance(); 329 } catch (ReflectiveOperationException e) { 330 throw new RuntimeException(e); 331 } 332 } 333 334 @Override 335 public Graph getTransientGraph(String type) { 336 Class<?> klass = graphTypes.get(type); 337 if (Graph.class.isAssignableFrom(klass)) { 338 try { 339 return (Graph) klass.newInstance(); 340 } catch (ReflectiveOperationException e) { 341 throw new RuntimeException(e); 342 } 343 } 344 throw new RuntimeException("Graph type cannot be transient: " + type); 345 } 346 347 @Override 348 public Resource getResource(String namespace, Serializable object, Map<String, Object> context) { 349 ResourceAdapter adapter = getResourceAdapterForNamespace(namespace); 350 if (adapter == null) { 351 log.error("Cannot find adapter for namespace: " + namespace); 352 return null; 353 } else { 354 return adapter.getResource(object, context); 355 } 356 } 357 358 @Override 359 public Set<Resource> getAllResources(Serializable object, Map<String, Object> context) { 360 // TODO OPTIM implement reverse map in registerContribution 361 Set<Resource> res = new HashSet<>(); 362 for (String ns : resourceAdapterRegistry.keySet()) { 363 ResourceAdapter adapter = getResourceAdapterForNamespace(ns); 364 if (adapter == null) { 365 continue; 366 } 367 Class<?> klass = adapter.getKlass(); 368 if (klass == null) { 369 continue; 370 } 371 if (klass.isAssignableFrom(object.getClass())) { 372 res.add(adapter.getResource(object, context)); 373 } 374 } 375 return res; 376 } 377 378 @Override 379 public Serializable getResourceRepresentation(String namespace, Resource resource, Map<String, Object> context) { 380 ResourceAdapter adapter = getResourceAdapterForNamespace(namespace); 381 if (adapter == null) { 382 log.error("Cannot find adapter for namespace: " + namespace); 383 return null; 384 } else { 385 return adapter.getResourceRepresentation(resource, context); 386 } 387 } 388 389 @Override 390 public List<String> getGraphNames() { 391 return new ArrayList<>(graphDescriptions.keySet()); 392 } 393 394 @Override 395 public void start(ComponentContext context) { 396 RepositoryService repositoryService = Framework.getService(RepositoryService.class); 397 if (repositoryService == null) { 398 // RepositoryService failed to start, no need to go further 399 return; 400 } 401 Transaction tx = TransactionHelper.suspendTransaction(); 402 try { 403 log.info("Relation Service initialization"); 404 405 // init jena Graph outside of Tx 406 for (String graphName : graphDescriptions.keySet()) { 407 GraphDescription desc = graphDescriptions.get(graphName); 408 if (desc.getGraphType().equalsIgnoreCase("jena")) { 409 log.info("create RDF Graph " + graphName); 410 Graph graph = getGraphByName(graphName); 411 graph.size(); 412 } 413 } 414 } finally { 415 TransactionHelper.resumeTransaction(tx); 416 } 417 418 // init non jena Graph inside a Tx 419 if (tx == null) { 420 TransactionHelper.startTransaction(); 421 } 422 boolean txErrors = true; 423 try { 424 for (String graphName : graphDescriptions.keySet()) { 425 GraphDescription desc = graphDescriptions.get(graphName); 426 if (!desc.getGraphType().equalsIgnoreCase("jena")) { 427 log.info("create RDF Graph " + graphName); 428 Graph graph = getGraphByName(graphName); 429 graph.size(); 430 } 431 } 432 txErrors = false; 433 } finally { 434 if (txErrors) { 435 TransactionHelper.setTransactionRollbackOnly(); 436 } 437 if (tx == null) { 438 TransactionHelper.commitOrRollbackTransaction(); 439 } 440 } 441 } 442 443}