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