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}