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