001/* 002 * Copyright 2016 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.plugins.userdirectory; 017 018import java.text.Normalizer; 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.HashSet; 022import java.util.LinkedHashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Objects; 026import java.util.Set; 027import java.util.SortedSet; 028import java.util.TreeSet; 029import java.util.regex.Matcher; 030import java.util.regex.Pattern; 031import java.util.stream.Collectors; 032 033import org.apache.avalon.framework.activity.Initializable; 034import org.apache.avalon.framework.component.Component; 035import org.apache.avalon.framework.service.ServiceException; 036import org.apache.avalon.framework.service.ServiceManager; 037import org.apache.avalon.framework.service.Serviceable; 038import org.apache.commons.lang3.StringUtils; 039 040import org.ametys.cms.contenttype.ContentType; 041import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 042import org.ametys.cms.repository.Content; 043import org.ametys.cms.repository.ContentTypeExpression; 044import org.ametys.cms.repository.LanguageExpression; 045import org.ametys.core.cache.AbstractCacheManager; 046import org.ametys.core.cache.Cache; 047import org.ametys.plugins.core.impl.cache.AbstractCacheKey; 048import org.ametys.plugins.repository.AmetysObjectIterable; 049import org.ametys.plugins.repository.AmetysObjectResolver; 050import org.ametys.plugins.repository.AmetysRepositoryException; 051import org.ametys.plugins.repository.UnknownAmetysObjectException; 052import org.ametys.plugins.repository.provider.WorkspaceSelector; 053import org.ametys.plugins.repository.query.QueryHelper; 054import org.ametys.plugins.repository.query.SortCriteria; 055import org.ametys.plugins.repository.query.expression.AndExpression; 056import org.ametys.plugins.repository.query.expression.Expression; 057import org.ametys.plugins.repository.query.expression.Expression.Operator; 058import org.ametys.plugins.repository.query.expression.OrExpression; 059import org.ametys.plugins.repository.query.expression.StringExpression; 060import org.ametys.plugins.repository.query.expression.VirtualFactoryExpression; 061import org.ametys.plugins.userdirectory.page.VirtualUserDirectoryPageFactory; 062import org.ametys.runtime.i18n.I18nizableText; 063import org.ametys.runtime.plugin.component.AbstractLogEnabled; 064import org.ametys.web.repository.page.Page; 065import org.ametys.web.repository.page.PageQueryHelper; 066 067/** 068 * Component providing methods to retrieve user directory virtual pages, such as the user directory root, 069 * transitional page and user page. 070 */ 071public class UserDirectoryPageHandler extends AbstractLogEnabled implements Component, Serviceable, Initializable 072{ 073 /** The avalon role. */ 074 public static final String ROLE = UserDirectoryPageHandler.class.getName(); 075 076 /** The data name for the content type of the user directory */ 077 public static final String CONTENT_TYPE_DATA_NAME = "user-directory-root-contenttype"; 078 /** The data name for the users' view to use */ 079 public static final String USER_VIEW_NAME = "user-directory-root-view-name"; 080 /** The data name for the classification attribute of the user directory */ 081 public static final String CLASSIFICATION_ATTRIBUTE_DATA_NAME = "user-directory-root-classification-metadata"; 082 /** The data name for the depth of the user directory */ 083 public static final String DEPTH_DATA_NAME = "user-directory-root-depth"; 084 /** The parent content type id */ 085 public static final String ABSTRACT_USER_CONTENT_TYPE = "org.ametys.plugins.userdirectory.Content.user"; 086 /** The user directory root pages cache id */ 087 protected static final String ROOT_PAGES_CACHE = UserDirectoryPageHandler.class.getName() + "$rootPageIds"; 088 /** The user directory transitional pages cache id */ 089 protected static final String TRANSITIONAL_PAGES_CACHE = UserDirectoryPageHandler.class.getName() + "$transitionalPageIds"; 090 /** The user directory user pages cache id */ 091 protected static final String USER_PAGES_CACHE = UserDirectoryPageHandler.class.getName() + "$userPages"; 092 093 /** The workspace selector. */ 094 protected WorkspaceSelector _workspaceSelector; 095 /** The ametys object resolver. */ 096 protected AmetysObjectResolver _resolver; 097 /** The extension point for content types */ 098 protected ContentTypeExtensionPoint _contentTypeEP; 099 /** The cache manager */ 100 protected AbstractCacheManager _abstractCacheManager; 101 102 @Override 103 public void service(ServiceManager manager) throws ServiceException 104 { 105 _workspaceSelector = (WorkspaceSelector) manager.lookup(WorkspaceSelector.ROLE); 106 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 107 _contentTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 108 _abstractCacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE); 109 } 110 111 @Override 112 public void initialize() throws Exception 113 { 114 _abstractCacheManager.createMemoryCache(ROOT_PAGES_CACHE, 115 new I18nizableText("plugin.user-directory", "PLUGINS_USER_DIRECTORY_CACHE_ROOT_PAGES_LABEL"), 116 new I18nizableText("plugin.user-directory", "PLUGINS_USER_DIRECTORY_CACHE_ROOT_PAGES_DESCRIPTION"), 117 true, 118 null); 119 _abstractCacheManager.createMemoryCache(TRANSITIONAL_PAGES_CACHE, 120 new I18nizableText("plugin.user-directory", "PLUGINS_USER_DIRECTORY_CACHE_TRANSITIONAL_PAGES_LABEL"), 121 new I18nizableText("plugin.user-directory", "PLUGINS_USER_DIRECTORY_CACHE_TRANSITIONAL_PAGES_DESCRIPTION"), 122 true, 123 null); 124 _abstractCacheManager.createMemoryCache(USER_PAGES_CACHE, 125 new I18nizableText("plugin.user-directory", "PLUGINS_USER_DIRECTORY_CACHE_USER_PAGES_LABEL"), 126 new I18nizableText("plugin.user-directory", "PLUGINS_USER_DIRECTORY_CACHE_USER_PAGES_DESCRIPTION"), 127 true, 128 null); 129 } 130 131 /** 132 * Gets the user directory root pages from the given content type id, whatever the site. 133 * @param contentTypeId The content type id 134 * @return the user directory root pages. 135 * @throws AmetysRepositoryException if an error occured. 136 */ 137 public Set<Page> getUserDirectoryRootPages(String contentTypeId) throws AmetysRepositoryException 138 { 139 Expression expression = new VirtualFactoryExpression(VirtualUserDirectoryPageFactory.class.getName()); 140 Expression contentTypeExp = new StringExpression(CONTENT_TYPE_DATA_NAME, Operator.EQ, contentTypeId); 141 142 AndExpression andExp = new AndExpression(expression, contentTypeExp); 143 144 String query = PageQueryHelper.getPageXPathQuery(null, null, null, andExp, null); 145 146 AmetysObjectIterable<Page> pages = _resolver.query(query); 147 148 return pages.stream().collect(Collectors.toSet()); 149 } 150 151 /** 152 * Gets the user directory root page of a specific content type. 153 * @param siteName The site name 154 * @param sitemapName The sitemap 155 * @param contentTypeId The content type id 156 * @return the user directory root pages. 157 * @throws AmetysRepositoryException if an error occured. 158 */ 159 public Page getUserDirectoryRootPage(String siteName, String sitemapName, String contentTypeId) throws AmetysRepositoryException 160 { 161 String contentTypeIdToCompare = contentTypeId != null ? contentTypeId : ""; 162 163 for (Page userDirectoryRootPage : getUserDirectoryRootPages(siteName, sitemapName)) 164 { 165 if (contentTypeIdToCompare.equals(getContentTypeId(userDirectoryRootPage))) 166 { 167 return userDirectoryRootPage; 168 } 169 } 170 171 return null; 172 } 173 174 /** 175 * Gets the user directory root pages. 176 * @param siteName The site name 177 * @param sitemapName The sitemap 178 * @return the user directory root pages. 179 * @throws AmetysRepositoryException if an error occured. 180 */ 181 public Set<Page> getUserDirectoryRootPages(String siteName, String sitemapName) throws AmetysRepositoryException 182 { 183 Set<Page> rootPages = new HashSet<>(); 184 185 String workspace = _workspaceSelector.getWorkspace(); 186 187 Cache<RootPageCacheKey, Set<String>> cache = getRootPagesCache(); 188 189 RootPageCacheKey key = RootPageCacheKey.of(workspace, siteName, sitemapName); 190 if (cache.hasKey(key)) 191 { 192 rootPages = cache.get(key).stream() 193 .map(this::_resolvePage) 194 .filter(Objects::nonNull) 195 .collect(Collectors.toSet()); 196 } 197 else 198 { 199 rootPages = _getUserDirectoryRootPages(siteName, sitemapName); 200 Set<String> userDirectoryRootPageIds = rootPages.stream() 201 .map(Page::getId) 202 .collect(Collectors.toSet()); 203 cache.put(key, userDirectoryRootPageIds); 204 } 205 206 return rootPages; 207 } 208 209 private Page _resolvePage(String pageId) 210 { 211 try 212 { 213 return _resolver.resolveById(pageId); 214 } 215 catch (UnknownAmetysObjectException e) 216 { 217 // The page stored in cache may have been deleted 218 return null; 219 } 220 } 221 222 /** 223 * Get the user directory root pages, without searching in the cache. 224 * @param siteName the current site. 225 * @param sitemapName the sitemap name. 226 * @return the user directory root pages 227 * @throws AmetysRepositoryException if an error occured. 228 */ 229 protected Set<Page> _getUserDirectoryRootPages(String siteName, String sitemapName) throws AmetysRepositoryException 230 { 231 Expression expression = new VirtualFactoryExpression(VirtualUserDirectoryPageFactory.class.getName()); 232 233 String query = PageQueryHelper.getPageXPathQuery(siteName, sitemapName, null, expression, null); 234 235 AmetysObjectIterable<Page> pages = _resolver.query(query); 236 237 return pages.stream().collect(Collectors.toSet()); 238 } 239 240 /** 241 * Gets the depth of the user directory root page 242 * @param rootPage The user directory root page 243 * @return the depth of the user directory root page 244 */ 245 public int getDepth(Page rootPage) 246 { 247 return Math.toIntExact(rootPage.getValue(DEPTH_DATA_NAME)); 248 } 249 250 /** 251 * Gets the name of the classification attribute 252 * @param rootPage The user directory root page 253 * @return the name of the classification attribute 254 */ 255 public String getClassificationAttribute(Page rootPage) 256 { 257 return rootPage.getValue(CLASSIFICATION_ATTRIBUTE_DATA_NAME); 258 } 259 260 /** 261 * Gets the content type id 262 * @param rootPage The user directory root page 263 * @return the content type id 264 */ 265 public String getContentTypeId(Page rootPage) 266 { 267 return rootPage.getValue(CONTENT_TYPE_DATA_NAME); 268 } 269 270 /** 271 * Gets the content type 272 * @param rootPage The user directory root page 273 * @return the content type 274 */ 275 public ContentType getContentType(Page rootPage) 276 { 277 String contentTypeId = getContentTypeId(rootPage); 278 return StringUtils.isNotBlank(contentTypeId) ? _contentTypeEP.getExtension(contentTypeId) : null; 279 } 280 281 /** 282 * Gets the value of the classification attribute for the given content, transformed for building tree hierarchy 283 * <br>The transformation takes the lower-case of all characters, removes non-alphanumeric characters, 284 * and takes the first characters to not have a string with a size bigger than the depth 285 * <br>For instance, if the value for the content is "Aéa Foo-bar" and the depth is 7, 286 * then this method will return "aeafoob" 287 * @param rootPage The user directory root page 288 * @param content The content 289 * @return the transformed value of the classification attribute for the given content. Can be null 290 */ 291 public String getTransformedClassificationValue(Page rootPage, Content content) 292 { 293 String attribute = getClassificationAttribute(rootPage); 294 int depth = getDepth(rootPage); 295 296 // 1) get value of the classification attribute 297 String classification = content.getValue(attribute); 298 299 if (classification == null) 300 { 301 // The classification does not exists for the content 302 getLogger().info("The classification attribute '{}' does not exist for the content {}", attribute, content); 303 return null; 304 } 305 306 try 307 { 308 // 2) replace special character 309 // 3) remove '-' characters 310 311 // FIXME CMS-5758 FilterNameHelper.filterName do not authorized name with numbers only. 312 // So code of FilterNamehelper is temporarily duplicated here with a slightly modified RegExp 313// String transformedValue = FilterNameHelper.filterName(classification).replace("-", ""); 314 String transformedValue = _filterName(classification).replace("-", ""); 315 316 // 4) only keep 'depth' first characters (if depth = 3, "de" becomes "de", "debu" becomes "deb", etc.) 317 return StringUtils.substring(transformedValue, 0, depth); 318 } 319 catch (IllegalArgumentException e) 320 { 321 // The value of the classification attribute is not valid 322 getLogger().warn("The classification attribute '{}' does not have a valid value ({}) for the content {}", attribute, classification, content); 323 return null; 324 } 325 } 326 327 private String _filterName(String name) 328 { 329 Pattern pattern = Pattern.compile("^()[0-9-_]*[a-z0-9].*$"); 330 // Use lower case 331 // then remove accents 332 // then replace contiguous spaces with one dash 333 // and finally remove non-alphanumeric characters except - 334 String filteredName = Normalizer.normalize(name.toLowerCase(), Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]", "").trim(); 335 filteredName = filteredName.replaceAll("œ", "oe").replaceAll("æ", "ae").replaceAll(" +", "-").replaceAll("[^\\w-]", "-").replaceAll("-+", "-"); 336 337 Matcher m = pattern.matcher(filteredName); 338 if (!m.matches()) 339 { 340 throw new IllegalArgumentException(filteredName + " doesn't match the expected regular expression : " + pattern.pattern()); 341 } 342 343 filteredName = filteredName.substring(m.end(1)); 344 345 // Remove characters '-' and '_' at the start and the end of the string 346 return StringUtils.strip(filteredName, "-_"); 347 } 348 349 /** 350 * Get all transitional page child from page name 351 * @param rootPage the root page 352 * @param pagePath the page path 353 * @return all transitional page child from page name 354 */ 355 public SortedSet<String> getTransitionalPagesName(Page rootPage, String pagePath) 356 { 357 String cachePagePath = getName(pagePath); 358 359 String workspace = _workspaceSelector.getWorkspace(); 360 String site = rootPage.getSiteName(); 361 String contentType = getContentTypeId(rootPage); 362 _initializeCaches(rootPage, workspace, contentType); 363 PageCacheKey key = PageCacheKey.of(workspace, contentType, site, rootPage.getSitemapName(), cachePagePath); 364 Cache<PageCacheKey, SortedSet<String>> transitionalPagesCache = getTransitionalPagesCache(); 365 return transitionalPagesCache.get(key, k -> new TreeSet<>()); 366 } 367 368 private void _initializeCaches(Page rootPage, String workspace, String contentType) 369 { 370 String language = rootPage.getSitemapName(); 371 String site = rootPage.getSiteName(); 372 373 374 // Get transitional page cache 375 PageCacheKey transitionalPagesCacheKey = PageCacheKey.of(workspace, contentType, site, language, null); 376 Cache<PageCacheKey, SortedSet<String>> transitionalPagesCache = getTransitionalPagesCache(); 377 378 // Get user page cache 379 PageCacheKey userPagesCacheKey = PageCacheKey.of(workspace, contentType, site, language, null); 380 Cache<PageCacheKey, Map<String, String>> userPagesCache = getUserPagesCache(); 381 382 if (transitionalPagesCache.hasKey(transitionalPagesCacheKey) && userPagesCache.hasKey(userPagesCacheKey)) 383 { 384 // Both caches are initialized 385 getLogger().debug("TransitionalPageCache and UserPageCache are initialized for workspace '{}' and content type '{}' and language '{}'", workspace, contentType, language); 386 return; 387 } 388 389 // Get all contents which will appear in the sitemap 390 AmetysObjectIterable<Content> contents = getContentsForRootPage(rootPage); 391 392 // Get their classification attribute value 393 Map<Content, String> transformedValuesByContent = new LinkedHashMap<>(); 394 for (Content content : contents) 395 { 396 String value = getTransformedClassificationValue(rootPage, content); 397 if (value != null) 398 { 399 transformedValuesByContent.put(content, value); 400 } 401 } 402 403 if (!transitionalPagesCache.hasKey(transitionalPagesCacheKey)) 404 { 405 Set<String> transformedValues = new HashSet<>(transformedValuesByContent.values()); 406 _buildTransitionalPageCache(transformedValues, workspace, contentType, site, language); 407 getLogger().info("Transitional page cache was built for workspace '{}' and content type '{}' and language '{}'", workspace, contentType, language); 408 } 409 410 if (!userPagesCache.hasKey(userPagesCacheKey)) 411 { 412 int depth = getDepth(rootPage); 413 _buildUserPageCache(transformedValuesByContent, depth, workspace, contentType, site, language); 414 getLogger().info("User page cache was built for workspace '{}' and content type '{}' and language '{}'", workspace, contentType, language); 415 } 416 } 417 418 private void _buildTransitionalPageCache(Set<String> transformedValues, String workspace, String contentTypeId, String siteName, String siteMapName) 419 { 420 Cache<PageCacheKey, SortedSet<String>> transitionalPagesCache = getTransitionalPagesCache(); 421 for (String value : transformedValues) 422 { 423 char[] charArray = value.toCharArray(); 424 for (int i = 0; i < charArray.length; i++) 425 { 426 String lastChar = String.valueOf(charArray[i]); 427 if (i == 0) 428 { 429 // case _root 430 SortedSet<String> root = transitionalPagesCache.get(PageCacheKey.of(workspace, contentTypeId, siteName, siteMapName, "_root"), k -> new TreeSet<>()); 431 if (!root.contains(lastChar)) 432 { 433 root.add(lastChar); 434 } 435 } 436 else 437 { 438 String currentPrefixWithoutLastChar = value.substring(0, i); // if value == "debu", equals to "deb" 439 String currentPathWithoutLastChar = StringUtils.join(currentPrefixWithoutLastChar.toCharArray(), '/'); // if value == "debu", equals to "d/e/b" 440 SortedSet<String> childPageNames = transitionalPagesCache.get(PageCacheKey.of(workspace, contentTypeId, siteName, siteMapName, currentPathWithoutLastChar), k -> new TreeSet<>()); 441 442 if (!childPageNames.contains(lastChar)) 443 { 444 childPageNames.add(lastChar); // if value == "debu", add "u" in childPageNames for key "d/e/b" 445 } 446 } 447 } 448 } 449 } 450 451 private void _buildUserPageCache(Map<Content, String> transformedValuesByContent, int depth, String workspace, String contentTypeId, String siteName, String language) 452 { 453 if (depth == 0) 454 { 455 Map<String, String> rootContents = new LinkedHashMap<>(); 456 for (Content content : transformedValuesByContent.keySet()) 457 { 458 rootContents.put(content.getName(), content.getId()); 459 } 460 getUserPagesCache().put(PageCacheKey.of(workspace, contentTypeId, siteName, language, "_root"), rootContents); 461 return; 462 } 463 464 for (Content content : transformedValuesByContent.keySet()) 465 { 466 String transformedValue = transformedValuesByContent.get(content); 467 for (int i = 0; i < depth; i++) 468 { 469 String currentPrefix = StringUtils.substring(transformedValue, 0, i + 1); 470 String currentPath = StringUtils.join(currentPrefix.toCharArray(), '/'); 471 PageCacheKey userPageKey = PageCacheKey.of(workspace, contentTypeId, siteName, language, currentPath); 472 Map<String, String> contentsForPath = getUserPagesCache().get(userPageKey, k -> new LinkedHashMap<>()); 473 474 String contentName = content.getName(); 475 if (!contentsForPath.containsKey(contentName)) 476 { 477 contentsForPath.put(contentName, content.getId()); 478 } 479 } 480 } 481 } 482 483 /** 484 * Get all user page child from page name 485 * @param rootPage the root page 486 * @param pagePath the page path 487 * @return all user page child from page name 488 */ 489 public Map<String, String> getUserPagesContent(Page rootPage, String pagePath) 490 { 491 String cachePagePath = getName(pagePath); 492 493 String workspace = _workspaceSelector.getWorkspace(); 494 String site = rootPage.getSiteName(); 495 String contentType = getContentTypeId(rootPage); 496 _initializeCaches(rootPage, workspace, contentType); 497 498 return getUserPagesCache().get(PageCacheKey.of(workspace, contentType, site, rootPage.getSitemapName(), cachePagePath), k -> new HashMap<>()); 499 } 500 501 /** 502 * Get the user contents for a given root page 503 * @param rootPage the root page 504 * @return the user contents 505 */ 506 public AmetysObjectIterable<Content> getContentsForRootPage(Page rootPage) 507 { 508 String contentType = getContentTypeId(rootPage); 509 String lang = rootPage.getSitemapName(); 510 511 Set<String> subTypes = _contentTypeEP.getSubTypes(contentType); 512 513 List<Expression> contentTypeExpressions = new ArrayList<>(); 514 contentTypeExpressions.add(new ContentTypeExpression(Operator.EQ, contentType)); 515 for (String subType : subTypes) 516 { 517 contentTypeExpressions.add(new ContentTypeExpression(Operator.EQ, subType)); 518 } 519 520 Expression contentTypeExpression = new OrExpression(contentTypeExpressions.toArray(new Expression[subTypes.size() + 1])); 521 522 Expression finalExpr = new AndExpression(contentTypeExpression, new LanguageExpression(Operator.EQ, lang)); 523 524 SortCriteria sort = new SortCriteria(); 525 sort.addCriterion(Content.ATTRIBUTE_TITLE, true, true); 526 527 String xPathQuery = QueryHelper.getXPathQuery(null, "ametys:content", finalExpr, sort); 528 529 return _resolver.query(xPathQuery); 530 } 531 532 /** 533 * Gets name form path name 534 * @param pathName the path name 535 * @return the name 536 */ 537 public String getName(String pathName) 538 { 539 String prefix = "page-"; 540 String name = ""; 541 for (String transitionalPageName : pathName.split("/")) 542 { 543 if (!name.equals("")) 544 { 545 name += "/"; 546 } 547 name += StringUtils.startsWith(transitionalPageName, prefix) ? StringUtils.substringAfter(transitionalPageName, prefix) : transitionalPageName; 548 } 549 return name; 550 } 551 552 /** 553 * Checks if name contains only Unicode digits and if so, prefix it with "page-" 554 * @param name The page name 555 * @return The potentially prefixed page name 556 */ 557 public String getPathName(String name) 558 { 559 return StringUtils.isNumeric(name) ? "page-" + name : name; 560 } 561 562 /** 563 * Clear root page cache 564 * @param rootPage the root page 565 */ 566 public void clearCache(Page rootPage) 567 { 568 clearCache(getContentTypeId(rootPage)); 569 } 570 571 /** 572 * Clear root page cache 573 * @param contentTypeId the content type id 574 */ 575 public void clearCache(String contentTypeId) 576 { 577 getTransitionalPagesCache().invalidate(PageCacheKey.of(null, contentTypeId, null, null, null)); 578 579 getUserPagesCache().invalidate(PageCacheKey.of(null, contentTypeId, null, null, null)); 580 581 getRootPagesCache().invalidateAll(); 582 } 583 584 /** 585 * Cache of the user directory root pages. 586 * The cache store a Set of TODO indexed by the workspaceName, siteName, siteMapName 587 * @return the cache 588 */ 589 protected Cache<RootPageCacheKey, Set<String>> getRootPagesCache() 590 { 591 return _abstractCacheManager.get(ROOT_PAGES_CACHE); 592 } 593 594 /** 595 * Key to index a user directory root page in a cache 596 */ 597 protected static final class RootPageCacheKey extends AbstractCacheKey 598 { 599 /** 600 * Basic constructor 601 * @param workspaceName the workspace name. Can be null. 602 * @param siteName the site name. Can be null. 603 * @param language the sitemap name. Can be null. 604 */ 605 public RootPageCacheKey(String workspaceName, String siteName, String language) 606 { 607 super(workspaceName, siteName, language); 608 } 609 610 /** 611 * Generate a cache key 612 * @param workspaceName the workspace name. Can be null. 613 * @param siteName the site name. Can be null. 614 * @param language the sitemap name. Can be null. 615 * @return the cache key 616 */ 617 public static RootPageCacheKey of(String workspaceName, String siteName, String language) 618 { 619 return new RootPageCacheKey(workspaceName, siteName, language); 620 } 621 } 622 623 /** 624 * Cache of the user directory transitional pages. 625 * The cache store a sorted set of TODO indexed by the workspaceName, siteName, siteMapName, pageName. 626 * @return the cache 627 */ 628 protected Cache<PageCacheKey, SortedSet<String>> getTransitionalPagesCache() 629 { 630 return _abstractCacheManager.get(TRANSITIONAL_PAGES_CACHE); 631 } 632 633 /** 634 * Cache of the user directory user pages. 635 * The cache store a {@link Map} of (content name, content id) of all the content of the page. 636 * The cache is indexed by workspaceName, siteName, siteMapName, pageName. 637 * @return the cache 638 */ 639 protected Cache<PageCacheKey, Map<String, String>> getUserPagesCache() 640 { 641 return _abstractCacheManager.get(USER_PAGES_CACHE); 642 } 643 644 /** 645 * Key to index a user directory page in a cache 646 */ 647 protected static final class PageCacheKey extends AbstractCacheKey 648 { 649 650 /** 651 * Basic constructor 652 * @param workspaceName the workspace name. Can be null. 653 * @param contentTypeId the contentType id. Can be null. 654 * @param siteName the site name. Can be null. 655 * @param language the sitemap name. Can be null. 656 * @param pageName the page name. Can be null. 657 */ 658 public PageCacheKey(String workspaceName, String contentTypeId, String siteName, String language, String pageName) 659 { 660 super(workspaceName, contentTypeId, siteName, language, pageName); 661 } 662 663 /** 664 * Generate a cache key 665 * @param workspaceName the workspace name. Can be null. 666 * @param contentTypeId the contentType id. Can be null. 667 * @param siteName the site name. Can be null. 668 * @param language the sitemap name. Can be null. 669 * @param pageName the page name. Can be null. 670 * @return the cache key 671 */ 672 public static PageCacheKey of(String workspaceName, String contentTypeId, String siteName, String language, String pageName) 673 { 674 return new PageCacheKey(workspaceName, contentTypeId, siteName, language, pageName); 675 } 676 } 677}