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