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