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