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.Collection;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.avalon.framework.activity.Disposable;
025import org.apache.avalon.framework.configuration.Configurable;
026import org.apache.avalon.framework.configuration.Configuration;
027import org.apache.avalon.framework.configuration.ConfigurationException;
028import org.apache.avalon.framework.context.Context;
029import org.apache.avalon.framework.context.ContextException;
030import org.apache.avalon.framework.context.Contextualizable;
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.avalon.framework.service.Serviceable;
034import org.apache.solr.common.SolrInputDocument;
035
036import org.ametys.cms.content.indexing.solr.SolrContentIndexer;
037import org.ametys.core.ui.ClientSideElement.Script;
038import org.ametys.core.ui.ClientSideElement.ScriptFile;
039import org.ametys.plugins.core.ui.util.ConfigurationHelper;
040import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder;
041import org.ametys.plugins.repository.model.parsing.RepeaterDefinitionParser;
042import org.ametys.runtime.i18n.I18nizableText;
043import org.ametys.runtime.model.ElementDefinition;
044import org.ametys.runtime.model.Enumerator;
045import org.ametys.runtime.model.ModelItem;
046import org.ametys.runtime.model.View;
047import org.ametys.runtime.model.type.ElementType;
048import org.ametys.runtime.parameter.Validator;
049import org.ametys.runtime.plugin.component.AbstractLogEnabled;
050import org.ametys.runtime.plugin.component.PluginAware;
051import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
052import org.ametys.web.parameters.ViewAndParametersParser;
053import org.ametys.web.parameters.ViewAndParametersParser.ViewAndParameters;
054import org.ametys.web.repository.page.Page;
055import org.ametys.web.repository.page.ZoneItem;
056
057/**
058 * Class representing a business service. <br>
059 * A service is identified by an id and a Cocoon-URL.<br>
060 * This URL corresponds to a pipeline called by a page template.<br>
061 * URL must be relative to the sitemap of the plugin containing the service.
062 */
063public class StaticService extends AbstractLogEnabled implements Service, Contextualizable, Configurable, PluginAware, Serviceable, Disposable
064{
065    /** The plugin name */
066    protected String _pluginName;
067    /** The feature name */
068    protected String _featureName;
069    /** The service manager */
070    protected ServiceManager _manager;
071    /** The context. */
072    protected Context _context;
073    /** The script configured */
074    protected Script _paramsScript;
075    
076    /** The view of the service */
077    protected View _view;
078    
079    /** The map of model items */
080    protected Map<String, ModelItem> _modelItems;
081    
082    /** The view and parameters parser */
083    protected ViewAndParametersParser _viewAndParametersParser;
084
085    /** The service parameter definition parser */
086    protected ServiceParameterDefinitionParser _serviceParameterDefinitionParser;
087    
088    /** The repeater definition parser */
089    protected RepeaterDefinitionParser _repeaterDefinitionParser;
090
091    private String _id;
092    private I18nizableText _label;
093    private I18nizableText _description;
094    private I18nizableText _category;
095    
096    private String _iconGlyph;
097    private String _iconDecorator;
098    private String _smallIcon;
099    private String _mediumIcon;
100    private String _largeIcon;
101    private Integer _creationBoxHeight;
102    private Integer _creationBoxWidth;
103    private List<ScriptFile> _cssFiles;
104    
105    private boolean _isCacheable;
106    private boolean _isPrivate;
107    /** The right needed to create an instance of this service, blank or null if no right is needed. */
108    private String _right;
109    
110    private String _url;
111    
112    private List<String> _parametersToIndex;
113    
114    // ComponentManager for validators
115    private ThreadSafeComponentManager<Validator> _validatorManager;
116    
117    // ComponentManager for enumerators
118    private ThreadSafeComponentManager<Enumerator> _enumeratorManager;
119    
120    private ServiceParameterTypeExtensionPoint _serviceParameterTypeExtensionPoint;
121
122    
123    @Override
124    public void service(ServiceManager smanager) throws ServiceException
125    {
126        _manager = smanager;
127        _serviceParameterTypeExtensionPoint = (ServiceParameterTypeExtensionPoint) smanager.lookup(ServiceParameterTypeExtensionPoint.ROLE);
128        _viewAndParametersParser = (ViewAndParametersParser) smanager.lookup(ViewAndParametersParser.ROLE);
129    }
130
131    @Override
132    public void contextualize(Context context) throws ContextException
133    {
134        _context = context;
135    }
136    
137    @Override
138    public void dispose()
139    {
140        _validatorManager.dispose();
141        _validatorManager = null;
142        
143        _enumeratorManager.dispose();
144        _enumeratorManager = null;
145    }
146    
147    @Override
148    public void configure(Configuration configuration) throws ConfigurationException
149    {
150        _validatorManager = new ThreadSafeComponentManager<>();
151        _validatorManager.setLogger(getLogger());
152        _validatorManager.contextualize(_context);
153        _validatorManager.service(_manager);
154        
155        _enumeratorManager = new ThreadSafeComponentManager<>();
156        _enumeratorManager.setLogger(getLogger());
157        _enumeratorManager.contextualize(_context);
158        _enumeratorManager.service(_manager);
159        
160        _label = _parseI18nizableText(configuration, "label");
161        _description = _parseI18nizableText(configuration, "description");
162        _category = _parseI18nizableText(configuration, "category");
163        
164        _isCacheable = configuration.getChild("cacheable").getValueAsBoolean(false);
165        _isPrivate = configuration.getChild("private").getValueAsBoolean(false);
166        _right = configuration.getChild("right").getValue(null);
167
168        this._iconGlyph = configuration.getChild("thumbnail").getChild("glyph").getValue(null);
169        this._iconDecorator = configuration.getChild("thumbnail").getChild("decorator").getValue(null);
170        this._smallIcon = _configureThumbnail(configuration.getChild("thumbnail").getChild("small"), "/plugins/web/resources/img/service/servicepage_16.png");
171        this._mediumIcon = _configureThumbnail(configuration.getChild("thumbnail").getChild("medium"), "/plugins/web/resources/img/service/servicepage_32.png");
172        this._largeIcon = _configureThumbnail(configuration.getChild("thumbnail").getChild("marge"), "/plugins/web/resources/img/service/servicepage_50.png");
173        this._creationBoxHeight = configureDialogBoxDimension(configuration, "height");
174        this._creationBoxWidth = configureDialogBoxDimension(configuration, "width");
175
176        _cssFiles = _configureImports(configuration.getChild("css"));
177        _paramsScript = _configureScript(configuration.getChild("parameters"));
178        
179        _url = configuration.getChild("url").getValue();
180        
181        _serviceParameterDefinitionParser = new ServiceParameterDefinitionParser(_serviceParameterTypeExtensionPoint, _enumeratorManager, _validatorManager);
182        _repeaterDefinitionParser = new RepeaterDefinitionParser(_serviceParameterTypeExtensionPoint);
183        
184        Configuration parametersConfiguration = configuration.getChild("parameters");
185        configureParameters(parametersConfiguration);
186        
187        try
188        {
189            _serviceParameterDefinitionParser.lookupComponents();
190        }
191        catch (Exception e)
192        {
193            throw new ConfigurationException("Unable to lookup parameter local components", configuration, e);
194        }
195        
196        configureIndexation(configuration.getChild("indexation"));
197    }
198
199    /**
200     * Configure the service parameters
201     * @param parametersConfiguration the parameters configuration
202     * @throws ConfigurationException if a configuration exception occurred
203     */
204    protected void configureParameters(Configuration parametersConfiguration) throws ConfigurationException
205    {
206        ViewAndParameters viewAndParameters = _viewAndParametersParser.parseParameters(parametersConfiguration, _pluginName, "plugin." + _pluginName, this, _serviceParameterDefinitionParser, _repeaterDefinitionParser);
207        _view = viewAndParameters.getView();
208        _modelItems = viewAndParameters.getParameters();
209    }
210    
211    /**
212     * Configure the indexation process.<br>
213     * This class only allow to index the value of some parameters (not repeaters).
214     * @param configuration the indexation configuration.
215     * @throws ConfigurationException if an error occurs.
216     */
217    protected void configureIndexation(Configuration configuration) throws ConfigurationException
218    {
219        _parametersToIndex = new ArrayList<>();
220        
221        for (Configuration config : configuration.getChildren("parameter"))
222        {
223            String id = config.getValue("");
224            ModelItem param = _modelItems.get(id);
225            
226            if (param == null || !(param instanceof ElementDefinition))
227            {
228                if (getLogger().isWarnEnabled())
229                {
230                    getLogger().warn("Invalid indexation configuration for service " + _id + " : there's no parameter '" + id + "'");
231                }
232            }
233            else
234            {
235                _parametersToIndex.add(id);
236            }
237        }
238    }
239    
240    @Override
241    public void setPluginInfo(String pluginName, String featureName, String id)
242    {
243        _pluginName = pluginName;
244        _featureName = featureName;
245        _id = id;
246    }
247
248    @Override
249    public String getPluginName()
250    {
251        return _pluginName;
252    }
253
254    @Override
255    public String getId()
256    {
257        return _id;
258    }
259    
260    public String getName()
261    {
262        return _id;
263    }
264    
265    @Override
266    public boolean isCacheable(Page currentPage, ZoneItem zoneItem)
267    {
268        return _isCacheable;
269    }
270    
271    @Override
272    public I18nizableText getLabel()
273    {
274        return _label;
275    }
276    
277    @Override
278    public I18nizableText getDescription()
279    {
280        return _description;
281    }
282    
283    @Override
284    public I18nizableText getCategory()
285    {
286        return _category;
287    }
288    
289    @Override
290    public String getIconGlyph()
291    {
292        return _iconGlyph;
293    }
294    
295    @Override
296    public String getIconDecorator()
297    {
298        return _iconDecorator;
299    }
300    
301    @Override
302    public String getSmallIcon()
303    {
304        return _smallIcon;
305    }
306    
307    @Override
308    public String getMediumIcon()
309    {
310        return _mediumIcon;
311    }
312    
313    @Override
314    public String getLargeIcon()
315    {
316        return _largeIcon;
317    }
318    
319    @Override
320    public Integer getCreationBoxHeight()
321    {
322        return _creationBoxHeight;
323    }
324    
325    @Override
326    public Integer getCreationBoxWidth()
327    {
328        return _creationBoxWidth;
329    }
330
331    @Override
332    public String getURL()
333    {
334        return "cocoon://_plugins/" + _pluginName + "/" + _url;
335    }
336    
337    @Override
338    public Script getParametersScript()
339    {
340        return _paramsScript;
341    }
342
343    @Override
344    public List<ScriptFile> getCSSFiles()
345    {
346        return _cssFiles;
347    }
348    
349    @Override
350    public boolean isPrivate()
351    {
352        return _isPrivate;
353    }
354    
355    @Override
356    public String getRight()
357    {
358        return _right;
359    }
360    
361    @SuppressWarnings("unchecked")
362    @Override
363    public void index(ZoneItem zoneItem, SolrInputDocument document)
364    {
365        for (String id : _parametersToIndex)
366        {
367            ModelAwareDataHolder dataHolder = zoneItem.getServiceParameters();
368            if (dataHolder.hasNonEmptyValue(id))
369            {
370                String language = zoneItem.getZone().getPage().getSitemapName();
371                Object value = dataHolder.getValue(id);
372                
373                ElementDefinition parameter = (ElementDefinition) _modelItems.get(id);
374                ElementType type = parameter.getType();
375                
376                if (parameter.isMultiple())
377                {
378                    for (Object singleValue : (Object[]) value)
379                    {
380                        SolrContentIndexer.indexFulltextValue(document, type.toString(singleValue), language);
381                    }
382                }
383                else
384                {
385                    SolrContentIndexer.indexFulltextValue(document, type.toString(value), language);
386                }
387            }
388        }
389    }
390    
391    /**
392     * Parse an i18n text.
393     * @param config the configuration to use.
394     * @param name the child name.
395     * @return the i18n text.
396     */
397    protected I18nizableText _parseI18nizableText(Configuration config, String name)
398    {
399        return I18nizableText.parseI18nizableText(config.getChild(name), "plugin." + _pluginName, "");
400    }
401    
402    private String _configureThumbnail(Configuration valueConf, String defaultImage)
403    {
404        String value = valueConf.getValue(null);
405        if (value == null)
406        {
407            return defaultImage;
408        }
409        else
410        {
411            String pluginName = valueConf.getAttribute("plugin", this._pluginName);
412            return "/plugins/" + pluginName + "/resources/" + value;
413        }
414    }
415    
416    /**
417     * Configure the dimensions of the dialog box
418     * @param configuration the global configuration
419     * @param dimensionName the name of the dimension to configure
420     * @return the value of the dimension
421     * @throws ConfigurationException if configuration is invalid
422     */
423    protected Integer configureDialogBoxDimension(Configuration configuration, String dimensionName) throws ConfigurationException
424    {
425        Configuration dimensionConfiguration = configuration.getChild("dialogBox").getChild(dimensionName, false);
426        if (dimensionConfiguration != null)
427        {
428            return dimensionConfiguration.getValueAsInteger();
429        }
430        else
431        {
432            return null;
433        }
434    }
435    
436    /**
437     * Configure the script
438     * @param configuration the global configuration
439     * @return The script created
440     * @throws ConfigurationException if configuration is invalid
441     */
442    protected Script _configureScript(Configuration configuration) throws ConfigurationException
443    {
444        List<ScriptFile> scriptsImports = _configureImports(configuration.getChild("scripts"));
445        List<ScriptFile> cssImports = _configureImports(configuration.getChild("css"));
446        String jsClassName = _configureClass(configuration.getChild("action"));
447        
448        return new Script(this.getId(), jsClassName, scriptsImports, cssImports, new HashMap<>());
449    }
450    
451    
452    /**
453     * Configure the js class name
454     * @param configuration The configuration on action tag
455     * @return The js class name
456     * @throws ConfigurationException If an error occurs
457     */
458    protected String _configureClass(Configuration configuration) throws ConfigurationException
459    {
460        String jsClassName = configuration.getAttribute("class", "");
461        if (getLogger().isDebugEnabled())
462        {
463            getLogger().debug("Js class configured is '" + jsClassName + "'");
464        }
465        return jsClassName;        
466    }
467    
468    /**
469     * Configure the import part
470     * @param configuration The imports configuration
471     * @return The set of the complete url of imported file
472     * @throws ConfigurationException If an error occurs
473     */
474    protected List<ScriptFile> _configureImports(Configuration configuration) throws ConfigurationException
475    {
476        return ConfigurationHelper.parsePluginResourceList(configuration, getPluginName(), getLogger());
477    }
478    
479    public Collection<ModelItem> getModelItems()
480    {
481        return _modelItems.values();
482    }
483    
484    public Map<String, ModelItem> getParameters()
485    {
486        return _modelItems;
487    }
488
489    public View getView()
490    {
491        return _view;
492    }
493}