com.xpn.xwiki.objects.BaseCollection.java Source code

Java tutorial

Introduction

Here is the source code for com.xpn.xwiki.objects.BaseCollection.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package com.xpn.xwiki.objects;

import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.dom.DOMDocument;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.xwiki.model.EntityType;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.model.reference.EntityReferenceResolver;
import org.xwiki.model.reference.EntityReferenceSerializer;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.merge.CollisionException;
import com.xpn.xwiki.doc.merge.MergeConfiguration;
import com.xpn.xwiki.doc.merge.MergeResult;
import com.xpn.xwiki.objects.classes.BaseClass;
import com.xpn.xwiki.objects.classes.PropertyClass;
import com.xpn.xwiki.web.Utils;

/**
 * Base class for representing an element having a collection of properties. For example:
 * <ul>
 * <li>an XClass definition (composed of XClass properties)</li>
 * <li>an XObject definition (composed of XObject properties)</li>
 * <li>an XWikiStats object (composed of stats properties)</li>
 * </ul>
 * 
 * @version $Id: d6f118cad0cbd465a88345e7fb5555bbdba5de16 $
 */
public abstract class BaseCollection<R extends EntityReference> extends BaseElement<R>
        implements ObjectInterface, Cloneable {
    protected static final Log LOG = LogFactory.getLog(BaseCollection.class);

    /**
     * The meaning of this reference fields depends on the element represented. Examples:
     * <ul>
     * <li>If this BaseCollection instance represents an XObject then refers to the document where the XObject's XClass
     * is defined.</li>
     * <li>If this BaseCollection instance represents an XClass then it's not used.</li>
     * </ul>
     * Note that we're saving the XClass reference as a relative reference instead of an absolute one. This is because
     * We want the ability (for example) to create an XObject relative to the current space or wiki so that a copy of
     * that XObject would retain that relativity. This is for example useful when copying a Wiki into another Wiki so
     * that the copied XObject have a XClassReference pointing in the new wiki.
     */
    private EntityReference xClassReference;

    /**
     * Cache the XClass reference resolved as an absolute reference for improved performance (so that we don't have to
     * resolve the relative reference every time getXClassReference() is called.
     */
    private DocumentReference xClassReferenceCache;

    /**
     * List of properties (eg XClass properties, XObject properties, etc).
     */
    protected Map<String, Object> fields = new LinkedHashMap<String, Object>();

    protected List fieldsToRemove = new ArrayList();

    /**
     * The meaning of this reference fields depends on the element represented. Examples:
     * <ul>
     * <li>When the BaseCollection represents an XObject, this number is the position of this XObject in the document
     * where it's located. The first XObject of a given XClass type is at position 0, and other XObject of the same
     * XClass type are at position 1, etc.</li>
     * </ul>
     */
    protected int number;

    /**
     * Used to resolve XClass references in the way they are stored externally (database, xml, etc), ie relative or
     * absolute.
     */
    private EntityReferenceResolver<String> relativeEntityReferenceResolver = Utils
            .getComponent(EntityReferenceResolver.class, "relative");

    /**
     * Used to convert a proper Class Reference to a string but without the wiki name.
     */
    private EntityReferenceSerializer<String> localEntityReferenceSerializer = Utils
            .getComponent(EntityReferenceSerializer.class, "local");

    /**
     * Used to normalize references.
     */
    private DocumentReferenceResolver<EntityReference> currentReferenceDocumentReferenceResolver = Utils
            .getComponent(DocumentReferenceResolver.class, "current/reference");

    public int getId() {
        return hashCode();
    }

    /**
     * {@inheritDoc}
     * 
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        return (getName() + getClassName()).hashCode();
    }

    public void setId(int id) {
    }

    public int getNumber() {
        return this.number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public void addPropertyForRemoval(PropertyInterface field) {
        getFieldsToRemove().add(field);
    }

    /**
     * @since 2.2M2
     */
    public DocumentReference getXClassReference() {
        // Ensure we always return an absolute references since we always want well-constructed to be used from outside
        // this class.
        if (this.xClassReferenceCache == null && getRelativeXClassReference() != null) {
            this.xClassReferenceCache = this.currentReferenceDocumentReferenceResolver
                    .resolve(getRelativeXClassReference(), getDocumentReference());
        }

        return this.xClassReferenceCache;
    }

    /**
     * @since 2.2.3
     */
    private EntityReference getRelativeXClassReference() {
        return this.xClassReference;
    }

    /**
     * Note that this method cannot be removed for now since it's used by Hibernate for saving an XObject.
     * 
     * @deprecated since 2.2M2 use {@link #getXClassReference()} instead
     */
    @Deprecated
    public String getClassName() {
        String xClassAsString;
        if (getXClassReference() != null) {
            xClassAsString = this.localEntityReferenceSerializer.serialize(getXClassReference());
        } else {
            xClassAsString = "";
        }
        return xClassAsString;
    }

    /**
     * @since 2.2.3
     */
    public void setXClassReference(EntityReference xClassReference) {
        this.xClassReference = xClassReference;
        this.xClassReferenceCache = null;
    }

    /**
     * Note that this method cannot be removed for now since it's used by Hibernate for loading an XObject.
     * 
     * @deprecated since 2.2.3 use {@link #setXClassReference(EntityReference)} ()} instead
     */
    @Deprecated
    public void setClassName(String name) {
        EntityReference xClassReference = null;
        if (!StringUtils.isEmpty(name)) {
            // Handle backward compatibility: In the past, for statistics objects we used to use a special class name
            // of "internal". We now check for a null Class Reference instead wherever we were previously checking for
            // "internal".
            if (!"internal".equals(name)) {
                xClassReference = this.relativeEntityReferenceResolver.resolve(name, EntityType.DOCUMENT);
            }
        }
        setXClassReference(xClassReference);
    }

    /**
     * {@inheritDoc}
     * 
     * @see com.xpn.xwiki.objects.ObjectInterface#safeget(java.lang.String)
     */
    public PropertyInterface safeget(String name) {
        return (PropertyInterface) getFields().get(name);
    }

    /**
     * {@inheritDoc}
     * 
     * @see com.xpn.xwiki.objects.ObjectInterface#get(java.lang.String)
     */
    public PropertyInterface get(String name) throws XWikiException {
        return safeget(name);
    }

    /**
     * {@inheritDoc}
     * 
     * @see com.xpn.xwiki.objects.ObjectInterface#safeput(java.lang.String, com.xpn.xwiki.objects.PropertyInterface)
     */
    public void safeput(String name, PropertyInterface property) {
        addField(name, property);
        if (property instanceof BaseProperty) {
            ((BaseProperty) property).setObject(this);
            ((BaseProperty) property).setName(name);
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see com.xpn.xwiki.objects.ObjectInterface#put(java.lang.String, com.xpn.xwiki.objects.PropertyInterface)
     */
    public void put(String name, PropertyInterface property) throws XWikiException {
        safeput(name, property);
    }

    /**
     * @since 2.2M1
     */
    public BaseClass getXClass(XWikiContext context) {
        BaseClass baseClass = null;

        if ((context == null) || (context.getWiki() == null)) {
            return baseClass;
        }

        DocumentReference classReference = getXClassReference();

        if (classReference != null) {
            try {
                baseClass = context.getWiki().getXClass(classReference, context);
            } catch (Exception e) {
                LOG.error("Failed to get class [" + classReference + "]", e);
            }
        }

        return baseClass;
    }

    /**
     * {@inheritDoc}
     * 
     * @see com.xpn.xwiki.objects.ObjectInterface#getxWikiClass(com.xpn.xwiki.XWikiContext)
     * @deprecated since 2.2M1 use {@link #getXClass(com.xpn.xwiki.XWikiContext)}
     */
    @Deprecated
    public BaseClass getxWikiClass(XWikiContext context) {
        return getXClass(context);
    }

    public String getStringValue(String name) {
        BaseProperty prop = (BaseProperty) safeget(name);
        if (prop == null || prop.getValue() == null) {
            return "";
        } else {
            return prop.getValue().toString();
        }
    }

    public String getLargeStringValue(String name) {
        return getStringValue(name);
    }

    public void setStringValue(String name, String value) {
        BaseStringProperty property = (BaseStringProperty) safeget(name);
        if (property == null) {
            property = new StringProperty();
        }
        property.setName(name);
        property.setValue(value);
        safeput(name, property);
    }

    public void setLargeStringValue(String name, String value) {
        BaseStringProperty property = (BaseStringProperty) safeget(name);
        if (property == null) {
            property = new LargeStringProperty();
        }
        property.setName(name);
        property.setValue(value);
        safeput(name, property);
    }

    public int getIntValue(String name) {
        return getIntValue(name, 0);
    }

    public int getIntValue(String name, int default_value) {
        try {
            NumberProperty prop = (NumberProperty) safeget(name);
            if (prop == null) {
                return default_value;
            } else {
                return ((Number) prop.getValue()).intValue();
            }
        } catch (Exception e) {
            return default_value;
        }
    }

    public void setIntValue(String name, int value) {
        NumberProperty property = new IntegerProperty();
        property.setName(name);
        property.setValue(value);
        safeput(name, property);
    }

    public long getLongValue(String name) {
        try {
            NumberProperty prop = (NumberProperty) safeget(name);
            if (prop == null) {
                return 0;
            } else {
                return ((Number) prop.getValue()).longValue();
            }
        } catch (Exception e) {
            return 0;
        }
    }

    public void setLongValue(String name, long value) {
        NumberProperty property = new LongProperty();
        property.setName(name);
        property.setValue(value);
        safeput(name, property);
    }

    public float getFloatValue(String name) {
        try {
            NumberProperty prop = (NumberProperty) safeget(name);
            if (prop == null) {
                return 0;
            } else {
                return ((Number) prop.getValue()).floatValue();
            }
        } catch (Exception e) {
            return 0;
        }
    }

    public void setFloatValue(String name, float value) {
        NumberProperty property = new FloatProperty();
        property.setName(name);
        property.setValue(new Float(value));
        safeput(name, property);
    }

    public double getDoubleValue(String name) {
        try {
            NumberProperty prop = (NumberProperty) safeget(name);
            if (prop == null) {
                return 0;
            } else {
                return ((Number) prop.getValue()).doubleValue();
            }
        } catch (Exception e) {
            return 0;
        }
    }

    public void setDoubleValue(String name, double value) {
        NumberProperty property = new DoubleProperty();
        property.setName(name);
        property.setValue(new Double(value));
        safeput(name, property);
    }

    public Date getDateValue(String name) {
        try {
            DateProperty prop = (DateProperty) safeget(name);
            if (prop == null) {
                return null;
            } else {
                return (Date) prop.getValue();
            }
        } catch (Exception e) {
            return null;
        }
    }

    public void setDateValue(String name, Date value) {
        DateProperty property = new DateProperty();
        property.setName(name);
        property.setValue(value);
        safeput(name, property);
    }

    public Set<?> getSetValue(String name) {
        ListProperty prop = (ListProperty) safeget(name);
        if (prop == null) {
            return new HashSet<Object>();
        } else {
            return new HashSet<Object>((Collection<?>) prop.getValue());
        }
    }

    public void setSetValue(String name, Set<?> value) {
        ListProperty property = new ListProperty();
        property.setValue(value);
        safeput(name, property);
    }

    public List getListValue(String name) {
        ListProperty prop = (ListProperty) safeget(name);
        if (prop == null) {
            return new ArrayList();
        } else {
            return (List) prop.getValue();
        }
    }

    public void setStringListValue(String name, List value) {
        ListProperty property = (ListProperty) safeget(name);
        if (property == null) {
            property = new StringListProperty();
        }
        property.setValue(value);
        safeput(name, property);
    }

    public void setDBStringListValue(String name, List value) {
        ListProperty property = (ListProperty) safeget(name);
        if (property == null) {
            property = new DBStringListProperty();
        }
        property.setValue(value);
        safeput(name, property);
    }

    // These functions should not be used
    // but instead our own implementation
    private Map<String, Object> getFields() {
        return this.fields;
    }

    public void setFields(Map fields) {
        this.fields = fields;
    }

    public PropertyInterface getField(String name) {
        return (PropertyInterface) this.fields.get(name);
    }

    public void addField(String name, PropertyInterface element) {
        this.fields.put(name, element);
    }

    public void removeField(String name) {
        Object field = safeget(name);
        if (field != null) {
            this.fields.remove(name);
            this.fieldsToRemove.add(field);
        }
    }

    public Collection getFieldList() {
        return this.fields.values();
    }

    public Set<String> getPropertyList() {
        return this.fields.keySet();
    }

    public Object[] getProperties() {
        return getFields().values().toArray();
    }

    public String[] getPropertyNames() {
        return getFields().keySet().toArray(new String[0]);
    }

    /**
     * Return an iterator that will operate on a collection of values (as would be returned by getProperties or
     * getFieldList) sorted by their name (ElementInterface.getName()).
     */
    public Iterator getSortedIterator() {
        Iterator it = null;
        try {
            // Use getProperties to get the values in list form (rather than as generic collection)
            List propList = Arrays.asList(getProperties());

            // Use the element comparator to sort the properties by name (based on ElementInterface)
            Collections.sort(propList, new ElementComparator());

            // Iterate over the sorted property list
            it = propList.iterator();
        } catch (ClassCastException ccex) {
            // If sorting by the comparator resulted in a ClassCastException (possible),
            // iterate over the generic collection of values.
            it = getFieldList().iterator();
        }

        return it;
    }

    /**
     * {@inheritDoc}
     * 
     * @see com.xpn.xwiki.objects.BaseElement#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object coll) {
        // Same Java object, they sure are equal
        if (this == coll) {
            return true;
        }

        if (!super.equals(coll)) {
            return false;
        }
        BaseCollection collection = (BaseCollection) coll;
        if (collection.getXClassReference() == null) {
            if (getXClassReference() != null) {
                return false;
            }
        } else if (!collection.getXClassReference().equals(getXClassReference())) {
            return false;
        }

        if (getFields().size() != collection.getFields().size()) {
            return false;
        }

        for (Map.Entry<String, Object> entry : getFields().entrySet()) {
            Object prop = entry.getValue();
            Object prop2 = collection.getFields().get(entry.getKey());
            if (!prop.equals(prop2)) {
                return false;
            }
        }

        return true;
    }

    /**
     * {@inheritDoc}
     * 
     * @see com.xpn.xwiki.objects.BaseElement#clone()
     */
    @Override
    public BaseCollection clone() {
        BaseCollection collection = (BaseCollection) super.clone();
        collection.setXClassReference(getRelativeXClassReference());
        collection.setNumber(getNumber());
        Map fields = getFields();
        Map cfields = new HashMap();
        for (Object objEntry : fields.entrySet()) {
            Map.Entry entry = (Map.Entry) objEntry;
            PropertyInterface prop = (PropertyInterface) ((BaseElement) entry.getValue()).clone();
            prop.setObject(collection);
            cfields.put(entry.getKey(), prop);
        }
        collection.setFields(cfields);

        return collection;
    }

    public void merge(BaseObject object) {
        Iterator itfields = object.getPropertyList().iterator();
        while (itfields.hasNext()) {
            String name = (String) itfields.next();
            if (safeget(name) == null) {
                safeput(name, (PropertyInterface) ((BaseElement) object.safeget(name)).clone());
            }
        }
    }

    public List<ObjectDiff> getDiff(Object oldObject, XWikiContext context) {
        ArrayList<ObjectDiff> difflist = new ArrayList<ObjectDiff>();
        BaseCollection oldCollection = (BaseCollection) oldObject;
        // Iterate over the new properties first, to handle changed and added objects
        for (Object key : this.getFields().keySet()) {
            String propertyName = (String) key;
            BaseProperty newProperty = (BaseProperty) this.getFields().get(propertyName);
            BaseProperty oldProperty = (BaseProperty) oldCollection.getFields().get(propertyName);
            BaseClass bclass = getXClass(context);
            PropertyClass pclass = (PropertyClass) ((bclass == null) ? null : bclass.getField(propertyName));
            String propertyType = (pclass == null) ? ""
                    : StringUtils.substringAfterLast(pclass.getClassType(), ".");

            if (oldProperty == null) {
                // The property exist in the new object, but not in the old one
                if ((newProperty != null) && (!newProperty.toText().equals(""))) {
                    if (pclass != null) {
                        String newPropertyValue = (newProperty.getValue() instanceof String) ? newProperty.toText()
                                : pclass.displayView(propertyName, this, context);
                        difflist.add(new ObjectDiff(getXClassReference(), getNumber(), "",
                                ObjectDiff.ACTION_PROPERTYADDED, propertyName, propertyType, "", newPropertyValue));
                    }
                }
            } else if (!oldProperty.toText().equals(((newProperty == null) ? "" : newProperty.toText()))) {
                // The property exists in both objects and is different
                if (pclass != null) {
                    // Put the values as they would be displayed in the interface
                    String newPropertyValue = (newProperty.getValue() instanceof String) ? newProperty.toText()
                            : pclass.displayView(propertyName, this, context);
                    String oldPropertyValue = (oldProperty.getValue() instanceof String) ? oldProperty.toText()
                            : pclass.displayView(propertyName, oldCollection, context);
                    difflist.add(
                            new ObjectDiff(getXClassReference(), getNumber(), "", ObjectDiff.ACTION_PROPERTYCHANGED,
                                    propertyName, propertyType, oldPropertyValue, newPropertyValue));
                } else {
                    // Cannot get property definition, so use the plain value
                    difflist.add(
                            new ObjectDiff(getXClassReference(), getNumber(), "", ObjectDiff.ACTION_PROPERTYCHANGED,
                                    propertyName, propertyType, oldProperty.toText(), newProperty.toText()));
                }
            }
        }

        // Iterate over the old properties, in case there are some removed properties
        for (Object key : oldCollection.getFields().keySet()) {
            String propertyName = (String) key;
            BaseProperty newProperty = (BaseProperty) this.getFields().get(propertyName);
            BaseProperty oldProperty = (BaseProperty) oldCollection.getFields().get(propertyName);
            BaseClass bclass = getXClass(context);
            PropertyClass pclass = (PropertyClass) ((bclass == null) ? null : bclass.getField(propertyName));
            String propertyType = (pclass == null) ? ""
                    : StringUtils.substringAfterLast(pclass.getClassType(), ".");

            if (newProperty == null) {
                // The property exists in the old object, but not in the new one
                if ((oldProperty != null) && (!oldProperty.toText().equals(""))) {
                    if (pclass != null) {
                        // Put the values as they would be displayed in the interface
                        String oldPropertyValue = (oldProperty.getValue() instanceof String) ? oldProperty.toText()
                                : pclass.displayView(propertyName, oldCollection, context);
                        difflist.add(new ObjectDiff(oldCollection.getXClassReference(), oldCollection.getNumber(),
                                "", ObjectDiff.ACTION_PROPERTYREMOVED, propertyName, propertyType, oldPropertyValue,
                                ""));
                    } else {
                        // Cannot get property definition, so use the plain value
                        difflist.add(new ObjectDiff(oldCollection.getXClassReference(), oldCollection.getNumber(),
                                "", ObjectDiff.ACTION_PROPERTYREMOVED, propertyName, propertyType,
                                oldProperty.toText(), ""));
                    }
                }
            }
        }

        return difflist;
    }

    public List getFieldsToRemove() {
        return this.fieldsToRemove;
    }

    public void setFieldsToRemove(List fieldsToRemove) {
        this.fieldsToRemove = fieldsToRemove;
    }

    /**
     * {@inheritDoc}
     * 
     * @see com.xpn.xwiki.objects.ObjectInterface#toXML(com.xpn.xwiki.objects.classes.BaseClass)
     */
    public abstract Element toXML(BaseClass bclass);

    public String toXMLString() {
        Document doc = new DOMDocument();
        doc.setRootElement(toXML(null));
        OutputFormat outputFormat = new OutputFormat("", true);
        StringWriter out = new StringWriter();
        XMLWriter writer = new XMLWriter(out, outputFormat);
        try {
            writer.write(doc);
            return out.toString();
        } catch (IOException e) {
            e.printStackTrace();

            return "";
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return toXMLString();
    }

    /**
     * @since 2.4M2
     */
    public Map<String, Object> getCustomMappingMap() throws XWikiException {
        Map<String, Object> map = new HashMap<String, Object>();
        for (String name : this.fields.keySet()) {
            BaseProperty property = (BaseProperty) get(name);
            map.put(name, property.getCustomMappingValue());
        }
        map.put("id", getId());

        return map;
    }

    /**
     * {@inheritDoc}
     * 
     * @see BaseElement#setDocumentReference(org.xwiki.model.reference.DocumentReference)
     * @since 2.2.3
     */
    @Override
    public void setDocumentReference(DocumentReference reference) {
        super.setDocumentReference(reference);

        // We force to refresh the XClass reference so that next time it's retrieved again it'll be resolved against
        // the new document reference.
        this.xClassReferenceCache = null;
    }

    /**
     * {@inheritDoc}
     * 
     * @see BaseElement#setName(String)
     * @since 2.2.3
     */
    @Override
    public void setName(String name) {
        super.setName(name);

        // We force to refresh the XClass reference so that next time it's retrieved again it'll be resolved against
        // the new document reference.
        this.xClassReferenceCache = null;
    }

    /**
     * {@inheritDoc}
     * 
     * @see com.xpn.xwiki.objects.BaseElement#merge(com.xpn.xwiki.objects.ElementInterface,
     *      com.xpn.xwiki.objects.ElementInterface, com.xpn.xwiki.doc.merge.MergeConfiguration,
     *      com.xpn.xwiki.XWikiContext, com.xpn.xwiki.doc.merge.MergeResult)
     */
    public void merge(ElementInterface previousElement, ElementInterface newElement,
            MergeConfiguration configuration, XWikiContext context, MergeResult mergeResult) {
        BaseCollection<R> previousCollection = (BaseCollection<R>) previousElement;
        BaseCollection<R> newCollection = (BaseCollection<R>) newElement;

        List<ObjectDiff> classDiff = previousCollection.getDiff(newCollection, context);
        for (ObjectDiff diff : classDiff) {
            PropertyInterface propertyResult = getField(diff.getPropName());
            PropertyInterface previousProperty = previousCollection.getField(diff.getPropName());
            PropertyInterface newProperty = newCollection.getField(diff.getPropName());

            if (diff.getAction() == ObjectDiff.ACTION_PROPERTYADDED) {
                if (propertyResult == null) {
                    // Add if none has been added by user already
                    addField(diff.getPropName(),
                            configuration.isProvidedVersionsModifiables() ? newProperty : newProperty.clone());
                    mergeResult.setModified(true);
                } else if (!propertyResult.equals(newProperty)) {
                    // XXX: collision between DB and new: property to add but already exists in the DB
                    mergeResult.error(new CollisionException(
                            "Collision found on property [" + newProperty.getReference() + "]"));
                }
            } else if (diff.getAction() == ObjectDiff.ACTION_PROPERTYREMOVED) {
                if (propertyResult != null) {
                    if (propertyResult.equals(previousProperty)) {
                        // Delete if it's the same as previous one
                        removeField(diff.getPropName());
                        mergeResult.setModified(true);
                    } else {
                        // XXX: collision between DB and new: property to remove but not the same as previous
                        // version
                        mergeResult.error(new CollisionException(
                                "Collision found on property [" + previousProperty.getReference() + "]"));
                    }
                } else {
                    // Already removed from DB, lets assume the user is prescient
                    mergeResult.warn(new CollisionException(
                            "Property [" + previousProperty.getReference() + "] already removed"));
                }
            } else if (diff.getAction() == ObjectDiff.ACTION_PROPERTYCHANGED) {
                if (propertyResult != null) {
                    if (propertyResult.equals(previousProperty)) {
                        // Let some automatic migration take care of that modification between DB and new
                        addField(diff.getPropName(),
                                configuration.isProvidedVersionsModifiables() ? newProperty : newProperty.clone());
                        mergeResult.setModified(true);
                    } else if (!propertyResult.equals(newProperty)) {
                        // Try to apply 3 ways merge on the property
                        propertyResult.merge(previousProperty, newProperty, configuration, context, mergeResult);
                    }
                } else {
                    // XXX: collision between DB and new: property to modify but does not exists in DB
                    // Lets assume it's a mistake to fix
                    mergeResult.warn(new CollisionException(
                            "Collision found on property [" + newProperty.getReference() + "]"));

                    addField(diff.getPropName(),
                            configuration.isProvidedVersionsModifiables() ? newProperty : newProperty.clone());
                    mergeResult.setModified(true);
                }
            }
        }
    }
}