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