PropertyValueDAO and alf_prop_class implementation

- Contains patch that is incomplete i.e. future DAO unit tests won't work for incremental dev upgrades

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@15405 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2009-07-25 03:23:04 +00:00
parent 7d43509474
commit 20b602324c
22 changed files with 1443 additions and 498 deletions

View File

@@ -78,6 +78,7 @@
<value>classpath:alfresco/dbscripts/create/3.0/${db.script.dialect}/create-activities-extras.sql</value>
<value>classpath:alfresco/dbscripts/create/3.2/${db.script.dialect}/AlfrescoPostCreate-3.2-LockTables.sql</value>
<value>classpath:alfresco/dbscripts/create/3.2/${db.script.dialect}/AlfrescoPostCreate-3.2-ContentTables.sql</value>
<value>classpath:alfresco/dbscripts/create/3.3/${db.script.dialect}/AlfrescoPostCreate-3.3-PropertyValueTables.sql</value>
</list>
</property>
<property name="validateUpdateScriptPatches">
@@ -97,6 +98,7 @@
<ref bean="patch.db-V2.2-Person-2" />
<ref bean="patch.db-V3.2-LockTables" />
<ref bean="patch.db-V3.2-ContentTables" />
<ref bean="patch.db-V3.3-PropertyValueTables" />
</list>
</property>
<property name="postUpdateScriptPatches">

View File

@@ -207,6 +207,42 @@
</property>
</bean>
<!-- ===================================== -->
<!-- ID lookup for general, shared, immutable entities -->
<!-- ===================================== -->
<!-- The cross-transaction shared cache for Encoding entities -->
<bean name="immutableEntitySharedCache" class="org.alfresco.repo.cache.EhCacheAdapter">
<property name="cache">
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean" >
<property name="cacheManager">
<ref bean="internalEHCacheManager" />
</property>
<property name="cacheName">
<value>org.alfresco.cache.immutableEntityCache</value>
</property>
</bean>
</property>
</bean>
<!-- The transactional cache for Encoding entities -->
<bean name="immutableEntityCache" class="org.alfresco.repo.cache.TransactionalCache">
<property name="sharedCache">
<ref bean="immutableEntitySharedCache" />
</property>
<property name="cacheManager" >
<ref bean="transactionalEHCacheManager" />
</property>
<property name="name">
<value>org.alfresco.cache.immutableEntityTransactionalCache</value>
</property>
<property name="maxCacheSize">
<value>100</value>
</property>
</bean>
<!-- =============================== -->
<!-- Store and Node ID cache lookup -->
<!-- =============================== -->

View File

@@ -43,4 +43,9 @@
<property name="sqlMapClientTemplate" ref="contentSqlMapClientTemplate"/>
</bean>
<bean id="propertyValueDAO" class="org.alfresco.repo.domain.propval.ibatis.PropertyValueDAOImpl">
<property name="sqlMapClientTemplate" ref="propertyValueSqlMapClientTemplate"/>
<property name="propertyClassCache" ref="immutableEntityCache"/>
</bean>
</beans>

View File

@@ -0,0 +1,32 @@
--
-- Title: Property Value tables
-- Database: MySQL InnoDB
-- Since: V3.3 Schema 3001
-- Author: Derek Hulley
--
-- Please contact support@alfresco.com if you need assistance with the upgrade.
--
CREATE TABLE alf_prop_class
(
id BIGINT NOT NULL AUTO_INCREMENT,
version BIGINT NOT NULL,
java_class_name VARCHAR(255) NOT NULL,
java_class_name_short VARCHAR(32) NOT NULL,
java_class_name_crc BIGINT NOT NULL,
UNIQUE INDEX idx_prop_class_crc (java_class_name_crc, java_class_name_short),
INDEX idx_prop_class_class (java_class_name),
PRIMARY KEY (id)
) ENGINE=InnoDB;
--
-- Record script finish
--
DELETE FROM alf_applied_patch WHERE id = 'patch.db-V3.3-PropertyValueTables';
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)
VALUES
(
'patch.db-V3.3-PropertyValueTables', 'Manually executed script upgrade V3.3: PropertyValue Tables',
0, 3000, -1, 3001, null, 'UNKOWN', 1, 1, 'Script completed'
);

View File

@@ -296,6 +296,18 @@
eternal="true"
overflowToDisk="false"
/>
<cache
name="org.alfresco.cache.encodingEntityCache"
maxElementsInMemory="1000"
eternal="true"
overflowToDisk="false"
/>
<cache
name="org.alfresco.cache.immutableEntityCache"
maxElementsInMemory="10000"
eternal="true"
overflowToDisk="false"
/>
<cache
name="org.alfresco.cache.storeAndNodeIdCache"
maxElementsInMemory="10000"

View File

@@ -74,4 +74,16 @@
<property name="sqlMapClient" ref="contentSqlMapClient"/>
</bean>
<!-- iBatis config for PropertyValue domain -->
<bean id="propertyValueSqlMapClient" class="org.alfresco.ibatis.HierarchicalSqlMapClientFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="resourceLoader" ref="dialectResourceLoader" />
<property name="configLocation">
<value>classpath:alfresco/ibatis/propval-SqlMapConfig.xml</value>
</property>
</bean>
<bean id="propertyValueSqlMapClientTemplate" class="org.springframework.orm.ibatis.SqlMapClientTemplate">
<property name="sqlMapClient" ref="propertyValueSqlMapClient"/>
</bean>
</beans>

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap
PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="alfresco.propval">
<!-- -->
<!-- Type Defs -->
<!-- -->
<typeAlias alias="PropertyClass" type="org.alfresco.repo.domain.propval.PropertyClassEntity"/>
<!-- -->
<!-- Result Maps -->
<!-- -->
<resultMap id="result.PropertyClass" class="PropertyClass">
<result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="version" column="version" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="javaClassName" column="java_class_name" jdbcType="VARCHAR" javaType="java.lang.String"/>
<result property="javaClassNameShort" column="java_class_name_short" jdbcType="VARCHAR" javaType="java.lang.String"/>
<result property="javaClassNameCrc" column="java_class_name_crc" jdbcType="BIGINT" javaType="java.lang.Long"/>
</resultMap>
<!-- -->
<!-- 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 id="insert.PropertyClass.AutoIncrement">
insert into alf_prop_class (version, java_class_name, java_class_name_short, java_class_name_crc)
values (#version#, #javaClassName#, #javaClassNameShort#, #javaClassNameCrc#)
</sql>
<!-- -->
<!-- Statements -->
<!-- -->
<!-- Get a property class by ID -->
<select id="select.PropertyClassByID" parameterClass="PropertyClass" resultMap="result.PropertyClass">
select
*
from
alf_prop_class
where
id = #id#
</select>
<!-- Get the property class by class name -->
<select id="select.PropertyClassByName" parameterClass="PropertyClass" resultMap="result.PropertyClass">
select
*
from
alf_prop_class
where
java_class_name_crc = #javaClassNameCrc# and
java_class_name_short = #javaClassNameShort#
</select>
</sqlMap>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap
PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="alfresco.propval">
<insert id="insert.PropertyClass" parameterClass="PropertyClass" >
<include refid="insert.PropertyClass.AutoIncrement"/>
<selectKey resultClass="long" keyProperty="id" type="post">
KEY_COLUMN:GENERATED_KEY
</selectKey>
</insert>
</sqlMap>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig
PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<sqlMap resource="alfresco/ibatis/#resource.dialect#/propval-common-SqlMap.xml"/>
<sqlMap resource="alfresco/ibatis/#resource.dialect#/propval-insert-SqlMap.xml"/>
</sqlMapConfig>

View File

@@ -1964,4 +1964,16 @@
<property name="scriptsACP"><value>alfresco/templates/imap/command_processor_scripts.acp</value></property>
</bean>
<bean id="patch.db-V3.3-PropertyValueTables" class="org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch" parent="basePatch">
<property name="id"><value>patch.db-V3.3-PropertyValueTables</value></property>
<property name="description"><value>patch.schemaUpgradeScript.description</value></property>
<property name="fixesFromSchema"><value>0</value></property>
<property name="fixesToSchema"><value>3000</value></property>
<property name="targetSchema"><value>3001</value></property>
<property name="scriptUrl">
<!-- Share a create script -->
<value>classpath:alfresco/dbscripts/create/3.3/${db.script.dialect}/AlfrescoPostCreate-3.3-PropertyValueTables.sql</value>
</property>
</bean>
</beans>

View File

@@ -19,4 +19,4 @@ version.build=@build-number@
# Schema number
version.schema=3000
version.schema=3001

View File

@@ -35,28 +35,19 @@ import org.alfresco.util.Pair;
* <p>
* The keys must have good <code>equals</code> and </code>hashCode</code> implementations and
* must respect the case-sensitivity of the use-case.
* <p>
* All keys will be unique to the given cache region, allowing the cache to be shared
* between instances of this class.
*
* @author Derek Hulley
* @since 3.3
*/
public class EntityLookupCache<K extends Serializable, V extends Object, VK extends Serializable>
{
private static final String NULL_VALUE = "@@NULL_VALUE@@";
private final SimpleCache<Serializable, Object> cache;
private final EntityLookup<K, V, VK> entityLookup;
@SuppressWarnings("unchecked")
public EntityLookupCache(SimpleCache cache, EntityLookup<K, V, VK> entityLookup)
{
this.cache = cache;
this.entityLookup = entityLookup;
}
/**
* Interface to support lookups of the entities using keys and values.
*/
public static interface EntityLookup<K1 extends Serializable, V1 extends Object, VK1 extends Serializable>
public static interface EntityLookupCallbackDAO<K1 extends Serializable, V1 extends Object, VK1 extends Serializable>
{
/**
* Resolve the given value into a unique value key that can be used to find the entity's ID.
@@ -92,11 +83,49 @@ public class EntityLookupCache<K extends Serializable, V extends Object, VK exte
Pair<K1, V1> createValue(V1 value);
}
private static final String NULL_VALUE = "@@NULL_VALUE@@";
private static final String CACHE_REGION_DEFAULT = "DEFAULT";
private final SimpleCache<Serializable, Object> cache;
private final EntityLookupCallbackDAO<K, V, VK> entityLookup;
private final String cacheRegion;
/**
* Construct the lookup cache, using the {@link #CACHE_REGION_DEFAULT default cache region}.
*
* @param cache the cache that will back the two-way lookups
* @param entityLookup the instance that is able to find and persist entities
*/
@SuppressWarnings("unchecked")
Pair<K, V> getByKey(K key)
public EntityLookupCache(SimpleCache cache, EntityLookupCallbackDAO<K, V, VK> entityLookup)
{
this(cache, CACHE_REGION_DEFAULT, entityLookup);
}
/**
* Construct the lookup cache, using the given cache region.
* <p>
* All keys will be unique to the given cache region, allowing the cache to be shared
* between instances of this class.
*
* @param cache the cache that will back the two-way lookups
* @param cacheRegion the region within the cache to use.
* @param entityLookup the instance that is able to find and persist entities
*/
@SuppressWarnings("unchecked")
public EntityLookupCache(SimpleCache cache, String cacheRegion, EntityLookupCallbackDAO<K, V, VK> entityLookup)
{
this.cache = cache;
this.entityLookup = entityLookup;
this.cacheRegion = cacheRegion;
}
@SuppressWarnings("unchecked")
public Pair<K, V> getByKey(K key)
{
CacheRegionKey cacheKey = new CacheRegionKey(cacheRegion, key);
// Look in the cache
V value = (V) cache.get(key);
V value = (V) cache.get(cacheKey);
if (value != null && value.equals(NULL_VALUE))
{
// We checked before
@@ -111,24 +140,25 @@ public class EntityLookupCache<K extends Serializable, V extends Object, VK exte
if (entityPair == null)
{
// Cache nulls
cache.put(key, NULL_VALUE);
cache.put(cacheKey, NULL_VALUE);
}
else
{
// Cache the value
cache.put(key, entityPair.getSecond());
cache.put(cacheKey, entityPair.getSecond());
}
// Done
return entityPair;
}
@SuppressWarnings("unchecked")
Pair<K, V> getByValue(V value)
public Pair<K, V> getByValue(V value)
{
// Get the value key
VK valueKey = entityLookup.getValueKey(value);
CacheRegionKey cacheKey = new CacheRegionKey(cacheRegion, valueKey);
// Look in the cache
K key = (K) cache.get(valueKey);
K key = (K) cache.get(cacheKey);
// Check if we have looked this up already
if (key != null && key.equals(NULL_VALUE))
{
@@ -144,24 +174,26 @@ public class EntityLookupCache<K extends Serializable, V extends Object, VK exte
if (entityPair == null)
{
// Cache a null
cache.put(valueKey, NULL_VALUE);
cache.put(cacheKey, NULL_VALUE);
}
else
{
key = entityPair.getFirst();
// Cache the key
cache.put(valueKey, key);
cache.put(cacheKey, key);
}
// Done
return entityPair;
}
@SuppressWarnings("unchecked")
Pair<K, V> getOrCreateByValue(V value)
public Pair<K, V> getOrCreateByValue(V value)
{
// Get the value key
VK valueKey = entityLookup.getValueKey(value);
CacheRegionKey cacheKey = new CacheRegionKey(cacheRegion, valueKey);
// Look in the cache
K key = (K) cache.get(valueKey);
K key = (K) cache.get(cacheKey);
// Check if the value is already mapped to a key
if (key != null && !key.equals(NULL_VALUE))
{
@@ -176,9 +208,67 @@ public class EntityLookupCache<K extends Serializable, V extends Object, VK exte
}
key = entityPair.getFirst();
// Cache the key and value
cache.put(valueKey, key);
cache.put(key, value);
cache.put(cacheKey, key);
cache.put(new CacheRegionKey(cacheRegion, key), value);
// Done
return entityPair;
}
@SuppressWarnings("unchecked")
public void remove(K key)
{
CacheRegionKey keyCacheKey = new CacheRegionKey(cacheRegion, key);
V value = (V) cache.get(keyCacheKey);
if (value != null && !value.equals(NULL_VALUE))
{
// Get the value key and remove it
VK valueKey = entityLookup.getValueKey(value);
CacheRegionKey valueCacheKey = new CacheRegionKey(cacheRegion, valueKey);
cache.remove(valueCacheKey);
}
cache.remove(keyCacheKey);
}
/**
* Key-wrapper used to separate cache regions, allowing a single cache to be used for different
* purposes.
*/
private static class CacheRegionKey implements Serializable
{
private static final long serialVersionUID = -213050301938804468L;
private final String cacheRegion;
private final Serializable cacheKey;
private final int hashCode;
private CacheRegionKey(String cacheRegion, Serializable cacheKey)
{
this.cacheRegion = cacheRegion;
this.cacheKey = cacheKey;
this.hashCode = cacheRegion.hashCode() + cacheKey.hashCode();
}
@Override
public String toString()
{
return cacheRegion + "." + cacheKey.toString();
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
else if (!(obj instanceof CacheRegionKey))
{
return false;
}
CacheRegionKey that = (CacheRegionKey) obj;
return this.cacheRegion.equals(that.cacheRegion) && this.cacheKey.equals(that.cacheKey);
}
@Override
public int hashCode()
{
return hashCode;
}
}
}

View File

@@ -32,7 +32,7 @@ import junit.framework.TestCase;
import org.alfresco.repo.cache.MemoryCache;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookup;
import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAO;
import org.alfresco.util.Pair;
/**
@@ -45,16 +45,19 @@ import org.alfresco.util.Pair;
* @author Derek Hulley
* @since 3.3
*/
public class EntityLookupCacheTest extends TestCase implements EntityLookup<Long, Object, String>
public class EntityLookupCacheTest extends TestCase implements EntityLookupCallbackDAO<Long, Object, String>
{
private EntityLookupCache<Long, Object, String> entityLookupCache;
SimpleCache<Long, Object> cache;
private EntityLookupCache<Long, Object, String> entityLookupCacheA;
private EntityLookupCache<Long, Object, String> entityLookupCacheB;
private TreeMap<Long, String> database;
@Override
protected void setUp() throws Exception
{
SimpleCache<Long, Object> cache = new MemoryCache<Long, Object>();
entityLookupCache = new EntityLookupCache<Long, Object, String>(cache, this);
cache = new MemoryCache<Long, Object>();
entityLookupCacheA = new EntityLookupCache<Long, Object, String>(cache, "A", this);
entityLookupCacheB = new EntityLookupCache<Long, Object, String>(cache, "B", this);
database = new TreeMap<Long, String>();
}
@@ -63,7 +66,7 @@ public class EntityLookupCacheTest extends TestCase implements EntityLookup<Long
try
{
// Keep the "database" empty
entityLookupCache.getByValue(this);
entityLookupCacheA.getByValue(this);
}
catch (AssertionFailedError e)
{
@@ -74,28 +77,28 @@ public class EntityLookupCacheTest extends TestCase implements EntityLookup<Long
public void testLookupAgainstEmpty() throws Exception
{
TestValue value = new TestValue("AAA");
Pair<Long, Object> entityPair = entityLookupCache.getByValue(value);
Pair<Long, Object> entityPair = entityLookupCacheA.getByValue(value);
assertNull(entityPair);
assertTrue(database.isEmpty());
// Now do lookup or create
entityPair = entityLookupCache.getOrCreateByValue(value);
entityPair = entityLookupCacheA.getOrCreateByValue(value);
assertNotNull("Expected a value to be found", entityPair);
Long entityId = entityPair.getFirst();
assertTrue("Database ID should have been created", database.containsKey(entityId));
assertEquals("Database value incorrect", value.val, database.get(entityId));
// Do lookup or create again
entityPair = entityLookupCache.getOrCreateByValue(value);
entityPair = entityLookupCacheA.getOrCreateByValue(value);
assertNotNull("Expected a value to be found", entityPair);
assertEquals("Expected same entity ID", entityId, entityPair.getFirst());
// Look it up using the value
entityPair = entityLookupCache.getByValue(value);
entityPair = entityLookupCacheA.getByValue(value);
assertNotNull("Lookup after create should work", entityPair);
// Look it up using the ID
entityPair = entityLookupCache.getByKey(entityId);
entityPair = entityLookupCacheA.getByKey(entityId);
assertNotNull("Lookup by key should work after create", entityPair);
assertTrue("Looked-up type incorrect", entityPair.getSecond() instanceof TestValue);
assertEquals("Looked-up type value incorrect", value, entityPair.getSecond());
@@ -109,20 +112,41 @@ public class EntityLookupCacheTest extends TestCase implements EntityLookup<Long
createValue(new TestValue("CCC"));
// Look up by value
Pair<Long, Object> entityPair = entityLookupCache.getByValue(new TestValue("AAA"));
Pair<Long, Object> entityPair = entityLookupCacheA.getByValue(new TestValue("AAA"));
assertNotNull("Expected value to be found", entityPair);
assertEquals("ID is incorrect", new Long(1), entityPair.getFirst());
// Look up by ID
entityPair = entityLookupCache.getByKey(new Long(2));
entityPair = entityLookupCacheA.getByKey(new Long(2));
assertNotNull("Expected value to be found", entityPair);
// Do lookup or create
entityPair = entityLookupCache.getByValue(new TestValue("CCC"));
entityPair = entityLookupCacheA.getByValue(new TestValue("CCC"));
assertNotNull("Expected value to be found", entityPair);
assertEquals("ID is incorrect", new Long(3), entityPair.getFirst());
}
public void testRegions() throws Exception
{
TestValue valueAAA = new TestValue("AAA");
Pair<Long, Object> entityPairAAA = entityLookupCacheA.getOrCreateByValue(valueAAA);
assertNotNull(entityPairAAA);
assertEquals("AAA", database.get(entityPairAAA.getFirst()));
assertEquals(2, cache.getKeys().size());
TestValue valueBBB = new TestValue("BBB");
Pair<Long, Object> entityPairBBB = entityLookupCacheB.getOrCreateByValue(valueBBB);
assertNotNull(entityPairBBB);
assertEquals("BBB", database.get(entityPairBBB.getFirst()));
assertEquals(4, cache.getKeys().size());
// Now cross-check against the caches and make sure that the cache
entityPairBBB = entityLookupCacheA.getByValue(valueBBB);
assertEquals(5, cache.getKeys().size());
entityPairBBB = entityLookupCacheB.getByValue(valueAAA);
assertEquals(6, cache.getKeys().size());
}
/**
* Helper class to represent business object
*/

View File

@@ -0,0 +1,119 @@
/*
* 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;
import java.io.UnsupportedEncodingException;
import java.util.zip.CRC32;
import org.alfresco.util.Pair;
/**
* Helper class to calculate CRC values for string persistence.
*
* @author Derek Hulley
* @since 3.3
*/
public class CrcHelper
{
public static final String EMPTY_STRING = ".empty";
/**
* Calculate a persistable, unique pair of values that can be persisted in a database unique
* key and guarantee correct case-sensitivity.
* <p>
* While the short-string version of the value is always lowercase, the CRC is
* calculated from the virgin string if case-sensitivity is enforced; in the case-insensitive
* case, the CRC is calculated from a lowercase version of the string.
* <p>
* If the value is an empty string, then {@link #EMPTY_STRING} is used instead. This ensures
* that persisted values don't fall foul of the Oracle empty string comparison "behaviour" i.e
* you should never persist an empty string in Oracle as it equates to a SQL <b>NULL</b>.
*
* @param value the raw value that will be persisted
* @param dataLength the maximum number of characters that can be persisted
* @param useCharsFromStart <tt>true</tt> if the shortened string value must be made from
* the first characters of the string or <tt>false</tt> to use
* characters from the end of the string.
* @param caseSensitive <tt>true</tt> if the resulting pair must be case-sensitive or
* <tt>false</tt> if the pair must be case-insensitive.
* @return Return the persistable pair. The result will never be <tt>null</tt>,
* but the individual pair values will be <tt>null</tt> if the
* value given is <tt>null</tt>
*/
public static Pair<String, Long> getStringCrcPair(
String value,
int dataLength,
boolean useCharsFromStart,
boolean caseSensitive)
{
String valueLowerCase;
if (value == null)
{
return new Pair<String, Long>(null, null);
}
else if (value.length() == 0)
{
value = CrcHelper.EMPTY_STRING;
valueLowerCase = value;
}
else
{
valueLowerCase = value.toLowerCase();
}
Long valueCrc;
try
{
CRC32 crc = new CRC32();
if (caseSensitive)
{
crc.update(value.getBytes("UTF-8"));
}
else
{
crc.update(valueLowerCase.getBytes("UTF-8"));
}
valueCrc = crc.getValue();
}
catch (UnsupportedEncodingException e)
{
throw new RuntimeException("UTF-8 encoding is not supported");
}
// Get the short value (case-sensitive or not)
String valueShort = null;
int valueLen = valueLowerCase.length();
if (valueLen < dataLength)
{
valueShort = value;
}
else if (useCharsFromStart)
{
valueShort = valueLowerCase.substring(0, dataLength - 1);
}
else
{
valueShort = valueLowerCase.substring(valueLen - dataLength);
}
return new Pair<String, Long>(valueShort, valueCrc);
}
}

View File

@@ -52,6 +52,7 @@ public class DomainTestSuite extends TestSuite
suite.addTestSuite(LocaleDAOTest.class);
suite.addTestSuite(PropertyValueTest.class);
suite.addTestSuite(QNameDAOTest.class);
suite.addTestSuite(PropertyValueTest.class);
return suite;
}

View File

@@ -24,9 +24,7 @@
*/
package org.alfresco.repo.domain.contentdata;
import java.io.UnsupportedEncodingException;
import java.util.zip.CRC32;
import org.alfresco.repo.domain.CrcHelper;
import org.alfresco.util.EqualsHelper;
import org.alfresco.util.Pair;
@@ -98,51 +96,7 @@ public class ContentUrlEntity
*/
private static Pair<String, Long> getContentUrlCrcPair(String internalContentUrl)
{
if (internalContentUrl == null)
{
return new Pair<String, Long>(null, null);
}
// Calculate the CRC value
CRC32 crc = new CRC32();
try
{
crc.update(internalContentUrl.getBytes("UTF-8"));
}
catch (UnsupportedEncodingException e)
{
throw new RuntimeException("UTF-8 encoding is not supported");
}
// Get the short name (case-insensitive)
String contentUrlShort = null;
int contentUrlLen = internalContentUrl.length();
if (contentUrlLen < 12)
{
contentUrlShort = internalContentUrl.toLowerCase();
}
else
{
contentUrlShort = internalContentUrl.toLowerCase().substring(contentUrlLen - 12);
}
// Done
return new Pair<String, Long>(contentUrlShort, crc.getValue());
}
private static String getInternalUrl(String contentUrl)
{
if (contentUrl == null)
{
return null;
}
// Deal with Oracle's NULL-EMPTY confusion
if (contentUrl.length() == 0)
{
return EMPTY_URL;
}
else
{
return contentUrl;
}
return CrcHelper.getStringCrcPair(internalContentUrl, 12, false, true);
}
/**
@@ -195,8 +149,7 @@ public class ContentUrlEntity
{
this.contentUrl = contentUrl;
// Convert the URL to a persistable value
String internalContentUrl = ContentUrlEntity.getInternalUrl(contentUrl);
Pair<String, Long> contentUrlPair = ContentUrlEntity.getContentUrlCrcPair(internalContentUrl);
Pair<String, Long> contentUrlPair = ContentUrlEntity.getContentUrlCrcPair(contentUrl);
this.contentUrlShort = contentUrlPair.getFirst();
this.contentUrlCrc = contentUrlPair.getSecond();
}

View File

@@ -43,7 +43,6 @@ import org.alfresco.util.ParameterCheck;
public abstract class AbstractEncodingDAOImpl implements EncodingDAO
{
private static final Long CACHE_NULL_LONG = Long.MIN_VALUE;
private static final String NULL_SAFE_STRING = ".null";
private SimpleCache<Serializable, Serializable> encodingEntityCache;
/**

View File

@@ -0,0 +1,128 @@
/*
* 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;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.cache.lookup.EntityLookupCache;
import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAO;
import org.alfresco.util.Pair;
/**
* Abstract implementation for Property Value DAO.
* <p>
* This provides basic services such as caching, but defers to the underlying implementation
* for CRUD operations.
*
* @author Derek Hulley
* @since 3.3
*/
public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
{
private static final String CACHE_REGION_PROPERTY_CLASS = "PropertyClass";
private EntityLookupCache<Long, Class<?>, String> propertyClassCache;
/**
*
* @param propertyClassCache the cache of IDs to property classes
*/
public void setPropertyClassCache(SimpleCache<Serializable, Object> propertyClassCache)
{
PropertyValueCallbackDAO daoCallback = new PropertyValueCallbackDAO();
this.propertyClassCache = new EntityLookupCache<Long, Class<?>, String>(
propertyClassCache,
CACHE_REGION_PROPERTY_CLASS,
daoCallback);
}
public Pair<Long, Class<?>> getPropertyClass(Long id)
{
Pair<Long, Class<?>> entityPair = propertyClassCache.getByKey(id);
if (entityPair == null)
{
throw new AlfrescoRuntimeException("No property class exists for ID " + id);
}
return entityPair;
}
public Pair<Long, Class<?>> getPropertyClass(Class<?> clazz)
{
Pair<Long, Class<?>> entityPair = propertyClassCache.getByValue(clazz);
return entityPair;
}
public Pair<Long, Class<?>> getOrCreatePropertyClass(Class<?> clazz)
{
Pair<Long, Class<?>> entityPair = propertyClassCache.getOrCreateByValue(clazz);
return entityPair;
}
/**
* Callback for <b>alf_prop_type</b> DAO.
*/
private class PropertyValueCallbackDAO implements EntityLookupCallbackDAO<Long, Class<?>, String>
{
private final Pair<Long, Class<?>> convertEntityToPair(PropertyClassEntity propertyClassEntity)
{
if (propertyClassEntity == null)
{
return null;
}
else
{
return propertyClassEntity.getEntityPair();
}
}
public String getValueKey(Class<?> value)
{
return value.getName();
}
public Pair<Long, Class<?>> createValue(Class<?> value)
{
PropertyClassEntity propertyClassEntity = createClass(value);
return convertEntityToPair(propertyClassEntity);
}
public Pair<Long, Class<?>> findByKey(Long key)
{
PropertyClassEntity propertyClassEntity = findClassById(key);
return convertEntityToPair(propertyClassEntity);
}
public Pair<Long, Class<?>> findByValue(Class<?> value)
{
PropertyClassEntity propertyClassEntity = findClassByValue(value);
return convertEntityToPair(propertyClassEntity);
}
}
protected abstract PropertyClassEntity createClass(Class<?> value);
protected abstract PropertyClassEntity findClassById(Long id);
protected abstract PropertyClassEntity findClassByValue(Class<?> value);
}

View File

@@ -0,0 +1,172 @@
/*
* 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 org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.domain.CrcHelper;
import org.alfresco.util.EqualsHelper;
import org.alfresco.util.Pair;
/**
* Entity bean for <b>alf_prop_class</b> table.
*
* @author Derek Hulley
* @since 3.3
*/
public class PropertyClassEntity
{
public static final Long CONST_LONG_ZERO = new Long(0L);
public static final String EMPTY_URL = "empty";
private Long id;
private Long version;
private Class<?> javaClass;
private String javaClassName;
private String javaClassNameShort;
private long javaClassNameCrc;
public PropertyClassEntity()
{
}
@Override
public int hashCode()
{
return (javaClass == null ? 0 : javaClass.hashCode());
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
else if (obj instanceof PropertyClassEntity)
{
PropertyClassEntity that = (PropertyClassEntity) obj;
return EqualsHelper.nullSafeEquals(this.javaClass, that.javaClass);
}
else
{
return false;
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder(512);
sb.append("PropertyClassEntity")
.append("[ ID=").append(id)
.append(", javaClass=").append(javaClass)
.append("]");
return sb.toString();
}
/**
* @return Returns the ID-class pair
*/
public Pair<Long, Class<?>> getEntityPair()
{
return new Pair<Long, Class<?>>(id, getJavaClass());
}
public Class<?> getJavaClass()
{
if (javaClass == null && javaClassName != null)
{
try
{
javaClass = Class.forName(javaClassName);
}
catch (ClassNotFoundException e)
{
throw new AlfrescoRuntimeException(
"Property class '" + javaClassName + "' is not available to the VM");
}
}
return javaClass;
}
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public Long getVersion()
{
return version;
}
public void setVersion(Long version)
{
this.version = version;
}
public void setJavaClass(Class<?> javaClass)
{
this.javaClass = javaClass;
this.javaClassName = javaClass.getName();
Pair<String, Long> crcPair = CrcHelper.getStringCrcPair(javaClassName, 32, true, true);
this.javaClassNameShort = crcPair.getFirst();
this.javaClassNameCrc = crcPair.getSecond();
}
public String getJavaClassName()
{
return javaClassName;
}
public void setJavaClassName(String javaClassName)
{
this.javaClassName = javaClassName;
}
public String getJavaClassNameShort()
{
return javaClassNameShort;
}
public void setJavaClassNameShort(String javaClassNameShort)
{
this.javaClassNameShort = javaClassNameShort;
}
public long getJavaClassNameCrc()
{
return javaClassNameCrc;
}
public void setJavaClassNameCrc(long javaClassNameCrc)
{
this.javaClassNameCrc = javaClassNameCrc;
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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 org.alfresco.util.Pair;
/**
* DAO services for <b>alf_prop_XXX</b> tables.
*
* @author Derek Hulley
* @since 3.3
*/
public interface PropertyValueDAO
{
Pair<Long, Class<?>> getPropertyClass(Class<?> clazz);
Pair<Long, Class<?>> getPropertyClass(Long id);
Pair<Long, Class<?>> getOrCreatePropertyClass(Class<?> clazz);
}

View File

@@ -0,0 +1,121 @@
/*
* 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 junit.framework.TestCase;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.Pair;
import org.springframework.context.ApplicationContext;
/**
* @see PropertyValueDAO
*
* @author Derek Hulley
* @since 3.3
*/
public class PropertyValueDAOTest extends TestCase
{
private ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
private TransactionService transactionService;
private RetryingTransactionHelper txnHelper;
private PropertyValueDAO propertyValueDAO;
@Override
public void setUp() throws Exception
{
ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
transactionService = serviceRegistry.getTransactionService();
txnHelper = transactionService.getRetryingTransactionHelper();
propertyValueDAO = (PropertyValueDAO) ctx.getBean("propertyValueDAO");
}
public void testPropertyClass() throws Exception
{
final Class<?> clazz = this.getClass();
RetryingTransactionCallback<Pair<Long, Class<?>>> createClassCallback = new RetryingTransactionCallback<Pair<Long, Class<?>>>()
{
public Pair<Long, Class<?>> execute() throws Throwable
{
// Get the classes
return propertyValueDAO.getOrCreatePropertyClass(clazz);
}
};
final Pair<Long, Class<?>> clazzEntityPair = txnHelper.doInTransaction(createClassCallback, false);
assertNotNull(clazzEntityPair);
assertNotNull(clazzEntityPair.getFirst());
assertEquals(clazz, clazzEntityPair.getSecond());
// Now retrieve it
RetryingTransactionCallback<Void> getClassCallback = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
Pair<Long, Class<?>> checkPair1 = propertyValueDAO.getPropertyClass(clazzEntityPair.getFirst());
assertEquals(clazzEntityPair, checkPair1);
Pair<Long, Class<?>> checkPair2 = propertyValueDAO.getPropertyClass(clazzEntityPair.getSecond());
assertEquals(clazzEntityPair, checkPair2);
return null;
}
};
txnHelper.doInTransaction(getClassCallback, true);
// Test failure when requesting invalid ID
RetryingTransactionCallback<Void> badGetCallback = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
propertyValueDAO.getPropertyClass(Long.MIN_VALUE);
return null;
}
};
try
{
txnHelper.doInTransaction(badGetCallback, false);
fail("Expected exception when using invalid ID.");
}
catch (RuntimeException e)
{
// Expected
}
// Test null caching
RetryingTransactionCallback<Void> noHitCallback = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
propertyValueDAO.getPropertyClass(this.getClass());
propertyValueDAO.getPropertyClass(this.getClass());
return null;
}
};
txnHelper.doInTransaction(noHitCallback, false);
}
}

View File

@@ -0,0 +1,82 @@
/*
* 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.ibatis;
import org.alfresco.repo.domain.propval.AbstractPropertyValueDAOImpl;
import org.alfresco.repo.domain.propval.PropertyClassEntity;
import org.springframework.orm.ibatis.SqlMapClientTemplate;
/**
* iBatis-specific implementation of the PropertyValue DAO.
*
* @author Derek Hulley
* @since 3.3
*/
public class PropertyValueDAOImpl extends AbstractPropertyValueDAOImpl
{
private static final Long VERSION_ONE = new Long(1L);
private static final String SELECT_PROPERTY_CLASS_BY_ID = "select.PropertyClassByID";
private static final String SELECT_PROPERTY_CLASS_BY_NAME = "select.PropertyClassByName";
private static final String INSERT_PROPERTY_CLASS = "insert.PropertyClass";
private SqlMapClientTemplate template;
public void setSqlMapClientTemplate(SqlMapClientTemplate sqlMapClientTemplate)
{
this.template = sqlMapClientTemplate;
}
@Override
protected PropertyClassEntity findClassById(Long id)
{
PropertyClassEntity propertyClassEntity = new PropertyClassEntity();
propertyClassEntity.setId(id);
propertyClassEntity = (PropertyClassEntity) template.queryForObject(SELECT_PROPERTY_CLASS_BY_ID, propertyClassEntity);
// Done
return propertyClassEntity;
}
@Override
protected PropertyClassEntity findClassByValue(Class<?> value)
{
PropertyClassEntity propertyClassEntity = new PropertyClassEntity();
propertyClassEntity.setJavaClass(value);
propertyClassEntity = (PropertyClassEntity) template.queryForObject(SELECT_PROPERTY_CLASS_BY_NAME, propertyClassEntity);
// Done
return propertyClassEntity;
}
@Override
protected PropertyClassEntity createClass(Class<?> value)
{
PropertyClassEntity propertyClassEntity = new PropertyClassEntity();
propertyClassEntity.setJavaClass(value);
propertyClassEntity.setVersion(VERSION_ONE);
Long id = (Long) template.insert(INSERT_PROPERTY_CLASS, propertyClassEntity);
propertyClassEntity.setId(id);
// Done
return propertyClassEntity;
}
}