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