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