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