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