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()) || (!group.isI18n() && group.getLabel().isEmpty()))
253        {
254            group = new I18nizableText("plugin.web", "PLUGINS_WEB_SERVICE_CATEGORY_90_OTHERS");
255        }
256        
257        if (!groups.containsKey(group))
258        {
259            groups.put(group, new TreeSet<>(new ServiceComparator()));
260        }
261        
262        Set<Service> services = groups.get(group);
263        services.add(service);
264    }
265    
266    
267    /**
268     * Determines if the service is a valid service for the gallery
269     * @param service The service
270     * @return true if it is a valid service
271     */
272    protected boolean isValidService (Service service)
273    {
274        return !service.isPrivate();
275    }
276    
277    /**
278     * Get the configuration of the service item
279     * @param id The id of item
280     * @param service The service
281     * @return The configuration
282     */
283    protected Configuration _getServiceConfiguration (String id, Service service)
284    {
285        DefaultConfiguration conf = new DefaultConfiguration("extension");
286        conf.setAttribute("id", id);
287        
288        DefaultConfiguration classConf = new DefaultConfiguration("class");
289        classConf.setAttribute("name", "Ametys.ribbon.element.ui.ButtonController");
290        
291        // Label
292        DefaultConfiguration labelConf = new DefaultConfiguration("label");
293        I18nizableText label = service.getLabel();
294        if (label.isI18n())
295        {
296            labelConf.setAttribute("i18n", "true");
297            labelConf.setValue(label.getCatalogue() + ":" + label.getKey());
298        }
299        else
300        {
301            labelConf.setValue(label.getLabel());
302        }
303        classConf.addChild(labelConf);
304        
305        // Description
306        DefaultConfiguration descConf = new DefaultConfiguration("description");
307        I18nizableText description = service.getLabel();
308        if (description.isI18n())
309        {
310            descConf.setAttribute("i18n", "true");
311            descConf.setValue(description.getCatalogue() + ":" + description.getKey());
312        }
313        else
314        {
315            descConf.setValue(description.getLabel());
316        }
317        classConf.addChild(descConf);
318        
319        // Service id
320        DefaultConfiguration idConf = new DefaultConfiguration("serviceId");
321        idConf.setValue(service.getId());
322        classConf.addChild(idConf);
323        
324        // Icons or glyph
325        if (service.getIconGlyph() != null)
326        {
327            DefaultConfiguration iconGlyphConf = new DefaultConfiguration("icon-glyph");
328            iconGlyphConf.setValue(service.getIconGlyph());
329            classConf.addChild(iconGlyphConf);
330        }
331        if (service.getIconDecorator() != null)
332        {
333            DefaultConfiguration iconDecoratorConf = new DefaultConfiguration("icon-decorator");
334            iconDecoratorConf.setValue(service.getIconDecorator());
335            classConf.addChild(iconDecoratorConf);
336        }
337        if (service.getSmallIcon() != null)
338        {
339            DefaultConfiguration iconSmallConf = new DefaultConfiguration("icon-small");
340            iconSmallConf.setValue(service.getSmallIcon());
341            classConf.addChild(iconSmallConf);
342            DefaultConfiguration iconMediumConf = new DefaultConfiguration("icon-medium");
343            iconMediumConf.setValue(service.getMediumIcon());
344            classConf.addChild(iconMediumConf);
345            DefaultConfiguration iconLargeConf = new DefaultConfiguration("icon-large");
346            iconLargeConf.setValue(service.getLargeIcon());
347            classConf.addChild(iconLargeConf);
348        }
349        
350        // Parameters action
351        DefaultConfiguration paramsAction = new DefaultConfiguration("params-action");
352        paramsAction.setValue(service.getParametersScript().getScriptClassname());
353        classConf.addChild(paramsAction);
354        
355        // Common configuration
356        @SuppressWarnings("unchecked")
357        Map<String, Object> commonConfig = (Map<String, Object>) this._script.getParameters().get("items-config");
358        for (String tagName : commonConfig.keySet())
359        {
360            DefaultConfiguration c = new DefaultConfiguration(tagName);
361            c.setValue(String.valueOf(commonConfig.get(tagName)));
362            classConf.addChild(c);
363        }
364        
365        conf.addChild(classConf);
366        return conf;
367    }
368    
369    class ServiceComparator implements Comparator<Service>
370    {
371        @Override
372        public int compare(Service s1, Service s2)
373        {
374            I18nizableText t1 = s1.getLabel();
375            I18nizableText t2 = s2.getLabel();
376            
377            String str1 = t1.isI18n() ? t1.getKey() : t1.getLabel();
378            String str2 = t2.isI18n() ? t2.getKey() : t2.getLabel();
379            
380            int compareTo = str1.toString().compareTo(str2.toString());
381            if (compareTo == 0)
382            {
383                // Content types have same keys but there are not equals, so do not return 0 to add it in TreeSet
384                // Indeed, in a TreeSet implementation two elements that are equal by the method compareTo are, from the standpoint of the set, equal 
385                return 1;
386            }
387            return compareTo;
388        }
389    }
390    
391    /**
392     * Test if the current user has the right needed by the service to create it.
393     * @param service the service.
394     * @param page the current page.
395     * @return true if the user has the right needed, false otherwise.
396     */
397    protected boolean hasRight(Service service, Page page)
398    {
399        boolean hasRight = false;
400        
401        if (service != null)
402        {
403            String right = service.getRight();
404            
405            if (right == null)
406            {
407                hasRight = true;
408            }
409            else
410            {
411                UserIdentity user = _currentUserProvider.getUser();
412                hasRight = _rightManager.hasRight(user, right, page) == RightResult.RIGHT_ALLOW;
413            }
414        }
415        
416        return hasRight;
417    }
418    
419}