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