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.model.type.ElementType;
050import org.ametys.runtime.parameter.Validator;
051import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint;
052import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
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 AbstractThreadSafeComponentExtensionPoint<? extends ElementType> _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, SkinParameterTypeExtensionPoint 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<Validator> validatorManager = new ThreadSafeComponentManager<>();
143        ThreadSafeComponentManager<Enumerator> enumeratorManager = new ThreadSafeComponentManager<>();
144        
145        try
146        {
147            validatorManager.setLogger(LoggerFactory.getLogger(getClass()));
148            validatorManager.contextualize(_context);
149            validatorManager.service(_manager);
150            
151            enumeratorManager.setLogger(LoggerFactory.getLogger(getClass()));
152            enumeratorManager.contextualize(_context);
153            enumeratorManager.service(_manager);
154            
155            SkinParameterParser definitionParser = new SkinParameterParser(_skinParameterTypeEP, enumeratorManager, validatorManager, _skinDir.resolve("i18n").toUri().toString());
156            CategorizedElementDefinitionParser parser = new CategorizedElementDefinitionParser(definitionParser);
157            Configuration configuration = _getConfigurationModel(_skinDir);
158            
159            if (configuration != null)
160            {
161                Configuration[] parameterConfigs = configuration.getChild("parameters").getChildren("parameter");
162                for (Configuration parameterConfig : parameterConfigs)
163                {
164                    CategorizedElementDefinitionWrapper parameter = parser.parse(_manager, "tempskin", parameterConfig, this, null);
165                    skinParameterWrappers.add(parameter);
166                }
167                
168                try
169                {
170                    definitionParser.lookupComponents();
171                }
172                catch (Exception e)
173                {
174                    throw new ConfigurationException("Unable to lookup parameter local components", configuration, e);
175                }
176            }
177            
178            return skinParameterWrappers;
179        }
180        catch (ConfigurationException | SAXException | IOException e)
181        {
182            throw new ProcessingException(e);
183        }
184    }
185    
186    private Configuration _getConfigurationModel(Path skinDir) throws ConfigurationException, SAXException, IOException
187    {
188        Path configFile = skinDir.resolve("stylesheets/config/config-model.xml");
189        
190        if (Files.exists(configFile))
191        {
192            try (InputStream is = Files.newInputStream(configFile))
193            {
194                DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
195                return builder.build(is);
196            }
197        }
198
199        // There is no parameter
200        return null;
201    }
202}