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.LinkedHashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Optional;
025
026import org.apache.avalon.framework.activity.Disposable;
027import org.apache.avalon.framework.activity.Initializable;
028import org.apache.avalon.framework.component.Component;
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.context.ContextException;
034import org.apache.avalon.framework.context.Contextualizable;
035import org.apache.avalon.framework.service.ServiceException;
036import org.apache.avalon.framework.service.ServiceManager;
037import org.apache.avalon.framework.service.Serviceable;
038import org.apache.cocoon.components.ContextHelper;
039import org.apache.cocoon.environment.Request;
040import org.apache.excalibur.source.Source;
041import org.apache.excalibur.source.SourceNotFoundException;
042
043import org.ametys.core.util.filereloader.FileReloader;
044import org.ametys.core.util.filereloader.FileReloaderUtils;
045import org.ametys.plugins.repository.model.parsing.RepeaterDefinitionParser;
046import org.ametys.runtime.model.Enumerator;
047import org.ametys.runtime.model.View;
048import org.ametys.runtime.parameter.Validator;
049import org.ametys.runtime.plugin.component.AbstractLogEnabled;
050import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
051import org.ametys.web.WebConstants;
052import org.ametys.web.data.type.ModelItemTypeExtensionPoint;
053import org.ametys.web.parameters.ViewAndParametersParser;
054import org.ametys.web.parameters.ViewAndParametersParser.ViewAndParameters;
055import org.ametys.web.parameters.view.GlobalViewParametersManager.ViewParametersType;
056import org.ametys.web.service.Service;
057import org.ametys.web.service.ServiceExtensionPoint;
058import org.ametys.web.source.ServiceSourceFactory;
059
060/**
061 * Manager for service view parameters
062 */
063public class ServiceViewParametersManager extends AbstractLogEnabled implements Component, Serviceable, Contextualizable, Initializable, Disposable
064{
065    /** Avalon Role */
066    public static final String ROLE = ServiceViewParametersManager.class.getName();
067    
068    /** The file reloader utils */
069    protected FileReloaderUtils _fileReloaderUtils;
070    
071    /** The view parameter type extension point */
072    protected ModelItemTypeExtensionPoint _viewParametersEP;
073    
074    /** The view and parameters parser */
075    protected ViewAndParametersParser _viewAndParametersParser;
076    
077    /** The service extension point */
078    protected ServiceExtensionPoint _serviceExtensionPoint;
079    
080    /** The avalon context */
081    protected Context _context;
082    
083    /** The service manager */
084    protected ServiceManager _manager;
085    
086    /** The object representing the view parameters of the different service by skin */
087    protected SkinsServiceViewParameters _skinsServiceViewParameters;
088    
089    /** The view parameters manager */
090    protected ViewParametersManager _viewParametersManager;
091    
092    private List<ThreadSafeComponentManager> _components;
093    
094    public void service(ServiceManager manager) throws ServiceException
095    {
096        _manager = manager;
097        _fileReloaderUtils = (FileReloaderUtils) manager.lookup(FileReloaderUtils.ROLE);
098        _viewParametersEP = (ModelItemTypeExtensionPoint) manager.lookup(ModelItemTypeExtensionPoint.ROLE_VIEW_PARAM);
099        _viewAndParametersParser = (ViewAndParametersParser) manager.lookup(ViewAndParametersParser.ROLE);
100        _serviceExtensionPoint = (ServiceExtensionPoint) manager.lookup(ServiceExtensionPoint.ROLE);
101        _viewParametersManager = (ViewParametersManager) manager.lookup(ViewParametersManager.ROLE);
102    }
103    
104    public void contextualize(Context context) throws ContextException
105    {
106        _context = context;
107    }
108    
109    public void initialize() throws Exception
110    {
111        _skinsServiceViewParameters = new SkinsServiceViewParameters();
112        _components = new ArrayList<>();
113    }
114    
115    /**
116     * Get the view parameters of a service
117     * @param skinId the skin id
118     * @param serviceId the service id
119     * @param viewName the view name
120     * @return the view parameters
121     */
122    public Optional<ViewParametersModel> getViewParameters(String skinId, String serviceId, String viewName)
123    {
124        Service service = _serviceExtensionPoint.getExtension(serviceId);
125        
126        String valueWithNoExtension = viewName.substring(0, viewName.length() - 4);
127        String sourceUrl = "service:" + service.getPluginName() + "://" + valueWithNoExtension + ".xml";
128        
129        Request request = ContextHelper.getRequest(_context);
130        Object originalSkinId = request.getAttribute(WebConstants.REQUEST_ATTR_SKIN_ID);
131        try
132        {
133            request.setAttribute(WebConstants.REQUEST_ATTR_SKIN_ID, skinId);
134            _fileReloaderUtils.updateFile(sourceUrl, new ServiceViewParametersReloader(skinId, service, viewName, this));
135        }
136        catch (SourceNotFoundException e)
137        {
138            // Do nothing, the parameter xml file doesn't exist
139        }
140        catch (Exception e)
141        {
142            getLogger().error("Failed to read the service parameters file '{}'. It will be ignored", sourceUrl, e);
143        }
144        finally
145        {
146            request.setAttribute(WebConstants.REQUEST_ATTR_SKIN_ID, originalSkinId);
147        }
148        
149        Optional<ViewParametersModel> viewParameters = _skinsServiceViewParameters.getServiceViewParameters(skinId, serviceId, viewName);
150        return _viewParametersManager.addGlobalViewParameters(skinId, ViewParametersType.SERVICES, viewParameters);
151    }
152    
153    /**
154     * Class representing a service view parameters reloader
155     */
156    public static class ServiceViewParametersReloader implements FileReloader
157    {
158        private String _skinId;
159        private Service _service;
160        private String _viewName;
161        private ServiceViewParametersManager _serviceViewParameterManager;
162     
163        /**
164         * Constructor for the reloader
165         * @param skinId the skin id
166         * @param service the service
167         * @param viewName the service view name
168         * @param manager the service view parameters manager
169         */
170        public ServiceViewParametersReloader (String skinId, Service service, String viewName, ServiceViewParametersManager manager)
171        {
172            _skinId = skinId;
173            _service = service;
174            _viewName = viewName;
175            _serviceViewParameterManager = manager;
176        }
177        
178        /**
179         * Get the skin id
180         * @return the skin id
181         */
182        public String getSkinId()
183        {
184            return _skinId;
185        }
186        
187        /**
188         * Get the service
189         * @return the service
190         */
191        public Service getService()
192        {
193            return _service;
194        }
195        
196        /**
197         * Get the service view name
198         * @return the service view name
199         */
200        public String getViewName()
201        {
202            return _viewName;
203        }
204        
205        /**
206         * Get the service view parameters manager
207         * @return the service view parameters manager
208         */
209        public ServiceViewParametersManager getServiceViewParameterManager()
210        {
211            return _serviceViewParameterManager;
212        }
213        
214        public void updateFile(String sourceUrl, Source source, InputStream is) throws Exception
215        {
216            if (is != null && source != null)
217            {
218                ServiceViewParametersManager manager = getServiceViewParameterManager();
219                
220                manager._disposeComponents();
221                
222                String plugin = getService().getPluginName();
223                String catalog = "plugin." + plugin;
224                
225                if (source instanceof ServiceSourceFactory.ServiceSource)
226                {
227                    if (((ServiceSourceFactory.ServiceSource) source).getSourceType() != ServiceSourceFactory.SourceType.PLUGIN)
228                    {
229                        catalog = "skin." + getSkinId();
230                    }
231                }
232                        
233                Configuration conf = new DefaultConfigurationBuilder().build(is);
234                
235                String viewParametersId = getSkinId() + "_" + getService().getId() + "_" + getViewName() + "_service_view_parameters";
236                Configuration paramConfiguration = conf.getChild(ViewParametersManager.VIEW_PARAMETERS_SERVICE_CONF_NAME);
237                
238                Optional<ViewParametersModel> viewParameters = manager._configureViewParameters(paramConfiguration, viewParametersId, plugin, catalog);
239                manager._skinsServiceViewParameters.addServiceViewParameters(
240                            getSkinId(),
241                            getService().getId(),
242                            getViewName(),
243                            viewParameters
244                );
245            }
246        }
247        
248        public String getId(String sourceUrl)
249        {
250            return ServiceViewParametersManager.class.getName() + "#" + getSkinId() + "_" + getService().getId() + "_" + getViewName();
251        }
252    }
253    
254    /**
255     * Parse service view parameters from the configuration
256     * @param paramConfiguration the configuration
257     * @param viewParametersId the view parameters id
258     * @param plugin the plugin
259     * @param catalog the catalog
260     * @return the view parameters
261     * @throws ConfigurationException if a configuration error occurred
262     */
263    protected Optional<ViewParametersModel> _configureViewParameters(Configuration paramConfiguration, String viewParametersId, String plugin, String catalog) throws ConfigurationException
264    {
265        ThreadSafeComponentManager<Validator> validatorManager = new ThreadSafeComponentManager<>();
266        validatorManager.setLogger(getLogger());
267        validatorManager.contextualize(_context);
268        validatorManager.service(_manager);
269        _components.add(validatorManager);
270        
271        ThreadSafeComponentManager<Enumerator> enumeratorManager = new ThreadSafeComponentManager<>();
272        enumeratorManager.setLogger(getLogger());
273        enumeratorManager.contextualize(_context);
274        enumeratorManager.service(_manager);
275        _components.add(enumeratorManager);
276        
277        ViewParametersModel viewParameters = new ViewParametersModel(viewParametersId, new View(), new LinkedHashMap<>());
278            
279        ViewParameterDefinitionParser elementDefinitionParser = new ViewParameterDefinitionParser(_viewParametersEP, enumeratorManager, validatorManager);
280        RepeaterDefinitionParser repeaterDefinitionParser = new RepeaterDefinitionParser(_viewParametersEP);
281        
282        ViewAndParameters viewAndParameters = _viewAndParametersParser.parseParameters(paramConfiguration, plugin, catalog, viewParameters, elementDefinitionParser, repeaterDefinitionParser);
283        viewParameters.setView(viewAndParameters.getView());
284        viewParameters.setModelItems(viewAndParameters.getParameters());
285
286        try
287        {
288            elementDefinitionParser.lookupComponents();
289        }
290        catch (Exception e)
291        {
292            throw new ConfigurationException("Unable to lookup parameter local components", paramConfiguration, e);
293        }
294        
295        return Optional.ofNullable(viewParameters);
296    }
297    
298    public void dispose()
299    {
300        _disposeComponents();
301    }
302    
303    private void _disposeComponents()
304    {
305        for (ThreadSafeComponentManager component : _components)
306        {
307            component.dispose();
308        }
309    }
310    
311    static class SkinsServiceViewParameters
312    {
313        Map<String, SkinWrapper> _servicesBySkin;
314        
315        SkinsServiceViewParameters()
316        {
317            this._servicesBySkin = new HashMap<>();
318        }
319        
320        Optional<ViewParametersModel> getServiceViewParameters(String skinId, String serviceId, String viewName)
321        {
322            SkinWrapper skin = _servicesBySkin.getOrDefault(skinId, new SkinWrapper());
323            return skin.getServiceViewParameters(serviceId, viewName);
324        }
325
326        void addServiceViewParameters(String skinId, String serviceId, String viewName, Optional<ViewParametersModel> viewParameters)
327        {
328            if (!_servicesBySkin.containsKey(skinId))
329            {
330                _servicesBySkin.put(skinId, new SkinWrapper());
331            }
332            
333            SkinWrapper skin = _servicesBySkin.get(skinId);
334            skin.addServiceViewParameters(serviceId, viewName, viewParameters);
335        }
336    }
337    
338    static class SkinWrapper
339    {
340        Map<String, ServiceWrapper> _viewsByService;
341        
342        SkinWrapper()
343        {
344            this._viewsByService = new HashMap<>();
345        }
346        
347        Optional<ViewParametersModel> getServiceViewParameters(String serviceId, String viewName)
348        {
349            ServiceWrapper service = _viewsByService.getOrDefault(serviceId, new ServiceWrapper());
350            return service.getServiceViewParameters(viewName);
351        }
352
353        void addServiceViewParameters(String serviceId, String viewName, Optional<ViewParametersModel> viewParameters)
354        {
355            if (!_viewsByService.containsKey(serviceId))
356            {
357                _viewsByService.put(serviceId, new ServiceWrapper());
358            }
359            
360            ServiceWrapper service = _viewsByService.get(serviceId);
361            service.addServiceViewParameters(viewName, viewParameters);
362        }
363    }
364    
365    static class ServiceWrapper
366    {
367        Map<String, Optional<ViewParametersModel>> _parametersByServiceView;
368        
369        ServiceWrapper()
370        {
371            this._parametersByServiceView = new HashMap<>();
372        }
373        
374        Optional<ViewParametersModel> getServiceViewParameters(String viewName)
375        {
376            return _parametersByServiceView.getOrDefault(viewName, Optional.empty());
377        }
378
379        void addServiceViewParameters(String viewName, Optional<ViewParametersModel> viewParameters)
380        {
381            _parametersByServiceView.put(viewName, viewParameters);
382        }
383    }
384}