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