/* * Copyright (C) 2005 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a * copy of the License at * * http://www.alfresco.org/legal/license.txt * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific * language governing permissions and limitations under the * License. */ package org.alfresco.repo.domain; import java.io.Serializable; import java.util.ArrayList; 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 org.alfresco.error.AlfrescoRuntimeException; 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.NodeRef; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; 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; /** * Immutable property value storage class. *
* The
*
* @author Derek Hulley
*/
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 = "";
private static Log logger = LogFactory.getLog(PropertyValue.class);
private static Log loggerOracle = LogFactory.getLog(PropertyValue.class.getName() + ".oracle");
/** potential value types */
private static enum ValueType
{
NULL
{
@Override
Serializable convert(Serializable value)
{
return null;
}
},
BOOLEAN
{
@Override
Serializable convert(Serializable value)
{
return DefaultTypeConverter.INSTANCE.convert(Boolean.class, value);
}
},
INTEGER
{
@Override
protected ValueType getPersistedType(Serializable value)
{
return ValueType.LONG;
}
@Override
Serializable convert(Serializable value)
{
return DefaultTypeConverter.INSTANCE.convert(Integer.class, value);
}
},
LONG
{
@Override
Serializable convert(Serializable value)
{
return DefaultTypeConverter.INSTANCE.convert(Long.class, value);
}
},
FLOAT
{
@Override
Serializable convert(Serializable value)
{
return DefaultTypeConverter.INSTANCE.convert(Float.class, value);
}
},
DOUBLE
{
@Override
Serializable convert(Serializable value)
{
return DefaultTypeConverter.INSTANCE.convert(Double.class, value);
}
},
STRING
{
/**
* Strings longer than the maximum of 1024 characters will be serialized.
*/
@Override
protected ValueType getPersistedType(Serializable value)
{
if (value instanceof String)
{
String valueStr = (String) value;
if (valueStr.length() > 1024)
{
return ValueType.SERIALIZABLE;
}
}
return ValueType.STRING;
}
@Override
Serializable convert(Serializable value)
{
return DefaultTypeConverter.INSTANCE.convert(String.class, value);
}
},
DATE
{
@Override
protected ValueType getPersistedType(Serializable value)
{
return ValueType.STRING;
}
@Override
Serializable convert(Serializable value)
{
return DefaultTypeConverter.INSTANCE.convert(Date.class, value);
}
},
SERIALIZABLE
{
@Override
Serializable convert(Serializable value)
{
return value;
}
},
CONTENT
{
@Override
protected ValueType getPersistedType(Serializable value)
{
return ValueType.STRING;
}
@Override
Serializable convert(Serializable value)
{
return DefaultTypeConverter.INSTANCE.convert(ContentData.class, value);
}
},
NODEREF
{
@Override
protected ValueType getPersistedType(Serializable value)
{
return ValueType.STRING;
}
@Override
Serializable convert(Serializable value)
{
return DefaultTypeConverter.INSTANCE.convert(NodeRef.class, value);
}
},
CHILD_ASSOC_REF
{
@Override
protected ValueType getPersistedType(Serializable value)
{
return ValueType.STRING;
}
@Override
Serializable convert(Serializable value)
{
return DefaultTypeConverter.INSTANCE.convert(ChildAssociationRef.class, value);
}
},
ASSOC_REF
{
@Override
protected ValueType getPersistedType(Serializable value)
{
return ValueType.STRING;
}
@Override
Serializable convert(Serializable value)
{
return DefaultTypeConverter.INSTANCE.convert(AssociationRef.class, value);
}
},
QNAME
{
@Override
protected ValueType getPersistedType(Serializable value)
{
return ValueType.STRING;
}
@Override
Serializable convert(Serializable value)
{
return DefaultTypeConverter.INSTANCE.convert(QName.class, value);
}
},
PATH
{
@Override
protected ValueType getPersistedType(Serializable value)
{
return ValueType.SERIALIZABLE;
}
@Override
Serializable convert(Serializable value)
{
return DefaultTypeConverter.INSTANCE.convert(Path.class, value);
}
},
LOCALE
{
@Override
protected ValueType getPersistedType(Serializable value)
{
return ValueType.STRING;
}
@Override
Serializable convert(Serializable value)
{
return DefaultTypeConverter.INSTANCE.convert(Locale.class, value);
}
},
VERSION_NUMBER
{
@Override
protected ValueType getPersistedType(Serializable value)
{
return ValueType.STRING;
}
@Override
Serializable convert(Serializable value)
{
return DefaultTypeConverter.INSTANCE.convert(VersionNumber.class, value);
}
};
/**
* 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;
}
/**
* @see DefaultTypeConverter.INSTANCE#convert(Class, Object)
*/
abstract Serializable convert(Serializable value);
protected ArrayListQName
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 String that represents it in
* the database.
*
* @param typeQName the type qualified name
* @return Returns the String representation of the type,
* e.g. CONTENT for type d:content.
*/
public static String getActualTypeString(QName typeQName)
{
ValueType valueType = makeValueType(typeQName);
return valueType.toString();
}
@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.booleanValue, that.booleanValue) &&
EqualsHelper.nullSafeEquals(this.longValue, that.longValue) &&
EqualsHelper.nullSafeEquals(this.floatValue, that.floatValue) &&
EqualsHelper.nullSafeEquals(this.doubleValue, that.doubleValue) &&
EqualsHelper.nullSafeEquals(this.stringValue, that.stringValue) &&
EqualsHelper.nullSafeEquals(this.serializableValue, that.serializableValue)
);
}
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(", multi-valued=").append(isMultiValued)
.append(", value-type=").append(persistedType)
.append(", value=").append(getPersistedValue())
.append("]");
return sb.toString();
}
public String getActualType()
{
return actualType.toString();
}
public void setActualType(String actualType)
{
this.actualType = ValueType.valueOf(actualType);
}
public boolean isMultiValued()
{
return isMultiValued;
}
public void setMultiValued(boolean isMultiValued)
{
this.isMultiValued = isMultiValued;
}
public String getPersistedType()
{
return persistedType.toString();
}
public void setPersistedType(String persistedType)
{
this.persistedType = ValueType.valueOf(persistedType);
}
/**
* 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 = (Serializable) value;
break;
default:
throw new AlfrescoRuntimeException("Unrecognised value type: " + persistedType);
}
// we store the type that we persisted as
this.persistedType = persistedType;
}
/**
* @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 PropertyValue.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 (persistedType == ValueType.NULL)
{
ret = null;
}
else if (this.isMultiValued)
{
// collections are always stored
Collection collection = (Collection) this.serializableValue;
// convert the collection values - we need to do this to ensure that the
// values provided conform to the given type
ArrayList