Nested collection (maps, sets, lists) property retrieval

- Single-shot query retrieves ordered view
 - MLText, HashMap and ArrayList currently supported


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@15783 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2009-08-18 10:26:43 +00:00
parent ec7a10b7f8
commit f00d441cae
9 changed files with 462 additions and 264 deletions

View File

@@ -68,24 +68,24 @@ CREATE TABLE alf_prop_value
PRIMARY KEY (id) PRIMARY KEY (id)
) ENGINE=InnoDB; ) ENGINE=InnoDB;
CREATE TABLE alf_prop_collection_link CREATE TABLE alf_prop_link
( (
root_coll_prop_id BIGINT NOT NULL, root_prop_id BIGINT NOT NULL,
curr_coll_prop_id BIGINT NOT NULL, current_prop_id BIGINT NOT NULL,
key_prop_id BIGINT NOT NULL,
value_prop_id BIGINT NOT NULL, value_prop_id BIGINT NOT NULL,
INDEX idx_alf_prop_coll_rev (value_prop_id, root_coll_prop_id), key_prop_id BIGINT NOT NULL,
PRIMARY KEY (root_coll_prop_id, curr_coll_prop_id, key_prop_id, value_prop_id) INDEX idx_alf_prop_coll_rev (value_prop_id, root_prop_id),
PRIMARY KEY (root_prop_id, current_prop_id, value_prop_id, key_prop_id)
) ENGINE=InnoDB; ) ENGINE=InnoDB;
-- --
-- Record script finish -- Record script finish
-- --
DELETE FROM alf_applied_patch WHERE id = 'patch.db-V3.3-PropertyValueTables'; -- DELETE FROM alf_applied_patch WHERE id = 'patch.db-V3.3-PropertyValueTables';
INSERT INTO alf_applied_patch -- INSERT INTO alf_applied_patch
(id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) -- (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report)
VALUES -- VALUES
( -- (
'patch.db-V3.3-PropertyValueTables', 'Manually executed script upgrade V3.3: PropertyValue Tables', -- 'patch.db-V3.3-PropertyValueTables', 'Manually executed script upgrade V3.3: PropertyValue Tables',
0, 3000, -1, 3001, null, 'UNKOWN', 1, 1, 'Script completed' -- 0, 3000, -1, 3001, null, 'UNKOWN', 1, 1, 'Script completed'
); -- );

View File

@@ -15,7 +15,8 @@
<typeAlias alias="PropertyStringValue" type="org.alfresco.repo.domain.propval.PropertyStringValueEntity"/> <typeAlias alias="PropertyStringValue" type="org.alfresco.repo.domain.propval.PropertyStringValueEntity"/>
<typeAlias alias="PropertyDoubleValue" type="org.alfresco.repo.domain.propval.PropertyDoubleValueEntity"/> <typeAlias alias="PropertyDoubleValue" type="org.alfresco.repo.domain.propval.PropertyDoubleValueEntity"/>
<typeAlias alias="PropertyValue" type="org.alfresco.repo.domain.propval.PropertyValueEntity"/> <typeAlias alias="PropertyValue" type="org.alfresco.repo.domain.propval.PropertyValueEntity"/>
<typeAlias alias="PropertyCollectionLink" type="org.alfresco.repo.domain.propval.PropertyCollectionLinkEntity"/> <typeAlias alias="PropertyLink" type="org.alfresco.repo.domain.propval.PropertyLinkEntity"/>
<typeAlias alias="PropertyIdSearchRow" type="org.alfresco.repo.domain.propval.PropertyIdSearchRow"/>
<!-- --> <!-- -->
<!-- Result Maps --> <!-- Result Maps -->
@@ -57,27 +58,29 @@
<result property="stringValue" column="string_value" jdbcType="VARCHAR" javaType="java.lang.String"/> <result property="stringValue" column="string_value" jdbcType="VARCHAR" javaType="java.lang.String"/>
<result property="serializableValue" column="serializable_value" jdbcType="BLOB" javaType="java.lang.Object"/> <result property="serializableValue" column="serializable_value" jdbcType="BLOB" javaType="java.lang.Object"/>
</resultMap> </resultMap>
<resultMap id="result.PropertyCollectionLink" class="PropertyCollectionLink"> <resultMap id="result.PropertyLink" class="PropertyLink">
<result property="rootCollectionPropId" column="root_coll_prop_id" jdbcType="BIGINT" javaType="long"/> <result property="rootPropId" column="root_prop_id" jdbcType="BIGINT" javaType="long"/>
<result property="currentCollectionPropId" column="persisted_type" jdbcType="BIGINT" javaType="long"/> <result property="currentPropId" column="current_prop_id" jdbcType="BIGINT" javaType="long"/>
<result property="keyPropId" column="key_prop_id" jdbcType="BIGINT" javaType="long"/>
<result property="valuePropId" column="value_prop_id" jdbcType="BIGINT" javaType="long"/> <result property="valuePropId" column="value_prop_id" jdbcType="BIGINT" javaType="long"/>
<result property="keyPropId" column="key_prop_id" jdbcType="BIGINT" javaType="long"/>
</resultMap>
<resultMap id="result.PropertyIdSearchRow" class="PropertyIdSearchRow">
<result property="rootPropId" column="root_prop_id" jdbcType="BIGINT" javaType="long"/>
<result property="currentPropId" column="current_prop_id" jdbcType="BIGINT" javaType="long"/>
<result property="valuePropId" column="value_prop_id" jdbcType="BIGINT" javaType="long"/>
<result property="keyPropId" column="key_prop_id" jdbcType="BIGINT" javaType="long"/>
<result property="actualTypeId" column="actual_type_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="persistedType" column="persisted_type" jdbcType="TINYINT" javaType="java.lang.Short"/>
<result property="longValue" column="long_value" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="doubleValue" column="double_value" jdbcType="DOUBLE" javaType="java.lang.Double"/>
<result property="stringValue" column="string_value" jdbcType="VARCHAR" javaType="java.lang.String"/>
<result property="serializableValue" column="serializable_value" jdbcType="BLOB" javaType="java.lang.Object"/>
</resultMap> </resultMap>
<!-- --> <!-- -->
<!-- Parameter Maps --> <!-- Parameter Maps -->
<!-- --> <!-- -->
<!--
<parameterMap id="parameter.ExclusiveLockUpdateMap" class="map">
<parameter property="newLockToken" jdbcType="VARCHAR" javaType="java.lang.String"/>
<parameter property="newStartTime" jdbcType="BIGINT" javaType="java.lang.Long"/>
<parameter property="newExpiryTime" jdbcType="BIGINT" javaType="java.lang.Long"/>
<parameter property="exclusiveLockResourceId" jdbcType="BIGINT" javaType="java.lang.Long"/>
<parameter property="oldLockToken" jdbcType="VARCHAR" javaType="java.lang.String"/>
</parameterMap>
-->
<!-- --> <!-- -->
<!-- SQL Snippets --> <!-- SQL Snippets -->
<!-- --> <!-- -->
@@ -260,32 +263,29 @@
</select> </select>
<!-- Get the property value by ID --> <!-- Get the property value by ID -->
<select id="select.PropertyValueById" parameterClass="PropertyValue" resultMap="result.PropertyValue"> <select id="select.PropertyValueById" parameterClass="PropertyValue" resultMap="result.PropertyIdSearchRow">
select select
pv.id as id, cl.root_prop_id, cl.current_prop_id, cl.value_prop_id, cl.key_prop_id,
pv.actual_type_id as actual_type_id, v.actual_type_id, v.persisted_type,
pv.persisted_type as persisted_type, v.long_value, dv.double_value, sv.string_value, serv.serializable_value
pv.long_value as long_value,
dv.double_value as double_value,
sv.string_value as string_value,
serv.serializable_value as serializable_value
from from
alf_prop_value pv alf_prop_link cl
left join alf_prop_double_value dv on (dv.id = pv.long_value and pv.persisted_type = 2) join alf_prop_value v on (cl.value_prop_id = v.id)
left join alf_prop_string_value sv on (sv.id = pv.long_value and pv.persisted_type = 3) left join alf_prop_double_value dv on (dv.id = v.long_value and v.persisted_type = 2)
left join alf_prop_serializable_value serv on (serv.id = pv.long_value and pv.persisted_type = 4) left join alf_prop_string_value sv on (sv.id = v.long_value and (v.persisted_type = 3 || v.persisted_type = 5))
where left join alf_prop_serializable_value serv on (serv.id = v.long_value and v.persisted_type = 4)
pv.id = #id# where cl.root_prop_id = #id#
order by current_prop_id, value_prop_id, key_prop_id
</select> </select>
<insert id="insert.PropertyCollectionLink" parameterClass="PropertyCollectionLink" > <insert id="insert.PropertyLink" parameterClass="PropertyLink" >
insert into alf_prop_collection_link insert into alf_prop_link
( (
root_coll_prop_id, curr_coll_prop_id, key_prop_id, value_prop_id root_prop_id, current_prop_id, value_prop_id, key_prop_id
) )
values values
( (
#rootCollectionPropId#, #currentCollectionPropId#, #keyPropId#, #valuePropId# #rootPropId#, #currentPropId#, #valuePropId#, #keyPropId#
) )
</insert> </insert>

View File

@@ -30,6 +30,7 @@ import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -41,6 +42,8 @@ import org.alfresco.repo.domain.CrcHelper;
import org.alfresco.repo.domain.propval.PropertyValueEntity.PersistedType; import org.alfresco.repo.domain.propval.PropertyValueEntity.PersistedType;
import org.alfresco.service.cmr.repository.MLText; import org.alfresco.service.cmr.repository.MLText;
import org.alfresco.util.Pair; import org.alfresco.util.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/** /**
* Abstract implementation for Property Value DAO. * Abstract implementation for Property Value DAO.
@@ -59,6 +62,8 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
private static final String CACHE_REGION_PROPERTY_DOUBLE_VALUE = "PropertyDoubleValue"; private static final String CACHE_REGION_PROPERTY_DOUBLE_VALUE = "PropertyDoubleValue";
private static final String CACHE_REGION_PROPERTY_VALUE = "PropertyValue"; private static final String CACHE_REGION_PROPERTY_VALUE = "PropertyValue";
private static final Log logger = LogFactory.getLog(AbstractPropertyValueDAOImpl.class);
protected PropertyTypeConverter converter; protected PropertyTypeConverter converter;
private final PropertyClassCallbackDAO propertyClassDaoCallback; private final PropertyClassCallbackDAO propertyClassDaoCallback;
@@ -626,6 +631,29 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
*/ */
private class PropertyValueCallbackDAO extends EntityLookupCallbackDAOAdaptor<Long, Serializable, Serializable> private class PropertyValueCallbackDAO extends EntityLookupCallbackDAOAdaptor<Long, Serializable, Serializable>
{ {
private final Serializable convertToValue(PropertyValueEntity entity)
{
if (entity == null)
{
return null;
}
final Serializable actualValue;
if (entity.getPersistedTypeEnum() == PersistedType.CONSTRUCTABLE)
{
actualValue = entity.getPersistedValue();
}
else
{
Long actualTypeId = entity.getActualTypeId();
Class<?> actualType = getPropertyClassById(actualTypeId).getSecond();
Serializable entityValue = entity.getPersistedValue();
actualValue = (Serializable) converter.convert(actualType, entityValue);
}
// Done
return actualValue;
}
private final Pair<Long, Serializable> convertEntityToPair(PropertyValueEntity entity) private final Pair<Long, Serializable> convertEntityToPair(PropertyValueEntity entity)
{ {
if (entity == null) if (entity == null)
@@ -633,13 +661,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
return null; return null;
} }
Long entityId = entity.getId(); Long entityId = entity.getId();
Serializable entityValue = entity.getPersistedValue(); Serializable actualValue = convertToValue(entity);
// Dig out the class to convert the value to i.e. the actual type of the value
Long actualTypeId = entity.getActualTypeId();
Class<?> actualType = getPropertyClassById(actualTypeId).getSecond();
// Convert it
Serializable actualValue = (Serializable) converter.convert(actualType, entityValue);
// Done // Done
return new Pair<Long, Serializable>(entityId, actualValue); return new Pair<Long, Serializable>(entityId, actualValue);
} }
@@ -669,15 +691,21 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
PropertyValueEntity entity = createPropertyValue(value); PropertyValueEntity entity = createPropertyValue(value);
Long entityId = entity.getId(); Long entityId = entity.getId();
// Create the link entry for the property // Create the link entry for the property
createPropertyLink(entityId, entityId, 0L, entityId); createPropertyLink(entityId, entityId, entityId, 0L);
// Done // Done
return new Pair<Long, Serializable>(entity.getId(), value); return new Pair<Long, Serializable>(entity.getId(), value);
} }
public Pair<Long, Serializable> findByKey(Long key) public Pair<Long, Serializable> findByKey(Long key)
{ {
PropertyValueEntity entity = findPropertyValueById(key); List<PropertyIdSearchRow> rows = findPropertyValueById(key);
return convertEntityToPair(entity); if (rows.size() == 0)
{
// No results
return null;
}
Serializable value = convertPropertyIdSearchRows(rows);
return new Pair<Long, Serializable>(key, value);
} }
public Pair<Long, Serializable> findByValue(Serializable value) public Pair<Long, Serializable> findByValue(Serializable value)
@@ -694,10 +722,102 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
} }
} }
protected abstract PropertyValueEntity findPropertyValueById(Long id); protected abstract List<PropertyIdSearchRow> findPropertyValueById(Long id);
protected abstract PropertyValueEntity findPropertyValueByValue(Serializable value); protected abstract PropertyValueEntity findPropertyValueByValue(Serializable value);
protected abstract PropertyValueEntity createPropertyValue(Serializable value); protected abstract PropertyValueEntity createPropertyValue(Serializable value);
@SuppressWarnings("unchecked")
protected Serializable convertPropertyIdSearchRows(List<PropertyIdSearchRow> rows)
{
/*
* The results are ordered by the root_prop_id, current_prop_id and value_prop_id.
* However, for safety (data patching, etc) we take a first pass to create the
* basic properties before hooking them all together in a second pass.
*/
final Map<Long, Serializable> values = new HashMap<Long, Serializable>(rows.size());
List<PropertyLinkEntity> keyRows = new ArrayList<PropertyLinkEntity>(5);
Serializable result = null;
for (PropertyIdSearchRow row : rows)
{
PropertyLinkEntity linkEntity = row.getLinkEntity();
PropertyValueEntity valueEntity = row.getValueEntity();
// Construct the value (Maps and Collections should be CONSTRUCTABLE)
Serializable value = propertyValueCallback.convertToValue(valueEntity);
// Keep it
values.put(new Long(linkEntity.getValuePropId()), value);
// If this row is a mapping row (the property value ID does not match the current property ID)
// then store it for quicker use later
if (linkEntity.getCurrentPropId() != linkEntity.getValuePropId())
{
keyRows.add(linkEntity);
}
if (linkEntity.getRootPropId() == linkEntity.getValuePropId())
{
// We found the root
result = value;
}
}
// We expect a value to be found unless the results are empty
if (result == null)
{
return null;
}
// Now we have all our constructed values and the mapping rows: build the collections
for (PropertyLinkEntity propertyLinkEntity : keyRows)
{
Serializable value = values.get(propertyLinkEntity.getValuePropId());
Serializable currentProp = values.get(propertyLinkEntity.getCurrentPropId());
if (value == null)
{
logger.error("No value found for link property: " + propertyLinkEntity);
continue;
}
if (currentProp == null)
{
logger.error("No current property found for link property: " + propertyLinkEntity);
continue;
}
Long keyId = propertyLinkEntity.getKeyPropId();
// put the value into the container
if (currentProp instanceof Map<?, ?>)
{
Pair<Long, Serializable> keyPair = getPropertyValueById(keyId);
if (keyPair == null)
{
logger.error("Current property (map) has key without a value: " + propertyLinkEntity);
continue;
}
Serializable key = keyPair.getSecond();
Map<Serializable, Serializable> map = (Map<Serializable, Serializable>) currentProp;
map.put(key, value);
}
else if (currentProp instanceof Set<?>)
{
// We can ignore the key - it won't make a difference
Set<Serializable> set = (Set<Serializable>) currentProp;
set.add(value);
}
// Results 'should' be ordered by key
// else if (currentProp instanceof List<?>)
// {
// // The order is important
// List<Serializable> collection = (List<Serializable>) currentProp;
// collection.add(keyId.intValue(), value);
// }
else if (currentProp instanceof Collection<?>)
{
// The order is important
Collection<Serializable> collection = (Collection<Serializable>) currentProp;
collection.add(value);
}
}
// This will have put the values into the correct containers
return result;
}
//================================ //================================
// Special handling of maps and collections // Special handling of maps and collections
//================================ //================================
@@ -726,7 +846,10 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
{ {
clazz = HashMap.class; clazz = HashMap.class;
} }
Long entityId = createPropertyMapRoot(clazz);
// Can't use a cached instance as each map is unique. Go direct to entity creation.
Long entityId = createPropertyValue(clazz).getId();
// Use this as the root if this is the first entry into this method // Use this as the root if this is the first entry into this method
if (rootId == null) if (rootId == null)
{ {
@@ -734,7 +857,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
} }
// Create the link entry for the root // Create the link entry for the root
createPropertyLink(rootId, entityId, 0L, entityId); createPropertyLink(rootId, entityId, entityId, 0L);
// Now iterate over the entries and create properties for the keys and values // Now iterate over the entries and create properties for the keys and values
for (Map.Entry<K, V> entry : map.entrySet()) for (Map.Entry<K, V> entry : map.entrySet())
@@ -752,7 +875,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
Long valueId = valuePair.getFirst(); Long valueId = valuePair.getFirst();
// Now write the mapping entry // Now write the mapping entry
createPropertyLink(rootId, entityId, keyId, valueId); createPropertyLink(rootId, entityId, valueId, keyId);
} }
// Done // Done
@@ -783,7 +906,10 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
{ {
clazz = ArrayList.class; clazz = ArrayList.class;
} }
Long entityId = createPropertyCollectionRoot(clazz);
// Can't use a cached instance as each collection is unique. Go direct to entity creation.
Long entityId = createPropertyValue(clazz).getId();
// Use this as the root if this is the first entry into this method // Use this as the root if this is the first entry into this method
if (rootId == null) if (rootId == null)
{ {
@@ -791,7 +917,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
} }
// Create the link entry for the root // Create the link entry for the root
createPropertyLink(rootId, entityId, 0L, entityId); createPropertyLink(rootId, entityId, entityId, 0L);
// Now iterate over the entries and create properties for the keys and values // Now iterate over the entries and create properties for the keys and values
long index = 0L; long index = 0L;
@@ -806,48 +932,24 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
Long valueId = valuePair.getFirst(); Long valueId = valuePair.getFirst();
// Now write the mapping entry // Now write the mapping entry
Long keyId = new Long(index); Long keyId = new Long(index);
createPropertyLink(rootId, entityId, keyId, valueId); createPropertyLink(rootId, entityId, valueId, keyId);
// Keep iterating // Keep iterating
index++; index++;
} }
return entityId; return entityId;
} }
/**
* Create a property value entry for <b>maps</b>. The class is assumed to
* be correct with a default constructor for later reconstruction.
* <p/>
* This method must not create the associated link property.
*
* @param clazz The map instance that must be used for re-instantiation.
* This must be an instance derived from {@link Map} with a default constructor.
* @return Returns the newly-created property ID
*/
protected abstract Long createPropertyMapRoot(Class<?> clazz);
/**
* Create a property value entry for <b>collections</b>. The class is assumed to
* be correct with a default constructor for later reconstruction.
* <p/>
* This method must not create the associated link property.
*
* @param clazz The collection instance that must be used for re-instantiation.
* This must be an instance derived from {@link Collection} with a default constructor.
* @return Returns the newly-created property ID
*/
protected abstract Long createPropertyCollectionRoot(Class<?> clazz);
/** /**
* Create an entry for the map or collection link * Create an entry for the map or collection link
* *
* @param rootCollectionId the root (entry-point) map or collection ID * @param rootPropId the root (entry-point) map or collection ID
* @param currentCollectionId the current map or collection ID * @param currentPropId the current map or collection ID
* @param keyId the map key entity ID or collection position count
* @param valueId the ID of the entity storing the value (may be another map or collection) * @param valueId the ID of the entity storing the value (may be another map or collection)
* @param keyId the map key entity ID or collection position count
*/ */
protected abstract void createPropertyLink( protected abstract void createPropertyLink(
Long rootCollectionId, Long rootPropId,
Long currentCollectionId, Long currentPropId,
Long keyId, Long valueId,
Long valueId); Long keyId);
} }

View File

@@ -0,0 +1,144 @@
/*
* Copyright (C) 2005-2009 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.propval;
import java.io.Serializable;
/**
* Entity bean search results from <b>alf_prop_collections_link</b> and <b>alf_prop_value</b> tables.
*
* @author Derek Hulley
* @since 3.3
*/
public class PropertyIdSearchRow
{
private final PropertyLinkEntity linkEntity;
private final PropertyValueEntity valueEntity;
public PropertyIdSearchRow()
{
linkEntity = new PropertyLinkEntity();
valueEntity = new PropertyValueEntity();
}
// @Override
// public int hashCode()
// {
// return (int) rootCollectionPropId + (int) valuePropId;
// }
//
// @Override
// public boolean equals(Object obj)
// {
// if (this == obj)
// {
// return true;
// }
// else if (obj instanceof PropertyCollectionLinkEntity)
// {
// PropertyCollectionLinkEntity that = (PropertyCollectionLinkEntity) obj;
// return
// this.rootCollectionPropId == that.rootCollectionPropId &&
// this.currentCollectionPropId == that.currentCollectionPropId &&
// this.valuePropId == that.valuePropId &&
// this.keyPropId == that.keyPropId;
// }
// else
// {
// return false;
// }
// }
//
@Override
public String toString()
{
StringBuilder sb = new StringBuilder(512);
sb.append("PropertyIdSearchRow")
.append("[ ").append(linkEntity)
.append(", ").append(valueEntity)
.append("]");
return sb.toString();
}
public PropertyLinkEntity getLinkEntity()
{
return linkEntity;
}
public PropertyValueEntity getValueEntity()
{
return valueEntity;
}
public void setRootPropId(long rootPropId)
{
linkEntity.setRootPropId(rootPropId);
}
public void setCurrentPropId(long currentPropId)
{
linkEntity.setCurrentPropId(currentPropId);
}
public void setValuePropId(long valuePropId)
{
linkEntity.setValuePropId(valuePropId);
}
public void setKeyPropId(long keyPropId)
{
linkEntity.setKeyPropId(keyPropId);
}
public void setActualTypeId(Long actualTypeId)
{
valueEntity.setActualTypeId(actualTypeId);
}
public void setPersistedType(Short persistedType)
{
valueEntity.setPersistedType(persistedType);
}
public void setLongValue(Long longValue)
{
valueEntity.setLongValue(longValue);
}
public void setStringValue(String stringValue)
{
valueEntity.setStringValue(stringValue);
}
public void setDoubleValue(Double doubleValue)
{
valueEntity.setDoubleValue(doubleValue);
}
public void setSerializableValue(Serializable serializableValue)
{
valueEntity.setSerializableValue(serializableValue);
}
}

View File

@@ -25,26 +25,26 @@
package org.alfresco.repo.domain.propval; package org.alfresco.repo.domain.propval;
/** /**
* Entity bean for <b>alf_prop_collections_link</b> table. * Entity bean for <b>alf_prop_link</b> table.
* *
* @author Derek Hulley * @author Derek Hulley
* @since 3.3 * @since 3.3
*/ */
public class PropertyCollectionLinkEntity public class PropertyLinkEntity
{ {
private long rootCollectionPropId; private long rootPropId;
private long currentCollectionPropId; private long currentPropId;
private long keyPropId;
private long valuePropId; private long valuePropId;
private long keyPropId;
public PropertyCollectionLinkEntity() public PropertyLinkEntity()
{ {
} }
@Override @Override
public int hashCode() public int hashCode()
{ {
return (int) rootCollectionPropId + (int) valuePropId; return (int) rootPropId + (int) valuePropId;
} }
@Override @Override
@@ -54,14 +54,14 @@ public class PropertyCollectionLinkEntity
{ {
return true; return true;
} }
else if (obj instanceof PropertyCollectionLinkEntity) else if (obj instanceof PropertyLinkEntity)
{ {
PropertyCollectionLinkEntity that = (PropertyCollectionLinkEntity) obj; PropertyLinkEntity that = (PropertyLinkEntity) obj;
return return
this.rootCollectionPropId == that.rootCollectionPropId && this.rootPropId == that.rootPropId &&
this.currentCollectionPropId == that.currentCollectionPropId && this.currentPropId == that.currentPropId &&
this.keyPropId == that.keyPropId && this.valuePropId == that.valuePropId &&
this.valuePropId == that.valuePropId; this.keyPropId == that.keyPropId;
} }
else else
{ {
@@ -73,42 +73,33 @@ public class PropertyCollectionLinkEntity
public String toString() public String toString()
{ {
StringBuilder sb = new StringBuilder(512); StringBuilder sb = new StringBuilder(512);
sb.append("PropertyCollectionsLinkEntity") sb.append("PropertyLinkEntity")
.append("[ currentCollectionPropId=").append(currentCollectionPropId) .append("[ rootPropId=").append(rootPropId)
.append(", keyPropId=").append(keyPropId) .append(", currentPropId=").append(currentPropId)
.append(", valuePropId=").append(valuePropId) .append(", valuePropId=").append(valuePropId)
.append(", keyPropId=").append(keyPropId)
.append("]"); .append("]");
return sb.toString(); return sb.toString();
} }
public long getRootCollectionPropId() public long getRootPropId()
{ {
return rootCollectionPropId; return rootPropId;
} }
public void setRootCollectionPropId(long rootCollectionPropId) public void setRootPropId(long rootPropId)
{ {
this.rootCollectionPropId = rootCollectionPropId; this.rootPropId = rootPropId;
} }
public long getCurrentCollectionPropId() public long getCurrentPropId()
{ {
return currentCollectionPropId; return currentPropId;
} }
public void setCurrentCollectionPropId(long currentCollectionPropId) public void setCurrentPropId(long currentPropId)
{ {
this.currentCollectionPropId = currentCollectionPropId; this.currentPropId = currentPropId;
}
public long getKeyPropId()
{
return keyPropId;
}
public void setKeyPropId(long keyPropId)
{
this.keyPropId = keyPropId;
} }
public long getValuePropId() public long getValuePropId()
@@ -120,4 +111,14 @@ public class PropertyCollectionLinkEntity
{ {
this.valuePropId = valuePropId; this.valuePropId = valuePropId;
} }
public long getKeyPropId()
{
return keyPropId;
}
public void setKeyPropId(long keyPropId)
{
this.keyPropId = keyPropId;
}
} }

View File

@@ -26,9 +26,7 @@ package org.alfresco.repo.domain.propval;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import java.util.Map;
import org.alfresco.repo.action.evaluator.compare.PropertyValueComparator;
import org.alfresco.util.Pair; import org.alfresco.util.Pair;
/** /**

View File

@@ -64,6 +64,18 @@ public class PropertyValueDAOTest extends TestCase
txnHelper = transactionService.getRetryingTransactionHelper(); txnHelper = transactionService.getRetryingTransactionHelper();
propertyValueDAO = (PropertyValueDAO) ctx.getBean("propertyValueDAO"); propertyValueDAO = (PropertyValueDAO) ctx.getBean("propertyValueDAO");
// Remove the caches to test all functionality
removeCaches();
}
private void removeCaches()
{
((AbstractPropertyValueDAOImpl)propertyValueDAO).setPropertyClassCache(null);
((AbstractPropertyValueDAOImpl)propertyValueDAO).setPropertyDateValueCache(null);
((AbstractPropertyValueDAOImpl)propertyValueDAO).setPropertyDoubleValueCache(null);
((AbstractPropertyValueDAOImpl)propertyValueDAO).setPropertyStringValueCache(null);
((AbstractPropertyValueDAOImpl)propertyValueDAO).setPropertyValueCache(null);
} }
public void testPropertyClass() throws Exception public void testPropertyClass() throws Exception
@@ -238,6 +250,13 @@ public class PropertyValueDAOTest extends TestCase
* Tests that the given value can be persisted and retrieved with the same resulting ID * Tests that the given value can be persisted and retrieved with the same resulting ID
*/ */
private void runPropertyValueTest(final Serializable value) throws Exception private void runPropertyValueTest(final Serializable value) throws Exception
{
runPropertyValueTest(value, true);
}
/**
* Tests that the given value can be persisted and retrieved with the same resulting ID
*/
private void runPropertyValueTest(final Serializable value, final boolean runValueRetrieval) throws Exception
{ {
// Create it (if it doesn't exist) // Create it (if it doesn't exist)
RetryingTransactionCallback<Pair<Long, Serializable>> createValueCallback = new RetryingTransactionCallback<Pair<Long, Serializable>>() RetryingTransactionCallback<Pair<Long, Serializable>> createValueCallback = new RetryingTransactionCallback<Pair<Long, Serializable>>()
@@ -252,6 +271,8 @@ public class PropertyValueDAOTest extends TestCase
assertNotNull(entityPair); assertNotNull(entityPair);
assertEquals(value, entityPair.getSecond()); assertEquals(value, entityPair.getSecond());
if (runValueRetrieval)
{
// Retrieve it by value // Retrieve it by value
RetryingTransactionCallback<Pair<Long, Serializable>> getValueCallback = new RetryingTransactionCallback<Pair<Long, Serializable>>() RetryingTransactionCallback<Pair<Long, Serializable>> getValueCallback = new RetryingTransactionCallback<Pair<Long, Serializable>>()
{ {
@@ -264,6 +285,7 @@ public class PropertyValueDAOTest extends TestCase
final Pair<Long, Serializable> entityPairCheck = txnHelper.doInTransaction(getValueCallback, false); final Pair<Long, Serializable> entityPairCheck = txnHelper.doInTransaction(getValueCallback, false);
assertNotNull(entityPairCheck); assertNotNull(entityPairCheck);
assertEquals(entityPair, entityPairCheck); assertEquals(entityPair, entityPairCheck);
}
// Retrieve it by ID // Retrieve it by ID
RetryingTransactionCallback<Pair<Long, Serializable>> getByIdCallback = new RetryingTransactionCallback<Pair<Long, Serializable>>() RetryingTransactionCallback<Pair<Long, Serializable>> getByIdCallback = new RetryingTransactionCallback<Pair<Long, Serializable>>()
@@ -356,15 +378,7 @@ public class PropertyValueDAOTest extends TestCase
String value = "MAP-VALUE-" + i; String value = "MAP-VALUE-" + i;
map.put(key, value); map.put(key, value);
} }
RetryingTransactionCallback<Void> createCallback = new RetryingTransactionCallback<Void>() runPropertyValueTest(map, false);
{
public Void execute() throws Throwable
{
propertyValueDAO.getOrCreatePropertyValue(map);
return null;
}
};
transactionService.getRetryingTransactionHelper().doInTransaction(createCallback);
} }
public void testPropertyValue_MapOfMapOfStrings() throws Exception public void testPropertyValue_MapOfMapOfStrings() throws Exception
@@ -382,15 +396,7 @@ public class PropertyValueDAOTest extends TestCase
String key = "OUTERMAP-KEY-" + i; String key = "OUTERMAP-KEY-" + i;
mapOuter.put(key, mapInner); mapOuter.put(key, mapInner);
} }
RetryingTransactionCallback<Void> createCallback = new RetryingTransactionCallback<Void>() runPropertyValueTest(mapOuter, false);
{
public Void execute() throws Throwable
{
propertyValueDAO.getOrCreatePropertyValue(mapOuter);
return null;
}
};
transactionService.getRetryingTransactionHelper().doInTransaction(createCallback);
} }
public void testPropertyValue_CollectionOfStrings() throws Exception public void testPropertyValue_CollectionOfStrings() throws Exception
@@ -401,24 +407,7 @@ public class PropertyValueDAOTest extends TestCase
String value = "COLL-VALUE-" + i; String value = "COLL-VALUE-" + i;
list.add(value); list.add(value);
} }
RetryingTransactionCallback<Void> createCallback = new RetryingTransactionCallback<Void>() runPropertyValueTest(list, false);
{
public Void execute() throws Throwable
{
propertyValueDAO.getOrCreatePropertyValue(list);
return null;
}
};
transactionService.getRetryingTransactionHelper().doInTransaction(createCallback);
}
private void removeCaches()
{
((AbstractPropertyValueDAOImpl)propertyValueDAO).setPropertyClassCache(null);
((AbstractPropertyValueDAOImpl)propertyValueDAO).setPropertyDateValueCache(null);
((AbstractPropertyValueDAOImpl)propertyValueDAO).setPropertyDoubleValueCache(null);
((AbstractPropertyValueDAOImpl)propertyValueDAO).setPropertyStringValueCache(null);
((AbstractPropertyValueDAOImpl)propertyValueDAO).setPropertyValueCache(null);
} }
public void testPropertyClass_NoCache() throws Exception public void testPropertyClass_NoCache() throws Exception

View File

@@ -56,8 +56,7 @@ public class PropertyValueEntity
public static final Short ORDINAL_DOUBLE = 2; public static final Short ORDINAL_DOUBLE = 2;
public static final Short ORDINAL_STRING = 3; public static final Short ORDINAL_STRING = 3;
public static final Short ORDINAL_SERIALIZABLE = 4; public static final Short ORDINAL_SERIALIZABLE = 4;
public static final Short ORDINAL_MAP = 5; public static final Short ORDINAL_CONSTRUCTABLE = 5;
public static final Short ORDINAL_COLLECTION = 6;
/** /**
* Enumeration of persisted types for <b>alf_prop_value.persisted_type</b>. * Enumeration of persisted types for <b>alf_prop_value.persisted_type</b>.
@@ -135,30 +134,17 @@ public class PropertyValueEntity
return Serializable.class; return Serializable.class;
} }
}, },
MAP CONSTRUCTABLE
{ {
@Override @Override
public Short getOrdinalNumber() public Short getOrdinalNumber()
{ {
return ORDINAL_MAP; return ORDINAL_CONSTRUCTABLE;
} }
@Override @Override
public Class<?> getAssociatedClass() public Class<?> getAssociatedClass()
{ {
return Map.class; return Class.class;
}
},
COLLECTION
{
@Override
public Short getOrdinalNumber()
{
return ORDINAL_COLLECTION;
}
@Override
public Class<?> getAssociatedClass()
{
return Collection.class;
} }
}; };
@@ -213,6 +199,7 @@ public class PropertyValueEntity
mapClass.put(Date.class, PersistedType.LONG); mapClass.put(Date.class, PersistedType.LONG);
mapClass.put(Map.class, PersistedType.SERIALIZABLE); // Will be serialized if encountered mapClass.put(Map.class, PersistedType.SERIALIZABLE); // Will be serialized if encountered
mapClass.put(Collection.class, PersistedType.SERIALIZABLE); // Will be serialized if encountered mapClass.put(Collection.class, PersistedType.SERIALIZABLE); // Will be serialized if encountered
mapClass.put(Class.class, PersistedType.CONSTRUCTABLE); // Will construct a new instance
persistedTypesByClass = Collections.unmodifiableMap(mapClass); persistedTypesByClass = Collections.unmodifiableMap(mapClass);
} }
@@ -281,8 +268,6 @@ public class PropertyValueEntity
{ {
switch (persistedTypeEnum) switch (persistedTypeEnum)
{ {
case MAP:
case COLLECTION:
case NULL: case NULL:
return null; return null;
case LONG: case LONG:
@@ -293,6 +278,21 @@ public class PropertyValueEntity
return stringValue; return stringValue;
case SERIALIZABLE: case SERIALIZABLE:
return serializableValue; return serializableValue;
case CONSTRUCTABLE:
// Construct an instance
try
{
Class<?> clazz = Class.forName(stringValue);
return (Serializable) clazz.newInstance();
}
catch (ClassNotFoundException e)
{
throw new RuntimeException("Unable to construct instance of class " + stringValue, e);
}
catch (Throwable e)
{
throw new RuntimeException("Unable to create new instance of " + stringValue, e);
}
default: default:
throw new IllegalStateException("Should not be able to get through switch"); throw new IllegalStateException("Should not be able to get through switch");
} }
@@ -304,16 +304,21 @@ public class PropertyValueEntity
* *
* @param value the value to persist (may be <tt>null</tt>) * @param value the value to persist (may be <tt>null</tt>)
* @param converter the converter that will perform and type conversion * @param converter the converter that will perform and type conversion
* @return Returns the persisted type value
*/ */
public Serializable setValue(Serializable value, PropertyTypeConverter converter) public void setValue(Serializable value, PropertyTypeConverter converter)
{ {
if (value == null) if (value == null)
{ {
this.persistedType = ORDINAL_NULL; this.persistedType = ORDINAL_NULL;
this.persistedTypeEnum = PersistedType.NULL; this.persistedTypeEnum = PersistedType.NULL;
this.longValue = LONG_ZERO; this.longValue = LONG_ZERO;
return longValue; }
else if (value instanceof Class<?>)
{
Class<?> clazz = (Class<?>) value;
stringValue = clazz.getName();
persistedTypeEnum = PersistedType.CONSTRUCTABLE;
persistedType = persistedTypeEnum.getOrdinalNumber();
} }
else else
{ {
@@ -329,16 +334,16 @@ public class PropertyValueEntity
{ {
case LONG: case LONG:
longValue = converter.convert(Long.class, value); longValue = converter.convert(Long.class, value);
return longValue; break;
case DOUBLE: case DOUBLE:
doubleValue = converter.convert(Double.class, value); doubleValue = converter.convert(Double.class, value);
return doubleValue; break;
case STRING: case STRING:
stringValue = converter.convert(String.class, value); stringValue = converter.convert(String.class, value);
return stringValue; break;
case SERIALIZABLE: case SERIALIZABLE:
serializableValue = value; serializableValue = value;
return serializableValue; break;
default: default:
throw new IllegalStateException("Should not be able to get through switch"); throw new IllegalStateException("Should not be able to get through switch");
} }

View File

@@ -25,16 +25,15 @@
package org.alfresco.repo.domain.propval.ibatis; package org.alfresco.repo.domain.propval.ibatis;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map;
import org.alfresco.repo.domain.propval.AbstractPropertyValueDAOImpl; import org.alfresco.repo.domain.propval.AbstractPropertyValueDAOImpl;
import org.alfresco.repo.domain.propval.PropertyClassEntity; import org.alfresco.repo.domain.propval.PropertyClassEntity;
import org.alfresco.repo.domain.propval.PropertyCollectionLinkEntity;
import org.alfresco.repo.domain.propval.PropertyDateValueEntity; import org.alfresco.repo.domain.propval.PropertyDateValueEntity;
import org.alfresco.repo.domain.propval.PropertyDoubleValueEntity; import org.alfresco.repo.domain.propval.PropertyDoubleValueEntity;
import org.alfresco.repo.domain.propval.PropertyIdSearchRow;
import org.alfresco.repo.domain.propval.PropertyLinkEntity;
import org.alfresco.repo.domain.propval.PropertyStringValueEntity; import org.alfresco.repo.domain.propval.PropertyStringValueEntity;
import org.alfresco.repo.domain.propval.PropertyValueEntity; import org.alfresco.repo.domain.propval.PropertyValueEntity;
import org.alfresco.repo.domain.propval.PropertyValueEntity.PersistedType; import org.alfresco.repo.domain.propval.PropertyValueEntity.PersistedType;
@@ -71,7 +70,7 @@ public class PropertyValueDAOImpl extends AbstractPropertyValueDAOImpl
private static final String SELECT_PROPERTY_VALUE_BY_STRING_VALUE = "select.PropertyValueByStringValue"; private static final String SELECT_PROPERTY_VALUE_BY_STRING_VALUE = "select.PropertyValueByStringValue";
private static final String INSERT_PROPERTY_VALUE = "insert.PropertyValue"; private static final String INSERT_PROPERTY_VALUE = "insert.PropertyValue";
private static final String INSERT_PROPERTY_COLLECTION_LINK = "insert.PropertyCollectionLink"; private static final String INSERT_PROPERTY_LINK = "insert.PropertyLink";
private SqlMapClientTemplate template; private SqlMapClientTemplate template;
@@ -255,16 +254,17 @@ public class PropertyValueDAOImpl extends AbstractPropertyValueDAOImpl
// 'alf_prop_value' accessors // 'alf_prop_value' accessors
//================================ //================================
@SuppressWarnings("unchecked")
@Override @Override
protected PropertyValueEntity findPropertyValueById(Long id) protected List<PropertyIdSearchRow> findPropertyValueById(Long id)
{ {
PropertyValueEntity entity = new PropertyValueEntity(); PropertyValueEntity entity = new PropertyValueEntity();
entity.setId(id); entity.setId(id);
entity = (PropertyValueEntity) template.queryForObject( List<PropertyIdSearchRow> results = (List<PropertyIdSearchRow>) template.queryForList(
SELECT_PROPERTY_VALUE_BY_ID, SELECT_PROPERTY_VALUE_BY_ID,
entity); entity);
// Done // Done
return entity; return results;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -367,6 +367,7 @@ public class PropertyValueDAOImpl extends AbstractPropertyValueDAOImpl
insertEntity.setLongValue(insertDoublePair.getFirst()); insertEntity.setLongValue(insertDoublePair.getFirst());
break; break;
case STRING: case STRING:
case CONSTRUCTABLE:
String stringValue = insertEntity.getStringValue(); String stringValue = insertEntity.getStringValue();
Pair<Long, String> insertStringPair = getOrCreatePropertyStringValue(stringValue); Pair<Long, String> insertStringPair = getOrCreatePropertyStringValue(stringValue);
insertEntity.setLongValue(insertStringPair.getFirst()); insertEntity.setLongValue(insertStringPair.getFirst());
@@ -391,61 +392,19 @@ public class PropertyValueDAOImpl extends AbstractPropertyValueDAOImpl
return insertEntity; return insertEntity;
} }
@Override
protected Long createPropertyMapRoot(Class<?> clazz)
{
if (!Map.class.isAssignableFrom(clazz))
{
throw new IllegalArgumentException("Map root must be a Map instance");
}
final Pair<Long, Class<?>> clazzPair = getOrCreatePropertyClass(clazz);
final Long actualTypeId = clazzPair.getFirst();
// Construct a property value to represent the collection
PropertyValueEntity insertEntity = new PropertyValueEntity();
// We have to set the persisted type manually
insertEntity.setPersistedType(PersistedType.MAP.getOrdinalNumber());
insertEntity.setLongValue(PropertyValueEntity.LONG_ZERO);
insertEntity.setActualTypeId(actualTypeId);
Long collectionId = (Long) template.insert(INSERT_PROPERTY_VALUE, insertEntity);
// Done
return collectionId;
}
@Override
protected Long createPropertyCollectionRoot(Class<?> clazz)
{
if (!Collection.class.isAssignableFrom(clazz))
{
throw new IllegalArgumentException("Collection root must be a Collection instance");
}
final Pair<Long, Class<?>> clazzPair = getOrCreatePropertyClass(clazz);
final Long actualTypeId = clazzPair.getFirst();
// Construct a property value to represent the collection
PropertyValueEntity insertEntity = new PropertyValueEntity();
// We have to set the persisted type manually
insertEntity.setPersistedType(PersistedType.COLLECTION.getOrdinalNumber());
insertEntity.setLongValue(PropertyValueEntity.LONG_ZERO);
insertEntity.setActualTypeId(actualTypeId);
Long collectionId = (Long) template.insert(INSERT_PROPERTY_VALUE, insertEntity);
// Done
return collectionId;
}
@Override @Override
protected void createPropertyLink( protected void createPropertyLink(
Long rootCollectionId, Long rootPropId,
Long currentCollectionId, Long currentPropId,
Long keyId, Long valueId,
Long valueId) Long keyId)
{ {
PropertyCollectionLinkEntity insertEntity = new PropertyCollectionLinkEntity(); PropertyLinkEntity insertEntity = new PropertyLinkEntity();
insertEntity.setRootCollectionPropId(rootCollectionId); insertEntity.setRootPropId(rootPropId);
insertEntity.setCurrentCollectionPropId(currentCollectionId); insertEntity.setCurrentPropId(currentPropId);
insertEntity.setKeyPropId(keyId);
insertEntity.setValuePropId(valueId); insertEntity.setValuePropId(valueId);
template.insert(INSERT_PROPERTY_COLLECTION_LINK, insertEntity); insertEntity.setKeyPropId(keyId);
template.insert(INSERT_PROPERTY_LINK, insertEntity);
// Done // Done
} }
} }