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