001/* 002 * Copyright 2010 Anyware Services 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 017package org.ametys.plugins.repository; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collection; 024import java.util.List; 025import java.util.Map; 026import java.util.Set; 027 028import javax.jcr.ItemExistsException; 029import javax.jcr.NamespaceRegistry; 030import javax.jcr.Node; 031import javax.jcr.PathNotFoundException; 032import javax.jcr.Repository; 033import javax.jcr.RepositoryException; 034import javax.jcr.Session; 035import javax.jcr.Value; 036import javax.jcr.nodetype.NodeType; 037import javax.jcr.query.Query; 038 039import org.apache.avalon.framework.activity.Initializable; 040import org.apache.avalon.framework.component.Component; 041import org.apache.avalon.framework.service.ServiceException; 042import org.apache.avalon.framework.service.ServiceManager; 043import org.apache.avalon.framework.service.Serviceable; 044import org.apache.excalibur.source.Source; 045import org.apache.excalibur.source.SourceResolver; 046import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; 047import org.apache.jackrabbit.core.nodetype.NodeTypeDefStore; 048import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; 049import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; 050import org.apache.jackrabbit.spi.Name; 051import org.apache.jackrabbit.spi.QNodeTypeDefinition; 052 053import org.ametys.core.util.LambdaUtils; 054import org.ametys.plugins.repository.jcr.JCRAmetysObject; 055import org.ametys.plugins.repository.jcr.JCRAmetysObjectFactory; 056import org.ametys.plugins.repository.jcr.NodeTypeHelper; 057import org.ametys.plugins.repository.provider.AbstractRepository; 058import org.ametys.plugins.repository.virtual.VirtualAmetysObjectFactory; 059import org.ametys.runtime.plugin.component.AbstractLogEnabled; 060 061/** 062 * Base component for accessing {@link AmetysObject}s. 063 */ 064public class AmetysObjectResolver extends AbstractLogEnabled implements Serviceable, Initializable, Component 065{ 066 /** Avalon ROLE. */ 067 public static final String ROLE = AmetysObjectResolver.class.getName(); 068 069 /** JCR Relative Path to root. */ 070 public static final String ROOT_REPO = "ametys:root"; 071 072 /** JCR type for root node. */ 073 public static final String ROOT_TYPE = "ametys:root"; 074 075 /** JCR mixin type for objects. */ 076 public static final String OBJECT_TYPE = "ametys:object"; 077 078 /** JCR property name for virtual objects. */ 079 public static final String VIRTUAL_PROPERTY = "ametys-internal:virtual"; 080 081 private AmetysObjectFactoryExtensionPoint _ametysFactoryExtensionPoint; 082 private NamespacesExtensionPoint _namespacesExtensionPoint; 083 private NodeTypeDefinitionsExtensionPoint _nodetypeDefsExtensionPoint; 084 private Repository _repository; 085 private SourceResolver _resolver; 086 087 088 @Override 089 public void service(ServiceManager manager) throws ServiceException 090 { 091 _resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 092 _repository = (Repository) manager.lookup(AbstractRepository.ROLE); 093 _ametysFactoryExtensionPoint = (AmetysObjectFactoryExtensionPoint) manager.lookup(AmetysObjectFactoryExtensionPoint.ROLE); 094 _namespacesExtensionPoint = (NamespacesExtensionPoint) manager.lookup(NamespacesExtensionPoint.ROLE); 095 _nodetypeDefsExtensionPoint = (NodeTypeDefinitionsExtensionPoint) manager.lookup(NodeTypeDefinitionsExtensionPoint.ROLE); 096 } 097 098 @Override 099 public void initialize() throws Exception 100 { 101 // On vérifie que le root soit bien créé 102 Session session = _repository.login(); 103 104 _initNamespaces(session); 105 _initNodetypes(session); 106 107 if (!session.getRootNode().hasNode(ROOT_REPO)) 108 { 109 getLogger().info("Creating ametys root Node"); 110 111 session.getRootNode().addNode(ROOT_REPO, ROOT_TYPE); 112 } 113 114 if (session.hasPendingChanges()) 115 { 116 session.save(); 117 } 118 119 session.logout(); 120 } 121 122 private void _initNamespaces(Session session) throws RepositoryException 123 { 124 NamespaceRegistry registry = session.getWorkspace().getNamespaceRegistry(); 125 Collection prefixes = Arrays.asList(registry.getPrefixes()); 126 127 _namespacesExtensionPoint.getExtensionsIds().stream() 128 .filter(prefix -> !prefixes.contains(prefix)) 129 .forEach(LambdaUtils.wrapConsumer(prefix -> 130 { 131 String namespace = _namespacesExtensionPoint.getNamespace(prefix); 132 getLogger().debug("Adding {} namespace", prefix); 133 registry.registerNamespace(prefix, namespace); 134 })); 135 } 136 137 private void _initNodetypes(Session session) throws RepositoryException, InvalidNodeTypeDefException, IOException 138 { 139 NodeTypeDefStore store = new NodeTypeDefStore(); 140 141 // Hard-coded first nodetypes file 142 Source fsource = _resolver.resolveURI("plugin:repository://nodetypes/ametys_nodetypes.xml"); 143 try (InputStream is = fsource.getInputStream()) 144 { 145 store.load(is); 146 } 147 finally 148 { 149 _resolver.release(fsource); 150 } 151 152 // Load all declared nodetype definitions in the store. 153 for (String nodetypeDef : _nodetypeDefsExtensionPoint.getNodeTypeDefinitions()) 154 { 155 Source source = _resolver.resolveURI(nodetypeDef); 156 try (InputStream is = source.getInputStream()) 157 { 158 store.load(is); 159 } 160 finally 161 { 162 _resolver.release(source); 163 } 164 } 165 166 // Load all declared nodetype definitions in the store. 167 Map<String, Set<String>> nodeTypeDefinitions = _ametysFactoryExtensionPoint.getNodeTypeDefinitions(); 168 for (String pluginName : nodeTypeDefinitions.keySet()) 169 { 170 for (String nodetypeDef : nodeTypeDefinitions.get(pluginName)) 171 { 172 Source source = _resolver.resolveURI("plugin:" + pluginName + "://" + nodetypeDef); 173 try (InputStream is = source.getInputStream()) 174 { 175 store.load(is); 176 } 177 finally 178 { 179 _resolver.release(source); 180 } 181 } 182 } 183 184 NodeTypeManagerImpl ntManager = (NodeTypeManagerImpl) session.getWorkspace().getNodeTypeManager(); 185 NodeTypeRegistry registry = ntManager.getNodeTypeRegistry(); 186 187 // Remove all already registered nodetypes from the store. 188 for (Name name : registry.getRegisteredNodeTypes()) 189 { 190 store.remove(name); 191 } 192 193 // Register the "new" nodetype definitions. 194 Collection<QNodeTypeDefinition> ntDefs = store.all(); 195 if (!ntDefs.isEmpty()) 196 { 197 registry.registerNodeTypes(ntDefs); 198 } 199 } 200 201 /** 202 * Retrieves an {@link AmetysObject} from an absolute path. 203 * The given path is absolute in the Ametys tree.<br> 204 * The path may omit the leading <code>'/'</code>, but the path 205 * is always considered absolute, <code>null</code> path is forbidden.<br> 206 * @param <A> the actual type of {@link AmetysObject}. 207 * @param absolutePath the path to use. 208 * @return the corresponding AmetysObject. 209 * @throws AmetysRepositoryException if an error occurs. 210 * @throws UnknownAmetysObjectException if no such object exists for the given path. 211 * @deprecated Use resolveByPath instead 212 */ 213 @Deprecated 214 public <A extends AmetysObject> A resolve(String absolutePath) throws AmetysRepositoryException, UnknownAmetysObjectException 215 { 216 return resolveByPath(absolutePath); 217 } 218 219 /** 220 * Retrieves an {@link AmetysObject} from an absolute path. 221 * The given path is absolute in the Ametys tree.<br> 222 * The path may omit the leading <code>'/'</code>, but the path 223 * is always considered absolute, <code>null</code> path is forbidden.<br> 224 * @param <A> the actual type of {@link AmetysObject}. 225 * @param absolutePath the path to use. 226 * @return the corresponding AmetysObject. 227 * @throws AmetysRepositoryException if an error occurs. 228 * @throws UnknownAmetysObjectException if no such object exists for the given path. 229 */ 230 public <A extends AmetysObject> A resolveByPath(String absolutePath) throws AmetysRepositoryException, UnknownAmetysObjectException 231 { 232 if (getLogger().isDebugEnabled()) 233 { 234 getLogger().debug("Resolving " + absolutePath); 235 } 236 237 if (absolutePath == null) 238 { 239 throw new AmetysRepositoryException("Absolute path cannot be null"); 240 } 241 242 Node rootNode; 243 Session session = null; 244 try 245 { 246 session = _repository.login(); 247 rootNode = session.getRootNode().getNode(ROOT_REPO); 248 } 249 catch (PathNotFoundException e) 250 { 251 if (session != null) 252 { 253 session.logout(); 254 } 255 256 throw new AmetysRepositoryException("Unable to get ametys:root Node", e); 257 } 258 catch (RepositoryException e) 259 { 260 if (session != null) 261 { 262 session.logout(); 263 } 264 265 throw new AmetysRepositoryException("An error occured while getting ametys:root node", e); 266 } 267 268 try 269 { 270 return this.<A>_resolve(null, rootNode, absolutePath, false); 271 } 272 catch (RepositoryException e) 273 { 274 session.logout(); 275 276 throw new AmetysRepositoryException("An error occured while resolving " + absolutePath, e); 277 } 278 } 279 280 /** 281 * Retrieves an {@link AmetysObject} by its unique id. 282 * @param <A> the actual type of {@link AmetysObject}. 283 * @param id the identifier representing the wanted {@link AmetysObject} is the Ametys repository. 284 * @return the corresponding {@link AmetysObject}. 285 * @throws AmetysRepositoryException if an error occurs. 286 * @throws UnknownAmetysObjectException if no such object exists for the given id. 287 */ 288 public <A extends AmetysObject> A resolveById(String id) throws AmetysRepositoryException, UnknownAmetysObjectException 289 { 290 if (getLogger().isDebugEnabled()) 291 { 292 getLogger().debug("Resolving " + id); 293 } 294 295 int index = id.indexOf("://"); 296 if (index == -1) 297 { 298 throw new AmetysRepositoryException("An object id must conform to the <protocol>://<protocol-specific-part> syntax: " + id); 299 } 300 301 String scheme = id.substring(0, index); 302 303 AmetysObjectFactory<A> factory = _ametysFactoryExtensionPoint.getFactoryForScheme(scheme); 304 305 if (factory == null) 306 { 307 throw new UnknownAmetysObjectException("There's no object for id " + id); 308 } 309 310 return factory.getAmetysObjectById(id); 311 } 312 313 /** 314 * <b>Expert</b>. Retrieves an {@link AmetysObject} by its unique id and the provided JCR Session.<br> 315 * It only works with id corresponding to a {@link JCRAmetysObjectFactory}.<br> 316 * This method should be uses to avoid useless Session creation. 317 * @param <A> the actual type of {@link AmetysObject}. 318 * @param id the identifier representing the wanted {@link AmetysObject} is the Ametys repository. 319 * @param session the JCR Session to use to retrieve the {@link AmetysObject}. 320 * @return the corresponding {@link AmetysObject}. 321 * @throws AmetysRepositoryException if an error occurs. 322 * @throws UnknownAmetysObjectException if no such object exists for the given id. 323 * @throws RepositoryException if a JCR error occurs. 324 */ 325 public <A extends AmetysObject> A resolveById(String id, Session session) throws AmetysRepositoryException, UnknownAmetysObjectException, RepositoryException 326 { 327 if (getLogger().isDebugEnabled()) 328 { 329 getLogger().debug("Resolving " + id); 330 } 331 332 int index = id.indexOf("://"); 333 if (index == -1) 334 { 335 throw new AmetysRepositoryException("An object id must conform to the <protocol>://<protocol-specific-part> syntax: " + id); 336 } 337 338 String scheme = id.substring(0, index); 339 340 AmetysObjectFactory<A> factory = _ametysFactoryExtensionPoint.getFactoryForScheme(scheme); 341 342 if (factory == null) 343 { 344 throw new UnknownAmetysObjectException("There's no object for id " + id); 345 } 346 347 if (!(factory instanceof JCRAmetysObjectFactory)) 348 { 349 throw new IllegalArgumentException("The expert method resolveById(String, Session) should only be called for id corresponding to a JCRAmetysObjectFactory"); 350 } 351 352 return ((JCRAmetysObjectFactory<A>) factory).getAmetysObjectById(id, session); 353 } 354 355 /** 356 * Return true if the specified id correspond to an existing {@link AmetysObject}. 357 * @param id the identifier. 358 * @return true if the specified id correspond to an existing {@link AmetysObject}. 359 * @throws AmetysRepositoryException if an error occurs. 360 */ 361 public boolean hasAmetysObjectForId(String id) throws AmetysRepositoryException 362 { 363 int index = id.indexOf("://"); 364 if (index == -1) 365 { 366 throw new AmetysRepositoryException("An object id must conform to the <protocol>://<protocol-specific-part> syntax: " + id); 367 } 368 369 String scheme = id.substring(0, index); 370 371 AmetysObjectFactory factory = _ametysFactoryExtensionPoint.getFactoryForScheme(scheme); 372 373 if (factory == null) 374 { 375 return false; 376 } 377 378 return factory.hasAmetysObjectForId(id); 379 } 380 381 /** 382 * <b>Expert</b>. Returns the {@link AmetysObject} corresponding to a given JCR Node.<br> 383 * It is strictly equivalent to call <code>resolve(null, node, null, allowUnknownNode)</code> 384 * @param <A> the actual type of {@link AmetysObject}s 385 * @param node an existing node in the underlying JCR repository. 386 * @param allowUnknownNode if <code>true</code>, returns <code>null</code> if the node type 387 * does not correspond to a factory. If <code>false</code> and no factory 388 * corresponds, an {@link AmetysRepositoryException} is thrown. 389 * @return the {@link AmetysObject} corresponding to a given JCR node. 390 * @throws AmetysRepositoryException if an error occurs. 391 * @throws RepositoryException if a JCR error occurs. 392 */ 393 public <A extends AmetysObject> A resolve(Node node, boolean allowUnknownNode) throws AmetysRepositoryException, RepositoryException 394 { 395 return this.<A>_resolve(null, node, null, allowUnknownNode); 396 } 397 398 /** 399 * <b>Expert</b>. Retrieves an {@link AmetysObject}, given a JCR Node, a relative path 400 * and the parentPath in the Ametys hierarchy.<br> 401 * The path is always relative, even if it begins with a <code>'/'</code>, 402 * <code>null</code> path or empty path are equivalent.<br> 403 * May return null if ignoreUnknownNodes is true. 404 * @param <A> the actual type of {@link AmetysObject}. 405 * @param parentPath the parentPath of the returned AmetysObject, in the Ametys hierarchy. 406 * @param node the context JCR node. 407 * @param childPath the path relative to the JCR node. 408 * @param allowUnknownNode if <code>true</code>, returns <code>null</code> if the node type 409 * does not correspond to a factory. If <code>false</code> and no factory 410 * corresponds, an {@link AmetysRepositoryException} is thrown. 411 * @return the corresponding AmetysObject. 412 * @throws AmetysRepositoryException if an error occurs. 413 * @throws UnknownAmetysObjectException if no such object exists for the given path. 414 * @throws RepositoryException if a JCR error occurs. 415 */ 416 public <A extends AmetysObject> A resolve(String parentPath, Node node, String childPath, boolean allowUnknownNode) throws AmetysRepositoryException, UnknownAmetysObjectException, RepositoryException 417 { 418 return this.<A>_resolve(parentPath, node, childPath, allowUnknownNode); 419 } 420 421 @SuppressWarnings("unchecked") 422 private <T extends AmetysObject> T _resolve(String parentPath, Node node, String childPath, boolean allowUnknownNode) throws AmetysRepositoryException, UnknownAmetysObjectException, RepositoryException 423 { 424 if (getLogger().isDebugEnabled()) 425 { 426 getLogger().debug("Entering _resolve with parentPath=" + parentPath + ", node=" + node.getPath() + ", childPath=" + childPath + ", ignoreUnknownNodes=" + allowUnknownNode); 427 } 428 429 String path = childPath == null ? "" : childPath; 430 path = path.length() == 0 || path.charAt(0) != '/' ? path : path.substring(1); 431 432 if (path.length() != 0 && (Character.isSpaceChar(path.charAt(0)) || Character.isSpaceChar(path.charAt(path.length() - 1)))) 433 { 434 throw new AmetysRepositoryException("Path cannot begin or end with a space character"); 435 } 436 437 String nodeType = NodeTypeHelper.getNodeTypeName(node); 438 439 JCRAmetysObjectFactory jcrFactory = _getJCRFactory(nodeType, allowUnknownNode, parentPath, childPath); 440 441 if (jcrFactory == null) 442 { 443 return null; 444 } 445 446 AmetysObject rootObject = jcrFactory.getAmetysObject(node, parentPath); 447 448 if (path.length() != 0) 449 { 450 if (!(rootObject instanceof TraversableAmetysObject)) 451 { 452 throw new AmetysRepositoryException("The node of type '" + nodeType + "' at path '" + node.getPath() + "' does not corresponds to a TraversableAmetysObject"); 453 } 454 455 return (T) ((TraversableAmetysObject) rootObject).getChild(path); 456 } 457 else 458 { 459 return (T) rootObject; 460 } 461 } 462 463 464 private JCRAmetysObjectFactory _getJCRFactory(String nodeType, boolean allowUnknownNode, String parentPath, String childPath) 465 { 466 if (getLogger().isDebugEnabled()) 467 { 468 getLogger().debug("Nodetype is " + nodeType); 469 } 470 471 AmetysObjectFactory<?> factory = _ametysFactoryExtensionPoint.getFactoryForNodetype(nodeType); 472 473 if (factory == null) 474 { 475 if (allowUnknownNode) 476 { 477 if (getLogger().isDebugEnabled()) 478 { 479 getLogger().debug("No factory for nodetype " + nodeType + ". Unknown node is allowed, returning null."); 480 } 481 482 return null; 483 } 484 485 throw new UnknownAmetysObjectException("Cannot get factory for node '" + childPath + "' under '" + parentPath + "': There's no factory for nodetype: " + nodeType); 486 } 487 488 if (getLogger().isDebugEnabled()) 489 { 490 getLogger().debug("Factory is " + factory.getClass().getName()); 491 } 492 493 if (!(factory instanceof JCRAmetysObjectFactory)) 494 { 495 throw new AmetysRepositoryException("A factory resolving JCR nodes must implements JCRAmetysObjectFactory"); 496 } 497 498 JCRAmetysObjectFactory jcrFactory = (JCRAmetysObjectFactory) factory; 499 500 return jcrFactory; 501 } 502 503 /** 504 * <b>Expert</b>. Retrieves the virtual children of a concrete JCR Node.<br> 505 * @param <A> the actual type of {@link AmetysObject}s. 506 * @param parent the {@link JCRAmetysObject} "hosting" the {@link VirtualAmetysObjectFactory} reference. 507 * @return all virtual children under the given JCR Node in the Ametys hierarchy. 508 * @throws AmetysRepositoryException if an error occurs. 509 * @throws RepositoryException if a JCR error occurs. 510 */ 511 public <A extends AmetysObject> AmetysObjectIterable<A> resolveVirtualChildren(JCRAmetysObject parent) throws AmetysRepositoryException, RepositoryException 512 { 513 Node contextNode = parent.getNode(); 514 515 if (getLogger().isDebugEnabled()) 516 { 517 getLogger().debug("Entering resolveVirtualChildren with parent=" + parent); 518 } 519 520 if (!contextNode.hasProperty(VIRTUAL_PROPERTY)) 521 { 522 return null; 523 } 524 525 Value[] values = contextNode.getProperty(VIRTUAL_PROPERTY).getValues(); 526 List<AmetysObjectIterable<A>> children = new ArrayList<>(values.length); 527 for (Value value : values) 528 { 529 String id = value.getString(); 530 531 if (getLogger().isDebugEnabled()) 532 { 533 getLogger().debug("Found virtual factory id: " + id); 534 } 535 536 AmetysObjectFactory<A> factory = _ametysFactoryExtensionPoint.getExtension(id); 537 538 if (factory == null) 539 { 540 throw new AmetysRepositoryException("There's no virtual factory for id " + id); 541 } 542 543 if (getLogger().isDebugEnabled()) 544 { 545 getLogger().debug("Found factory: " + factory.getClass().getName()); 546 } 547 548 if (!(factory instanceof VirtualAmetysObjectFactory)) 549 { 550 throw new AmetysRepositoryException("A factory handling virtual objects must implement VirtualAmetysObjectFactory"); 551 } 552 553 VirtualAmetysObjectFactory<A> virtualFactory = (VirtualAmetysObjectFactory<A>) factory; 554 children.add(virtualFactory.getChildren(parent)); 555 } 556 557 return new ChainedAmetysObjectIterable<>(children); 558 } 559 560 /** 561 * <b>Expert</b>. Retrieves the virtual child of a concrete JCR Node.<br> 562 * @param parent the {@link JCRAmetysObject} "hosting" the {@link VirtualAmetysObjectFactory} reference. 563 * @param childPath the name of the virtual child. 564 * @return a named child under the given JCR Node in the Ametys hierarchy. 565 * @throws AmetysRepositoryException if an error occurs. 566 * @throws RepositoryException if a JCR error occurs. 567 * @throws UnknownAmetysObjectException if the named child does not exist 568 */ 569 public AmetysObject resolveVirtualChild(JCRAmetysObject parent, String childPath) throws AmetysRepositoryException, RepositoryException, UnknownAmetysObjectException 570 { 571 Node contextNode = parent.getNode(); 572 573 if (getLogger().isDebugEnabled()) 574 { 575 getLogger().debug("Entering resolveVirtualChild with parent=" + parent); 576 } 577 578 if (!contextNode.hasProperty(VIRTUAL_PROPERTY)) 579 { 580 throw new UnknownAmetysObjectException("There's no virtual child at Ametys path " + parent.getPath()); 581 } 582 583 String path = childPath == null ? "" : childPath; 584 path = path.length() == 0 || path.charAt(0) != '/' ? path : path.substring(1); 585 int index = path.indexOf('/'); 586 String childName = index == -1 ? path : path.substring(0, index); 587 String subPath = index == -1 ? null : path.substring(index + 1); 588 589 if (childName.length() == 0) 590 { 591 throw new AmetysRepositoryException("A path element cannot be empty in " + childPath); 592 } 593 else if (Character.isSpaceChar(path.charAt(0)) || Character.isSpaceChar(path.charAt(path.length() - 1))) 594 { 595 throw new AmetysRepositoryException("Path element cannot begin or end with a space character: " + childName); 596 } 597 598 Value[] values = contextNode.getProperty(VIRTUAL_PROPERTY).getValues(); 599 AmetysObject object = _getVirtualChild(parent, childName, values); 600 601 if (object == null) 602 { 603 throw new UnknownAmetysObjectException("There's no virtual object named " + childName + " at Ametys path " + parent.getPath()); 604 } 605 606 if (subPath != null) 607 { 608 if (!(object instanceof TraversableAmetysObject)) 609 { 610 throw new AmetysRepositoryException("The virtual object " + childName + "at path '" + childPath + "' does not corresponds to a TraversableAmetysObject"); 611 } 612 613 return ((TraversableAmetysObject) object).getChild(subPath); 614 } 615 else 616 { 617 return object; 618 } 619 } 620 621 /** 622 * Executes the given JCR XPath query and resolves results as 623 * {@link AmetysObject}s.<br> 624 * The resulting {@link AmetysObjectIterable} supports lazy loading, but 625 * will also fail lazily if one if the result nodes does not correspond to 626 * an {@link AmetysObject}. 627 * @param <A> the actual type of the results. 628 * @param jcrQuery a JCR XPath query. 629 * @return an Iterator over the resulting {@link AmetysObject}. 630 */ 631 public <A extends AmetysObject> AmetysObjectIterable<A> query(String jcrQuery) 632 { 633 Session session = null; 634 try 635 { 636 session = _repository.login(); 637 return query(jcrQuery, session); 638 } 639 catch (RepositoryException ex) 640 { 641 if (session != null) 642 { 643 session.logout(); 644 } 645 646 throw new AmetysRepositoryException("An error occured executing the JCR query : " + jcrQuery, ex); 647 } 648 } 649 650 /** 651 * <b>Expert</b>. Executes the given JCR XPath query with the provided JCR Session and resolves results as 652 * {@link AmetysObject}s.<br> 653 * The resulting {@link AmetysObjectIterable} supports lazy loading, but 654 * will also fail lazily if one if the result nodes does not correspond to 655 * an {@link AmetysObject}. 656 * @param <A> the actual type of the results. 657 * @param jcrQuery a JCR XPath query. 658 * @param session the JCR Session to use to execute the request. 659 * @return an Iterator over the resulting {@link AmetysObject}. 660 * @throws RepositoryException if a JCR error occurs. 661 */ 662 @SuppressWarnings("deprecation") 663 public <A extends AmetysObject> AmetysObjectIterable<A> query(String jcrQuery, Session session) throws RepositoryException 664 { 665 if (getLogger().isDebugEnabled()) 666 { 667 getLogger().debug("Executing XPath query: '" + jcrQuery + "'"); 668 } 669 670 Query query = session.getWorkspace().getQueryManager().createQuery(jcrQuery, Query.XPATH); 671 672 long t1 = System.currentTimeMillis(); 673 AmetysObjectIterable<A> it = new NodeIteratorIterable<>(this, query.execute().getNodes(), null, session); 674 675 if (getLogger().isInfoEnabled()) 676 { 677 getLogger().info("JCR query '" + jcrQuery + "' executed in " + (System.currentTimeMillis() - t1) + " ms"); 678 } 679 680 return it; 681 } 682 683 private AmetysObject _getVirtualChild(JCRAmetysObject parent, String childName, Value[] values) throws RepositoryException 684 { 685 int i = 0; 686 AmetysObject object = null; 687 688 while (object == null && i < values.length) 689 { 690 Value value = values[i]; 691 String id = value.getString(); 692 693 if (getLogger().isDebugEnabled()) 694 { 695 getLogger().debug("Found virtual factory id: " + id); 696 } 697 698 AmetysObjectFactory factory = _ametysFactoryExtensionPoint.getExtension(id); 699 700 if (factory == null) 701 { 702 throw new AmetysRepositoryException("There's no virtual factory for id " + id); 703 } 704 705 if (getLogger().isDebugEnabled()) 706 { 707 getLogger().debug("Found factory: " + factory.getClass().getName()); 708 } 709 710 if (!(factory instanceof VirtualAmetysObjectFactory)) 711 { 712 throw new AmetysRepositoryException("A factory handling virtual objects must implement VirtualAmetysObjectFactory: " + id); 713 } 714 715 VirtualAmetysObjectFactory virtualFactory = (VirtualAmetysObjectFactory) factory; 716 717 try 718 { 719 object = virtualFactory.getChild(parent, childName); 720 } 721 catch (UnknownAmetysObjectException e) 722 { 723 // Not an error 724 if (getLogger().isDebugEnabled()) 725 { 726 getLogger().debug("The factory: " + factory.getClass().getName() + " has no child named" + childName, e); 727 } 728 729 i++; 730 } 731 } 732 733 return object; 734 } 735 736 /** 737 * <b>Expert</b>. Creates a child object in the JCR tree and resolve it to an {@link AmetysObject}. 738 * @param <A> the actual type of {@link AmetysObject}s 739 * @param parentPath the parentPath of the new object. 740 * @param parentNode the parent JCR Node of the new object. 741 * @param childName the name of the new object. 742 * @param nodetype the type of the Node backing the new object. 743 * @return the newly created {@link AmetysObject}. 744 * @throws AmetysRepositoryException if an error occurs. 745 * @throws RepositoryIntegrityViolationException if an object with the same name already 746 * exists and same name siblings is not allowed. 747 * @throws RepositoryException if a JCR error occurs. 748 */ 749 public <A extends AmetysObject> A createAndResolve(String parentPath, Node parentNode, String childName, String nodetype) throws AmetysRepositoryException, RepositoryIntegrityViolationException, RepositoryException 750 { 751 if (getLogger().isDebugEnabled()) 752 { 753 getLogger().debug("Entering createAndResolve with parentPath=" + parentPath + ", parentNode=" + parentNode.getPath() + ", childName=" + childName + ", nodetype=" + nodetype); 754 } 755 756 if (_ametysFactoryExtensionPoint.getFactoryForNodetype(nodetype) == null) 757 { 758 throw new AmetysRepositoryException("Cannot create a node '" + childName + "' under '" + parentPath + "': There's no factory for nodetype: " + nodetype); 759 } 760 761 try 762 { 763 Node node = parentNode.addNode(childName, nodetype); 764 NodeType[] mixinNodeTypes = node.getMixinNodeTypes(); 765 boolean foundMixin = false; 766 767 int i = 0; 768 while (!foundMixin && i < mixinNodeTypes.length) 769 { 770 if (OBJECT_TYPE.equals(mixinNodeTypes[i].getName())) 771 { 772 foundMixin = true; 773 } 774 775 i++; 776 } 777 778 if (!foundMixin) 779 { 780 node.addMixin(OBJECT_TYPE); 781 } 782 783 return this.<A>resolve(parentPath, node, null, false); 784 } 785 catch (ItemExistsException e) 786 { 787 throw new RepositoryIntegrityViolationException("The object " + childName + " already exist at path " + parentPath, e); 788 } 789 catch (RepositoryException e) 790 { 791 throw new AmetysRepositoryException("Unable to add child node for the underlying node for object at path " + parentPath, e); 792 } 793 } 794}