001/* 002 * Copyright 2018 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.runtime.model; 017 018import java.lang.reflect.Array; 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023 024import org.apache.avalon.framework.configuration.Configuration; 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.cocoon.ProcessingException; 027import org.apache.commons.lang3.tuple.ImmutablePair; 028import org.apache.commons.lang3.tuple.Pair; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032import org.ametys.runtime.config.Config; 033import org.ametys.runtime.config.DisableCondition; 034import org.ametys.runtime.config.DisableConditions; 035import org.ametys.runtime.i18n.I18nizableText; 036import org.ametys.runtime.model.exception.BadItemTypeException; 037import org.ametys.runtime.model.exception.UnknownTypeException; 038import org.ametys.runtime.model.type.DataContext; 039import org.ametys.runtime.model.type.ElementType; 040import org.ametys.runtime.model.type.ModelItemType; 041import org.ametys.runtime.parameter.Validator; 042import org.ametys.runtime.plugin.ExtensionPoint; 043 044/** 045 * The definition of a single model item (parameter, attribute) 046 * @param <T> Type of the element value 047 */ 048public class ElementDefinition<T> extends AbstractModelItem 049{ 050 /** config type for default values */ 051 public static final String CONFIG_DEFAULT_VALUE_TYPE = "config"; 052 053 /** The definition logger */ 054 protected Logger _logger = LoggerFactory.getLogger(this.getClass()); 055 056 private String _pluginName; 057 private ElementType<T> _type; 058 private String _widget; 059 private Map<String, I18nizableText> _widgetParams; 060 private Enumerator<T> _enumerator; 061 private String _customEnumerator; 062 private Configuration _enumeratorConfiguration; 063 private Validator _validator; 064 private String _customValidator; 065 private Configuration _validatorConfiguration; 066 private boolean _isMultiple; 067 private DisableConditions _disableConditions; 068 private List<Pair<String, Object>> _parsedDefaultValues; 069 070 /** 071 * Default constructor. 072 */ 073 public ElementDefinition() 074 { 075 super(); 076 } 077 078 /** 079 * Constructor used to create simple models and items 080 * @param name the name of the definition 081 * @param isMultiple the element multiple status 082 * @param type the type of the definition 083 */ 084 public ElementDefinition(String name, boolean isMultiple, ElementType<T> type) 085 { 086 super(name); 087 _type = type; 088 _isMultiple = isMultiple; 089 } 090 091 /** 092 * Constructor by copying an existing {@link ElementDefinition}. 093 * @param definitionToCopy The {@link ElementDefinition} to copy 094 */ 095 public ElementDefinition(ElementDefinition<T> definitionToCopy) 096 { 097 super(definitionToCopy); 098 099 // ElementDefinition 100 setPluginName(definitionToCopy.getPluginName()); 101 setType(definitionToCopy.getType()); 102 103 // Widget 104 setWidget(definitionToCopy.getWidget()); 105 setWidgetParameters(definitionToCopy.getWidgetParameters()); 106 107 // Enumerator 108 setEnumerator(definitionToCopy.getEnumerator()); 109 setCustomEnumerator(definitionToCopy.getCustomEnumerator()); 110 setEnumeratorConfiguration(definitionToCopy.getEnumeratorConfiguration()); 111 112 // Validator 113 setValidator(definitionToCopy.getValidator()); 114 setCustomValidator(definitionToCopy.getCustomValidator()); 115 setValidatorConfiguration(definitionToCopy.getValidatorConfiguration()); 116 117 // Other 118 setParsedDefaultValues(definitionToCopy._getParsedDefaultValues()); 119 setMultiple(definitionToCopy.isMultiple()); 120 setDisableConditions(definitionToCopy.getDisableConditions()); 121 } 122 123 /** 124 * Retrieves the name of the plugin declaring this element. 125 * @return the plugin name. 126 */ 127 public String getPluginName() 128 { 129 return _pluginName; 130 } 131 132 /** 133 * Set the name of the plugin declaring this element. 134 * @param pluginName the plugin name. 135 */ 136 public void setPluginName(String pluginName) 137 { 138 _pluginName = pluginName; 139 } 140 141 @Override 142 public ElementType<T> getType() 143 { 144 return _type; 145 } 146 147 @SuppressWarnings("unchecked") 148 public void setType(ModelItemType type) 149 { 150 if (type instanceof ElementType) 151 { 152 _type = (ElementType<T>) type; 153 } 154 else 155 { 156 throw new IllegalArgumentException("Unable to set the type '" + type.getClass() + "' on the element type '" + getName() + "'"); 157 } 158 } 159 160 /** 161 * Retrieves the widget to use for rendering. 162 * @return the widget or <code>null</code> if none is defined. 163 */ 164 public String getWidget() 165 { 166 return _widget; 167 } 168 169 /** 170 * Set the widget. 171 * @param widget the widget. 172 */ 173 public void setWidget(String widget) 174 { 175 _widget = widget; 176 } 177 178 /** 179 * Get the widget's parameters 180 * @return the widget's parameters 181 */ 182 public Map<String, I18nizableText> getWidgetParameters() 183 { 184 return _widgetParams; 185 } 186 187 /** 188 * Set the widget's parameters 189 * @param params the parameters to set 190 */ 191 public void setWidgetParameters (Map<String, I18nizableText> params) 192 { 193 _widgetParams = params; 194 } 195 196 /** 197 * Retrieves the enumerator. 198 * @return the enumerator or <code>null</code> if none is defined. 199 */ 200 public Enumerator<T> getEnumerator() 201 { 202 return _enumerator; 203 } 204 205 /** 206 * Set the enumerator. 207 * @param enumerator the enumerator. 208 */ 209 public void setEnumerator(Enumerator<T> enumerator) 210 { 211 _enumerator = enumerator; 212 } 213 214 /** 215 * Retrieves the custom enumerator's class name 216 * @return the custom enumerator's class name 217 */ 218 public String getCustomEnumerator() 219 { 220 return _customEnumerator; 221 } 222 223 /** 224 * Set the custom enumerator's class name 225 * @param customEnumerator the custom enumerator's class name 226 */ 227 public void setCustomEnumerator(String customEnumerator) 228 { 229 this._customEnumerator = customEnumerator; 230 } 231 232 /** 233 * Retrieves the custom enumerator's configuration 234 * @return the custom enumerator's configuration 235 */ 236 public Configuration getEnumeratorConfiguration() 237 { 238 return _enumeratorConfiguration; 239 } 240 241 /** 242 * Set the custom enumerator's configuration 243 * @param enumeratorConfiguration the custom enumerator's configuration 244 */ 245 public void setEnumeratorConfiguration(Configuration enumeratorConfiguration) 246 { 247 _enumeratorConfiguration = enumeratorConfiguration; 248 } 249 250 /** 251 * Retrieves the validator. 252 * @return the validator or <code>null</code> if none is defined. 253 */ 254 public Validator getValidator() 255 { 256 return _validator; 257 } 258 259 /** 260 * Set the validator. 261 * @param validator the validator. 262 */ 263 public void setValidator(Validator validator) 264 { 265 _validator = validator; 266 } 267 268 /** 269 * Retrieves the custom validator's class name 270 * @return the custom validator's class name 271 */ 272 public String getCustomValidator() 273 { 274 return _customValidator; 275 } 276 277 /** 278 * Set the custom validator's class name 279 * @param customValidator the custom validator's class name 280 */ 281 public void setCustomValidator(String customValidator) 282 { 283 this._customValidator = customValidator; 284 } 285 286 /** 287 * Retrieves the custom validator's configuraiton 288 * @return the custom validator's configuration 289 */ 290 public Configuration getValidatorConfiguration() 291 { 292 return _validatorConfiguration; 293 } 294 295 /** 296 * Set the custom validator's configuration 297 * @param validatorConfiguration the custom validator's configuration 298 */ 299 public void setValidatorConfiguration(Configuration validatorConfiguration) 300 { 301 _validatorConfiguration = validatorConfiguration; 302 } 303 304 /** 305 * Retrieves the default value, as an object corresponding to the definition's type and cardinality 306 * Retrieves <code>null</code> if no default value is defined for this definition 307 * @param <X> The type of the default value 308 * @return the default value. 309 */ 310 @SuppressWarnings("unchecked") 311 public <X> X getDefaultValue() 312 { 313 if (_parsedDefaultValues != null) 314 { 315 if (isMultiple()) 316 { 317 Object defaultValuesAsArray = Array.newInstance(getType().getManagedClass(), _parsedDefaultValues.size()); 318 int index = 0; 319 for (Pair<String, Object> parsedDefaultValue : _parsedDefaultValues) 320 { 321 // Compute the parsed default value according to the default value type 322 T defaultValue = _getDefaultValue(parsedDefaultValue.getLeft(), parsedDefaultValue.getRight()); 323 Array.set(defaultValuesAsArray, index, defaultValue); 324 index++; 325 } 326 327 return (X) defaultValuesAsArray; 328 } 329 else 330 { 331 if (!_parsedDefaultValues.isEmpty()) 332 { 333 if (_parsedDefaultValues.size() > 1) 334 { 335 _logger.warn("the data '" + this + "' is single, only the first declared default value will be used"); 336 } 337 338 // Compute the parsed default value according to the default value type 339 Pair<String, Object> parsedDefaultValue = _parsedDefaultValues.get(0); 340 return (X) _getDefaultValue(parsedDefaultValue.getLeft(), parsedDefaultValue.getRight()); 341 } 342 else 343 { 344 return null; 345 } 346 } 347 } 348 else 349 { 350 return null; 351 } 352 } 353 354 /** 355 * Retrieves the default value from the parsed one, according to the type of the default value 356 * @param parsedDefaultValue the parsed default value (can be an {@link I18nizableText}, a config parameter name, ... depending on the default value type) 357 * @param defaultValueType the type of the default value 358 * @return the default value. 359 */ 360 @SuppressWarnings("unchecked") 361 protected T _getDefaultValue(String defaultValueType, Object parsedDefaultValue) 362 { 363 if (CONFIG_DEFAULT_VALUE_TYPE.equals(defaultValueType)) 364 { 365 assert parsedDefaultValue instanceof String; 366 return Config.getInstance().getValue((String) parsedDefaultValue); 367 } 368 else 369 { 370 return (T) parsedDefaultValue; 371 } 372 } 373 374 /** 375 * Set the parsed default values. 376 * If the definition is not multiple, the list should contain only one element 377 * A parsed default value is described by its type and an object depending on the type 378 * @param parsedDefaultValues the parsed default values. 379 */ 380 public void setParsedDefaultValues(List<Pair<String, Object>> parsedDefaultValues) 381 { 382 _parsedDefaultValues = parsedDefaultValues; 383 } 384 385 /** 386 * Set a default value to the definition 387 * The default value is single, classic default value 388 * @param defaultValue the default value to set 389 */ 390 public void setDefaultValue(T defaultValue) 391 { 392 _parsedDefaultValues = List.of(new ImmutablePair<>(null, defaultValue)); 393 } 394 395 /** 396 * Retrieves the parsed default values 397 * @return the parsed default values 398 */ 399 protected List<Pair<String, Object>> _getParsedDefaultValues() 400 { 401 return _parsedDefaultValues; 402 } 403 404 /** 405 * Test if the element is multiple. 406 * @return <code>true</code> if the metadata is multiple. 407 */ 408 public boolean isMultiple() 409 { 410 return _isMultiple; 411 } 412 413 /** 414 * Set the element multiple status. 415 * @param isMultiple the element multiple status. 416 */ 417 public void setMultiple(boolean isMultiple) 418 { 419 _isMultiple = isMultiple; 420 } 421 422 /** 423 * Retrieves the disable condition. 424 * @return the disable condition or <code>null</code> if none is defined. 425 */ 426 public DisableConditions getDisableConditions() 427 { 428 return _disableConditions; 429 } 430 431 /** 432 * Sets the disable condition. 433 * @param disableConditions the disable condition. 434 */ 435 public void setDisableConditions(DisableConditions disableConditions) 436 { 437 _disableConditions = disableConditions; 438 } 439 440 @Override 441 protected Map<String, Object> _toJSON(DefinitionContext context) throws ProcessingException 442 { 443 Map<String, Object> result = super._toJSON(context); 444 445 result.put("plugin", getPluginName()); 446 result.put("multiple", isMultiple()); 447 448 if (getType() != null) 449 { 450 result.put("type", getType().getId()); 451 result.put("default-value", getType().valueToJSONForClient(getDefaultValue(), DataContext.newInstance())); 452 } 453 454 455 if (getValidator() != null) 456 { 457 result.put("validation", getValidator().getConfiguration()); 458 } 459 460 if (getEnumerator() != null) 461 { 462 List<Map<String, Object>> enumeration = new ArrayList<>(); 463 464 try 465 { 466 Map<T, I18nizableText> entries = getEnumerator().getTypedEntries(); 467 for (Map.Entry<T, I18nizableText> entry : entries.entrySet()) 468 { 469 Map<String, Object> option = new HashMap<>(); 470 option.put("value", getType().valueToJSONForClient(entry.getKey(), DataContext.newInstance())); 471 option.put("label", entry.getValue()); 472 enumeration.add(option); 473 } 474 } 475 catch (Exception e) 476 { 477 throw new ProcessingException("Unable to enumerate entries with enumerator: " + getEnumerator(), e); 478 } 479 480 result.put("enumeration", enumeration); 481 result.put("enumerationConfig", getEnumerator().getConfiguration()); 482 } 483 484 result.put("widget", getWidget()); 485 486 Map<String, I18nizableText> widgetParameters = getWidgetParameters(); 487 if (widgetParameters != null && !widgetParameters.isEmpty()) 488 { 489 result.put("widget-params", widgetParameters); 490 } 491 492 if (getDisableConditions() != null) 493 { 494 result.put("disableCondition", _disableConditionstoJSON(getDisableConditions())); 495 } 496 497 return result; 498 } 499 500 /** 501 * Converts the definition's disable conditions in a JSON map 502 * @param disableConditions the disable conditions to convert 503 * @return The definition's disable conditions as a JSON map 504 */ 505 private Map<String, Object> _disableConditionstoJSON(DisableConditions disableConditions) 506 { 507 Map<String, Object> map = new HashMap<>(); 508 509 // Handle simple conditions 510 List<Map<String, String>> disableConditionList = new ArrayList<>(); 511 map.put("condition", disableConditionList); 512 for (DisableCondition disableCondition : disableConditions.getConditions()) 513 { 514 Map<String, String> disableConditionAsMap = _disableConditiontoJSON(disableCondition); 515 disableConditionList.add(disableConditionAsMap); 516 } 517 518 // Handle nested conditions 519 List<Map<String, Object>> disableConditionsList = new ArrayList<>(); 520 map.put("conditions", disableConditionsList); 521 for (DisableConditions subDisableConditions : disableConditions.getSubConditions()) 522 { 523 Map<String, Object> disableConditionsAsMap = _disableConditionstoJSON(subDisableConditions); 524 disableConditionsList.add(disableConditionsAsMap); 525 } 526 527 // Handle type 528 map.put("type", disableConditions.getAssociationType().toString().toLowerCase()); 529 530 return map; 531 } 532 533 private static Map<String, String> _disableConditiontoJSON(DisableCondition disableCondition) 534 { 535 Map<String, String> map = new HashMap<>(); 536 map.put("id", disableCondition.getId()); 537 map.put("operator", disableCondition.getOperator().toString().toLowerCase()); 538 map.put("value", disableCondition.getValue()); 539 return map; 540 } 541 542 /** 543 * Creates an {@link ElementDefinition} 544 * @param name the definition's name 545 * @param isMultiple the definition's cardinality 546 * @param typeId the definition's type identifier 547 * @param availableTypesExtensionPoint the role of the extension point containing all available types for this {@link ElementDefinition} 548 * @return the created {@link ElementDefinition} 549 * @throws UnknownTypeException if the given type identifier is not available in the extension point 550 * @throws BadItemTypeException if the given type identifier can not be used for an {@link ElementDefinition} 551 * @throws ServiceException if an error occurs while getting the extension point of available types 552 */ 553 @SuppressWarnings("unchecked") 554 public static ElementDefinition of(String name, boolean isMultiple, String typeId, String availableTypesExtensionPoint) throws UnknownTypeException, BadItemTypeException, ServiceException 555 { 556 ExtensionPoint<ModelItemType> availableTypes = (ExtensionPoint<ModelItemType>) __serviceManager.lookup(availableTypesExtensionPoint); 557 if (!availableTypes.hasExtension(typeId)) 558 { 559 throw new UnknownTypeException("The type '" + typeId + "' (used for data '" + name + "') is not available for the given extension point."); 560 } 561 else 562 { 563 ModelItemType type = availableTypes.getExtension(typeId); 564 if (!(type instanceof ElementType)) 565 { 566 throw new BadItemTypeException("The type '" + typeId + "' (used for data '" + name + "') can not be used for an element definition."); 567 } 568 else 569 { 570 return new ElementDefinition(name, isMultiple, (ElementType) type); 571 } 572 } 573 } 574}