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