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