001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (C) Alkacon Software (https://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.acacia.shared; 029 030import org.opencms.acacia.shared.CmsEntityChangeEvent.ChangeType; 031 032import java.io.Serializable; 033import java.util.ArrayList; 034import java.util.HashMap; 035import java.util.List; 036import java.util.Map; 037import java.util.Map.Entry; 038 039import com.google.common.collect.Lists; 040import com.google.gwt.event.logical.shared.HasValueChangeHandlers; 041import com.google.gwt.event.logical.shared.ValueChangeEvent; 042import com.google.gwt.event.logical.shared.ValueChangeHandler; 043import com.google.gwt.event.shared.EventHandler; 044import com.google.gwt.event.shared.GwtEvent; 045import com.google.gwt.event.shared.HandlerRegistration; 046import com.google.gwt.event.shared.SimpleEventBus; 047 048/** 049 * Serializable entity implementation.<p> 050 */ 051public class CmsEntity implements HasValueChangeHandlers<CmsEntity>, Serializable { 052 053 /** 054 * Handles child entity changes.<p> 055 */ 056 protected class EntityChangeHandler implements ValueChangeHandler<CmsEntity> { 057 058 /** 059 * @see com.google.gwt.event.logical.shared.ValueChangeHandler#onValueChange(com.google.gwt.event.logical.shared.ValueChangeEvent) 060 */ 061 public void onValueChange(ValueChangeEvent<CmsEntity> event) { 062 063 ChangeType type = ((CmsEntityChangeEvent)event).getChangeType(); 064 fireChange(type); 065 } 066 } 067 068 /** The serial version id. */ 069 private static final long serialVersionUID = -6933931178070025267L; 070 071 /** The entity attribute values. */ 072 private Map<String, List<CmsEntity>> m_entityAttributes; 073 074 /** The entity id. */ 075 private String m_id; 076 077 /** The simple attribute values. */ 078 private Map<String, List<String>> m_simpleAttributes; 079 080 /** The type name. */ 081 private String m_typeName; 082 083 /** The event bus. */ 084 private transient SimpleEventBus m_eventBus; 085 086 /** The child entites change handler. */ 087 private transient EntityChangeHandler m_childChangeHandler = new EntityChangeHandler(); 088 089 /** The handler registrations. */ 090 private transient Map<String, HandlerRegistration> m_changeHandlerRegistry; 091 092 /** 093 * Constructor.<p> 094 * 095 * @param id the entity id/URI 096 * @param typeName the entity type name 097 */ 098 public CmsEntity(String id, String typeName) { 099 100 this(); 101 m_id = id; 102 m_typeName = typeName; 103 } 104 105 /** 106 * Constructor. For serialization only.<p> 107 */ 108 protected CmsEntity() { 109 110 m_simpleAttributes = new HashMap<String, List<String>>(); 111 m_entityAttributes = new HashMap<String, List<CmsEntity>>(); 112 m_changeHandlerRegistry = new HashMap<String, HandlerRegistration>(); 113 } 114 115 /** 116 * Returns the value of a simple attribute for the given path or <code>null</code>, if the value does not exist.<p> 117 * 118 * @param entity the entity to get the value from 119 * @param pathElements the path elements 120 * 121 * @return the value 122 */ 123 public static String getValueForPath(CmsEntity entity, String[] pathElements) { 124 125 String result = null; 126 if ((pathElements != null) && (pathElements.length >= 1)) { 127 String attributeName = pathElements[0]; 128 int index = CmsContentDefinition.extractIndex(attributeName); 129 if (index > 0) { 130 index--; 131 } 132 attributeName = entity.getTypeName() + "/" + CmsContentDefinition.removeIndex(attributeName); 133 CmsEntityAttribute attribute = entity.getAttribute(attributeName); 134 if (!((attribute == null) || (attribute.isComplexValue() && (pathElements.length == 1)))) { 135 if (attribute.isSimpleValue()) { 136 if ((pathElements.length == 1) && (attribute.getValueCount() > 0)) { 137 List<String> values = attribute.getSimpleValues(); 138 result = values.get(index); 139 } 140 } else if (attribute.getValueCount() > (index)) { 141 String[] childPathElements = new String[pathElements.length - 1]; 142 for (int i = 1; i < pathElements.length; i++) { 143 childPathElements[i - 1] = pathElements[i]; 144 } 145 List<CmsEntity> values = attribute.getComplexValues(); 146 result = getValueForPath(values.get(index), childPathElements); 147 } 148 } 149 } 150 return result; 151 } 152 153 /** 154 * Gets the list of values reachable from the given base object with the given path.<p> 155 * 156 * @param baseObject the base object (a CmsEntity or a string) 157 * @param pathComponents the path components 158 * @return the list of values for the given path (either of type String or CmsEntity) 159 */ 160 public static List<Object> getValuesForPath(Object baseObject, String[] pathComponents) { 161 162 List<Object> currentList = Lists.newArrayList(); 163 currentList.add(baseObject); 164 for (String pathComponent : pathComponents) { 165 List<Object> newList = Lists.newArrayList(); 166 for (Object element : currentList) { 167 newList.addAll(getValuesForPathComponent(element, pathComponent)); 168 } 169 currentList = newList; 170 } 171 return currentList; 172 } 173 174 /** 175 * Gets the values reachable from a given object (an entity or a string) with a single XPath component.<p> 176 * 177 * If entityOrString is a string, and pathComponent is "VALUE", a list containing only entityOrString is returned. 178 * Otherwise, entityOrString is assumed to be an entity, and the pathComponent is interpreted as a field of the entity 179 * (possibly with an index). 180 * 181 * @param entityOrString the entity or string from which to get the values for the given path component 182 * @param pathComponent the path component 183 * @return the list of reachable values 184 */ 185 public static List<Object> getValuesForPathComponent(Object entityOrString, String pathComponent) { 186 187 List<Object> result = Lists.newArrayList(); 188 if (pathComponent.equals("VALUE")) { 189 result.add(entityOrString); 190 } else { 191 if (entityOrString instanceof CmsEntity) { 192 CmsEntity entity = (CmsEntity)entityOrString; 193 boolean hasIndex = CmsContentDefinition.hasIndex(pathComponent); 194 int index = CmsContentDefinition.extractIndex(pathComponent); 195 if (index > 0) { 196 index--; 197 } 198 String attributeName = entity.getTypeName() + "/" + CmsContentDefinition.removeIndex(pathComponent); 199 CmsEntityAttribute attribute = entity.getAttribute(attributeName); 200 201 if (attribute != null) { 202 if (hasIndex) { 203 if (index < attribute.getValueCount()) { 204 if (attribute.isSimpleValue()) { 205 result.add(attribute.getSimpleValues().get(index)); 206 } else { 207 result.add(attribute.getComplexValues().get(index)); 208 } 209 } 210 } else { 211 if (attribute.isSimpleValue()) { 212 result.addAll(attribute.getSimpleValues()); 213 } else { 214 result.addAll(attribute.getComplexValues()); 215 } 216 } 217 } 218 } 219 } 220 return result; 221 } 222 223 /** 224 * Adds the given attribute value.<p> 225 * 226 * @param attributeName the attribute name 227 * @param value the attribute value 228 */ 229 public void addAttributeValue(String attributeName, CmsEntity value) { 230 231 if (m_simpleAttributes.containsKey(attributeName)) { 232 throw new RuntimeException("Attribute already exists with a simple type value."); 233 } 234 if (m_entityAttributes.containsKey(attributeName)) { 235 m_entityAttributes.get(attributeName).add(value); 236 } else { 237 List<CmsEntity> values = new ArrayList<CmsEntity>(); 238 values.add(value); 239 m_entityAttributes.put(attributeName, values); 240 } 241 registerChangeHandler(value); 242 fireChange(ChangeType.add); 243 } 244 245 /** 246 * Adds the given attribute value.<p> 247 * 248 * @param attributeName the attribute name 249 * @param value the attribute value 250 */ 251 public void addAttributeValue(String attributeName, String value) { 252 253 if (m_entityAttributes.containsKey(attributeName)) { 254 throw new RuntimeException("Attribute already exists with a entity type value."); 255 } 256 if (m_simpleAttributes.containsKey(attributeName)) { 257 m_simpleAttributes.get(attributeName).add(value); 258 } else { 259 List<String> values = new ArrayList<String>(); 260 values.add(value); 261 m_simpleAttributes.put(attributeName, values); 262 } 263 fireChange(ChangeType.add); 264 } 265 266 /** 267 * @see com.google.gwt.event.logical.shared.HasValueChangeHandlers#addValueChangeHandler(com.google.gwt.event.logical.shared.ValueChangeHandler) 268 */ 269 public HandlerRegistration addValueChangeHandler(ValueChangeHandler<CmsEntity> handler) { 270 271 return addHandler(handler, ValueChangeEvent.getType()); 272 } 273 274 /** 275 * Clones the given entity keeping all entity ids.<p> 276 * 277 * @return returns the cloned instance 278 */ 279 public CmsEntity cloneEntity() { 280 281 CmsEntity clone = new CmsEntity(getId(), getTypeName()); 282 for (CmsEntityAttribute attribute : getAttributes()) { 283 if (attribute.isSimpleValue()) { 284 List<String> values = attribute.getSimpleValues(); 285 for (String value : values) { 286 clone.addAttributeValue(attribute.getAttributeName(), value); 287 } 288 } else { 289 List<CmsEntity> values = attribute.getComplexValues(); 290 for (CmsEntity value : values) { 291 clone.addAttributeValue(attribute.getAttributeName(), value.cloneEntity()); 292 } 293 } 294 } 295 return clone; 296 } 297 298 /** 299 * Creates a deep copy of this entity.<p> 300 * 301 * @param entityId the id of the new entity, if <code>null</code> a generic id will be used 302 * 303 * @return the entity copy 304 */ 305 public CmsEntity createDeepCopy(String entityId) { 306 307 CmsEntity result = new CmsEntity(entityId, getTypeName()); 308 for (CmsEntityAttribute attribute : getAttributes()) { 309 if (attribute.isSimpleValue()) { 310 List<String> values = attribute.getSimpleValues(); 311 for (String value : values) { 312 result.addAttributeValue(attribute.getAttributeName(), value); 313 } 314 } else { 315 List<CmsEntity> values = attribute.getComplexValues(); 316 for (CmsEntity value : values) { 317 result.addAttributeValue(attribute.getAttributeName(), value.createDeepCopy(null)); 318 } 319 } 320 } 321 return result; 322 } 323 324 /** 325 * Ensures that the change event is also fired on child entity change.<p> 326 */ 327 public void ensureChangeHandlers() { 328 329 if (!m_changeHandlerRegistry.isEmpty()) { 330 for (HandlerRegistration reg : m_changeHandlerRegistry.values()) { 331 reg.removeHandler(); 332 } 333 m_changeHandlerRegistry.clear(); 334 } 335 for (List<CmsEntity> attr : m_entityAttributes.values()) { 336 for (CmsEntity child : attr) { 337 registerChangeHandler(child); 338 child.ensureChangeHandlers(); 339 } 340 } 341 } 342 343 /** 344 * @see java.lang.Object#equals(java.lang.Object) 345 */ 346 @Override 347 public boolean equals(Object obj) { 348 349 boolean result = false; 350 if (obj instanceof CmsEntity) { 351 CmsEntity test = (CmsEntity)obj; 352 if (m_simpleAttributes.keySet().equals(test.m_simpleAttributes.keySet()) 353 && m_entityAttributes.keySet().equals(test.m_entityAttributes.keySet())) { 354 result = true; 355 for (String attributeName : m_simpleAttributes.keySet()) { 356 if (!m_simpleAttributes.get(attributeName).equals(test.m_simpleAttributes.get(attributeName))) { 357 result = false; 358 break; 359 } 360 } 361 if (result) { 362 for (String attributeName : m_entityAttributes.keySet()) { 363 if (!m_entityAttributes.get(attributeName).equals(test.m_entityAttributes.get(attributeName))) { 364 result = false; 365 break; 366 } 367 } 368 } 369 } 370 } 371 return result; 372 } 373 374 /** 375 * @see com.google.gwt.event.shared.HasHandlers#fireEvent(com.google.gwt.event.shared.GwtEvent) 376 */ 377 public void fireEvent(GwtEvent<?> event) { 378 379 ensureHandlers().fireEventFromSource(event, this); 380 } 381 382 /** 383 * Returns an attribute.<p> 384 * 385 * @param attributeName the attribute name 386 * 387 * @return the attribute value 388 */ 389 public CmsEntityAttribute getAttribute(String attributeName) { 390 391 if (m_simpleAttributes.containsKey(attributeName)) { 392 return CmsEntityAttribute.createSimpleAttribute(attributeName, m_simpleAttributes.get(attributeName)); 393 } 394 if (m_entityAttributes.containsKey(attributeName)) { 395 return CmsEntityAttribute.createEntityAttribute(attributeName, m_entityAttributes.get(attributeName)); 396 } 397 return null; 398 } 399 400 /** 401 * Returns all entity attributes.<p> 402 * 403 * @return the entity attributes 404 */ 405 public List<CmsEntityAttribute> getAttributes() { 406 407 List<CmsEntityAttribute> result = new ArrayList<CmsEntityAttribute>(); 408 for (String name : m_simpleAttributes.keySet()) { 409 result.add(getAttribute(name)); 410 } 411 for (String name : m_entityAttributes.keySet()) { 412 result.add(getAttribute(name)); 413 } 414 return result; 415 } 416 417 /** 418 * Returns this or a child entity with the given id.<p> 419 * Will return <code>null</code> if no entity with the given id is present.<p> 420 * 421 * @param entityId the entity id 422 * 423 * @return the entity 424 */ 425 public CmsEntity getEntityById(String entityId) { 426 427 CmsEntity result = null; 428 if (m_id.equals(entityId)) { 429 result = this; 430 } else { 431 for (List<CmsEntity> children : m_entityAttributes.values()) { 432 for (CmsEntity child : children) { 433 result = child.getEntityById(entityId); 434 if (result != null) { 435 break; 436 } 437 } 438 if (result != null) { 439 break; 440 } 441 } 442 } 443 return result; 444 } 445 446 /** 447 * Returns the entity id.<p> 448 * 449 * @return the id 450 */ 451 public String getId() { 452 453 return m_id; 454 } 455 456 /** 457 * Returns the entity type name.<p> 458 * 459 * @return the entity type name 460 */ 461 public String getTypeName() { 462 463 return m_typeName; 464 } 465 466 /** 467 * Returns if the entity has the given attribute.<p> 468 * 469 * @param attributeName the attribute name 470 * 471 * @return <code>true</code> if the entity has the given attribute 472 */ 473 public boolean hasAttribute(String attributeName) { 474 475 return m_simpleAttributes.containsKey(attributeName) || m_entityAttributes.containsKey(attributeName); 476 } 477 478 /** 479 * @see java.lang.Object#hashCode() 480 */ 481 @Override 482 public int hashCode() { 483 484 return super.hashCode(); 485 } 486 487 /** 488 * Inserts a new attribute value at the given index.<p> 489 * 490 * @param attributeName the attribute name 491 * @param value the attribute value 492 * @param index the value index 493 */ 494 public void insertAttributeValue(String attributeName, CmsEntity value, int index) { 495 496 if (m_entityAttributes.containsKey(attributeName)) { 497 m_entityAttributes.get(attributeName).add(index, value); 498 } else { 499 setAttributeValue(attributeName, value); 500 } 501 registerChangeHandler(value); 502 fireChange(ChangeType.add); 503 } 504 505 /** 506 * Inserts a new attribute value at the given index.<p> 507 * 508 * @param attributeName the attribute name 509 * @param value the attribute value 510 * @param index the value index 511 */ 512 public void insertAttributeValue(String attributeName, String value, int index) { 513 514 if (m_simpleAttributes.containsKey(attributeName)) { 515 m_simpleAttributes.get(attributeName).add(index, value); 516 } else { 517 setAttributeValue(attributeName, value); 518 } 519 fireChange(ChangeType.add); 520 } 521 522 /** 523 * Removes the given attribute.<p> 524 * 525 * @param attributeName the attribute name 526 */ 527 public void removeAttribute(String attributeName) { 528 529 removeAttributeSilent(attributeName); 530 fireChange(ChangeType.remove); 531 } 532 533 /** 534 * Removes the attribute without triggering any change events.<p> 535 * 536 * @param attributeName the attribute name 537 */ 538 public void removeAttributeSilent(String attributeName) { 539 540 CmsEntityAttribute attr = getAttribute(attributeName); 541 if (attr != null) { 542 if (attr.isSimpleValue()) { 543 m_simpleAttributes.remove(attributeName); 544 } else { 545 for (CmsEntity child : attr.getComplexValues()) { 546 removeChildChangeHandler(child); 547 } 548 m_entityAttributes.remove(attributeName); 549 } 550 } 551 } 552 553 /** 554 * Removes a specific attribute value.<p> 555 * 556 * @param attributeName the attribute name 557 * @param index the value index 558 */ 559 public void removeAttributeValue(String attributeName, int index) { 560 561 if (m_simpleAttributes.containsKey(attributeName)) { 562 List<String> values = m_simpleAttributes.get(attributeName); 563 if ((values.size() == 1) && (index == 0)) { 564 removeAttributeSilent(attributeName); 565 } else { 566 values.remove(index); 567 } 568 } else if (m_entityAttributes.containsKey(attributeName)) { 569 List<CmsEntity> values = m_entityAttributes.get(attributeName); 570 if ((values.size() == 1) && (index == 0)) { 571 removeAttributeSilent(attributeName); 572 } else { 573 CmsEntity child = values.remove(index); 574 removeChildChangeHandler(child); 575 } 576 } 577 fireChange(ChangeType.remove); 578 } 579 580 /** 581 * Sets the given attribute value. Will remove all previous attribute values.<p> 582 * 583 * @param attributeName the attribute name 584 * @param value the attribute value 585 */ 586 public void setAttributeValue(String attributeName, CmsEntity value) { 587 588 // make sure there is no attribute value set 589 removeAttributeSilent(attributeName); 590 addAttributeValue(attributeName, value); 591 } 592 593 /** 594 * Sets the given attribute value at the given index.<p> 595 * 596 * @param attributeName the attribute name 597 * @param value the attribute value 598 * @param index the value index 599 */ 600 public void setAttributeValue(String attributeName, CmsEntity value, int index) { 601 602 if (m_simpleAttributes.containsKey(attributeName)) { 603 throw new RuntimeException("Attribute already exists with a simple type value."); 604 } 605 if (!m_entityAttributes.containsKey(attributeName)) { 606 if (index != 0) { 607 throw new IndexOutOfBoundsException(); 608 } else { 609 addAttributeValue(attributeName, value); 610 } 611 } else { 612 if (m_entityAttributes.get(attributeName).size() > index) { 613 CmsEntity child = m_entityAttributes.get(attributeName).remove(index); 614 removeChildChangeHandler(child); 615 } 616 m_entityAttributes.get(attributeName).add(index, value); 617 fireChange(ChangeType.change); 618 } 619 } 620 621 /** 622 * Sets the given attribute value. Will remove all previous attribute values.<p> 623 * 624 * @param attributeName the attribute name 625 * @param value the attribute value 626 */ 627 public void setAttributeValue(String attributeName, String value) { 628 629 m_entityAttributes.remove(attributeName); 630 List<String> values = new ArrayList<String>(); 631 values.add(value); 632 m_simpleAttributes.put(attributeName, values); 633 fireChange(ChangeType.change); 634 } 635 636 /** 637 * Sets the given attribute value at the given index.<p> 638 * 639 * @param attributeName the attribute name 640 * @param value the attribute value 641 * @param index the value index 642 */ 643 public void setAttributeValue(String attributeName, String value, int index) { 644 645 if (m_entityAttributes.containsKey(attributeName)) { 646 throw new RuntimeException("Attribute already exists with a simple type value."); 647 } 648 if (!m_simpleAttributes.containsKey(attributeName)) { 649 if (index != 0) { 650 throw new IndexOutOfBoundsException(); 651 } else { 652 addAttributeValue(attributeName, value); 653 } 654 } else { 655 if (m_simpleAttributes.get(attributeName).size() > index) { 656 m_simpleAttributes.get(attributeName).remove(index); 657 } 658 m_simpleAttributes.get(attributeName).add(index, value); 659 fireChange(ChangeType.change); 660 } 661 } 662 663 /** 664 * Returns the JSON string representation of this entity.<p> 665 * 666 * @return the JSON string representation of this entity 667 */ 668 public String toJSON() { 669 670 StringBuffer result = new StringBuffer(); 671 result.append("{\n"); 672 for (Entry<String, List<String>> simpleEntry : m_simpleAttributes.entrySet()) { 673 result.append("\"").append(simpleEntry.getKey()).append("\"").append(": [\n"); 674 boolean firstValue = true; 675 for (String value : simpleEntry.getValue()) { 676 if (firstValue) { 677 firstValue = false; 678 } else { 679 result.append(",\n"); 680 } 681 result.append("\"").append(value).append("\""); 682 } 683 result.append("],\n"); 684 } 685 for (Entry<String, List<CmsEntity>> entityEntry : m_entityAttributes.entrySet()) { 686 result.append("\"").append(entityEntry.getKey()).append("\"").append(": [\n"); 687 boolean firstValue = true; 688 for (CmsEntity value : entityEntry.getValue()) { 689 if (firstValue) { 690 firstValue = false; 691 } else { 692 result.append(",\n"); 693 } 694 result.append(value.toJSON()); 695 } 696 result.append("],\n"); 697 } 698 result.append("\"id\": \"").append(m_id).append("\""); 699 result.append("}"); 700 return result.toString(); 701 } 702 703 /** 704 * @see java.lang.Object#toString() 705 */ 706 @Override 707 public String toString() { 708 709 return toJSON(); 710 } 711 712 /** 713 * Adds this handler to the widget. 714 * 715 * @param <H> the type of handler to add 716 * @param type the event type 717 * @param handler the handler 718 * @return {@link HandlerRegistration} used to remove the handler 719 */ 720 protected final <H extends EventHandler> HandlerRegistration addHandler(final H handler, GwtEvent.Type<H> type) { 721 722 return ensureHandlers().addHandlerToSource(type, this, handler); 723 } 724 725 /** 726 * Fires the change event for this entity.<p> 727 * 728 * @param type the change type 729 */ 730 void fireChange(ChangeType type) { 731 732 CmsEntityChangeEvent event = new CmsEntityChangeEvent(this, type); 733 fireEvent(event); 734 } 735 736 /** 737 * Lazy initializing the handler manager.<p> 738 * 739 * @return the handler manager 740 */ 741 private SimpleEventBus ensureHandlers() { 742 743 if (m_eventBus == null) { 744 m_eventBus = new SimpleEventBus(); 745 } 746 return m_eventBus; 747 } 748 749 /** 750 * Adds the value change handler to the given entity.<p> 751 * 752 * @param child the child entity 753 */ 754 private void registerChangeHandler(CmsEntity child) { 755 756 HandlerRegistration reg = child.addValueChangeHandler(m_childChangeHandler); 757 m_changeHandlerRegistry.put(child.getId(), reg); 758 } 759 760 /** 761 * Removes the child entity change handler.<p> 762 * 763 * @param child the child entity 764 */ 765 private void removeChildChangeHandler(CmsEntity child) { 766 767 HandlerRegistration reg = m_changeHandlerRegistry.remove(child.getId()); 768 if (reg != null) { 769 reg.removeHandler(); 770 } 771 } 772}