/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see
QName
to the corresponding value type */
private static MapQName
into a ValueType
*
* @return Returns the ValueType
- 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 int ordinal number
* that represents it in the database.
*
* @param typeQName the type qualified name
* @return Returns the int representation of the type,
* e.g. CONTENT.getOrdinalNumber() for type d:content.
*/
public static int convertToTypeOrdinal(QName typeQName)
{
ValueType valueType = makeValueType(typeQName);
return valueType.getOrdinalNumber();
}
/** 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;
/**
* default constructor
*/
public NodePropertyValue()
{
}
/**
* 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 NodePropertyValue(QName typeQName, Serializable value)
{
ParameterCheck.mandatory("typeQName", typeQName);
if (value == null)
{
this.actualType = NodePropertyValue.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);
value = valueType.convert(value);
this.actualType = NodePropertyValue.getActualType(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 NodePropertyValue)
{
NodePropertyValue that = (NodePropertyValue) 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 = NodePropertyValue.valueTypesByOrdinalNumber.get(actualType);
if (type == null)
{
logger.error("Unknown property actual type ordinal number: " + actualType);
}
this.actualType = type;
}
public Integer getPersistedType()
{
return persistedType == null ? null : persistedType.getOrdinalNumber();
}
public void setPersistedType(Integer persistedType)
{
ValueType type = NodePropertyValue.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 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
*/
private 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 NodePropertyValue.STRING_EMPTY;
}
else
{
return this.stringValue;
}
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 Collection
* 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)
{
// first check for null
ValueType requiredType = makeValueType(typeQName);
if (requiredType == ValueType.SERIALIZABLE)
{
// the required type must be the actual type
requiredType = this.actualType;
}
// we need to convert
Serializable ret = null;
if (actualType == ValueType.COLLECTION && persistedType == ValueType.NULL)
{
// This is a special case of an empty collection
ret = (Serializable) Collections.emptyList();
}
else if (persistedType == ValueType.NULL)
{
ret = null;
}
else
{
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
{
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