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.skin;
017
018import java.io.InputStream;
019import java.util.ArrayList;
020import java.util.Date;
021import java.util.LinkedHashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Optional;
025
026import org.apache.avalon.framework.configuration.Configuration;
027import org.apache.avalon.framework.configuration.ConfigurationException;
028import org.apache.excalibur.source.Source;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032import org.ametys.cms.data.holder.group.DataHolderRepeaterDefinitionParser;
033import org.ametys.runtime.i18n.I18nizableText;
034import org.ametys.runtime.model.Enumerator;
035import org.ametys.runtime.model.View;
036import org.ametys.runtime.model.disableconditions.DisableConditions;
037import org.ametys.runtime.parameter.Validator;
038import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
039import org.ametys.web.parameters.ViewAndParametersParser.ViewAndParameters;
040import org.ametys.web.parameters.view.GlobalViewParametersManager.ViewParametersType;
041import org.ametys.web.parameters.view.ViewParameterDefinitionParser;
042import org.ametys.web.parameters.view.ViewParametersManager;
043import org.ametys.web.parameters.view.ViewParametersModel;
044
045/**
046 * Represent a skin template.
047 */
048public class SkinTemplate
049{
050    private static Logger _logger = LoggerFactory.getLogger(SkinTemplate.class);
051    
052    /** The skin id */
053    protected String _skinId;
054    /** Template id (e.g. the directory name) */
055    protected String _id;
056    /** Template label */
057    protected I18nizableText _label;
058    /** Template description */
059    protected I18nizableText _description;
060    /** Template thumbnail 16px */
061    protected String _smallImage;
062    /** Template thumbnail 32px */
063    protected String _mediumImage;
064    /** Template thumbnail 48px */
065    protected String _largeImage;
066    /** Template zones. the key is the zone id */
067    protected Map<String, SkinTemplateZone> _zones;
068    /** The template view parameters */
069    protected Optional<ViewParametersModel> _viewParameters;
070        
071    /** The last time the file was loaded */
072    protected long _lastConfUpdate;
073    /** The skins manager */
074    protected SkinsManager _skinsManager;
075    /** The skin configuration helper */
076    protected SkinConfigurationHelper _skinConfigurationHelper;
077
078    private List<ThreadSafeComponentManager> _components;
079
080    
081    /**
082     * Creates a template
083     * @param skinId The skin id
084     * @param templateId The template id
085     * @param skinsManager The skins manager
086     * @param skinConfigurationHelper The skin configuration helper
087     */
088    public SkinTemplate(String skinId, String templateId, SkinsManager skinsManager, SkinConfigurationHelper skinConfigurationHelper)
089    {
090        _skinId = skinId;
091        _id = templateId;
092        _skinsManager = skinsManager;
093        _skinConfigurationHelper = skinConfigurationHelper;
094        
095        _viewParameters = Optional.empty();
096        
097        _components = new ArrayList<>();
098    }
099    
100    /**
101     * The configuration default values (if configuration file does not exist or is unreadable)
102     */
103    protected void _defaultValues()
104    {
105        _lastConfUpdate = new Date().getTime();
106        
107        this._label = new I18nizableText(this._id);
108        this._description = new I18nizableText("");
109        this._smallImage = "/plugins/web/resources/img/skin/template_16.png";
110        this._mediumImage = "/plugins/web/resources/img/skin/template_32.png";
111        this._largeImage = "/plugins/web/resources/img/skin/template_48.png";
112        this._zones = new LinkedHashMap<>();
113    }
114    
115    /**
116     * Refresh the configuration values
117     */
118    public void refreshValues()
119    {
120        String relateFileLoc = Skin.TEMPLATES_PATH + "/" + _id + "/template.xml";
121        String relateDefaultFileLoc = "conf/template-default.xml";
122        
123        Source configurationFile = null;
124        Source defaultConfigurationFile = null;
125        try
126        {
127            configurationFile = _skinsManager.getSourceResolver().resolveURI("skin:" + _skinId + "://" + relateFileLoc);
128            defaultConfigurationFile = _skinsManager.getSourceResolver().resolveURI("skin:" + _skinId + "://" + relateDefaultFileLoc);
129            if (configurationFile.exists() || defaultConfigurationFile.exists())
130            {
131                long fileTime;
132                if (configurationFile.exists() && defaultConfigurationFile.exists())
133                {
134                    fileTime = Math.max(configurationFile.getLastModified(), defaultConfigurationFile.getLastModified());
135                }
136                else if (configurationFile.exists())
137                {
138                    fileTime = configurationFile.getLastModified();
139                }
140                else
141                {
142                    fileTime = defaultConfigurationFile.getLastModified();
143                }
144                
145                if (_lastConfUpdate < fileTime)
146                {
147                    _defaultValues();
148
149                    _lastConfUpdate = fileTime;
150                    
151                    Skin skin = _skinsManager.getSkin(_skinId);
152                    Configuration configuration = null;
153                    
154                    try (InputStream xslIs = getClass().getResourceAsStream("skin-template-merge.xsl"))
155                    {
156                        configuration = _skinConfigurationHelper.getInheritanceMergedConfiguration(skin, relateFileLoc, relateDefaultFileLoc, xslIs);
157                    }
158                    
159                    this._label = _configureI18n(configuration.getChild("label", false), this._label);
160                    this._description = _configureI18n(configuration.getChild("description", false), this._description);
161                    this._smallImage = _configureThumbnail(configuration.getChild("thumbnail").getChild("small").getValue(null), this._smallImage);
162                    this._mediumImage = _configureThumbnail(configuration.getChild("thumbnail").getChild("medium").getValue(null), this._mediumImage);
163                    this._largeImage = _configureThumbnail(configuration.getChild("thumbnail").getChild("large").getValue(null), this._largeImage);
164                    
165                    _viewParameters = _configureTemplateViewParameters(this._skinId, this._id, configuration);
166                    
167                    this._zones = new LinkedHashMap<>();
168                    for (Configuration zoneConfiguration : configuration.getChild("zones").getChildren("zone"))
169                    {
170                        String zoneId = zoneConfiguration.getAttribute("id");
171                        String zoneType = zoneConfiguration.getAttribute("type", SkinTemplateZone.TYPE_PRIMARY);
172                        I18nizableText label = _configureI18n(zoneConfiguration.getChild("label", false), new I18nizableText(zoneId));
173                        I18nizableText description = _configureI18n(zoneConfiguration.getChild("description", false), new I18nizableText(zoneId));
174                        String smallImage = _configureThumbnail(zoneConfiguration.getChild("thumbnail").getChild("small").getValue(null), "/plugins/web/resources/img/skin/zone_16.png");
175                        String mediumImage = _configureThumbnail(zoneConfiguration.getChild("thumbnail").getChild("medium").getValue(null), "/plugins/web/resources/img/skin/zone_32.png");
176                        String largeImage = _configureThumbnail(zoneConfiguration.getChild("thumbnail").getChild("marge").getValue(null), "/plugins/web/resources/img/skin/zone_48.png");
177                        
178                        String inheritanceString = zoneConfiguration.getAttribute("inherit", null);
179                        
180                        Optional<ViewParametersModel> zoneViewParameters = _configureZoneViewParameters(this._skinId, this._id, zoneId, zoneConfiguration);
181                        Optional<ViewParametersModel> zoneItemViewParameters = _configureZoneItemViewParameters(this._skinId, this._id, zoneId, zoneConfiguration);
182                        
183                        SkinTemplateZone zone = new SkinTemplateZone(
184                                this._skinId,
185                                this._id,
186                                zoneId,
187                                zoneType,
188                                label,
189                                description,
190                                smallImage,
191                                mediumImage,
192                                largeImage,
193                                inheritanceString,
194                                zoneViewParameters,
195                                zoneItemViewParameters
196                                );
197                        
198                        _zones.put(zoneId, zone);
199                    }
200                }
201            }
202            else
203            {
204                _defaultValues();
205            }
206        }
207        catch (Exception e)
208        {
209            _defaultValues();
210            if (_logger.isWarnEnabled())
211            {
212                _logger.warn("Cannot read the configuration file " + relateFileLoc + " for the skin '" + this._skinId + "'. Continue as if file was not existing", e);
213            }
214        }
215        finally
216        {
217            _skinsManager.getSourceResolver().release(configurationFile);
218            _skinsManager.getSourceResolver().release(defaultConfigurationFile);
219        }
220    }
221    
222    /**
223     * Parse template view parameters
224     * @param skinId the skin id
225     * @param templateId the template id
226     * @param templateConfiguration the template configuration
227     * @return the zone item view parameters
228     * @throws ConfigurationException if a configuration error occurred
229     */
230    protected Optional<ViewParametersModel> _configureTemplateViewParameters(String skinId, String templateId, Configuration templateConfiguration) throws ConfigurationException
231    {
232        Configuration parametersConf = templateConfiguration.getChild(ViewParametersManager.VIEW_PARAMETERS_TEMPLATE_CONF_NAME);
233        String viewParametersUniqueId = skinId + "_" + templateId + "_template_view_parameters";
234        
235        Optional<ViewParametersModel> viewParameters = _configureViewParameters(parametersConf, viewParametersUniqueId);
236        
237        return _skinsManager.getViewParametersManager().addGlobalViewParameters(skinId, ViewParametersType.TEMPLATES, viewParameters);
238    }
239    
240    /**
241     * Parse zone view parameters
242     * @param skinId the skin id
243     * @param templateId the template id
244     * @param zoneId the zone id
245     * @param zoneConfiguration the zone configuration
246     * @return the zone view parameters
247     * @throws ConfigurationException if a configuration error occurred
248     */
249    protected Optional<ViewParametersModel> _configureZoneViewParameters(String skinId, String templateId, String zoneId, Configuration zoneConfiguration) throws ConfigurationException
250    {
251        Configuration parametersConf = zoneConfiguration.getChild(ViewParametersManager.VIEW_PARAMETERS_ZONE_CONF_NAME);
252        String viewParametersUniqueId = skinId + "_" + templateId + "_" + zoneId + "_zone_view_parameters";
253        
254        Optional<ViewParametersModel> viewParameters = _configureViewParameters(parametersConf, viewParametersUniqueId);
255        
256        return _skinsManager.getViewParametersManager().addGlobalViewParameters(skinId, ViewParametersType.ZONES , viewParameters);
257    }
258    
259    /**
260     * Parse zone item view parameters
261     * @param skinId the skin id
262     * @param templateId the template id
263     * @param zoneId the zone id
264     * @param zoneConfiguration the zone configuration
265     * @return the zone item view parameters
266     * @throws ConfigurationException if a configuration error occurred
267     */
268    protected Optional<ViewParametersModel> _configureZoneItemViewParameters(String skinId, String templateId, String zoneId, Configuration zoneConfiguration) throws ConfigurationException
269    {
270        Configuration parametersConf = zoneConfiguration.getChild(ViewParametersManager.VIEW_PARAMETERS_ZONE_ITEM_PARENT_CONF_NAME).getChild(ViewParametersManager.VIEW_PARAMETERS_ZONE_ITEM_CONF_NAME);
271        String viewParametersUniqueId = skinId + "_" + templateId + "_" + zoneId + "_zone_item_view_parameters";
272        
273        Optional<ViewParametersModel> viewParameters = _configureViewParameters(parametersConf, viewParametersUniqueId);
274        
275        return _skinsManager.getViewParametersManager().addGlobalViewParameters(skinId, ViewParametersType.ZONEITEMS , viewParameters);
276    }
277    
278    /**
279     * Parse view parameters from configuration
280     * @param paramConfiguration the configuration
281     * @param viewParametersId the view parameters id
282     * @return the view parameters
283     * @throws ConfigurationException if a configuration error occurred
284     */
285    protected Optional<ViewParametersModel> _configureViewParameters(Configuration paramConfiguration, String viewParametersId) throws ConfigurationException
286    {
287        ThreadSafeComponentManager<DisableConditions> parametersDisableConditionsManager = new ThreadSafeComponentManager<>();
288        parametersDisableConditionsManager.setLogger(_logger);
289        parametersDisableConditionsManager.contextualize(_skinsManager.getContext());
290        parametersDisableConditionsManager.service(_skinsManager.getServiceManager());
291        _components.add(parametersDisableConditionsManager);
292        
293        ThreadSafeComponentManager<DisableConditions> repeaterDisableConditionsManager = new ThreadSafeComponentManager<>();
294        repeaterDisableConditionsManager.setLogger(_logger);
295        repeaterDisableConditionsManager.contextualize(_skinsManager.getContext());
296        repeaterDisableConditionsManager.service(_skinsManager.getServiceManager());
297        _components.add(repeaterDisableConditionsManager);
298        
299        ThreadSafeComponentManager<Validator> validatorManager = new ThreadSafeComponentManager<>();
300        validatorManager.setLogger(_logger);
301        validatorManager.contextualize(_skinsManager.getContext());
302        validatorManager.service(_skinsManager.getServiceManager());
303        _components.add(validatorManager);
304        
305        ThreadSafeComponentManager<Enumerator> enumeratorManager = new ThreadSafeComponentManager<>();
306        enumeratorManager.setLogger(_logger);
307        enumeratorManager.contextualize(_skinsManager.getContext());
308        enumeratorManager.service(_skinsManager.getServiceManager());
309        _components.add(enumeratorManager);
310        
311        ViewParametersModel viewParameters = new ViewParametersModel(viewParametersId, new View(), new LinkedHashMap<>());
312            
313        ViewParameterDefinitionParser elementDefinitionParser = new ViewParameterDefinitionParser(_skinsManager.getViewParameterTypeExtensionPoint(), parametersDisableConditionsManager, enumeratorManager, validatorManager);
314        DataHolderRepeaterDefinitionParser repeaterDefinitionParser = new DataHolderRepeaterDefinitionParser(_skinsManager.getViewParameterTypeExtensionPoint(), repeaterDisableConditionsManager);
315        
316        String catalog = "skin." + this._skinId;
317        String defaultPlugin = "web"; // Default plugin name for enumerator and validator component
318        ViewAndParameters viewAndParameters = _skinsManager.getViewAndParametersParser().parseParameters(paramConfiguration, defaultPlugin, catalog, viewParameters, elementDefinitionParser, repeaterDefinitionParser);
319        viewParameters.setView(viewAndParameters.getView());
320        viewParameters.setModelItems(viewAndParameters.getParameters());
321
322        try
323        {
324            elementDefinitionParser.lookupComponents();
325        }
326        catch (Exception e)
327        {
328            throw new ConfigurationException("Unable to lookup parameter local components", paramConfiguration, e);
329        }
330        
331        return Optional.ofNullable(viewParameters);
332    }
333    
334    /**
335     * Dispose skin template component
336     */
337    public void dispose()
338    {
339        for (ThreadSafeComponentManager component : _components)
340        {
341            component.dispose();
342        }
343    }
344
345    private String _configureThumbnail(String value, String defaultImage)
346    {
347        if (value == null)
348        {
349            return defaultImage;
350        }
351        else
352        {
353            return "/skins/" + this._skinId + "/templates/" + this._id + "/resources/" + value;
354        }
355    }
356
357    private I18nizableText _configureI18n(Configuration child, I18nizableText defaultValue) throws ConfigurationException
358    {
359        if (child != null)
360        {
361            String value = child.getValue();
362            if (child.getAttributeAsBoolean("i18n", false))
363            {
364                return new I18nizableText("skin." + this._skinId, value);
365            }
366            else
367            {
368                return new I18nizableText(value);
369            }
370        }
371        else
372        {
373            return defaultValue;
374        }
375    }
376
377    /**
378     * The template id
379     * @return the id
380     */
381    public String getId()
382    {
383        return _id;
384    }
385    
386    /**
387     * The template label
388     * @return The label
389     */
390    public I18nizableText getLabel()
391    {
392        return _label;
393    }
394    /**
395     * The template description
396     * @return The description. Can not be null but can be empty
397     */
398    public I18nizableText getDescription()
399    {
400        return _description;
401    }
402    
403    /**
404     * The small image file uri
405     * @return The small image file uri
406     */
407    public String getSmallImage()
408    {
409        return _smallImage;
410    }
411
412    /**
413     * The medium image file uri
414     * @return The medium image file uri
415     */
416    public String getMediumImage()
417    {
418        return _mediumImage;
419    }
420    
421    /**
422     * The large image file uri
423     * @return The large image file uri
424     */
425    public String getLargeImage()
426    {
427        return _largeImage;
428    }
429    
430    /**
431     * The zones defined in by the template def
432     * @return The zones
433     */
434    public Map<String, SkinTemplateZone> getZones()
435    {
436        return _zones;
437    }
438    
439    /**
440     * The zone identifier by its id
441     * @param zoneId The id of the zone definition to get
442     * @return The zone or null if no zone has this name
443     */
444    public SkinTemplateZone getZone(String zoneId)
445    {
446        return _zones.get(zoneId);
447    }
448    
449    /**
450     * Get the primary default zone.
451     * That is the first primary zone, or the 'default' zone if it exists and is primary
452     * @return The default zone or null if there is no primary zone
453     */
454    public String getDefaultZoneId()
455    {
456        String defaultZoneId = null;
457        
458        Map<String, SkinTemplateZone> zones = this.getZones();
459        for (SkinTemplateZone zone : zones.values())
460        {
461            if (SkinTemplateZone.TYPE_PRIMARY.equals(zone.getType())
462                && ("default".equals(zone.getId()) || defaultZoneId == null))
463            {
464                defaultZoneId = zone.getId();
465            }
466        }
467        
468        return defaultZoneId;
469    }
470    
471    /**
472     * Get the view parameters model
473     * @return the view parameters
474     */
475    public Optional<ViewParametersModel> getViewParameters()
476    {
477        return _viewParameters;
478    }
479}
480