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.util.ArrayList; 019import java.util.Collections; 020import java.util.HashMap; 021import java.util.HashSet; 022import java.util.List; 023import java.util.Map; 024import java.util.Objects; 025import java.util.Set; 026 027import org.apache.avalon.framework.component.Component; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.cocoon.ProcessingException; 030import org.apache.cocoon.xml.AttributesImpl; 031import org.xml.sax.ContentHandler; 032import org.xml.sax.SAXException; 033 034import org.ametys.core.util.XMLUtils; 035import org.ametys.runtime.config.DisableCondition; 036import org.ametys.runtime.config.DisableConditions; 037import org.ametys.runtime.i18n.I18nizableText; 038import org.ametys.runtime.model.checker.ItemCheckerDescriptor; 039 040/** 041 * Abstract class for model items 042 */ 043public abstract class AbstractModelItem implements ModelItem 044{ 045 /** The service manager */ 046 protected static ServiceManager __serviceManager; 047 048 private String _name; 049 private String _pluginName; 050 private I18nizableText _label; 051 private I18nizableText _description; 052 private Set<ItemCheckerDescriptor> _itemCheckers = new HashSet<>(); 053 private String _widget; 054 private Map<String, I18nizableText> _widgetParams; 055 private DisableConditions _disableConditions; 056 057 private Model _model; 058 private ModelItemGroup _parent; 059 060 private String _path; 061 062 /** 063 * Default constructor. 064 */ 065 public AbstractModelItem() 066 { 067 // Empty constructor 068 } 069 070 /** 071 * Constructor used to create simple models and items 072 * @param name the name of the item 073 */ 074 public AbstractModelItem(String name) 075 { 076 _name = name; 077 } 078 079 /** 080 * Constructor by copying an existing {@link AbstractModelItem}. 081 * @param modelItemToCopy The {@link AbstractModelItem} to copy 082 */ 083 public AbstractModelItem(AbstractModelItem modelItemToCopy) 084 { 085 setName(modelItemToCopy.getName()); 086 setPluginName(modelItemToCopy.getPluginName()); 087 setLabel(modelItemToCopy.getLabel()); 088 setDescription(modelItemToCopy.getDescription()); 089 for (ItemCheckerDescriptor itemChecker : modelItemToCopy.getItemCheckers()) 090 { 091 addItemChecker(itemChecker); 092 } 093 setModel(modelItemToCopy.getModel()); 094 setParent(modelItemToCopy.getParent()); 095 096 // Widget 097 setWidget(modelItemToCopy.getWidget()); 098 setWidgetParameters(modelItemToCopy.getWidgetParameters()); 099 100 setDisableConditions(modelItemToCopy.getDisableConditions()); 101 } 102 103 public String getName() 104 { 105 return _name; 106 } 107 108 public void setName(String name) 109 { 110 _name = name; 111 _path = null; 112 } 113 114 public String getPluginName() 115 { 116 return _pluginName; 117 } 118 119 public void setPluginName(String pluginName) 120 { 121 _pluginName = pluginName; 122 } 123 124 public I18nizableText getLabel() 125 { 126 return _label; 127 } 128 129 public void setLabel(I18nizableText label) 130 { 131 _label = label; 132 } 133 134 public I18nizableText getDescription() 135 { 136 return _description; 137 } 138 139 public void setDescription(I18nizableText description) 140 { 141 _description = description; 142 } 143 144 public void addItemChecker(ItemCheckerDescriptor itemChecker) 145 { 146 _itemCheckers.add(itemChecker); 147 } 148 149 public Set<ItemCheckerDescriptor> getItemCheckers() 150 { 151 return Collections.unmodifiableSet(_itemCheckers); 152 } 153 154 155 public String getWidget() 156 { 157 return _widget; 158 } 159 160 public void setWidget(String widget) 161 { 162 _widget = widget; 163 } 164 165 public Map<String, I18nizableText> getWidgetParameters() 166 { 167 return _widgetParams; 168 } 169 170 public void setWidgetParameters (Map<String, I18nizableText> params) 171 { 172 _widgetParams = params; 173 } 174 175 public DisableConditions getDisableConditions() 176 { 177 return _disableConditions; 178 } 179 180 public void setDisableConditions(DisableConditions disableConditions) 181 { 182 _disableConditions = disableConditions; 183 } 184 185 public String getPath() 186 { 187 if (_path != null) 188 { 189 return _path; 190 } 191 192 if (getName() == null) 193 { 194 return null; 195 } 196 197 StringBuilder path = new StringBuilder(); 198 199 ModelItemGroup parent = getParent(); 200 if (parent != null && parent.getPath() != null) 201 { 202 path.append(parent.getPath()).append(ITEM_PATH_SEPARATOR); 203 } 204 205 path.append(getName()); 206 _path = path.toString(); 207 return _path; 208 } 209 210 public Model getModel() 211 { 212 return _model; 213 } 214 215 public void setModel(Model model) 216 { 217 _model = model; 218 } 219 220 public ModelItemGroup getParent() 221 { 222 return _parent; 223 } 224 225 public void setParent(ModelItemGroup parent) 226 { 227 _parent = parent; 228 } 229 230 public Map<String, Object> toJSON(DefinitionContext context) throws ProcessingException 231 { 232 if (_shouldJSONBeEmpty(context)) 233 { 234 return Map.of(); 235 } 236 else 237 { 238 return _toJSON(context); 239 } 240 } 241 242 /** 243 * Converts the model item in a JSON map 244 * @param context the context of the definition 245 * @return The model item as a JSON map 246 * @throws ProcessingException If an error occurs when converting the model item 247 */ 248 protected Map<String, Object> _toJSON(DefinitionContext context) throws ProcessingException 249 { 250 Map<String, Object> result = new HashMap<>(); 251 252 result.put("name", getName()); 253 result.put("plugin", getPluginName()); 254 result.put("label", getLabel()); 255 result.put("description", getDescription()); 256 result.put("path", getPath()); 257 258 if (!getItemCheckers().isEmpty()) 259 { 260 List<Map<String, Object>> checkers2json = new ArrayList<>(); 261 for (ItemCheckerDescriptor paramChecker : getItemCheckers()) 262 { 263 checkers2json.add(paramChecker.toJSON()); 264 } 265 266 result.put("field-checker", checkers2json); 267 } 268 269 result.put("widget", getWidget()); 270 271 Map<String, I18nizableText> widgetParameters = getWidgetParameters(); 272 if (widgetParameters != null && !widgetParameters.isEmpty()) 273 { 274 result.put("widget-params", widgetParameters); 275 } 276 277 278 if (getDisableConditions() != null) 279 { 280 result.put("disableCondition", _disableConditionstoJSON(getDisableConditions())); 281 } 282 283 return result; 284 } 285 286 /** 287 * Converts the definition's disable conditions in a JSON map 288 * @param disableConditions the disable conditions to convert 289 * @return The definition's disable conditions as a JSON map 290 */ 291 private Map<String, Object> _disableConditionstoJSON(DisableConditions disableConditions) 292 { 293 Map<String, Object> map = new HashMap<>(); 294 295 // Handle simple conditions 296 List<Map<String, String>> disableConditionList = new ArrayList<>(); 297 map.put("condition", disableConditionList); 298 for (DisableCondition disableCondition : disableConditions.getConditions()) 299 { 300 Map<String, String> disableConditionAsMap = _disableConditiontoJSON(disableCondition); 301 disableConditionList.add(disableConditionAsMap); 302 } 303 304 // Handle nested conditions 305 List<Map<String, Object>> disableConditionsList = new ArrayList<>(); 306 map.put("conditions", disableConditionsList); 307 for (DisableConditions subDisableConditions : disableConditions.getSubConditions()) 308 { 309 Map<String, Object> disableConditionsAsMap = _disableConditionstoJSON(subDisableConditions); 310 disableConditionsList.add(disableConditionsAsMap); 311 } 312 313 // Handle type 314 map.put("type", disableConditions.getAssociationType().toString().toLowerCase()); 315 316 return map; 317 } 318 319 private static Map<String, String> _disableConditiontoJSON(DisableCondition disableCondition) 320 { 321 Map<String, String> map = new HashMap<>(); 322 map.put("id", disableCondition.getId()); 323 map.put("operator", disableCondition.getOperator().toString().toLowerCase()); 324 map.put("value", disableCondition.getValue()); 325 return map; 326 } 327 328 /** 329 * Checks if the current definition JSON conversion should return an empty map 330 * @param context the context of the definition 331 * @return <code>true</code> if the JSON conversion should return an empty map, <code>false</code> otherwise 332 */ 333 protected boolean _shouldJSONBeEmpty(DefinitionContext context) 334 { 335 return false; 336 } 337 338 @SuppressWarnings("static-access") 339 public void toSAX(ContentHandler contentHandler, DefinitionContext context) throws SAXException 340 { 341 if (!getItemCheckers().isEmpty()) 342 { 343 for (ItemCheckerDescriptor paramChecker : getItemCheckers()) 344 { 345 XMLUtils.startElement(contentHandler, "field-checker"); 346 paramChecker.toSAX(contentHandler); 347 XMLUtils.endElement(contentHandler, "field-checker"); 348 } 349 } 350 351 _widgetToSAX(contentHandler, context); 352 353 if (getDisableConditions() != null) 354 { 355 _disableConditionsToSAX(contentHandler, getDisableConditions()); 356 } 357 } 358 359 /** 360 * Generates SAX events for the model item's widget 361 * @param contentHandler the {@link ContentHandler} that will receive the SAX events 362 * @param context the context of the definition 363 * @throws SAXException if an error occurs during the SAX events generation 364 */ 365 @SuppressWarnings("static-access") 366 protected void _widgetToSAX(ContentHandler contentHandler, DefinitionContext context) throws SAXException 367 { 368 XMLUtils.createElementIfNotNull(contentHandler, "widget", getWidget()); 369 370 Map<String, I18nizableText> widgetParameters = getWidgetParameters(); 371 if (widgetParameters != null && !widgetParameters.isEmpty()) 372 { 373 XMLUtils.startElement(contentHandler, "widget-params"); 374 375 for (Map.Entry<String, I18nizableText> param : widgetParameters.entrySet()) 376 { 377 _widgetParameterToSAX(contentHandler, param.getKey(), param.getValue(), context); 378 } 379 380 XMLUtils.endElement(contentHandler, "widget-params"); 381 } 382 } 383 384 /** 385 * Generates SAX events for the given widget parameter 386 * @param contentHandler the {@link ContentHandler} that will receive the SAX events 387 * @param parameterName the name of the parameter 388 * @param parameterValue the value of the parameter 389 * @param context the context of the definition 390 * @throws SAXException if an error occurs during the SAX events generation 391 */ 392 @SuppressWarnings("static-access") 393 protected void _widgetParameterToSAX(ContentHandler contentHandler, String parameterName, I18nizableText parameterValue, DefinitionContext context) throws SAXException 394 { 395 AttributesImpl paramAttributes = new AttributesImpl(); 396 paramAttributes.addCDATAAttribute("name", parameterName); 397 398 XMLUtils.startElement(contentHandler, "param", paramAttributes); 399 parameterValue.toSAX(contentHandler); 400 XMLUtils.endElement(contentHandler, "param"); 401 } 402 403 @SuppressWarnings("static-access") 404 private void _disableConditionsToSAX(ContentHandler contentHandler, DisableConditions disableConditions) throws SAXException 405 { 406 AttributesImpl attributes = new AttributesImpl(); 407 attributes.addCDATAAttribute("type", disableConditions.getAssociationType().toString().toLowerCase()); 408 XMLUtils.startElement(contentHandler, "disable-conditions", attributes); 409 410 // Handle simple conditions 411 XMLUtils.startElement(contentHandler, "conditions"); 412 for (DisableCondition disableCondition : disableConditions.getConditions()) 413 { 414 _disableConditionToSAX(contentHandler, disableCondition); 415 } 416 XMLUtils.endElement(contentHandler, "conditions"); 417 418 // Handle nested conditions 419 XMLUtils.startElement(contentHandler, "nested-conditions"); 420 for (DisableConditions subDisableConditions : disableConditions.getSubConditions()) 421 { 422 _disableConditionsToSAX(contentHandler, subDisableConditions); 423 } 424 XMLUtils.endElement(contentHandler, "nested-conditions"); 425 426 XMLUtils.endElement(contentHandler, "disable-conditions"); 427 } 428 429 @SuppressWarnings("static-access") 430 private void _disableConditionToSAX(ContentHandler contentHandler, DisableCondition disableCondition) throws SAXException 431 { 432 AttributesImpl attributes = new AttributesImpl(); 433 attributes.addCDATAAttribute("id", disableCondition.getId()); 434 attributes.addCDATAAttribute("operator", disableCondition.getOperator().toString().toLowerCase()); 435 436 XMLUtils.createElement(contentHandler, "condition", attributes, disableCondition.getValue()); 437 } 438 439 public int compareTo(ModelItem item) 440 { 441 if (item == null) 442 { 443 return 1; 444 } 445 446 String name = getName(); 447 if (name != null) 448 { 449 return name.compareTo(item.getName()); 450 } 451 452 I18nizableText label = getLabel(); 453 if (label != null) 454 { 455 I18nizableText otherLabel = item.getLabel(); 456 if (otherLabel == null) 457 { 458 return 1; 459 } 460 461 return label.toString().compareTo(otherLabel.toString()); 462 } 463 464 return 0; 465 } 466 467 @Override 468 public boolean equals(Object obj) 469 { 470 if (obj == null || !getClass().equals(obj.getClass())) 471 { 472 return false; 473 } 474 475 ModelItem item = (ModelItem) obj; 476 477 // The item's model can be null 478 if (getModel() != item.getModel()) 479 { 480 if (getModel() == null ^ item.getModel() == null) 481 { 482 return false; 483 } 484 485 if (!Objects.equals(getModel().getFamilyId(), item.getModel().getFamilyId()) || !Objects.equals(getModel().getId(), item.getModel().getId())) 486 { 487 return false; 488 } 489 } 490 491 if (getPath() != null || item.getPath() != null) 492 { 493 return Objects.equals(getPath(), item.getPath()); 494 } 495 496 if (getLabel() != null || item.getLabel() != null) 497 { 498 return Objects.equals(getLabel(), item.getLabel()); 499 } 500 501 return false; 502 } 503 504 @Override 505 public int hashCode() 506 { 507 if (getPath() != null) 508 { 509 return getPath().hashCode(); 510 } 511 512 return getLabel().hashCode(); 513 } 514 515 @Override 516 public String toString() 517 { 518 if (getPath() != null) 519 { 520 return getPath(); 521 } 522 523 return getLabel().toString(); 524 } 525 526 /** 527 * Set the service manager 528 * The {@link ServiceManager} is used in the model items creation methods to get the model item type. 529 * {@link ModelItem} is not a {@link Component} and can't have a {@link ServiceManager} itself. Another {@link Component} has to set it 530 * @param manager the service manager to set 531 */ 532 public static void setServiceManager(ServiceManager manager) 533 { 534 __serviceManager = manager; 535 } 536}