mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-16 17:55:15 +00:00
9018: MT: system-wide jobs should run across all stores/indexes 9204: Merged V2.2 to V2.9 8633: Merged V2.1 to V2.2 8629: Merged V2.1-A to V2.1 8493: Fixed ADB-51: ImporterBootstrap doesn't use transaction retrying 8494: EHCache and JGroup patches and upgrades 8546: ACT-1650: performance optimization 8550: Fixes to transactional cache handling 8553: Fixed tests: MLText is a Map, but will always have at least one entry, even that entry is null. 8583: ACT-954: IndexInfo files now reopen when they close (for whatever reason) 8640: Merged V2.1 to V2.2 8638: Used correct exception type for IO channel reopen logic 9102: Unit test to check that transactional cache size overrun is handled 9106: Merged V2.1 to V2.2 9043: Fixed AR-2291: SchemaBootstrap lock is only required before first SQL execution 9045: Fix AR-2291: SchemaBootstrap lock is only required before first SQL execution 9047: Fixed AR-2305: Index tracking in AUTO mode doesn't report anything on bootstrap 9048: Fixed AR-2300: Random-based GUID instead of time-based GUIDs 9049: Fix patches to only run once 9050 <Defered>: Changed getString() method to use the available buffer length rather than a hard coded value. 9060: Fixed ETWOONE-109 and ETWOONE-128: RetryingTransactionHelper fixes and improvements 9061: Fixed NodeRefPropertyMethodInterceptorTest 9075 <Defered>: Added delete permission check when marking a file for delete on close. ETWOONE-141/ACT-2416. 9080: Fixed EHCache source zip 9081: Fixed ETWOONE-118: Tomcat failed bootstrap doesn't clean up EHCache cluster structures 9085: Fixed ETWOONE-154: Added JSR107 Jar to WAR 9115: Fixed test: TransactionalCache uses LRU so repeatedly checking if a entry is there keeps it in the cache. 9206: Merged V2.2 to V2.9 8857: Improvements to ACL performance for large ACLs 8951: Always check permission entry changes are made at position 0 9219 <No change>: Made NTLMLogonDetails class Serializable, port of r8973. 9220: Added delete permission check when marking a file for delete on close. Port of r9075. 9222: Merged V2.1 to V2.9 8683: Early warning for nodes indexed in the wrong store (ACT-964) 8684: Enhanced tests 8685: Enhanced tests 8686: Additional tests 9223: Merged V2.2 to V2.9 9120: Merged V2.1 to V2.2 8740: Fix for AR-2173 - do no recheck case of the user name when validating tickets (it has been done) 9122: Additional unit test from support case. 9224: Merged V2.2 to V2.9 9076: Fixed ETWOTWO-426: Upgrading alfresco from 2.1.1 to 2.2 throws errors with Mysql 5.0.51 9104: Merged V2.1 to V2.2 9025: Fixed AR-2314, AR-2299: Optimizations after profiling 9105: Merged V2.1 to V2.2 8745: Fix AR-2233 (regression introduced by fix for AR-2221) 9121: Merged V2.1 to V2.2 9017: Fix index back up failing due to background index merge/deletions (inlcudes back port of CHK-2588) 9137: Incorporated additions from Will into AVM console (ETWOTWO-439) 9225: Merged V2.1 to V2.9 8641: Merged V2.1-A to V2.1 7729: Fix to Repository Web Service (queryAssociated) to allow reverse association lookup (ie. given target, get the source) 8673: Fix for AR-2098 - shorter URL form now has NTLM filter mapping example in web.xml 8682: Fix for AR-2005 8695: AR-2054. 8696: Improved sort test to include prefix form of field name 9226: Fix ALFCOM-994 (see also earlier change in r9223) git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@9233 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
1114 lines
36 KiB
Java
1114 lines
36 KiB
Java
/*
|
|
* Copyright (C) 2005-2007 Alfresco Software Limited.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
|
|
* This program 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 General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
* As a special exception to the terms and conditions of version 2.0 of
|
|
* the GPL, you may redistribute this Program in connection with Free/Libre
|
|
* and Open Source Software ("FLOSS") applications as described in Alfresco's
|
|
* FLOSS exception. You should have recieved a copy of the text describing
|
|
* the FLOSS exception, and it is also available here:
|
|
* http://www.alfresco.com/legal/licensing"
|
|
*/
|
|
package org.alfresco.repo.domain;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.ObjectInputStream;
|
|
import java.io.ObjectOutputStream;
|
|
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.repo.attributes.Attribute;
|
|
import org.alfresco.repo.attributes.AttributeConverter;
|
|
import org.alfresco.repo.domain.schema.SchemaBootstrap;
|
|
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.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.
|
|
*
|
|
* @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
|
|
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 DefaultTypeConverter.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 DefaultTypeConverter.INSTANCE.convert(Integer.class, value);
|
|
}
|
|
},
|
|
LONG
|
|
{
|
|
@Override
|
|
public Integer getOrdinalNumber()
|
|
{
|
|
return Integer.valueOf(3);
|
|
}
|
|
|
|
@Override
|
|
Serializable convert(Serializable value)
|
|
{
|
|
return DefaultTypeConverter.INSTANCE.convert(Long.class, value);
|
|
}
|
|
},
|
|
FLOAT
|
|
{
|
|
@Override
|
|
public Integer getOrdinalNumber()
|
|
{
|
|
return Integer.valueOf(4);
|
|
}
|
|
|
|
@Override
|
|
Serializable convert(Serializable value)
|
|
{
|
|
return DefaultTypeConverter.INSTANCE.convert(Float.class, value);
|
|
}
|
|
},
|
|
DOUBLE
|
|
{
|
|
@Override
|
|
public Integer getOrdinalNumber()
|
|
{
|
|
return Integer.valueOf(5);
|
|
}
|
|
|
|
@Override
|
|
Serializable convert(Serializable value)
|
|
{
|
|
return DefaultTypeConverter.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() > SchemaBootstrap.getMaxStringLength())
|
|
{
|
|
return ValueType.SERIALIZABLE;
|
|
}
|
|
}
|
|
return ValueType.STRING;
|
|
}
|
|
|
|
@Override
|
|
Serializable convert(Serializable value)
|
|
{
|
|
return DefaultTypeConverter.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 DefaultTypeConverter.INSTANCE.convert(Date.class, value);
|
|
}
|
|
},
|
|
DB_ATTRIBUTE
|
|
{
|
|
@Override
|
|
public Integer getOrdinalNumber()
|
|
{
|
|
return Integer.valueOf(8);
|
|
}
|
|
|
|
/** class that is able to convert from persisted attributes to normal attributes */
|
|
private AttributeConverter attributeConverter = new AttributeConverter();
|
|
|
|
@Override
|
|
Serializable convert(Serializable value)
|
|
{
|
|
Attribute attribute = DefaultTypeConverter.INSTANCE.convert(Attribute.class, value);
|
|
return attributeConverter.toPersistent(attribute);
|
|
}
|
|
},
|
|
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)
|
|
{
|
|
if (value instanceof MLText)
|
|
{
|
|
MLText mlText = (MLText) value;
|
|
if (mlText.getDefaultValue() == null)
|
|
{
|
|
return ValueType.NULL;
|
|
}
|
|
else if (mlText.size() == 1)
|
|
{
|
|
return ValueType.STRING;
|
|
}
|
|
}
|
|
return ValueType.DB_ATTRIBUTE;
|
|
}
|
|
|
|
@Override
|
|
Serializable convert(Serializable value)
|
|
{
|
|
return DefaultTypeConverter.INSTANCE.convert(MLText.class, value);
|
|
}
|
|
},
|
|
CONTENT
|
|
{
|
|
@Override
|
|
public Integer getOrdinalNumber()
|
|
{
|
|
return Integer.valueOf(11);
|
|
}
|
|
|
|
@Override
|
|
protected ValueType getPersistedType(Serializable value)
|
|
{
|
|
return ValueType.STRING;
|
|
}
|
|
|
|
@Override
|
|
Serializable convert(Serializable value)
|
|
{
|
|
return DefaultTypeConverter.INSTANCE.convert(ContentData.class, value);
|
|
}
|
|
},
|
|
NODEREF
|
|
{
|
|
@Override
|
|
public Integer getOrdinalNumber()
|
|
{
|
|
return Integer.valueOf(12);
|
|
}
|
|
|
|
@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
|
|
public Integer getOrdinalNumber()
|
|
{
|
|
return Integer.valueOf(13);
|
|
}
|
|
|
|
@Override
|
|
protected ValueType getPersistedType(Serializable value)
|
|
{
|
|
return ValueType.STRING;
|
|
}
|
|
|
|
@Override
|
|
Serializable convert(Serializable value)
|
|
{
|
|
return DefaultTypeConverter.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 DefaultTypeConverter.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 DefaultTypeConverter.INSTANCE.convert(QName.class, value);
|
|
}
|
|
},
|
|
PATH
|
|
{
|
|
@Override
|
|
public Integer getOrdinalNumber()
|
|
{
|
|
return Integer.valueOf(16);
|
|
}
|
|
|
|
@Override
|
|
protected ValueType getPersistedType(Serializable value)
|
|
{
|
|
return ValueType.SERIALIZABLE;
|
|
}
|
|
|
|
@Override
|
|
Serializable convert(Serializable value)
|
|
{
|
|
return DefaultTypeConverter.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 DefaultTypeConverter.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 DefaultTypeConverter.INSTANCE.convert(VersionNumber.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 DefaultTypeConverter.INSTANCE#convert(Class, Object)
|
|
*/
|
|
abstract Serializable convert(Serializable value);
|
|
|
|
protected ArrayList<Serializable> convert(Collection collection)
|
|
{
|
|
ArrayList<Serializable> arrayList = new ArrayList<Serializable>(collection.size());
|
|
for (Object object : collection)
|
|
{
|
|
Serializable newValue = null;
|
|
if (object != null)
|
|
{
|
|
if (!(object instanceof Serializable))
|
|
{
|
|
throw new AlfrescoRuntimeException("Collection values must contain Serializable instances: \n" +
|
|
" value type: " + this + "\n" +
|
|
" collection: " + collection + "\n" +
|
|
" value: " + object);
|
|
}
|
|
Serializable value = (Serializable) object;
|
|
newValue = convert(value);
|
|
}
|
|
arrayList.add(newValue);
|
|
}
|
|
// done
|
|
return arrayList;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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) || (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 ContentData)
|
|
{
|
|
return ValueType.CONTENT;
|
|
}
|
|
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 Attribute)
|
|
{
|
|
return ValueType.DB_ATTRIBUTE;
|
|
}
|
|
else if (value instanceof MLText)
|
|
{
|
|
return ValueType.MLTEXT;
|
|
}
|
|
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.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);
|
|
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);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/** the type of the property, prior to serialization persistence */
|
|
private ValueType actualType;
|
|
/** true if the property values are contained in a collection */
|
|
private boolean isMultiValued;
|
|
/** the type of persistence used */
|
|
private ValueType persistedType;
|
|
|
|
private Boolean booleanValue;
|
|
private Long longValue;
|
|
private Float floatValue;
|
|
private Double doubleValue;
|
|
private String stringValue;
|
|
private Attribute attributeValue;
|
|
private Serializable serializableValue;
|
|
|
|
/**
|
|
* 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.actualType = PropertyValue.getActualType(value);
|
|
if (value == null)
|
|
{
|
|
setPersistedValue(ValueType.NULL, null);
|
|
setMultiValued(false);
|
|
}
|
|
else if (value instanceof Collection)
|
|
{
|
|
Collection collection = (Collection) value;
|
|
ValueType collectionValueType = makeValueType(typeQName);
|
|
// convert the collection values - we need to do this to ensure that the
|
|
// values provided conform to the given type
|
|
ArrayList<Serializable> convertedCollection = collectionValueType.convert(collection);
|
|
// the persisted type is, nonetheless, a serializable
|
|
setPersistedValue(ValueType.SERIALIZABLE, convertedCollection);
|
|
setMultiValued(true);
|
|
}
|
|
else
|
|
{
|
|
// Does the client consider the type to be important?
|
|
if (typeQName != null)
|
|
{
|
|
// 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);
|
|
}
|
|
// get the persisted type
|
|
ValueType persistedValueType = this.actualType.getPersistedType(value);
|
|
// convert to the persistent type
|
|
value = persistedValueType.convert(value);
|
|
setPersistedValue(persistedValueType, value);
|
|
setMultiValued(false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
|
|
@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.attributeValue, that.attributeValue) &&
|
|
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 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 boolean isMultiValued()
|
|
{
|
|
return isMultiValued;
|
|
}
|
|
|
|
public void setMultiValued(boolean isMultiValued)
|
|
{
|
|
this.isMultiValued = isMultiValued;
|
|
}
|
|
|
|
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 DB_ATTRIBUTE:
|
|
this.attributeValue = (Attribute) value;
|
|
break;
|
|
case SERIALIZABLE:
|
|
this.serializableValue = cloneSerializable(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 PropertyValue.STRING_EMPTY;
|
|
}
|
|
else
|
|
{
|
|
return this.stringValue;
|
|
}
|
|
case DB_ATTRIBUTE:
|
|
return this.attributeValue;
|
|
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)
|
|
{
|
|
// 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<Serializable> convertedCollection = requiredType.convert(collection);
|
|
ret = convertedCollection;
|
|
}
|
|
else
|
|
{
|
|
Serializable persistedValue = getPersistedValue();
|
|
// convert the type
|
|
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)
|
|
*/
|
|
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 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 void setStringValue(String value)
|
|
{
|
|
this.stringValue = value;
|
|
}
|
|
|
|
public Attribute getAttributeValue()
|
|
{
|
|
return attributeValue;
|
|
}
|
|
public void setAttributeValue(Attribute value)
|
|
{
|
|
this.attributeValue = value;
|
|
}
|
|
|
|
public Serializable getSerializableValue()
|
|
{
|
|
return serializableValue;
|
|
}
|
|
public void setSerializableValue(Serializable value)
|
|
{
|
|
this.serializableValue = value;
|
|
}
|
|
}
|