001/* 002 * Copyright 2011 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.sitemap; 017 018import java.lang.reflect.Array; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collections; 022import java.util.Comparator; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import javax.jcr.Node; 028import javax.jcr.Property; 029import javax.jcr.RepositoryException; 030import javax.jcr.Value; 031 032import org.apache.avalon.framework.component.Component; 033import org.apache.avalon.framework.context.Context; 034import org.apache.avalon.framework.context.ContextException; 035import org.apache.avalon.framework.context.Contextualizable; 036import org.apache.avalon.framework.service.ServiceException; 037import org.apache.avalon.framework.service.ServiceManager; 038import org.apache.avalon.framework.service.Serviceable; 039import org.apache.cocoon.components.ContextHelper; 040import org.apache.cocoon.environment.Request; 041import org.slf4j.Logger; 042import org.slf4j.LoggerFactory; 043 044import org.ametys.core.right.RightManager; 045import org.ametys.plugins.repository.AmetysObjectResolver; 046import org.ametys.plugins.repository.UnknownAmetysObjectException; 047import org.ametys.plugins.repository.data.holder.ModelLessDataHolder; 048import org.ametys.plugins.repository.data.holder.group.Composite; 049import org.ametys.plugins.repository.data.holder.group.Repeater; 050import org.ametys.plugins.repository.jcr.JCRAmetysObject; 051import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector; 052import org.ametys.runtime.model.type.ElementType; 053import org.ametys.web.repository.page.MetadataAwarePagesContainer; 054import org.ametys.web.repository.page.Page; 055import org.ametys.web.repository.page.Page.PageType; 056import org.ametys.web.repository.page.PagesContainer; 057import org.ametys.web.repository.site.Site; 058import org.ametys.web.repository.sitemap.Sitemap; 059import org.ametys.web.sitemap.SitemapIcon.Condition; 060 061/** 062 * Default implementation of {@link SitemapDecoratorsHandler} 063 * 064 */ 065public class DefaultSitemapDecoratorsHandler implements SitemapDecoratorsHandler, Serviceable, Contextualizable, Component 066{ 067 private static Logger _logger = LoggerFactory.getLogger(DefaultSitemapDecoratorsHandler.class); 068 069 private Context _context; 070 071 private AmetysObjectResolver _ametysResolver; 072 073 private RightManager _rightManager; 074 075 private SitemapIconsExtensionPoint _sitemapIconsEP; 076 private SitemapDecoratorsExtensionPoint _sitemapDecoratorsEP; 077 078 @Override 079 public void service(ServiceManager manager) throws ServiceException 080 { 081 _ametysResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 082 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 083 _sitemapIconsEP = (SitemapIconsExtensionPoint) manager.lookup(SitemapIconsExtensionPoint.ROLE); 084 _sitemapDecoratorsEP = (SitemapDecoratorsExtensionPoint) manager.lookup(SitemapDecoratorsExtensionPoint.ROLE); 085 } 086 087 @Override 088 public void contextualize(Context context) throws ContextException 089 { 090 _context = context; 091 } 092 093 @Override 094 public List<SitemapIcon> getIcons(Site site) 095 { 096 String skinId = site.getSkinId(); 097 return getIcons(skinId); 098 } 099 100 /** 101 * Get the list of available icons 102 * @param skinId name of the skin 103 * @return the list of available icons 104 */ 105 protected List<SitemapIcon> getIcons(String skinId) 106 { 107 List<SitemapIcon> icons = new ArrayList<>(); 108 109 for (String extensionId : _sitemapIconsEP.getExtensionsIds()) 110 { 111 SitemapIcons sitemapIcons = _sitemapIconsEP.getExtension(extensionId); 112 icons.addAll(sitemapIcons.getIcons(skinId)); 113 } 114 115 Collections.sort(icons, new IconOrderComparator()); 116 117 return icons; 118 } 119 120 @Override 121 public SitemapIcon getIcon(Page page) 122 { 123 String skinId = page.getSite().getSkinId(); 124 125 List<SitemapIcon> icons = getIcons(skinId); 126 for (SitemapIcon icon : icons) 127 { 128 if (_matches(icon, page)) 129 { 130 return icon; 131 } 132 } 133 134 return null; 135 } 136 137 @Override 138 public List<SitemapDecorator> getDecorators (Site site) 139 { 140 String skinId = site.getSkinId(); 141 return getDecorators(skinId); 142 } 143 144 /** 145 * Get the list of available decorators 146 * @param skinId name of the skin 147 * @return the list of available decorators 148 */ 149 protected List<SitemapDecorator> getDecorators(String skinId) 150 { 151 List<SitemapDecorator> result = new ArrayList<>(); 152 for (String extensionId : _sitemapDecoratorsEP.getExtensionsIds()) 153 { 154 SitemapDecorators sitemapDecorators = _sitemapDecoratorsEP.getExtension(extensionId); 155 Set<SitemapDecorator> decorators = sitemapDecorators.getDecorators(skinId); 156 if (decorators != null && !decorators.isEmpty()) 157 { 158 result.addAll(decorators); 159 } 160 } 161 162 return result; 163 } 164 165 @Override 166 public List<SitemapDecorator> getDecorators(Sitemap sitemap) 167 { 168 String skinId = sitemap.getSite().getSkinId(); 169 170 List<SitemapDecorator> sitemapDecorators = new ArrayList<>(); 171 for (SitemapDecorator decorator : getDecorators(skinId)) 172 { 173 if (_matches(decorator, sitemap)) 174 { 175 sitemapDecorators.add(decorator); 176 } 177 } 178 return sitemapDecorators; 179 } 180 181 @Override 182 public List<SitemapDecorator> getDecorators(Page page) 183 { 184 String skinId = page.getSite().getSkinId(); 185 186 List<SitemapDecorator> pageDecorators = new ArrayList<>(); 187 for (SitemapDecorator decorator : getDecorators(skinId)) 188 { 189 if (_matches(decorator, page)) 190 { 191 pageDecorators.add(decorator); 192 } 193 } 194 return pageDecorators; 195 } 196 197 /** 198 * Determines if page matches the decorator 199 * @param icon the icon 200 * @param sitemap the sitemap 201 * @return true if the page matches the decorator 202 */ 203 protected boolean _matches (SitemapIcon icon, Sitemap sitemap) 204 { 205 if (!_matchesMetadata(icon, sitemap)) 206 { 207 return false; 208 } 209 210 if (!_matchesProperties (icon, sitemap)) 211 { 212 return false; 213 } 214 215 if (!icon.getTags().isEmpty()) 216 { 217 return false; 218 } 219 220 if (icon.isLive()) 221 { 222 return false; 223 } 224 225 if (icon.getPageType() != null) 226 { 227 return false; 228 } 229 230 if (icon.isRestricted() && _rightManager.hasAnonymousReadAccess(sitemap)) 231 { 232 return false; 233 } 234 235 return true; 236 } 237 238 /** 239 * Determines if page matches the decorator 240 * @param icon the icon 241 * @param page the page to test 242 * @return true if the page matches the decorator 243 */ 244 protected boolean _matches (SitemapIcon icon, Page page) 245 { 246 PageType pageType = icon.getPageType(); 247 if (pageType != null && !page.getType().equals(pageType)) 248 { 249 return false; 250 } 251 252 if (!_matchesTags (icon, page)) 253 { 254 return false; 255 } 256 257 if (!_matchesMetadata(icon, page)) 258 { 259 return false; 260 } 261 262 if (!_matchesProperties (icon, page)) 263 { 264 return false; 265 } 266 267 if (icon.isLive()) 268 { 269 Request request = ContextHelper.getRequest(_context); 270 String currentWp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 271 272 try 273 { 274 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, "live"); 275 _ametysResolver.resolveById(page.getId()); 276 } 277 catch (UnknownAmetysObjectException e) 278 { 279 return false; 280 } 281 finally 282 { 283 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWp); 284 } 285 } 286 287 if (icon.isRestricted() && _rightManager.hasAnonymousReadAccess(page)) 288 { 289 return false; 290 } 291 292 return true; 293 } 294 295 private boolean _matchesTags (SitemapIcon icon, Page page) 296 { 297 List<String> tags = icon.getTags(); 298 299 if (!tags.isEmpty()) 300 { 301 Condition condition = icon.getTagsCondition(); 302 if (condition == Condition.AND) 303 { 304 return page.getTags().containsAll(tags); 305 } 306 else if (condition == Condition.OR) 307 { 308 for (String tagName : tags) 309 { 310 if (page.getTags().contains(tagName)) 311 { 312 return true; 313 } 314 } 315 return false; 316 } 317 } 318 return true; 319 } 320 321 private boolean _matchesMetadata (SitemapIcon icon, MetadataAwarePagesContainer page) 322 { 323 Map<String, String> metadata = icon.getMetadata(); 324 if (!metadata.isEmpty()) 325 { 326 Condition condition = icon.getMetadataCondition(); 327 if (condition == Condition.AND) 328 { 329 return _matchesAndData (metadata, page); 330 } 331 else if (condition == Condition.OR) 332 { 333 return _matchesOrData (metadata, page); 334 } 335 } 336 return true; 337 } 338 339 private boolean _matchesProperties (SitemapIcon icon, PagesContainer page) 340 { 341 Map<String, String> properties = icon.getProperties(); 342 if (!properties.isEmpty()) 343 { 344 Condition condition = icon.getMetadataCondition(); 345 if (condition == Condition.AND) 346 { 347 return _matchesAndProperties(properties, page); 348 } 349 else if (condition == Condition.OR) 350 { 351 return _matchesOrProperties(properties, page); 352 } 353 } 354 return true; 355 } 356 357 private boolean _matchesAndProperties (Map<String, String> properties, PagesContainer page) 358 { 359 if (!(page instanceof JCRAmetysObject)) 360 { 361 return properties.size() == 0; 362 } 363 364 JCRAmetysObject jcrPage = (JCRAmetysObject) page; 365 Node node = jcrPage.getNode(); 366 367 try 368 { 369 for (String propertyName : properties.keySet()) 370 { 371 String valueToTest = properties.get(propertyName); 372 373 if (!node.hasProperty(propertyName)) 374 { 375 // property does not exits 376 return false; 377 } 378 379 Property property = node.getProperty(propertyName); 380 if (property.getDefinition().isMultiple()) 381 { 382 Value[] values = property.getValues(); 383 if (valueToTest.length() > 0 && values.length == 0) 384 { 385 // metadata exits but is empty 386 return false; 387 } 388 else if (valueToTest.length() > 0) 389 { 390 String[] results = new String[values.length]; 391 for (int i = 0; i < values.length; i++) 392 { 393 Value value = values[i]; 394 results[i] = value.getString(); 395 } 396 397 List<String> asList = Arrays.asList(results); 398 String[] valuesToTest = valueToTest.split(","); 399 for (String val : valuesToTest) 400 { 401 if (!asList.contains(val)) 402 { 403 // values do not contain all test values 404 return false; 405 } 406 } 407 } 408 } 409 else 410 { 411 String value = property.getValue().getString(); 412 if (valueToTest.length() > 0 && !valueToTest.equals(value)) 413 { 414 // value is not equals to test value 415 return false; 416 } 417 } 418 } 419 } 420 catch (RepositoryException e) 421 { 422 _logger.error("An error occurred while testing properties", e); 423 return false; 424 } 425 return true; 426 } 427 428 private boolean _matchesOrProperties (Map<String, String> properties, PagesContainer page) 429 { 430 if (!(page instanceof JCRAmetysObject)) 431 { 432 return properties.size() == 0; 433 } 434 435 JCRAmetysObject jcrPage = (JCRAmetysObject) page; 436 Node node = jcrPage.getNode(); 437 438 try 439 { 440 for (String propertyName : properties.keySet()) 441 { 442 String valueToTest = properties.get(propertyName); 443 444 if (node.hasProperty(propertyName)) 445 { 446 Property property = node.getProperty(propertyName); 447 if (property.getDefinition().isMultiple()) 448 { 449 Value[] values = property.getValues(); 450 if (valueToTest.length() == 0 && values.length > 0) 451 { 452 // multiple metadata exists and is not empty 453 return true; 454 } 455 else if (valueToTest.length() != 0) 456 { 457 String[] results = new String[values.length]; 458 for (int i = 0; i < values.length; i++) 459 { 460 Value value = values[i]; 461 results[i] = value.getString(); 462 } 463 464 List<String> asList = Arrays.asList(results); 465 String[] valuesToTest = valueToTest.split(","); 466 boolean findAll = true; 467 for (String val : valuesToTest) 468 { 469 if (!asList.contains(val)) 470 { 471 findAll = false; 472 } 473 } 474 if (findAll) 475 { 476 // values contain all test values 477 return true; 478 } 479 } 480 } 481 else 482 { 483 String value = property.getString(); 484 if (valueToTest.length() == 0 || valueToTest.equals(value)) 485 { 486 // value is equals to test value 487 return true; 488 } 489 } 490 } 491 } 492 } 493 catch (RepositoryException e) 494 { 495 _logger.error("An error occurred while testing properties", e); 496 return false; 497 } 498 499 return false; 500 } 501 502 @SuppressWarnings("unchecked") 503 boolean _matchesAndData (Map<String, String> data, ModelLessDataHolder dataHolder) 504 { 505 for (String dataPath : data.keySet()) 506 { 507 if (!dataHolder.hasValue(dataPath)) 508 { 509 // data does not exits 510 return false; 511 } 512 513 Object value = dataHolder.getValue(dataPath); 514 515 if (value instanceof Composite) 516 { 517 if (((Composite) value).getDataNames().isEmpty()) 518 { 519 // the composite data is empty 520 return false; 521 } 522 } 523 else if (value instanceof Repeater) 524 { 525 if (((Repeater) value).getSize() <= 0) 526 { 527 // the repeater is empty 528 return false; 529 } 530 } 531 else 532 { 533 String valueToTest = data.get(dataPath); 534 if (!valueToTest.isEmpty() && !dataHolder.hasNonEmptyValue(dataPath)) 535 { 536 // data exits but is empty 537 return false; 538 } 539 else if (!valueToTest.isEmpty()) 540 { 541 ElementType type = (ElementType) dataHolder.getType(dataPath); 542 if (dataHolder.isMultiple(dataPath)) 543 { 544 if (!_findAllValuesInMultipleData(valueToTest, value, type)) 545 { 546 // value does not contain all test values 547 return false; 548 } 549 } 550 else 551 { 552 if (!valueToTest.isEmpty() && !valueToTest.equals(type.toString(value))) 553 { 554 // value is not equals to test value 555 return false; 556 } 557 } 558 } 559 } 560 } 561 562 // All data have matched 563 return true; 564 } 565 566 boolean _matchesOrData (Map<String, String> data, ModelLessDataHolder dataHolder) 567 { 568 for (String dataPath : data.keySet()) 569 { 570 if (_matchesOrData(dataPath, data, dataHolder)) 571 { 572 return true; 573 } 574 } 575 576 return false; 577 } 578 579 @SuppressWarnings("unchecked") 580 private boolean _matchesOrData (String dataPath, Map<String, String> data, ModelLessDataHolder dataHolder) 581 { 582 if (!dataHolder.hasValue(dataPath)) 583 { 584 // data does not exists 585 return false; 586 } 587 588 Object value = dataHolder.getValue(dataPath); 589 590 if (value instanceof Composite) 591 { 592 if (!((Composite) value).getDataNames().isEmpty()) 593 { 594 // composite data is not empty, the condition matched 595 return true; 596 } 597 } 598 else if (value instanceof Repeater) 599 { 600 if (((Repeater) value).getSize() > 0) 601 { 602 // repeater data is not empty, the condition matched 603 return true; 604 } 605 } 606 else 607 { 608 String valueToTest = data.get(dataPath); 609 if (valueToTest.isEmpty()) 610 { 611 // No data to test 612 return true; 613 } 614 else if (dataHolder.hasNonEmptyValue(dataPath)) 615 { 616 ElementType type = (ElementType) dataHolder.getType(dataPath); 617 if (dataHolder.isMultiple(dataPath)) 618 { 619 if (_findAllValuesInMultipleData(valueToTest, value, type)) 620 { 621 // values contain all test values 622 return true; 623 } 624 } 625 else if (valueToTest.equals(type.toString(value))) 626 { 627 // value is equals to test value 628 return true; 629 } 630 } 631 } 632 633 return false; 634 } 635 636 private boolean _findAllValuesInMultipleData(String valueToTest, Object valueFromDataHolder, ElementType type) 637 { 638 List<String> valuesFromDataHolderAsString = new ArrayList<>(); 639 for (int i = 0; i < Array.getLength(valueFromDataHolder); i++) 640 { 641 @SuppressWarnings("unchecked") 642 String valueAsString = type.toString(Array.get(valueFromDataHolder, i)); 643 valuesFromDataHolderAsString.add(valueAsString); 644 } 645 646 String[] valuesToTest = valueToTest.split(","); 647 for (String value : valuesToTest) 648 { 649 if (!valuesFromDataHolderAsString.contains(value)) 650 { 651 return false; 652 } 653 } 654 655 return true; 656 } 657 658 class IconOrderComparator implements Comparator<SitemapIcon> 659 { 660 @Override 661 public int compare(SitemapIcon i1, SitemapIcon i2) 662 { 663 return i1.getOrder() - i2.getOrder(); 664 } 665 } 666 667}