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.service;
017
018import java.util.ArrayList;
019import java.util.Collections;
020import java.util.HashMap;
021import java.util.LinkedHashMap;
022import java.util.List;
023import java.util.Map;
024
025import org.apache.avalon.framework.activity.Disposable;
026import org.apache.avalon.framework.configuration.Configurable;
027import org.apache.avalon.framework.configuration.Configuration;
028import org.apache.avalon.framework.configuration.ConfigurationException;
029import org.apache.avalon.framework.context.Context;
030import org.apache.avalon.framework.context.ContextException;
031import org.apache.avalon.framework.context.Contextualizable;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.avalon.framework.service.Serviceable;
035import org.apache.solr.common.SolrInputDocument;
036
037import org.ametys.cms.content.indexing.solr.SolrContentIndexer;
038import org.ametys.core.ui.ClientSideElement.Script;
039import org.ametys.core.ui.ClientSideElement.ScriptFile;
040import org.ametys.plugins.core.ui.util.ConfigurationHelper;
041import org.ametys.runtime.i18n.I18nizableText;
042import org.ametys.runtime.parameter.AbstractParameterParser;
043import org.ametys.runtime.parameter.Enumerator;
044import org.ametys.runtime.parameter.ParameterHelper;
045import org.ametys.runtime.parameter.ParameterHelper.ParameterType;
046import org.ametys.runtime.parameter.Validator;
047import org.ametys.runtime.plugin.component.AbstractLogEnabled;
048import org.ametys.runtime.plugin.component.PluginAware;
049import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
050import org.ametys.web.repository.page.Page;
051import org.ametys.web.repository.page.ZoneItem;
052
053/**
054 * Class representing a business service. <br>
055 * A service is identified by an id and a Cocoon-URL.<br>
056 * This URL corresponds to a pipeline called by a page template.<br>
057 * URL must be relative to the sitemap of the plugin containing the service.
058 */
059public class StaticService extends AbstractLogEnabled implements Service, Contextualizable, Configurable, PluginAware, Serviceable, Disposable
060{
061    /** The plugin name */
062    protected String _pluginName;
063    /** The feature name */
064    protected String _featureName;
065    /** The service manager */
066    protected ServiceManager _manager;
067    /** The context. */
068    protected Context _context;
069    /** The script configured */
070    protected Script _paramsScript;
071    
072    private String _id;
073    private I18nizableText _label;
074    private I18nizableText _description;
075    private I18nizableText _category;
076    
077    private String _iconGlyph;
078    private String _iconDecorator;
079    private String _smallIcon;
080    private String _mediumIcon;
081    private String _largeIcon;
082    private List<ScriptFile> _cssFiles;
083    
084    private boolean _isCacheable;
085    private boolean _isPrivate;
086    /** The right needed to create an instance of this service, blank or null if no right is needed. */
087    private String _right;
088    
089    private String _url;
090    private Map<String, ServiceParameterOrRepeater> _parameters;
091    private List<ServiceParameterGroup> _groups;
092    
093    private List<String> _parametersToIndex;
094    
095    // ComponentManager for validators
096    private ThreadSafeComponentManager<Validator> _validatorManager;
097    
098    // ComponentManager for enumerators
099    private ThreadSafeComponentManager<Enumerator> _enumeratorManager;
100
101    @Override
102    public void service(ServiceManager smanager) throws ServiceException
103    {
104        _manager = smanager;
105    }
106
107    @Override
108    public void contextualize(Context context) throws ContextException
109    {
110        _context = context;
111    }
112    
113    @Override
114    public void dispose()
115    {
116        _validatorManager.dispose();
117        _validatorManager = null;
118        
119        _enumeratorManager.dispose();
120        _enumeratorManager = null;
121    }
122    
123    @Override
124    public void configure(Configuration configuration) throws ConfigurationException
125    {
126        _validatorManager = new ThreadSafeComponentManager<>();
127        _validatorManager.setLogger(getLogger());
128        _validatorManager.contextualize(_context);
129        _validatorManager.service(_manager);
130        
131        _enumeratorManager = new ThreadSafeComponentManager<>();
132        _enumeratorManager.setLogger(getLogger());
133        _enumeratorManager.contextualize(_context);
134        _enumeratorManager.service(_manager);
135        
136        _label = _parseI18nizableText(configuration, "label");
137        _description = _parseI18nizableText(configuration, "description");
138        _category = _parseI18nizableText(configuration, "category");
139        
140        _isCacheable = configuration.getChild("cacheable").getValueAsBoolean(false);
141        _isPrivate = configuration.getChild("private").getValueAsBoolean(false);
142        _right = configuration.getChild("right").getValue(null);
143
144        this._iconGlyph = configuration.getChild("thumbnail").getChild("glyph").getValue(null);
145        this._iconDecorator = configuration.getChild("thumbnail").getChild("decorator").getValue(null);
146        this._smallIcon = _configureThumbnail(configuration.getChild("thumbnail").getChild("small"), "/plugins/web/resources/img/service/servicepage_16.png");
147        this._mediumIcon = _configureThumbnail(configuration.getChild("thumbnail").getChild("medium"), "/plugins/web/resources/img/service/servicepage_32.png");
148        this._largeIcon = _configureThumbnail(configuration.getChild("thumbnail").getChild("marge"), "/plugins/web/resources/img/service/servicepage_50.png");
149
150        _cssFiles = _configureImports(configuration.getChild("css"));
151        _paramsScript = _configureScript(configuration.getChild("parameters"));
152        
153        _url = configuration.getChild("url").getValue();
154        _parameters = new LinkedHashMap<>();
155        _groups = new ArrayList<>();
156        
157        ServiceParameterParser serviceParameterParser = new ServiceParameterParser(_enumeratorManager, _validatorManager);
158        ServiceParameterOrRepeaterParser serviceParamOrRepeaterParser = new ServiceParameterOrRepeaterParser(serviceParameterParser);
159        
160        configureParameters(configuration.getChild("parameters"), serviceParamOrRepeaterParser);
161        
162        try
163        {
164            serviceParameterParser.lookupComponents();
165        }
166        catch (Exception e)
167        {
168            throw new ConfigurationException("Unable to lookup parameter local components", configuration, e);
169        }
170        
171        configureIndexation(configuration.getChild("indexation"));
172    }
173    
174    /**
175     * Configure the service parameters.
176     * @param configuration the service configuration.
177     * @param serviceParameterParser the parser.
178     * @throws ConfigurationException if an error occurs.
179     */
180    protected void configureParameters(Configuration configuration, ServiceParameterOrRepeaterParser serviceParameterParser) throws ConfigurationException
181    {
182        Configuration[] groupConfigurations = configuration.getChildren("group");
183        if (groupConfigurations.length > 0)
184        {
185            // Has groups.
186            for (Configuration groupConfiguration : groupConfigurations)
187            {
188                ServiceParameterGroup group = new ServiceParameterGroup();
189                _groups.add(group);
190                
191                configureParameterGroup(group, groupConfiguration, serviceParameterParser);
192            }
193        }
194        else
195        {
196            // No group tag, create a single group and put all params in it.
197            ServiceParameterGroup group = new ServiceParameterGroup();
198            _groups.add(group);
199            
200            configureParameterGroup(group, configuration, serviceParameterParser);
201        }
202    }
203    
204    /**
205     * Configure the indexation process.<br>
206     * This class only allow to index the value of some parameters (not repeaters).
207     * @param configuration the indexation configuration.
208     * @throws ConfigurationException if an error occurs.
209     */
210    protected void configureIndexation(Configuration configuration) throws ConfigurationException
211    {
212        _parametersToIndex = new ArrayList<>();
213        
214        for (Configuration config : configuration.getChildren("parameter"))
215        {
216            String id = config.getValue("");
217            ServiceParameterOrRepeater param = _parameters.get(id);
218            
219            if (param == null || !(param instanceof ServiceParameter))
220            {
221                if (getLogger().isWarnEnabled())
222                {
223                    getLogger().warn("Invalid indexation configuration for service " + _id + " : there's no parameter '" + id + "'");
224                }
225            }
226            else
227            {
228                _parametersToIndex.add(id);
229            }
230        }
231    }
232    
233    /**
234     * Configure the service parameters.
235     * @param group the group to put the params in.
236     * @param configuration the service configuration.
237     * @param serviceParameterParser the parser.
238     * @throws ConfigurationException if an error occurs.
239     */
240    protected void configureParameterGroup(ServiceParameterGroup group, Configuration configuration, ServiceParameterOrRepeaterParser serviceParameterParser) throws ConfigurationException
241    {
242        for (Configuration paramConfiguration : configuration.getChildren())
243        {
244            String paramName = paramConfiguration.getName();
245            if (paramName.equals("parameter") || paramName.equals("repeater"))
246            {
247                ServiceParameterOrRepeater serviceParameter = serviceParameterParser.parse(_manager, _pluginName, paramConfiguration);
248                
249                // Add the param or repeater to the parameter map as well as to his group.
250                _parameters.put(serviceParameter.getId(), serviceParameter);
251                group.add(serviceParameter);
252            }
253        }
254    }
255    
256    @Override
257    public void setPluginInfo(String pluginName, String featureName, String id)
258    {
259        _pluginName = pluginName;
260        _featureName = featureName;
261        _id = id;
262    }
263
264    @Override
265    public String getPluginName()
266    {
267        return _pluginName;
268    }
269
270    @Override
271    public String getId()
272    {
273        return _id;
274    }
275    
276    @Override
277    public boolean isCacheable(Page currentPage, ZoneItem zoneItem)
278    {
279        return _isCacheable;
280    }
281    
282    @Override
283    public I18nizableText getLabel()
284    {
285        return _label;
286    }
287    
288    @Override
289    public I18nizableText getDescription()
290    {
291        return _description;
292    }
293    
294    @Override
295    public I18nizableText getCategory()
296    {
297        return _category;
298    }
299    
300    @Override
301    public String getIconGlyph()
302    {
303        return _iconGlyph;
304    }
305    
306    @Override
307    public String getIconDecorator()
308    {
309        return _iconDecorator;
310    }
311    
312    @Override
313    public String getSmallIcon()
314    {
315        return _smallIcon;
316    }
317    
318    @Override
319    public String getMediumIcon()
320    {
321        return _mediumIcon;
322    }
323    
324    @Override
325    public String getLargeIcon()
326    {
327        return _largeIcon;
328    }
329
330    @Override
331    public String getURL()
332    {
333        return "cocoon://_plugins/" + _pluginName + "/" + _url;
334    }
335    
336    @Override
337    public Map<String, ServiceParameterOrRepeater> getParameters()
338    {
339        return Collections.unmodifiableMap(_parameters);
340    }
341    
342    @Override
343    public List<ServiceParameterGroup> getParameterGroups()
344    {
345        return Collections.unmodifiableList(_groups);
346    }
347    
348    @Override
349    public Script getParametersScript()
350    {
351        return _paramsScript;
352    }
353
354    @Override
355    public List<ScriptFile> getCSSFiles()
356    {
357        return _cssFiles;
358    }
359    
360    @Override
361    public boolean isPrivate()
362    {
363        return _isPrivate;
364    }
365    
366    @Override
367    public String getRight()
368    {
369        return _right;
370    }
371    
372    @Override
373    public void index(ZoneItem zoneItem, SolrInputDocument document)
374    {
375        for (String id : _parametersToIndex)
376        {
377            if (zoneItem.getServiceParameters().hasMetadata(id))
378            {
379                ServiceParameter parameter = (ServiceParameter) _parameters.get(id);
380                ParameterType type = parameter.getType();
381                
382                switch (type)
383                {
384                    case BOOLEAN:
385                    case DOUBLE:
386                    case LONG:
387                    case STRING:
388                        String language = zoneItem.getZone().getPage().getSitemapName();
389                        
390                        String[] values = zoneItem.getServiceParameters().getStringArray(id);
391                        for (String value : values)
392                        {
393                            SolrContentIndexer.indexFulltextValue(document, value, language);
394                        }
395                        break;
396                    default:
397                        break;
398                }
399            }
400        }
401    }
402    
403    /**
404     * Parse an i18n text.
405     * @param config the configuration to use.
406     * @param name the child name.
407     * @return the i18n text.
408     */
409    protected I18nizableText _parseI18nizableText(Configuration config, String name)
410    {
411        return I18nizableText.parseI18nizableText(config.getChild(name), "plugin." + _pluginName, "");
412    }
413
414    private String _configureThumbnail(Configuration valueConf, String defaultImage)
415    {
416        String value = valueConf.getValue(null);
417        if (value == null)
418        {
419            return defaultImage;
420        }
421        else
422        {
423            String pluginName = valueConf.getAttribute("plugin", this._pluginName);
424            return "/plugins/" + pluginName + "/resources/" + value;
425        }
426    }
427    
428    /**
429     * Configure the script
430     * @param configuration the global configuration
431     * @return The script created
432     * @throws ConfigurationException if configuration is invalid
433     */
434    protected Script _configureScript(Configuration configuration) throws ConfigurationException
435    {
436        List<ScriptFile> scriptsImports = _configureImports(configuration.getChild("scripts"));
437        List<ScriptFile> cssImports = _configureImports(configuration.getChild("css"));
438        String jsClassName = _configureClass(configuration.getChild("action"));
439        
440        return new Script(this.getId(), jsClassName, scriptsImports, cssImports, new HashMap<>());
441    }
442    
443    
444    /**
445     * Configure the js class name
446     * @param configuration The configuration on action tag
447     * @return The js class name
448     * @throws ConfigurationException If an error occurs
449     */
450    protected String _configureClass(Configuration configuration) throws ConfigurationException
451    {
452        String jsClassName = configuration.getAttribute("class", "");
453        if (getLogger().isDebugEnabled())
454        {
455            getLogger().debug("Js class configured is '" + jsClassName + "'");
456        }
457        return jsClassName;        
458    }
459    
460    /**
461     * Configure the import part
462     * @param configuration The imports configuration
463     * @return The set of the complete url of imported file
464     * @throws ConfigurationException If an error occurs
465     */
466    protected List<ScriptFile> _configureImports(Configuration configuration) throws ConfigurationException
467    {
468        return ConfigurationHelper.parsePluginResourceList(configuration, getPluginName(), getLogger());
469    }
470    
471    private static class ServiceParameterParser extends AbstractParameterParser<ServiceParameter, ParameterType>
472    {
473        /**
474         * Creates an {@link ServiceParameterParser}.
475         * @param enumeratorManager the enumerator component manager.
476         * @param validatorManager the validator component manager.
477         */
478        public ServiceParameterParser(ThreadSafeComponentManager<Enumerator> enumeratorManager, ThreadSafeComponentManager<Validator> validatorManager)
479        {
480            super(enumeratorManager, validatorManager);
481        }
482        
483        @Override
484        protected ServiceParameter _createParameter(Configuration parameterConfig) throws ConfigurationException
485        {
486            return new ServiceParameter();
487        }
488        
489        @Override
490        protected String _parseId(Configuration parameterConfig) throws ConfigurationException
491        {
492            String parameterName = parameterConfig.getAttribute("name");
493            
494            if (!parameterName.matches("^[a-zA-Z0-9_-]+$"))
495            {
496                throw new ConfigurationException("Invalid parameter name: " + parameterName, parameterConfig);
497            }
498            
499            return parameterName;
500        }
501        
502        @Override
503        protected ParameterType _parseType(Configuration parameterConfig) throws ConfigurationException
504        {
505            try
506            {
507                return ParameterType.valueOf(parameterConfig.getAttribute("type").toUpperCase());
508            }
509            catch (IllegalArgumentException e)
510            {
511                throw new ConfigurationException("Invalid type", parameterConfig, e);
512            }
513        }
514        
515        @Override
516        protected Object _parseDefaultValue(Configuration parameterConfig, ServiceParameter parameter)
517        {
518            String value;
519            
520            Configuration childNode = parameterConfig.getChild("default-value", false);
521            if (childNode == null)
522            {
523                value = null;
524            }
525            else
526            {
527                value = childNode.getValue("");
528            }
529            
530            return ParameterHelper.castValue(value, parameter.getType());
531        }
532        
533        @Override
534        protected void _additionalParsing(ServiceManager manager, String pluginName, Configuration parameterConfig, String parameterId, ServiceParameter parameter) throws ConfigurationException
535        {
536            super._additionalParsing(manager, pluginName, parameterConfig, parameterId, parameter);
537            
538            parameter.setId(parameterId);
539            parameter.setMultiple(parameterConfig.getAttributeAsBoolean("multiple", false));
540        }
541    }
542    
543    private static class ServiceParameterOrRepeaterParser
544    {
545        
546        private ServiceParameterParser _serviceParameterParser;
547        
548        public ServiceParameterOrRepeaterParser(ServiceParameterParser paramParser)
549        {
550            _serviceParameterParser = paramParser;
551        }
552        
553        public ServiceParameterOrRepeater parse(ServiceManager manager, String pluginName, Configuration paramConfiguration) throws ConfigurationException
554        {
555            String name = paramConfiguration.getName();
556            if (name.equals("parameter"))
557            {
558                return _serviceParameterParser.parseParameter(manager, pluginName, paramConfiguration);
559            }
560            else if (name.equals("repeater"))
561            {
562                return parseRepeater(manager, pluginName, paramConfiguration);
563            }
564            else
565            {
566                throw new ConfigurationException("Only parameter and repeater can be found in a service parameters configuration. Invalid tag: " + name, paramConfiguration);
567            }
568        }
569        
570        protected ServiceParameterRepeater parseRepeater(ServiceManager manager, String pluginName, Configuration repeaterConfig) throws ConfigurationException
571        {
572            ServiceParameterRepeater repeater = new ServiceParameterRepeater();
573            
574            String parameterId = _parseId(repeaterConfig);
575            
576            repeater.setId(parameterId);
577            repeater.setPluginName(pluginName);
578            repeater.setLabel(_parseI18nizableText(repeaterConfig, pluginName, "label"));
579            repeater.setDescription(_parseI18nizableText(repeaterConfig, pluginName, "description"));
580            
581            repeater.setAddLabel(_parseI18nizableText(repeaterConfig, pluginName, "add-label"));
582            repeater.setEditLabel(_parseI18nizableText(repeaterConfig, pluginName, "edit-label"));
583            repeater.setDeleteLabel(_parseI18nizableText(repeaterConfig, pluginName, "del-label"));
584            repeater.setAddIcon(_parseIcon(repeaterConfig.getChild("add-icon"), pluginName, "/plugins/web/resources/img/widget/repeater/add_12.png"));
585            repeater.setEditIcon(_parseIcon(repeaterConfig.getChild("edit-icon"), pluginName, "/plugins/web/resources/img/widget/repeater/edit_12.png"));
586            repeater.setDeleteIcon(_parseIcon(repeaterConfig.getChild("del-icon"), pluginName, "/plugins/web/resources/img/widget/repeater/delete_12.png"));
587            
588            repeater.setInitialSize(repeaterConfig.getAttributeAsInteger("initial-size", 0));
589            repeater.setMinSize(repeaterConfig.getAttributeAsInteger("min-size", 0));
590            repeater.setMaxSize(repeaterConfig.getAttributeAsInteger("max-size", -1));
591            
592            repeater.setChildrenParameters(parseParameters(manager, pluginName, repeaterConfig));
593            
594            return repeater;
595        }
596        
597        protected String _parseId(Configuration parameterConfig) throws ConfigurationException
598        {
599            String parameterName = parameterConfig.getAttribute("name");
600            
601            if (!parameterName.matches("^[a-zA-Z0-9_-]+$"))
602            {
603                throw new ConfigurationException("Invalid parameter name: " + parameterName, parameterConfig);
604            }
605            
606            return parameterName;
607        }
608        
609        /**
610         * Parses an i18n text.
611         * @param config the configuration to use.
612         * @param pluginName the current plugin name.
613         * @param name the child name.
614         * @return the i18n text.
615         * @throws ConfigurationException if the configuration is not valid.
616         */
617        protected I18nizableText _parseI18nizableText(Configuration config, String pluginName, String name) throws ConfigurationException
618        {
619            return I18nizableText.parseI18nizableText(config.getChild(name), "plugin." + pluginName);
620        }
621        
622        /**
623         * Parse icon
624         * @param iconConfig The icon config
625         * @param defaultPluginName The default plugin name
626         * @param defaultImage The default image
627         * @return The icon path
628         */
629        protected String _parseIcon(Configuration iconConfig, String defaultPluginName, String defaultImage)
630        {
631            String value = iconConfig.getValue(null);
632            if (value == null)
633            {
634                return defaultImage;
635            }
636            else
637            {
638                String pluginName = iconConfig.getAttribute("plugin", defaultPluginName);
639                return "/plugins/" + pluginName + "/resources/" + value;
640            }
641        }
642        
643        protected Map<String, ServiceParameter> parseParameters(ServiceManager manager, String pluginName, Configuration repeaterConfig) throws ConfigurationException
644        {
645            Map<String, ServiceParameter> params = new LinkedHashMap<>();
646            
647            for (Configuration paramConfiguration : repeaterConfig.getChildren("parameter"))
648            {
649                ServiceParameter serviceParameter = _serviceParameterParser.parseParameter(manager, pluginName, paramConfiguration);
650
651                params.put(serviceParameter.getId(), serviceParameter);
652            }
653            
654            return params;
655        }
656    }
657}