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