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