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