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