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.Collections; 020import java.util.HashMap; 021import java.util.LinkedHashMap; 022import java.util.List; 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.content.indexing.solr.SolrContentIndexer; 038import org.ametys.core.ui.ClientSideElement.Script; 039import org.ametys.core.ui.ClientSideElement.ScriptFile; 040import org.ametys.plugins.core.ui.util.ConfigurationHelper; 041import org.ametys.runtime.i18n.I18nizableText; 042import org.ametys.runtime.parameter.AbstractParameterParser; 043import org.ametys.runtime.parameter.Enumerator; 044import org.ametys.runtime.parameter.ParameterHelper; 045import org.ametys.runtime.parameter.ParameterHelper.ParameterType; 046import org.ametys.runtime.parameter.Validator; 047import org.ametys.runtime.plugin.component.AbstractLogEnabled; 048import org.ametys.runtime.plugin.component.PluginAware; 049import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 050import org.ametys.web.repository.page.Page; 051import org.ametys.web.repository.page.ZoneItem; 052 053/** 054 * Class representing a business service. <br> 055 * A service is identified by an id and a Cocoon-URL.<br> 056 * This URL corresponds to a pipeline called by a page template.<br> 057 * URL must be relative to the sitemap of the plugin containing the service. 058 */ 059public class StaticService extends AbstractLogEnabled implements Service, Contextualizable, Configurable, PluginAware, Serviceable, Disposable 060{ 061 /** The plugin name */ 062 protected String _pluginName; 063 /** The feature name */ 064 protected String _featureName; 065 /** The service manager */ 066 protected ServiceManager _manager; 067 /** The context. */ 068 protected Context _context; 069 /** The script configured */ 070 protected Script _paramsScript; 071 072 private String _id; 073 private I18nizableText _label; 074 private I18nizableText _description; 075 private I18nizableText _category; 076 077 private String _iconGlyph; 078 private String _iconDecorator; 079 private String _smallIcon; 080 private String _mediumIcon; 081 private String _largeIcon; 082 private List<ScriptFile> _cssFiles; 083 084 private boolean _isCacheable; 085 private boolean _isPrivate; 086 /** The right needed to create an instance of this service, blank or null if no right is needed. */ 087 private String _right; 088 089 private String _url; 090 private Map<String, ServiceParameterOrRepeater> _parameters; 091 private List<ServiceParameterGroup> _groups; 092 093 private List<String> _parametersToIndex; 094 095 // ComponentManager for validators 096 private ThreadSafeComponentManager<Validator> _validatorManager; 097 098 // ComponentManager for enumerators 099 private ThreadSafeComponentManager<Enumerator> _enumeratorManager; 100 101 @Override 102 public void service(ServiceManager smanager) throws ServiceException 103 { 104 _manager = smanager; 105 } 106 107 @Override 108 public void contextualize(Context context) throws ContextException 109 { 110 _context = context; 111 } 112 113 @Override 114 public void dispose() 115 { 116 _validatorManager.dispose(); 117 _validatorManager = null; 118 119 _enumeratorManager.dispose(); 120 _enumeratorManager = null; 121 } 122 123 @Override 124 public void configure(Configuration configuration) throws ConfigurationException 125 { 126 _validatorManager = new ThreadSafeComponentManager<>(); 127 _validatorManager.setLogger(getLogger()); 128 _validatorManager.contextualize(_context); 129 _validatorManager.service(_manager); 130 131 _enumeratorManager = new ThreadSafeComponentManager<>(); 132 _enumeratorManager.setLogger(getLogger()); 133 _enumeratorManager.contextualize(_context); 134 _enumeratorManager.service(_manager); 135 136 _label = _parseI18nizableText(configuration, "label"); 137 _description = _parseI18nizableText(configuration, "description"); 138 _category = _parseI18nizableText(configuration, "category"); 139 140 _isCacheable = configuration.getChild("cacheable").getValueAsBoolean(false); 141 _isPrivate = configuration.getChild("private").getValueAsBoolean(false); 142 _right = configuration.getChild("right").getValue(null); 143 144 this._iconGlyph = configuration.getChild("thumbnail").getChild("glyph").getValue(null); 145 this._iconDecorator = configuration.getChild("thumbnail").getChild("decorator").getValue(null); 146 this._smallIcon = _configureThumbnail(configuration.getChild("thumbnail").getChild("small"), "/plugins/web/resources/img/service/servicepage_16.png"); 147 this._mediumIcon = _configureThumbnail(configuration.getChild("thumbnail").getChild("medium"), "/plugins/web/resources/img/service/servicepage_32.png"); 148 this._largeIcon = _configureThumbnail(configuration.getChild("thumbnail").getChild("marge"), "/plugins/web/resources/img/service/servicepage_50.png"); 149 150 _cssFiles = _configureImports(configuration.getChild("css")); 151 _paramsScript = _configureScript(configuration.getChild("parameters")); 152 153 _url = configuration.getChild("url").getValue(); 154 _parameters = new LinkedHashMap<>(); 155 _groups = new ArrayList<>(); 156 157 ServiceParameterParser serviceParameterParser = new ServiceParameterParser(_enumeratorManager, _validatorManager); 158 ServiceParameterOrRepeaterParser serviceParamOrRepeaterParser = new ServiceParameterOrRepeaterParser(serviceParameterParser); 159 160 configureParameters(configuration.getChild("parameters"), serviceParamOrRepeaterParser); 161 162 try 163 { 164 serviceParameterParser.lookupComponents(); 165 } 166 catch (Exception e) 167 { 168 throw new ConfigurationException("Unable to lookup parameter local components", configuration, e); 169 } 170 171 configureIndexation(configuration.getChild("indexation")); 172 } 173 174 /** 175 * Configure the service parameters. 176 * @param configuration the service configuration. 177 * @param serviceParameterParser the parser. 178 * @throws ConfigurationException if an error occurs. 179 */ 180 protected void configureParameters(Configuration configuration, ServiceParameterOrRepeaterParser serviceParameterParser) throws ConfigurationException 181 { 182 Configuration[] groupConfigurations = configuration.getChildren("group"); 183 if (groupConfigurations.length > 0) 184 { 185 // Has groups. 186 for (Configuration groupConfiguration : groupConfigurations) 187 { 188 ServiceParameterGroup group = new ServiceParameterGroup(); 189 _groups.add(group); 190 191 configureParameterGroup(group, groupConfiguration, serviceParameterParser); 192 } 193 } 194 else 195 { 196 // No group tag, create a single group and put all params in it. 197 ServiceParameterGroup group = new ServiceParameterGroup(); 198 _groups.add(group); 199 200 configureParameterGroup(group, configuration, serviceParameterParser); 201 } 202 } 203 204 /** 205 * Configure the indexation process.<br> 206 * This class only allow to index the value of some parameters (not repeaters). 207 * @param configuration the indexation configuration. 208 * @throws ConfigurationException if an error occurs. 209 */ 210 protected void configureIndexation(Configuration configuration) throws ConfigurationException 211 { 212 _parametersToIndex = new ArrayList<>(); 213 214 for (Configuration config : configuration.getChildren("parameter")) 215 { 216 String id = config.getValue(""); 217 ServiceParameterOrRepeater param = _parameters.get(id); 218 219 if (param == null || !(param instanceof ServiceParameter)) 220 { 221 if (getLogger().isWarnEnabled()) 222 { 223 getLogger().warn("Invalid indexation configuration for service " + _id + " : there's no parameter '" + id + "'"); 224 } 225 } 226 else 227 { 228 _parametersToIndex.add(id); 229 } 230 } 231 } 232 233 /** 234 * Configure the service parameters. 235 * @param group the group to put the params in. 236 * @param configuration the service configuration. 237 * @param serviceParameterParser the parser. 238 * @throws ConfigurationException if an error occurs. 239 */ 240 protected void configureParameterGroup(ServiceParameterGroup group, Configuration configuration, ServiceParameterOrRepeaterParser serviceParameterParser) throws ConfigurationException 241 { 242 for (Configuration paramConfiguration : configuration.getChildren()) 243 { 244 String paramName = paramConfiguration.getName(); 245 if (paramName.equals("parameter") || paramName.equals("repeater")) 246 { 247 ServiceParameterOrRepeater serviceParameter = serviceParameterParser.parse(_manager, _pluginName, paramConfiguration); 248 249 // Add the param or repeater to the parameter map as well as to his group. 250 _parameters.put(serviceParameter.getId(), serviceParameter); 251 group.add(serviceParameter); 252 } 253 } 254 } 255 256 @Override 257 public void setPluginInfo(String pluginName, String featureName, String id) 258 { 259 _pluginName = pluginName; 260 _featureName = featureName; 261 _id = id; 262 } 263 264 @Override 265 public String getPluginName() 266 { 267 return _pluginName; 268 } 269 270 @Override 271 public String getId() 272 { 273 return _id; 274 } 275 276 @Override 277 public boolean isCacheable(Page currentPage, ZoneItem zoneItem) 278 { 279 return _isCacheable; 280 } 281 282 @Override 283 public I18nizableText getLabel() 284 { 285 return _label; 286 } 287 288 @Override 289 public I18nizableText getDescription() 290 { 291 return _description; 292 } 293 294 @Override 295 public I18nizableText getCategory() 296 { 297 return _category; 298 } 299 300 @Override 301 public String getIconGlyph() 302 { 303 return _iconGlyph; 304 } 305 306 @Override 307 public String getIconDecorator() 308 { 309 return _iconDecorator; 310 } 311 312 @Override 313 public String getSmallIcon() 314 { 315 return _smallIcon; 316 } 317 318 @Override 319 public String getMediumIcon() 320 { 321 return _mediumIcon; 322 } 323 324 @Override 325 public String getLargeIcon() 326 { 327 return _largeIcon; 328 } 329 330 @Override 331 public String getURL() 332 { 333 return "cocoon://_plugins/" + _pluginName + "/" + _url; 334 } 335 336 @Override 337 public Map<String, ServiceParameterOrRepeater> getParameters() 338 { 339 return Collections.unmodifiableMap(_parameters); 340 } 341 342 @Override 343 public List<ServiceParameterGroup> getParameterGroups() 344 { 345 return Collections.unmodifiableList(_groups); 346 } 347 348 @Override 349 public Script getParametersScript() 350 { 351 return _paramsScript; 352 } 353 354 @Override 355 public List<ScriptFile> getCSSFiles() 356 { 357 return _cssFiles; 358 } 359 360 @Override 361 public boolean isPrivate() 362 { 363 return _isPrivate; 364 } 365 366 @Override 367 public String getRight() 368 { 369 return _right; 370 } 371 372 @Override 373 public void index(ZoneItem zoneItem, SolrInputDocument document) 374 { 375 for (String id : _parametersToIndex) 376 { 377 if (zoneItem.getServiceParameters().hasMetadata(id)) 378 { 379 ServiceParameter parameter = (ServiceParameter) _parameters.get(id); 380 ParameterType type = parameter.getType(); 381 382 switch (type) 383 { 384 case BOOLEAN: 385 case DOUBLE: 386 case LONG: 387 case STRING: 388 String language = zoneItem.getZone().getPage().getSitemapName(); 389 390 String[] values = zoneItem.getServiceParameters().getStringArray(id); 391 for (String value : values) 392 { 393 SolrContentIndexer.indexFulltextValue(document, value, language); 394 } 395 break; 396 default: 397 break; 398 } 399 } 400 } 401 } 402 403 /** 404 * Parse an i18n text. 405 * @param config the configuration to use. 406 * @param name the child name. 407 * @return the i18n text. 408 */ 409 protected I18nizableText _parseI18nizableText(Configuration config, String name) 410 { 411 return I18nizableText.parseI18nizableText(config.getChild(name), "plugin." + _pluginName, ""); 412 } 413 414 private String _configureThumbnail(Configuration valueConf, String defaultImage) 415 { 416 String value = valueConf.getValue(null); 417 if (value == null) 418 { 419 return defaultImage; 420 } 421 else 422 { 423 String pluginName = valueConf.getAttribute("plugin", this._pluginName); 424 return "/plugins/" + pluginName + "/resources/" + value; 425 } 426 } 427 428 /** 429 * Configure the script 430 * @param configuration the global configuration 431 * @return The script created 432 * @throws ConfigurationException if configuration is invalid 433 */ 434 protected Script _configureScript(Configuration configuration) throws ConfigurationException 435 { 436 List<ScriptFile> scriptsImports = _configureImports(configuration.getChild("scripts")); 437 List<ScriptFile> cssImports = _configureImports(configuration.getChild("css")); 438 String jsClassName = _configureClass(configuration.getChild("action")); 439 440 return new Script(this.getId(), jsClassName, scriptsImports, cssImports, new HashMap<>()); 441 } 442 443 444 /** 445 * Configure the js class name 446 * @param configuration The configuration on action tag 447 * @return The js class name 448 * @throws ConfigurationException If an error occurs 449 */ 450 protected String _configureClass(Configuration configuration) throws ConfigurationException 451 { 452 String jsClassName = configuration.getAttribute("class", ""); 453 if (getLogger().isDebugEnabled()) 454 { 455 getLogger().debug("Js class configured is '" + jsClassName + "'"); 456 } 457 return jsClassName; 458 } 459 460 /** 461 * Configure the import part 462 * @param configuration The imports configuration 463 * @return The set of the complete url of imported file 464 * @throws ConfigurationException If an error occurs 465 */ 466 protected List<ScriptFile> _configureImports(Configuration configuration) throws ConfigurationException 467 { 468 return ConfigurationHelper.parsePluginResourceList(configuration, getPluginName(), getLogger()); 469 } 470 471 private static class ServiceParameterParser extends AbstractParameterParser<ServiceParameter, ParameterType> 472 { 473 /** 474 * Creates an {@link ServiceParameterParser}. 475 * @param enumeratorManager the enumerator component manager. 476 * @param validatorManager the validator component manager. 477 */ 478 public ServiceParameterParser(ThreadSafeComponentManager<Enumerator> enumeratorManager, ThreadSafeComponentManager<Validator> validatorManager) 479 { 480 super(enumeratorManager, validatorManager); 481 } 482 483 @Override 484 protected ServiceParameter _createParameter(Configuration parameterConfig) throws ConfigurationException 485 { 486 return new ServiceParameter(); 487 } 488 489 @Override 490 protected String _parseId(Configuration parameterConfig) throws ConfigurationException 491 { 492 String parameterName = parameterConfig.getAttribute("name"); 493 494 if (!parameterName.matches("^[a-zA-Z0-9_-]+$")) 495 { 496 throw new ConfigurationException("Invalid parameter name: " + parameterName, parameterConfig); 497 } 498 499 return parameterName; 500 } 501 502 @Override 503 protected ParameterType _parseType(Configuration parameterConfig) throws ConfigurationException 504 { 505 try 506 { 507 return ParameterType.valueOf(parameterConfig.getAttribute("type").toUpperCase()); 508 } 509 catch (IllegalArgumentException e) 510 { 511 throw new ConfigurationException("Invalid type", parameterConfig, e); 512 } 513 } 514 515 @Override 516 protected Object _parseDefaultValue(Configuration parameterConfig, ServiceParameter parameter) 517 { 518 String value; 519 520 Configuration childNode = parameterConfig.getChild("default-value", false); 521 if (childNode == null) 522 { 523 value = null; 524 } 525 else 526 { 527 value = childNode.getValue(""); 528 } 529 530 return ParameterHelper.castValue(value, parameter.getType()); 531 } 532 533 @Override 534 protected void _additionalParsing(ServiceManager manager, String pluginName, Configuration parameterConfig, String parameterId, ServiceParameter parameter) throws ConfigurationException 535 { 536 super._additionalParsing(manager, pluginName, parameterConfig, parameterId, parameter); 537 538 parameter.setId(parameterId); 539 parameter.setMultiple(parameterConfig.getAttributeAsBoolean("multiple", false)); 540 } 541 } 542 543 private static class ServiceParameterOrRepeaterParser 544 { 545 546 private ServiceParameterParser _serviceParameterParser; 547 548 public ServiceParameterOrRepeaterParser(ServiceParameterParser paramParser) 549 { 550 _serviceParameterParser = paramParser; 551 } 552 553 public ServiceParameterOrRepeater parse(ServiceManager manager, String pluginName, Configuration paramConfiguration) throws ConfigurationException 554 { 555 String name = paramConfiguration.getName(); 556 if (name.equals("parameter")) 557 { 558 return _serviceParameterParser.parseParameter(manager, pluginName, paramConfiguration); 559 } 560 else if (name.equals("repeater")) 561 { 562 return parseRepeater(manager, pluginName, paramConfiguration); 563 } 564 else 565 { 566 throw new ConfigurationException("Only parameter and repeater can be found in a service parameters configuration. Invalid tag: " + name, paramConfiguration); 567 } 568 } 569 570 protected ServiceParameterRepeater parseRepeater(ServiceManager manager, String pluginName, Configuration repeaterConfig) throws ConfigurationException 571 { 572 ServiceParameterRepeater repeater = new ServiceParameterRepeater(); 573 574 String parameterId = _parseId(repeaterConfig); 575 576 repeater.setId(parameterId); 577 repeater.setPluginName(pluginName); 578 repeater.setLabel(_parseI18nizableText(repeaterConfig, pluginName, "label")); 579 repeater.setDescription(_parseI18nizableText(repeaterConfig, pluginName, "description")); 580 581 repeater.setAddLabel(_parseI18nizableText(repeaterConfig, pluginName, "add-label")); 582 repeater.setEditLabel(_parseI18nizableText(repeaterConfig, pluginName, "edit-label")); 583 repeater.setDeleteLabel(_parseI18nizableText(repeaterConfig, pluginName, "del-label")); 584 repeater.setAddIcon(_parseIcon(repeaterConfig.getChild("add-icon"), pluginName, "/plugins/web/resources/img/widget/repeater/add_12.png")); 585 repeater.setEditIcon(_parseIcon(repeaterConfig.getChild("edit-icon"), pluginName, "/plugins/web/resources/img/widget/repeater/edit_12.png")); 586 repeater.setDeleteIcon(_parseIcon(repeaterConfig.getChild("del-icon"), pluginName, "/plugins/web/resources/img/widget/repeater/delete_12.png")); 587 588 repeater.setInitialSize(repeaterConfig.getAttributeAsInteger("initial-size", 0)); 589 repeater.setMinSize(repeaterConfig.getAttributeAsInteger("min-size", 0)); 590 repeater.setMaxSize(repeaterConfig.getAttributeAsInteger("max-size", -1)); 591 592 repeater.setChildrenParameters(parseParameters(manager, pluginName, repeaterConfig)); 593 594 return repeater; 595 } 596 597 protected String _parseId(Configuration parameterConfig) throws ConfigurationException 598 { 599 String parameterName = parameterConfig.getAttribute("name"); 600 601 if (!parameterName.matches("^[a-zA-Z0-9_-]+$")) 602 { 603 throw new ConfigurationException("Invalid parameter name: " + parameterName, parameterConfig); 604 } 605 606 return parameterName; 607 } 608 609 /** 610 * Parses an i18n text. 611 * @param config the configuration to use. 612 * @param pluginName the current plugin name. 613 * @param name the child name. 614 * @return the i18n text. 615 * @throws ConfigurationException if the configuration is not valid. 616 */ 617 protected I18nizableText _parseI18nizableText(Configuration config, String pluginName, String name) throws ConfigurationException 618 { 619 return I18nizableText.parseI18nizableText(config.getChild(name), "plugin." + pluginName); 620 } 621 622 /** 623 * Parse icon 624 * @param iconConfig The icon config 625 * @param defaultPluginName The default plugin name 626 * @param defaultImage The default image 627 * @return The icon path 628 */ 629 protected String _parseIcon(Configuration iconConfig, String defaultPluginName, String defaultImage) 630 { 631 String value = iconConfig.getValue(null); 632 if (value == null) 633 { 634 return defaultImage; 635 } 636 else 637 { 638 String pluginName = iconConfig.getAttribute("plugin", defaultPluginName); 639 return "/plugins/" + pluginName + "/resources/" + value; 640 } 641 } 642 643 protected Map<String, ServiceParameter> parseParameters(ServiceManager manager, String pluginName, Configuration repeaterConfig) throws ConfigurationException 644 { 645 Map<String, ServiceParameter> params = new LinkedHashMap<>(); 646 647 for (Configuration paramConfiguration : repeaterConfig.getChildren("parameter")) 648 { 649 ServiceParameter serviceParameter = _serviceParameterParser.parseParameter(manager, pluginName, paramConfiguration); 650 651 params.put(serviceParameter.getId(), serviceParameter); 652 } 653 654 return params; 655 } 656 } 657}