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