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