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}