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