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