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