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