001/*
002 *  Copyright 2010 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.web.clientsideelement;
017
018import java.util.ArrayList;
019import java.util.Comparator;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024import java.util.TreeMap;
025import java.util.TreeSet;
026import java.util.stream.Collectors;
027
028import org.apache.avalon.framework.configuration.Configuration;
029import org.apache.avalon.framework.configuration.ConfigurationException;
030import org.apache.avalon.framework.configuration.DefaultConfiguration;
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033
034import org.ametys.core.right.RightManager.RightResult;
035import org.ametys.core.ui.Callable;
036import org.ametys.core.ui.StaticClientSideElement;
037import org.ametys.core.user.UserIdentity;
038import org.ametys.core.util.I18nUtils;
039import org.ametys.core.util.I18nizableTextKeyComparator;
040import org.ametys.plugins.repository.AmetysObjectResolver;
041import org.ametys.runtime.i18n.I18nizableText;
042import org.ametys.web.repository.page.ModifiablePage;
043import org.ametys.web.repository.page.Page;
044import org.ametys.web.repository.page.PageDAO;
045import org.ametys.web.service.Service;
046import org.ametys.web.service.ServiceExtensionPoint;
047
048/**
049 * Menu that lists the services
050 */
051public class ServiceMenu extends AbstractPageMenu
052{
053    /** The list of content types */
054    protected ServiceExtensionPoint _serviceExtensionPoint;
055    /** The service assignment handler */
056    protected PageDAO _pageDAO;
057    /** The i18n utils */
058    protected I18nUtils _i18nUtils;
059    
060    private boolean _servicesInitialized;
061    
062    @Override
063    public void service(ServiceManager smanager) throws ServiceException
064    {
065        super.service(smanager);
066        
067        _serviceExtensionPoint = (ServiceExtensionPoint) smanager.lookup(ServiceExtensionPoint.ROLE);
068        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
069        _pageDAO = (PageDAO) smanager.lookup(PageDAO.ROLE);
070        _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE);
071    }
072    
073    @Override
074    protected Script _configureScript(Configuration configuration) throws ConfigurationException
075    {
076        Script script = super._configureScript(configuration);
077        
078        for (String serviceId : _serviceExtensionPoint.getExtensionsIds())
079        {
080            Service service = _serviceExtensionPoint.getExtension(serviceId);
081            
082            script.getCSSFiles().addAll(service.getCSSFiles());
083            script.getCSSFiles().addAll(service.getParametersScript().getCSSFiles());
084            script.getScriptFiles().addAll(service.getParametersScript().getScriptFiles());
085        }
086        
087        return script;
088    }
089    
090    @Override
091    public List<Script> getScripts(boolean ignoreRights, Map<String, Object> contextParameters)
092    {
093        if (_serviceExtensionPoint.getExtensionsIds().size() == 0)
094        {
095            // Hide menu if there is no service
096            return new ArrayList<>();
097        }
098
099        return super.getScripts(ignoreRights, contextParameters);
100    }
101    
102    /**
103     * Get the available templates for pages
104     * @param pageId The id of the page
105     * @param zoneName the name of the zone
106     * @return the list of templates' name
107     */
108    @Callable
109    public Map<String, Object> getAvailableServices (String pageId, String zoneName)
110    {
111        Map<String, Object> result = new HashMap<>();
112        
113        List<String> services = new ArrayList<>();
114        
115        Page page = _resolver.resolveById(pageId);
116        
117        if (!(page instanceof ModifiablePage))
118        {
119            Map<String, Object> pageParams = getPageDefaultParameters(page);
120            pageParams.put("description", getNoModifiablePageDescription(page));
121
122            result.put("nomodifiable-page", pageParams);
123        }
124        else if (!hasRight(page))
125        {
126            Map<String, Object> pageParams = getPageDefaultParameters(page);
127            pageParams.put("description", getNoRightPageDescription(page));
128
129            @SuppressWarnings("unchecked")
130            List<Map<String, Object>> norightPages = (List<Map<String, Object>>) result.get("noright-pages");
131            norightPages.add(pageParams);
132        }
133        else
134        {
135            services = _pageDAO.getAvailableServices(pageId, zoneName).stream()
136                            .map(info -> (String) info.get("id"))
137                            .collect(Collectors.toList());
138        }
139        
140        result.put("services", services);
141        return result;
142    }
143    
144    @Override
145    protected void _getGalleryItems(Map<String, Object> parameters, Map<String, Object> contextualParameters)
146    {
147        try
148        {
149            _lazyInitializeServiceGallery();
150        }
151        catch (Exception e)
152        {
153            throw new IllegalStateException("Unable to lookup client side element local components", e);
154        }
155        
156        super._getGalleryItems(parameters, contextualParameters);
157    }
158    
159    private synchronized void _lazyInitializeServiceGallery() throws ConfigurationException
160    {
161        if (!_servicesInitialized)
162        {
163            Map<I18nizableText, Set<Service>> servicesByGroup = _getServicesByGroup();
164            
165            if (servicesByGroup.size() > 0)
166            {
167                GalleryItem galleryItem = new GalleryItem();
168                
169                for (I18nizableText groupLabel : servicesByGroup.keySet())
170                {
171                    GalleryGroup galleryGroup = new GalleryGroup(groupLabel);
172                    galleryItem.addGroup(galleryGroup);
173                    
174                    Set<Service> cTypes = servicesByGroup.get(groupLabel);
175                    for (Service service : cTypes)
176                    {
177                        String id = this.getId() + "-" + service.getId();
178                        
179                        Configuration conf = _getServiceConfiguration (id, service);
180                        _getGalleryItemManager().addComponent(_pluginName, null, id, StaticClientSideElement.class, conf);
181                        galleryGroup.addItem(new UnresolvedItem(id, true));
182                    }
183                }
184                
185                _galleryItems.add(galleryItem);
186            }
187            
188            if (_galleryItems.size() > 0)
189            {
190                try
191                {
192                    _getGalleryItemManager().initialize();
193                }
194                catch (Exception e)
195                {
196                    throw new ConfigurationException("Unable to lookup parameter local components", e);
197                }
198            }
199        }
200        
201        _servicesInitialized = true;
202    }
203    
204    /**
205     * Get the list of services classified by groups
206     * @return The content types
207     */
208    protected Map<I18nizableText, Set<Service>> _getServicesByGroup ()
209    {
210        Map<I18nizableText, Set<Service>> groups = new TreeMap<>(new I18nizableTextKeyComparator());
211        
212        if (this._script.getParameters().get("services") != null)
213        {
214            String[] serviceIds = ((String) this._script.getParameters().get("services")).split(",");
215            
216            for (String serviceId : serviceIds)
217            {
218                Service service = _serviceExtensionPoint.getExtension(serviceId);
219                
220                if (isValidService(service))
221                {
222                    addService (service, groups);
223                }
224            }
225        }
226        else
227        {
228            Set<String> serviceIds = _serviceExtensionPoint.getExtensionsIds();
229            
230            for (String serviceId : serviceIds)
231            {
232                Service service = _serviceExtensionPoint.getExtension(serviceId);
233                
234                if (isValidService(service))
235                {
236                    addService (service, groups);
237                }
238            }
239        }
240        
241        return groups;
242    }
243    
244    /**
245     * Add service to groups
246     * @param service The service
247     * @param groups The groups
248     */
249    protected void addService (Service service, Map<I18nizableText, Set<Service>> groups)
250    {
251        I18nizableText group = service.getCategory();
252        if (group.isI18n() && group.getKey().isEmpty()
253            || !group.isI18n() && group.getLabel().isEmpty())
254        {
255            group = new I18nizableText("plugin.web", "PLUGINS_WEB_SERVICE_CATEGORY_90_OTHERS");
256        }
257        
258        if (!groups.containsKey(group))
259        {
260            groups.put(group, new TreeSet<>(new ServiceComparator()));
261        }
262        
263        Set<Service> services = groups.get(group);
264        services.add(service);
265    }
266    
267    
268    /**
269     * Determines if the service is a valid service for the gallery
270     * @param service The service
271     * @return true if it is a valid service
272     */
273    protected boolean isValidService (Service service)
274    {
275        return !service.isPrivate();
276    }
277    
278    /**
279     * Get the configuration of the service item
280     * @param id The id of item
281     * @param service The service
282     * @return The configuration
283     */
284    protected Configuration _getServiceConfiguration (String id, Service service)
285    {
286        DefaultConfiguration conf = new DefaultConfiguration("extension");
287        conf.setAttribute("id", id);
288        
289        DefaultConfiguration classConf = new DefaultConfiguration("class");
290        classConf.setAttribute("name", "Ametys.ribbon.element.ui.ButtonController");
291        
292        // Label
293        DefaultConfiguration labelConf = new DefaultConfiguration("label");
294        I18nizableText label = service.getLabel();
295        if (label.isI18n())
296        {
297            labelConf.setAttribute("i18n", "true");
298            labelConf.setValue(label.getCatalogue() + ":" + label.getKey());
299        }
300        else
301        {
302            labelConf.setValue(label.getLabel());
303        }
304        classConf.addChild(labelConf);
305        
306        // Description
307        DefaultConfiguration descConf = new DefaultConfiguration("description");
308        I18nizableText description = service.getDescription();
309        if (description.isI18n())
310        {
311            descConf.setAttribute("i18n", "true");
312            descConf.setValue(description.getCatalogue() + ":" + description.getKey());
313        }
314        else
315        {
316            descConf.setValue(description.getLabel());
317        }
318        classConf.addChild(descConf);
319        
320        // Service id
321        DefaultConfiguration idConf = new DefaultConfiguration("serviceId");
322        idConf.setValue(service.getId());
323        classConf.addChild(idConf);
324        
325        // Icons or glyph
326        if (service.getIconGlyph() != null)
327        {
328            DefaultConfiguration iconGlyphConf = new DefaultConfiguration("icon-glyph");
329            iconGlyphConf.setValue(service.getIconGlyph());
330            classConf.addChild(iconGlyphConf);
331        }
332        if (service.getIconDecorator() != null)
333        {
334            DefaultConfiguration iconDecoratorConf = new DefaultConfiguration("icon-decorator");
335            iconDecoratorConf.setValue(service.getIconDecorator());
336            classConf.addChild(iconDecoratorConf);
337        }
338        if (service.getSmallIcon() != null)
339        {
340            DefaultConfiguration iconSmallConf = new DefaultConfiguration("icon-small");
341            iconSmallConf.setValue(service.getSmallIcon());
342            classConf.addChild(iconSmallConf);
343            DefaultConfiguration iconMediumConf = new DefaultConfiguration("icon-medium");
344            iconMediumConf.setValue(service.getMediumIcon());
345            classConf.addChild(iconMediumConf);
346            DefaultConfiguration iconLargeConf = new DefaultConfiguration("icon-large");
347            iconLargeConf.setValue(service.getLargeIcon());
348            classConf.addChild(iconLargeConf);
349        }
350        
351        // Parameters action
352        DefaultConfiguration paramsAction = new DefaultConfiguration("params-action");
353        paramsAction.setValue(service.getParametersScript().getScriptClassname());
354        classConf.addChild(paramsAction);
355        
356        // Common configuration
357        @SuppressWarnings("unchecked")
358        Map<String, Object> commonConfig = (Map<String, Object>) this._script.getParameters().get("items-config");
359        for (String tagName : commonConfig.keySet())
360        {
361            DefaultConfiguration c = new DefaultConfiguration(tagName);
362            c.setValue(String.valueOf(commonConfig.get(tagName)));
363            classConf.addChild(c);
364        }
365        
366        conf.addChild(classConf);
367        return conf;
368    }
369    
370    class ServiceComparator implements Comparator<Service>
371    {
372        @Override
373        public int compare(Service s1, Service s2)
374        {
375            I18nizableText t1 = s1.getLabel();
376            I18nizableText t2 = s2.getLabel();
377            
378            String str1 = t1.isI18n() ? t1.getKey() : t1.getLabel();
379            String str2 = t2.isI18n() ? t2.getKey() : t2.getLabel();
380            
381            int compareTo = str1.toString().compareTo(str2.toString());
382            if (compareTo == 0)
383            {
384                // Content types have same keys but there are not equals, so do not return 0 to add it in TreeSet
385                // Indeed, in a TreeSet implementation two elements that are equal by the method compareTo are, from the standpoint of the set, equal 
386                return 1;
387            }
388            return compareTo;
389        }
390    }
391    
392    /**
393     * Test if the current user has the right needed by the service to create it.
394     * @param service the service.
395     * @param page the current page.
396     * @return true if the user has the right needed, false otherwise.
397     */
398    protected boolean hasRight(Service service, Page page)
399    {
400        boolean hasRight = false;
401        
402        if (service != null)
403        {
404            String right = service.getRight();
405            
406            if (right == null)
407            {
408                hasRight = true;
409            }
410            else
411            {
412                UserIdentity user = _currentUserProvider.getUser();
413                hasRight = _rightManager.hasRight(user, right, page) == RightResult.RIGHT_ALLOW;
414            }
415        }
416        
417        return hasRight;
418    }
419    
420}