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