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