001/*
002 *  Copyright 2020 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.web.parameters.view;
017
018import java.io.InputStream;
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Optional;
024
025import org.apache.avalon.framework.activity.Disposable;
026import org.apache.avalon.framework.activity.Initializable;
027import org.apache.avalon.framework.component.Component;
028import org.apache.avalon.framework.configuration.Configuration;
029import org.apache.avalon.framework.configuration.ConfigurationException;
030import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
031import org.apache.avalon.framework.context.Context;
032import org.apache.avalon.framework.context.ContextException;
033import org.apache.avalon.framework.context.Contextualizable;
034import org.apache.avalon.framework.service.ServiceException;
035import org.apache.avalon.framework.service.ServiceManager;
036import org.apache.avalon.framework.service.Serviceable;
037import org.apache.commons.lang.StringUtils;
038import org.apache.excalibur.source.Source;
039import org.apache.excalibur.source.SourceNotFoundException;
040
041import org.ametys.cms.data.holder.group.DataHolderRepeaterDefinitionParser;
042import org.ametys.core.util.filereloader.FileReloader;
043import org.ametys.core.util.filereloader.FileReloaderUtils;
044import org.ametys.runtime.model.Enumerator;
045import org.ametys.runtime.model.View;
046import org.ametys.runtime.model.disableconditions.DisableConditions;
047import org.ametys.runtime.parameter.Validator;
048import org.ametys.runtime.plugin.component.AbstractLogEnabled;
049import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
050import org.ametys.web.data.type.ModelItemTypeExtensionPoint;
051import org.ametys.web.parameters.ViewAndParametersParser;
052import org.ametys.web.parameters.ViewAndParametersParser.ViewAndParameters;
053
054/**
055 * Manager for global view parameters
056 */
057public class GlobalViewParametersManager extends AbstractLogEnabled implements Component, Serviceable, Contextualizable, Initializable, Disposable        
058{
059    /** Avalon Role */
060    public static final String ROLE = GlobalViewParametersManager.class.getName();
061    
062    /** The file reloader utils */
063    protected FileReloaderUtils _fileReloaderUtils;
064    
065    /** The view parameter type extension point */
066    protected ModelItemTypeExtensionPoint _viewParametersEP;
067    
068    /** The view and parameters parser */
069    protected ViewAndParametersParser _viewAndParametersParser;
070    
071    /** The avalon context */
072    protected Context _context;
073    
074    /** The service manager */
075    protected ServiceManager _manager;
076    
077    /** The object representing the global view parameters by skin */
078    protected SkinsGlobalViewParameters _skinsGlobalViewParameters;
079    
080    private List<ThreadSafeComponentManager> _components;
081    
082    public void service(ServiceManager manager) throws ServiceException
083    {
084        _manager = manager;
085        _fileReloaderUtils = (FileReloaderUtils) manager.lookup(FileReloaderUtils.ROLE);
086        _viewParametersEP = (ModelItemTypeExtensionPoint) manager.lookup(ModelItemTypeExtensionPoint.ROLE_VIEW_PARAM);
087        _viewAndParametersParser = (ViewAndParametersParser) manager.lookup(ViewAndParametersParser.ROLE);
088    }
089    
090    public void contextualize(Context context) throws ContextException
091    {
092        _context = context;
093    }
094    
095    public void initialize() throws Exception
096    {
097        _skinsGlobalViewParameters = new SkinsGlobalViewParameters();
098        _components = new ArrayList<>();
099    }
100    
101    /**
102     * The view parameters type
103     */
104    public enum ViewParametersType
105    {
106        /** View parameters for template */
107        TEMPLATES,
108        /** View parameters for zone */
109        ZONES,
110        /** View parameters for zone item */
111        ZONEITEMS,
112        /** View parameters for service */
113        SERVICES,
114        /** View parameters for content */
115        CONTENTS
116    }
117    
118    /**
119     * Get global view parameters for one type (template, zone, zone item, service or content)
120     * @param skinId the skin id
121     * @param type the type
122     * @return the view parameters
123     */
124    public Optional<ViewParametersModel> getViewParameters(String skinId, ViewParametersType type)
125    {
126        String sourceUrl = "skin:" + skinId + "://conf/parameters.xml";
127        try
128        {
129            _fileReloaderUtils.updateFile(sourceUrl, new GlobalViewParametersReloader(skinId, this));
130        }
131        catch (SourceNotFoundException e) 
132        {
133            // Do nothing, the parameter xml file doesn't exist
134        }
135        catch (Exception e) 
136        {
137            getLogger().error("Failed to read the global parameters file '{}'. It will be ignored", sourceUrl, e);
138        }
139        
140        return _skinsGlobalViewParameters.getGlobalViewParameters(skinId, type);
141    }
142    
143    /**
144     * Class representing a global view parameters reloader
145     */
146    public static class GlobalViewParametersReloader implements FileReloader
147    {
148        private String _skinId;
149        private GlobalViewParametersManager _globalViewParameterManager;
150     
151        /**
152         * Constructor for the reloader
153         * @param skinId the skin id
154         * @param manager the global view parameters manager
155         */
156        public GlobalViewParametersReloader (String skinId, GlobalViewParametersManager manager)
157        {
158            _skinId = skinId;
159            _globalViewParameterManager = manager;
160        }
161        
162        /**
163         * Get the skin id
164         * @return the skin id
165         */
166        public String getSkinId()
167        {
168            return _skinId;
169        }
170        
171        /**
172         * Get the global view parameters manager
173         * @return the global view parameters manager
174         */
175        public GlobalViewParametersManager getGlobalViewParameterManager()
176        {
177            return _globalViewParameterManager;
178        }
179        
180        public void updateFile(String sourceUrl, Source source, InputStream is) throws Exception
181        {
182            if (is != null)
183            {
184                GlobalViewParametersManager manager = getGlobalViewParameterManager();
185                
186                manager._disposeComponents();
187                
188                String skinId = getSkinId();
189                String plugin = "web"; // default plugin for enumerator and validator component 
190                String catalog = "skin." + getSkinId();
191                
192                Configuration conf = new DefaultConfigurationBuilder().build(is);
193                
194                manager._addViewParameters(conf, skinId, ViewParametersType.TEMPLATES, plugin, catalog);
195                manager._addViewParameters(conf, skinId, ViewParametersType.ZONES, plugin, catalog);
196                manager._addViewParameters(conf, skinId, ViewParametersType.ZONEITEMS, plugin, catalog);
197                manager._addViewParameters(conf, skinId, ViewParametersType.SERVICES, plugin, catalog);
198                manager._addViewParameters(conf, skinId, ViewParametersType.CONTENTS, plugin, catalog);
199                
200            }
201        }
202        
203        public String getId(String sourceUrl)
204        {
205            return GlobalViewParametersManager.class.getName() + "#" + getSkinId();
206        }
207    }
208    
209    /**
210     * Add view parameters from the configuration of a given type
211     * @param paramConfiguration the configuration
212     * @param skinId the skin Id
213     * @param type the type
214     * @param plugin the plugin
215     * @param catalog the catalog
216     * @throws ConfigurationException if a configuration error occurred
217     */
218    protected void _addViewParameters(Configuration paramConfiguration, String skinId, ViewParametersType type, String plugin, String catalog) throws ConfigurationException
219    {
220        String viewParametersId = skinId + "_" + StringUtils.lowerCase(type.name()) + "_global_view_parameters";
221        
222        Configuration typeParamConfiguration = paramConfiguration.getChild(StringUtils.lowerCase(type.name()));
223        Optional<ViewParametersModel> viewParameters = _configureViewParameters(typeParamConfiguration, viewParametersId, plugin, catalog);
224        
225        _skinsGlobalViewParameters.addGlobalViewParameters(skinId, type, viewParameters);
226    }
227    
228    /**
229     * Parse global view parameters from the configuration
230     * @param paramConfiguration the configuration
231     * @param viewParametersId the view parameters id
232     * @param plugin the plugin
233     * @param catalog the catalog
234     * @return the view parameters
235     * @throws ConfigurationException if a configuration error occurred
236     */
237    protected Optional<ViewParametersModel> _configureViewParameters(Configuration paramConfiguration, String viewParametersId, String plugin, String catalog) throws ConfigurationException
238    {
239        ThreadSafeComponentManager<DisableConditions> parametersDisableConditionsManager = new ThreadSafeComponentManager<>();
240        parametersDisableConditionsManager.setLogger(getLogger());
241        parametersDisableConditionsManager.contextualize(_context);
242        parametersDisableConditionsManager.service(_manager);
243        _components.add(parametersDisableConditionsManager);
244        
245        ThreadSafeComponentManager<DisableConditions> repeaterDisableConditionsManager = new ThreadSafeComponentManager<>();
246        repeaterDisableConditionsManager.setLogger(getLogger());
247        repeaterDisableConditionsManager.contextualize(_context);
248        repeaterDisableConditionsManager.service(_manager);
249        _components.add(repeaterDisableConditionsManager);
250        
251        ThreadSafeComponentManager<Validator> validatorManager = new ThreadSafeComponentManager<>();
252        validatorManager.setLogger(getLogger());
253        validatorManager.contextualize(_context);
254        validatorManager.service(_manager);
255        _components.add(validatorManager);
256        
257        ThreadSafeComponentManager<Enumerator> enumeratorManager = new ThreadSafeComponentManager<>();
258        enumeratorManager.setLogger(getLogger());
259        enumeratorManager.contextualize(_context);
260        enumeratorManager.service(_manager);
261        _components.add(enumeratorManager);
262        
263        ViewParametersModel viewParameters = new ViewParametersModel(viewParametersId, new View(), new HashMap<>());
264            
265        ViewParameterDefinitionParser elementDefinitionParser = new ViewParameterDefinitionParser(_viewParametersEP, parametersDisableConditionsManager, enumeratorManager, validatorManager);
266        DataHolderRepeaterDefinitionParser repeaterDefinitionParser = new DataHolderRepeaterDefinitionParser(_viewParametersEP, repeaterDisableConditionsManager);
267        
268        ViewAndParameters viewAndParameters = _viewAndParametersParser.parseParameters(paramConfiguration, plugin, catalog, viewParameters, elementDefinitionParser, repeaterDefinitionParser);
269        viewParameters.setView(viewAndParameters.getView());
270        viewParameters.setModelItems(viewAndParameters.getParameters());
271
272        try
273        {
274            elementDefinitionParser.lookupComponents();
275            repeaterDefinitionParser.lookupComponents();
276        }
277        catch (Exception e)
278        {
279            throw new ConfigurationException("Unable to lookup parameter local components", paramConfiguration, e);
280        }
281        
282        return Optional.ofNullable(viewParameters);
283    }
284    
285    public void dispose()
286    {
287        _disposeComponents();
288    }
289    
290    private void _disposeComponents()
291    {
292        for (ThreadSafeComponentManager component : _components)
293        {
294            component.dispose();
295        }
296    }
297    
298    static class SkinsGlobalViewParameters
299    {
300        Map<String, SkinWrapper> _globalViewParametersBySkin;
301        
302        SkinsGlobalViewParameters()
303        {
304            this._globalViewParametersBySkin = new HashMap<>();
305        }
306        
307        Optional<ViewParametersModel> getGlobalViewParameters(String skinId, ViewParametersType type)
308        {
309            SkinWrapper skin = _globalViewParametersBySkin.getOrDefault(skinId, new SkinWrapper());
310            return skin.getGlobalViewParameters(type);
311        }
312
313        void addGlobalViewParameters(String skinId, ViewParametersType type, Optional<ViewParametersModel> viewParameters)
314        {
315            if (!_globalViewParametersBySkin.containsKey(skinId))
316            {
317                _globalViewParametersBySkin.put(skinId, new SkinWrapper());
318            }
319            
320            SkinWrapper skin = _globalViewParametersBySkin.get(skinId);
321            skin.addGlobalViewParameters(type, viewParameters);
322        }
323    }
324    
325    static class SkinWrapper
326    {
327        Map<ViewParametersType, Optional<ViewParametersModel>> _parametersByType;
328        
329        SkinWrapper()
330        {
331            this._parametersByType = new HashMap<>();
332        }
333        
334        Optional<ViewParametersModel> getGlobalViewParameters(ViewParametersType type)
335        {
336            return _parametersByType.getOrDefault(type, Optional.empty());
337        }
338
339        void addGlobalViewParameters(ViewParametersType type, Optional<ViewParametersModel> viewParameters)
340        {
341            _parametersByType.put(type, viewParameters);
342        }
343    }
344}