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