001/* 002 * Copyright 2012 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.odfweb.repository; 017 018import java.util.Arrays; 019import java.util.Collections; 020import java.util.Iterator; 021import java.util.List; 022import java.util.Map; 023import java.util.Objects; 024import java.util.Set; 025import java.util.stream.Collectors; 026 027import javax.jcr.Node; 028import javax.jcr.RepositoryException; 029import javax.jcr.Value; 030 031import org.apache.avalon.framework.activity.Initializable; 032import org.apache.avalon.framework.component.Component; 033import org.apache.avalon.framework.service.ServiceException; 034import org.apache.avalon.framework.service.ServiceManager; 035import org.apache.avalon.framework.service.Serviceable; 036import org.apache.commons.lang3.StringUtils; 037 038import org.ametys.cms.content.ContentHelper; 039import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 040import org.ametys.cms.contenttype.ContentTypesHelper; 041import org.ametys.cms.repository.Content; 042import org.ametys.core.cache.AbstractCacheManager; 043import org.ametys.core.cache.Cache; 044import org.ametys.core.ui.Callable; 045import org.ametys.core.util.I18nUtils; 046import org.ametys.odf.ProgramItem; 047import org.ametys.odf.catalog.CatalogsManager; 048import org.ametys.odf.course.Course; 049import org.ametys.odf.enumeration.OdfReferenceTableHelper; 050import org.ametys.odf.orgunit.RootOrgUnitProvider; 051import org.ametys.odf.program.AbstractProgram; 052import org.ametys.odf.program.Program; 053import org.ametys.odf.program.SubProgram; 054import org.ametys.odf.tree.OdfClassificationHandler; 055import org.ametys.odf.tree.OdfClassificationHandler.LevelValue; 056import org.ametys.plugins.core.impl.cache.AbstractCacheKey; 057import org.ametys.plugins.odfweb.restrictions.OdfProgramRestriction; 058import org.ametys.plugins.odfweb.restrictions.OdfProgramRestrictionManager; 059import org.ametys.plugins.repository.AmetysObjectIterable; 060import org.ametys.plugins.repository.AmetysObjectResolver; 061import org.ametys.plugins.repository.AmetysRepositoryException; 062import org.ametys.plugins.repository.jcr.JCRAmetysObject; 063import org.ametys.plugins.repository.jcr.NameHelper; 064import org.ametys.plugins.repository.provider.WorkspaceSelector; 065import org.ametys.plugins.repository.query.expression.Expression; 066import org.ametys.plugins.repository.query.expression.VirtualFactoryExpression; 067import org.ametys.runtime.i18n.I18nizableText; 068import org.ametys.runtime.model.ModelItem; 069import org.ametys.runtime.plugin.component.AbstractLogEnabled; 070import org.ametys.web.repository.page.Page; 071import org.ametys.web.repository.page.PageQueryHelper; 072import org.ametys.web.repository.site.Site; 073import org.ametys.web.repository.sitemap.Sitemap; 074 075import com.google.common.collect.ImmutableList; 076 077/** 078 * Component providing methods to retrieve ODF virtual pages, such as the ODF root, 079 * level 1 and 2 metadata names, and so on. 080 */ 081public class OdfPageHandler extends AbstractLogEnabled implements Component, Initializable, Serviceable 082{ 083 /** The avalon role. */ 084 public static final String ROLE = OdfPageHandler.class.getName(); 085 086 /** First level attribute name. */ 087 public static final String LEVEL1_ATTRIBUTE_NAME = "firstLevel"; 088 089 /** Second level attribute name. */ 090 public static final String LEVEL2_ATTRIBUTE_NAME = "secondLevel"; 091 092 /** Catalog data name. */ 093 public static final String CATALOG_DATA_NAME = "odf-root-catalog"; 094 095 /** Content types that are not eligible for first and second level */ 096 // See ODF-1115 Exclude the mentions enumerator from the list : 097 protected static final List<String> NON_ELIGIBLE_CTYPES_FOR_LEVEL = Arrays.asList("org.ametys.plugins.odf.Content.programItem", "odf-enumeration.Mention"); 098 099 private static final String __ODF_ROOT_PAGES_CACHE = OdfPageHandler.class.getName() + "$odfRootPages"; 100 private static final String __HAS_ODF_ROOT_CACHE = OdfPageHandler.class.getName() + "$hasOdfRootPage"; 101 private static final String __PROGRAM_LEVEL_PATH_CACHE = OdfPageHandler.class.getName() + "$programLevelPath"; 102 private static final String __PROGRAM_RESTRICTION_CACHE = OdfPageHandler.class.getName() + "$programRestriction"; 103 104 private static final String __ROOT_CACHE_ALL_SITES_KEY = "ALL"; 105 private static final String __ROOT_CACHE_ALL_SITEMAPS_KEY = "ALL"; 106 107 /** The ametys object resolver. */ 108 protected AmetysObjectResolver _resolver; 109 110 /** The i18n utils. */ 111 protected I18nUtils _i18nUtils; 112 113 /** The content type extension point. */ 114 protected ContentTypeExtensionPoint _cTypeEP; 115 116 /** The ODF Catalog enumeration */ 117 protected CatalogsManager _catalogsManager; 118 119 /** The workspace selector. */ 120 protected WorkspaceSelector _workspaceSelector; 121 122 /** Avalon service manager */ 123 protected ServiceManager _manager; 124 125 /** Restriction manager */ 126 protected OdfProgramRestrictionManager _odfRestrictionsManager; 127 128 /** Content types helper */ 129 protected ContentTypesHelper _contentTypesHelper; 130 131 /** Content helper */ 132 protected ContentHelper _contentHelper; 133 134 /** Odf reference table helper */ 135 protected OdfReferenceTableHelper _odfReferenceTableHelper; 136 137 /** Root orgunit provider */ 138 protected RootOrgUnitProvider _orgUnitProvider; 139 140 /** Root orgunit provider */ 141 protected OdfClassificationHandler _odfClassificationHandler; 142 143 /** The cache manager */ 144 protected AbstractCacheManager _cacheManager; 145 146 @Override 147 public void service(ServiceManager serviceManager) throws ServiceException 148 { 149 _manager = serviceManager; 150 _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 151 _i18nUtils = (I18nUtils) serviceManager.lookup(I18nUtils.ROLE); 152 _cTypeEP = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE); 153 _workspaceSelector = (WorkspaceSelector) serviceManager.lookup(WorkspaceSelector.ROLE); 154 _catalogsManager = (CatalogsManager) serviceManager.lookup(CatalogsManager.ROLE); 155 _odfRestrictionsManager = (OdfProgramRestrictionManager) serviceManager.lookup(OdfProgramRestrictionManager.ROLE); 156 _contentTypesHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE); 157 _contentHelper = (ContentHelper) serviceManager.lookup(ContentHelper.ROLE); 158 _odfReferenceTableHelper = (OdfReferenceTableHelper) serviceManager.lookup(OdfReferenceTableHelper.ROLE); 159 _orgUnitProvider = (RootOrgUnitProvider) serviceManager.lookup(RootOrgUnitProvider.ROLE); 160 _odfClassificationHandler = (OdfClassificationHandler) serviceManager.lookup(OdfClassificationHandler.ROLE); 161 _cacheManager = (AbstractCacheManager) serviceManager.lookup(AbstractCacheManager.ROLE); 162 } 163 164 @Override 165 public void initialize() throws Exception 166 { 167 _cacheManager.createMemoryCache(__ODF_ROOT_PAGES_CACHE, 168 new I18nizableText("plugin.odf-web", "PLUGINS_ODF_WEB_CACHE_ODF_ROOT_PAGES_LABEL"), 169 new I18nizableText("plugin.odf-web", "PLUGINS_ODF_WEB_CACHE_ODF_ROOT_PAGES_DESCRIPTION"), 170 true, 171 null); 172 173 _cacheManager.createMemoryCache(__HAS_ODF_ROOT_CACHE, 174 new I18nizableText("plugin.odf-web", "PLUGINS_ODF_WEB_CACHE_HAS_ODF_ROOT_PAGE_LABEL"), 175 new I18nizableText("plugin.odf-web", "PLUGINS_ODF_WEB_CACHE_HAS_ODF_ROOT_PAGE_DESCRIPTION"), 176 true, 177 null); 178 179 _cacheManager.createRequestCache(__PROGRAM_LEVEL_PATH_CACHE, 180 new I18nizableText("plugin.odf-web", "PLUGINS_ODF_WEB_CACHE_PROGRAM_LEVEL_PATH_LABEL"), 181 new I18nizableText("plugin.odf-web", "PLUGINS_ODF_WEB_CACHE_PROGRAM_LEVEL_PATH_DESCRIPTION"), 182 false); 183 184 _cacheManager.createRequestCache(__PROGRAM_RESTRICTION_CACHE, 185 new I18nizableText("plugin.odf-web", "PLUGINS_ODF_WEB_CACHE_PROGRAM_RESTRICTION_LABEL"), 186 new I18nizableText("plugin.odf-web", "PLUGINS_ODF_WEB_CACHE_PROGRAM_RESTRICTION_DESCRIPTION"), 187 false); 188 } 189 190 /** 191 * Get the first ODF root page. 192 * @param siteName The site name 193 * @param sitemapName The sitemap's name 194 * @return a ODF root page or null if not found. 195 * @throws AmetysRepositoryException if an error occurs 196 */ 197 public Page getOdfRootPage(String siteName, String sitemapName) throws AmetysRepositoryException 198 { 199 Set<Page> rootPages = getOdfRootPages(siteName, sitemapName); 200 return rootPages.isEmpty() ? null : rootPages.iterator().next(); 201 } 202 203 /** 204 * Get ODF root page of a specific catalog. 205 * @param siteName The site name 206 * @param sitemapName The sitemap name 207 * @param catalogName The catalog name 208 * @return the ODF root page or null if not found. 209 * @throws AmetysRepositoryException if an error occurs 210 */ 211 public Page getOdfRootPage(String siteName, String sitemapName, String catalogName) throws AmetysRepositoryException 212 { 213 String catalogToCompare = catalogName != null ? catalogName : ""; 214 215 for (Page odfRootPage : getOdfRootPages(siteName, sitemapName)) 216 { 217 if (catalogToCompare.equals(getCatalog(odfRootPage))) 218 { 219 return odfRootPage; 220 } 221 } 222 223 return null; 224 } 225 226 /** 227 * Get the id of ODF root pages 228 * @param siteName The site name 229 * @param sitemapName The sitemap name 230 * @return The ids of ODF root pages 231 * @throws AmetysRepositoryException if an error occurs. 232 */ 233 @Callable 234 public List<String> getOdfRootPageIds(String siteName, String sitemapName) throws AmetysRepositoryException 235 { 236 Set<Page> pages = getOdfRootPages(siteName, sitemapName); 237 return pages.stream().map(p -> p.getId()).collect(Collectors.toList()); 238 } 239 240 /** 241 * Get the ODF root pages. 242 * @param siteName the current site. 243 * @param sitemapName the current sitemap/language. 244 * @return the ODF root pages 245 * @throws AmetysRepositoryException if an error occurs. 246 */ 247 public Set<Page> getOdfRootPages(String siteName, String sitemapName) throws AmetysRepositoryException 248 { 249 Cache<OdfRootPageCacheKey, Set<String>> cache = _getOdfRootPagesCache(); 250 251 String workspaceName = _workspaceSelector.getWorkspace(); 252 253 Set<String> rootPageIds = cache.get(OdfRootPageCacheKey.of(workspaceName, Objects.toString(siteName, __ROOT_CACHE_ALL_SITES_KEY), Objects.toString(sitemapName, __ROOT_CACHE_ALL_SITEMAPS_KEY)), item -> { 254 return _getOdfRootPages(siteName, sitemapName) 255 .stream() 256 .map(Page::getId) 257 .collect(Collectors.toSet()); 258 }); 259 260 return rootPageIds.stream() 261 .map(id -> (Page) _resolver.resolveById(id)) 262 .collect(Collectors.toSet()); 263 } 264 265 /** 266 * Test if the given site has at least one sitemap with an odf root page. 267 * @param site the site to test. 268 * @return true if the site has at least one sitemap with an odf root page, false otherwise. 269 */ 270 public boolean hasOdfRootPage(Site site) 271 { 272 Cache<HasOdfRootPageCacheKey, Boolean> cache = _getHasOdfRootPageCache(); 273 274 String workspace = _workspaceSelector.getWorkspace(); 275 276 return cache.get(HasOdfRootPageCacheKey.of(workspace, site.getName()), item -> { 277 278 Iterator<Sitemap> sitemaps = site.getSitemaps().iterator(); 279 while (sitemaps.hasNext()) 280 { 281 String sitemapName = sitemaps.next().getName(); 282 283 if (!getOdfRootPages(site.getName(), sitemapName).isEmpty()) 284 { 285 return true; 286 } 287 } 288 289 return false; 290 }); 291 } 292 293 /** 294 * Determines if the program is part of the site restrictions 295 * @param rootPage The ODF root page 296 * @param program The program 297 * @return <code>true</code> the program is part of the site restrictions 298 */ 299 public boolean isValidRestriction(Page rootPage, Program program) 300 { 301 Cache<ProgramInRootCacheKey, Boolean> cache = _cacheManager.get(__PROGRAM_RESTRICTION_CACHE); 302 303 return cache.get(ProgramInRootCacheKey.of(rootPage.getId(), program.getId()), isValid -> { 304 // Check catalog 305 if (!program.getCatalog().equals(getCatalog(rootPage))) 306 { 307 return false; 308 } 309 310 // Check language 311 if (!program.getLanguage().equals(rootPage.getSitemapName())) 312 { 313 return false; 314 } 315 316 // Check site restrictions 317 OdfProgramRestriction restriction = _odfRestrictionsManager.getRestriction(rootPage); 318 if (restriction != null) 319 { 320 return restriction.contains(program); 321 } 322 323 return true; 324 }); 325 } 326 327 /** 328 * Clear the ODF root page cache. 329 */ 330 public void clearRootCache() 331 { 332 _getOdfRootPagesCache().invalidateAll(); 333 _getHasOdfRootPageCache().invalidateAll(); 334 } 335 336 /** 337 * Clear the ODF root page cache for a given site and language. 338 * @param siteName the current site. 339 * @param sitemapName the current sitemap/language. 340 */ 341 public void clearRootCache(String siteName, String sitemapName) 342 { 343 _getOdfRootPagesCache().invalidate(OdfRootPageCacheKey.of(null, siteName, sitemapName)); 344 _getHasOdfRootPageCache().invalidate(HasOdfRootPageCacheKey.of(null, siteName)); 345 } 346 347 /** 348 * Determines if the page is a ODF root page 349 * @param page The page to test 350 * @return true if the page is a ODF root page 351 */ 352 public boolean isODFRootPage (Page page) 353 { 354 if (page instanceof JCRAmetysObject) 355 { 356 try 357 { 358 JCRAmetysObject jcrPage = (JCRAmetysObject) page; 359 Node node = jcrPage.getNode(); 360 361 if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY)) 362 { 363 Value[] values = node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues(); 364 365 boolean hasValue = false; 366 for (int i = 0; i < values.length && !hasValue; i++) 367 { 368 hasValue = FirstLevelPageFactory.class.getName().equals(values[i].getString()); 369 } 370 371 return hasValue; 372 } 373 else 374 { 375 return false; 376 } 377 } 378 catch (RepositoryException e) 379 { 380 return false; 381 } 382 } 383 384 return false; 385 386 } 387 388 /** 389 * Get the ODF root page. 390 * @param siteName the current site. 391 * @param sitemapName the current sitemap/language. 392 * @return the ODF root page or null if not found. 393 * @throws AmetysRepositoryException if an error occurs. 394 */ 395 protected Page _getOdfRootPage(String siteName, String sitemapName) throws AmetysRepositoryException 396 { 397 Expression expression = new VirtualFactoryExpression(FirstLevelPageFactory.class.getName()); 398 String query = PageQueryHelper.getPageXPathQuery(siteName, sitemapName, null, expression, null); 399 400 AmetysObjectIterable<Page> pages = _resolver.query(query); 401 Page page = pages.stream().findFirst().orElse(null); 402 403 return page; 404 } 405 406 /** 407 * Get the ODF root page. 408 * @param siteName the current site. 409 * @param sitemapName the current sitemap/language. 410 * @return the ODF root page or null if not found. 411 * @throws AmetysRepositoryException if an error occurs. 412 */ 413 protected Set<Page> _getOdfRootPages(String siteName, String sitemapName) throws AmetysRepositoryException 414 { 415 Expression expression = new VirtualFactoryExpression(FirstLevelPageFactory.class.getName()); 416 String query = PageQueryHelper.getPageXPathQuery(siteName, sitemapName, null, expression, null); 417 418 return _resolver.<Page>query(query).stream().collect(Collectors.toSet()); 419 } 420 421 /** 422 * Get the catalog value of the ODF root page 423 * @param rootPage The ODF root page 424 * @return the catalog value 425 */ 426 public String getCatalog (Page rootPage) 427 { 428 return rootPage.getValue(CATALOG_DATA_NAME, StringUtils.EMPTY); 429 } 430 431 /** 432 * Get the first level metadata name. 433 * @param siteName the site name. 434 * @param sitemapName the sitemap name. 435 * @param catalog the current selected catalog. 436 * @return the first level metadata name. 437 */ 438 public String getLevel1Metadata(String siteName, String sitemapName, String catalog) 439 { 440 Page rootPage = getOdfRootPage(siteName, sitemapName, catalog); 441 442 return getLevel1Metadata(rootPage); 443 } 444 445 /** 446 * Get the first level metadata name. 447 * @param rootPage the ODF root page. 448 * @return the first level metadata name. 449 */ 450 public String getLevel1Metadata(Page rootPage) 451 { 452 return rootPage.getValue(LEVEL1_ATTRIBUTE_NAME); 453 } 454 455 /** 456 * Get the second level metadata name. 457 * @param siteName the site name. 458 * @param sitemapName the sitemap name. 459 * @param catalog the current selected catalog. 460 * @return the second level metadata name. 461 */ 462 public String getLevel2Metadata(String siteName, String sitemapName, String catalog) 463 { 464 Page rootPage = getOdfRootPage(siteName, sitemapName, catalog); 465 466 return getLevel2Metadata(rootPage); 467 } 468 469 /** 470 * Get the second level metadata name. 471 * @param rootPage the ODF root page. 472 * @return the second level metadata name. 473 */ 474 public String getLevel2Metadata(Page rootPage) 475 { 476 return rootPage.getValue(LEVEL2_ATTRIBUTE_NAME); 477 } 478 479 /** 480 * Get the first level metadata values (with translated label). 481 * @param siteName the site name. 482 * @param sitemapName the sitemap name. 483 * @param catalog the current selected catalog. 484 * @return the first level metadata values. 485 */ 486 public Map<String, LevelValue> getLevel1Values(String siteName, String sitemapName, String catalog) 487 { 488 Page rootPage = getOdfRootPage(siteName, sitemapName, catalog); 489 490 return getLevel1Values(rootPage); 491 } 492 493 /** 494 * Get the level value of a program by extracting and transforming the raw program value at the desired metadata path 495 * @param program The program 496 * @param levelMetaPath The desired metadata path that represent a level 497 * @return The final level value 498 */ 499 public String getProgramLevelValue(Program program, String levelMetaPath) 500 { 501 List<String> programLevelValue = _odfClassificationHandler.getProgramLevelValues(program, levelMetaPath); 502 return programLevelValue.isEmpty() ? null : programLevelValue.get(0); 503 } 504 505 /** 506 * Get the first level value of a program by extracting and transforming the raw program value 507 * @param rootPage The root page 508 * @param program The program 509 * @return The final level value or <code>null</code> if not found 510 */ 511 public String getProgramLevel1Value(Page rootPage, Program program) 512 { 513 String level1Metadata = getLevel1Metadata(rootPage); 514 if (StringUtils.isNotBlank(level1Metadata)) 515 { 516 List<String> programLevelValue = _odfClassificationHandler.getProgramLevelValues(program, level1Metadata); 517 return programLevelValue.isEmpty() ? null : programLevelValue.get(0); 518 } 519 else 520 { 521 return null; 522 } 523 } 524 525 /** 526 * Get the second level value of a program by extracting and transforming the raw program value 527 * @param rootPage The root page 528 * @param program The program 529 * @return The final level value or <code>null</code> if not found 530 */ 531 public String getProgramLevel2Value(Page rootPage, Program program) 532 { 533 String level2Metadata = getLevel2Metadata(rootPage); 534 if (StringUtils.isNotBlank(level2Metadata)) 535 { 536 List<String> programLevelValue = _odfClassificationHandler.getProgramLevelValues(program, level2Metadata); 537 return programLevelValue.isEmpty() ? null : programLevelValue.get(0); 538 } 539 else 540 { 541 return null; 542 } 543 } 544 545 /** 546 * Get the name of the page of first level for a given program 547 * @param rootPage The ODF root page 548 * @param program The program 549 * @return The page's name 550 */ 551 public String getLevel1PageName(Page rootPage, Program program) 552 { 553 String value = getProgramLevel1Value(rootPage, program); 554 return value != null ? NameHelper.filterName(value) + "-" + encodeLevelValue(value) : ""; 555 } 556 557 /** 558 * Get the name of the page of second level for a given program 559 * @param rootPage The ODF root page 560 * @param program The program 561 * @return The page's name 562 */ 563 public String getLevel2PageName(Page rootPage, Program program) 564 { 565 String value = getProgramLevel2Value(rootPage, program); 566 return value != null ? NameHelper.filterName(value) + "-" + encodeLevelValue(value) : ""; 567 } 568 569 /** 570 * Get the orgunit identifier given an uai code 571 * @param rootPage Odf root page 572 * @param uaiCode The uai code 573 * @return The orgunit id or null if not found 574 */ 575 public String getOrgunitIdFromUaiCode(Page rootPage, String uaiCode) 576 { 577 return _odfClassificationHandler.getOrgunitIdFromUaiCode(rootPage.getSitemapName(), uaiCode); 578 } 579 580 /** 581 * Get the programs available for a ODF root page, taking account the site's restrictions 582 * @param rootPage The ODF root page 583 * @param level1 filters results with a level1 value. Can be null. 584 * @param level2 filters results with a level2 value. Can be null. 585 * @param programCode expected program code. Can be null. 586 * @param programName expected program name. Can be null. 587 * @return an iterator over resulting programs 588 */ 589 public AmetysObjectIterable<Program> getProgramsWithRestrictions(Page rootPage, String level1, String level2, String programCode, String programName) 590 { 591 return getProgramsWithRestrictions(rootPage, getLevel1Metadata(rootPage), level1, getLevel2Metadata(rootPage), level2, programCode, programName); 592 } 593 594 /** 595 * Get the programs available for a ODF root page, taking account the site's restrictions 596 * @param rootPage The ODF root page 597 * @param level1Metadata metadata name for first level 598 * @param level1 filters results with a level1 value. Can be null. 599 * @param level2Metadata metadata name for second level 600 * @param level2 filters results with a level2 value. Can be null. 601 * @param programCode expected program code. Can be null. 602 * @param programName expected program name. Can be null. 603 * @return an iterator over resulting programs 604 */ 605 public AmetysObjectIterable<Program> getProgramsWithRestrictions(Page rootPage, String level1Metadata, String level1, String level2Metadata, String level2, String programCode, String programName) 606 { 607 OdfProgramRestriction restriction = _odfRestrictionsManager.getRestriction(rootPage); 608 return _odfClassificationHandler.getPrograms(getCatalog(rootPage), rootPage.getSitemapName(), level1Metadata, level1, level2Metadata, level2, programCode, programName, restriction == null ? null : ImmutableList.of(restriction.getExpression())); 609 } 610 611 /** 612 * Get the first level metadata values (with translated label) 613 * @param rootPage the ODF root page. 614 * @return the first level metadata values. Can be empty if there is no level1 attribute on root page. 615 */ 616 public Map<String, LevelValue> getLevel1Values(Page rootPage) 617 { 618 String level1Value = getLevel1Metadata(rootPage); 619 if (StringUtils.isNotBlank(level1Value)) 620 { 621 return _odfClassificationHandler.getLevelValues(level1Value, rootPage.getSitemapName()); 622 } 623 else 624 { 625 return Collections.EMPTY_MAP; 626 } 627 } 628 629 /** 630 * Get the second level metadata values (with translated label). 631 * @param siteName the site name. 632 * @param sitemapName the sitemap name. 633 * @param catalog the current selected catalog. 634 * @return the second level metadata values. 635 */ 636 public Map<String, LevelValue> getLevel2Values(String siteName, String sitemapName, String catalog) 637 { 638 Page rootPage = getOdfRootPage(siteName, sitemapName, catalog); 639 640 return getLevel2Values(rootPage); 641 } 642 643 /** 644 * Get the second level metadata values (with translated label). 645 * @param rootPage the ODF root page. 646 * @return the second level metadata values. Can be empty if there is no level2 attribute on root page. 647 */ 648 public Map<String, LevelValue> getLevel2Values(Page rootPage) 649 { 650 String level2Value = getLevel2Metadata(rootPage); 651 if (StringUtils.isNotBlank(level2Value)) 652 { 653 return _odfClassificationHandler.getLevelValues(level2Value, rootPage.getSitemapName()); 654 } 655 else 656 { 657 return Collections.EMPTY_MAP; 658 } 659 } 660 661 /** 662 * Encode level value to be use into a URI. 663 * Double-encode characters ':', '-' and '/'. 664 * @param value The raw value 665 * @return the encoded value 666 */ 667 public String encodeLevelValue (String value) 668 { 669 return _odfClassificationHandler.encodeLevelValue(value); 670 } 671 672 /** 673 * Decode level value used in a URI 674 * @param value The encoded value 675 * @return the decoded value 676 */ 677 public String decodeLevelValue (String value) 678 { 679 return _odfClassificationHandler.decodeLevelValue(value); 680 } 681 682 /** 683 * Returns the page's name of a {@link ProgramItem}. 684 * Only {@link AbstractProgram} and {@link Course} can have a page. 685 * @param item The program item 686 * @return The page's name 687 * @throws IllegalArgumentException if the program item is not a {@link AbstractProgram} nor a {@link Course}. 688 */ 689 public String getPageName (ProgramItem item) 690 { 691 if (item instanceof AbstractProgram || item instanceof Course) 692 { 693 String filteredTitle = ""; 694 try 695 { 696 filteredTitle = NameHelper.filterName(((Content) item).getTitle()); 697 } 698 catch (IllegalArgumentException e) 699 { 700 // title does not match the expected regular expression : ^([0-9-_]*)[a-z].*$, use default title 701 if (item instanceof Program) 702 { 703 filteredTitle = "program"; 704 } 705 else if (item instanceof SubProgram) 706 { 707 filteredTitle = "subprogram"; 708 } 709 else if (item instanceof Course) 710 { 711 filteredTitle = "course"; 712 } 713 } 714 715 return filteredTitle + "-" + item.getCode(); 716 } 717 else 718 { 719 throw new IllegalArgumentException("Illegal program item : no page can be associated for a program item of type " + item.getClass().getName()); 720 } 721 } 722 723 /** 724 * Get the eligible enumerated attribute definitions for ODF page level 725 * @return the eligible attribute definitions 726 */ 727 public Map<String, ModelItem> getEligibleAttributesForLevel() 728 { 729 return _odfClassificationHandler.getEligibleAttributesForLevel(); 730 } 731 732 /** 733 * Get the ODF catalogs 734 * @return the ODF catalogs 735 */ 736 public Map<String, I18nizableText> getCatalogs() 737 { 738 return _odfClassificationHandler.getCatalogs(); 739 } 740 741 /** 742 * Get the enumerated attribute definitions for the given content type. 743 * Attribute with enumerator or content attribute are considered as enumerated 744 * @param programContentTypeId The content type's id 745 * @param allowMultiple <code>true</code> true to allow multiple attributes 746 * @return The definitions of enumerated attributes 747 */ 748 public Map<String, ModelItem> getEnumeratedAttributes(String programContentTypeId, boolean allowMultiple) 749 { 750 return _odfClassificationHandler.getEnumeratedAttributes(programContentTypeId, allowMultiple); 751 } 752 753 /** 754 * Compute the path from the root odf page, representing the first and second level pages. 755 * @param rootPage The odf root page 756 * @param parentProgram The program to compute 757 * @return the path, can be empty if no levels defined, and null if the parent program does not have values for levels attributes 758 */ 759 public String computeLevelsPath(Page rootPage, Program parentProgram) 760 { 761 Cache<ProgramInRootCacheKey, String> levelCache = _cacheManager.get(__PROGRAM_LEVEL_PATH_CACHE); 762 763 return levelCache.get(ProgramInRootCacheKey.of(rootPage.getId(), parentProgram.getId()), item -> { 764 // Level 1 is defined => Check the value 765 if (getLevel1Metadata(rootPage) != null) 766 { 767 String level1 = getProgramLevel1Value(rootPage, parentProgram); 768 769 // Value for level 1 is defined => Check for the second level 770 if (StringUtils.isNotBlank(level1)) 771 { 772 // Level 2 is defined => Check the value 773 if (getLevel2Metadata(rootPage) != null) 774 { 775 String level2 = getProgramLevel2Value(rootPage, parentProgram); 776 777 // Value for level 2 is defined => Return the level 2 page 778 if (StringUtils.isNotBlank(level2)) 779 { 780 Page secondLevelPage = findSecondLevelPage(rootPage, level1, level2); 781 return secondLevelPage.getParent().getName() + "/" + secondLevelPage.getName(); 782 } 783 784 // Value for level 2 is not defined => Return null 785 return null; 786 } 787 788 // Level 2 is not defined => Return the level 1 page 789 return findFirstLevelPage(rootPage, level1).getName(); 790 } 791 792 // Value for level 1 is not defined => Return null 793 return null; 794 } 795 796 // Level 1 is not defined => Return an empty path, all pages are on the root page 797 return StringUtils.EMPTY; 798 }); 799 } 800 801 /** 802 * Get the first level page from the given root page with the level 1 value. 803 * @param rootPage The odf root page 804 * @param level1Value The first level value 805 * @return a first level page 806 */ 807 public FirstLevelPage findFirstLevelPage(Page rootPage, String level1Value) 808 { 809 // Calculate the real path 810 String firstLevelPageId = "odfLevel1://" + level1Value + "?rootId=" + rootPage.getId(); 811 return _resolver.resolveById(firstLevelPageId); 812 } 813 814 /** 815 * Get the second level page from the given root page with the level 1 and level 2 values. 816 * @param rootPage The odf root page 817 * @param level1Value The first level value 818 * @param level2Value The second level value 819 * @return a second level page 820 */ 821 public SecondLevelPage findSecondLevelPage(Page rootPage, String level1Value, String level2Value) 822 { 823 // Calculate the real path 824 String secondLevelPageId = "odfLevel2://" + level1Value + "/" + level2Value + "?rootId=" + rootPage.getId(); 825 return _resolver.resolveById(secondLevelPageId); 826 } 827 828 /** 829 * Add an intermediate redirect page if the called page name doesn't match the real page name. 830 * @param page The page 831 * @param name The called page name 832 * @return The page maybe included in a {@link RedirectPage} 833 */ 834 public Page addRedirectIfNeeded(Page page, String name) 835 { 836 if (!name.equals(page.getName())) 837 { 838 getLogger().warn("Redirect path '{}' to '{}' page", name, page.getName()); 839 return new RedirectPage(page instanceof RedirectPage redirectPage ? redirectPage.getRedirectPage() : page); 840 } 841 return page; 842 } 843 844 /** 845 * Explore the queue path if it is not empty 846 * @param page The root page to explore 847 * @param queuePath The path 848 * @return The child page, or given page if queue path is empty 849 */ 850 public Page exploreQueuePath(Page page, String queuePath) 851 { 852 if (StringUtils.isNotEmpty(queuePath)) 853 { 854 return page.getChild(queuePath); 855 } 856 return page; 857 } 858 859 private Cache<OdfRootPageCacheKey, Set<String>> _getOdfRootPagesCache() 860 { 861 return _cacheManager.get(__ODF_ROOT_PAGES_CACHE); 862 } 863 864 private Cache<HasOdfRootPageCacheKey, Boolean> _getHasOdfRootPageCache() 865 { 866 return _cacheManager.get(__HAS_ODF_ROOT_CACHE); 867 } 868 869 static class OdfRootPageCacheKey extends AbstractCacheKey 870 { 871 public OdfRootPageCacheKey(String workspaceName, String siteName, String sitemapName) 872 { 873 super(workspaceName, siteName, sitemapName); 874 } 875 876 public static OdfRootPageCacheKey of(String workspaceName, String siteName, String sitemapName) 877 { 878 return new OdfRootPageCacheKey(workspaceName, siteName, sitemapName); 879 } 880 } 881 882 static class HasOdfRootPageCacheKey extends AbstractCacheKey 883 { 884 public HasOdfRootPageCacheKey(String workspaceName, String siteName) 885 { 886 super(workspaceName, siteName); 887 } 888 889 public static HasOdfRootPageCacheKey of(String workspaceName, String siteName) 890 { 891 return new HasOdfRootPageCacheKey(workspaceName, siteName); 892 } 893 } 894 895 static class ProgramInRootCacheKey extends AbstractCacheKey 896 { 897 public ProgramInRootCacheKey(String rootPageId, String programId) 898 { 899 super(rootPageId, programId); 900 } 901 902 public static ProgramInRootCacheKey of(String rootPageId, String programId) 903 { 904 return new ProgramInRootCacheKey(rootPageId, programId); 905 } 906 } 907}