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