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}