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