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;
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}