org.alfresco.serializers.PropertyValue.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.serializers.PropertyValue.java

Source

/*
 * Copyright 2014 Alfresco Software, Ltd.  All rights reserved.
 *
 * License rights for this program may be obtained from Alfresco Software, Ltd. 
 * pursuant to a written agreement and any use of this program without such an 
 * agreement is prohibited. 
 */
package org.alfresco.serializers;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import javax.crypto.SealedObject;

import org.alfresco.error.AlfrescoRuntimeException;
//import org.alfresco.repo.domain.node.ContentDataId;
//import org.alfresco.repo.domain.node.ContentDataWithId;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.MLText;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.Path;
import org.alfresco.service.cmr.repository.Period;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.EqualsHelper;
import org.alfresco.util.VersionNumber;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.surf.util.ParameterCheck;

/**
 * Immutable property value storage class.
 * 
 * @author Derek Hulley
 * @since 3.4
 */
public class PropertyValue implements Cloneable, Serializable {
    private static final long serialVersionUID = -497902497351493075L;

    /** used to take care of empty strings being converted to nulls by the database */
    private static final String STRING_EMPTY = "";
    /** used to provide empty collection values in and out */
    public static final Serializable EMPTY_COLLECTION_VALUE = (Serializable) Collections.emptyList();

    private static Log logger = LogFactory.getLog(PropertyValue.class);
    private static Log loggerOracle = LogFactory.getLog(PropertyValue.class.getName() + ".oracle");

    // TODO
    private static int getMaxStringLength() {
        return 1024;
    }

    /**
     * Immutable classes in addition to {@link ValueProtectingMap#DEFAULT_IMMUTABLE_CLASSES}
     * <li>ContentData</li>
     * <li>ContentDataId</li>
     * <li>NodeRef</li>
     * <li>ChildAssociationRef</li>
     * <li>AssociationRef</li>
     * <li>QName</li>
     * <li>VersionNumber</li>
     * <li>Period</li>
     */
    //    public static final Set<Class<?>> IMMUTABLE_CLASSES;
    //    static
    //    {
    //        IMMUTABLE_CLASSES = new HashSet<Class<?>>(13);
    //        IMMUTABLE_CLASSES.add(ContentData.class);
    //        IMMUTABLE_CLASSES.add(ContentDataId.class);
    //        IMMUTABLE_CLASSES.add(NodeRef.class);
    //        IMMUTABLE_CLASSES.add(ChildAssociationRef.class);
    //        IMMUTABLE_CLASSES.add(AssociationRef.class);
    //        IMMUTABLE_CLASSES.add(QName.class);
    //        IMMUTABLE_CLASSES.add(VersionNumber.class);
    //        IMMUTABLE_CLASSES.add(Period.class);
    //    }

    /** potential value types */
    public static enum ValueType {
        NULL {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(0);
            }

            @Override
            Serializable convert(Serializable value) {
                return null;
            }
        },
        BOOLEAN {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(1);
            }

            @Override
            Serializable convert(Serializable value) {
                return PropertiesTypeConverter.INSTANCE.convert(Boolean.class, value);
            }
        },
        INTEGER {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(2);
            }

            @Override
            protected ValueType getPersistedType(Serializable value) {
                return ValueType.LONG;
            }

            @Override
            Serializable convert(Serializable value) {
                return PropertiesTypeConverter.INSTANCE.convert(Integer.class, value);
            }
        },
        LONG {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(3);
            }

            @Override
            Serializable convert(Serializable value) {
                if (value == null) {
                    return null;
                }
                //                else if (value instanceof ContentDataId)
                //                {
                //                    return ((ContentDataId)value).getId();
                //                }
                //                else if (value instanceof ContentDataWithId)
                //                {
                //                    return ((ContentDataWithId)value).getId();
                //                }
                else {
                    return PropertiesTypeConverter.INSTANCE.convert(Long.class, value);
                }
            }
        },
        FLOAT {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(4);
            }

            /**
             * Cope with special values (ALF-16906)
             */
            @Override
            protected ValueType getPersistedType(Serializable value) {
                if (value instanceof Float) {
                    Float f = (Float) value;
                    if (Float.isInfinite(f) || Float.isNaN(f)) {
                        return ValueType.STRING;
                    }
                }
                return ValueType.FLOAT;
            }

            @Override
            Serializable convert(Serializable value) {
                return PropertiesTypeConverter.INSTANCE.convert(Float.class, value);
            }
        },
        DOUBLE {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(5);
            }

            /**
             * Cope with special values (ALF-16906)
             */
            @Override
            protected ValueType getPersistedType(Serializable value) {
                if (value instanceof Double) {
                    Double d = (Double) value;
                    if (Double.isInfinite(d) || Double.isNaN(d)) {
                        return ValueType.STRING;
                    }
                }
                return ValueType.DOUBLE;
            }

            @Override
            Serializable convert(Serializable value) {
                return PropertiesTypeConverter.INSTANCE.convert(Double.class, value);
            }
        },
        STRING {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(6);
            }

            /**
             * Strings longer than the maximum of {@link PropertyValue#DEFAULT_MAX_STRING_LENGTH}
             * characters will be serialized.
             */
            @Override
            protected ValueType getPersistedType(Serializable value) {
                if (value instanceof String) {
                    String valueStr = (String) value;
                    // Check how long the String can be
                    if (valueStr.length() > getMaxStringLength()) {
                        return ValueType.SERIALIZABLE;
                    }
                }
                return ValueType.STRING;
            }

            @Override
            Serializable convert(Serializable value) {
                return PropertiesTypeConverter.INSTANCE.convert(String.class, value);
            }
        },
        DATE {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(7);
            }

            @Override
            protected ValueType getPersistedType(Serializable value) {
                return ValueType.STRING;
            }

            @Override
            Serializable convert(Serializable value) {
                return PropertiesTypeConverter.INSTANCE.convert(Date.class, value);
            }
        },
        SERIALIZABLE {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(9);
            }

            @Override
            Serializable convert(Serializable value) {
                return value;
            }
        },
        MLTEXT {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(10);
            }

            @Override
            protected ValueType getPersistedType(Serializable value) {
                ValueType ret = null;

                ret = ValueType.JSONOBJECT;

                return ret;
            }

            @Override
            Serializable convert(Serializable value) {
                Serializable ret = null;

                ret = value;

                return ret;
            }
        },
        CONTENT {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(11);
            }

            @Override
            protected ValueType getPersistedType(Serializable value) {
                if (value instanceof ContentData) {
                    return ValueType.JSONOBJECT;
                } else {
                    throw new RuntimeException("ContentData persistence must be by ContentDataId.");
                }
            }

            @Override
            Serializable convert(Serializable value) {
                if (value instanceof Long) {
                    return value;
                } else if (value instanceof String) {
                    logger.warn("Content URL converter has not run to completion: " + value);
                    return PropertiesTypeConverter.INSTANCE.convert(ContentData.class, value);
                } else {
                    return PropertiesTypeConverter.INSTANCE.convert(ContentData.class, value);
                }
            }
        },
        NODEREF {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(12);
            }

            @Override
            protected ValueType getPersistedType(Serializable value) {
                return ValueType.JSONOBJECT;
            }

            @Override
            Serializable convert(Serializable value) {
                return PropertiesTypeConverter.INSTANCE.convert(NodeRef.class, value);
            }
        },
        CHILD_ASSOC_REF {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(13);
            }

            @Override
            protected ValueType getPersistedType(Serializable value) {
                return ValueType.STRING;
            }

            @Override
            Serializable convert(Serializable value) {
                return PropertiesTypeConverter.INSTANCE.convert(ChildAssociationRef.class, value);
            }
        },
        ASSOC_REF {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(14);
            }

            @Override
            protected ValueType getPersistedType(Serializable value) {
                return ValueType.STRING;
            }

            @Override
            Serializable convert(Serializable value) {
                return PropertiesTypeConverter.INSTANCE.convert(AssociationRef.class, value);
            }
        },
        QNAME {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(15);
            }

            @Override
            protected ValueType getPersistedType(Serializable value) {
                return ValueType.STRING;
            }

            @Override
            Serializable convert(Serializable value) {
                return PropertiesTypeConverter.INSTANCE.convert(QName.class, value);
            }
        },
        PATH {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(16);
            }

            @Override
            protected ValueType getPersistedType(Serializable value) {
                return ValueType.JSONOBJECT;
            }

            @Override
            Serializable convert(Serializable value) {
                return PropertiesTypeConverter.INSTANCE.convert(Path.class, value);
            }
        },
        LOCALE {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(17);
            }

            @Override
            protected ValueType getPersistedType(Serializable value) {
                return ValueType.STRING;
            }

            @Override
            Serializable convert(Serializable value) {
                return PropertiesTypeConverter.INSTANCE.convert(Locale.class, value);
            }
        },
        VERSION_NUMBER {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(18);
            }

            @Override
            protected ValueType getPersistedType(Serializable value) {
                return ValueType.STRING;
            }

            @Override
            Serializable convert(Serializable value) {
                return PropertiesTypeConverter.INSTANCE.convert(VersionNumber.class, value);
            }
        },
        COLLECTION {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(19);
            }

            /**
             * @return      Returns and empty <tt>Collection</tt> if the value is null
             *              otherwise it just returns the original value
             */
            @Override
            Serializable convert(Serializable value) {
                if (value == null) {
                    return (Serializable) Collections.emptyList();
                } else {
                    return value;
                }
            }
        },
        PERIOD {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(20);
            }

            @Override
            protected ValueType getPersistedType(Serializable value) {
                return ValueType.STRING;
            }

            @Override
            Serializable convert(Serializable value) {
                return PropertiesTypeConverter.INSTANCE.convert(Period.class, value);
            }
        },
        //        CONTENT_DATA_ID
        //        {
        //            @Override
        //            public Integer getOrdinalNumber()
        //            {
        //                return Integer.valueOf(21);
        //            }
        //            
        //            @Override
        //            protected ValueType getPersistedType(Serializable value)
        //            {
        //                return ValueType.JSONOBJECT;
        //            }
        //
        //            @Override
        //            Serializable convert(Serializable value)
        //            {
        //                if (value == null)
        //                {
        //                    return null;
        //                }
        //                else if (value instanceof Long)
        //                {
        //                    throw new IllegalArgumentException("Don't know how to persist content data id with just an id, need a ContentDataWithId object");
        //                }
        //                else if (value instanceof ContentDataId)
        //                {
        //                    return value;
        //                }
        //                else
        //                {
        //                    return PropertiesTypeConverter.INSTANCE.convert(ContentDataWithId.class, value);
        //                }
        //            }
        //        },
        SEALED_OBJECT {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(22);
            }

            @Override
            protected ValueType getPersistedType(Serializable value) {
                return ValueType.SERIALIZABLE;
            }

            @Override
            Serializable convert(Serializable value) {
                if (value == null) {
                    return null;
                } else if (value instanceof SealedObject) {
                    return value;
                } else {
                    throw new IllegalArgumentException("Encrypted properties must be encrypted by the client.");
                }
            }
        },
        JSONOBJECT {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(23);
            }

            @Override
            protected ValueType getPersistedType(Serializable value) {
                ValueType ret = null;

                ret = ValueType.JSONOBJECT;

                return ret;
            }

            @Override
            Serializable convert(Serializable value) {
                Serializable ret = null;

                ret = PropertiesTypeConverter.INSTANCE.convert(JSON.class, value);

                return ret;
            }
        },
        FIXED_POINT {
            @Override
            public Integer getOrdinalNumber() {
                return Integer.valueOf(24);
            }

            @Override
            protected ValueType getPersistedType(Serializable value) {
                // TODO not supported for persistenceType == SQL, Mongo only
                return ValueType.JSONOBJECT;
            }

            @Override
            Serializable convert(Serializable value) {
                return PropertiesTypeConverter.INSTANCE.convert(BigDecimal.class, value);
            }
        },;

        /**
         * @return      Returns the manually-maintained ordinal number for the value
         */
        public abstract Integer getOrdinalNumber();

        /**
         * Override if the type gets persisted in a different format.
         * 
         * @param value the actual value that is to be persisted.  May not be null.
         */
        protected ValueType getPersistedType(Serializable value) {
            return this;
        }

        /**
         * Converts a value to this type.  The implementation must be able to cope with any legitimate
         * source value.
         * 
         * @see PropertiesTypeConverter.INSTANCE#convert(Class, Object)
         */
        abstract Serializable convert(Serializable value);
    }

    /**
     * Determine the actual value type to aid in more concise persistence.
     * 
     * @param value the value that is to be persisted
     * @return Returns the value type equivalent of the 
     */
    private static ValueType getActualType(Serializable value) {
        if (value == null) {
            return ValueType.NULL;
        } else if (value instanceof Boolean) {
            return ValueType.BOOLEAN;
        } else if (value instanceof Integer) {
            return ValueType.INTEGER;
        } else if (value instanceof Long) {
            return ValueType.LONG;
        } else if (value instanceof Float) {
            return ValueType.FLOAT;
        } else if (value instanceof Double) {
            return ValueType.DOUBLE;
        } else if (value instanceof String) {
            return ValueType.STRING;
        } else if (value instanceof Date) {
            return ValueType.DATE;
        } else if (value instanceof NodeRef) {
            return ValueType.NODEREF;
        } else if (value instanceof ChildAssociationRef) {
            return ValueType.CHILD_ASSOC_REF;
        } else if (value instanceof AssociationRef) {
            return ValueType.ASSOC_REF;
        } else if (value instanceof QName) {
            return ValueType.QNAME;
        } else if (value instanceof Path) {
            return ValueType.PATH;
        } else if (value instanceof Locale) {
            return ValueType.LOCALE;
        } else if (value instanceof VersionNumber) {
            return ValueType.VERSION_NUMBER;
        } else if (value instanceof Period) {
            return ValueType.PERIOD;
        }
        //        else if (value instanceof ContentDataId)
        //        {
        //            return ValueType.CONTENT_DATA_ID;
        //        }
        //        else if (value instanceof ContentDataWithId)
        //        {
        //            return ValueType.CONTENT_DATA_ID;
        //        }
        else if (value instanceof ContentData) {
            return ValueType.CONTENT;
        } else if (value instanceof SealedObject) {
            return ValueType.SEALED_OBJECT;
        } else if (value instanceof JSON) {
            // TODO check this is needed
            JSON json = (JSON) value;
            String type = (String) json.get("t");
            switch (type) {
            case "FIXED_POINT":
                return ValueType.FIXED_POINT;
            case "CONTENT":
                return ValueType.CONTENT;
            //            case "CONTENT_DATA_ID":
            //                return ValueType.CONTENT_DATA_ID;
            case "QNAME":
                return ValueType.QNAME;
            case "NODEREF":
                return ValueType.NODEREF;
            default:
                throw new IllegalArgumentException();
            }
        } else if (value instanceof MLText) {
            return ValueType.MLTEXT;
        } else if (value instanceof BigDecimal) {
            return ValueType.FIXED_POINT;
        } else {
            // type is not recognised as belonging to any particular slot
            return ValueType.SERIALIZABLE;
        }
    }

    /** a mapping from a property type <code>QName</code> to the corresponding value type */
    private static Map<QName, ValueType> valueTypesByPropertyType;
    /**
     * a mapping of {@link ValueType} ordinal number to the enum.  This is manually maintained
     * and <b>MUST NOT BE CHANGED FOR EXISTING VALUES</b>.
     */
    private static Map<Integer, ValueType> valueTypesByOrdinalNumber;
    static {
        valueTypesByPropertyType = new HashMap<QName, ValueType>(37);
        valueTypesByPropertyType.put(DataTypeDefinition.ANY, ValueType.SERIALIZABLE);
        valueTypesByPropertyType.put(DataTypeDefinition.ENCRYPTED, ValueType.SEALED_OBJECT);
        valueTypesByPropertyType.put(DataTypeDefinition.BOOLEAN, ValueType.BOOLEAN);
        valueTypesByPropertyType.put(DataTypeDefinition.INT, ValueType.INTEGER);
        valueTypesByPropertyType.put(DataTypeDefinition.LONG, ValueType.LONG);
        valueTypesByPropertyType.put(DataTypeDefinition.DOUBLE, ValueType.DOUBLE);
        valueTypesByPropertyType.put(DataTypeDefinition.FLOAT, ValueType.FLOAT);
        valueTypesByPropertyType.put(DataTypeDefinition.DATE, ValueType.DATE);
        valueTypesByPropertyType.put(DataTypeDefinition.DATETIME, ValueType.DATE);
        valueTypesByPropertyType.put(DataTypeDefinition.CATEGORY, ValueType.NODEREF);
        //        valueTypesByPropertyType.put(DataTypeDefinition.CONTENT, ValueType.CONTENT_DATA_ID);
        valueTypesByPropertyType.put(DataTypeDefinition.CONTENT, ValueType.CONTENT);
        valueTypesByPropertyType.put(DataTypeDefinition.TEXT, ValueType.STRING);
        valueTypesByPropertyType.put(DataTypeDefinition.MLTEXT, ValueType.MLTEXT);
        valueTypesByPropertyType.put(DataTypeDefinition.NODE_REF, ValueType.NODEREF);
        valueTypesByPropertyType.put(DataTypeDefinition.CHILD_ASSOC_REF, ValueType.CHILD_ASSOC_REF);
        valueTypesByPropertyType.put(DataTypeDefinition.ASSOC_REF, ValueType.ASSOC_REF);
        valueTypesByPropertyType.put(DataTypeDefinition.PATH, ValueType.PATH);
        valueTypesByPropertyType.put(DataTypeDefinition.QNAME, ValueType.QNAME);
        valueTypesByPropertyType.put(DataTypeDefinition.LOCALE, ValueType.LOCALE);
        valueTypesByPropertyType.put(DataTypeDefinition.PERIOD, ValueType.PERIOD);
        //        valueTypesByPropertyType.put(DataTypeDefinition.FIXED_POINT, ValueType.FIXED_POINT);

        valueTypesByOrdinalNumber = new HashMap<Integer, ValueType>(37);
        for (ValueType valueType : ValueType.values()) {
            Integer ordinalNumber = valueType.getOrdinalNumber();
            if (valueTypesByOrdinalNumber.containsKey(ordinalNumber)) {
                throw new RuntimeException("ValueType has duplicate ordinal number: " + valueType);
            } else if (ordinalNumber.intValue() == -1) {
                throw new RuntimeException("ValueType doesn't have an ordinal number: " + valueType);
            }
            valueTypesByOrdinalNumber.put(ordinalNumber, valueType);
        }
    }

    /**
     * Helper method to convert the type <code>QName</code> into a <code>ValueType</code>
     * 
     * @return Returns the <code>ValueType</code>  - never null
     */
    private static ValueType makeValueType(QName typeQName) {
        ValueType valueType = valueTypesByPropertyType.get(typeQName);
        if (valueType == null) {
            throw new AlfrescoRuntimeException("Property type not recognised: \n" + "   type: " + typeQName);
        }
        return valueType;
    }

    /**
     * Given an actual type qualified name, returns the <tt>int</tt> ordinal number
     * that represents it in the database.
     * 
     * @param typeQName the type qualified name
     * @return Returns the <tt>int</tt> representation of the type,
     *      e.g. <b>CONTENT.getOrdinalNumber()</b> for type <b>d:content</b>.
     */
    public static int convertToTypeOrdinal(QName typeQName) {
        ValueType valueType = makeValueType(typeQName);
        return valueType.getOrdinalNumber();
    }

    /**
     * If property value of the type <code>QName</code> is supported
     * 
     * @param typeQName the type qualified name
     */
    public static boolean isDataTypeSupported(QName typeQName) {
        return valueTypesByPropertyType.keySet().contains(typeQName);
    }

    /** the type of the property, prior to serialization persistence */
    private ValueType actualType;
    /** the type of persistence used */
    private ValueType persistedType;

    private Boolean booleanValue;
    private Long longValue;
    private Float floatValue;
    private Double doubleValue;
    private String stringValue;
    private Serializable serializableValue;
    private JSON jsonObject;

    /**
     * default constructor
     */
    public PropertyValue() {
    }

    /**
     * Construct a new property value.
     * 
     * @param typeQName         the dictionary-defined property type to store the property as
     * @param value             the value to store.  This will be converted into a format compatible
     *                          with the type given
     * 
     * @throws java.lang.UnsupportedOperationException if the value cannot be converted to the type given
     */
    public PropertyValue(QName typeQName, Serializable value) {
        this();

        ParameterCheck.mandatory("typeQName", typeQName);

        if (value == null) {
            this.actualType = PropertyValue.getActualType(value);
            setPersistedValue(ValueType.NULL, null);
        } else {
            // Convert the value to the type required.  This ensures that any type conversion issues
            // are caught early and prevent the scenario where the data in the DB cannot be given
            // back out because it is unconvertable.
            ValueType valueType = makeValueType(typeQName);
            if (valueType == ValueType.SERIALIZABLE) {
                // data type == any. Need to store the type information with the value.

                this.actualType = PropertyValue.getActualType(value);

                ValueType persistedValueType = actualType.getPersistedType(value);
                switch (persistedValueType) {
                case SERIALIZABLE: {
                    // TODO
                    // special case
                    persistedValueType = actualType;
                    value = valueType.convert(value);
                    // get the persisted type
                    persistedValueType = this.actualType.getPersistedType(value);
                    // convert to the persistent type
                    value = persistedValueType.convert(value);
                    break;
                }
                case JSONOBJECT: {
                    // these already encode the type information, just do a straight conversion to DBObject
                    persistedValueType = ValueType.JSONOBJECT;
                    ValueType valueType1 = actualType.getPersistedType(value);
                    value = valueType1.convert(value);
                    break;
                }
                default: {
                    // these map to primitive types, need to encode the type information explicitly
                    persistedValueType = ValueType.JSONOBJECT;
                    ValueType valueType1 = actualType.getPersistedType(value);
                    Serializable value1 = valueType1.convert(value);
                    JSON json = new JSON();
                    json.put("t", actualType.toString());
                    json.put("v", value1);
                    value = json;
                    break;
                }
                }

                setPersistedValue(persistedValueType, value);
            } else {
                this.actualType = PropertyValue.getActualType(value);
                value = valueType.convert(value);

                // get the persisted type
                ValueType persistedValueType = this.actualType.getPersistedType(value);
                // convert to the persistent type
                value = persistedValueType.convert(value);
                setPersistedValue(persistedValueType, value);
            }
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (obj instanceof PropertyValue) {
            PropertyValue that = (PropertyValue) obj;
            return (this.actualType.equals(that.actualType)
                    && EqualsHelper.nullSafeEquals(this.getPersistedValue(), that.getPersistedValue()));
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        int h = 0;
        if (actualType != null)
            h = actualType.hashCode();
        Serializable persistedValue = getPersistedValue();
        if (persistedValue != null)
            h += 17 * persistedValue.hashCode();
        return h;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(128);
        sb.append("PropertyValue").append("[actual-type=").append(actualType).append(", value-type=")
                .append(persistedType).append(", value=").append(getPersistedValue()).append("]");
        return sb.toString();
    }

    public Integer getActualType() {
        return actualType == null ? null : actualType.getOrdinalNumber();
    }

    /**
     * @return          Returns the actual type's String representation
     */
    public String getActualTypeString() {
        return actualType == null ? null : actualType.toString();
    }

    public void setActualType(Integer actualType) {
        ValueType type = PropertyValue.valueTypesByOrdinalNumber.get(actualType);
        if (type == null) {
            logger.error("Unknown property actual type ordinal number: " + actualType);
        }
        this.actualType = type;
    }

    public ValueType getPersistedValueType() {
        return persistedType;
    }

    public Integer getPersistedType() {
        return persistedType == null ? null : persistedType.getOrdinalNumber();
    }

    public void setPersistedType(Integer persistedType) {
        ValueType type = PropertyValue.valueTypesByOrdinalNumber.get(persistedType);
        if (type == null) {
            logger.error("Unknown property persisted type ordinal number: " + persistedType);
        }
        this.persistedType = type;
    }

    /**
     * Stores the value in the correct slot based on the type of persistence requested.
     * No conversion is done.
     * 
     * @param persistedType the value type
     * @param value the value - it may only be null if the persisted type is {@link ValueType#NULL}
     */
    public void setPersistedValue(ValueType persistedType, Serializable value) {
        switch (persistedType) {
        case NULL:
            if (value != null) {
                throw new AlfrescoRuntimeException("Value must be null for persisted type: " + persistedType);
            }
            break;
        case BOOLEAN:
            this.booleanValue = (Boolean) value;
            break;
        case LONG:
            this.longValue = (Long) value;
            break;
        case FLOAT:
            this.floatValue = (Float) value;
            break;
        case DOUBLE:
            this.doubleValue = (Double) value;
            break;
        case STRING:
            this.stringValue = (String) value;
            break;
        case SERIALIZABLE:
            this.serializableValue = cloneSerializable(value);
            break;
        case JSONOBJECT:
            this.jsonObject = (JSON) value;
            break;
        //            case ENCRYPTED_STRING:
        //                this.serializableValue = encrypt(value);
        //                break;
        default:
            throw new AlfrescoRuntimeException("Unrecognised value type: " + persistedType);
        }
        // we store the type that we persisted as
        this.persistedType = persistedType;
    }

    /**
     * Clones a serializable object to disconnect the original instance from the persisted instance.
     * 
     * @param original          the original object
     * @return                  the new cloned object
     */
    private Serializable cloneSerializable(Serializable original) {
        ObjectOutputStream objectOut = null;
        ByteArrayOutputStream byteOut = null;
        ObjectInputStream objectIn = null;
        try {
            // Write the object out to a byte array
            byteOut = new ByteArrayOutputStream();
            objectOut = new ObjectOutputStream(byteOut);
            objectOut.writeObject(original);
            objectOut.flush();

            objectIn = new ObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()));
            Object target = objectIn.readObject();
            // Done
            return (Serializable) target;
        } catch (Throwable e) {
            throw new AlfrescoRuntimeException("Failed to clone serializable object: " + original, e);
        } finally {
            if (objectOut != null) {
                try {
                    objectOut.close();
                } catch (Throwable e) {
                }
            }
            if (byteOut != null) {
                try {
                    byteOut.close();
                } catch (Throwable e) {
                }
            }
            if (objectIn != null) {
                try {
                    objectIn.close();
                } catch (Throwable e) {
                }
            }
        }
    }

    /**
     * @return Returns the persisted value, keying off the persisted value type
     */
    public Serializable getPersistedValue() {
        switch (persistedType) {
        case NULL:
            return null;
        case BOOLEAN:
            return this.booleanValue;
        case LONG:
            return this.longValue;
        case FLOAT:
            return this.floatValue;
        case DOUBLE:
            return this.doubleValue;
        case STRING:
            // Oracle stores empty strings as 'null'...
            if (this.stringValue == null) {
                // We know that we stored a non-null string, but now it is null.
                // It can only mean one thing - Oracle
                if (loggerOracle.isDebugEnabled()) {
                    logger.debug("string_value is 'null'.  Forcing to empty String");
                }
                return PropertyValue.STRING_EMPTY;
            } else {
                return this.stringValue;
            }
        case JSONOBJECT:
            return this.jsonObject;
        case SERIALIZABLE:
            return this.serializableValue;
        default:
            throw new AlfrescoRuntimeException("Unrecognised value type: " + persistedType);
        }
    }

    /**
     * Fetches the value as a desired type.  Collections (i.e. multi-valued properties)
     * will be converted as a whole to ensure that all the values returned within the
     * collection match the given type.
     * 
     * @param typeQName the type required for the return value
     * @return Returns the value of this property as the desired type, or a <code>Collection</code>
     *      of values of the required type
     * 
     * @throws AlfrescoRuntimeException
     *      if the type given is not recognized
     * @throws org.alfresco.service.cmr.repository.datatype.TypeConversionException
     *      if the conversion to the required type fails
     * 
     * @see DataTypeDefinition#ANY The static qualified names for the types
     */
    public Serializable getValue(QName typeQName) {
        Serializable ret = null;
        ValueType requiredType = makeValueType(typeQName);

        Serializable persistedValue = getPersistedValue();
        // convert the type
        // In order to cope with historical data, where collections were serialized
        // regardless of type.
        if (persistedValue instanceof Collection<?>) {
            // We assume that the collection contained the correct type values.  They would
            // have been converted on the way in.
            ret = (Serializable) persistedValue;
        } else if (persistedValue instanceof SealedObject) {
            ret = (Serializable) persistedValue;
        } else {
            if (requiredType == ValueType.SERIALIZABLE) {
                if (persistedValue != null) {
                    //  datatype == any
                    requiredType = this.actualType;

                    switch (requiredType) {
                    case QNAME:
                    case VERSION_NUMBER:
                    case DATE:
                    case LOCALE:
                    case PERIOD:
                    case LONG:
                    case DOUBLE:
                    case FLOAT:
                    case BOOLEAN:
                    case INTEGER:
                    case STRING: {
                        JSON json = (JSON) persistedValue;
                        Serializable value = (Serializable) json.get("v");
                        ret = requiredType.convert(value);
                        break;
                    }
                    case SERIALIZABLE:
                        ret = requiredType.convert(persistedValue);
                        break;
                    case JSONOBJECT: {
                        throw new IllegalArgumentException();
                    }
                    case NULL: {
                        ret = null;
                        break;
                    }
                    default: {
                        ret = requiredType.convert(persistedValue);
                        break;
                    }
                    }
                } else {
                    ret = null;
                }
            } else {
                ret = requiredType.convert(persistedValue);
            }
        }

        // done
        if (logger.isDebugEnabled()) {
            logger.debug("Fetched value: \n" + "   property value: " + this + "\n" + "   requested type: "
                    + requiredType + "\n" + "   result: " + ret);
        }
        return ret;
    }

    /**
     * Gets the value or values as a guaranteed collection.
     * 
     * @see #getValue(QName)
     */
    @SuppressWarnings("unchecked")
    public Collection<Serializable> getCollection(QName typeQName) {
        Serializable value = getValue(typeQName);
        if (value instanceof Collection) {
            return (Collection<Serializable>) value;
        } else {
            return Collections.singletonList(value);
        }
    }

    public boolean getBooleanValue() {
        if (booleanValue == null)
            return false;
        else
            return booleanValue.booleanValue();
    }

    public void setBooleanValue(boolean value) {
        this.booleanValue = Boolean.valueOf(value);
    }

    public long getLongValue() {
        if (longValue == null)
            return 0;
        else
            return longValue.longValue();
    }

    public void setLongValue(long value) {
        this.longValue = Long.valueOf(value);
    }

    public void setJSON(JSON dbObject) {
        String actualTypeStr = (String) dbObject.get("t");
        this.actualType = ValueType.valueOf(actualTypeStr);
        this.persistedType = ValueType.JSONOBJECT;
        this.jsonObject = dbObject;
    }

    public float getFloatValue() {
        if (floatValue == null)
            return 0.0F;
        else
            return floatValue.floatValue();
    }

    public void setFloatValue(float value) {
        this.floatValue = Float.valueOf(value);
    }

    public double getDoubleValue() {
        if (doubleValue == null)
            return 0.0;
        else
            return doubleValue.doubleValue();
    }

    public void setDoubleValue(double value) {
        this.doubleValue = Double.valueOf(value);
    }

    public String getStringValue() {
        return stringValue;
    }

    public JSON getJSON()// throws IOException
    {
        //        XContentBuilder builder = jsonBuilder();
        //        for(Map.Entry<String, Object> entry : jsonObject.getMap().entrySet())
        //        {
        //            builder.field(entry.getKey(), entry.getValue());
        //        }
        return jsonObject;
    }

    public void setStringValue(String value) {
        this.stringValue = value;
    }

    public Serializable getSerializableValue() {
        return serializableValue;
    }

    public void setSerializableValue(Serializable value) {
        this.serializableValue = value;
    }
}