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