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}