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