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