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