001/*
002 *  Copyright 2018 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 */
016
017package org.ametys.web.skin;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.nio.file.Files;
022import java.nio.file.Path;
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.Comparator;
026import java.util.List;
027import java.util.stream.Collectors;
028
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.context.Context;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.cocoon.ProcessingException;
035import org.slf4j.LoggerFactory;
036import org.xml.sax.SAXException;
037
038import org.ametys.core.util.I18nizableTextKeyComparator;
039import org.ametys.runtime.i18n.I18nizableText;
040import org.ametys.runtime.model.CategorizedElementDefinitionHelper;
041import org.ametys.runtime.model.CategorizedElementDefinitionParser;
042import org.ametys.runtime.model.CategorizedElementDefinitionWrapper;
043import org.ametys.runtime.model.CategorizedElementDefinitionWrapperComparator;
044import org.ametys.runtime.model.ElementDefinition;
045import org.ametys.runtime.model.Enumerator;
046import org.ametys.runtime.model.Model;
047import org.ametys.runtime.model.View;
048import org.ametys.runtime.model.exception.UndefinedItemPathException;
049import org.ametys.runtime.parameter.Validator;
050import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
051import org.ametys.web.data.type.ModelItemTypeExtensionPoint;
052
053/**
054 * Model for Skin (do not mistake this with {@link SkinModel} )
055 */
056public class SkinParametersModel implements Model
057{
058    private String _skinName;
059    private Path _skinDir;
060    private ModelItemTypeExtensionPoint _skinParameterTypeEP;
061    private Context _context;
062    private ServiceManager _manager;
063    
064    private List<CategorizedElementDefinitionWrapper> _parameterWrappers;
065    private List<ElementDefinition> _parameters;
066    
067    /**
068     * Create an instance of a model, based on the skin directory
069     * @param skinName name of the skin (must be unique)
070     * @param skinDir the skin directory where the configuration will be read
071     * @param skinParameterTypeEP the skin parameter type extension point
072     * @param context the current context
073     * @param manager the service manager
074     * @throws ProcessingException something went wrong while parsing the file
075     */
076    public SkinParametersModel(String skinName, Path skinDir, ModelItemTypeExtensionPoint skinParameterTypeEP, Context context, ServiceManager manager) throws ProcessingException
077    {
078        _skinName = skinName;
079        _skinDir = skinDir;
080        _skinParameterTypeEP = skinParameterTypeEP;
081        _context = context;
082        _manager = manager;
083        _initialize();
084    }
085    
086    public Collection<ElementDefinition> getModelItems()
087    {
088        return _parameters;
089    }
090    
091    @Override
092    public ElementDefinition getModelItem(String itemPath) throws UndefinedItemPathException
093    {
094        // Skin parameter can only be element definitions
095        return (ElementDefinition) Model.super.getModelItem(itemPath);
096    }
097    
098    /**
099     * Retrieves the view of skin parameters
100     * @return the skin parameters' view
101     */
102    public View getView()
103    {
104        Comparator<I18nizableText> groupsComparator = new I18nizableTextKeyComparator();
105        Comparator<CategorizedElementDefinitionWrapper> elementsComparator = new CategorizedElementDefinitionWrapperComparator<>();
106        
107        return CategorizedElementDefinitionHelper.buildViewFromCategories(_parameterWrappers, null, groupsComparator, elementsComparator);
108    }
109
110    public String getId()
111    {
112        return _skinName;
113    }
114
115    public String getFamilyId()
116    {
117        return this.getClass().getName();
118    }
119    
120    private void _initialize() throws ProcessingException
121    {
122        if (_parameterWrappers == null)
123        {
124            _parameterWrappers = _readSkinParameters();
125        }
126        
127        _parameters = _parameterWrappers.stream()
128                .map(CategorizedElementDefinitionWrapper::getDefinition)
129                .collect(Collectors.toList());
130    }
131    
132    /**
133     * Read the {@link CategorizedElementDefinitionWrapper} from the skin directory
134     * @return a list of {@link CategorizedElementDefinitionWrapper}
135     * @throws ProcessingException error while reading the XML
136     */
137    private List<CategorizedElementDefinitionWrapper> _readSkinParameters() throws ProcessingException
138    {
139        List<CategorizedElementDefinitionWrapper> skinParameterWrappers = new ArrayList<>();
140        
141        ThreadSafeComponentManager<Validator> validatorManager = new ThreadSafeComponentManager<>();
142        ThreadSafeComponentManager<Enumerator> enumeratorManager = new ThreadSafeComponentManager<>();
143        
144        try
145        {
146            validatorManager.setLogger(LoggerFactory.getLogger(getClass()));
147            validatorManager.contextualize(_context);
148            validatorManager.service(_manager);
149            
150            enumeratorManager.setLogger(LoggerFactory.getLogger(getClass()));
151            enumeratorManager.contextualize(_context);
152            enumeratorManager.service(_manager);
153            
154            SkinParameterParser definitionParser = new SkinParameterParser(_skinParameterTypeEP, enumeratorManager, validatorManager, _skinDir.resolve("i18n").toUri().toString());
155            CategorizedElementDefinitionParser parser = new CategorizedElementDefinitionParser(definitionParser);
156            Configuration configuration = _getConfigurationModel(_skinDir);
157            
158            if (configuration != null)
159            {
160                Configuration[] parameterConfigs = configuration.getChild("parameters").getChildren("parameter");
161                for (Configuration parameterConfig : parameterConfigs)
162                {
163                    CategorizedElementDefinitionWrapper parameter = parser.parse(_manager, "tempskin", parameterConfig, this, null);
164                    skinParameterWrappers.add(parameter);
165                }
166                
167                try
168                {
169                    definitionParser.lookupComponents();
170                }
171                catch (Exception e)
172                {
173                    throw new ConfigurationException("Unable to lookup parameter local components", configuration, e);
174                }
175            }
176            
177            return skinParameterWrappers;
178        }
179        catch (ConfigurationException | SAXException | IOException e)
180        {
181            throw new ProcessingException(e);
182        }
183    }
184    
185    private Configuration _getConfigurationModel(Path skinDir) throws ConfigurationException, SAXException, IOException
186    {
187        Path configFile = skinDir.resolve("stylesheets/config/config-model.xml");
188        
189        if (Files.exists(configFile))
190        {
191            try (InputStream is = Files.newInputStream(configFile))
192            {
193                DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
194                return builder.build(is);
195            }
196        }
197
198        // There is no parameter
199        return null;
200    }
201}