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 */ 016package org.ametys.web.repository.site; 017 018import java.io.InputStream; 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Date; 022import java.util.List; 023import java.util.Optional; 024 025import javax.jcr.ItemExistsException; 026import javax.jcr.Node; 027import javax.jcr.NodeIterator; 028import javax.jcr.Property; 029import javax.jcr.PropertyIterator; 030import javax.jcr.RepositoryException; 031 032import org.apache.cocoon.util.HashUtil; 033import org.apache.commons.lang3.StringUtils; 034 035import org.ametys.cms.repository.Content; 036import org.ametys.cms.repository.ModifiableContent; 037import org.ametys.plugins.repository.AmetysObject; 038import org.ametys.plugins.repository.AmetysObjectIterable; 039import org.ametys.plugins.repository.AmetysRepositoryException; 040import org.ametys.plugins.repository.CollectionIterable; 041import org.ametys.plugins.repository.CopiableAmetysObject; 042import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 043import org.ametys.plugins.repository.MovableAmetysObject; 044import org.ametys.plugins.repository.RepositoryConstants; 045import org.ametys.plugins.repository.RepositoryIntegrityViolationException; 046import org.ametys.plugins.repository.TraversableAmetysObject; 047import org.ametys.plugins.repository.UnknownAmetysObjectException; 048import org.ametys.plugins.repository.collection.AmetysObjectCollection; 049import org.ametys.plugins.repository.collection.AmetysObjectCollectionFactory; 050import org.ametys.plugins.repository.data.UnknownDataException; 051import org.ametys.plugins.repository.data.ametysobject.ModifiableModelAwareDataAwareAmetysObject; 052import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder; 053import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableModelAwareDataHolder; 054import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData; 055import org.ametys.plugins.repository.data.repositorydata.RepositoryData; 056import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData; 057import org.ametys.plugins.repository.jcr.DefaultTraversableAmetysObject; 058import org.ametys.plugins.repository.jcr.SimpleAmetysObject; 059import org.ametys.plugins.repository.metadata.BinaryMetadata; 060import org.ametys.plugins.repository.metadata.ModifiableBinaryMetadata; 061import org.ametys.plugins.repository.metadata.UnknownMetadataException; 062import org.ametys.web.pageaccess.RestrictedPagePolicy; 063import org.ametys.web.repository.sitemap.Sitemap; 064import org.ametys.web.site.SiteConfigurationExtensionPoint; 065 066/** 067 * {@link AmetysObject} for storing site informations. 068 */ 069public final class Site extends DefaultTraversableAmetysObject<SiteFactory> implements MovableAmetysObject, CopiableAmetysObject, ModifiableModelAwareDataAwareAmetysObject 070{ 071 /** Site node type name. */ 072 public static final String NODE_TYPE = RepositoryConstants.NAMESPACE_PREFIX + ":site"; 073 074 /** Constants for title metadata. */ 075 private static final String __TITLE_ATTRIBUTE = "title"; 076 /** Constants for type metadata. */ 077 private static final String __TYPE_ATTRIBUTE = "type"; 078 /** Constants for description metadata. */ 079 private static final String __DESCRIPTION_ATTRIBUTE = "description"; 080 /** Constants for URL metadata. */ 081 private static final String __URL_ATTRIBUTE = "url"; 082 /** Constants for color metadata. */ 083 private static final String __COLOR_ATTRIBUTE = "color"; 084 /** Constants for skin metadata. */ 085 private static final String __SKIN_ATTRIBUTE = "skin"; 086 /** Constants for restricted page policy metadata. */ 087 private static final String __DISPLAY_RESTRICTED_PAGES_ATTRIBUTE = "display-restricted-pages"; 088 /** Metadata name for project's site illustration */ 089 private static final String __ILLUSTRATION_ATTRIBUTE = "illustration"; 090 /** Constants for sitemaps node name. */ 091 private static final String __SITEMAPS_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":sitemaps"; 092 /** Constants for contents node name. */ 093 private static final String __CONTENTS_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":contents"; 094 /** Constants for resources node name. */ 095 private static final String __RESOURCES_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":resources"; 096 /** Constants for plugins node name. */ 097 private static final String __PLUGINS_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":plugins"; 098 099 /** 100 * Creates a {@link Site}. 101 * @param node the node backing this {@link AmetysObject}. 102 * @param parentPath the parent path in the Ametys hierarchy. 103 * @param factory the {@link SiteFactory} which creates the AmetysObject. 104 */ 105 public Site(Node node, String parentPath, SiteFactory factory) 106 { 107 super(node, parentPath, factory); 108 } 109 110 /** 111 * Retrieves the title. 112 * @return the title. 113 * @throws AmetysRepositoryException if an error occurs. 114 */ 115 public String getTitle() throws AmetysRepositoryException 116 { 117 return (String) getValue(__TITLE_ATTRIBUTE); 118 } 119 120 /** 121 * Retrieves the type. 122 * @return the type. 123 * @throws AmetysRepositoryException if an error occurs. 124 */ 125 public String getType() throws AmetysRepositoryException 126 { 127 try 128 { 129 // return default site type for null metadata for 3.0 compatibility 130 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 131 return repositoryData.getString(__TYPE_ATTRIBUTE); 132 } 133 catch (UnknownDataException me) 134 { 135 return SiteType.DEFAULT_SITE_TYPE_ID; 136 } 137 } 138 139 140 /** 141 * Set the type. 142 * @param type the type. 143 * @throws AmetysRepositoryException if another error occurs. 144 */ 145 public void setType(String type) throws AmetysRepositoryException 146 { 147 ModifiableRepositoryData repositoryData = new JCRRepositoryData(getNode()); 148 repositoryData.setValue(__TYPE_ATTRIBUTE, type); 149 } 150 151 /** 152 * Set the title. 153 * @param title the title. 154 * @throws AmetysRepositoryException if another error occurs. 155 */ 156 public void setTitle(String title) throws AmetysRepositoryException 157 { 158 setValue(__TITLE_ATTRIBUTE, title); 159 } 160 161 /** 162 * Retrieves the description. 163 * @return the description. 164 * @throws AmetysRepositoryException if an error occurs. 165 */ 166 public String getDescription() throws AmetysRepositoryException 167 { 168 return getValue(__DESCRIPTION_ATTRIBUTE); 169 } 170 171 /** 172 * Set the description. 173 * @param description the description. 174 * @throws AmetysRepositoryException if an error occurs. 175 */ 176 public void setDescription(String description) throws AmetysRepositoryException 177 { 178 setValue(__DESCRIPTION_ATTRIBUTE, description); 179 } 180 181 /** 182 * Retrieves the main url. 183 * @return the main url. 184 * @throws AmetysRepositoryException if an error occurs. 185 */ 186 public String getUrl() throws AmetysRepositoryException 187 { 188 String[] aliases = getUrlAliases(); 189 190 return aliases != null && aliases.length > 0 ? aliases[0] : null; 191 } 192 193 /** 194 * Retrieves the url aliases. 195 * @return the url. 196 * @throws AmetysRepositoryException if an error occurs. 197 */ 198 public String[] getUrlAliases() throws AmetysRepositoryException 199 { 200 String url = getValue(__URL_ATTRIBUTE); 201 if (url == null) 202 { 203 return new String[0]; 204 } 205 else 206 { 207 String[] aliases = StringUtils.split(url, ','); 208 209 for (int i = 0; i < aliases.length; i++) 210 { 211 aliases[i] = aliases[i].trim(); 212 } 213 214 return aliases; 215 } 216 } 217 218 /** 219 * Set the url. 220 * @param url the url. 221 * @throws AmetysRepositoryException if an error occurs. 222 */ 223 public void setUrl(String url) throws AmetysRepositoryException 224 { 225 setValue(__URL_ATTRIBUTE, url); 226 } 227 228 /** 229 * Retrieves the color 230 * @return The color. 231 * @throws AmetysRepositoryException if an error occurs. 232 */ 233 public String getColor() throws AmetysRepositoryException 234 { 235 return getValue(__COLOR_ATTRIBUTE, false, StringUtils.EMPTY); 236 } 237 238 239 /** 240 * Retrieves a sitemap. 241 * @param sitemapName the sitemap name. 242 * @return the sitemap found. 243 * @throws AmetysRepositoryException if an error occurs. 244 * @throws UnknownAmetysObjectException if the object does not exist. 245 */ 246 public Sitemap getSitemap(String sitemapName) throws AmetysRepositoryException, UnknownAmetysObjectException 247 { 248 return ((TraversableAmetysObject) getChild(__SITEMAPS_NODE_NAME)).getChild(sitemapName); 249 } 250 251 /** 252 * Determines the existence of a sitemap 253 * @param lang The sitemap language 254 * @return true if the sitemap exists 255 * @throws AmetysRepositoryException if an error occurred 256 */ 257 public boolean hasSitemap (String lang) throws AmetysRepositoryException 258 { 259 DefaultTraversableAmetysObject<?> sitemaps = getChild(__SITEMAPS_NODE_NAME); 260 return sitemaps.hasChild(lang); 261 } 262 263 /** 264 * Add a sitemap language to the site 265 * @param lang The sitemap language 266 * @return The created sitemap 267 * @throws AmetysRepositoryException if an error occurred or if the sitemap already exists 268 */ 269 public Sitemap addSitemap (String lang) throws AmetysRepositoryException 270 { 271 DefaultTraversableAmetysObject<?> sitemaps = getChild(__SITEMAPS_NODE_NAME); 272 if (sitemaps.hasChild(lang)) 273 { 274 throw new AmetysRepositoryException ("The sitemap '" + lang + "' already exists"); 275 } 276 return sitemaps.createChild(lang, "ametys:sitemap"); 277 } 278 279 /** 280 * Retrieves sitemaps. 281 * @return the sitemaps or an empty {@link AmetysObjectIterable}. 282 * @throws AmetysRepositoryException if an error occurs. 283 */ 284 public AmetysObjectIterable<Sitemap> getSitemaps() throws AmetysRepositoryException 285 { 286 return ((TraversableAmetysObject) getChild(__SITEMAPS_NODE_NAME)).getChildren(); 287 } 288 289 /** 290 * Retrieves root contents. 291 * @return the root for contents 292 * @throws AmetysRepositoryException if an error occurs. 293 */ 294 public ModifiableTraversableAmetysObject getRootContents() throws AmetysRepositoryException 295 { 296 return getChild(__CONTENTS_NODE_NAME); 297 } 298 299 /** 300 * Retrieves contents. 301 * @return the contents or an empty {@link AmetysObjectIterable}. 302 * @throws AmetysRepositoryException if an error occurs. 303 */ 304 public AmetysObjectIterable<Content> getContents() throws AmetysRepositoryException 305 { 306 return ((TraversableAmetysObject) getChild(__CONTENTS_NODE_NAME)).getChildren(); 307 } 308 309 /** 310 * Retrieves root resources. 311 * @return the root for resources 312 * @throws AmetysRepositoryException if an error occurs. 313 */ 314 public ModifiableTraversableAmetysObject getRootResources() throws AmetysRepositoryException 315 { 316 return getChild(__RESOURCES_NODE_NAME); 317 } 318 319 /** 320 * Retrieves resources. 321 * @return the resources or an empty {@link AmetysObjectIterable}. 322 * @throws AmetysRepositoryException if an error occurs. 323 */ 324 public AmetysObjectIterable<AmetysObject> getResources() throws AmetysRepositoryException 325 { 326 return ((TraversableAmetysObject) getChild(__RESOURCES_NODE_NAME)).getChildren(); 327 } 328 329 /** 330 * Get the root for plugins 331 * @return the root for plugins 332 * @throws AmetysRepositoryException if an error occurs. 333 */ 334 public ModifiableTraversableAmetysObject getRootPlugins () throws AmetysRepositoryException 335 { 336 return (ModifiableTraversableAmetysObject) getChild(__PLUGINS_NODE_NAME); 337 } 338 339 /** 340 * Set the skin id for this site. 341 * @param skinId ths skin id 342 */ 343 public void setSkinId(String skinId) 344 { 345 setValue(__SKIN_ATTRIBUTE, skinId); 346 } 347 348 /** 349 * Returns the skin id for this site. 350 * @return the skin id for this site. 351 */ 352 public String getSkinId() 353 { 354 return getValue(__SKIN_ATTRIBUTE); 355 } 356 357 /** 358 * Returns the parent site, if any. 359 * @return the parent site, if any. 360 */ 361 public Site getParentSite() 362 { 363 AmetysObject parent = getParent(); 364 if (SiteManager.ROOT_SITES_PATH.equals(parent.getPath())) 365 { 366 return null; 367 } 368 return parent.getParent(); 369 } 370 371 /** 372 * Returns the site path 373 * @return the site path 374 */ 375 public String getSitePath () 376 { 377 String path = getName(); 378 379 Site parentSite = getParentSite(); 380 while (parentSite != null) 381 { 382 path = parentSite.getName() + "/" + path; 383 parentSite = parentSite.getParentSite(); 384 } 385 386 return path; 387 } 388 389 /** 390 * Returns the {@link RestrictedPagePolicy} associated with this Site. 391 * @return the {@link RestrictedPagePolicy} associated with this Site. 392 */ 393 public RestrictedPagePolicy getRestrictedPagePolicy() 394 { 395 Boolean displayRestrictedPages = getValue(__DISPLAY_RESTRICTED_PAGES_ATTRIBUTE, false, true); 396 397 if (displayRestrictedPages) 398 { 399 return RestrictedPagePolicy.DISPLAYED; 400 } 401 else 402 { 403 return RestrictedPagePolicy.HIDDEN; 404 } 405 } 406 407 /** 408 * Returns the named {@link Site}. 409 * @param siteName the site name. 410 * @return the named {@link Site}. 411 * @throws UnknownAmetysObjectException if the named site does not exist. 412 */ 413 public Site getSite (String siteName) throws UnknownAmetysObjectException 414 { 415 DefaultTraversableAmetysObject root = getChild(SiteManager.ROOT_SITES); 416 return (Site) root.getChild(siteName); 417 } 418 419 /** 420 * Return true if the given site if an ancestor 421 * @param siteName the site name. 422 * @return true if the given site if an ancestor 423 */ 424 public boolean hasAncestor (String siteName) 425 { 426 // Is a parent ? 427 Site parentSite = getParentSite(); 428 while (parentSite != null) 429 { 430 if (parentSite.getName().equals(siteName)) 431 { 432 return true; 433 } 434 parentSite = parentSite.getParentSite(); 435 } 436 437 return false; 438 } 439 440 /** 441 * Return true if the given site if a descendant 442 * @param siteName the site name. 443 * @return true if the given site if a descendant 444 */ 445 public boolean hasDescendant (String siteName) 446 { 447 if (hasChildrenSite(siteName)) 448 { 449 return true; 450 } 451 452 AmetysObjectIterable<Site> childrenSites = getChildrenSites(); 453 for (Site child : childrenSites) 454 { 455 if (child.hasDescendant (siteName)) 456 { 457 return true; 458 } 459 } 460 461 return false; 462 } 463 464 /** 465 * Returns true if the given site exists. 466 * @param siteName the site name. 467 * @return true if the given site exists. 468 */ 469 public boolean hasChildrenSite(String siteName) 470 { 471 try 472 { 473 DefaultTraversableAmetysObject root = getChild(SiteManager.ROOT_SITES); 474 return root.hasChild(siteName); 475 } 476 catch (UnknownAmetysObjectException e) 477 { 478 return false; 479 } 480 } 481 482 /** 483 * Returns the sites names. 484 * @return the sites names. 485 */ 486 public Collection<String> getChildrenSiteNames() 487 { 488 AmetysObjectIterable<Site> sites = getChildrenSites(); 489 490 ArrayList<String> result = new ArrayList<>(); 491 492 for (Site site : sites) 493 { 494 result.add(site.getName()); 495 } 496 497 return result; 498 } 499 500 /** 501 * Returns all children sites, or empty if none. 502 * @return all children sites, or empty if none. 503 * @throws AmetysRepositoryException if an error occurs 504 */ 505 public AmetysObjectIterable<Site> getChildrenSites() throws AmetysRepositoryException 506 { 507 try 508 { 509 TraversableAmetysObject rootSites = getChild(SiteManager.ROOT_SITES); 510 return rootSites.getChildren(); 511 } 512 catch (UnknownAmetysObjectException e) 513 { 514 return new CollectionIterable<>(new ArrayList<Site>()); 515 } 516 } 517 518 /** 519 * Returns the illustration of the site 520 * @return the illustration of the site 521 */ 522 public BinaryMetadata getIllustration() 523 { 524 //TODO NEWATTRIBUTEAPI: Rewrite when BinaryRepositoryElementType exists 525 try 526 { 527 return getMetadataHolder().getBinaryMetadata(__ILLUSTRATION_ATTRIBUTE); 528 } 529 catch (UnknownMetadataException e) 530 { 531 return null; 532 } 533 } 534 535 /** 536 * Set the illustration of the site 537 * @param is The input stream of the illustration 538 * @param mimeType The mimetype of the illustration 539 * @param filename The filename of the illustration 540 * @param lastModifiedDate The last modified date of the illustration 541 */ 542 public void setIllustration(InputStream is, String mimeType, String filename, Date lastModifiedDate) 543 { 544 //TODO NEWATTRIBUTEAPI: Rewrite when BinaryRepositoryElementType exists 545 if (getMetadataHolder().hasMetadata(__ILLUSTRATION_ATTRIBUTE)) 546 { 547 getMetadataHolder().removeMetadata(__ILLUSTRATION_ATTRIBUTE); 548 } 549 if (is != null) 550 { 551 ModifiableBinaryMetadata illustration = getMetadataHolder().getBinaryMetadata(__ILLUSTRATION_ATTRIBUTE, true); 552 illustration.setInputStream(is); 553 Optional.ofNullable(mimeType).ifPresent(illustration::setMimeType); 554 Optional.ofNullable(filename).ifPresent(illustration::setFilename); 555 Optional.ofNullable(lastModifiedDate).ifPresent(illustration::setLastModified); 556 } 557 } 558 559 @Override 560 public void remove() throws AmetysRepositoryException, RepositoryIntegrityViolationException 561 { 562 for (Content content : getContents()) 563 { 564 // FIXME API check if not modifiable 565 if (content instanceof ModifiableContent) 566 { 567 ((ModifiableContent) content).remove(); 568 } 569 } 570 571 for (Site site : getChildrenSites()) 572 { 573 site.remove(); 574 } 575 576 super.remove(); 577 } 578 579 @Override 580 public boolean canMoveTo(AmetysObject newParent) throws AmetysRepositoryException 581 { 582 return newParent instanceof Site || newParent instanceof AmetysObjectCollection; 583 } 584 585 @Override 586 public void moveTo(AmetysObject newParent, boolean renameIfExist) throws AmetysRepositoryException, RepositoryIntegrityViolationException 587 { 588 Node node = getNode(); 589 590 try 591 { 592 if (getParent().equals(newParent)) 593 { 594 // Do nothing 595 } 596 else 597 { 598 if (!canMoveTo(newParent)) 599 { 600 throw new AmetysRepositoryException("Site " + toString() + " can only be moved to a site or the root of site"); 601 } 602 603 String name = node.getName(); 604 605 try 606 { 607 // Move node 608 if (newParent instanceof Site) 609 { 610 Site parentSite = (Site) newParent; 611 if (!parentSite.hasChild("ametys-internal:sites")) 612 { 613 parentSite.createChild("ametys-internal:sites", "ametys:sites"); 614 } 615 node.getSession().move(node.getPath(), parentSite.getNode().getPath() + "/ametys-internal:sites/" + name); 616 } 617 else 618 { 619 Node contextNode = ((AmetysObjectCollection) newParent).getNode(); 620 String[] hash = _getHashedPath (name); 621 for (String hashPart : hash) 622 { 623 if (!contextNode.hasNode(hashPart)) 624 { 625 contextNode = contextNode.addNode(hashPart, AmetysObjectCollectionFactory.COLLECTION_ELEMENT_NODETYPE); 626 } 627 else 628 { 629 contextNode = contextNode.getNode(hashPart); 630 } 631 } 632 node.getSession().move(node.getPath(), contextNode.getPath() + "/" + name); 633 } 634 635 } 636 catch (ItemExistsException e) 637 { 638 throw new AmetysRepositoryException(String.format("A site already exists for in parent path '%s'", newParent.getPath() + "/" + name), e); 639 } 640 641 // Invalidate parent path as the parent path has changed 642 _invalidateParentPath(); 643 } 644 } 645 catch (RepositoryException e) 646 { 647 throw new AmetysRepositoryException(String.format("Unable to move site '%s' to node '%s'", this, newParent.getId()), e); 648 } 649 } 650 651 @Override 652 public Site copyTo(ModifiableTraversableAmetysObject parent, String name) throws AmetysRepositoryException 653 { 654 try 655 { 656 Node parentNode = _getParentNode(parent, name); 657 Node clonedSite = parentNode.addNode(name, "ametys:site"); 658 659 parent.saveChanges(); 660 661 // Copy properties 662 PropertyIterator properties = getNode().getProperties("ametys:*"); 663 while (properties.hasNext()) 664 { 665 Property property = (Property) properties.next(); 666 667 if (property.getDefinition().isMultiple()) 668 { 669 clonedSite.setProperty(property.getName(), property.getValues()); 670 } 671 else 672 { 673 clonedSite.setProperty(property.getName(), property.getValue()); 674 } 675 } 676 677 // Copy resources 678 Node resourcesNode = getNode().getNode("ametys-internal:resources"); 679 getNode().getSession().getWorkspace().copy(resourcesNode.getPath(), clonedSite.getPath() + "/" + "ametys-internal:resources"); 680 681 // Copy plugins (ametys-internal:plugins is auto-created) 682 NodeIterator plugins = getNode().getNode("ametys-internal:plugins").getNodes(); 683 while (plugins.hasNext()) 684 { 685 Node pluginNode = (Node) plugins.next(); 686 getNode().getSession().getWorkspace().copy(pluginNode.getPath(), clonedSite.getPath() + "/ametys-internal:plugins/" + pluginNode.getName()); 687 } 688 689 // Copy sitemaps (ametys-internal:sitemaps is auto-created) 690 NodeIterator sitemaps = getNode().getNode("ametys-internal:sitemaps").getNodes(); 691 while (sitemaps.hasNext()) 692 { 693 Node sitemapNode = (Node) sitemaps.next(); 694 getNode().getSession().getWorkspace().copy(sitemapNode.getPath(), clonedSite.getPath() + "/ametys-internal:sitemaps/" + sitemapNode.getName()); 695 } 696 697 // Copy contents (ametys-internal:contents is auto-created) 698 NodeIterator contents = getNode().getNode("ametys-internal:contents").getNodes(); 699 while (contents.hasNext()) 700 { 701 Node contentNode = (Node) contents.next(); 702 getNode().getSession().getWorkspace().copy(contentNode.getPath(), clonedSite.getPath() + "/ametys-internal:contents/" + contentNode.getName()); 703 } 704 705 if (parent instanceof Site) 706 { 707 return ((Site) parent).getSite(name); 708 } 709 else 710 { 711 return parent.getChild(name); 712 } 713 } 714 catch (RepositoryException e) 715 { 716 throw new AmetysRepositoryException(e); 717 } 718 } 719 720 @Override 721 public AmetysObject copyTo(ModifiableTraversableAmetysObject parent, String name, List<String> restrictTo) throws AmetysRepositoryException 722 { 723 return copyTo(parent, name); 724 } 725 726 private Node _getParentNode (ModifiableTraversableAmetysObject parent, String siteName) throws AmetysRepositoryException 727 { 728 try 729 { 730 if (parent instanceof Site) 731 { 732 return ((Site) parent).getNode().getNode("ametys-internal:sites"); 733 } 734 else if (parent instanceof SimpleAmetysObject) 735 { 736 String[] hash = _getHashedPath(siteName); 737 738 Node contextNode = ((SimpleAmetysObject) parent).getNode(); 739 740 for (String hashPart : hash) 741 { 742 if (!contextNode.hasNode(hashPart)) 743 { 744 contextNode = contextNode.addNode(hashPart, AmetysObjectCollectionFactory.COLLECTION_ELEMENT_NODETYPE); 745 } 746 else 747 { 748 contextNode = contextNode.getNode(hashPart); 749 } 750 } 751 752 return contextNode; 753 } 754 } 755 catch (RepositoryException e) 756 { 757 throw new AmetysRepositoryException("Unable to create child: " + siteName, e); 758 } 759 760 throw new AmetysRepositoryException("Unable to get JCR node of object : " + parent.getId()); 761 } 762 763 private String[] _getHashedPath(String name) 764 { 765 long hash = Math.abs(HashUtil.hash(name)); 766 String hashStr = Long.toString(hash, 16); 767 hashStr = StringUtils.leftPad(hashStr, 4, '0'); 768 769 return new String[]{hashStr.substring(0, 2), hashStr.substring(2, 4)}; 770 } 771 772 @Override 773 public void orderBefore(AmetysObject siblingNode) throws AmetysRepositoryException 774 { 775 throw new UnsupportedOperationException("Site ordering is not supported"); 776 } 777 778 public ModifiableModelAwareDataHolder getDataHolder() 779 { 780 ModifiableRepositoryData repositoryData = new JCRRepositoryData(getNode()); 781 SiteConfigurationExtensionPoint siteConfigurationTypeExtensionPoint = _getFactory().getSiteConfigurationTypeExtensionPoint(); 782 return new DefaultModifiableModelAwareDataHolder(repositoryData, siteConfigurationTypeExtensionPoint); 783 } 784}