001/*
002 *  Copyright 2013 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.skinfactory.model;
017
018import java.io.InputStream;
019import java.nio.file.Files;
020import java.nio.file.Path;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.Map;
024import java.util.Set;
025import java.util.stream.Collectors;
026
027import org.apache.avalon.framework.activity.Initializable;
028import org.apache.avalon.framework.component.Component;
029import org.apache.avalon.framework.configuration.Configuration;
030import org.apache.avalon.framework.configuration.ConfigurationException;
031import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
032import org.apache.avalon.framework.logger.AbstractLogEnabled;
033import org.apache.avalon.framework.service.ServiceException;
034import org.apache.avalon.framework.service.ServiceManager;
035import org.apache.avalon.framework.service.Serviceable;
036import org.apache.avalon.framework.thread.ThreadSafe;
037import org.apache.commons.lang3.StringUtils;
038
039import org.ametys.core.cache.AbstractCacheManager;
040import org.ametys.core.cache.Cache;
041import org.ametys.runtime.i18n.I18nizableText;
042import org.ametys.skinfactory.SkinFactoryComponent;
043import org.ametys.skinfactory.parameters.AbstractSkinParameter;
044import org.ametys.skinfactory.parameters.I18nizableTextParameter;
045import org.ametys.skinfactory.parameters.ImageParameter;
046import org.ametys.web.skin.SkinModel;
047import org.ametys.web.skin.SkinModelsManager;
048
049/**
050 * Manages the design conceptions of a model
051 */
052public class ModelDesignsManager extends AbstractLogEnabled implements ThreadSafe, Serviceable, Initializable, Component
053{
054    /** The avalon role name */
055    public static final String ROLE = ModelDesignsManager.class.getName();
056    
057    private static final String __MODEL_DESIGN_CACHE = ModelDesignsManager.class.getName() + "$modelDesign";
058    
059    private SkinModelsManager _modelsManager;
060    private SkinFactoryComponent _skinFactoryManager;
061
062    private AbstractCacheManager _cacheManager;
063
064    @Override
065    public void service(ServiceManager smanager) throws ServiceException
066    {
067        _modelsManager = (SkinModelsManager) smanager.lookup(SkinModelsManager.ROLE);
068        _skinFactoryManager = (SkinFactoryComponent) smanager.lookup(SkinFactoryComponent.ROLE);
069        _cacheManager = (AbstractCacheManager) smanager.lookup(AbstractCacheManager.ROLE);
070    }
071    
072    public void initialize() throws Exception
073    {
074        _cacheManager.createMemoryCache(__MODEL_DESIGN_CACHE,
075                new I18nizableText("plugin.skinfactory", "PLUGINS_SKINFACTORY_CACHE_MODEL_DESIGN_LABEL"),
076                new I18nizableText("plugin.skinfactory", "PLUGINS_SKINFACTORY_CACHE_MODEL_DESIGN_DESCRIPTION"),
077                true,
078                null);
079    }
080    
081    /**
082     * Get all design instances for given model
083     * @param modelName The model name
084     * @return all design instances for given model
085     */
086    public Set<Design> getDesigns (String modelName)
087    {
088        return _getDesignCache().get(modelName, this::_getDesigns);
089    }
090    
091    /**
092     * Get design instance of given id and model name
093     * @param modelName The model name
094     * @param id The id
095     * @return design instance
096     */
097    public Design getDesign (String modelName, String id)
098    {
099        Set<Design> designs = _getDesignCache().get(modelName, this::_getDesigns);
100        return designs.stream()
101            .filter(design -> StringUtils.equals(design.getId(), id))
102            .findFirst().orElse(null);
103    }
104    
105    /**
106     * Apply a design
107     * @param modelName The model name
108     * @param id Id of design
109     * @param skinDir The skin directory (could be temp, work or skins)
110     */
111    public void applyDesign (String modelName, String id, Path skinDir)
112    {
113        SkinModel model = _modelsManager.getModel(modelName);
114     
115        Path file = model.getPath().resolve("model/designs/" + id + ".xml");
116        if (Files.exists(file))
117        {
118            // Apply color theme
119            String themeId = _getColorTheme(file);
120            if (themeId != null)
121            {
122                _skinFactoryManager.saveColorTheme(skinDir, themeId);
123            }
124            
125            // Apply values
126            Map<String, Object> values = _getParameterValues (modelName, file);
127            _skinFactoryManager.applyModelParameters(modelName, skinDir, values);
128        }
129    }
130    
131    
132    private Set<Design> _getDesigns (String modelName)
133    {
134        SkinModel model = _modelsManager.getModel(modelName);
135        
136        Set<Design> designs;
137        
138        Path designDir = model.getPath().resolve("model/designs");
139        if (Files.exists(designDir))
140        {
141            try
142            {
143                designs = Files.walk(designDir, 1)
144                     .filter(f -> f.getFileName().toString().toLowerCase().endsWith(".xml"))
145                     .map(f -> _configureDesign(modelName, f))
146                     .filter(design -> design != null)
147                     .collect(Collectors.toSet());
148            }
149            catch (Exception e)
150            {
151                throw new RuntimeException("Cannot read the configuration file model/designs for the model " + modelName);
152            }
153        }
154        else
155        {
156            designs = new HashSet<>();
157        }
158        return designs;
159    }
160    
161    
162    private Design _configureDesign (String modelName, Path configurationFile)
163    {
164        try (InputStream is = Files.newInputStream(configurationFile))
165        {
166            String fileName = configurationFile.getFileName().toString();
167            String id = fileName.substring(0, fileName.lastIndexOf("."));
168            
169            Configuration configuration = new DefaultConfigurationBuilder().build(is);
170            I18nizableText label = _configureI18nizableText(configuration.getChild("label", false), new I18nizableText(id), modelName);
171            I18nizableText description = _configureI18nizableText(configuration.getChild("description", false), new I18nizableText(id), modelName);
172            
173            String iconName = id + ".png";
174            String icon = "/plugins/skinfactory/resources/img/actions/designs_32.png";
175            Path iconFile = configurationFile.getParent().resolve(iconName);
176            if (Files.exists(iconFile))
177            {
178                icon = "/plugins/skinfactory/" + modelName + "/_thumbnail/32/32/model/designs/" + iconName;
179            }
180            
181            return new Design(id, label, description, icon);
182        }
183        catch (Exception e)
184        {
185            if (getLogger().isWarnEnabled())
186            {
187                getLogger().warn("Cannot read the configuration file model/designs/" + configurationFile.getFileName().toString()  + " for the model '" + modelName + "'. Continue as if file was not existing", e);
188            }
189            return null;
190        }
191        
192    }
193    
194    private String _getColorTheme(Path file)
195    {
196        try (InputStream is = Files.newInputStream(file))
197        {
198            
199            Configuration configuration = new DefaultConfigurationBuilder(true).build(is);
200            return configuration.getChild("color-theme").getValue(null);
201        }
202        catch (Exception e)
203        {
204            getLogger().error("Unable to get color theme", e);
205            return null;
206        }
207    }
208    
209    private Map<String, Object> _getParameterValues (String modelName, Path file)
210    {
211        Map<String, Object> values = new HashMap<>();
212        
213        try (InputStream is = Files.newInputStream(file))
214        {
215            
216            Configuration configuration = new DefaultConfigurationBuilder(true).build(is);
217            Configuration[] parametersConf = configuration.getChild("parameters").getChildren("parameter");
218            
219            Map<String, AbstractSkinParameter> modelParameters = _skinFactoryManager.getModelParameters(modelName);
220            
221            for (Configuration paramConf : parametersConf)
222            {
223                String id = paramConf.getAttribute("id");
224                AbstractSkinParameter modelParam = modelParameters.get(id);
225                if (modelParam != null)
226                {
227                    if (modelParam instanceof I18nizableTextParameter)
228                    {
229                        Configuration[] children = paramConf.getChildren();
230                        Map<String, String> langValues = new HashMap<>();
231                        for (Configuration langConfig : children)
232                        {
233                            langValues.put(langConfig.getName(), langConfig.getValue(""));
234                        }
235                        values.put(id, langValues);
236                    }
237                    else if (modelParam instanceof ImageParameter)
238                    {
239                        values.put(id, new ImageParameter.FileValue(paramConf.getValue(""), false));
240                    }
241                    else
242                    {
243                        values.put(id, paramConf.getValue(""));
244                    }
245                }
246            }
247            
248            return values;
249        }
250        catch (Exception e)
251        {
252            getLogger().error("Unable to get values of all parameters", e);
253            return new HashMap<>();
254        }
255    }
256    
257    
258    private I18nizableText _configureI18nizableText(Configuration configuration, I18nizableText defaultValue, String modelName) throws ConfigurationException
259    {
260        if (configuration != null)
261        {
262            boolean i18nSupported = configuration.getAttributeAsBoolean("i18n", false);
263            if (i18nSupported)
264            {
265                String catalogue = configuration.getAttribute("catalogue", null);
266                if (catalogue == null)
267                {
268                    catalogue = "model." + modelName;
269                }
270
271                return new I18nizableText(catalogue, configuration.getValue());
272            }
273            else
274            {
275                return new I18nizableText(configuration.getValue(""));
276            }
277        }
278        else
279        {
280            return defaultValue;
281        }
282        
283    }
284    
285    private Cache<String, Set<Design>> _getDesignCache()
286    {
287        return _cacheManager.get(__MODEL_DESIGN_CACHE);
288    }
289    
290    /**
291     * Bean representing a model design
292     *
293     */
294    public static class Design 
295    {
296        private String _id;
297        private I18nizableText _label;
298        private I18nizableText _description;
299        private String _icon;
300
301        /**
302         * Constructor
303         * @param id the theme id
304         * @param label the theme's label
305         * @param description the theme's description
306         * @param icon the icon
307         */
308        public Design (String id, I18nizableText label, I18nizableText description, String icon)
309        {
310            _id = id;
311            _label = label;
312            _description = description;
313            _icon = icon;
314        }
315        
316        /**
317         * Get the id
318         * @return the id
319         */
320        public String getId ()
321        {
322            return _id;
323        }
324        
325        /**
326         * Get the label
327         * @return the label
328         */
329        public I18nizableText getLabel ()
330        {
331            return _label;
332        }
333        
334        /**
335         * Get the description
336         * @return the description
337         */
338        public I18nizableText getDescription ()
339        {
340            return _description;
341        }
342        
343        /**
344         * Get the icon
345         * @return the icon
346         */
347        public String getIcon ()
348        {
349            return _icon;
350        }
351    }        
352
353}