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