001/* 002 * Copyright 2013 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.core.ui; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.LinkedHashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.Map.Entry; 024 025import org.apache.avalon.framework.component.ComponentException; 026import org.apache.avalon.framework.configuration.Configuration; 027import org.apache.avalon.framework.configuration.ConfigurationException; 028import org.apache.avalon.framework.configuration.DefaultConfiguration; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.commons.lang3.StringUtils; 032import org.slf4j.LoggerFactory; 033 034import org.ametys.runtime.i18n.I18nizableText; 035import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 036 037/** 038 * This element creates a control button with a menu 039 */ 040public class SimpleMenu extends StaticClientSideElement implements MenuClientSideElement 041{ 042 /** The client side element component manager for menu items. */ 043 protected ThreadSafeComponentManager<ClientSideElement> _menuItemManager; 044 /** The ribbon control manager */ 045 protected RibbonControlsManager _ribbonControlManager; 046 /** The service manager */ 047 protected ServiceManager _smanager; 048 049 /** The menu items */ 050 protected List<ClientSideElement> _menuItems; 051 /** The gallery items */ 052 protected List<GalleryItem> _galleryItems; 053 /** The primary menu item */ 054 protected ClientSideElement _primaryMenuItem; 055 /** The unresolved menu items */ 056 protected List<UnresolvedItem> _unresolvedMenuItems; 057 058 /** The client side element component manager for gallery items. */ 059 private ThreadSafeComponentManager<ClientSideElement> _galleryItemManager; 060 061 @Override 062 public void service(ServiceManager smanager) throws ServiceException 063 { 064 super.service(smanager); 065 _smanager = smanager; 066 _ribbonControlManager = (RibbonControlsManager) smanager.lookup(RibbonControlsManager.ROLE); 067 } 068 069 @Override 070 public void configure(Configuration configuration) throws ConfigurationException 071 { 072 _menuItemManager = new ThreadSafeComponentManager<>(); 073 _menuItemManager.setLogger(LoggerFactory.getLogger("cms.plugin.threadsafecomponent")); 074 _menuItemManager.service(_smanager); 075 076 _initializeGalleryItemManager(); 077 078 super.configure(configuration); 079 080 _galleryItems = new ArrayList<>(); 081 082 _menuItems = new ArrayList<>(); 083 _unresolvedMenuItems = new ArrayList<>(); 084 085 _configureGalleries (configuration); 086 _configureItemsMenu(configuration); 087 } 088 089 @Override 090 protected String _configureClass(Configuration configuration) throws ConfigurationException 091 { 092 String jsClassName = configuration.getAttribute("name", ""); 093 if (StringUtils.isNotEmpty(jsClassName)) 094 { 095 if (getLogger().isDebugEnabled()) 096 { 097 getLogger().debug("Js class configured is '" + jsClassName + "'"); 098 } 099 } 100 return jsClassName; 101 } 102 103 /** 104 * Initialize the gallery item manager 105 */ 106 protected void _initializeGalleryItemManager() 107 { 108 if (_galleryItemManager != null) 109 { 110 _galleryItemManager.dispose(); 111 } 112 113 _galleryItemManager = new ThreadSafeComponentManager<>(); 114 _galleryItemManager.setLogger(LoggerFactory.getLogger("cms.plugin.threadsafecomponent")); 115 _galleryItemManager.service(_smanager); 116 } 117 118 119 /** 120 * Get the gallery item manager 121 * @return the gallery item manager 122 */ 123 protected ThreadSafeComponentManager<ClientSideElement> _getGalleryItemManager() 124 { 125 return _galleryItemManager; 126 } 127 128 /** 129 * Get the gallery items 130 * @return the gallery items 131 */ 132 protected List<GalleryItem> _getGalleryItems() 133 { 134 return _galleryItems; 135 } 136 137 @Override 138 public List<Script> getScripts(boolean ignoreRights, Map<String, Object> contextParameters) 139 { 140 try 141 { 142 // ensure all menu items have been resolved 143 _resolveMenuItems(); 144 145 // ensure all gallery items have been resolved 146 _resolveGalleryItems(); 147 } 148 catch (Exception e) 149 { 150 throw new IllegalStateException("Unable to lookup client side element local components", e); 151 } 152 153 // FIXME handle rights for workspace admin, here is a temporary workaround 154 if (ignoreRights || hasRight(getRights(contextParameters))) 155 { 156 List<Script> scripts = super.getScripts(ignoreRights, contextParameters); 157 Map<String, Object> parameters = new HashMap<>(); 158 List<ScriptFile> cssFiles = new ArrayList<>(); 159 List<ScriptFile> scriptFiles = new ArrayList<>(); 160 for (Script script : scripts) 161 { 162 cssFiles.addAll(script.getCSSFiles()); 163 scriptFiles.addAll(script.getScriptFiles()); 164 parameters.putAll(script.getParameters()); 165 } 166 167 String scriptClassName = _script.getScriptClassname(); 168 169 if (_primaryMenuItem != null) 170 { 171 List<Script> itemScripts = _primaryMenuItem.getScripts(ignoreRights, contextParameters); 172 for (Script script : itemScripts) 173 { 174 Map<String, Object> primaryParameters = script.getParameters(); 175 parameters.put("primary-menu-item-id", script.getId()); 176 for (String paramId : primaryParameters.keySet()) 177 { 178 if (!parameters.containsKey(paramId)) 179 { 180 parameters.put(paramId, primaryParameters.get(paramId)); 181 } 182 } 183 184 if (StringUtils.isEmpty(scriptClassName)) 185 { 186 scriptClassName = script.getScriptClassname(); 187 } 188 } 189 } 190 191 if (StringUtils.isNotBlank(scriptClassName)) 192 { 193 // Gallery items 194 _getGalleryItems(parameters, contextParameters); 195 196 // Menu items 197 _getMenuItems(parameters, contextParameters); 198 199 List<Script> result = new ArrayList<>(); 200 result.add(new Script(this.getId(), scriptClassName, scriptFiles, cssFiles, parameters)); 201 return result; 202 } 203 } 204 205 return new ArrayList<>(); 206 } 207 208 @Override 209 public Map<String, String> getRights(Map<String, Object> contextParameters) 210 { 211 Map<String, String> rights = super.getRights(contextParameters); 212 213 if (rights.size() == 0 && _primaryMenuItem != null) 214 { 215 return _primaryMenuItem.getRights(contextParameters); 216 } 217 218 return rights; 219 } 220 221 /** 222 * Get the gallery items 223 * @param parameters Contextual the parameters given to the control script class. 224 * @param contextualParameters Contextual parameters transmitted by the environment. 225 */ 226 @SuppressWarnings("unchecked") 227 protected void _getGalleryItems (Map<String, Object> parameters, Map<String, Object> contextualParameters) 228 { 229 List<GalleryItem> galleryItems = _getGalleryItems(); 230 231 if (galleryItems.size() > 0) 232 { 233 parameters.put("gallery-item", new LinkedHashMap<String, Object>()); 234 235 for (GalleryItem galleryItem : galleryItems) 236 { 237 Map<String, Object> galleryItemsParams = (Map<String, Object>) parameters.get("gallery-item"); 238 galleryItemsParams.put("gallery-groups", new ArrayList<>()); 239 240 for (GalleryGroup galleryGp : galleryItem.getGroups()) 241 { 242 List<Object> galleryGroups = (List<Object>) galleryItemsParams.get("gallery-groups"); 243 244 Map<String, Object> groupParams = new LinkedHashMap<>(); 245 246 I18nizableText label = galleryGp.getLabel(); 247 groupParams.put("label", label); 248 249 // Group items 250 List<String> gpItems = new ArrayList<>(); 251 for (ClientSideElement element : galleryGp.getItems()) 252 { 253 gpItems.add(element.getId()); 254 } 255 groupParams.put("items", gpItems); 256 257 galleryGroups.add(groupParams); 258 } 259 } 260 } 261 } 262 263 /** 264 * Get the menu items 265 * @param parameters Contextual the parameters given to the control script class. 266 * @param contextualParameters Contextual parameters transmitted by the environment. 267 */ 268 protected void _getMenuItems (Map<String, Object> parameters, Map<String, Object> contextualParameters) 269 { 270 if (_menuItems.size() > 0) 271 { 272 List<String> menuItems = new ArrayList<>(); 273 for (ClientSideElement element : _menuItems) 274 { 275 menuItems.add(element.getId()); 276 } 277 parameters.put("menu-items", menuItems); 278 } 279 } 280 281 @Override 282 public List<ClientSideElement> getReferencedClientSideElements(Map<String, Object> contextParameters) 283 { 284 List<ClientSideElement> result = new ArrayList<>(); 285 286 if (hasRight(getRights(contextParameters))) 287 { 288 result.addAll(_menuItems); 289 290 _getGalleryItems().stream() 291 .map(GalleryItem::getGroups) 292 .flatMap(List::stream) 293 .map(GalleryGroup::getItems) 294 .forEach(result::addAll); 295 } 296 297 return result; 298 } 299 300 /** 301 * Configure the galleries 302 * @param configuration the configuration 303 * @throws ConfigurationException If the configuration has an issue 304 */ 305 protected void _configureGalleries(Configuration configuration) throws ConfigurationException 306 { 307 for (Configuration galleryConfiguration : configuration.getChildren("gallery-item")) 308 { 309 GalleryItem galleryItem = new GalleryItem(); 310 311 for (Configuration gpConfiguration : galleryConfiguration.getChildren("gallery-group")) 312 { 313 galleryItem.addGroup(_configureGroupGallery(gpConfiguration)); 314 } 315 316 _galleryItems.add(galleryItem); 317 } 318 319 // FIXME 320 if (_galleryItems.size() > 0) 321 { 322 try 323 { 324 _getGalleryItemManager().initialize(); 325 } 326 catch (Exception e) 327 { 328 throw new ConfigurationException("Unable to lookup parameter local components", configuration, e); 329 } 330 } 331 } 332 333 /** 334 * Configure a group gallery 335 * @param configuration the configuration 336 * @return The configured group gallery 337 * @throws ConfigurationException If the configuration has an issue 338 */ 339 protected GalleryGroup _configureGroupGallery(Configuration configuration) throws ConfigurationException 340 { 341 I18nizableText label; 342 343 Configuration labelConfig = configuration.getChild("label"); 344 if (labelConfig.getAttributeAsBoolean("i18n", false)) 345 { 346 label = new I18nizableText("plugin." + _pluginName, labelConfig.getValue("")); 347 } 348 else 349 { 350 label = new I18nizableText(labelConfig.getValue("")); 351 } 352 353 GalleryGroup galleryGroup = new GalleryGroup(label); 354 355 for (Configuration itemConfig : configuration.getChildren("item")) 356 { 357 if (itemConfig.getAttribute("ref", null) != null) 358 { 359 galleryGroup.addItem(new UnresolvedItem(itemConfig.getAttribute("ref"), false)); 360 } 361 else 362 { 363 String id = itemConfig.getAttribute("id"); 364 DefaultConfiguration conf = new DefaultConfiguration("extension"); 365 conf.setAttribute("id", id); 366 conf.addChild(itemConfig.getChild("class")); 367 368 Map<String, List<String>> childDependencies = _configureDependencies(itemConfig); 369 _addDependencies(childDependencies); 370 371 if (itemConfig.getChild("right", false) != null) 372 { 373 conf.addChild(itemConfig.getChild("right")); 374 } 375 if (itemConfig.getChild("rights", false) != null) 376 { 377 conf.addChild(itemConfig.getChild("rights")); 378 } 379 380 _getGalleryItemManager().addComponent(_pluginName, null, id, StaticClientSideElement.class, conf); 381 382 galleryGroup.addItem(new UnresolvedItem(id, true)); 383 } 384 } 385 386 return galleryGroup; 387 } 388 389 390 /** 391 * Configure the items menu 392 * @param configuration the configuration 393 * @throws ConfigurationException If the configuration has an issue 394 */ 395 protected void _configureItemsMenu(Configuration configuration) throws ConfigurationException 396 { 397 for (Configuration menuItemConfiguration : configuration.getChildren("menu-items")) 398 { 399 for (Configuration itemConfig : menuItemConfiguration.getChildren("item")) 400 { 401 boolean isPrimary = itemConfig.getAttributeAsBoolean("primaryItem", false); 402 403 if (itemConfig.getAttribute("ref", null) != null) 404 { 405 _unresolvedMenuItems.add(new UnresolvedItem(itemConfig.getAttribute("ref"), false, isPrimary)); 406 } 407 else 408 { 409 String id = itemConfig.getAttribute("id"); 410 DefaultConfiguration conf = new DefaultConfiguration("extension"); 411 conf.setAttribute("id", id); 412 conf.addChild(itemConfig.getChild("class")); 413 414 Map<String, List<String>> childDependencies = _configureDependencies(itemConfig); 415 _addDependencies(childDependencies); 416 417 if (itemConfig.getChild("menu-items", false) != null || itemConfig.getChild("gallery-item", false) != null) 418 { 419 if (itemConfig.getChild("menu-items", false) != null) 420 { 421 conf.addChild(itemConfig.getChild("menu-items")); 422 } 423 424 if (itemConfig.getChild("gallery-item", false) != null) 425 { 426 conf.addChild(itemConfig.getChild("gallery-item")); 427 } 428 429 _menuItemManager.addComponent(_pluginName, null, id, SimpleMenu.class, conf); 430 } 431 else 432 { 433 _menuItemManager.addComponent(_pluginName, null, id, StaticClientSideElement.class, conf); 434 } 435 436 _unresolvedMenuItems.add(new UnresolvedItem(id, true, isPrimary)); 437 } 438 } 439 } 440 } 441 442 private void _resolveMenuItems () throws Exception 443 { 444 if (_unresolvedMenuItems != null) 445 { 446 _menuItemManager.initialize(); 447 448 for (UnresolvedItem unresolvedItem : _unresolvedMenuItems) 449 { 450 String id = unresolvedItem.getId(); 451 ClientSideElement element; 452 if (unresolvedItem.isLocalItem()) 453 { 454 try 455 { 456 element = _menuItemManager.lookup(id); 457 } 458 catch (ComponentException e) 459 { 460 throw new Exception("Unable to lookup client side element role: '" + id + "'", e); 461 } 462 } 463 else 464 { 465 element = _ribbonControlManager.getExtension(id); 466 } 467 468 if (unresolvedItem.isPrimary()) 469 { 470 _primaryMenuItem = element; 471 } 472 473 _menuItems.add(element); 474 475 _addDependencies(element.getDependencies()); 476 } 477 } 478 479 _unresolvedMenuItems = null; 480 } 481 482 private void _resolveGalleryItems() 483 { 484 for (GalleryItem item : _getGalleryItems()) 485 { 486 for (GalleryGroup group : item.getGroups()) 487 { 488 group.getItems(); 489 } 490 } 491 } 492 493 /** 494 * Add additional dependencies to the Menu, such as dependencies inherited from its menu items or gallery items. 495 * @param additionalDependencies The dependencies to add 496 * @throws ConfigurationException If an error occurs 497 */ 498 protected void _addDependencies(Map<String, List<String>> additionalDependencies) throws ConfigurationException 499 { 500 if (!additionalDependencies.isEmpty()) 501 { 502 for (Entry<String, List<String>> additionalDependency : additionalDependencies.entrySet()) 503 { 504 String key = additionalDependency.getKey(); 505 if (!_dependencies.containsKey(key)) 506 { 507 _dependencies.put(key, new ArrayList<String>()); 508 } 509 List<String> dependenciesList = _dependencies.get(key); 510 511 for (String dependency : additionalDependency.getValue()) 512 { 513 if (!dependenciesList.contains(dependency)) 514 { 515 dependenciesList.add(dependency); 516 } 517 } 518 } 519 } 520 } 521 522 /** 523 * Class representing a gallery item 524 * 525 */ 526 public class GalleryItem 527 { 528 private final List<GalleryGroup> _groups; 529 530 /** 531 * Constructor 532 */ 533 public GalleryItem() 534 { 535 _groups = new ArrayList<>(); 536 } 537 538 /** 539 * Add a group of this gallery 540 * @param group The gallery group to add 541 */ 542 public void addGroup (GalleryGroup group) 543 { 544 _groups.add(group); 545 } 546 547 /** 548 * Get gallery's groups 549 * @return The gallery's group 550 */ 551 public List<GalleryGroup> getGroups () 552 { 553 return _groups; 554 } 555 556 } 557 558 /** 559 * Class representing a gallery group 560 * 561 */ 562 public class GalleryGroup 563 { 564 private final I18nizableText _label; 565 private List<UnresolvedItem> _unresolvedGalleryItems; 566 private final List<ClientSideElement> _items; 567 568 /** 569 * Constructor 570 * @param label The group's label 571 */ 572 public GalleryGroup(I18nizableText label) 573 { 574 _label = label; 575 _items = new ArrayList<>(); 576 _unresolvedGalleryItems = new ArrayList<>(); 577 } 578 579 /** 580 * Add a new item to group 581 * @param item The item to add 582 */ 583 public void addItem (UnresolvedItem item) 584 { 585 _unresolvedGalleryItems.add(item); 586 } 587 588 /** 589 * Get the group's label 590 * @return The group's label 591 */ 592 public I18nizableText getLabel () 593 { 594 return _label; 595 } 596 597 /** 598 * Get the gallery item 599 * @return The gallery item 600 */ 601 public List<ClientSideElement> getItems () 602 { 603 try 604 { 605 _resolveGalleryItems(); 606 } 607 catch (Exception e) 608 { 609 throw new IllegalStateException("Unable to lookup client side element local components", e); 610 } 611 612 return _items; 613 } 614 615 private void _resolveGalleryItems () throws Exception 616 { 617 if (_unresolvedGalleryItems != null) 618 { 619 for (UnresolvedItem unresolvedItem : _unresolvedGalleryItems) 620 { 621 ClientSideElement element; 622 String id = unresolvedItem.getId(); 623 if (unresolvedItem.isLocalItem()) 624 { 625 try 626 { 627 element = _getGalleryItemManager().lookup(id); 628 } 629 catch (ComponentException e) 630 { 631 throw new Exception("Unable to lookup client side element role: '" + id + "'", e); 632 } 633 } 634 else 635 { 636 element = _ribbonControlManager.getExtension(id); 637 } 638 639 _items.add(element); 640 } 641 } 642 643 _unresolvedGalleryItems = null; 644 } 645 } 646 647 /** 648 * The unresolved item 649 * 650 */ 651 protected class UnresolvedItem 652 { 653 private final String _itemId; 654 private final boolean _local; 655 private final boolean _primary; 656 657 /** 658 * Constructor 659 * @param id The item id 660 * @param local true if it is a local item 661 */ 662 public UnresolvedItem(String id, boolean local) 663 { 664 _itemId = id; 665 _local = local; 666 _primary = false; 667 } 668 669 /** 670 * Constructor 671 * @param id The item id 672 * @param local true if it is a local item 673 * @param primary true if it is a primary item 674 */ 675 public UnresolvedItem(String id, boolean local, boolean primary) 676 { 677 _itemId = id; 678 _local = local; 679 _primary = primary; 680 } 681 682 /** 683 * Get the item id 684 * @return the item id 685 */ 686 public String getId () 687 { 688 return _itemId; 689 } 690 691 /** 692 * Return true if it is a local item 693 * @return true if it is a local item 694 */ 695 public boolean isLocalItem () 696 { 697 return _local; 698 } 699 700 /** 701 * Return true if it is a primary item 702 * @return true if it is a primary item 703 */ 704 public boolean isPrimary () 705 { 706 return _primary; 707 } 708 } 709}