001/* 002 * Copyright 2010 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.service; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023 024import org.apache.avalon.framework.activity.Disposable; 025import org.apache.avalon.framework.configuration.Configurable; 026import org.apache.avalon.framework.configuration.Configuration; 027import org.apache.avalon.framework.configuration.ConfigurationException; 028import org.apache.avalon.framework.context.Context; 029import org.apache.avalon.framework.context.ContextException; 030import org.apache.avalon.framework.context.Contextualizable; 031import org.apache.avalon.framework.service.ServiceException; 032import org.apache.avalon.framework.service.ServiceManager; 033import org.apache.avalon.framework.service.Serviceable; 034import org.apache.commons.lang3.LocaleUtils; 035import org.apache.solr.common.SolrInputDocument; 036 037import org.ametys.cms.data.holder.group.DataHolderRepeaterDefinitionParser; 038import org.ametys.cms.data.type.indexing.IndexableElementTypeHelper; 039import org.ametys.cms.model.CMSDataContext; 040import org.ametys.core.ui.ClientSideElement.Script; 041import org.ametys.core.ui.ClientSideElement.ScriptFile; 042import org.ametys.plugins.core.ui.util.ConfigurationHelper; 043import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 044import org.ametys.runtime.i18n.I18nizableText; 045import org.ametys.runtime.model.ElementDefinition; 046import org.ametys.runtime.model.Enumerator; 047import org.ametys.runtime.model.ItemParserHelper; 048import org.ametys.runtime.model.ItemParserHelper.ConfigurationAndPluginName; 049import org.ametys.runtime.model.ModelItem; 050import org.ametys.runtime.model.View; 051import org.ametys.runtime.model.disableconditions.DisableConditions; 052import org.ametys.runtime.model.type.ElementType; 053import org.ametys.runtime.parameter.Validator; 054import org.ametys.runtime.plugin.component.AbstractLogEnabled; 055import org.ametys.runtime.plugin.component.PluginAware; 056import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 057import org.ametys.web.data.type.ModelItemTypeExtensionPoint; 058import org.ametys.web.parameters.ViewAndParametersParser; 059import org.ametys.web.parameters.ViewAndParametersParser.ViewAndParameters; 060import org.ametys.web.repository.page.Page; 061import org.ametys.web.repository.page.ZoneItem; 062 063/** 064 * Class representing a business service. <br> 065 * A service is identified by an id and a Cocoon-URL.<br> 066 * This URL corresponds to a pipeline called by a page template.<br> 067 * URL must be relative to the sitemap of the plugin containing the service. 068 */ 069public class StaticService extends AbstractLogEnabled implements Service, Contextualizable, Configurable, PluginAware, Serviceable, Disposable 070{ 071 /** The plugin name */ 072 protected String _pluginName; 073 /** The feature name */ 074 protected String _featureName; 075 /** The service manager */ 076 protected ServiceManager _manager; 077 /** The context. */ 078 protected Context _context; 079 /** The script configured */ 080 protected Script _paramsScript; 081 082 /** The view of the service */ 083 protected View _view; 084 085 /** The map of model items */ 086 protected Map<String, ModelItem> _modelItems; 087 088 /** The view and parameters parser */ 089 protected ViewAndParametersParser _viewAndParametersParser; 090 091 /** The service parameter definition parser */ 092 protected ServiceParameterDefinitionParser _serviceParameterDefinitionParser; 093 094 /** The repeater definition parser */ 095 protected DataHolderRepeaterDefinitionParser _repeaterDefinitionParser; 096 097 private String _id; 098 private I18nizableText _label; 099 private I18nizableText _description; 100 private I18nizableText _category; 101 102 private String _iconGlyph; 103 private String _iconDecorator; 104 private String _smallIcon; 105 private String _mediumIcon; 106 private String _largeIcon; 107 private Integer _creationBoxHeight; 108 private Integer _creationBoxWidth; 109 private List<ScriptFile> _cssFiles; 110 111 private boolean _isCacheable; 112 private boolean _isPrivate; 113 /** The right needed to create an instance of this service, blank or null if no right is needed. */ 114 private String _right; 115 116 private String _url; 117 118 private List<String> _parametersToIndex; 119 120 // ComponentManager for disable conditions on parameters 121 private ThreadSafeComponentManager<DisableConditions> _parameterDisableConditionsManager; 122 123 // ComponentManager for disable conditions on repeaters 124 private ThreadSafeComponentManager<DisableConditions> _repeaterDisableConditionsManager; 125 126 // ComponentManager for validators 127 private ThreadSafeComponentManager<Validator> _validatorManager; 128 129 // ComponentManager for enumerators 130 private ThreadSafeComponentManager<Enumerator> _enumeratorManager; 131 132 private ModelItemTypeExtensionPoint _serviceParameterTypeExtensionPoint; 133 134 135 @Override 136 public void service(ServiceManager smanager) throws ServiceException 137 { 138 _manager = smanager; 139 _serviceParameterTypeExtensionPoint = (ModelItemTypeExtensionPoint) smanager.lookup(ModelItemTypeExtensionPoint.ROLE_SERVICE_PARAM); 140 _viewAndParametersParser = (ViewAndParametersParser) smanager.lookup(ViewAndParametersParser.ROLE); 141 } 142 143 @Override 144 public void contextualize(Context context) throws ContextException 145 { 146 _context = context; 147 } 148 149 @Override 150 public void dispose() 151 { 152 _parameterDisableConditionsManager.dispose(); 153 _parameterDisableConditionsManager = null; 154 _repeaterDisableConditionsManager.dispose(); 155 _repeaterDisableConditionsManager = null; 156 157 _validatorManager.dispose(); 158 _validatorManager = null; 159 160 _enumeratorManager.dispose(); 161 _enumeratorManager = null; 162 } 163 164 @Override 165 public void configure(Configuration configuration) throws ConfigurationException 166 { 167 _parameterDisableConditionsManager = new ThreadSafeComponentManager<>(); 168 _parameterDisableConditionsManager.setLogger(getLogger()); 169 _parameterDisableConditionsManager.contextualize(_context); 170 _parameterDisableConditionsManager.service(_manager); 171 172 _repeaterDisableConditionsManager = new ThreadSafeComponentManager<>(); 173 _repeaterDisableConditionsManager.setLogger(getLogger()); 174 _repeaterDisableConditionsManager.contextualize(_context); 175 _repeaterDisableConditionsManager.service(_manager); 176 177 _validatorManager = new ThreadSafeComponentManager<>(); 178 _validatorManager.setLogger(getLogger()); 179 _validatorManager.contextualize(_context); 180 _validatorManager.service(_manager); 181 182 _enumeratorManager = new ThreadSafeComponentManager<>(); 183 _enumeratorManager.setLogger(getLogger()); 184 _enumeratorManager.contextualize(_context); 185 _enumeratorManager.service(_manager); 186 187 ConfigurationAndPluginName configurationAndPluginName = new ConfigurationAndPluginName(configuration, _pluginName); 188 _label = ItemParserHelper.parseI18nizableText(configurationAndPluginName, "label"); 189 _description = ItemParserHelper.parseI18nizableText(configurationAndPluginName, "description"); 190 _category = ItemParserHelper.parseI18nizableText(configurationAndPluginName, "category"); 191 192 _isCacheable = configuration.getChild("cacheable").getValueAsBoolean(false); 193 _isPrivate = configuration.getChild("private").getValueAsBoolean(false); 194 _right = configuration.getChild("right").getValue(null); 195 196 this._iconGlyph = configuration.getChild("thumbnail").getChild("glyph").getValue(null); 197 this._iconDecorator = configuration.getChild("thumbnail").getChild("decorator").getValue(null); 198 this._smallIcon = _configureThumbnail(configuration.getChild("thumbnail").getChild("small"), "/plugins/web/resources/img/service/servicepage_16.png"); 199 this._mediumIcon = _configureThumbnail(configuration.getChild("thumbnail").getChild("medium"), "/plugins/web/resources/img/service/servicepage_32.png"); 200 this._largeIcon = _configureThumbnail(configuration.getChild("thumbnail").getChild("marge"), "/plugins/web/resources/img/service/servicepage_50.png"); 201 this._creationBoxHeight = configureDialogBoxDimension(configuration, "height"); 202 this._creationBoxWidth = configureDialogBoxDimension(configuration, "width"); 203 204 _cssFiles = _configureImports(configuration.getChild("css")); 205 _paramsScript = _configureScript(configuration.getChild("parameters")); 206 207 _url = configuration.getChild("url").getValue(); 208 209 _serviceParameterDefinitionParser = new ServiceParameterDefinitionParser(_serviceParameterTypeExtensionPoint, _parameterDisableConditionsManager, _enumeratorManager, _validatorManager); 210 _repeaterDefinitionParser = new DataHolderRepeaterDefinitionParser(_serviceParameterTypeExtensionPoint, _repeaterDisableConditionsManager); 211 212 Configuration parametersConfiguration = configuration.getChild("parameters"); 213 configureParameters(parametersConfiguration); 214 215 try 216 { 217 _serviceParameterDefinitionParser.lookupComponents(); 218 _repeaterDefinitionParser.lookupComponents(); 219 } 220 catch (Exception e) 221 { 222 throw new ConfigurationException("Unable to lookup parameter local components", configuration, e); 223 } 224 225 configureIndexation(configuration.getChild("indexation")); 226 } 227 228 /** 229 * Configure the service parameters 230 * @param parametersConfiguration the parameters configuration 231 * @throws ConfigurationException if a configuration exception occurred 232 */ 233 protected void configureParameters(Configuration parametersConfiguration) throws ConfigurationException 234 { 235 ViewAndParameters viewAndParameters = _viewAndParametersParser.parseParameters(parametersConfiguration, _pluginName, "plugin." + _pluginName, this, _serviceParameterDefinitionParser, _repeaterDefinitionParser); 236 _view = viewAndParameters.getView(); 237 _modelItems = viewAndParameters.getParameters(); 238 } 239 240 /** 241 * Configure the indexation process.<br> 242 * This class only allow to index the value of some parameters (not repeaters). 243 * @param configuration the indexation configuration. 244 * @throws ConfigurationException if an error occurs. 245 */ 246 protected void configureIndexation(Configuration configuration) throws ConfigurationException 247 { 248 _parametersToIndex = new ArrayList<>(); 249 250 for (Configuration config : configuration.getChildren("parameter")) 251 { 252 String id = config.getValue(""); 253 ModelItem param = _modelItems.get(id); 254 255 if (param == null || !(param instanceof ElementDefinition)) 256 { 257 if (getLogger().isWarnEnabled()) 258 { 259 getLogger().warn("Invalid indexation configuration for service " + _id + " : there's no parameter '" + id + "'"); 260 } 261 } 262 else 263 { 264 _parametersToIndex.add(id); 265 } 266 } 267 } 268 269 @Override 270 public void setPluginInfo(String pluginName, String featureName, String id) 271 { 272 _pluginName = pluginName; 273 _featureName = featureName; 274 _id = id; 275 } 276 277 @Override 278 public String getPluginName() 279 { 280 return _pluginName; 281 } 282 283 @Override 284 public String getId() 285 { 286 return _id; 287 } 288 289 public String getName() 290 { 291 return _id; 292 } 293 294 @Override 295 public boolean isCacheable(Page currentPage, ZoneItem zoneItem) 296 { 297 return _isCacheable; 298 } 299 300 @Override 301 public I18nizableText getLabel() 302 { 303 return _label; 304 } 305 306 @Override 307 public I18nizableText getDescription() 308 { 309 return _description; 310 } 311 312 @Override 313 public I18nizableText getCategory() 314 { 315 return _category; 316 } 317 318 @Override 319 public String getIconGlyph() 320 { 321 return _iconGlyph; 322 } 323 324 @Override 325 public String getIconDecorator() 326 { 327 return _iconDecorator; 328 } 329 330 @Override 331 public String getSmallIcon() 332 { 333 return _smallIcon; 334 } 335 336 @Override 337 public String getMediumIcon() 338 { 339 return _mediumIcon; 340 } 341 342 @Override 343 public String getLargeIcon() 344 { 345 return _largeIcon; 346 } 347 348 @Override 349 public Integer getCreationBoxHeight() 350 { 351 return _creationBoxHeight; 352 } 353 354 @Override 355 public Integer getCreationBoxWidth() 356 { 357 return _creationBoxWidth; 358 } 359 360 @Override 361 public String getURL() 362 { 363 return "cocoon://_plugins/" + _pluginName + "/" + _url; 364 } 365 366 @Override 367 public Script getParametersScript() 368 { 369 return _paramsScript; 370 } 371 372 @Override 373 public List<ScriptFile> getCSSFiles() 374 { 375 return _cssFiles; 376 } 377 378 @Override 379 public boolean isPrivate() 380 { 381 return _isPrivate; 382 } 383 384 @Override 385 public String getRight() 386 { 387 return _right; 388 } 389 390 @SuppressWarnings("unchecked") 391 @Override 392 public void index(ZoneItem zoneItem, SolrInputDocument document) 393 { 394 for (String id : _parametersToIndex) 395 { 396 ModelAwareDataHolder dataHolder = zoneItem.getServiceParameters(); 397 if (dataHolder.hasValue(id)) 398 { 399 String language = zoneItem.getZone().getSitemapElement().getSitemapName(); 400 401 Object value = dataHolder.getValue(id); 402 403 ElementDefinition parameter = (ElementDefinition) _modelItems.get(id); 404 ElementType type = parameter.getType(); 405 406 CMSDataContext context = CMSDataContext.newInstance() 407 .withLocale(LocaleUtils.toLocale(language)) 408 .withModelItem(parameter); 409 if (parameter.isMultiple()) 410 { 411 for (Object singleValue : (Object[]) value) 412 { 413 IndexableElementTypeHelper.indexFulltextValue(document, type.toString(singleValue), context); 414 } 415 } 416 else 417 { 418 IndexableElementTypeHelper.indexFulltextValue(document, type.toString(value), context); 419 } 420 } 421 } 422 } 423 424 private String _configureThumbnail(Configuration valueConf, String defaultImage) 425 { 426 String value = valueConf.getValue(null); 427 if (value == null) 428 { 429 return defaultImage; 430 } 431 else 432 { 433 String pluginName = valueConf.getAttribute("plugin", this._pluginName); 434 return "/plugins/" + pluginName + "/resources/" + value; 435 } 436 } 437 438 /** 439 * Configure the dimensions of the dialog box 440 * @param configuration the global configuration 441 * @param dimensionName the name of the dimension to configure 442 * @return the value of the dimension 443 * @throws ConfigurationException if configuration is invalid 444 */ 445 protected Integer configureDialogBoxDimension(Configuration configuration, String dimensionName) throws ConfigurationException 446 { 447 Configuration dimensionConfiguration = configuration.getChild("dialogBox").getChild(dimensionName, false); 448 if (dimensionConfiguration != null) 449 { 450 return dimensionConfiguration.getValueAsInteger(); 451 } 452 else 453 { 454 return null; 455 } 456 } 457 458 /** 459 * Configure the script 460 * @param configuration the global configuration 461 * @return The script created 462 * @throws ConfigurationException if configuration is invalid 463 */ 464 protected Script _configureScript(Configuration configuration) throws ConfigurationException 465 { 466 List<ScriptFile> scriptsImports = _configureImports(configuration.getChild("scripts")); 467 List<ScriptFile> cssImports = _configureImports(configuration.getChild("css")); 468 String jsClassName = _configureClass(configuration.getChild("action")); 469 470 return new Script(this.getId(), jsClassName, scriptsImports, cssImports, new HashMap<>()); 471 } 472 473 474 /** 475 * Configure the js class name 476 * @param configuration The configuration on action tag 477 * @return The js class name 478 * @throws ConfigurationException If an error occurs 479 */ 480 protected String _configureClass(Configuration configuration) throws ConfigurationException 481 { 482 String jsClassName = configuration.getAttribute("class", ""); 483 if (getLogger().isDebugEnabled()) 484 { 485 getLogger().debug("Js class configured is '" + jsClassName + "'"); 486 } 487 return jsClassName; 488 } 489 490 /** 491 * Configure the import part 492 * @param configuration The imports configuration 493 * @return The set of the complete url of imported file 494 * @throws ConfigurationException If an error occurs 495 */ 496 protected List<ScriptFile> _configureImports(Configuration configuration) throws ConfigurationException 497 { 498 return ConfigurationHelper.parsePluginResourceList(configuration, getPluginName(), getLogger()); 499 } 500 501 public Collection<ModelItem> getModelItems() 502 { 503 return _modelItems.values(); 504 } 505 506 public Map<String, ModelItem> getParameters() 507 { 508 return _modelItems; 509 } 510 511 public View getView() 512 { 513 return _view; 514 } 515}