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.RepeaterDefinition;
042import org.ametys.plugins.repository.model.parsing.RepeaterDefinitionParser;
043import org.ametys.runtime.i18n.I18nizableText;
044import org.ametys.runtime.model.ElementDefinition;
045import org.ametys.runtime.model.Enumerator;
046import org.ametys.runtime.model.ModelItem;
047import org.ametys.runtime.model.ModelItemGroup;
048import org.ametys.runtime.model.ModelViewItem;
049import org.ametys.runtime.model.ModelViewItemGroup;
050import org.ametys.runtime.model.SimpleViewItemGroup;
051import org.ametys.runtime.model.View;
052import org.ametys.runtime.model.ViewElement;
053import org.ametys.runtime.model.ViewItem;
054import org.ametys.runtime.model.ViewItemGroup;
055import org.ametys.runtime.model.type.ElementType;
056import org.ametys.runtime.parameter.Validator;
057import org.ametys.runtime.plugin.component.AbstractLogEnabled;
058import org.ametys.runtime.plugin.component.PluginAware;
059import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
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    private String _id;
089    private I18nizableText _label;
090    private I18nizableText _description;
091    private I18nizableText _category;
092    
093    private String _iconGlyph;
094    private String _iconDecorator;
095    private String _smallIcon;
096    private String _mediumIcon;
097    private String _largeIcon;
098    private Integer _creationBoxHeight;
099    private Integer _creationBoxWidth;
100    private List<ScriptFile> _cssFiles;
101    
102    private boolean _isCacheable;
103    private boolean _isPrivate;
104    /** The right needed to create an instance of this service, blank or null if no right is needed. */
105    private String _right;
106    
107    private String _url;
108    
109    private List<String> _parametersToIndex;
110    
111    // ComponentManager for validators
112    private ThreadSafeComponentManager<Validator> _validatorManager;
113    
114    // ComponentManager for enumerators
115    private ThreadSafeComponentManager<Enumerator> _enumeratorManager;
116    
117    private ServiceParameterTypeExtensionPoint _serviceParameterTypeExtensionPoint;
118    private ServiceParameterDefinitionParser _serviceParameterDefinitionParser;
119    private RepeaterDefinitionParser _repeaterDefinitionParser;
120
121    @Override
122    public void service(ServiceManager smanager) throws ServiceException
123    {
124        _manager = smanager;
125        _serviceParameterTypeExtensionPoint = (ServiceParameterTypeExtensionPoint) smanager.lookup(ServiceParameterTypeExtensionPoint.ROLE);
126    }
127
128    @Override
129    public void contextualize(Context context) throws ContextException
130    {
131        _context = context;
132    }
133    
134    @Override
135    public void dispose()
136    {
137        _validatorManager.dispose();
138        _validatorManager = null;
139        
140        _enumeratorManager.dispose();
141        _enumeratorManager = null;
142    }
143    
144    @Override
145    public void configure(Configuration configuration) throws ConfigurationException
146    {
147        _validatorManager = new ThreadSafeComponentManager<>();
148        _validatorManager.setLogger(getLogger());
149        _validatorManager.contextualize(_context);
150        _validatorManager.service(_manager);
151        
152        _enumeratorManager = new ThreadSafeComponentManager<>();
153        _enumeratorManager.setLogger(getLogger());
154        _enumeratorManager.contextualize(_context);
155        _enumeratorManager.service(_manager);
156        
157        _label = _parseI18nizableText(configuration, "label");
158        _description = _parseI18nizableText(configuration, "description");
159        _category = _parseI18nizableText(configuration, "category");
160        
161        _isCacheable = configuration.getChild("cacheable").getValueAsBoolean(false);
162        _isPrivate = configuration.getChild("private").getValueAsBoolean(false);
163        _right = configuration.getChild("right").getValue(null);
164
165        this._iconGlyph = configuration.getChild("thumbnail").getChild("glyph").getValue(null);
166        this._iconDecorator = configuration.getChild("thumbnail").getChild("decorator").getValue(null);
167        this._smallIcon = _configureThumbnail(configuration.getChild("thumbnail").getChild("small"), "/plugins/web/resources/img/service/servicepage_16.png");
168        this._mediumIcon = _configureThumbnail(configuration.getChild("thumbnail").getChild("medium"), "/plugins/web/resources/img/service/servicepage_32.png");
169        this._largeIcon = _configureThumbnail(configuration.getChild("thumbnail").getChild("marge"), "/plugins/web/resources/img/service/servicepage_50.png");
170        this._creationBoxHeight = configureDialogBoxDimension(configuration, "height");
171        this._creationBoxWidth = configureDialogBoxDimension(configuration, "width");
172
173        _cssFiles = _configureImports(configuration.getChild("css"));
174        _paramsScript = _configureScript(configuration.getChild("parameters"));
175        
176        _url = configuration.getChild("url").getValue();
177        
178        _serviceParameterDefinitionParser = new ServiceParameterDefinitionParser(_serviceParameterTypeExtensionPoint, _enumeratorManager, _validatorManager);
179        _repeaterDefinitionParser = new RepeaterDefinitionParser(_serviceParameterTypeExtensionPoint);
180        
181        Configuration parametersConfiguration = configuration.getChild("parameters");
182        configureParameters(parametersConfiguration);
183        
184        try
185        {
186            _serviceParameterDefinitionParser.lookupComponents();
187        }
188        catch (Exception e)
189        {
190            throw new ConfigurationException("Unable to lookup parameter local components", configuration, e);
191        }
192        
193        configureIndexation(configuration.getChild("indexation"));
194    }
195
196    /**
197     * Configure the service parameters.
198     * @param parametersConfiguration the service parameters configuration.
199     * @throws ConfigurationException if an error occurs.
200     */
201    protected void configureParameters(Configuration parametersConfiguration) throws ConfigurationException
202    {
203        _view = new View();
204        _modelItems = new HashMap<>();
205        
206        // Parse all groups, fielsets and parameters to build the view
207        Configuration[] groupConfigurations = parametersConfiguration.getChildren("group");
208        if (groupConfigurations.length > 0)
209        {
210            if (parametersConfiguration.getChildren("fieldset").length > 0
211                    || parametersConfiguration.getChildren("parameter").length > 0
212                    || parametersConfiguration.getChildren("repeater").length > 0)
213            {
214                throw new ConfigurationException("The service '" + getId() + "' should not have parameters out of its groups.", parametersConfiguration);
215            }
216            
217            // Has groups.
218            for (Configuration groupConfiguration : groupConfigurations)
219            {
220                SimpleViewItemGroup group = _parseSimpleViewItemGroup(groupConfiguration, null);
221                group.setRole(ViewItemGroup.TAB_ROLE);
222                _view.addViewItem(group);
223            }
224        }
225        else
226        {
227            SimpleViewItemGroup group = _parseSimpleViewItemGroup(parametersConfiguration, null);
228            group.setRole(ViewItemGroup.TAB_ROLE);
229            _view.addViewItem(group);
230        }
231    }
232    
233    /**
234     * Parses a simple view item group (group or fieldset)
235     * @param groupConfiguration the item group configuration
236     * @param modelParent The last model item group to use as parent for next definitions
237     * @return the parsed simple view item group
238     * @throws ConfigurationException if an error occurs
239     */
240    protected SimpleViewItemGroup _parseSimpleViewItemGroup(Configuration groupConfiguration, ModelItemGroup modelParent) throws ConfigurationException
241    {
242        SimpleViewItemGroup group = new SimpleViewItemGroup();
243        group.setName(groupConfiguration.getAttribute("name", null));
244        group.setLabel(_parseI18nizableText(groupConfiguration, "label"));
245        group.setDescription(_parseI18nizableText(groupConfiguration, "description"));
246        
247        _parseChildren(groupConfiguration, group, modelParent);
248        
249        return group;
250    }
251
252    /**
253     * Parses a service parameter
254     * @param paramConfiguration the parameter configuration
255     * @param parent the parent of the service parameter. Can be <code>null</code> if the parameter has no parent
256     * @return the parsed parameter
257     * @throws ConfigurationException if an error occurs
258     */
259    protected ViewElement _parseParameter(Configuration paramConfiguration, ModelItemGroup parent) throws ConfigurationException
260    {
261        return _parseParameter(paramConfiguration, parent, _pluginName);
262    }
263
264    /**
265     * Parses a service parameter
266     * @param paramConfiguration the parameter configuration
267     * @param parent the parent of the service parameter. Can be <code>null</code> if the parameter has no parent
268     * @param pluginName Name of the plugin that defines this parameter
269     * @return the parsed parameter
270     * @throws ConfigurationException if an error occurs
271     */
272    protected ViewElement _parseParameter(Configuration paramConfiguration, ModelItemGroup parent, String pluginName) throws ConfigurationException
273    {
274        ServiceParameter serviceParameter = _serviceParameterDefinitionParser.parse(_manager, pluginName, paramConfiguration, this, parent);
275        
276        if (serviceParameter != null)
277        {
278            ViewElement viewElement = new ViewElement();
279            viewElement.setDefinition(serviceParameter);
280            
281            return viewElement;
282        }
283        
284        return null;
285    }
286    
287    /**
288     * Parses a repeater
289     * @param repeaterConfiguration the repeater configuration
290     * @param parent the parent of the repeater. Can be <code>null</code> if the repeater has no parent
291     * @return the parsed repeater
292     * @throws ConfigurationException if an error occurs
293     */
294    protected ModelViewItemGroup _parseRepeater(Configuration repeaterConfiguration, ModelItemGroup parent) throws ConfigurationException
295    {
296        return _parseRepeater(repeaterConfiguration, parent, _pluginName);
297    }
298    
299    /**
300     * Parses a repeater
301     * @param repeaterConfiguration the repeater configuration
302     * @param parent the parent of the repeater. Can be <code>null</code> if the repeater has no parent
303     * @param pluginName Name of the plugin that defines this parameter
304     * @return the parsed repeater
305     * @throws ConfigurationException if an error occurs
306     */
307    protected ModelViewItemGroup _parseRepeater(Configuration repeaterConfiguration, ModelItemGroup parent, String pluginName) throws ConfigurationException
308    {
309        RepeaterDefinition repeaterDefinition = _repeaterDefinitionParser.parse(_manager, pluginName, repeaterConfiguration, this, parent);
310        
311        if (repeaterDefinition != null)
312        {
313            ModelViewItemGroup viewItemGroup = new ModelViewItemGroup();
314            viewItemGroup.setDefinition(repeaterDefinition);
315            
316            _parseChildren(repeaterConfiguration, viewItemGroup, repeaterDefinition);
317            
318            return viewItemGroup;
319        }
320        
321        return null;
322    }
323
324    /**
325     * Parses children of a view item group
326     * @param groupConfiguration the item group configuration
327     * @param viewItemGroup the item group
328     * @param modelParent The last model item group to use as parent for next definitions
329     * @throws ConfigurationException if an error occurs
330     */
331    protected void _parseChildren(Configuration groupConfiguration, ViewItemGroup viewItemGroup, ModelItemGroup modelParent) throws ConfigurationException
332    {
333        Configuration[] children = groupConfiguration.getChildren();
334        for (Configuration child : children)
335        {
336            ViewItem viewItem = null;
337            switch (child.getName())
338            {
339                case "fieldset":
340                    viewItem = _parseSimpleViewItemGroup(child, modelParent);
341                    ((ViewItemGroup) viewItem).setRole(ViewItemGroup.FIELDSET_ROLE);
342                    break;
343                case "parameter":
344                    viewItem = _parseParameter(child, modelParent);
345                    break;
346                case "repeater":
347                    viewItem = _parseRepeater(child, modelParent);
348                    break;
349                case "group":
350                    throw new ConfigurationException("The service '" + getId() + "' should not have subgroups", child);
351                default:
352                    break;
353            }
354            
355            if (viewItem != null)
356            {
357                viewItemGroup.addViewItem(viewItem);
358                if (viewItem instanceof ModelViewItem && modelParent == null)
359                {
360                    ModelItem definition = ((ModelViewItem) viewItem).getDefinition();
361                    _modelItems.put(definition.getName(), definition);
362                }
363            }
364        }
365    }
366    
367    /**
368     * Configure the indexation process.<br>
369     * This class only allow to index the value of some parameters (not repeaters).
370     * @param configuration the indexation configuration.
371     * @throws ConfigurationException if an error occurs.
372     */
373    protected void configureIndexation(Configuration configuration) throws ConfigurationException
374    {
375        _parametersToIndex = new ArrayList<>();
376        
377        for (Configuration config : configuration.getChildren("parameter"))
378        {
379            String id = config.getValue("");
380            ModelItem param = _modelItems.get(id);
381            
382            if (param == null || !(param instanceof ElementDefinition))
383            {
384                if (getLogger().isWarnEnabled())
385                {
386                    getLogger().warn("Invalid indexation configuration for service " + _id + " : there's no parameter '" + id + "'");
387                }
388            }
389            else
390            {
391                _parametersToIndex.add(id);
392            }
393        }
394    }
395    
396    @Override
397    public void setPluginInfo(String pluginName, String featureName, String id)
398    {
399        _pluginName = pluginName;
400        _featureName = featureName;
401        _id = id;
402    }
403
404    @Override
405    public String getPluginName()
406    {
407        return _pluginName;
408    }
409
410    @Override
411    public String getId()
412    {
413        return _id;
414    }
415    
416    public String getName()
417    {
418        return _id;
419    }
420    
421    @Override
422    public boolean isCacheable(Page currentPage, ZoneItem zoneItem)
423    {
424        return _isCacheable;
425    }
426    
427    @Override
428    public I18nizableText getLabel()
429    {
430        return _label;
431    }
432    
433    @Override
434    public I18nizableText getDescription()
435    {
436        return _description;
437    }
438    
439    @Override
440    public I18nizableText getCategory()
441    {
442        return _category;
443    }
444    
445    @Override
446    public String getIconGlyph()
447    {
448        return _iconGlyph;
449    }
450    
451    @Override
452    public String getIconDecorator()
453    {
454        return _iconDecorator;
455    }
456    
457    @Override
458    public String getSmallIcon()
459    {
460        return _smallIcon;
461    }
462    
463    @Override
464    public String getMediumIcon()
465    {
466        return _mediumIcon;
467    }
468    
469    @Override
470    public String getLargeIcon()
471    {
472        return _largeIcon;
473    }
474    
475    @Override
476    public Integer getCreationBoxHeight()
477    {
478        return _creationBoxHeight;
479    }
480    
481    @Override
482    public Integer getCreationBoxWidth()
483    {
484        return _creationBoxWidth;
485    }
486
487    @Override
488    public String getURL()
489    {
490        return "cocoon://_plugins/" + _pluginName + "/" + _url;
491    }
492    
493    @Override
494    public Script getParametersScript()
495    {
496        return _paramsScript;
497    }
498
499    @Override
500    public List<ScriptFile> getCSSFiles()
501    {
502        return _cssFiles;
503    }
504    
505    @Override
506    public boolean isPrivate()
507    {
508        return _isPrivate;
509    }
510    
511    @Override
512    public String getRight()
513    {
514        return _right;
515    }
516    
517    @SuppressWarnings("unchecked")
518    @Override
519    public void index(ZoneItem zoneItem, SolrInputDocument document)
520    {
521        for (String id : _parametersToIndex)
522        {
523            ModelAwareDataHolder dataHolder = zoneItem.getServiceParameters();
524            if (dataHolder.hasValue(id))
525            {
526                String language = zoneItem.getZone().getPage().getSitemapName();
527                Object value = dataHolder.getValue(id);
528                
529                ElementDefinition parameter = (ElementDefinition) _modelItems.get(id);
530                ElementType type = parameter.getType();
531                
532                if (parameter.isMultiple())
533                {
534                    for (Object singleValue : (Object[]) value)
535                    {
536                        SolrContentIndexer.indexFulltextValue(document, type.toString(singleValue), language);
537                    }
538                }
539                else
540                {
541                    SolrContentIndexer.indexFulltextValue(document, type.toString(value), language);
542                }
543            }
544        }
545    }
546    
547    /**
548     * Parse an i18n text.
549     * @param config the configuration to use.
550     * @param name the child name.
551     * @return the i18n text.
552     */
553    protected I18nizableText _parseI18nizableText(Configuration config, String name)
554    {
555        return I18nizableText.parseI18nizableText(config.getChild(name), "plugin." + _pluginName, "");
556    }
557    
558    private String _configureThumbnail(Configuration valueConf, String defaultImage)
559    {
560        String value = valueConf.getValue(null);
561        if (value == null)
562        {
563            return defaultImage;
564        }
565        else
566        {
567            String pluginName = valueConf.getAttribute("plugin", this._pluginName);
568            return "/plugins/" + pluginName + "/resources/" + value;
569        }
570    }
571    
572    /**
573     * Configure the dimensions of the dialog box
574     * @param configuration the global configuration
575     * @param dimensionName the name of the dimension to configure
576     * @return the value of the dimension
577     * @throws ConfigurationException if configuration is invalid
578     */
579    protected Integer configureDialogBoxDimension(Configuration configuration, String dimensionName) throws ConfigurationException
580    {
581        Configuration dimensionConfiguration = configuration.getChild("dialogBox").getChild(dimensionName, false);
582        if (dimensionConfiguration != null)
583        {
584            return dimensionConfiguration.getValueAsInteger();
585        }
586        else
587        {
588            return null;
589        }
590    }
591    
592    /**
593     * Configure the script
594     * @param configuration the global configuration
595     * @return The script created
596     * @throws ConfigurationException if configuration is invalid
597     */
598    protected Script _configureScript(Configuration configuration) throws ConfigurationException
599    {
600        List<ScriptFile> scriptsImports = _configureImports(configuration.getChild("scripts"));
601        List<ScriptFile> cssImports = _configureImports(configuration.getChild("css"));
602        String jsClassName = _configureClass(configuration.getChild("action"));
603        
604        return new Script(this.getId(), jsClassName, scriptsImports, cssImports, new HashMap<>());
605    }
606    
607    
608    /**
609     * Configure the js class name
610     * @param configuration The configuration on action tag
611     * @return The js class name
612     * @throws ConfigurationException If an error occurs
613     */
614    protected String _configureClass(Configuration configuration) throws ConfigurationException
615    {
616        String jsClassName = configuration.getAttribute("class", "");
617        if (getLogger().isDebugEnabled())
618        {
619            getLogger().debug("Js class configured is '" + jsClassName + "'");
620        }
621        return jsClassName;        
622    }
623    
624    /**
625     * Configure the import part
626     * @param configuration The imports configuration
627     * @return The set of the complete url of imported file
628     * @throws ConfigurationException If an error occurs
629     */
630    protected List<ScriptFile> _configureImports(Configuration configuration) throws ConfigurationException
631    {
632        return ConfigurationHelper.parsePluginResourceList(configuration, getPluginName(), getLogger());
633    }
634    
635    public Collection<ModelItem> getModelItems()
636    {
637        return _modelItems.values();
638    }
639    
640    public Map<String, ModelItem> getParameters()
641    {
642        return _modelItems;
643    }
644
645    public View getView()
646    {
647        return _view;
648    }
649}