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