001/*
002 *  Copyright 2012 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;
017
018import java.io.File;
019import java.io.FileFilter;
020import java.io.FileInputStream;
021import java.io.IOException;
022import java.io.InputStream;
023import java.nio.charset.StandardCharsets;
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.LinkedHashMap;
027import java.util.List;
028import java.util.Map;
029import java.util.regex.Matcher;
030import java.util.regex.Pattern;
031
032import org.apache.avalon.framework.component.Component;
033import org.apache.avalon.framework.configuration.Configuration;
034import org.apache.avalon.framework.configuration.ConfigurationException;
035import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
036import org.apache.avalon.framework.logger.AbstractLogEnabled;
037import org.apache.avalon.framework.service.ServiceException;
038import org.apache.avalon.framework.service.ServiceManager;
039import org.apache.avalon.framework.service.Serviceable;
040import org.apache.avalon.framework.thread.ThreadSafe;
041import org.apache.commons.io.IOUtils;
042import org.apache.commons.lang.StringUtils;
043import org.apache.excalibur.source.Source;
044import org.apache.excalibur.source.SourceResolver;
045import org.apache.excalibur.source.SourceUtil;
046import org.apache.excalibur.source.impl.FileSource;
047
048import org.ametys.runtime.i18n.I18nizableText;
049import org.ametys.skinfactory.parameters.AbstractSkinParameter;
050import org.ametys.skinfactory.parameters.CSSColorParameter;
051import org.ametys.skinfactory.parameters.CSSParameter;
052import org.ametys.skinfactory.parameters.I18nizableTextParameter;
053import org.ametys.skinfactory.parameters.ImageParameter;
054import org.ametys.skinfactory.parameters.ImageParameter.FileValue;
055import org.ametys.skinfactory.parameters.TextParameter;
056import org.ametys.skinfactory.parameters.Variant;
057import org.ametys.skinfactory.parameters.VariantParameter;
058import org.ametys.web.skin.SkinModel;
059import org.ametys.web.skin.SkinModelsManager;
060
061/**
062 * Manager for skin parameters
063 * 
064 */
065public class SkinFactoryComponent extends AbstractLogEnabled implements Component, ThreadSafe, Serviceable
066{
067    /** Avalon role */
068    public static final String ROLE = SkinFactoryComponent.class.getName();
069
070    /** Pattern for CSS parameters. E.g: color: #EEEEEE /* AMETYS("myuniqueid", "Label", DESCRIPTION_I18N_KEY) *\/; **/ 
071    public static final Pattern CSS_PARAM_PATTERN = Pattern.compile("\\s*([^,:\\s]*)\\s*:\\s*([^:;!]*)\\s*(?:!important)?\\s*\\/\\*\\s*AMETYS\\s*\\(\\s*\"([^\"]+)\"\\s*(?:,\\s*([^,\"\\s]+|\"[^\"]*\")\\s*)?(?:,\\s*([^,\"\\s]+|\"[^\"]*\")\\s*)?\\)\\s*\\*\\/\\s*;?\\s*", Pattern.MULTILINE); 
072    
073    /** Pattern for i18n text parameters. E.g: <message key="SKIN_TITLE">Ametys, Web Java Open Source CMS<!-- Ametys("text.i18n.site.title", "Titre du site") --></message> */
074    public static final Pattern I18N_PARAM_PATTERN = Pattern.compile("^\\s*<message key=\"([^,:\\s]*)\">([^<]*)<!--\\s*AMETYS\\s*\\(\\s*\"([^\"]+)\"\\s*(?:,\\s*([^,\"\\s]+|\"[^\"]*\")\\s*)?(?:,\\s*([^,\"\\s]+|\"[^\"]*\")\\s*)?\\)\\s*--></message>\\s*$", Pattern.MULTILINE); 
075    
076    /** Pattern for text parameters. E.g: &lt;xsl:variable name="foo"&gt;test&lt;!-- Ametys("variable.foo", "Foo", "Foo") --&gt;&lt;/xsl:variable&gt; */
077    public static final Pattern TXT_PARAM_PATTERN = Pattern.compile("^[^>]*>([^<]*)<!--\\s*AMETYS\\s*\\(\\s*\"([^\"]+)\"\\s*(?:,\\s*([^,\"\\s]+|\"[^\"]*\")\\s*)?(?:,\\s*([^,\"\\s]+|\"[^\"]*\")\\s*)?\\)\\s*-->.*$", Pattern.MULTILINE); 
078    
079    private static final Pattern __I18N_CATALOG_LANGUAGE = Pattern.compile("^\\s*<catalogue xml:lang=\"([a-z]{2})\">\\s*$", Pattern.MULTILINE); 
080    
081    private final Map<String, Map<String, AbstractSkinParameter>> _modelsParameters = new HashMap<>();
082    private final Map<String, String> _modelsHash = new HashMap<>();
083    
084    private SkinModelsManager _modelsManager;
085    private SourceResolver _resolver;
086    
087    @Override
088    public void service(ServiceManager smanager) throws ServiceException
089    {
090        _modelsManager = (SkinModelsManager) smanager.lookup(SkinModelsManager.ROLE);
091        _resolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE);
092    }
093
094    /**
095     * Remove all model parameters from cache
096     */
097    public void clearModelsParametersCache()
098    {
099        _modelsParameters.clear();
100    }
101    
102    /**
103     * Get the skin parameter of the given model
104     * @param modelName The model name
105     * @param paramId The id of the parameter
106     * @return The skin parameter or <code>null</code> if it doesn't exist
107     */
108    public AbstractSkinParameter getModelParamater (String modelName, String paramId)
109    {
110        Map<String, AbstractSkinParameter> modelParameters = getModelParameters(modelName);
111        return modelParameters.get(paramId);
112    }
113    
114    /**
115     * Determines if the model was changed
116     * @param modelName The model name
117     * @return <code>true</code> if the model was changed
118     */
119    public boolean isModelUpToDate (String modelName)
120    {
121        String hash = _modelsManager.getModelHash(modelName);
122        if (!_modelsHash.containsKey(modelName) || !_modelsHash.get(modelName).equals(hash))
123        {
124            // The model is not up-to-date, clear cache
125            _modelsHash.put(modelName, hash);
126            return false;
127        }
128        return true;
129    }
130    
131    /**
132     * Get the skin parameters of a model
133     * @param modelName the model name
134     * @return skin parameters in a List
135     */
136    public Map<String, AbstractSkinParameter> getModelParameters (String modelName)
137    {
138        if (!isModelUpToDate(modelName))
139        {
140            // The model is not up-to-date, clear cache
141            _modelsParameters.remove(modelName);
142        }
143        
144        if (_modelsParameters.containsKey(modelName))
145        {
146            return _modelsParameters.get(modelName);
147        }
148        
149        _modelsParameters.put(modelName, new LinkedHashMap<String, AbstractSkinParameter>());
150        
151        Map<String, AbstractSkinParameter> skinParams = _modelsParameters.get(modelName);
152        
153        File modelDir = _modelsManager.getModel(modelName).getFile();
154
155        // Variants parameters
156        skinParams.putAll(_getVariantParameters(modelDir, modelName));
157        
158        // Images Parameters
159        skinParams.putAll(_getImageParameters(modelDir, modelName));
160        
161        // CSS parameters
162        skinParams.putAll(_getCSSParameters(modelDir, modelName));
163        
164        // I18n parameters
165        skinParams.putAll(_getI18nTextParameters(modelDir, modelName));
166        
167        // Text parameters
168        skinParams.putAll(_getTextParameters(modelDir, modelName));
169        
170        return skinParams;
171    }
172    
173    private Map<String, VariantParameter> _getVariantParameters (File modelDir, String modelName)
174    {
175        Map<String, VariantParameter> params = new HashMap<>();
176        
177        File variantsDir = new File(modelDir, "model/variants");
178        if (variantsDir.exists())
179        {
180            _findVariantsParameters (variantsDir, params, modelName);
181        }
182        return params;
183    }
184    
185    private void _findVariantsParameters (File variantsDir, Map<String, VariantParameter> params, String modelName)
186    {
187        File[] listFiles = variantsDir.listFiles(new SkinDirectoryFilter());
188        for (File child : listFiles)
189        {
190            String filename = child.getName();
191            List<Variant> values = _getVariantValues (child, modelName);
192            
193            // avoid false directory such as CVS
194            if (values.size() != 0)
195            {
196                VariantParameter variantParameter = new VariantParameter(filename, new I18nizableText(filename), new I18nizableText(""), values);
197                _configureVariant (child, variantParameter, modelName);
198                params.put(variantParameter.getId(), variantParameter);
199            }
200            else
201            {
202                getLogger().debug("Discarding variant " + child.getAbsolutePath() + " because it has no sub directories as values");
203            }
204        }
205    }
206    
207    private List<Variant> _getVariantValues (File variant, String modelName)
208    {
209        List<Variant> values = new ArrayList<>();
210        
211        for (File child : variant.listFiles(new SkinDirectoryFilter()))
212        {
213            String id = child.getName();
214            
215            // Thumbnail
216            String thumbnailPath = null;
217            File thumbnailFile = new File(child, id + ".png");
218            if (thumbnailFile.exists())
219            {
220                thumbnailPath = thumbnailFile.getAbsolutePath().substring(variant.getParentFile().getAbsolutePath().length() + 1).replaceAll("\\\\", "/");
221            }
222            
223            I18nizableText label = new I18nizableText(id);
224            I18nizableText description = new I18nizableText("");
225            File file = new File(child, id + ".xml");
226            if (file.exists())
227            {
228                try (InputStream is = new FileInputStream(file))
229                {
230                    Configuration configuration = new DefaultConfigurationBuilder().build(is);
231                    label = _configureI18nizableText(configuration.getChild("label"), modelName);
232                    
233                    description = _configureI18nizableText(configuration.getChild("description"), modelName);
234                }
235                catch (Exception e)
236                {
237                    getLogger().error("Unable to configure variant '" + id + "'", e);
238                }
239            }
240            
241            values.add(new Variant(id, label, description, thumbnailPath));
242        }
243        return values;
244    }
245    
246    private void _configureVariant (File variantFile, VariantParameter param, String modelName)
247    {
248        File file = new File(variantFile + "/" + variantFile.getName() + ".xml");
249        
250        if (file.exists())
251        {
252            try (InputStream is = new FileInputStream(file))
253            {
254                Configuration configuration = new DefaultConfigurationBuilder().build(is);
255
256                I18nizableText label = _configureI18nizableText(configuration.getChild("label"), modelName);
257                param.setLabel(label);
258                
259                I18nizableText description = _configureI18nizableText(configuration.getChild("description"), modelName);
260                param.setDescription(description);
261                
262                param.setIconGlyph(configuration.getChild("icon-glyph").getValue(null));
263            }
264            catch (Exception e)
265            {
266                getLogger().error("Unable to configure variant '" + param.getId() + "'", e);
267            }
268        }
269        
270        File iconSmall = new File(variantFile + "/thumbnail_16.png");
271        if (iconSmall.exists())
272        {
273            param.setIconSmall(iconSmall.getName());
274        }
275        
276        File iconLarge = new File(variantFile + "/thumbnail_32.png");
277        if (iconLarge.exists())
278        {
279            param.setIconLarge(iconLarge.getName());
280        }
281    }
282    
283    private Map<String, ImageParameter> _getImageParameters (File modelDir, String modelName)
284    {
285        Map<String, ImageParameter> params = new HashMap<>();
286        
287        File imagesDir = new File(modelDir, "model/images");
288        if (imagesDir.exists())
289        {
290            _findImagesParameters (modelDir, imagesDir, imagesDir, params, modelName);
291        }
292        return params;
293    }
294    
295    private void _findImagesParameters (File modelDir, File imagesDir, File file, Map<String, ImageParameter> params, String modelName)
296    {
297        File[] listFiles = file.listFiles();
298        for (File child : listFiles)
299        {
300            if (child.isDirectory())
301            {
302                String filename = child.getName();
303                String lcFilename = filename.toLowerCase();
304                if (lcFilename.endsWith(".png") || lcFilename.endsWith(".jpg") || lcFilename.endsWith(".jpeg") || lcFilename.endsWith(".gif"))
305                {
306                    String imagePath = child.getAbsolutePath().substring(imagesDir.getAbsolutePath().length() + 1);
307                    ImageParameter imageParameter = new ImageParameter(imagePath, new I18nizableText(filename), new I18nizableText(""));
308                    _configureImage (child, imageParameter, modelName);
309                    params.put(imageParameter.getId(), imageParameter);
310                }
311                else
312                {
313                    _findImagesParameters (modelDir, imagesDir, child, params, modelName);
314                }
315            }
316        }
317    }
318    
319    private void _configureImage (File imageFile, ImageParameter param, String modelName)
320    {
321        String filename = imageFile.getName();
322        int i = filename.lastIndexOf(".");
323        if (i > 0)
324        {
325            filename = filename.substring(0, i);
326        }
327        File file = new File(imageFile + "/" + filename + ".xml");
328        
329        if (file.exists())
330        {
331            try (InputStream is = new FileInputStream(file))
332            {
333                Configuration configuration = new DefaultConfigurationBuilder().build(is);
334
335                I18nizableText label = _configureI18nizableText(configuration.getChild("label"), modelName);
336                param.setLabel(label);
337                
338                I18nizableText description = _configureI18nizableText(configuration.getChild("description"), modelName);
339                param.setDescription(description);
340                
341                param.setIconGlyph(configuration.getChild("icon-glyph").getValue(null));
342            }
343            catch (Exception e)
344            {
345                getLogger().error("Unable to configure image parameter '" + param.getId() + "'", e);
346            }
347        }
348        
349        File iconSmall = new File(imageFile + "/thumbnail_16.png");
350        if (iconSmall.exists())
351        {
352            param.setIconSmall(iconSmall.getName());
353        }
354        File iconLarge = new File(imageFile + "/thumbnail_32.png");
355        if (iconLarge.exists())
356        {
357            param.setIconLarge(iconLarge.getName());
358        }
359    }
360    
361    
362    private Map<String, CSSParameter> _getCSSParameters(File modelDir, String modelName)
363    {
364        Map<String, CSSParameter> cssParams = new HashMap<>();
365
366        // Parse css files in the root resources directory.
367        for (File file : _listCSSFiles (new File(modelDir, "/resources")))
368        {
369            _parseCSSFile(cssParams, modelName, file);
370        }
371        
372        // Parse css files for each templates.
373        File templatesDir = new File(modelDir, "/templates");
374        if (templatesDir.isDirectory())
375        {
376            File[] templates = templatesDir.listFiles();
377            if (templates != null)
378            {
379                for (File template : templates)
380                {
381                    for (File file : _listCSSFiles (new File(template, "resources")))
382                    {
383                        _parseCSSFile(cssParams, modelName, file);
384                    }
385                }
386            }
387        }
388        
389        // Parse xsl files for inline css 
390        for (File file : _listXSLFiles(modelDir))
391        {
392            if (file.isFile())
393            {
394                _parseCSSFile(cssParams, modelName, file);
395            }
396        }
397
398        return cssParams;
399    }
400
401    private List<File> _listCSSFiles (File file)
402    {
403        List<File> files = new ArrayList<>();
404
405        File[] listFiles = file.listFiles();
406        if (listFiles != null)
407        {
408            for (File child : listFiles)
409            {
410                if (child.isFile() && child.getName().endsWith(".css"))
411                {
412                    files.add(child);
413                }
414                else if (child.isDirectory())
415                {
416                    files.addAll(_listCSSFiles(child));
417                }
418            }
419        }
420
421        return files;
422    }
423
424    private void _parseCSSFile (Map<String, CSSParameter> cssParams, String modelName, File cssFile)
425    {
426        try (InputStream is = new FileInputStream(cssFile))
427        {
428            String string = IOUtils.toString(is, "UTF-8");
429            
430            Matcher m = CSS_PARAM_PATTERN.matcher(string);
431            while (m.find())
432            {
433                String id = m.group(3);
434                if (cssParams.containsKey(id))
435                {
436                    CSSParameter cssParameter = cssParams.get(id);
437                    cssParameter.addCSSFile(cssFile);
438                    
439                    I18nizableText label = m.group(4) != null ? _parseI18nizableText(m.group(4), modelName) : null;
440                    if (label != null)
441                    {
442                        cssParameter.setLabel(label);
443                    }
444                    
445                    I18nizableText description = m.group(5) != null ? _parseI18nizableText(m.group(5), modelName) : null;
446                    if (description != null)
447                    {
448                        cssParameter.setDescription(description);
449                    }
450                }
451                else
452                {
453                    String cssProperty = m.group(1);
454                    String defaultValue = m.group(2).trim();
455
456                    I18nizableText label = m.group(4) != null ? _parseI18nizableText(m.group(4), modelName) : null;
457                    I18nizableText description = m.group(5) != null ? _parseI18nizableText(m.group(5), modelName) : null;
458                    
459                    if (cssProperty.equals("color") || cssProperty.equals("background-color") || cssProperty.equals("border-color"))
460                    {
461                        CSSColorParameter cssParameter = new CSSColorParameter (id, label, description, cssFile, cssProperty, defaultValue, _modelsManager.getModel(modelName), this);
462                        cssParams.put(id, cssParameter);
463                    }
464                    else
465                    {
466                        CSSParameter cssParameter = new CSSParameter(id, label, description, cssFile, cssProperty, defaultValue);
467                        cssParams.put(id, cssParameter);
468                    }
469                }
470            }
471        }
472        catch (IOException e)
473        {
474            getLogger().error("Unable to parse file '" + cssFile.getName() + "'", e);
475        }
476    }
477    
478    private Map<String, TextParameter> _getTextParameters(File modelDir, String modelName)
479    {
480        Map<String, TextParameter> textParams = new HashMap<>();
481
482        for (File file : _listXSLFiles(modelDir))
483        {
484            if (file.isFile())
485            {
486                textParams.putAll(_parseXSLFile(modelName, file));
487            }
488        }
489
490        return textParams;
491    }
492    
493    private List<File> _listXSLFiles (File file)
494    {
495        List<File> files = new ArrayList<>();
496
497        File[] listFiles = file.listFiles();
498        if (listFiles != null)
499        {
500            for (File child : listFiles)
501            {
502                if (child.isFile() && child.getName().endsWith(".xsl"))
503                {
504                    files.add(child);
505                }
506                else if (child.isDirectory())
507                {
508                    files.addAll(_listXSLFiles(child));
509                }
510            }
511        }
512
513        return files;
514    }
515    
516    private Map<String, TextParameter> _parseXSLFile (String skinName, File xslFile)
517    {
518        Map<String, TextParameter> txtParams = new LinkedHashMap<>();
519        
520        try (InputStream is = new FileInputStream(xslFile))
521        {
522            String string = IOUtils.toString(is, "UTF-8");
523            
524            Matcher m = TXT_PARAM_PATTERN.matcher(string);
525            while (m.find())
526            {
527                String id = m.group(2);
528                String defaultValue = m.group(1);
529                
530                I18nizableText label = m.group(3) != null ? _parseI18nizableText(m.group(3), skinName) : new I18nizableText(id);
531                I18nizableText description = m.group(4) != null ? _parseI18nizableText(m.group(4), skinName) : new I18nizableText("");
532                
533                TextParameter txtParam = new TextParameter(id, label, description, xslFile, defaultValue);
534                txtParams.put(id, txtParam);
535            }
536        }
537        catch (IOException e)
538        {
539            getLogger().error("Unable to parse file '" + xslFile.getName() + "'", e);
540        }
541        
542        return txtParams;
543    }
544    
545    private Map<String, I18nizableTextParameter> _getI18nTextParameters(File modelDir, String modelName)
546    {
547        Map<String, I18nizableTextParameter> i18nParams = new HashMap<>();
548
549        File i18nDir = new File(modelDir, "/i18n");
550        for (File file : i18nDir.listFiles())
551        {
552            if (file.getName().equals("messages.xml"))
553            {
554                i18nParams.putAll(_parseI18nFile(modelName, file));
555            }
556        }
557
558        return i18nParams;
559    }
560    
561
562    private Map<String, I18nizableTextParameter> _parseI18nFile (String skinName, File i18nFile)
563    {
564        Map<String, I18nizableTextParameter> i18nParams = new LinkedHashMap<>();
565        
566        try (InputStream is = new FileInputStream(i18nFile))
567        {
568            String string = IOUtils.toString(is, StandardCharsets.UTF_8);
569            
570            String defaultLang = _getCatalogLanguage (string, i18nFile.getName());
571            
572            Matcher m = I18N_PARAM_PATTERN.matcher(string);
573            while (m.find())
574            {
575                String i18nKey = m.group(1);
576                String defaultValue = m.group(2).trim();
577                String id = m.group(3);
578                I18nizableText label = m.group(4) != null ? _parseI18nizableText(m.group(4), skinName) : new I18nizableText(id);
579                I18nizableText description = m.group(5) != null ? _parseI18nizableText(m.group(5), skinName) : new I18nizableText("");
580                
581                // Default values
582                Map<String, String> defaultValues = new HashMap<>();
583                defaultValues.put(defaultLang, defaultValue);
584                defaultValues.putAll(_getI18nOtherDefaultValues (i18nFile, i18nKey));
585                
586                I18nizableTextParameter i18nParam = new I18nizableTextParameter(id, label, description, i18nKey, defaultValues);
587                i18nParams.put(id, i18nParam);
588            }
589        }
590        catch (IOException e)
591        {
592            getLogger().error("Unable to parse file '" + i18nFile.getName() + "'", e);
593        }
594        
595        return i18nParams;
596    }
597    
598    private String _getCatalogLanguage (String string, String fileName)
599    {
600        Matcher m = __I18N_CATALOG_LANGUAGE.matcher(string);
601        if (m.find())
602        {
603            return m.group(1);
604        }
605        else if (fileName.startsWith("messages_"))
606        {
607            return fileName.substring("messages_".length(), 2);
608        }
609        return "en";
610    }
611    
612    private Map<String, String> _getI18nOtherDefaultValues (File defaultCatalog, String i18nKey)
613    {
614        Pattern pattern = Pattern.compile("^\\s*<message key=\"" + i18nKey + "\">([^<]*)</message>\\s*$", Pattern.MULTILINE); 
615        
616        Map<String, String> defaultValues = new HashMap<>();
617        
618        for (File file : defaultCatalog.getParentFile().listFiles())
619        {
620            String filename = file.getName();
621            if (filename.startsWith("messages") && !filename.equals(defaultCatalog.getName()))
622            {
623                try (InputStream is = new FileInputStream(file))
624                {
625                    String string = org.apache.commons.io.IOUtils.toString(is, "UTF-8");
626                    
627                    String lang = _getCatalogLanguage (string, filename);
628                    
629                    Matcher m = pattern.matcher(string);
630                    if (m.find())
631                    {
632                        String value = m.group(1);
633                        defaultValues.put(lang, value);
634                    }
635                }
636                catch (IOException e)
637                {
638                    getLogger().error("Unable to parse file '" + file.getName() + "'", e);
639                }
640            }
641        }
642        
643        return defaultValues;
644    }
645    
646    private I18nizableText _parseI18nizableText (String label, String modelName)
647    {
648        if (label.startsWith("\"") && label.endsWith("\""))
649        {
650            return new I18nizableText(label.substring(1, label.length() - 1));
651        }
652        else
653        {
654            return new I18nizableText("model." + modelName, label);
655        }
656    }
657    
658    /**
659     * Get the parameter's value
660     * @param skinDir The skin directory (could be in temp, work or skin)
661     * @param modelName The model name
662     * @param id The parameter id
663     * @return The parameter's value
664     */
665    public Object getParameterValue (File skinDir, String modelName, String id)
666    {
667        List<String> ids = new ArrayList<>();
668        ids.add(id);
669        
670        return getParameterValues (skinDir, modelName, ids).get(id);
671    }
672    
673    /**
674     * Get the parameters' values
675     * @param skinDir The skin directory (could be in temp, work or skin)
676     * @param modelName The model name
677     * @param ids The parameters' id
678     * @return The parameters' values
679     */
680    public Map<String, Object> getParameterValues (File skinDir, String modelName, List<String> ids)
681    {
682        Map<String, Object> values = new HashMap<>();
683        
684        File modelFile = new File(skinDir, "model.xml");
685        
686        Source src = null;
687        try
688        {
689            src = new FileSource("file", modelFile);
690            
691            Configuration configuration = new DefaultConfigurationBuilder(true).build(src.getInputStream());
692            Configuration[] parametersConf = configuration.getChild("parameters").getChildren("parameter");
693            
694            for (Configuration paramConf : parametersConf)
695            {
696                String id = paramConf.getAttribute("id");
697                if (ids.contains(id))
698                {
699                    AbstractSkinParameter modelParam = getModelParamater(modelName, id);
700                    if (modelParam instanceof I18nizableTextParameter)
701                    {
702                        Configuration[] children = paramConf.getChildren();
703                        Map<String, String> langValues = new HashMap<>();
704                        for (Configuration langConfig : children)
705                        {
706                            langValues.put(langConfig.getName(), langConfig.getValue(""));
707                        }
708                    }
709                    else
710                    {
711                        values.put(id, paramConf.getValue(""));
712                    }
713                }
714            }
715            
716            return values;
717        }
718        catch (Exception e)
719        {
720            getLogger().error("Unable to get values for parameters '" + StringUtils.join(ids, ", ") + "'", e);
721            return new HashMap<>();
722        }
723        finally
724        {
725            if (src != null)
726            {
727                _resolver.release(src);
728            }
729        }
730    }
731    
732    /**
733     * Get the current theme
734     * @param skinDir The skin directory (could be in temp, work or skin)
735     * @return The current theme id
736     */
737    public String getColorTheme (File skinDir)
738    {
739        File modelFile = new File(skinDir, "model.xml");
740        
741        Source src = null;
742        try
743        {
744            src = new FileSource("file", modelFile);
745            
746            Configuration configuration = new DefaultConfigurationBuilder(true).build(src.getInputStream());
747            return configuration.getChild("color-theme").getValue(null);
748        }
749        catch (Exception e)
750        {
751            getLogger().error("Unable to get theme value", e);
752            return null;
753        }
754        finally
755        {
756            if (src != null)
757            {
758                _resolver.release(src);
759            }
760        }
761    }
762    
763    /**
764     * Get all parameters' values
765     * @param skinDir The skin directory (could be in temp, work or skin)
766     * @param modelName The model name
767     * @return The all parameters' values
768     */
769    public Map<String, Object> getParameterValues (File skinDir, String modelName)
770    {
771        Map<String, Object> values = new HashMap<>();
772        
773        File modelFile = new File(skinDir, "model.xml");
774        
775        Source src = null;
776        try
777        {
778            
779            src = new FileSource("file", modelFile);
780            
781            Configuration configuration = new DefaultConfigurationBuilder(true).build(src.getInputStream());
782            Configuration[] parametersConf = configuration.getChild("parameters").getChildren("parameter");
783            
784            Map<String, AbstractSkinParameter> modelParameters = getModelParameters(modelName);
785            
786            for (Configuration paramConf : parametersConf)
787            {
788                String id = paramConf.getAttribute("id");
789                AbstractSkinParameter modelParam = modelParameters.get(id);
790                if (modelParam != null)
791                {
792                    if (modelParam instanceof I18nizableTextParameter)
793                    {
794                        Configuration[] children = paramConf.getChildren();
795                        Map<String, String> langValues = new HashMap<>();
796                        for (Configuration langConfig : children)
797                        {
798                            langValues.put(langConfig.getName(), langConfig.getValue(""));
799                        }
800                        values.put(id, langValues);
801                    }
802                    else if (modelParam instanceof ImageParameter)
803                    {
804                        boolean uploaded = Boolean.valueOf(paramConf.getAttribute("uploaded", "false"));
805                        values.put(id, new ImageParameter.FileValue(paramConf.getValue(""), uploaded));
806                    }
807                    else
808                    {
809                        values.put(id, paramConf.getValue(""));
810                    }
811                }
812            }
813            
814            return values;
815        }
816        catch (Exception e)
817        {
818            getLogger().error("Unable to get values of all parameters", e);
819            return new HashMap<>();
820        }
821        finally
822        {
823            if (src != null)
824            {
825                _resolver.release(src);
826            }
827        }
828    }
829    
830    /**
831     * Modify the color theme
832     * @param skinDir The skin directory (could be in temp, work or skin)
833     * @param themeId The id of the theme. Can be null to clear theme.
834     * @return <code>true</code> is modification success
835     */
836    public boolean saveColorTheme (File skinDir, String themeId)
837    {
838        File currentFile = new File(skinDir, "model.xml");
839        
840        Source currentSrc = null;
841        Source src = null;
842        try
843        {
844            currentSrc = _resolver.resolveURI("file://" + currentFile.getAbsolutePath());
845            
846            Map<String, Object> parentContext = new HashMap<>();
847            parentContext.put("modelUri", currentSrc.getURI());
848            if (themeId != null)
849            {
850                parentContext.put("themeId", themeId);
851            }
852            
853            src = _resolver.resolveURI("cocoon://_plugins/skinfactory/change-color-theme", null, parentContext);
854            
855            SourceUtil.copy(src, currentSrc);
856            
857            return true;
858        }
859        catch (IOException e)
860        {
861            getLogger().error("Unable to update color theme for skin '" + skinDir.getName() + "'", e);
862            return false;
863        }
864        finally
865        {
866            _resolver.release(src);
867            _resolver.release(currentSrc);
868        }
869    }
870    
871    /**
872     * Modify a skin parameter's value
873     * @param skinDir The skin directory (could be in temp, work or skin)
874     * @param id the id of the parameter
875     * @param value the new value
876     * @return <code>true</code> is modification success
877     */
878    public boolean saveParameter(File skinDir, String id, Object value)
879    {
880        Map<String, Object> parameters = new HashMap<>();
881        parameters.put(id, value);
882        
883        return saveParameters (skinDir, parameters);
884    }
885    
886    /**
887     * Save skin parameters
888     * @param skinDir The skin directory (could be in temp, work or skin)
889     * @param parameters The skins parameters to save
890     * @return <code>true</code> is modification success
891     */
892    public boolean saveParameters(File skinDir, Map<String, Object> parameters)
893    {
894        File currentFile = new File(skinDir, "model.xml");
895        
896        Source currentSrc = null;
897        Source src = null;
898        try
899        {
900            currentSrc = _resolver.resolveURI("file://" + currentFile.getAbsolutePath());
901            
902            Map<String, Object> parentContext = new HashMap<>();
903            parentContext.put("modelUri", currentSrc.getURI());
904            parentContext.put("skinParameters", parameters);
905            
906            src = _resolver.resolveURI("cocoon://_plugins/skinfactory/change-parameters", null, parentContext);
907            
908            SourceUtil.copy(src, currentSrc);
909            
910            return true;
911        }
912        catch (IOException e)
913        {
914            getLogger().error("Unable to save parameters from skin '" + skinDir.getName() + "'", e);
915            return false;
916        }
917        finally
918        {
919            _resolver.release(src);
920            _resolver.release(currentSrc);
921        }
922    }
923    
924    /**
925     * Apply model parameters in given skin
926     * @param modelName The model name
927     * @param skinDir The skin directory (could be temp, work or skins)
928     */
929    public void applyModelParameters (String modelName, File skinDir)
930    {
931        Map<String, Object> currentValues = getParameterValues(skinDir, modelName);
932        applyModelParameters (modelName, skinDir, currentValues);
933    }
934    
935    /**
936     * Apply model parameters in given skin
937     * @param modelName The model name
938     * @param skinDir The skin directory
939     * @param values The values to set
940     */
941    public void applyModelParameters (String modelName, File skinDir, Map<String, Object> values)
942    {
943        Map<String, Object> parameterValues = new HashMap<>();
944        SkinModel model = _modelsManager.getModel(modelName);
945        
946        Map<String, String> modelDefaultValues = model.getDefaultValues();
947        
948        Map<String, AbstractSkinParameter> modelParameters = getModelParameters(modelName);
949        for (AbstractSkinParameter skinParameter : modelParameters.values())
950        {
951            String paramId = skinParameter.getId();
952            
953            if (skinParameter instanceof I18nizableTextParameter)
954            {
955                Map<String, String> defaultValues = ((I18nizableTextParameter) skinParameter).getDefaultValues();
956                @SuppressWarnings("unchecked")
957                Map<String, String> currentValues = (Map<String, String>) values.get(paramId);
958                if (currentValues == null)
959                {
960                    currentValues = new HashMap<>();
961                }
962                for (String lang : defaultValues.keySet())
963                {
964                    if (currentValues.get(lang) != null)
965                    {
966                        applyParameter(skinParameter, skinDir, modelName, currentValues.get(lang), lang);
967                    }
968                    else
969                    {
970                        // Default value
971                        applyParameter(skinParameter, skinDir, modelName, defaultValues.get(lang), lang);
972                        currentValues.put(lang, defaultValues.get(lang));
973                    }
974                }
975                parameterValues.put(skinParameter.getId(), currentValues);
976            }
977            else if (skinParameter instanceof ImageParameter)
978            {
979                FileValue imgValue = (FileValue) _getValue(model, skinParameter, values, modelDefaultValues);
980                applyParameter(skinParameter, skinDir, modelName, imgValue, null);
981                parameterValues.put(skinParameter.getId(), imgValue);
982            }
983            else
984            {
985                String value = (String) _getValue (model, skinParameter, values, modelDefaultValues);
986                applyParameter(skinParameter, skinDir, modelName, value, null);
987                parameterValues.put(skinParameter.getId(), value);
988            }
989        }
990        
991        saveParameters(skinDir, parameterValues);
992    }
993    
994    private Object _getValue (SkinModel model, AbstractSkinParameter param, Map<String, Object> values, Map<String, String> defaultValues)
995    {
996        String id = param.getId();
997        
998        // First search in current values
999        if (values.containsKey(id))
1000        {
1001            return values.get(id);
1002        }
1003        
1004        // Then search in default values from model
1005        if (defaultValues.containsKey(id))
1006        {
1007            String valueAsStr =  defaultValues.get(id);
1008            
1009            if (param instanceof ImageParameter)
1010            {
1011                return new FileValue (valueAsStr, false);
1012            }
1013            else
1014            {
1015                return valueAsStr;
1016            }
1017        }
1018        
1019        // If nor found, get the parameter default value
1020        return param.getDefaultValue(model);
1021    }
1022    /**
1023     * Update hash
1024     * @param xmlFile the xml {@link File}
1025     * @param hash the new hash of the file
1026     * @throws IOException if an error occurs while manipulating the file
1027     */
1028    public void updateHash (File xmlFile, String hash) throws IOException
1029    {
1030        Source currentSrc = null;
1031        Source src = null;
1032        try
1033        {
1034            currentSrc = _resolver.resolveURI("file://" + xmlFile.getAbsolutePath());
1035            
1036            Map<String, Object> parentContext = new HashMap<>();
1037            parentContext.put("modelUri", currentSrc.getURI());
1038            parentContext.put("hash", hash);
1039            
1040            src = _resolver.resolveURI("cocoon://_plugins/skinfactory/change-hash", null, parentContext);
1041            
1042            SourceUtil.copy(src, currentSrc);
1043        }
1044        finally
1045        {
1046            _resolver.release(src);
1047            _resolver.release(currentSrc);
1048        }
1049        
1050    }
1051    
1052    /**
1053     * Apply parameter
1054     * @param parameter the skin parameter
1055     * @param skinDir the skin directory (could be temp, work or skins)
1056     * @param modelName the model name
1057     * @param value the parameter value
1058     * @param lang The language
1059     */
1060    public void applyParameter (AbstractSkinParameter parameter, File skinDir, String modelName, Object value, String lang)
1061    {
1062        File modelDir = _modelsManager.getModel(modelName).getFile();
1063        parameter.apply(skinDir, modelDir, value, lang);
1064    }
1065    
1066    /**
1067     * Apply new color theme 
1068     * @param skinDir the skin directory (could be temp, work or skins)
1069     * @param modelName the model name
1070     */
1071    public void applyColorTheme (String modelName, File skinDir)
1072    {
1073        File modelDir = _modelsManager.getModel(modelName).getFile();
1074        
1075        Map<String, Object> currentValues = getParameterValues(skinDir, modelName);
1076        
1077        Map<String, AbstractSkinParameter> modelParameters = getModelParameters(modelName);
1078        for (AbstractSkinParameter skinParameter : modelParameters.values())
1079        {
1080            if (skinParameter instanceof CSSColorParameter)
1081            {
1082                String value = (String) currentValues.get(skinParameter.getId());
1083                if (StringUtils.isEmpty(value))
1084                {
1085                    value = (String) skinParameter.getDefaultValue(_modelsManager.getModel(modelName));
1086                }
1087                
1088                skinParameter.apply(skinDir, modelDir, value, null);
1089            }
1090        }
1091    }
1092    
1093    private I18nizableText _configureI18nizableText(Configuration configuration, String modelName) throws ConfigurationException
1094    {
1095        boolean i18nSupported = configuration.getAttributeAsBoolean("i18n", false);
1096
1097        if (i18nSupported)
1098        {
1099            String catalogue = configuration.getAttribute("catalogue", null);
1100            if (catalogue == null)
1101            {
1102                catalogue = "model." + modelName;
1103            }
1104
1105            return new I18nizableText(catalogue, configuration.getValue());
1106        }
1107        else
1108        {
1109            return new I18nizableText(configuration.getValue(""));
1110        }
1111    }
1112    
1113    class SkinDirectoryFilter implements FileFilter
1114    {
1115        @Override
1116        public boolean accept(File file)
1117        {
1118            String filename = file.getName();
1119            return file.isDirectory() && !filename.equals("CVS") && !filename.equals(".svn");
1120        }
1121    }
1122}