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