001/* 002 * (C) Copyright 2006-2014 Nuxeo SA (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.InputStream; 024import java.io.OutputStream; 025import java.io.Serializable; 026import java.util.ArrayList; 027import java.util.HashSet; 028import java.util.Hashtable; 029import java.util.List; 030import java.util.Map; 031import java.util.Set; 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.Node; 042import org.nuxeo.ecm.platform.relations.api.QueryResult; 043import org.nuxeo.ecm.platform.relations.api.RelationManager; 044import org.nuxeo.ecm.platform.relations.api.Resource; 045import org.nuxeo.ecm.platform.relations.api.ResourceAdapter; 046import org.nuxeo.ecm.platform.relations.api.Statement; 047import org.nuxeo.ecm.platform.relations.descriptors.GraphTypeDescriptor; 048import org.nuxeo.ecm.platform.relations.descriptors.ResourceAdapterDescriptor; 049import org.nuxeo.runtime.api.Framework; 050import org.nuxeo.runtime.model.ComponentContext; 051import org.nuxeo.runtime.model.ComponentInstance; 052import org.nuxeo.runtime.model.ComponentName; 053import org.nuxeo.runtime.model.DefaultComponent; 054import org.nuxeo.runtime.transaction.TransactionHelper; 055 056/** 057 * Relation service. 058 * <p> 059 * It handles a registry of graph instances through extension points. 060 */ 061public class RelationService extends DefaultComponent implements RelationManager { 062 063 public static final ComponentName NAME = new ComponentName( 064 "org.nuxeo.ecm.platform.relations.services.RelationService"); 065 066 private static final long serialVersionUID = -4778456059717447736L; 067 068 private static final Log log = LogFactory.getLog(RelationService.class); 069 070 /** Graph type -> class. */ 071 protected final Map<String, Class<?>> graphTypes; 072 073 /** Graph name -> description */ 074 protected final Map<String, GraphDescription> graphDescriptions; 075 076 /** Graph name -> factory. */ 077 public final Map<String, GraphFactory> graphFactories; 078 079 /** Graph name -> graph instance. */ 080 public final Map<String, Graph> graphRegistry; 081 082 protected final Map<String, String> resourceAdapterRegistry; 083 084 public RelationService() { 085 // Hashtable to get implicit synchronization 086 graphTypes = new Hashtable<String, Class<?>>(); 087 graphDescriptions = new Hashtable<String, GraphDescription>(); 088 graphRegistry = new Hashtable<String, Graph>(); 089 graphFactories = new Hashtable<String, GraphFactory>(); 090 resourceAdapterRegistry = new Hashtable<String, String>(); 091 } 092 093 @Override 094 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 095 if (extensionPoint.equals("graphtypes")) { 096 registerGraphType(contribution); 097 } else if (extensionPoint.equals("graphs")) { 098 registerGraph(contribution); 099 } else if (extensionPoint.equals("resourceadapters")) { 100 registerResourceAdapter(contribution); 101 } else { 102 log.error(String.format("Unknown extension point %s, can't register !", extensionPoint)); 103 } 104 } 105 106 @Override 107 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 108 if (extensionPoint.equals("graphtypes")) { 109 unregisterGraphType(contribution); 110 } else if (extensionPoint.equals("graphs")) { 111 unregisterGraph(contribution); 112 } else if (extensionPoint.equals("resourceadapters")) { 113 unregisterResourceAdapter(contribution); 114 } else { 115 log.error(String.format("Unknown extension point %s, can't unregister !", extensionPoint)); 116 } 117 } 118 119 @Override 120 @SuppressWarnings("unchecked") 121 public <T> T getAdapter(Class<T> adapter) { 122 if (adapter.isAssignableFrom(RelationManager.class)) { 123 return (T) this; 124 } else if (adapter.isAssignableFrom(DocumentRelationManager.class)) { 125 return (T) new DocumentRelationService(); 126 } 127 return null; 128 } 129 130 // Graph types 131 132 /** 133 * Registers a graph type, saving a name and a class name. 134 * <p> 135 * The name will be used when registering graphs. 136 */ 137 private void registerGraphType(Object contribution) { 138 GraphTypeDescriptor graphTypeDescriptor = (GraphTypeDescriptor) contribution; 139 String graphType = graphTypeDescriptor.getName(); 140 String className = graphTypeDescriptor.getClassName(); 141 142 if (graphTypes.containsKey(graphType)) { 143 log.error(String.format("Graph type %s already registered using %s", graphType, graphTypes.get(graphType))); 144 return; 145 } 146 Class<?> klass; 147 try { 148 klass = getClass().getClassLoader().loadClass(className); 149 } catch (ClassNotFoundException e) { 150 throw new RuntimeException(String.format("Cannot register unknown class for graph type %s: %s", graphType, 151 className), e); 152 } 153 if (!Graph.class.isAssignableFrom(klass) && !GraphFactory.class.isAssignableFrom(klass)) { 154 throw new RuntimeException("Invalid graph class/factory type: " + className); 155 } 156 graphTypes.put(graphType, klass); 157 log.info(String.format("Registered graph type: %s (%s)", graphType, className)); 158 } 159 160 /** 161 * Unregisters a graph type. 162 */ 163 private void unregisterGraphType(Object contrib) { 164 GraphTypeDescriptor graphTypeExtension = (GraphTypeDescriptor) contrib; 165 String graphType = graphTypeExtension.getName(); 166 List<GraphDescription> list = new ArrayList<GraphDescription>(graphDescriptions.values()); // copy 167 for (GraphDescription graphDescription : list) { 168 if (graphType.equals(graphDescription.getGraphType())) { 169 String name = graphDescription.getName(); 170 graphFactories.remove(name); 171 graphRegistry.remove(name); 172 graphDescriptions.remove(name); 173 log.info("Unregistered graph: " + name); 174 } 175 } 176 graphTypes.remove(graphType); 177 log.info("Unregistered graph type: " + graphType); 178 } 179 180 public List<String> getGraphTypes() { 181 List<String> res = new ArrayList<String>(); 182 for (String type : graphTypes.keySet()) { 183 res.add(type); 184 } 185 return res; 186 } 187 188 /** 189 * Registers a graph instance. 190 * <p> 191 * The graph has to be declared as using a type already registered in the graph type registry. 192 */ 193 protected void registerGraph(Object contribution) { 194 GraphDescription graphDescription = (GraphDescription) contribution; 195 String name = graphDescription.getName(); 196 if (graphDescriptions.containsKey(name)) { 197 log.info(String.format("Overriding graph %s definition", name)); 198 graphDescriptions.remove(name); 199 } 200 graphDescriptions.put(name, graphDescription); 201 log.info("Registered graph: " + name); 202 203 // remove any existing graph instance in case its definition changed 204 graphRegistry.remove(name); 205 } 206 207 /** 208 * Unregisters a graph. 209 */ 210 protected void unregisterGraph(Object contribution) { 211 GraphDescription graphDescription = (GraphDescription) contribution; 212 String name = graphDescription.getName(); 213 if (graphDescriptions.containsKey(name)) { 214 graphFactories.remove(name); 215 graphRegistry.remove(name); 216 graphDescriptions.remove(name); 217 log.info("Unregistered graph: " + name); 218 } 219 } 220 221 // Resource adapters 222 223 private void registerResourceAdapter(Object contribution) { 224 ResourceAdapterDescriptor adapter = (ResourceAdapterDescriptor) contribution; 225 String ns = adapter.getNamespace(); 226 String adapterClassName = adapter.getClassName(); 227 if (resourceAdapterRegistry.containsKey(ns)) { 228 log.info("Overriding resource adapter config for namespace " + ns); 229 } 230 resourceAdapterRegistry.put(ns, adapterClassName); 231 log.info(String.format("%s namespace registered using adapter %s", ns, adapterClassName)); 232 } 233 234 private void unregisterResourceAdapter(Object contribution) { 235 ResourceAdapterDescriptor adapter = (ResourceAdapterDescriptor) contribution; 236 String ns = adapter.getNamespace(); 237 String adapterClassName = adapter.getClassName(); 238 String registered = resourceAdapterRegistry.get(ns); 239 if (registered == null) { 240 log.error(String.format("Namespace %s not found", ns)); 241 } else if (!registered.equals(adapterClassName)) { 242 log.error(String.format("Namespace %s: wrong class %s", ns, registered)); 243 } else { 244 resourceAdapterRegistry.remove(ns); 245 log.info(String.format("%s unregistered, was using %s", ns, adapterClassName)); 246 } 247 } 248 249 private ResourceAdapter getResourceAdapterForNamespace(String namespace) { 250 String adapterClassName = resourceAdapterRegistry.get(namespace); 251 if (adapterClassName == null) { 252 log.error(String.format("Cannot find adapter for namespace: %s", namespace)); 253 return null; 254 } else { 255 try { 256 // Thread context loader is not working in isolated EARs 257 ResourceAdapter adapter = (ResourceAdapter) RelationService.class.getClassLoader().loadClass( 258 adapterClassName).newInstance(); 259 adapter.setNamespace(namespace); 260 return adapter; 261 } catch (ReflectiveOperationException e) { 262 String msg = String.format("Cannot instantiate generator with namespace '%s': %s", namespace, e); 263 log.error(msg); 264 return null; 265 } 266 } 267 } 268 269 // RelationManager interface 270 271 @Override 272 public Graph getGraphByName(String name) { 273 return getGraph(name, null); 274 } 275 276 @Override 277 public Graph getGraph(String name, CoreSession session) { 278 GraphDescription graphDescription = graphDescriptions.get(name); 279 if (graphDescription == null) { 280 throw new RuntimeException("No such graph: " + name); 281 } 282 283 Graph graph = getGraphFromRegistries(graphDescription, session); 284 if (graph != null) { 285 return graph; 286 } 287 288 // check what we have for the graph type 289 Class<?> klass = graphTypes.get(graphDescription.getGraphType()); 290 if (Graph.class.isAssignableFrom(klass)) { 291 // instance 292 try { 293 graph = (Graph) klass.newInstance(); 294 } catch (ReflectiveOperationException e) { 295 throw new RuntimeException(e); 296 } 297 graphRegistry.put(name, graph); 298 } else { // GraphFactory.class.isAssignableFrom(klass) 299 // factory 300 GraphFactory factory; 301 try { 302 factory = (GraphFactory) klass.newInstance(); 303 } catch (ReflectiveOperationException e) { 304 throw new RuntimeException(e); 305 } 306 graphFactories.put(name, factory); 307 } 308 309 return getGraphFromRegistries(graphDescription, session); 310 } 311 312 /** Gets the graph from the registries. */ 313 protected Graph getGraphFromRegistries(GraphDescription graphDescription, CoreSession session) { 314 String name = graphDescription.getName(); 315 // check instances 316 Graph graph = graphRegistry.get(name); 317 if (graph != null) { 318 graph.setDescription(graphDescription); 319 return graph; 320 } 321 322 // check factories 323 GraphFactory factory = graphFactories.get(name); 324 if (factory != null) { 325 return factory.createGraph(graphDescription, session); 326 } 327 328 return null; 329 } 330 331 protected Graph newGraph(String className) { 332 try { 333 Class<?> klass = getClass().getClassLoader().loadClass(className); 334 return (Graph) klass.newInstance(); 335 } catch (ReflectiveOperationException e) { 336 throw new RuntimeException(e); 337 } 338 } 339 340 @Override 341 public Graph getTransientGraph(String type) { 342 Class<?> klass = graphTypes.get(type); 343 if (Graph.class.isAssignableFrom(klass)) { 344 try { 345 return (Graph) klass.newInstance(); 346 } catch (ReflectiveOperationException e) { 347 throw new RuntimeException(e); 348 } 349 } 350 throw new RuntimeException("Graph type cannot be transient: " + type); 351 } 352 353 @Override 354 public Resource getResource(String namespace, Serializable object, Map<String, Object> context) 355 { 356 ResourceAdapter adapter = getResourceAdapterForNamespace(namespace); 357 if (adapter == null) { 358 log.error("Cannot find adapter for namespace: " + namespace); 359 return null; 360 } else { 361 return adapter.getResource(object, context); 362 } 363 } 364 365 @Override 366 public Set<Resource> getAllResources(Serializable object, Map<String, Object> context) { 367 // TODO OPTIM implement reverse map in registerContribution 368 Set<Resource> res = new HashSet<Resource>(); 369 for (String ns : resourceAdapterRegistry.keySet()) { 370 ResourceAdapter adapter = getResourceAdapterForNamespace(ns); 371 if (adapter == null) { 372 continue; 373 } 374 Class<?> klass = adapter.getKlass(); 375 if (klass == null) { 376 continue; 377 } 378 if (klass.isAssignableFrom(object.getClass())) { 379 res.add(adapter.getResource(object, context)); 380 } 381 } 382 return res; 383 } 384 385 @Override 386 public Serializable getResourceRepresentation(String namespace, Resource resource, Map<String, Object> context) 387 { 388 ResourceAdapter adapter = getResourceAdapterForNamespace(namespace); 389 if (adapter == null) { 390 log.error("Cannot find adapter for namespace: " + namespace); 391 return null; 392 } else { 393 return adapter.getResourceRepresentation(resource, context); 394 } 395 } 396 397 @Override 398 @Deprecated 399 public void add(String graphName, List<Statement> statements) { 400 getGraphByName(graphName).add(statements); 401 } 402 403 @Override 404 @Deprecated 405 public void clear(String graphName) { 406 getGraphByName(graphName).clear(); 407 } 408 409 @Override 410 @Deprecated 411 public List<Node> getObjects(String graphName, Node subject, Node predicate) { 412 return getGraphByName(graphName).getObjects(subject, predicate); 413 } 414 415 @Override 416 @Deprecated 417 public List<Node> getPredicates(String graphName, Node subject, Node object) { 418 return getGraphByName(graphName).getPredicates(subject, object); 419 } 420 421 @Override 422 @Deprecated 423 public List<Statement> getStatements(String graphName, Statement statement) { 424 return getGraphByName(graphName).getStatements(statement); 425 } 426 427 @Override 428 @Deprecated 429 public List<Statement> getStatements(String graphName) { 430 return getGraphByName(graphName).getStatements(); 431 } 432 433 @Override 434 @Deprecated 435 public List<Node> getSubjects(String graphName, Node predicate, Node object) { 436 return getGraphByName(graphName).getSubjects(predicate, object); 437 } 438 439 @Override 440 @Deprecated 441 public boolean hasResource(String graphName, Resource resource) { 442 return getGraphByName(graphName).hasResource(resource); 443 } 444 445 @Override 446 @Deprecated 447 public boolean hasStatement(String graphName, Statement statement) { 448 return getGraphByName(graphName).hasStatement(statement); 449 } 450 451 @Override 452 @Deprecated 453 public QueryResult query(String graphName, String queryString, String language, String baseURI) 454 { 455 return getGraphByName(graphName).query(queryString, language, baseURI); 456 } 457 458 @Override 459 @Deprecated 460 public boolean read(String graphName, InputStream in, String lang, String base) { 461 return getGraphByName(graphName).read(in, lang, base); 462 } 463 464 @Override 465 @Deprecated 466 public void remove(String graphName, List<Statement> statements) { 467 getGraphByName(graphName).remove(statements); 468 } 469 470 @Override 471 @Deprecated 472 public Long size(String graphName) { 473 return getGraphByName(graphName).size(); 474 } 475 476 @Override 477 @Deprecated 478 public boolean write(String graphName, OutputStream out, String lang, String base) { 479 return getGraphByName(graphName).write(out, lang, base); 480 } 481 482 @Override 483 public List<String> getGraphNames() { 484 return new ArrayList<String>(graphDescriptions.keySet()); 485 } 486 487 @Override 488 public void applicationStarted(ComponentContext context) { 489 RepositoryService repositoryService = Framework.getService(RepositoryService.class); 490 if (repositoryService == null) { 491 // RepositoryService failed to start, no need to go further 492 return; 493 } 494 Thread t = new Thread("relation-service-init") { 495 @Override 496 public void run() { 497 Thread.currentThread().setContextClassLoader(RelationService.class.getClassLoader()); 498 log.info("Relation Service initialization"); 499 500 // init jena Graph outside of Tx 501 for (String graphName : graphDescriptions.keySet()) { 502 GraphDescription desc = graphDescriptions.get(graphName); 503 if (desc.getGraphType().equalsIgnoreCase("jena")) { 504 log.info("create RDF Graph " + graphName); 505 Graph graph = getGraphByName(graphName); 506 graph.size(); 507 } 508 } 509 510 // init non jena Graph inside a Tx 511 TransactionHelper.startTransaction(); 512 try { 513 for (String graphName : graphDescriptions.keySet()) { 514 GraphDescription desc = graphDescriptions.get(graphName); 515 if (!desc.getGraphType().equalsIgnoreCase("jena")) { 516 log.info("create RDF Graph " + graphName); 517 Graph graph = getGraphByName(graphName); 518 graph.size(); 519 } 520 } 521 } finally { 522 TransactionHelper.commitOrRollbackTransaction(); 523 } 524 } 525 }; 526 t.start(); 527 try { 528 t.join(); 529 } catch (InterruptedException e) { 530 log.error("Cannot join init thread", e); 531 } 532 } 533 534}