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