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.solr.common.SolrInputDocument; 035 036import org.ametys.cms.content.indexing.solr.SolrContentIndexer; 037import org.ametys.core.ui.ClientSideElement.Script; 038import org.ametys.core.ui.ClientSideElement.ScriptFile; 039import org.ametys.plugins.core.ui.util.ConfigurationHelper; 040import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 041import org.ametys.plugins.repository.model.RepeaterDefinition; 042import org.ametys.plugins.repository.model.parsing.RepeaterDefinitionParser; 043import org.ametys.runtime.i18n.I18nizableText; 044import org.ametys.runtime.model.ElementDefinition; 045import org.ametys.runtime.model.Enumerator; 046import org.ametys.runtime.model.ModelItem; 047import org.ametys.runtime.model.ModelItemGroup; 048import org.ametys.runtime.model.ModelViewItem; 049import org.ametys.runtime.model.ModelViewItemGroup; 050import org.ametys.runtime.model.SimpleViewItemGroup; 051import org.ametys.runtime.model.View; 052import org.ametys.runtime.model.ViewElement; 053import org.ametys.runtime.model.ViewItem; 054import org.ametys.runtime.model.ViewItemGroup; 055import org.ametys.runtime.model.type.ElementType; 056import org.ametys.runtime.parameter.Validator; 057import org.ametys.runtime.plugin.component.AbstractLogEnabled; 058import org.ametys.runtime.plugin.component.PluginAware; 059import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 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 private String _id; 089 private I18nizableText _label; 090 private I18nizableText _description; 091 private I18nizableText _category; 092 093 private String _iconGlyph; 094 private String _iconDecorator; 095 private String _smallIcon; 096 private String _mediumIcon; 097 private String _largeIcon; 098 private Integer _creationBoxHeight; 099 private Integer _creationBoxWidth; 100 private List<ScriptFile> _cssFiles; 101 102 private boolean _isCacheable; 103 private boolean _isPrivate; 104 /** The right needed to create an instance of this service, blank or null if no right is needed. */ 105 private String _right; 106 107 private String _url; 108 109 private List<String> _parametersToIndex; 110 111 // ComponentManager for validators 112 private ThreadSafeComponentManager<Validator> _validatorManager; 113 114 // ComponentManager for enumerators 115 private ThreadSafeComponentManager<Enumerator> _enumeratorManager; 116 117 private ServiceParameterTypeExtensionPoint _serviceParameterTypeExtensionPoint; 118 private ServiceParameterDefinitionParser _serviceParameterDefinitionParser; 119 private RepeaterDefinitionParser _repeaterDefinitionParser; 120 121 @Override 122 public void service(ServiceManager smanager) throws ServiceException 123 { 124 _manager = smanager; 125 _serviceParameterTypeExtensionPoint = (ServiceParameterTypeExtensionPoint) smanager.lookup(ServiceParameterTypeExtensionPoint.ROLE); 126 } 127 128 @Override 129 public void contextualize(Context context) throws ContextException 130 { 131 _context = context; 132 } 133 134 @Override 135 public void dispose() 136 { 137 _validatorManager.dispose(); 138 _validatorManager = null; 139 140 _enumeratorManager.dispose(); 141 _enumeratorManager = null; 142 } 143 144 @Override 145 public void configure(Configuration configuration) throws ConfigurationException 146 { 147 _validatorManager = new ThreadSafeComponentManager<>(); 148 _validatorManager.setLogger(getLogger()); 149 _validatorManager.contextualize(_context); 150 _validatorManager.service(_manager); 151 152 _enumeratorManager = new ThreadSafeComponentManager<>(); 153 _enumeratorManager.setLogger(getLogger()); 154 _enumeratorManager.contextualize(_context); 155 _enumeratorManager.service(_manager); 156 157 _label = _parseI18nizableText(configuration, "label"); 158 _description = _parseI18nizableText(configuration, "description"); 159 _category = _parseI18nizableText(configuration, "category"); 160 161 _isCacheable = configuration.getChild("cacheable").getValueAsBoolean(false); 162 _isPrivate = configuration.getChild("private").getValueAsBoolean(false); 163 _right = configuration.getChild("right").getValue(null); 164 165 this._iconGlyph = configuration.getChild("thumbnail").getChild("glyph").getValue(null); 166 this._iconDecorator = configuration.getChild("thumbnail").getChild("decorator").getValue(null); 167 this._smallIcon = _configureThumbnail(configuration.getChild("thumbnail").getChild("small"), "/plugins/web/resources/img/service/servicepage_16.png"); 168 this._mediumIcon = _configureThumbnail(configuration.getChild("thumbnail").getChild("medium"), "/plugins/web/resources/img/service/servicepage_32.png"); 169 this._largeIcon = _configureThumbnail(configuration.getChild("thumbnail").getChild("marge"), "/plugins/web/resources/img/service/servicepage_50.png"); 170 this._creationBoxHeight = configureDialogBoxDimension(configuration, "height"); 171 this._creationBoxWidth = configureDialogBoxDimension(configuration, "width"); 172 173 _cssFiles = _configureImports(configuration.getChild("css")); 174 _paramsScript = _configureScript(configuration.getChild("parameters")); 175 176 _url = configuration.getChild("url").getValue(); 177 178 _serviceParameterDefinitionParser = new ServiceParameterDefinitionParser(_serviceParameterTypeExtensionPoint, _enumeratorManager, _validatorManager); 179 _repeaterDefinitionParser = new RepeaterDefinitionParser(_serviceParameterTypeExtensionPoint); 180 181 Configuration parametersConfiguration = configuration.getChild("parameters"); 182 configureParameters(parametersConfiguration); 183 184 try 185 { 186 _serviceParameterDefinitionParser.lookupComponents(); 187 } 188 catch (Exception e) 189 { 190 throw new ConfigurationException("Unable to lookup parameter local components", configuration, e); 191 } 192 193 configureIndexation(configuration.getChild("indexation")); 194 } 195 196 /** 197 * Configure the service parameters. 198 * @param parametersConfiguration the service parameters configuration. 199 * @throws ConfigurationException if an error occurs. 200 */ 201 protected void configureParameters(Configuration parametersConfiguration) throws ConfigurationException 202 { 203 _view = new View(); 204 _modelItems = new HashMap<>(); 205 206 // Parse all groups, fielsets and parameters to build the view 207 Configuration[] groupConfigurations = parametersConfiguration.getChildren("group"); 208 if (groupConfigurations.length > 0) 209 { 210 if (parametersConfiguration.getChildren("fieldset").length > 0 211 || parametersConfiguration.getChildren("parameter").length > 0 212 || parametersConfiguration.getChildren("repeater").length > 0) 213 { 214 throw new ConfigurationException("The service '" + getId() + "' should not have parameters out of its groups.", parametersConfiguration); 215 } 216 217 // Has groups. 218 for (Configuration groupConfiguration : groupConfigurations) 219 { 220 SimpleViewItemGroup group = _parseSimpleViewItemGroup(groupConfiguration, null); 221 group.setRole(ViewItemGroup.TAB_ROLE); 222 _view.addViewItem(group); 223 } 224 } 225 else 226 { 227 SimpleViewItemGroup group = _parseSimpleViewItemGroup(parametersConfiguration, null); 228 group.setRole(ViewItemGroup.TAB_ROLE); 229 _view.addViewItem(group); 230 } 231 } 232 233 /** 234 * Parses a simple view item group (group or fieldset) 235 * @param groupConfiguration the item group configuration 236 * @param modelParent The last model item group to use as parent for next definitions 237 * @return the parsed simple view item group 238 * @throws ConfigurationException if an error occurs 239 */ 240 protected SimpleViewItemGroup _parseSimpleViewItemGroup(Configuration groupConfiguration, ModelItemGroup modelParent) throws ConfigurationException 241 { 242 SimpleViewItemGroup group = new SimpleViewItemGroup(); 243 group.setName(groupConfiguration.getAttribute("name", null)); 244 group.setLabel(_parseI18nizableText(groupConfiguration, "label")); 245 group.setDescription(_parseI18nizableText(groupConfiguration, "description")); 246 247 _parseChildren(groupConfiguration, group, modelParent); 248 249 return group; 250 } 251 252 /** 253 * Parses a service parameter 254 * @param paramConfiguration the parameter configuration 255 * @param parent the parent of the service parameter. Can be <code>null</code> if the parameter has no parent 256 * @return the parsed parameter 257 * @throws ConfigurationException if an error occurs 258 */ 259 protected ViewElement _parseParameter(Configuration paramConfiguration, ModelItemGroup parent) throws ConfigurationException 260 { 261 return _parseParameter(paramConfiguration, parent, _pluginName); 262 } 263 264 /** 265 * Parses a service parameter 266 * @param paramConfiguration the parameter configuration 267 * @param parent the parent of the service parameter. Can be <code>null</code> if the parameter has no parent 268 * @param pluginName Name of the plugin that defines this parameter 269 * @return the parsed parameter 270 * @throws ConfigurationException if an error occurs 271 */ 272 protected ViewElement _parseParameter(Configuration paramConfiguration, ModelItemGroup parent, String pluginName) throws ConfigurationException 273 { 274 ServiceParameter serviceParameter = _serviceParameterDefinitionParser.parse(_manager, pluginName, paramConfiguration, this, parent); 275 276 if (serviceParameter != null) 277 { 278 ViewElement viewElement = new ViewElement(); 279 viewElement.setDefinition(serviceParameter); 280 281 return viewElement; 282 } 283 284 return null; 285 } 286 287 /** 288 * Parses a repeater 289 * @param repeaterConfiguration the repeater configuration 290 * @param parent the parent of the repeater. Can be <code>null</code> if the repeater has no parent 291 * @return the parsed repeater 292 * @throws ConfigurationException if an error occurs 293 */ 294 protected ModelViewItemGroup _parseRepeater(Configuration repeaterConfiguration, ModelItemGroup parent) throws ConfigurationException 295 { 296 return _parseRepeater(repeaterConfiguration, parent, _pluginName); 297 } 298 299 /** 300 * Parses a repeater 301 * @param repeaterConfiguration the repeater configuration 302 * @param parent the parent of the repeater. Can be <code>null</code> if the repeater has no parent 303 * @param pluginName Name of the plugin that defines this parameter 304 * @return the parsed repeater 305 * @throws ConfigurationException if an error occurs 306 */ 307 protected ModelViewItemGroup _parseRepeater(Configuration repeaterConfiguration, ModelItemGroup parent, String pluginName) throws ConfigurationException 308 { 309 RepeaterDefinition repeaterDefinition = _repeaterDefinitionParser.parse(_manager, pluginName, repeaterConfiguration, this, parent); 310 311 if (repeaterDefinition != null) 312 { 313 ModelViewItemGroup viewItemGroup = new ModelViewItemGroup(); 314 viewItemGroup.setDefinition(repeaterDefinition); 315 316 _parseChildren(repeaterConfiguration, viewItemGroup, repeaterDefinition); 317 318 return viewItemGroup; 319 } 320 321 return null; 322 } 323 324 /** 325 * Parses children of a view item group 326 * @param groupConfiguration the item group configuration 327 * @param viewItemGroup the item group 328 * @param modelParent The last model item group to use as parent for next definitions 329 * @throws ConfigurationException if an error occurs 330 */ 331 protected void _parseChildren(Configuration groupConfiguration, ViewItemGroup viewItemGroup, ModelItemGroup modelParent) throws ConfigurationException 332 { 333 Configuration[] children = groupConfiguration.getChildren(); 334 for (Configuration child : children) 335 { 336 ViewItem viewItem = null; 337 switch (child.getName()) 338 { 339 case "fieldset": 340 viewItem = _parseSimpleViewItemGroup(child, modelParent); 341 ((ViewItemGroup) viewItem).setRole(ViewItemGroup.FIELDSET_ROLE); 342 break; 343 case "parameter": 344 viewItem = _parseParameter(child, modelParent); 345 break; 346 case "repeater": 347 viewItem = _parseRepeater(child, modelParent); 348 break; 349 case "group": 350 throw new ConfigurationException("The service '" + getId() + "' should not have subgroups", child); 351 default: 352 break; 353 } 354 355 if (viewItem != null) 356 { 357 viewItemGroup.addViewItem(viewItem); 358 if (viewItem instanceof ModelViewItem && modelParent == null) 359 { 360 ModelItem definition = ((ModelViewItem) viewItem).getDefinition(); 361 _modelItems.put(definition.getName(), definition); 362 } 363 } 364 } 365 } 366 367 /** 368 * Configure the indexation process.<br> 369 * This class only allow to index the value of some parameters (not repeaters). 370 * @param configuration the indexation configuration. 371 * @throws ConfigurationException if an error occurs. 372 */ 373 protected void configureIndexation(Configuration configuration) throws ConfigurationException 374 { 375 _parametersToIndex = new ArrayList<>(); 376 377 for (Configuration config : configuration.getChildren("parameter")) 378 { 379 String id = config.getValue(""); 380 ModelItem param = _modelItems.get(id); 381 382 if (param == null || !(param instanceof ElementDefinition)) 383 { 384 if (getLogger().isWarnEnabled()) 385 { 386 getLogger().warn("Invalid indexation configuration for service " + _id + " : there's no parameter '" + id + "'"); 387 } 388 } 389 else 390 { 391 _parametersToIndex.add(id); 392 } 393 } 394 } 395 396 @Override 397 public void setPluginInfo(String pluginName, String featureName, String id) 398 { 399 _pluginName = pluginName; 400 _featureName = featureName; 401 _id = id; 402 } 403 404 @Override 405 public String getPluginName() 406 { 407 return _pluginName; 408 } 409 410 @Override 411 public String getId() 412 { 413 return _id; 414 } 415 416 public String getName() 417 { 418 return _id; 419 } 420 421 @Override 422 public boolean isCacheable(Page currentPage, ZoneItem zoneItem) 423 { 424 return _isCacheable; 425 } 426 427 @Override 428 public I18nizableText getLabel() 429 { 430 return _label; 431 } 432 433 @Override 434 public I18nizableText getDescription() 435 { 436 return _description; 437 } 438 439 @Override 440 public I18nizableText getCategory() 441 { 442 return _category; 443 } 444 445 @Override 446 public String getIconGlyph() 447 { 448 return _iconGlyph; 449 } 450 451 @Override 452 public String getIconDecorator() 453 { 454 return _iconDecorator; 455 } 456 457 @Override 458 public String getSmallIcon() 459 { 460 return _smallIcon; 461 } 462 463 @Override 464 public String getMediumIcon() 465 { 466 return _mediumIcon; 467 } 468 469 @Override 470 public String getLargeIcon() 471 { 472 return _largeIcon; 473 } 474 475 @Override 476 public Integer getCreationBoxHeight() 477 { 478 return _creationBoxHeight; 479 } 480 481 @Override 482 public Integer getCreationBoxWidth() 483 { 484 return _creationBoxWidth; 485 } 486 487 @Override 488 public String getURL() 489 { 490 return "cocoon://_plugins/" + _pluginName + "/" + _url; 491 } 492 493 @Override 494 public Script getParametersScript() 495 { 496 return _paramsScript; 497 } 498 499 @Override 500 public List<ScriptFile> getCSSFiles() 501 { 502 return _cssFiles; 503 } 504 505 @Override 506 public boolean isPrivate() 507 { 508 return _isPrivate; 509 } 510 511 @Override 512 public String getRight() 513 { 514 return _right; 515 } 516 517 @SuppressWarnings("unchecked") 518 @Override 519 public void index(ZoneItem zoneItem, SolrInputDocument document) 520 { 521 for (String id : _parametersToIndex) 522 { 523 ModelAwareDataHolder dataHolder = zoneItem.getServiceParameters(); 524 if (dataHolder.hasValue(id)) 525 { 526 String language = zoneItem.getZone().getPage().getSitemapName(); 527 Object value = dataHolder.getValue(id); 528 529 ElementDefinition parameter = (ElementDefinition) _modelItems.get(id); 530 ElementType type = parameter.getType(); 531 532 if (parameter.isMultiple()) 533 { 534 for (Object singleValue : (Object[]) value) 535 { 536 SolrContentIndexer.indexFulltextValue(document, type.toString(singleValue), language); 537 } 538 } 539 else 540 { 541 SolrContentIndexer.indexFulltextValue(document, type.toString(value), language); 542 } 543 } 544 } 545 } 546 547 /** 548 * Parse an i18n text. 549 * @param config the configuration to use. 550 * @param name the child name. 551 * @return the i18n text. 552 */ 553 protected I18nizableText _parseI18nizableText(Configuration config, String name) 554 { 555 return I18nizableText.parseI18nizableText(config.getChild(name), "plugin." + _pluginName, ""); 556 } 557 558 private String _configureThumbnail(Configuration valueConf, String defaultImage) 559 { 560 String value = valueConf.getValue(null); 561 if (value == null) 562 { 563 return defaultImage; 564 } 565 else 566 { 567 String pluginName = valueConf.getAttribute("plugin", this._pluginName); 568 return "/plugins/" + pluginName + "/resources/" + value; 569 } 570 } 571 572 /** 573 * Configure the dimensions of the dialog box 574 * @param configuration the global configuration 575 * @param dimensionName the name of the dimension to configure 576 * @return the value of the dimension 577 * @throws ConfigurationException if configuration is invalid 578 */ 579 protected Integer configureDialogBoxDimension(Configuration configuration, String dimensionName) throws ConfigurationException 580 { 581 Configuration dimensionConfiguration = configuration.getChild("dialogBox").getChild(dimensionName, false); 582 if (dimensionConfiguration != null) 583 { 584 return dimensionConfiguration.getValueAsInteger(); 585 } 586 else 587 { 588 return null; 589 } 590 } 591 592 /** 593 * Configure the script 594 * @param configuration the global configuration 595 * @return The script created 596 * @throws ConfigurationException if configuration is invalid 597 */ 598 protected Script _configureScript(Configuration configuration) throws ConfigurationException 599 { 600 List<ScriptFile> scriptsImports = _configureImports(configuration.getChild("scripts")); 601 List<ScriptFile> cssImports = _configureImports(configuration.getChild("css")); 602 String jsClassName = _configureClass(configuration.getChild("action")); 603 604 return new Script(this.getId(), jsClassName, scriptsImports, cssImports, new HashMap<>()); 605 } 606 607 608 /** 609 * Configure the js class name 610 * @param configuration The configuration on action tag 611 * @return The js class name 612 * @throws ConfigurationException If an error occurs 613 */ 614 protected String _configureClass(Configuration configuration) throws ConfigurationException 615 { 616 String jsClassName = configuration.getAttribute("class", ""); 617 if (getLogger().isDebugEnabled()) 618 { 619 getLogger().debug("Js class configured is '" + jsClassName + "'"); 620 } 621 return jsClassName; 622 } 623 624 /** 625 * Configure the import part 626 * @param configuration The imports configuration 627 * @return The set of the complete url of imported file 628 * @throws ConfigurationException If an error occurs 629 */ 630 protected List<ScriptFile> _configureImports(Configuration configuration) throws ConfigurationException 631 { 632 return ConfigurationHelper.parsePluginResourceList(configuration, getPluginName(), getLogger()); 633 } 634 635 public Collection<ModelItem> getModelItems() 636 { 637 return _modelItems.values(); 638 } 639 640 public Map<String, ModelItem> getParameters() 641 { 642 return _modelItems; 643 } 644 645 public View getView() 646 { 647 return _view; 648 } 649}