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