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() 270 { 271 List<ClientSideElement> result = new ArrayList<>(); 272 273 result.addAll(_menuItems); 274 275 _getGalleryItems().stream() 276 .map(GalleryItem::getGroups) 277 .flatMap(List::stream) 278 .map(GalleryGroup::getItems) 279 .forEach(result::addAll); 280 281 return result; 282 } 283 284 /** 285 * Configure the galleries 286 * @param configuration the configuration 287 * @throws ConfigurationException If the configuration has an issue 288 */ 289 protected void _configureGalleries(Configuration configuration) throws ConfigurationException 290 { 291 for (Configuration galleryConfiguration : configuration.getChildren("gallery-item")) 292 { 293 GalleryItem galleryItem = new GalleryItem(); 294 295 for (Configuration gpConfiguration : galleryConfiguration.getChildren("gallery-group")) 296 { 297 galleryItem.addGroup(_configureGroupGallery(gpConfiguration)); 298 } 299 300 _galleryItems.add(galleryItem); 301 } 302 303 // FIXME 304 if (_galleryItems.size() > 0) 305 { 306 try 307 { 308 _getGalleryItemManager().initialize(); 309 } 310 catch (Exception e) 311 { 312 throw new ConfigurationException("Unable to lookup parameter local components", configuration, e); 313 } 314 } 315 } 316 317 /** 318 * Configure a group gallery 319 * @param configuration the configuration 320 * @return The configured group gallery 321 * @throws ConfigurationException If the configuration has an issue 322 */ 323 protected GalleryGroup _configureGroupGallery(Configuration configuration) throws ConfigurationException 324 { 325 I18nizableText label; 326 327 Configuration labelConfig = configuration.getChild("label"); 328 if (labelConfig.getAttributeAsBoolean("i18n", false)) 329 { 330 label = new I18nizableText("plugin." + _pluginName, labelConfig.getValue("")); 331 } 332 else 333 { 334 label = new I18nizableText(labelConfig.getValue("")); 335 } 336 337 GalleryGroup galleryGroup = new GalleryGroup(label); 338 339 for (Configuration itemConfig : configuration.getChildren("item")) 340 { 341 if (itemConfig.getAttribute("ref", null) != null) 342 { 343 galleryGroup.addItem(new UnresolvedItem(itemConfig.getAttribute("ref"), false)); 344 } 345 else 346 { 347 String id = itemConfig.getAttribute("id"); 348 DefaultConfiguration conf = new DefaultConfiguration("extension"); 349 conf.setAttribute("id", id); 350 conf.addChild(itemConfig.getChild("class")); 351 352 Map<String, List<String>> childDependencies = _configureDependencies(itemConfig); 353 _addDependencies(childDependencies); 354 355 if (itemConfig.getChild("right", false) != null) 356 { 357 conf.addChild(itemConfig.getChild("right")); 358 } 359 if (itemConfig.getChild("rights", false) != null) 360 { 361 conf.addChild(itemConfig.getChild("rights")); 362 } 363 364 _getGalleryItemManager().addComponent(_pluginName, null, id, StaticClientSideElement.class, conf); 365 366 galleryGroup.addItem(new UnresolvedItem(id, true)); 367 } 368 } 369 370 return galleryGroup; 371 } 372 373 374 /** 375 * Configure the items menu 376 * @param configuration the configuration 377 * @throws ConfigurationException If the configuration has an issue 378 */ 379 protected void _configureItemsMenu(Configuration configuration) throws ConfigurationException 380 { 381 for (Configuration menuItemConfiguration : configuration.getChildren("menu-items")) 382 { 383 for (Configuration itemConfig : menuItemConfiguration.getChildren("item")) 384 { 385 boolean isPrimary = itemConfig.getAttributeAsBoolean("primaryItem", false); 386 387 if (itemConfig.getAttribute("ref", null) != null) 388 { 389 _unresolvedMenuItems.add(new UnresolvedItem(itemConfig.getAttribute("ref"), false, isPrimary)); 390 } 391 else 392 { 393 String id = itemConfig.getAttribute("id"); 394 DefaultConfiguration conf = new DefaultConfiguration("extension"); 395 conf.setAttribute("id", id); 396 conf.addChild(itemConfig.getChild("class")); 397 398 Map<String, List<String>> childDependencies = _configureDependencies(itemConfig); 399 _addDependencies(childDependencies); 400 401 if (itemConfig.getChild("menu-items", false) != null || itemConfig.getChild("gallery-item", false) != null) 402 { 403 if (itemConfig.getChild("menu-items", false) != null) 404 { 405 conf.addChild(itemConfig.getChild("menu-items")); 406 } 407 408 if (itemConfig.getChild("gallery-item", false) != null) 409 { 410 conf.addChild(itemConfig.getChild("gallery-item")); 411 } 412 413 _menuItemManager.addComponent(_pluginName, null, id, SimpleMenu.class, conf); 414 } 415 else 416 { 417 _menuItemManager.addComponent(_pluginName, null, id, StaticClientSideElement.class, conf); 418 } 419 420 _unresolvedMenuItems.add(new UnresolvedItem(id, true, isPrimary)); 421 } 422 } 423 } 424 } 425 426 private void _resolveMenuItems () throws Exception 427 { 428 if (_unresolvedMenuItems != null) 429 { 430 _menuItemManager.initialize(); 431 432 for (UnresolvedItem unresolvedItem : _unresolvedMenuItems) 433 { 434 String id = unresolvedItem.getId(); 435 ClientSideElement element; 436 if (unresolvedItem.isLocalItem()) 437 { 438 try 439 { 440 element = _menuItemManager.lookup(id); 441 } 442 catch (ComponentException e) 443 { 444 throw new Exception("Unable to lookup client side element role: '" + id + "'", e); 445 } 446 } 447 else 448 { 449 element = _ribbonControlManager.getExtension(id); 450 } 451 452 if (unresolvedItem.isPrimary()) 453 { 454 _primaryMenuItem = element; 455 } 456 457 _menuItems.add(element); 458 459 _addDependencies(element.getDependencies()); 460 } 461 } 462 463 _unresolvedMenuItems = null; 464 } 465 466 private void _resolveGalleryItems() 467 { 468 for (GalleryItem item : _getGalleryItems()) 469 { 470 for (GalleryGroup group : item.getGroups()) 471 { 472 group.getItems(); 473 } 474 } 475 } 476 477 /** 478 * Add additional dependencies to the Menu, such as dependencies inherited from its menu items or gallery items. 479 * @param additionalDependencies The dependencies to add 480 * @throws ConfigurationException If an error occurs 481 */ 482 protected void _addDependencies(Map<String, List<String>> additionalDependencies) throws ConfigurationException 483 { 484 if (!additionalDependencies.isEmpty()) 485 { 486 for (Entry<String, List<String>> additionalDependency : additionalDependencies.entrySet()) 487 { 488 String key = additionalDependency.getKey(); 489 if (!_dependencies.containsKey(key)) 490 { 491 _dependencies.put(key, new ArrayList<String>()); 492 } 493 List<String> dependenciesList = _dependencies.get(key); 494 495 for (String dependency : additionalDependency.getValue()) 496 { 497 if (!dependenciesList.contains(dependency)) 498 { 499 dependenciesList.add(dependency); 500 } 501 } 502 } 503 } 504 } 505 506 /** 507 * Class representing a gallery item 508 * 509 */ 510 public class GalleryItem 511 { 512 private final List<GalleryGroup> _groups; 513 514 /** 515 * Constructor 516 */ 517 public GalleryItem() 518 { 519 _groups = new ArrayList<>(); 520 } 521 522 /** 523 * Add a group of this gallery 524 * @param group The gallery group to add 525 */ 526 public void addGroup (GalleryGroup group) 527 { 528 _groups.add(group); 529 } 530 531 /** 532 * Get gallery's groups 533 * @return The gallery's group 534 */ 535 public List<GalleryGroup> getGroups () 536 { 537 return _groups; 538 } 539 540 } 541 542 /** 543 * Class representing a gallery group 544 * 545 */ 546 public class GalleryGroup 547 { 548 private final I18nizableText _label; 549 private List<UnresolvedItem> _unresolvedGalleryItems; 550 private final List<ClientSideElement> _items; 551 552 /** 553 * Constructor 554 * @param label The group's label 555 */ 556 public GalleryGroup(I18nizableText label) 557 { 558 _label = label; 559 _items = new ArrayList<>(); 560 _unresolvedGalleryItems = new ArrayList<>(); 561 } 562 563 /** 564 * Add a new item to group 565 * @param item The item to add 566 */ 567 public void addItem (UnresolvedItem item) 568 { 569 _unresolvedGalleryItems.add(item); 570 } 571 572 /** 573 * Get the group's label 574 * @return The group's label 575 */ 576 public I18nizableText getLabel () 577 { 578 return _label; 579 } 580 581 /** 582 * Get the gallery item 583 * @return The gallery item 584 */ 585 public List<ClientSideElement> getItems () 586 { 587 try 588 { 589 _resolveGalleryItems(); 590 } 591 catch (Exception e) 592 { 593 throw new IllegalStateException("Unable to lookup client side element local components", e); 594 } 595 596 return _items; 597 } 598 599 private void _resolveGalleryItems () throws Exception 600 { 601 if (_unresolvedGalleryItems != null) 602 { 603 for (UnresolvedItem unresolvedItem : _unresolvedGalleryItems) 604 { 605 ClientSideElement element; 606 String id = unresolvedItem.getId(); 607 if (unresolvedItem.isLocalItem()) 608 { 609 try 610 { 611 element = _getGalleryItemManager().lookup(id); 612 } 613 catch (ComponentException e) 614 { 615 throw new Exception("Unable to lookup client side element role: '" + id + "'", e); 616 } 617 } 618 else 619 { 620 element = _ribbonControlManager.getExtension(id); 621 } 622 623 _items.add(element); 624 } 625 } 626 627 _unresolvedGalleryItems = null; 628 } 629 } 630 631 /** 632 * The unresolved item 633 * 634 */ 635 protected class UnresolvedItem 636 { 637 private final String _itemId; 638 private final boolean _local; 639 private final boolean _primary; 640 641 /** 642 * Constructor 643 * @param id The item id 644 * @param local true if it is a local item 645 */ 646 public UnresolvedItem(String id, boolean local) 647 { 648 _itemId = id; 649 _local = local; 650 _primary = false; 651 } 652 653 /** 654 * Constructor 655 * @param id The item id 656 * @param local true if it is a local item 657 * @param primary true if it is a primary item 658 */ 659 public UnresolvedItem(String id, boolean local, boolean primary) 660 { 661 _itemId = id; 662 _local = local; 663 _primary = primary; 664 } 665 666 /** 667 * Get the item id 668 * @return the item id 669 */ 670 public String getId () 671 { 672 return _itemId; 673 } 674 675 /** 676 * Return true if it is a local item 677 * @return true if it is a local item 678 */ 679 public boolean isLocalItem () 680 { 681 return _local; 682 } 683 684 /** 685 * Return true if it is a primary item 686 * @return true if it is a primary item 687 */ 688 public boolean isPrimary () 689 { 690 return _primary; 691 } 692 } 693}