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