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