Audit and Prop table enhancements

- alf_prop_xxx tables
   - Added alf_prop_root table
   - alf_prop_value_xxx tables enforce uniqueness
   - Better splitting up of Collections and Maps (attempt to use exact storage type)
   - Moved some indexes around to reduce size but maintain index data lookups
   - Allow updates and deletes of properties via alf_prop_root (entry-point table)
 - Audit Application
   - Unique by name
   - Add 'disabled paths' to control audit behaviour (not wired into services)
   - Added concurrency checks for updates to the Audit Application (model change, etc)


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@16217 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2009-09-11 13:59:45 +00:00
parent de1e940d73
commit 29b94cf0d7
23 changed files with 1496 additions and 653 deletions

View File

@@ -48,12 +48,15 @@
<property name="converter"> <property name="converter">
<bean class="org.alfresco.repo.domain.propval.DefaultPropertyTypeConverter"/> <bean class="org.alfresco.repo.domain.propval.DefaultPropertyTypeConverter"/>
</property> </property>
<!--
<property name="propertyClassCache" ref="immutableEntityCache"/> <property name="propertyClassCache" ref="immutableEntityCache"/>
<property name="propertyDateValueCache" ref="immutableEntityCache"/> <property name="propertyDateValueCache" ref="immutableEntityCache"/>
<property name="propertyStringValueCache" ref="immutableEntityCache"/> <property name="propertyStringValueCache" ref="immutableEntityCache"/>
<property name="propertyDoubleValueCache" ref="immutableEntityCache"/> <property name="propertyDoubleValueCache" ref="immutableEntityCache"/>
<property name="propertySerializableValueCache" ref="immutableEntityCache"/> <property name="propertySerializableValueCache" ref="immutableEntityCache"/>
-->
<property name="propertyValueCache" ref="immutableEntityCache"/> <property name="propertyValueCache" ref="immutableEntityCache"/>
<property name="propertyCache" ref="immutableEntityCache"/>
</bean> </bean>
<bean id="auditDAO" class="org.alfresco.repo.domain.audit.ibatis.AuditDAOImpl"> <bean id="auditDAO" class="org.alfresco.repo.domain.audit.ibatis.AuditDAOImpl">

View File

@@ -20,10 +20,14 @@ CREATE TABLE alf_audit_model
CREATE TABLE alf_audit_app CREATE TABLE alf_audit_app
( (
id BIGINT NOT NULL AUTO_INCREMENT, id BIGINT NOT NULL AUTO_INCREMENT,
audit_model_id BIGINT NOT NULL, version TINYINT NOT NULL,
app_name_id BIGINT NOT NULL, app_name_id BIGINT NOT NULL,
CONSTRAINT fk_alf_audit_app_model FOREIGN KEY (audit_model_id) REFERENCES alf_audit_model (id) ON DELETE CASCADE, audit_model_id BIGINT NOT NULL,
disabled_paths_id BIGINT NOT NULL,
CONSTRAINT fk_alf_audit_app_app FOREIGN KEY (app_name_id) REFERENCES alf_prop_value (id), CONSTRAINT fk_alf_audit_app_app FOREIGN KEY (app_name_id) REFERENCES alf_prop_value (id),
CONSTRAINT UNIQUE idx_alf_audit_app_app (app_name_id),
CONSTRAINT fk_alf_audit_app_model FOREIGN KEY (audit_model_id) REFERENCES alf_audit_model (id) ON DELETE CASCADE,
CONSTRAINT fk_alf_audit_app_dis FOREIGN KEY (disabled_paths_id) REFERENCES alf_prop_root (id),
PRIMARY KEY (id) PRIMARY KEY (id)
) ENGINE=InnoDB; ) ENGINE=InnoDB;
@@ -37,7 +41,7 @@ CREATE TABLE alf_audit_entry
CONSTRAINT fk_alf_audit_ent_app FOREIGN KEY (audit_app_id) REFERENCES alf_audit_app (id) ON DELETE CASCADE, CONSTRAINT fk_alf_audit_ent_app FOREIGN KEY (audit_app_id) REFERENCES alf_audit_app (id) ON DELETE CASCADE,
INDEX idx_alf_audit_ent_time (audit_time), INDEX idx_alf_audit_ent_time (audit_time),
CONSTRAINT fk_alf_audit_ent_user FOREIGN KEY (audit_user_id) REFERENCES alf_prop_value (id), CONSTRAINT fk_alf_audit_ent_user FOREIGN KEY (audit_user_id) REFERENCES alf_prop_value (id),
CONSTRAINT fk_alf_audit_ent_prop FOREIGN KEY (audit_values_id) REFERENCES alf_prop_value (id), CONSTRAINT fk_alf_audit_ent_prop FOREIGN KEY (audit_values_id) REFERENCES alf_prop_root (id),
PRIMARY KEY (id) PRIMARY KEY (id)
) ENGINE=InnoDB; ) ENGINE=InnoDB;

View File

@@ -38,10 +38,11 @@ CREATE TABLE alf_prop_double_value
( (
id BIGINT NOT NULL AUTO_INCREMENT, id BIGINT NOT NULL AUTO_INCREMENT,
double_value DOUBLE NOT NULL, double_value DOUBLE NOT NULL,
INDEX idx_alf_prop_dbl_val (double_value), UNIQUE INDEX idx_alf_prop_dbl_val (double_value),
PRIMARY KEY (id) PRIMARY KEY (id)
) ENGINE=InnoDB; ) ENGINE=InnoDB;
-- Stores unique, case-sensitive string values --
CREATE TABLE alf_prop_string_value CREATE TABLE alf_prop_string_value
( (
id BIGINT NOT NULL AUTO_INCREMENT, id BIGINT NOT NULL AUTO_INCREMENT,
@@ -49,7 +50,7 @@ CREATE TABLE alf_prop_string_value
string_end_lower VARCHAR(16) NOT NULL, string_end_lower VARCHAR(16) NOT NULL,
string_crc BIGINT NOT NULL, string_crc BIGINT NOT NULL,
INDEX idx_alf_prop_str (string_value(32)), INDEX idx_alf_prop_str (string_value(32)),
INDEX idx_alf_prop_crc (string_end_lower, string_crc), UNIQUE INDEX idx_alf_prop_crc (string_end_lower, string_crc),
PRIMARY KEY (id) PRIMARY KEY (id)
) ENGINE=InnoDB; ) ENGINE=InnoDB;
@@ -67,19 +68,29 @@ CREATE TABLE alf_prop_value
persisted_type TINYINT NOT NULL, persisted_type TINYINT NOT NULL,
long_value BIGINT NOT NULL, long_value BIGINT NOT NULL,
INDEX idx_alf_prop_per (persisted_type, long_value), INDEX idx_alf_prop_per (persisted_type, long_value),
INDEX idx_alf_prop_act (actual_type_id, long_value), UNIQUE INDEX idx_alf_prop_act (actual_type_id, long_value),
PRIMARY KEY (id)
) ENGINE=InnoDB;
CREATE TABLE alf_prop_root
(
id BIGINT NOT NULL AUTO_INCREMENT,
version TINYINT NOT NULL,
PRIMARY KEY (id) PRIMARY KEY (id)
) ENGINE=InnoDB; ) ENGINE=InnoDB;
CREATE TABLE alf_prop_link CREATE TABLE alf_prop_link
( (
root_prop_id BIGINT NOT NULL, root_prop_id BIGINT NOT NULL,
current_prop_id BIGINT NOT NULL, prop_index BIGINT NOT NULL,
value_prop_id BIGINT NOT NULL, contained_in BIGINT NOT NULL,
key_prop_id BIGINT NOT NULL, key_prop_id BIGINT NOT NULL,
INDEX idx_alf_prop_coll_rev (value_prop_id, root_prop_id), value_prop_id BIGINT NOT NULL,
CONSTRAINT fk_alf_prop_link_root FOREIGN KEY (root_prop_id) REFERENCES alf_prop_value (id) ON DELETE CASCADE, CONSTRAINT fk_alf_prop_link_root FOREIGN KEY (root_prop_id) REFERENCES alf_prop_root (id) ON DELETE CASCADE,
PRIMARY KEY (root_prop_id, current_prop_id, value_prop_id, key_prop_id) CONSTRAINT fk_alf_prop_link_key FOREIGN KEY (key_prop_id) REFERENCES alf_prop_value (id) ON DELETE CASCADE,
CONSTRAINT fk_alf_prop_link_val FOREIGN KEY (value_prop_id) REFERENCES alf_prop_value (id) ON DELETE CASCADE,
INDEX idx_alf_prop_link_for (root_prop_id, key_prop_id, value_prop_id),
PRIMARY KEY (root_prop_id, contained_in, prop_index)
) ENGINE=InnoDB; ) ENGINE=InnoDB;
-- --

View File

@@ -27,8 +27,10 @@
</resultMap> </resultMap>
<resultMap id="result_AuditApplication" class="AuditApplication"> <resultMap id="result_AuditApplication" class="AuditApplication">
<result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/> <result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="auditModelId" column="audit_model_id" jdbcType="BIGINT" javaType="java.lang.Long"/> <result property="version" column="version" jdbcType="TINYINT" javaType="java.lang.Short"/>
<result property="applicationNameId" column="app_name_id" jdbcType="BIGINT" javaType="java.lang.Long"/> <result property="applicationNameId" column="app_name_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="auditModelId" column="audit_model_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="disabledPathsId" column="disabled_paths_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
</resultMap> </resultMap>
<resultMap id="result_AuditEntry" class="AuditEntry"> <resultMap id="result_AuditEntry" class="AuditEntry">
<result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/> <result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/>
@@ -76,8 +78,8 @@
</sql> </sql>
<sql id="insert_AuditApplication_AutoIncrement"> <sql id="insert_AuditApplication_AutoIncrement">
insert into alf_audit_app (audit_model_id, app_name_id) insert into alf_audit_app (version, app_name_id, audit_model_id, disabled_paths_id)
values (#auditModelId#, #applicationNameId#) values (#version#, #applicationNameId#, #auditModelId#, #disabledPathsId#)
</sql> </sql>
<sql id="insert_AuditEntry_AutoIncrement"> <sql id="insert_AuditEntry_AutoIncrement">
@@ -99,65 +101,60 @@
content_crc = #contentCrc# content_crc = #contentCrc#
</select> </select>
<!-- Get the audit application by model ID --> <!-- Get the audit application by ID -->
<select id="select_AuditApplicationByModelId" parameterMap="parameter_IdMap" resultMap="result_AuditApplication"> <select id="select_AuditApplicationById" parameterMap="parameter_IdMap" resultMap="result_AuditApplication">
select select
* *
from from
alf_audit_app alf_audit_app
where where
audit_model_id = ? id = ?
</select> </select>
<!-- Get audit entries --> <!-- Get the audit application by name ID -->
<!-- TODO: Use compound parameters for string searching, possibly namespaced from propval --> <select id="select_AuditApplicationByNameId" parameterMap="parameter_IdMap" resultMap="result_AuditApplication">
<select id="select_AuditEntriesSimple" parameterClass="AuditQueryParameters" resultMap="result_AuditQueryNoValues">
select select
entry.id as audit_entry_id, *
user_sv.string_value as audit_user,
app_sv.string_value as audit_app_name,
entry.audit_time as audit_time,
entry.audit_values_id as audit_values_id
from from
alf_audit_app app alf_audit_app
join alf_prop_value app_pv on (app_pv.id = app.app_name_id) where
join alf_prop_string_value app_sv on (app_sv.id = app_pv.long_value and app_pv.persisted_type = 3) app_name_id = ?
join alf_audit_entry entry on (entry.audit_app_id = app.id)
join alf_prop_value user_pv on (user_pv.id = entry.audit_user_id)
join alf_prop_string_value user_sv on (user_sv.id = user_pv.long_value and user_pv.persisted_type = 3)
<dynamic prepend="where">
<isNotNull prepend="and" property="auditAppNameShort">
app_sv.string_end_lower = #auditAppNameShort# and
app_sv.string_crc = #auditAppNameCrc#
</isNotNull>
<isNotNull prepend="and" property="auditUserShort">
user_sv.string_end_lower = #auditUserShort# and
user_sv.string_crc = #auditUserCrc#
</isNotNull>
<isNotNull prepend="and" property="auditFromTime">
<![CDATA[entry.audit_time >= #auditFromTime#]]>
</isNotNull>
<isNotNull prepend="and" property="auditToTime">
<![CDATA[entry.audit_time < #auditToTime#]]>
</isNotNull>
</dynamic>
order by
entry.id
</select> </select>
<!-- Optimistic update of the audit application -->
<update id="update_AuditApplication" parameterClass="AuditApplication">
update
alf_audit_app
set
version = #version#,
app_name_id = #applicationNameId#
audit_model_id = #auditModelId#
disabled_paths_id = #disabledPathsId#
where
id = #id# and
version = (#version# -1)
</update>
<!-- Get audit entries --> <!-- Get audit entries -->
<!-- TODO: Use compound parameters for string searching, possibly namespaced from propval -->
<select id="select_AuditEntriesWithValues" parameterClass="AuditQueryParameters" resultMap="result_AuditQueryAllValues"> <select id="select_AuditEntriesWithValues" parameterClass="AuditQueryParameters" resultMap="result_AuditQueryAllValues">
select select
entry.id as audit_entry_id, entry.id as audit_entry_id,
user_sv.string_value as audit_user, user_sv.string_value as audit_user,
app_sv.string_value as audit_app_name, app_sv.string_value as audit_app_name,
entry.audit_time as audit_time, entry.audit_time as audit_time,
entry.audit_values_id as audit_values_id, entry.audit_values_id as audit_values_id,
pl.root_prop_id, pl.current_prop_id, pl.value_prop_id, pl.key_prop_id, pl.root_prop_id as link_root_prop_id,
v.actual_type_id, v.persisted_type, pl.prop_index as link_prop_index,
v.long_value, dv.double_value, sv.string_value, serv.serializable_value pl.contained_in as link_contained_in,
pl.key_prop_id as link_key_prop_id,
pl.value_prop_id as link_value_prop_id,
pv.actual_type_id as prop_actual_type_id,
pv.persisted_type as prop_persisted_type,
pv.long_value as prop_long_value,
dv.double_value as prop_double_value,
sv.string_value as prop_string_value,
serv.serializable_value as prop_serializable_value
from from
alf_audit_app app alf_audit_app app
join alf_prop_value app_pv on (app_pv.id = app.app_name_id) join alf_prop_value app_pv on (app_pv.id = app.app_name_id)
@@ -168,20 +165,20 @@
<isNotNull property="searchKey"> <isNotNull property="searchKey">
join alf_prop_link sp_kpl on (sp_kpl.root_prop_id = entry.audit_values_id) join alf_prop_link sp_kpl on (sp_kpl.root_prop_id = entry.audit_values_id)
join alf_prop_value sp_kv on (sp_kpl.key_prop_id = sp_kv.id) join alf_prop_value sp_kpv on (sp_kpl.key_prop_id = sp_kpv.id)
join alf_prop_string_value sp_ksv on (sp_ksv.id = sp_kv.long_value and sp_kv.persisted_type = 3) join alf_prop_string_value sp_ksv on (sp_ksv.id = sp_kpv.long_value and sp_kpv.persisted_type = 3)
</isNotNull> </isNotNull>
<isNotNull property="searchValueString"> <isNotNull property="searchValueString">
join alf_prop_link sp_mpl on (sp_mpl.root_prop_id = entry.audit_values_id) join alf_prop_link sp_mpl on (sp_mpl.root_prop_id = entry.audit_values_id)
join alf_prop_value sp_mv on (sp_mpl.value_prop_id = sp_mv.id) join alf_prop_value sp_mpv on (sp_mpl.value_prop_id = sp_mpv.id)
join alf_prop_string_value sp_msv on (sp_msv.id = sp_mv.long_value and sp_mv.persisted_type = 3) join alf_prop_string_value sp_msv on (sp_msv.id = sp_mpv.long_value and sp_mpv.persisted_type = 3)
</isNotNull> </isNotNull>
join alf_prop_link pl on (pl.root_prop_id = entry.audit_values_id) join alf_prop_link pl on (pl.root_prop_id = entry.audit_values_id)
join alf_prop_value v on (pl.value_prop_id = v.id) join alf_prop_value pv on (pl.value_prop_id = pv.id)
left join alf_prop_double_value dv on (dv.id = v.long_value and v.persisted_type = 2) left join alf_prop_double_value dv on (dv.id = pv.long_value and pv.persisted_type = 2)
left join alf_prop_string_value sv on (sv.id = v.long_value and (v.persisted_type = 3 || v.persisted_type = 5)) left join alf_prop_string_value sv on (sv.id = pv.long_value and (pv.persisted_type = 3 || pv.persisted_type = 5))
left join alf_prop_serializable_value serv on (serv.id = v.long_value and v.persisted_type = 4) left join alf_prop_serializable_value serv on (serv.id = pv.long_value and pv.persisted_type = 4)
<dynamic prepend="where"> <dynamic prepend="where">
<isNotNull prepend="and" property="auditAppNameShort"> <isNotNull prepend="and" property="auditAppNameShort">
app_sv.string_end_lower = #auditAppNameShort# and app_sv.string_end_lower = #auditAppNameShort# and
@@ -204,8 +201,6 @@
sp_msv.string_value = #searchValueString# sp_msv.string_value = #searchValueString#
</isNotNull> </isNotNull>
</dynamic> </dynamic>
order by
entry.id
</select> </select>
</sqlMap> </sqlMap>

View File

@@ -17,6 +17,7 @@
<typeAlias alias="PropertyDoubleValue" type="org.alfresco.repo.domain.propval.PropertyDoubleValueEntity"/> <typeAlias alias="PropertyDoubleValue" type="org.alfresco.repo.domain.propval.PropertyDoubleValueEntity"/>
<typeAlias alias="PropertySerializableValue" type="org.alfresco.repo.domain.propval.PropertySerializableValueEntity"/> <typeAlias alias="PropertySerializableValue" type="org.alfresco.repo.domain.propval.PropertySerializableValueEntity"/>
<typeAlias alias="PropertyValue" type="org.alfresco.repo.domain.propval.PropertyValueEntity"/> <typeAlias alias="PropertyValue" type="org.alfresco.repo.domain.propval.PropertyValueEntity"/>
<typeAlias alias="PropertyRoot" type="org.alfresco.repo.domain.propval.PropertyRootEntity"/>
<typeAlias alias="PropertyLink" type="org.alfresco.repo.domain.propval.PropertyLinkEntity"/> <typeAlias alias="PropertyLink" type="org.alfresco.repo.domain.propval.PropertyLinkEntity"/>
<typeAlias alias="PropertyIdSearchRow" type="org.alfresco.repo.domain.propval.PropertyIdSearchRow"/> <typeAlias alias="PropertyIdSearchRow" type="org.alfresco.repo.domain.propval.PropertyIdSearchRow"/>
@@ -58,31 +59,34 @@
<result property="serializableValue" column="serializable_value" jdbcType="BLOB" javaType="java.io.Serializable"/> <result property="serializableValue" column="serializable_value" jdbcType="BLOB" javaType="java.io.Serializable"/>
</resultMap> </resultMap>
<resultMap id="result_PropertyValue" class="PropertyValue"> <resultMap id="result_PropertyValue" class="PropertyValue">
<result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/> <result property="id" column="prop_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="actualTypeId" column="actual_type_id" jdbcType="BIGINT" javaType="java.lang.Long"/> <result property="actualTypeId" column="prop_actual_type_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="persistedType" column="persisted_type" jdbcType="TINYINT" javaType="java.lang.Short"/> <result property="persistedType" column="prop_persisted_type" jdbcType="TINYINT" javaType="java.lang.Short"/>
<result property="longValue" column="long_value" jdbcType="BIGINT" javaType="java.lang.Long"/> <result property="longValue" column="prop_long_value" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="doubleValue" column="double_value" jdbcType="DOUBLE" javaType="java.lang.Double"/> <result property="doubleValue" column="prop_double_value" jdbcType="DOUBLE" javaType="java.lang.Double"/>
<result property="stringValue" column="string_value" jdbcType="VARCHAR" javaType="java.lang.String"/> <result property="stringValue" column="prop_string_value" jdbcType="VARCHAR" javaType="java.lang.String"/>
<result property="serializableValue" column="serializable_value" jdbcType="BLOB" javaType="java.io.Serializable"/> <result property="serializableValue" column="prop_serializable_value" jdbcType="BLOB" javaType="java.io.Serializable"/>
</resultMap> </resultMap>
<resultMap id="result_PropertyLink" class="PropertyLink"> <resultMap id="result_PropertyLink" class="PropertyLink">
<result property="rootPropId" column="root_prop_id" jdbcType="BIGINT" javaType="long"/> <result property="rootPropId" column="link_root_prop_id" jdbcType="BIGINT" javaType="long"/>
<result property="currentPropId" column="current_prop_id" jdbcType="BIGINT" javaType="long"/> <result property="propIndex" column="link_prop_index" jdbcType="BIGINT" javaType="long"/>
<result property="valuePropId" column="value_prop_id" jdbcType="BIGINT" javaType="long"/> <result property="containedIn" column="link_contained_in" jdbcType="BIGINT" javaType="long"/>
<result property="keyPropId" column="key_prop_id" jdbcType="BIGINT" javaType="long"/> <result property="keyPropId" column="link_key_prop_id" jdbcType="BIGINT" javaType="long"/>
<result property="valuePropId" column="link_value_prop_id" jdbcType="BIGINT" javaType="long"/>
</resultMap> </resultMap>
<resultMap id="result_PropertyIdSearchRow" class="PropertyIdSearchRow"> <resultMap id="result_PropertyIdSearchRow" class="PropertyIdSearchRow">
<result property="rootPropId" column="root_prop_id" jdbcType="BIGINT" javaType="long"/> <!-- If we use nested ResultMaps, then this ResultMap can't be embedded... -->
<result property="currentPropId" column="current_prop_id" jdbcType="BIGINT" javaType="long"/> <result property="rootPropId" column="link_root_prop_id" jdbcType="BIGINT" javaType="long"/>
<result property="valuePropId" column="value_prop_id" jdbcType="BIGINT" javaType="long"/> <result property="propIndex" column="link_prop_index" jdbcType="BIGINT" javaType="long"/>
<result property="keyPropId" column="key_prop_id" jdbcType="BIGINT" javaType="long"/> <result property="containedIn" column="link_contained_in" jdbcType="BIGINT" javaType="long"/>
<result property="actualTypeId" column="actual_type_id" jdbcType="BIGINT" javaType="java.lang.Long"/> <result property="keyPropId" column="link_key_prop_id" jdbcType="BIGINT" javaType="long"/>
<result property="persistedType" column="persisted_type" jdbcType="TINYINT" javaType="java.lang.Short"/> <result property="valuePropId" column="link_value_prop_id" jdbcType="BIGINT" javaType="long"/>
<result property="longValue" column="long_value" jdbcType="BIGINT" javaType="java.lang.Long"/> <result property="actualTypeId" column="prop_actual_type_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="doubleValue" column="double_value" jdbcType="DOUBLE" javaType="java.lang.Double"/> <result property="persistedType" column="prop_persisted_type" jdbcType="TINYINT" javaType="java.lang.Short"/>
<result property="stringValue" column="string_value" jdbcType="VARCHAR" javaType="java.lang.String"/> <result property="longValue" column="prop_long_value" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="serializableValue" column="serializable_value" jdbcType="BLOB" javaType="java.io.Serializable"/> <result property="doubleValue" column="prop_double_value" jdbcType="DOUBLE" javaType="java.lang.Double"/>
<result property="stringValue" column="prop_string_value" jdbcType="VARCHAR" javaType="java.lang.String"/>
<result property="serializableValue" column="prop_serializable_value" jdbcType="BLOB" javaType="java.io.Serializable"/>
</resultMap> </resultMap>
<!-- --> <!-- -->
@@ -92,6 +96,9 @@
<parameterMap id="parameter_PropertySerializableValue" class="PropertySerializableValue"> <parameterMap id="parameter_PropertySerializableValue" class="PropertySerializableValue">
<parameter property="serializableValue" jdbcType="BLOB" javaType="java.io.Serializable"/> <parameter property="serializableValue" jdbcType="BLOB" javaType="java.io.Serializable"/>
</parameterMap> </parameterMap>
<parameterMap id="parameter_PropertyRoot" class="PropertyRoot">
<parameter property="version" jdbcType="TINYINT" javaType="java.lang.Short"/>
</parameterMap>
<!-- --> <!-- -->
<!-- SQL Snippets --> <!-- SQL Snippets -->
@@ -122,6 +129,11 @@
values (#actualTypeId#, #persistedType#, #longValue#) values (#actualTypeId#, #persistedType#, #longValue#)
</sql> </sql>
<sql id="insert_PropertyRoot_AutoIncrement">
insert into alf_prop_root (version)
values (?)
</sql>
<!-- --> <!-- -->
<!-- Statements --> <!-- Statements -->
<!-- --> <!-- -->
@@ -241,13 +253,13 @@
<!-- Get the property value by value in alf_prop_value --> <!-- Get the property value by value in alf_prop_value -->
<select id="select_PropertyValueByLocalValue" parameterClass="PropertyValue" resultMap="result_PropertyValue"> <select id="select_PropertyValueByLocalValue" parameterClass="PropertyValue" resultMap="result_PropertyValue">
select select
pv.id as id, pv.id as prop_id,
pv.actual_type_id as actual_type_id, pv.actual_type_id as prop_actual_type_id,
pv.persisted_type as persisted_type, pv.persisted_type as prop_persisted_type,
pv.long_value as long_value, pv.long_value as prop_long_value,
null as double_value, null as prop_double_value,
null as string_value, null as prop_string_value,
null as serializable_value null as prop_serializable_value
from from
alf_prop_value pv alf_prop_value pv
where where
@@ -258,13 +270,13 @@
<!-- Get the property value by value in alf_prop_double_value --> <!-- Get the property value by value in alf_prop_double_value -->
<select id="select_PropertyValueByDoubleValue" parameterClass="PropertyValue" resultMap="result_PropertyValue"> <select id="select_PropertyValueByDoubleValue" parameterClass="PropertyValue" resultMap="result_PropertyValue">
select select
pv.id as id, pv.id as prop_id,
pv.actual_type_id as actual_type_id, pv.actual_type_id as prop_actual_type_id,
pv.persisted_type as persisted_type, pv.persisted_type as prop_persisted_type,
pv.long_value as long_value, pv.long_value as prop_long_value,
dv.double_value as double_value, dv.double_value as prop_double_value,
null as string_value, null as prop_string_value,
null as serializable_value null as prop_serializable_value
from from
alf_prop_value pv alf_prop_value pv
join alf_prop_double_value dv on (dv.id = pv.long_value and pv.persisted_type = #persistedType#) join alf_prop_double_value dv on (dv.id = pv.long_value and pv.persisted_type = #persistedType#)
@@ -276,13 +288,13 @@
<!-- Get the property value by value in alf_prop_string_value --> <!-- Get the property value by value in alf_prop_string_value -->
<select id="select_PropertyValueByStringValue" parameterClass="PropertyStringQuery" resultMap="result_PropertyValue"> <select id="select_PropertyValueByStringValue" parameterClass="PropertyStringQuery" resultMap="result_PropertyValue">
select select
pv.id as id, pv.id as prop_id,
pv.actual_type_id as actual_type_id, pv.actual_type_id as prop_actual_type_id,
pv.persisted_type as persisted_type, pv.persisted_type as prop_persisted_type,
pv.long_value as long_value, pv.long_value as prop_long_value,
null as double_value, null as prop_double_value,
sv.string_value as string_value, sv.string_value as prop_string_value,
null as serializable_value null as prop_serializable_value
from from
alf_prop_value pv alf_prop_value pv
join alf_prop_string_value sv on (sv.id = pv.long_value and pv.persisted_type = #persistedType#) join alf_prop_string_value sv on (sv.id = pv.long_value and pv.persisted_type = #persistedType#)
@@ -293,29 +305,91 @@
</select> </select>
<!-- Get the property value by ID --> <!-- Get the property value by ID -->
<select id="select_PropertyValueById" parameterClass="PropertyValue" resultMap="result_PropertyIdSearchRow"> <select id="select_PropertyValueById" parameterClass="PropertyValue" resultMap="result_PropertyValue">
select select
cl.root_prop_id, cl.current_prop_id, cl.value_prop_id, cl.key_prop_id, pv.id as prop_id,
v.actual_type_id, v.persisted_type, pv.actual_type_id as prop_actual_type_id,
v.long_value, dv.double_value, sv.string_value, serv.serializable_value pv.persisted_type as prop_persisted_type,
from pv.long_value as prop_long_value,
alf_prop_link cl dv.double_value as prop_double_value,
join alf_prop_value v on (cl.value_prop_id = v.id) sv.string_value as prop_string_value,
left join alf_prop_double_value dv on (dv.id = v.long_value and v.persisted_type = 2) serv.serializable_value as prop_serializable_value
left join alf_prop_string_value sv on (sv.id = v.long_value and (v.persisted_type = 3 || v.persisted_type = 5)) from
left join alf_prop_serializable_value serv on (serv.id = v.long_value and v.persisted_type = 4) alf_prop_value pv
where cl.root_prop_id = #id# left join alf_prop_double_value dv on (dv.id = pv.long_value and pv.persisted_type = 2)
left join alf_prop_string_value sv on (sv.id = pv.long_value and (pv.persisted_type = 3 || pv.persisted_type = 5))
left join alf_prop_serializable_value serv on (serv.id = pv.long_value and pv.persisted_type = 4)
where
pv.id = #id#
</select> </select>
<!-- Get the property value by ID -->
<select id="select_PropertyById" parameterClass="PropertyValue" resultMap="result_PropertyIdSearchRow">
select
pl.root_prop_id as link_root_prop_id,
pl.prop_index as link_prop_index,
pl.contained_in as link_contained_in,
pl.key_prop_id as link_key_prop_id,
pl.value_prop_id as link_value_prop_id,
pv.actual_type_id as prop_actual_type_id,
pv.persisted_type as prop_persisted_type,
pv.long_value as prop_long_value,
dv.double_value as prop_double_value,
sv.string_value as prop_string_value,
serv.serializable_value as prop_serializable_value
from
alf_prop_link pl
join alf_prop_value pv on (pl.value_prop_id = pv.id)
left join alf_prop_double_value dv on (dv.id = pv.long_value and pv.persisted_type = 2)
left join alf_prop_string_value sv on (sv.id = pv.long_value and (pv.persisted_type = 3 || pv.persisted_type = 5))
left join alf_prop_serializable_value serv on (serv.id = pv.long_value and pv.persisted_type = 4)
where
pl.root_prop_id = #id#
</select>
<select id="select_PropertyRootById" parameterClass="PropertyRoot" resultClass="PropertyRoot">
select
id,
version
from
alf_prop_root
where
id = #id#
</select>
<update id="update_PropertyRoot" parameterClass="PropertyRoot">
update
alf_prop_root
set
version = #version#
where
id = #id# and
version = (#version# -1)
</update>
<delete id="delete_PropertyRootById" parameterClass="PropertyRoot">
delete from
alf_prop_root
where
id = #id#
</delete>
<insert id="insert_PropertyLink" parameterClass="PropertyLink" > <insert id="insert_PropertyLink" parameterClass="PropertyLink" >
insert into alf_prop_link insert into alf_prop_link
( (
root_prop_id, current_prop_id, value_prop_id, key_prop_id root_prop_id, prop_index, contained_in, key_prop_id, value_prop_id
) )
values values
( (
#rootPropId#, #currentPropId#, #valuePropId#, #keyPropId# #rootPropId#, #propIndex#, #containedIn#, #keyPropId#, #valuePropId#
) )
</insert> </insert>
<delete id="delete_PropertyLinksByRootId" parameterClass="PropertyRoot">
delete from
alf_prop_link
where
root_prop_id = #id#
</delete>
</sqlMap> </sqlMap>

View File

@@ -41,4 +41,11 @@
</selectKey> </selectKey>
</insert> </insert>
<insert id="insert_PropertyRoot" parameterMap="parameter_PropertyRoot" >
<include refid="insert_PropertyRoot_AutoIncrement"/>
<selectKey resultClass="long" keyProperty="id" type="post">
KEY_COLUMN:GENERATED_KEY
</selectKey>
</insert>
</sqlMap> </sqlMap>

View File

@@ -39,6 +39,7 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.audit.AuditComponentImpl; import org.alfresco.repo.audit.AuditComponentImpl;
@@ -48,6 +49,7 @@ import org.alfresco.repo.content.ContentContext;
import org.alfresco.repo.content.ContentStore; import org.alfresco.repo.content.ContentStore;
import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.domain.audit.AuditDAO; import org.alfresco.repo.domain.audit.AuditDAO;
import org.alfresco.repo.domain.audit.AuditDAO.AuditApplicationInfo;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.TransactionalDao; import org.alfresco.repo.transaction.TransactionalDao;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
@@ -670,7 +672,40 @@ public class HibernateAuditDAO extends HibernateDaoSupport implements AuditDAO,
* @throws UnsupportedOperationException always * @throws UnsupportedOperationException always
* @since 3.2 * @since 3.2
*/ */
public Long getOrCreateAuditApplication(Long modelId, String applicationName) public AuditApplicationInfo createAuditApplication(String application, Long modelId)
{
throw new UnsupportedOperationException();
}
/**
* Fallout implementation from new audit DAO
*
* @throws UnsupportedOperationException always
* @since 3.2
*/
public AuditApplicationInfo getAuditApplication(String applicationName)
{
throw new UnsupportedOperationException();
}
/**
* Fallout implementation from new audit DAO
*
* @throws UnsupportedOperationException always
* @since 3.2
*/
public void updateAuditApplicationModel(Long id, Long modelId)
{
throw new UnsupportedOperationException();
}
/**
* Fallout implementation from new audit DAO
*
* @throws UnsupportedOperationException always
* @since 3.2
*/
public void updateAuditApplicationDisabledPaths(Long id, Set<String> disabledPaths)
{ {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View File

@@ -55,6 +55,7 @@ import org.alfresco.repo.audit.model._3.DataExtractors;
import org.alfresco.repo.audit.model._3.DataGenerators; import org.alfresco.repo.audit.model._3.DataGenerators;
import org.alfresco.repo.audit.model._3.ObjectFactory; import org.alfresco.repo.audit.model._3.ObjectFactory;
import org.alfresco.repo.domain.audit.AuditDAO; import org.alfresco.repo.domain.audit.AuditDAO;
import org.alfresco.repo.domain.audit.AuditDAO.AuditApplicationInfo;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.transaction.TransactionService; import org.alfresco.service.transaction.TransactionService;
@@ -99,6 +100,10 @@ public class AuditModelRegistry
* Used to lookup a reference to the application * Used to lookup a reference to the application
*/ */
private final Map<String, Long> auditApplicationIdsByApplicationsName; private final Map<String, Long> auditApplicationIdsByApplicationsName;
/**
* Used to lookup application disabled paths
*/
private final Map<String, Set<String>> auditDisabledPathsByApplicationsName;
/** /**
* Default constructor * Default constructor
@@ -115,6 +120,7 @@ public class AuditModelRegistry
auditModels = new ArrayList<Audit>(7); auditModels = new ArrayList<Audit>(7);
auditApplicationsByName = new HashMap<String, AuditApplication>(7); auditApplicationsByName = new HashMap<String, AuditApplication>(7);
auditApplicationIdsByApplicationsName = new HashMap<String, Long>(7); auditApplicationIdsByApplicationsName = new HashMap<String, Long>(7);
auditDisabledPathsByApplicationsName = new HashMap<String, Set<String>>(7);
} }
/** /**
@@ -246,8 +252,8 @@ public class AuditModelRegistry
clearCaches(); clearCaches();
throw new AuditModelException( throw new AuditModelException(
"Failed to load audit model: " + auditModelUrl + "\n" + "Failed to load audit model: " + auditModelUrl,
e.getMessage()); e);
} }
} }
// NOTE: If we support other types of loading, then that will have to go here, too // NOTE: If we support other types of loading, then that will have to go here, too
@@ -308,6 +314,25 @@ public class AuditModelRegistry
} }
} }
/**
* Get all disabled paths for the given application name
*
* @param applicationName the name of the audited application
* @return a set of paths for which logging is disabled
*/
public Set<String> getAuditDisabledPaths(String applicationName)
{
readLock.lock();
try
{
return auditDisabledPathsByApplicationsName.get(applicationName);
}
finally
{
readLock.unlock();
}
}
/** /**
* Unmarshalls the Audit model from the URL. * Unmarshalls the Audit model from the URL.
* *
@@ -516,11 +541,21 @@ public class AuditModelRegistry
} }
// Get the ID of the application // Get the ID of the application
Long appId = auditDAO.getOrCreateAuditApplication(auditModelId, name); AuditApplicationInfo appInfo = auditDAO.getAuditApplication(name);
if (appInfo == null)
{
appInfo = auditDAO.createAuditApplication(name, auditModelId);
}
else
{
// Update it with the new model ID
auditDAO.updateAuditApplicationModel(appInfo.getId(), auditModelId);
}
AuditApplication wrapperApp = new AuditApplication(dataExtractorsByName, dataGeneratorsByName, application); AuditApplication wrapperApp = new AuditApplication(dataExtractorsByName, dataGeneratorsByName, application);
auditApplicationsByName.put(name, wrapperApp); auditApplicationsByName.put(name, wrapperApp);
auditApplicationIdsByApplicationsName.put(name, appId); auditApplicationIdsByApplicationsName.put(name, appInfo.getId());
auditDisabledPathsByApplicationsName.put(name, appInfo.getDisabledPaths());
} }
// Store the model itself // Store the model itself
auditModels.add(audit); auditModels.add(audit);

View File

@@ -29,8 +29,10 @@ import java.io.InputStream;
import java.io.Serializable; import java.io.Serializable;
import java.net.URL; import java.net.URL;
import java.util.Date; import java.util.Date;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.zip.CRC32; import java.util.zip.CRC32;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
@@ -49,6 +51,7 @@ import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.util.Pair; import org.alfresco.util.Pair;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.dao.DataIntegrityViolationException;
/** /**
* Abstract helper DAO for <b>alf_audit_XXX</b> tables. * Abstract helper DAO for <b>alf_audit_XXX</b> tables.
@@ -206,33 +209,105 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO
* alf_audit_application * alf_audit_application
*/ */
public Long getOrCreateAuditApplication(Long modelId, String application) @SuppressWarnings("unchecked")
public AuditApplicationInfo getAuditApplication(String application)
{ {
// Search for it AuditApplicationEntity entity = getAuditApplicationByName(application);
AuditApplicationEntity entity = getAuditApplicationByModelIdAndName(modelId, application);
if (entity == null) if (entity == null)
{ {
// Create it return null;
// Persist the string }
Long appNameId = propertyValueDAO.getOrCreatePropertyValue(application).getFirst(); else
// Create the audit session {
entity = createAuditApplication(modelId, appNameId); AuditApplicationInfo appInfo = new AuditApplicationInfo();
appInfo.setId(entity.getId());
appInfo.setname(application);
appInfo.setModelId(entity.getAuditModelId());
// Resolve the disabled paths
Set<String> disabledPaths = (Set<String>) propertyValueDAO.getPropertyById(entity.getDisabledPathsId());
appInfo.setDisabledPaths(disabledPaths);
// Done // Done
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {
logger.debug( logger.debug(
"Created new audit application: \n" + "Found existing audit application: \n" +
" Model: " + modelId + "\n" + " " + appInfo);
" App: " + application + "\n" +
" Result: " + entity);
} }
return appInfo;
} }
}
public AuditApplicationInfo createAuditApplication(String application, Long modelId)
{
// Persist the string
Long appNameId = propertyValueDAO.getOrCreatePropertyValue(application).getFirst();
// We need a property to hold any disabled paths
Set<String> disabledPaths = new HashSet<String>();
Long disabledPathsId = propertyValueDAO.createProperty((Serializable)disabledPaths);
// Create the audit app
AuditApplicationEntity entity = createAuditApplication(appNameId, modelId, disabledPathsId);
// Create return value
AuditApplicationInfo appInfo = new AuditApplicationInfo();
appInfo.setId(entity.getId());
appInfo.setname(application);
appInfo.setModelId(modelId);
appInfo.setDisabledPaths(disabledPaths);
// Done // Done
return entity.getId(); if (logger.isDebugEnabled())
{
logger.debug(
"Created new audit application: \n" +
" Model: " + modelId + "\n" +
" App: " + application + "\n" +
" Result: " + entity);
}
return appInfo;
} }
protected abstract AuditApplicationEntity getAuditApplicationByModelIdAndName(Long modelId, String appName); public void updateAuditApplicationModel(Long id, Long modelId)
protected abstract AuditApplicationEntity createAuditApplication(Long modelId, Long appNameId); {
AuditApplicationEntity entity = getAuditApplicationById(id);
if (entity == null)
{
throw new DataIntegrityViolationException("No audit application exists for ID " + id);
}
if (entity.getAuditModelId().equals(modelId))
{
// There is nothing to update
return;
}
// Update
entity.setAuditModelId(modelId);
updateAuditApplication(entity);
}
@SuppressWarnings("unchecked")
public void updateAuditApplicationDisabledPaths(Long id, Set<String> disabledPaths)
{
AuditApplicationEntity entity = getAuditApplicationById(id);
if (entity == null)
{
throw new DataIntegrityViolationException("No audit application exists for ID " + id);
}
// Resolve the current set
Long disabledPathsId = entity.getDisabledPathsId();
Set<String> oldDisabledPaths = (Set<String>) propertyValueDAO.getPropertyById(disabledPathsId);
if (oldDisabledPaths.equals(disabledPaths))
{
// Nothing changed
return;
}
// Update the property
propertyValueDAO.updateProperty(disabledPathsId, (Serializable) disabledPaths);
// Do a precautionary update to ensure that the application row is locked appropriately
updateAuditApplication(entity);
}
protected abstract AuditApplicationEntity getAuditApplicationById(Long id);
protected abstract AuditApplicationEntity getAuditApplicationByName(String appName);
protected abstract AuditApplicationEntity createAuditApplication(Long appNameId, Long modelId, Long disabledPathsId);
protected abstract AuditApplicationEntity updateAuditApplication(AuditApplicationEntity entity);
/* /*
* alf_audit_entry * alf_audit_entry
@@ -253,7 +328,7 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO
Long valuesId = null; Long valuesId = null;
if (values != null && values.size() > 0) if (values != null && values.size() > 0)
{ {
valuesId = propertyValueDAO.getOrCreatePropertyValue((Serializable)values).getFirst(); valuesId = propertyValueDAO.createProperty((Serializable)values);
} }
// Create the audit entry // Create the audit entry

View File

@@ -33,8 +33,10 @@ package org.alfresco.repo.domain.audit;
public class AuditApplicationEntity public class AuditApplicationEntity
{ {
private Long id; private Long id;
private Long auditModelId; private short version;
private Long applicationNameId; private Long applicationNameId;
private Long auditModelId;
private Long disabledPathsId;
public AuditApplicationEntity() public AuditApplicationEntity()
{ {
@@ -46,12 +48,26 @@ public class AuditApplicationEntity
StringBuilder sb = new StringBuilder(512); StringBuilder sb = new StringBuilder(512);
sb.append("AuditApplicationEntity") sb.append("AuditApplicationEntity")
.append("[ ID=").append(id) .append("[ ID=").append(id)
.append(", auditModelId=").append(auditModelId) .append(", version=").append(version)
.append(", applicationNameId=").append(applicationNameId) .append(", applicationNameId=").append(applicationNameId)
.append(", auditModelId=").append(auditModelId)
.append(", disabledPathsId=").append(disabledPathsId)
.append("]"); .append("]");
return sb.toString(); return sb.toString();
} }
public void incrementVersion()
{
if (version >= Short.MAX_VALUE)
{
this.version = 0;
}
else
{
this.version++;
}
}
public Long getId() public Long getId()
{ {
return id; return id;
@@ -62,14 +78,14 @@ public class AuditApplicationEntity
this.id = id; this.id = id;
} }
public Long getAuditModelId() public short getVersion()
{ {
return auditModelId; return version;
} }
public void setAuditModelId(Long auditModelId) public void setVersion(short version)
{ {
this.auditModelId = auditModelId; this.version = version;
} }
public Long getApplicationNameId() public Long getApplicationNameId()
@@ -81,4 +97,24 @@ public class AuditApplicationEntity
{ {
this.applicationNameId = applicationNameId; this.applicationNameId = applicationNameId;
} }
public Long getAuditModelId()
{
return auditModelId;
}
public void setAuditModelId(Long auditModelId)
{
this.auditModelId = auditModelId;
}
public Long getDisabledPathsId()
{
return disabledPathsId;
}
public void setDisabledPathsId(Long disabledPathsId)
{
this.disabledPathsId = disabledPathsId;
}
} }

View File

@@ -28,6 +28,7 @@ import java.io.Serializable;
import java.net.URL; import java.net.URL;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.alfresco.repo.audit.AuditState; import org.alfresco.repo.audit.AuditState;
import org.alfresco.service.cmr.audit.AuditInfo; import org.alfresco.service.cmr.audit.AuditInfo;
@@ -66,6 +67,66 @@ public interface AuditDAO
* V3.2 methods after here only, please * V3.2 methods after here only, please
*/ */
/**
* Information about the audit application to be passed in an out of the interface.
*
* @author Derek Hulley
* @since 3.2
*/
public static class AuditApplicationInfo
{
private Long id;
private String name;
private Long modelId;
private Set<String> disabledPaths;
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("AuditApplicationInfo ")
.append("[ id=").append(id)
.append(", name=").append(name)
.append(", modelId=").append(modelId)
.append(", disabledPaths=").append(disabledPaths)
.append("]");
return sb.toString();
}
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setname(String name)
{
this.name = name;
}
public Long getModelId()
{
return modelId;
}
public void setModelId(Long modelId)
{
this.modelId = modelId;
}
public Set<String> getDisabledPaths()
{
return disabledPaths;
}
public void setDisabledPaths(Set<String> disabledPaths)
{
this.disabledPaths = disabledPaths;
}
}
/** /**
* Creates a new audit model entry or finds an existing one * Creates a new audit model entry or finds an existing one
* *
@@ -76,13 +137,38 @@ public interface AuditDAO
Pair<Long, ContentData> getOrCreateAuditModel(URL url); Pair<Long, ContentData> getOrCreateAuditModel(URL url);
/** /**
* Creates a new audit application or finds an existing one * Get the audit application details.
* *
* @param modelId the ID of the model configuration
* @param applicationName the name of the application * @param applicationName the name of the application
* @return Returns the ID of the application entry * @return Returns details of an existing application or <tt>null</tt> if it doesn't exist
*/ */
Long getOrCreateAuditApplication(Long modelId, String applicationName); AuditApplicationInfo getAuditApplication(String applicationName);
/**
* Creates a new audit application. The application name must be unique.
*
* @param application the name of the application
* @param modelId the ID of the model configuration
*/
AuditApplicationInfo createAuditApplication(String application, Long modelId);
/**
* Update the audit application to refer to a new model.
* If the model did not change, then nothing will be done.
*
* @param id the ID of the audit application
* @param modelId the ID of the new model
*/
void updateAuditApplicationModel(Long id, Long modelId);
/**
* Update the audit application to hold a new set of disabled paths.
* If the value did not change, then nothing will be done.
*
* @param id the ID of the audit application
* @param disabledPaths the new disabled paths
*/
void updateAuditApplicationDisabledPaths(Long id, Set<String> disabledPaths);
/** /**
* Create a new audit entry with the given map of values. * Create a new audit entry with the given map of values.

View File

@@ -33,6 +33,7 @@ import java.util.Map;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.alfresco.repo.content.transform.AbstractContentTransformerTest; import org.alfresco.repo.content.transform.AbstractContentTransformerTest;
import org.alfresco.repo.domain.audit.AuditDAO.AuditApplicationInfo;
import org.alfresco.repo.domain.contentdata.ContentDataDAO; import org.alfresco.repo.domain.contentdata.ContentDataDAO;
import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
@@ -111,7 +112,11 @@ public class AuditDAOTest extends TestCase
{ {
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
auditDAO.getOrCreateAuditApplication(modelId, appName); AuditApplicationInfo appInfo = auditDAO.getAuditApplication(appName);
if (appInfo == null)
{
appInfo = auditDAO.createAuditApplication(appName, modelId);
}
} }
return null; return null;
} }
@@ -142,8 +147,13 @@ public class AuditDAOTest extends TestCase
{ {
public Long execute() throws Throwable public Long execute() throws Throwable
{ {
Long modelId = auditDAO.getOrCreateAuditModel(url).getFirst(); AuditApplicationInfo appInfo = auditDAO.getAuditApplication(appName);
return auditDAO.getOrCreateAuditApplication(modelId, appName); if (appInfo == null)
{
Long modelId = auditDAO.getOrCreateAuditModel(url).getFirst();
appInfo = auditDAO.createAuditApplication(appName, modelId);
}
return appInfo.getId();
} }
}; };
final Long sessionId = txnHelper.doInTransaction(createAppCallback); final Long sessionId = txnHelper.doInTransaction(createAppCallback);

View File

@@ -52,11 +52,14 @@ public class AuditDAOImpl extends AbstractAuditDAOImpl
private static final String SELECT_MODEL_BY_CRC = "alfresco.audit.select_AuditModelByCrc"; private static final String SELECT_MODEL_BY_CRC = "alfresco.audit.select_AuditModelByCrc";
private static final String INSERT_MODEL = "alfresco.audit.insert_AuditModel"; private static final String INSERT_MODEL = "alfresco.audit.insert_AuditModel";
private static final String SELECT_APPLICATION_BY_MODEL_ID = "alfresco.audit.select_AuditApplicationByModelId"; private static final String SELECT_APPLICATION_BY_ID = "alfresco.audit.select_AuditApplicationById";
private static final String SELECT_APPLICATION_BY_NAME_ID = "alfresco.audit.select_AuditApplicationByNameId";
private static final String INSERT_APPLICATION = "alfresco.audit.insert_AuditApplication"; private static final String INSERT_APPLICATION = "alfresco.audit.insert_AuditApplication";
private static final String UPDATE_APPLICATION = "alfresco.audit.update_AuditApplication";
private static final String INSERT_ENTRY = "alfresco.audit.insert_AuditEntry"; private static final String INSERT_ENTRY = "alfresco.audit.insert_AuditEntry";
@SuppressWarnings("unused")
private static final String SELECT_ENTRIES_SIMPLE = "alfresco.audit.select_AuditEntriesSimple"; private static final String SELECT_ENTRIES_SIMPLE = "alfresco.audit.select_AuditEntriesSimple";
private static final String SELECT_ENTRIES_WITH_VALUES = "alfresco.audit.select_AuditEntriesWithValues"; private static final String SELECT_ENTRIES_WITH_VALUES = "alfresco.audit.select_AuditEntriesWithValues";
@@ -90,54 +93,75 @@ public class AuditDAOImpl extends AbstractAuditDAOImpl
return entity; return entity;
} }
@SuppressWarnings("unchecked")
@Override @Override
protected AuditApplicationEntity getAuditApplicationByModelIdAndName(Long modelId, String appName) protected AuditApplicationEntity getAuditApplicationById(Long id)
{ {
Map<String, Object> params = new HashMap<String, Object>(11); Map<String, Object> params = new HashMap<String, Object>(11);
params.put("id", modelId); params.put("id", id);
List<AuditApplicationEntity> results = (List<AuditApplicationEntity>) template.queryForList( AuditApplicationEntity entity = (AuditApplicationEntity) template.queryForObject(
SELECT_APPLICATION_BY_MODEL_ID, SELECT_APPLICATION_BY_ID,
params); params);
// There could be multiple hits for the model ID. Go through them and find the correct app name.
AuditApplicationEntity result = null;
for (AuditApplicationEntity row : results)
{
Long appNameId = row.getApplicationNameId();
Pair<Long, Serializable> propPair = getPropertyValueDAO().getPropertyValueById(appNameId);
if (propPair == null)
{
// There is a FK to protect against this, but we'll just log it
logger.warn("An audit application references a non-existent app_name_id: " + appNameId);
}
// Check for exact match
Serializable propValue = propPair.getSecond();
if (propValue instanceof String && propValue.equals(appName))
{
// Got it
result = row;
break;
}
}
// Done // Done
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {
logger.debug("Searched for audit application with model id " + modelId + " and found: " + result); logger.debug("Searched for audit application ID " + id + " and found: " + entity);
} }
return result; return entity;
} }
@Override @Override
protected AuditApplicationEntity createAuditApplication(Long modelId, Long appNameId) protected AuditApplicationEntity getAuditApplicationByName(String appName)
{
// Resolve the name as a property ID
Pair<Long, Serializable> appNamePair = propertyValueDAO.getPropertyValue(appName);
if (appNamePair == null)
{
// There will be no results
return null;
}
Map<String, Object> params = new HashMap<String, Object>(11);
params.put("id", appNamePair.getFirst());
AuditApplicationEntity entity = (AuditApplicationEntity) template.queryForObject(
SELECT_APPLICATION_BY_NAME_ID,
params);
// Done
if (logger.isDebugEnabled())
{
logger.debug("Searched for audit application '" + appName + "' and found: " + entity);
}
return entity;
}
@Override
protected AuditApplicationEntity createAuditApplication(Long appNameId, Long modelId, Long disabledPathsId)
{ {
AuditApplicationEntity entity = new AuditApplicationEntity(); AuditApplicationEntity entity = new AuditApplicationEntity();
entity.setAuditModelId(modelId); entity.setVersion((short)0);
entity.setApplicationNameId(appNameId); entity.setApplicationNameId(appNameId);
entity.setAuditModelId(modelId);
entity.setDisabledPathsId(disabledPathsId);
Long id = (Long) template.insert(INSERT_APPLICATION, entity); Long id = (Long) template.insert(INSERT_APPLICATION, entity);
entity.setId(id); entity.setId(id);
return entity; return entity;
} }
@Override
protected AuditApplicationEntity updateAuditApplication(AuditApplicationEntity entity)
{
AuditApplicationEntity updateEntity = new AuditApplicationEntity();
updateEntity.setId(entity.getId());
updateEntity.setVersion(entity.getVersion());
updateEntity.incrementVersion();
updateEntity.setApplicationNameId(entity.getApplicationNameId());
updateEntity.setAuditModelId(entity.getAuditModelId());
updateEntity.setDisabledPathsId(entity.getDisabledPathsId());
template.update(UPDATE_APPLICATION, updateEntity, 1);
// Done
return updateEntity;
}
@Override @Override
protected AuditEntryEntity createAuditEntry(Long applicationId, long time, Long usernameId, Long valuesId) protected AuditEntryEntity createAuditEntry(Long applicationId, long time, Long usernameId, Long valuesId)
{ {

View File

@@ -33,17 +33,17 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeMap;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.cache.lookup.EntityLookupCache; import org.alfresco.repo.cache.lookup.EntityLookupCache;
import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAOAdaptor; import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAOAdaptor;
import org.alfresco.repo.domain.CrcHelper; 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.util.Pair; import org.alfresco.util.Pair;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.dao.DataIntegrityViolationException;
/** /**
* Abstract implementation for Property Value DAO. * Abstract implementation for Property Value DAO.
@@ -62,8 +62,9 @@ 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_SERIALIZABLE_VALUE = "PropertySerializableValue"; private static final String CACHE_REGION_PROPERTY_SERIALIZABLE_VALUE = "PropertySerializableValue";
private static final String CACHE_REGION_PROPERTY_VALUE = "PropertyValue"; private static final String CACHE_REGION_PROPERTY_VALUE = "PropertyValue";
private static final String CACHE_REGION_PROPERTY = "Property";
private static final Log logger = LogFactory.getLog(AbstractPropertyValueDAOImpl.class); protected final Log logger = LogFactory.getLog(getClass());
protected PropertyTypeConverter converter; protected PropertyTypeConverter converter;
@@ -73,6 +74,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
private final PropertyDoubleValueCallbackDAO propertyDoubleValueCallback; private final PropertyDoubleValueCallbackDAO propertyDoubleValueCallback;
private final PropertySerializableValueCallbackDAO propertySerializableValueCallback; private final PropertySerializableValueCallbackDAO propertySerializableValueCallback;
private final PropertyValueCallbackDAO propertyValueCallback; private final PropertyValueCallbackDAO propertyValueCallback;
private final PropertyCallbackDAO propertyCallback;
/** /**
* Cache for the property class:<br/> * Cache for the property class:<br/>
* KEY: ID<br/> * KEY: ID<br/>
@@ -115,6 +117,13 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
* VALUE KEY: A value key based on the persisted type<br/> * VALUE KEY: A value key based on the persisted type<br/>
*/ */
private EntityLookupCache<Long, Serializable, Serializable> propertyValueCache; private EntityLookupCache<Long, Serializable, Serializable> propertyValueCache;
/**
* Cache for the property:<br/>
* KEY: ID<br/>
* VALUE: The Serializable instance<br/>
* VALUE KEY: A value key based on the persisted type<br/>
*/
private EntityLookupCache<Long, Serializable, Serializable> propertyCache;
/** /**
* Default constructor. * Default constructor.
@@ -130,6 +139,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
this.propertyDoubleValueCallback = new PropertyDoubleValueCallbackDAO(); this.propertyDoubleValueCallback = new PropertyDoubleValueCallbackDAO();
this.propertySerializableValueCallback = new PropertySerializableValueCallbackDAO(); this.propertySerializableValueCallback = new PropertySerializableValueCallbackDAO();
this.propertyValueCallback = new PropertyValueCallbackDAO(); this.propertyValueCallback = new PropertyValueCallbackDAO();
this.propertyCallback = new PropertyCallbackDAO();
this.propertyClassCache = new EntityLookupCache<Long, Class<?>, String>(propertyClassDaoCallback); this.propertyClassCache = new EntityLookupCache<Long, Class<?>, String>(propertyClassDaoCallback);
this.propertyDateValueCache = new EntityLookupCache<Long, Date, Date>(propertyDateValueCallback); this.propertyDateValueCache = new EntityLookupCache<Long, Date, Date>(propertyDateValueCallback);
@@ -137,6 +147,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
this.propertyDoubleValueCache = new EntityLookupCache<Long, Double, Double>(propertyDoubleValueCallback); this.propertyDoubleValueCache = new EntityLookupCache<Long, Double, Double>(propertyDoubleValueCallback);
this.propertySerializableValueCache = new EntityLookupCache<Long, Serializable, Serializable>(propertySerializableValueCallback); this.propertySerializableValueCache = new EntityLookupCache<Long, Serializable, Serializable>(propertySerializableValueCallback);
this.propertyValueCache = new EntityLookupCache<Long, Serializable, Serializable>(propertyValueCallback); this.propertyValueCache = new EntityLookupCache<Long, Serializable, Serializable>(propertyValueCallback);
this.propertyCache = new EntityLookupCache<Long, Serializable, Serializable>(propertyCallback);
} }
/** /**
@@ -225,6 +236,19 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
propertyValueCallback); propertyValueCallback);
} }
/**
* Set the cache to use for <b>alf_prop_root</b> lookups (optional).
*
* @param propertyValueCache the cache of IDs to property values
*/
public void setPropertyCache(SimpleCache<Serializable, Object> propertyCache)
{
this.propertyCache = new EntityLookupCache<Long, Serializable, Serializable>(
propertyCache,
CACHE_REGION_PROPERTY,
propertyCallback);
}
//================================ //================================
// 'alf_prop_class' accessors // 'alf_prop_class' accessors
//================================ //================================
@@ -238,7 +262,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
Pair<Long, Class<?>> entityPair = propertyClassCache.getByKey(id); Pair<Long, Class<?>> entityPair = propertyClassCache.getByKey(id);
if (entityPair == null) if (entityPair == null)
{ {
throw new AlfrescoRuntimeException("No property class exists for ID " + id); throw new DataIntegrityViolationException("No property class exists for ID " + id);
} }
return entityPair; return entityPair;
} }
@@ -321,7 +345,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
Pair<Long, Date> entityPair = propertyDateValueCache.getByKey(id); Pair<Long, Date> entityPair = propertyDateValueCache.getByKey(id);
if (entityPair == null) if (entityPair == null)
{ {
throw new AlfrescoRuntimeException("No property date value exists for ID " + id); throw new DataIntegrityViolationException("No property date value exists for ID " + id);
} }
return entityPair; return entityPair;
} }
@@ -422,7 +446,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
Pair<Long, String> entityPair = propertyStringValueCache.getByKey(id); Pair<Long, String> entityPair = propertyStringValueCache.getByKey(id);
if (entityPair == null) if (entityPair == null)
{ {
throw new AlfrescoRuntimeException("No property string value exists for ID " + id); throw new DataIntegrityViolationException("No property string value exists for ID " + id);
} }
return entityPair; return entityPair;
} }
@@ -507,7 +531,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
Pair<Long, Double> entityPair = propertyDoubleValueCache.getByKey(id); Pair<Long, Double> entityPair = propertyDoubleValueCache.getByKey(id);
if (entityPair == null) if (entityPair == null)
{ {
throw new AlfrescoRuntimeException("No property double value exists for ID " + id); throw new DataIntegrityViolationException("No property double value exists for ID " + id);
} }
return entityPair; return entityPair;
} }
@@ -590,7 +614,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
Pair<Long, Serializable> entityPair = propertySerializableValueCache.getByKey(id); Pair<Long, Serializable> entityPair = propertySerializableValueCache.getByKey(id);
if (entityPair == null) if (entityPair == null)
{ {
throw new AlfrescoRuntimeException("No property serializable value exists for ID " + id); throw new DataIntegrityViolationException("No property serializable value exists for ID " + id);
} }
return entityPair; return entityPair;
} }
@@ -651,7 +675,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
Pair<Long, Serializable> entityPair = propertyValueCache.getByKey(id); Pair<Long, Serializable> entityPair = propertyValueCache.getByKey(id);
if (entityPair == null) if (entityPair == null)
{ {
throw new AlfrescoRuntimeException("No property value exists for ID " + id); throw new DataIntegrityViolationException("No property value exists for ID " + id);
} }
return entityPair; return entityPair;
} }
@@ -662,64 +686,10 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
return entityPair; return entityPair;
} }
/**
* {@inheritDoc}
* @see #getOrCreatePropertyValueImpl(Serializable, int, int)
*/
public Pair<Long, Serializable> getOrCreatePropertyValue(Serializable value) public Pair<Long, Serializable> getOrCreatePropertyValue(Serializable value)
{ {
return getOrCreatePropertyValueImpl(value, null, Integer.MAX_VALUE, 0); Pair<Long, Serializable> entityPair = propertyValueCache.getOrCreateByValue(value);
} return (Pair<Long, Serializable>) entityPair;
/**
* {@inheritDoc}
* @see #getOrCreatePropertyValueImpl(Serializable, Long, int, int)
*/
public Pair<Long, Serializable> getOrCreatePropertyValue(Serializable value, int maxDepth)
{
return getOrCreatePropertyValueImpl(value, null, maxDepth, 0);
}
@SuppressWarnings("unchecked")
private Pair<Long, Serializable> getOrCreatePropertyValueImpl(
Serializable value,
Long rootId,
int maxDepth,
int currentDepth)
{
if (value != null && maxDepth > currentDepth && value instanceof Map<?, ?>)
{
// TODO: Go through cache?
// The default is to do a deep expansion
Long mapId = createPropertyMapImpl(
(Map<? extends Serializable, ? extends Serializable>)value,
rootId,
maxDepth,
currentDepth);
Pair<Long, Serializable> entityPair = new Pair<Long, Serializable>(mapId, value);
// Cache instance by ID only
propertyValueCache.updateValue(mapId, value);
return entityPair;
}
else if (value != null && maxDepth > currentDepth && value instanceof Collection<?>)
{
// TODO: Go through cache?
// The default is to do a deep expansion
Long collectionId = createPropertyCollectionImpl(
(Collection<? extends Serializable>)value,
rootId,
maxDepth,
currentDepth);
Pair<Long, Serializable> entityPair = new Pair<Long, Serializable>(collectionId, value);
// Cache instance by ID only
propertyValueCache.updateValue(collectionId, value);
return entityPair;
}
else
{
Pair<Long, Serializable> entityPair = propertyValueCache.getOrCreateByValue(value);
return (Pair<Long, Serializable>) entityPair;
}
} }
/** /**
@@ -727,25 +697,16 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
*/ */
private class PropertyValueCallbackDAO extends EntityLookupCallbackDAOAdaptor<Long, Serializable, Serializable> private class PropertyValueCallbackDAO extends EntityLookupCallbackDAOAdaptor<Long, Serializable, Serializable>
{ {
@SuppressWarnings("unchecked")
private final Serializable convertToValue(PropertyValueEntity entity) private final Serializable convertToValue(PropertyValueEntity entity)
{ {
if (entity == null) if (entity == null)
{ {
return null; return null;
} }
final Serializable actualValue; Long actualTypeId = entity.getActualTypeId();
if (entity.getPersistedTypeEnum() == PersistedType.CONSTRUCTABLE) final Class<Serializable> actualType = (Class<Serializable>) getPropertyClassById(actualTypeId).getSecond();
{ final Serializable actualValue = entity.getValue(actualType, converter);
actualValue = entity.getPersistedValue();
}
else
{
Long actualTypeId = entity.getActualTypeId();
Class<?> actualType = getPropertyClassById(actualTypeId).getSecond();
Serializable entityValue = entity.getPersistedValue();
actualValue = (Serializable) converter.convert(actualType, entityValue);
}
// Done // Done
return actualValue; return actualValue;
} }
@@ -764,11 +725,11 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
public Serializable getValueKey(Serializable value) public Serializable getValueKey(Serializable value)
{ {
PersistedType persistedType = PropertyValueEntity.getPersistedTypeEnum(value); PersistedType persistedType = PropertyValueEntity.getPersistedTypeEnum(value, converter);
// We don't return keys for pure Serializable instances // We don't return keys for pure Serializable instances
if (persistedType == PersistedType.SERIALIZABLE) if (persistedType == PersistedType.SERIALIZABLE)
{ {
// It will be Serialized, so no key // It will be Serialized, so no search key
return null; return null;
} }
else if (value instanceof String) else if (value instanceof String)
@@ -785,34 +746,18 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
public Pair<Long, Serializable> createValue(Serializable value) public Pair<Long, Serializable> createValue(Serializable value)
{ {
PropertyValueEntity entity = createPropertyValue(value); PropertyValueEntity entity = createPropertyValue(value);
Long entityId = entity.getId();
// Create the link entry for the property
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)
{ {
List<PropertyIdSearchRow> rows = findPropertyValueById(key); PropertyValueEntity entity = findPropertyValueById(key);
if (rows.size() == 0) return convertEntityToPair(entity);
{
// 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)
{ {
if (value != null)
{
if (value instanceof Map<?, ?> || value instanceof Collection<?>)
{
throw new IllegalArgumentException("Should not be searching for Maps or Collections");
}
}
PropertyValueEntity entity = findPropertyValueByValue(value); PropertyValueEntity entity = findPropertyValueByValue(value);
return convertEntityToPair(entity); return convertEntityToPair(entity);
} }
@@ -828,22 +773,304 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
} }
} }
protected abstract List<PropertyIdSearchRow> findPropertyValueById(Long id); protected abstract PropertyValueEntity 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);
//================================
// 'alf_prop_root' accessors
//================================
public Serializable getPropertyById(Long id)
{
if (id == null)
{
throw new IllegalArgumentException("Cannot look up entity by null ID.");
}
Pair<Long, Serializable> entityPair = propertyCache.getByKey(id);
if (entityPair == null)
{
throw new DataIntegrityViolationException("No property value exists for ID " + id);
}
return entityPair.getSecond();
}
/**
* {@inheritDoc}
* @see #createPropertyImpl(Serializable, int, int)
*/
public Long createProperty(Serializable value)
{
// We will need a new root
Long rootPropId = createPropertyRoot();
createPropertyImpl(rootPropId, 0L, 0L, null, value);
// Push this value into the cache
propertyCache.updateValue(rootPropId, value);
// Done
if (logger.isDebugEnabled())
{
logger.debug(
"Created property: \n" +
" ID: " + rootPropId + "\n" +
" Value: " + value);
}
return rootPropId;
}
public void updateProperty(Long rootPropId, Serializable value)
{
// Remove all entries for the root
PropertyRootEntity entity = getPropertyRoot(rootPropId);
if (entity == null)
{
throw new DataIntegrityViolationException("No property root exists for ID " + rootPropId);
}
// Remove all links using the root
deletePropertyLinks(rootPropId);
// Create the new properties and update the cache
createPropertyImpl(rootPropId, 0L, 0L, null, value);
propertyCache.updateValue(rootPropId, value);
// Update the property root to detect concurrent modification
updatePropertyRoot(entity);
// Done
if (logger.isDebugEnabled())
{
logger.debug(
"Updated property: \n" +
" ID: " + rootPropId + "\n" +
" Value: " + value);
}
}
public void deleteProperty(Long id)
{
deletePropertyRoot(id);
// Done
if (logger.isDebugEnabled())
{
logger.debug(
"Deleted property: \n" +
" ID: " + id);
}
}
/**
* @param propIndex a unique index within the context of the current property root
*/
@SuppressWarnings("unchecked")
private long createPropertyImpl(
Long rootPropId,
long propIndex,
long containedIn,
Long keyPropId,
Serializable value)
{
// Keep track of the index for this property. It gets used later when making the link entry.
long thisPropIndex = propIndex;
Long valuePropId = null;
if (value == null)
{
// The key and the value are the same
valuePropId = getOrCreatePropertyValue(value).getFirst();
}
else if (value instanceof Map<?, ?>)
{
Map<Serializable, Serializable> map = (Map<Serializable, Serializable>) value;
// Check if the it has a default constructor
Serializable emptyInstance = constructEmptyContainer(value.getClass());
if (emptyInstance == null)
{
// No default constructor, so we just throw the whole thing in as a single property
valuePropId = getOrCreatePropertyValue(value).getFirst();
}
else
{
// Persist the empty map
valuePropId = getOrCreatePropertyValue(emptyInstance).getFirst();
// Persist the individual entries
for (Map.Entry<Serializable, Serializable> entry : map.entrySet())
{
// Recurse for each value
Serializable mapKey = entry.getKey();
Serializable mapValue = entry.getValue();
// Get the IDs for these
Long mapKeyId = getOrCreatePropertyValue(mapKey).getFirst();
propIndex = createPropertyImpl(
rootPropId,
propIndex + 1L,
thisPropIndex,
mapKeyId,
mapValue);
}
}
}
else if (value instanceof Collection<?>)
{
Collection<Serializable> collection = (Collection<Serializable>) value;
// Check if the it has a default constructor
Serializable emptyInstance = constructEmptyContainer(value.getClass());
if (emptyInstance == null)
{
// No default constructor, so we just throw the whole thing in as a single property
valuePropId = getOrCreatePropertyValue(value).getFirst();
}
else
{
// Persist the empty collection
valuePropId = getOrCreatePropertyValue(emptyInstance).getFirst();
// Persist the individual entries
for (Serializable collectionValue : collection)
{
// Recurse for each value
propIndex = createPropertyImpl(
rootPropId,
propIndex + 1L,
thisPropIndex,
null,
collectionValue);
}
}
}
else
{
// The key and the value are the same
valuePropId = getOrCreatePropertyValue(value).getFirst();
}
// Create a link entry
if (keyPropId == null)
{
// If the key matches the value then it is the root
keyPropId = valuePropId;
}
createPropertyLink(rootPropId, thisPropIndex, containedIn, keyPropId, valuePropId);
// Done
return propIndex;
}
private static final Serializable EMPTY_HASHMAP = new HashMap<Serializable, Serializable>();
private static final Serializable EMPTY_LIST = new ArrayList<Serializable>();
private static final Serializable EMPTY_SET = new HashSet<Serializable>();
/**
* Returns a reconstructable instance
*
* @return Returns an empty instance of the given container (map or collection), or
* <tt>null</tt> if it is not possible to do
*/
protected Serializable constructEmptyContainer(Class<?> clazz)
{
try
{
return (Serializable) clazz.getConstructor().newInstance();
}
catch (Throwable e)
{
// Can't be constructed, so we just choose a well-known implementation.
// There are so many variations on maps and collections (Unmodifiable, Immutable, etc)
// that to not choose an alternative would leave the database full of BLOBs
}
if (Map.class.isAssignableFrom(clazz))
{
return EMPTY_HASHMAP;
}
else if (List.class.isAssignableFrom(clazz))
{
return EMPTY_LIST;
}
else if (Set.class.isAssignableFrom(clazz))
{
return EMPTY_SET;
}
else
{
logger.warn("Unable to find suitable container type with default constructor: " + clazz);
return null;
}
}
/**
* Callback for <b>alf_prop_root</b> DAO.
*/
private class PropertyCallbackDAO extends EntityLookupCallbackDAOAdaptor<Long, Serializable, Serializable>
{
public Pair<Long, Serializable> createValue(Serializable value)
{
PropertyValueEntity entity = createPropertyValue(value);
// Done
return new Pair<Long, Serializable>(entity.getId(), value);
}
public Pair<Long, Serializable> findByKey(Long key)
{
List<PropertyIdSearchRow> rows = findPropertyById(key);
if (rows.size() == 0)
{
// No results
return null;
}
Serializable value = convertPropertyIdSearchRows(rows);
return new Pair<Long, Serializable>(key, value);
}
/**
* No-op. This is implemented as we just want to update the cache.
* @return Returns 0 always
*/
@Override
public int updateValue(Long key, Serializable value)
{
return 0;
}
}
protected abstract List<PropertyIdSearchRow> findPropertyById(Long id);
protected abstract Long createPropertyRoot();
protected abstract PropertyRootEntity getPropertyRoot(Long id);
protected abstract PropertyRootEntity updatePropertyRoot(PropertyRootEntity entity);
protected abstract void deletePropertyRoot(Long id);
/**
* Create an entry for the map or collection link.
*
* @param rootPropId the root (entry-point) property ID
* @param propIndex the property number within the root property
* @param containedIn the property that contains the current value
* @param keyPropId the map key entity ID or collection position count
* @param valuePropId the ID of the entity storing the value (may be another map or collection)
*/
protected abstract void createPropertyLink(
Long rootPropId,
Long propIndex,
Long containedIn,
Long keyPropId,
Long valuePropId);
/**
* Remove all property links for a given property root.
*
* @param rootPropId the root (entry-point) property ID
*/
protected abstract int deletePropertyLinks(Long rootPropId);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Serializable convertPropertyIdSearchRows(List<PropertyIdSearchRow> rows) public Serializable convertPropertyIdSearchRows(List<PropertyIdSearchRow> rows)
{ {
// Shortcut if there are no results
if (rows.size() == 0)
{
return null;
}
/* /*
* The results are ordered by the root_prop_id, current_prop_id and value_prop_id. * The results all share the same root property. Pass through the results and construct all
* However, for safety (data patching, etc) we take a first pass to create the * instances, storing them ordered by prop_index.
* basic properties before hooking them all together in a second pass.
*/ */
final Map<Long, Serializable> values = new HashMap<Long, Serializable>(rows.size()); Map<Long, Serializable> valuesByPropIndex = new HashMap<Long, Serializable>(7);
List<PropertyLinkEntity> keyRows = new ArrayList<PropertyLinkEntity>(5); TreeMap<Long, PropertyLinkEntity> linkEntitiesByPropIndex = new TreeMap<Long, PropertyLinkEntity>();
Serializable result = null; Long rootPropId = null; // Keep this to ensure the root_prop_id is common
Long rootPropId = null;
for (PropertyIdSearchRow row : rows) for (PropertyIdSearchRow row : rows)
{ {
// Check that we are handling a single root property // Check that we are handling a single root property
@@ -859,216 +1086,74 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
} }
PropertyLinkEntity linkEntity = row.getLinkEntity(); PropertyLinkEntity linkEntity = row.getLinkEntity();
Long propIndex = linkEntity.getPropIndex();
Long valuePropId = linkEntity.getValuePropId();
PropertyValueEntity valueEntity = row.getValueEntity(); PropertyValueEntity valueEntity = row.getValueEntity();
// Construct the value (Maps and Collections should be CONSTRUCTABLE) // Get the value
Serializable value = propertyValueCallback.convertToValue(valueEntity); Serializable value;
// Keep it if (valueEntity != null)
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); value = propertyValueCallback.convertToValue(valueEntity);
} }
if (linkEntity.getRootPropId() == linkEntity.getValuePropId()) else
{ {
// We found the root // Go N+1 if the value entity was not retrieved
result = value; value = getPropertyValueById(valuePropId);
} }
// Keep it for later
valuesByPropIndex.put(propIndex, value);
linkEntitiesByPropIndex.put(propIndex, linkEntity);
} }
// We expect a value to be found unless the results are empty Serializable result = null;
if (result == null) // Iterate again, adding values to the collections and looking for the root property
for (Map.Entry<Long, PropertyLinkEntity> entry : linkEntitiesByPropIndex.entrySet())
{ {
return null; PropertyLinkEntity linkEntity = entry.getValue();
} Long propIndex = linkEntity.getPropIndex();
Long containedIn = linkEntity.getContainedIn();
// Now we have all our constructed values and the mapping rows: build the collections Long keyPropId = linkEntity.getKeyPropId();
for (PropertyLinkEntity propertyLinkEntity : keyRows) Serializable value = valuesByPropIndex.get(propIndex);
{ // Check if this is the root property
Serializable value = values.get(propertyLinkEntity.getValuePropId()); if (propIndex.equals(containedIn))
Serializable currentProp = values.get(propertyLinkEntity.getCurrentPropId());
if (value == null)
{ {
logger.error("No value found for link property: " + propertyLinkEntity); if (result != null)
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); logger.error("Found inconsistent property root data: " + linkEntity);
continue; continue;
} }
Serializable key = keyPair.getSecond(); // This property is contained in itself i.e. it's the root
Map<Serializable, Serializable> map = (Map<Serializable, Serializable>) currentProp; result = value;
map.put(key, value);
} }
else if (currentProp instanceof Set<?>) else
{ {
// We can ignore the key - it won't make a difference // Add the value to the container to which it belongs.
Set<Serializable> set = (Set<Serializable>) currentProp; // The ordering is irrelevant for some containers; but where it is important,
set.add(value); // ordering given by the prop_index will ensure that values are added back
} // in the order in which the container originally iterated over them
// Results 'should' be ordered by key Serializable container = valuesByPropIndex.get(containedIn);
// else if (currentProp instanceof List<?>) if (container == null)
// { {
// // The order is important logger.error("Found container ID that doesn't have a value: " + linkEntity);
// List<Serializable> collection = (List<Serializable>) currentProp; }
// collection.add(keyId.intValue(), value); else if (container instanceof Map<?, ?>)
// } {
else if (currentProp instanceof Collection<?>) Map<Serializable, Serializable> map = (Map<Serializable, Serializable>) container;
{ Serializable mapKey = getPropertyValueById(keyPropId).getSecond();
// The order is important map.put(mapKey, value);
Collection<Serializable> collection = (Collection<Serializable>) currentProp; }
collection.add(value); else if (container instanceof Collection<?>)
{
Collection<Serializable> collection = (Collection<Serializable>) container;
collection.add(value);
}
else
{
logger.error("Found container ID that is not a map or collection: " + linkEntity);
}
} }
} }
// This will have put the values into the correct containers // This will have put the values into the correct containers
return result; return result;
} }
//================================
// Special handling of maps and collections
//================================
/**
* Recursive method to write a map out. If the root entity ID is <tt>null</tt> then the current
* map ID is used as the root.
*
* @param value the map to write
* @param rootId the root entity ID, which may be a map or a collection
* @return Returns the ID of the newly-written map
*/
private <K extends Serializable, V extends Serializable> Long createPropertyMapImpl(
Map<K, V> map,
Long rootId,
int maxDepth,
int currentDepth)
{
// Create the root of the map
Class<?> clazz = null;
if (map instanceof MLText)
{
clazz = MLText.class;
}
else
{
clazz = HashMap.class;
}
// 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
if (rootId == null)
{
rootId = entityId;
}
// Create the link entry for the root
createPropertyLink(rootId, entityId, entityId, 0L);
// Now iterate over the entries and create properties for the keys and values
for (Map.Entry<K, V> entry : map.entrySet())
{
K key = entry.getKey();
Long keyId = getOrCreatePropertyValue(key).getFirst();
V value = entry.getValue();
// Callback with this level, incrementing the current depth
Pair<Long, Serializable> valuePair = getOrCreatePropertyValueImpl(
(Serializable) value,
rootId,
maxDepth,
currentDepth + 1);
Long valueId = valuePair.getFirst();
// Now write the mapping entry
createPropertyLink(rootId, entityId, valueId, keyId);
}
// Done
return entityId;
}
/**
* Recursive method to write a collection out. If the root entity ID is <tt>null</tt> then the current
* collection ID is used as the root.
*
* @param value the collection to write
* @param rootId the root property ID
* @return Returns the ID of the newly-written collection
*/
private <V extends Serializable> Long createPropertyCollectionImpl(
Collection<V> collection,
Long rootId,
int maxDepth,
int currentDepth)
{
// Create the root of the collection
Class<?> clazz = null;
if (collection instanceof Set<?>)
{
clazz = HashSet.class;
}
else
{
clazz = ArrayList.class;
}
// 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
if (rootId == null)
{
rootId = entityId;
}
// Create the link entry for the root
createPropertyLink(rootId, entityId, entityId, 0L);
// Now iterate over the entries and create properties for the keys and values
long index = 0L;
for (V value : collection)
{
// Callback with this level, incrementing the current depth
Pair<Long, Serializable> valuePair = getOrCreatePropertyValueImpl(
(Serializable) value,
rootId,
maxDepth,
currentDepth + 1);
Long valueId = valuePair.getFirst();
// Now write the mapping entry
Long keyId = new Long(index);
createPropertyLink(rootId, entityId, valueId, keyId);
// Keep iterating
index++;
}
return entityId;
}
/**
* Create an entry for the map or collection link
*
* @param rootPropId the root (entry-point) map or collection ID
* @param currentPropId the current map or collection ID
* @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(
Long rootPropId,
Long currentPropId,
Long valueId,
Long keyId);
} }

View File

@@ -25,11 +25,15 @@
package org.alfresco.repo.domain.propval; package org.alfresco.repo.domain.propval;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.domain.propval.PropertyValueEntity.PersistedType; import org.alfresco.repo.domain.propval.PropertyValueEntity.PersistedType;
import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;
@@ -59,12 +63,23 @@ public class DefaultPropertyTypeConverter implements PropertyTypeConverter
{ {
// Create the map of class-type // Create the map of class-type
Map<Class<?>, PersistedType> mapClass = new HashMap<Class<?>, PersistedType>(29); Map<Class<?>, PersistedType> mapClass = new HashMap<Class<?>, PersistedType>(29);
mapClass.put(Boolean.class, PersistedType.LONG);
mapClass.put(Short.class, PersistedType.LONG);
mapClass.put(Integer.class, PersistedType.LONG);
mapClass.put(Long.class, PersistedType.LONG);
mapClass.put(Date.class, PersistedType.LONG);
mapClass.put(Float.class, PersistedType.DOUBLE);
mapClass.put(Double.class, PersistedType.DOUBLE);
mapClass.put(String.class, PersistedType.STRING);
mapClass.put(Class.class, PersistedType.STRING);
mapClass.put(NodeRef.class, PersistedType.STRING); mapClass.put(NodeRef.class, PersistedType.STRING);
mapClass.put(Period.class, PersistedType.STRING); mapClass.put(Period.class, PersistedType.STRING);
mapClass.put(Locale.class, PersistedType.STRING); mapClass.put(Locale.class, PersistedType.STRING);
mapClass.put(AssociationRef.class, PersistedType.STRING); mapClass.put(AssociationRef.class, PersistedType.STRING);
mapClass.put(ChildAssociationRef.class, PersistedType.STRING); mapClass.put(ChildAssociationRef.class, PersistedType.STRING);
// Everything else is just Serializable
defaultPersistedTypesByClass = Collections.unmodifiableMap(mapClass); defaultPersistedTypesByClass = Collections.unmodifiableMap(mapClass);
} }
private Map<Class<?>, PersistedType> persistenceMapping; private Map<Class<?>, PersistedType> persistenceMapping;
@@ -89,6 +104,72 @@ public class DefaultPropertyTypeConverter implements PropertyTypeConverter
this.persistenceMapping.put(clazz, targetType); this.persistenceMapping.put(clazz, targetType);
} }
/**
* Determines if the value can be adequately recreated (to equality) by creating
* a new instance. For example, a <b>java.util.HashMap</b> is constructable provided
* that the map is empty.
* <p>
* Subclasses can override this to handle any well-known types, and in conjunction with
* {@link #constructInstance(String)}, even choose to return <tt>true</tt> if it needs a
* non-default constructor.
*
* @param value the value to check
* @return Returns <tt>true</tt> if the value can be reconstructed by
* instantiation using a default constructor
*/
protected boolean isConstructable(Serializable value)
{
// Is it in the set directly
Class<?> valueClazz = value.getClass();
// Check for default constructor
try
{
valueClazz.getConstructor();
}
catch (NoSuchMethodException e)
{
// Can't reconstruct using just a type name
return false;
}
// Maps and Collections
if (value instanceof Map<?, ?>)
{
Map<?, ?> mapValue = (Map<?, ?>) value;
return mapValue.isEmpty();
}
else if (value instanceof Collection<?>)
{
Collection<?> collectionValue = (Collection<?>) value;
return collectionValue.isEmpty();
}
else
{
// We don't recognise it
return false;
}
}
/**
* {@inheritDoc}
*/
public Serializable constructInstance(String clazzName)
{
try
{
Class<?> clazz = Class.forName(clazzName);
Constructor<?> constructor = clazz.getConstructor();
return (Serializable) constructor.newInstance();
}
catch (ClassCastException e)
{
throw new AlfrescoRuntimeException("The constructed property is not serializable: " + clazzName);
}
catch (Throwable e)
{
throw new AlfrescoRuntimeException("Unable to construct property for class: " + clazzName);
}
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@@ -98,20 +179,38 @@ public class DefaultPropertyTypeConverter implements PropertyTypeConverter
Class<?> clazz = value.getClass(); Class<?> clazz = value.getClass();
PersistedType type = persistenceMapping.get(clazz); PersistedType type = persistenceMapping.get(clazz);
if (type == null) if (type != null)
{ {
return PersistedType.SERIALIZABLE; return type;
}
// Before we give up, check if it is constructable
if (isConstructable(value))
{
// It'll just be given back as a class name i.e. a CONSTRUCTABLE
return PersistedType.CONSTRUCTABLE;
} }
else else
{ {
return type; // Check if there are converters to and from well-known types, just in case
if (DefaultTypeConverter.INSTANCE.getConverter(clazz, Long.class) != null &&
DefaultTypeConverter.INSTANCE.getConverter(Long.class, clazz) != null)
{
return PersistedType.LONG;
}
else if (DefaultTypeConverter.INSTANCE.getConverter(clazz, String.class) != null &&
DefaultTypeConverter.INSTANCE.getConverter(String.class, clazz) != null)
{
return PersistedType.STRING;
}
// No hope of doing anything useful other than storing it
return PersistedType.SERIALIZABLE;
} }
} }
/** /**
* Performs the conversion * Performs the conversion
*/ */
public <T> T convert(Class<T> targetClass, Object value) public <T> T convert(Class<T> targetClass, Serializable value)
{ {
return DefaultTypeConverter.INSTANCE.convert(targetClass, value); return DefaultTypeConverter.INSTANCE.convert(targetClass, value);
} }

View File

@@ -64,25 +64,30 @@ public class PropertyIdSearchRow
return valueEntity; return valueEntity;
} }
public void setRootPropId(long rootPropId) public void setRootPropId(Long rootPropId)
{ {
linkEntity.setRootPropId(rootPropId); linkEntity.setRootPropId(rootPropId);
} }
public void setCurrentPropId(long currentPropId) public void setPropIndex(Long propIndex)
{ {
linkEntity.setCurrentPropId(currentPropId); linkEntity.setPropIndex(propIndex);
} }
public void setValuePropId(long valuePropId) public void setContainedIn(Long containedIn)
{ {
linkEntity.setValuePropId(valuePropId); linkEntity.setContainedIn(containedIn);
} }
public void setKeyPropId(long keyPropId) public void setKeyPropId(Long keyPropId)
{ {
linkEntity.setKeyPropId(keyPropId); linkEntity.setKeyPropId(keyPropId);
} }
public void setValuePropId(Long valuePropId)
{
linkEntity.setValuePropId(valuePropId);
}
public void setActualTypeId(Long actualTypeId) public void setActualTypeId(Long actualTypeId)
{ {

View File

@@ -32,93 +32,77 @@ package org.alfresco.repo.domain.propval;
*/ */
public class PropertyLinkEntity public class PropertyLinkEntity
{ {
private long rootPropId; private Long rootPropId;
private long currentPropId; private Long propIndex;
private long valuePropId; private Long containedIn;
private long keyPropId; private Long keyPropId;
private Long valuePropId;
public PropertyLinkEntity() public PropertyLinkEntity()
{ {
} }
@Override
public int hashCode()
{
return (int) rootPropId + (int) valuePropId;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
else if (obj instanceof PropertyLinkEntity)
{
PropertyLinkEntity that = (PropertyLinkEntity) obj;
return
this.rootPropId == that.rootPropId &&
this.currentPropId == that.currentPropId &&
this.valuePropId == that.valuePropId &&
this.keyPropId == that.keyPropId;
}
else
{
return false;
}
}
@Override @Override
public String toString() public String toString()
{ {
StringBuilder sb = new StringBuilder(512); StringBuilder sb = new StringBuilder(512);
sb.append("PropertyLinkEntity") sb.append("PropertyLinkEntity")
.append("[ rootPropId=").append(rootPropId) .append("[ rootPropId=").append(rootPropId)
.append(", currentPropId=").append(currentPropId) .append(", propIndex=").append(propIndex)
.append(", valuePropId=").append(valuePropId) .append(", containedIn=").append(containedIn)
.append(", keyPropId=").append(keyPropId) .append(", keyPropId=").append(keyPropId)
.append(", valuePropId=").append(valuePropId)
.append("]"); .append("]");
return sb.toString(); return sb.toString();
} }
public long getRootPropId() public Long getRootPropId()
{ {
return rootPropId; return rootPropId;
} }
public void setRootPropId(long rootPropId) public void setRootPropId(Long rootPropId)
{ {
this.rootPropId = rootPropId; this.rootPropId = rootPropId;
} }
public long getCurrentPropId() public Long getPropIndex()
{ {
return currentPropId; return propIndex;
} }
public void setCurrentPropId(long currentPropId) public void setPropIndex(Long propIndex)
{ {
this.currentPropId = currentPropId; this.propIndex = propIndex;
} }
public long getValuePropId() public Long getContainedIn()
{ {
return valuePropId; return containedIn;
} }
public void setValuePropId(long valuePropId) public void setContainedIn(Long containedIn)
{ {
this.valuePropId = valuePropId; this.containedIn = containedIn;
} }
public long getKeyPropId() public Long getKeyPropId()
{ {
return keyPropId; return keyPropId;
} }
public void setKeyPropId(long keyPropId) public void setKeyPropId(Long keyPropId)
{ {
this.keyPropId = keyPropId; this.keyPropId = keyPropId;
} }
public Long getValuePropId()
{
return valuePropId;
}
public void setValuePropId(Long valuePropId)
{
this.valuePropId = valuePropId;
}
} }

View File

@@ -0,0 +1,84 @@
/*
* 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;
/**
* Entity bean for <b>alf_prop_root</b> table.
*
* @author Derek Hulley
* @since 3.2
*/
public class PropertyRootEntity
{
private Long id;
private short version;
public PropertyRootEntity()
{
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder(512);
sb.append("PropertyRootEntity")
.append("[ ID=").append(id)
.append(", version=").append(version)
.append("]");
return sb.toString();
}
public void incrementVersion()
{
if (version >= Short.MAX_VALUE)
{
this.version = 0;
}
else
{
this.version++;
}
}
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public short getVersion()
{
return version;
}
public void setVersion(short version)
{
this.version = version;
}
}

View File

@@ -48,10 +48,14 @@ public interface PropertyTypeConverter
* <li>{@link PersistedType#DOUBLE}</li> * <li>{@link PersistedType#DOUBLE}</li>
* <li>{@link PersistedType#STRING}</li> * <li>{@link PersistedType#STRING}</li>
* <li>{@link PersistedType#SERIALIZABLE}</li> * <li>{@link PersistedType#SERIALIZABLE}</li>
* <li>{@link PersistedType#CONSTRUCTABLE}</li>
* </ul> * </ul>
* The converter should return {@link PersistedType#SERIALIZABLE} if no further conversions * The converter should return {@link PersistedType#SERIALIZABLE} if no further conversions
* are possible. Implicit in the return value is the converter's ability to do the * are possible. Implicit in the return value is the converter's ability to do the
* conversion when required. * conversion when required.
* <p/>
* If the converter can fully reconstruct an equal instance using just the name of the value's
* class, then {@link PersistedType#CONSTRUCTABLE} can be used.
* *
* @param value the value that does not have an obvious persistence slot * @param value the value that does not have an obvious persistence slot
* @return Returns the type of persistence to use * @return Returns the type of persistence to use
@@ -64,5 +68,13 @@ public interface PropertyTypeConverter
* @param value the value to convert * @param value the value to convert
* @return Returns the persisted type and value to persist * @return Returns the persisted type and value to persist
*/ */
<T> T convert(Class<T> targetClass, Object value); <T> T convert(Class<T> targetClass, Serializable value);
/**
* Construct an instance of an object that was deemed to be {@link PersistedType#CONSTRUCTABLE}.
*
* @param clazzName the name of the class
* @return Returns the new instance
*/
Serializable constructInstance(String clazzName);
} }

View File

@@ -175,27 +175,41 @@ public interface PropertyValueDAO
* @param value the value to find the ID for (may be <tt>null</tt>) * @param value the value to find the ID for (may be <tt>null</tt>)
*/ */
Pair<Long, Serializable> getOrCreatePropertyValue(Serializable value); Pair<Long, Serializable> getOrCreatePropertyValue(Serializable value);
//================================
// 'alf_prop_root' accessors
//================================
/** /**
* <b>alf_prop_value</b> accessor: find or create a property based on the value. * <b>alf_prop_root</b> accessor: get a property based on the database ID
* <b>Note:</b> This method will not recurse into maps or collections. Use the
* dedicated methods if you want recursion; otherwise maps and collections will
* be serialized and probably stored as BLOB values.
* <p>
* <u>Max depth examples</u> (there is no upper limit):
* <ul>
* <li>0: don't expand the value if it's a map or collection</li>
* <li>1: open the value up if it is a map or collection but don't do any more</li>
* <li>...</li>
* <li>10: open up 10 levels of maps or collections</li>
* </ul>
* All collections that are not opened up will be serialized unless there is a
* custom {@link PropertyTypeConverter converter} which can serialize it in an
* alternative format.
* *
* @param value the value to find the ID for (may be <tt>null</tt>) * @param id the ID (may not be <tt>null</tt>)
* @param maxDepth the maximum depth of collections and maps to iterate into * @return Returns the value of the property (never <tt>null</tt>)
*/ */
Pair<Long, Serializable> getOrCreatePropertyValue(Serializable value, int maxDepth); Serializable getPropertyById(Long id);
/**
* <b>alf_prop_root</b> accessor: find or create a property based on the value.
* <p/>
* All collections and maps will be opened up to any depth.
*
* @param value the value to create (may be <tt>null</tt>)
* @return Returns the new property's ID
*/
Long createProperty(Serializable value);
/**
* <b>alf_prop_root</b> accessor: update the property root to contain a new value.
*
* @param id the ID of the root property to change
* @param value the new property value
*/
void updateProperty(Long id, Serializable value);
/**
* <b>alf_prop_root</b> accessor: delete a property root completely
*
* @param id the ID of the root property to delete
*/
void deleteProperty(Long id);
/** /**
* Utility method to convert property query results into the original value. Note * Utility method to convert property query results into the original value. Note

View File

@@ -28,6 +28,7 @@ import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
@@ -65,6 +66,7 @@ public class PropertyValueDAOTest extends TestCase
ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
transactionService = serviceRegistry.getTransactionService(); transactionService = serviceRegistry.getTransactionService();
txnHelper = transactionService.getRetryingTransactionHelper(); txnHelper = transactionService.getRetryingTransactionHelper();
txnHelper.setMaxRetries(0);
propertyValueDAO = (PropertyValueDAO) ctx.getBean("propertyValueDAO"); propertyValueDAO = (PropertyValueDAO) ctx.getBean("propertyValueDAO");
@@ -434,6 +436,24 @@ public class PropertyValueDAOTest extends TestCase
} }
} }
public void testPropertyValue_EmptyHashMap() throws Exception
{
final HashMap<String, String> map = new HashMap<String, String>(15);
runPropertyValueTest(map, true);
}
public void testPropertyValue_EmptyArrayList() throws Exception
{
final ArrayList<String> list = new ArrayList<String>(20);
runPropertyValueTest(list, true);
}
public void testPropertyValue_EmptyHashSet() throws Exception
{
final HashSet<String> set = new HashSet<String>(20);
runPropertyValueTest(set, true);
}
public void testPropertyValue_MapOfStrings() throws Exception public void testPropertyValue_MapOfStrings() throws Exception
{ {
final HashMap<String, String> map = new HashMap<String, String>(15); final HashMap<String, String> map = new HashMap<String, String>(15);
@@ -446,7 +466,53 @@ public class PropertyValueDAOTest extends TestCase
runPropertyValueTest(map, false); runPropertyValueTest(map, false);
} }
public void testPropertyValue_MapOfMapOfSerializables() throws Exception /**
* Tests that the given value can be persisted and retrieved with the same resulting ID
*/
private Long runPropertyTest(final Serializable value) throws Exception
{
// Create it
RetryingTransactionCallback<Long> createValueCallback = new RetryingTransactionCallback<Long>()
{
public Long execute() throws Throwable
{
// Get the classes
return propertyValueDAO.createProperty(value);
}
};
final Long entityId = txnHelper.doInTransaction(createValueCallback, false);
assertNotNull(entityId);
// Retrieve it by ID
RetryingTransactionCallback<Serializable> getByIdCallback = new RetryingTransactionCallback<Serializable>()
{
public Serializable execute() throws Throwable
{
// Get the classes
return propertyValueDAO.getPropertyById(entityId);
}
};
final Serializable entityValueCheck = txnHelper.doInTransaction(getByIdCallback, false);
assertNotNull(entityValueCheck);
assertEquals(value, entityValueCheck);
// Done
return entityId;
}
public void testProperty_MapOfStrings() throws Exception
{
final HashMap<String, String> map = new HashMap<String, String>(15);
for (int i = 0; i < 20; i++)
{
String key = "MAP-KEY-" + i;
String value = "MAP-VALUE-" + i;
map.put(key, value);
}
runPropertyTest(map);
}
public void testProperty_MapOfMapOfSerializables() throws Exception
{ {
final HashMap<String, Serializable> mapInner = new HashMap<String, Serializable>(15); final HashMap<String, Serializable> mapInner = new HashMap<String, Serializable>(15);
for (int i = 0; i < 20; i++) for (int i = 0; i < 20; i++)
@@ -461,10 +527,10 @@ public class PropertyValueDAOTest extends TestCase
String key = "OUTERMAP-KEY-" + i; String key = "OUTERMAP-KEY-" + i;
mapOuter.put(key, mapInner); mapOuter.put(key, mapInner);
} }
runPropertyValueTest(mapOuter, false); runPropertyTest(mapOuter);
} }
public void testPropertyValue_MapOfMapOfStrings() throws Exception public void testProperty_MapOfMapOfStrings() throws Exception
{ {
final HashMap<String, String> mapInner = new HashMap<String, String>(15); final HashMap<String, String> mapInner = new HashMap<String, String>(15);
for (int i = 0; i < 20; i++) for (int i = 0; i < 20; i++)
@@ -479,10 +545,10 @@ public class PropertyValueDAOTest extends TestCase
String key = "OUTERMAP-KEY-" + i; String key = "OUTERMAP-KEY-" + i;
mapOuter.put(key, mapInner); mapOuter.put(key, mapInner);
} }
runPropertyValueTest(mapOuter, false); runPropertyTest(mapOuter);
} }
public void testPropertyValue_CollectionOfStrings() throws Exception public void testProperty_CollectionOfStrings() throws Exception
{ {
final ArrayList<String> list = new ArrayList<String>(20); final ArrayList<String> list = new ArrayList<String>(20);
for (int i = 0; i < 20; i++) for (int i = 0; i < 20; i++)
@@ -490,9 +556,77 @@ public class PropertyValueDAOTest extends TestCase
String value = "COLL-VALUE-" + i; String value = "COLL-VALUE-" + i;
list.add(value); list.add(value);
} }
runPropertyValueTest(list, false); runPropertyTest(list);
} }
public void testProperty_UpdateCollection() throws Exception
{
final ArrayList<String> list = new ArrayList<String>(20);
for (int i = 0; i < 20; i++)
{
String value = "COLL-VALUE-" + i;
list.add(value);
}
final Long propId = runPropertyTest(list);
// Now update it
list.add("Additional value");
RetryingTransactionCallback<Serializable> updateAndGetCallback = new RetryingTransactionCallback<Serializable>()
{
public Serializable execute() throws Throwable
{
// Get the classes
propertyValueDAO.updateProperty(propId, list);
// Get it by the ID again
return propertyValueDAO.getPropertyById(propId);
}
};
final Serializable entityValueCheck = txnHelper.doInTransaction(updateAndGetCallback, false);
assertNotNull(entityValueCheck);
assertEquals(list, entityValueCheck);
}
public void testProperty_Delete() throws Exception
{
final ArrayList<String> list = new ArrayList<String>(20);
final Long propId = runPropertyTest(list);
// Now delete it
RetryingTransactionCallback<Serializable> deleteCallback = new RetryingTransactionCallback<Serializable>()
{
public Serializable execute() throws Throwable
{
// Get the classes
propertyValueDAO.deleteProperty(propId);
return null;
}
};
txnHelper.doInTransaction(deleteCallback, false);
RetryingTransactionCallback<Serializable> failedGetCallback = new RetryingTransactionCallback<Serializable>()
{
public Serializable execute() throws Throwable
{
// Get it by the ID again
return propertyValueDAO.getPropertyById(propId);
}
};
try
{
final Serializable entityValueCheck = txnHelper.doInTransaction(failedGetCallback, false);
fail("Deleted property should not be gettable. Got: " + entityValueCheck);
}
catch(Throwable e)
{
// Expected
}
}
/*
* Switch off caches and rerun some of the tests
*/
public void testPropertyClass_NoCache() throws Exception public void testPropertyClass_NoCache() throws Exception
{ {
removeCaches(); removeCaches();

View File

@@ -25,9 +25,7 @@
package org.alfresco.repo.domain.propval; package org.alfresco.repo.domain.propval;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -171,11 +169,6 @@ public class PropertyValueEntity
*/ */
public static final Map<Short, PersistedType> persistedTypesByOrdinal; public static final Map<Short, PersistedType> persistedTypesByOrdinal;
/**
* An unmodifiable map of persisted type enums keyed by the classes they store
*/
public static final Map<Class<?>, PersistedType> persistedTypesByClass;
static static
{ {
// Create a pair for null values // Create a pair for null values
@@ -187,20 +180,6 @@ public class PropertyValueEntity
mapOrdinal.put(persistedType.getOrdinalNumber(), persistedType); mapOrdinal.put(persistedType.getOrdinalNumber(), persistedType);
} }
persistedTypesByOrdinal = Collections.unmodifiableMap(mapOrdinal); persistedTypesByOrdinal = Collections.unmodifiableMap(mapOrdinal);
// Create the map of class-type
Map<Class<?>, PersistedType> mapClass = new HashMap<Class<?>, PersistedType>(29);
mapClass.put(Boolean.class, PersistedType.LONG);
mapClass.put(Short.class, PersistedType.LONG);
mapClass.put(Integer.class, PersistedType.LONG);
mapClass.put(Long.class, PersistedType.LONG);
mapClass.put(Float.class, PersistedType.DOUBLE);
mapClass.put(Double.class, PersistedType.DOUBLE);
mapClass.put(String.class, PersistedType.STRING);
mapClass.put(Date.class, PersistedType.LONG);
mapClass.put(Map.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);
} }
private static final Log logger = LogFactory.getLog(PropertyValueEntity.class); private static final Log logger = LogFactory.getLog(PropertyValueEntity.class);
@@ -259,40 +238,29 @@ public class PropertyValueEntity
} }
/** /**
* Gets the value based on the persisted type. * Helper method to get the value based on the persisted type.
* Note that this is the value <b>as persisted</b> and not the original, client-required *
* value. * @param actualType the type to convert to
* @return Returns the persisted value * @param converter the data converter to use
* @return Returns the converted value
*/ */
public Serializable getPersistedValue() public Serializable getValue(Class<Serializable> actualType, PropertyTypeConverter converter)
{ {
switch (persistedTypeEnum) switch (persistedTypeEnum)
{ {
case NULL: case NULL:
return null; return null;
case LONG: case LONG:
return longValue; return converter.convert(actualType, Long.valueOf(longValue));
case DOUBLE: case DOUBLE:
return doubleValue; return converter.convert(actualType, Double.valueOf(doubleValue));
case STRING: case STRING:
return stringValue; return converter.convert(actualType, stringValue);
case SERIALIZABLE: case SERIALIZABLE:
return serializableValue; return converter.convert(actualType, serializableValue);
case CONSTRUCTABLE: case CONSTRUCTABLE:
// Construct an instance // Construct an instance using the converter (it knows best!)
try return converter.constructInstance(stringValue);
{
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");
} }
@@ -313,22 +281,11 @@ public class PropertyValueEntity
this.persistedTypeEnum = PersistedType.NULL; this.persistedTypeEnum = PersistedType.NULL;
this.longValue = LONG_ZERO; this.longValue = LONG_ZERO;
} }
else if (value instanceof Class<?>)
{
Class<?> clazz = (Class<?>) value;
stringValue = clazz.getName();
persistedTypeEnum = PersistedType.CONSTRUCTABLE;
persistedType = persistedTypeEnum.getOrdinalNumber();
}
else else
{ {
Class<?> valueClazz = value.getClass(); // The converter will be responsible for deserializing, so let it choose
persistedTypeEnum = persistedTypesByClass.get(valueClazz); // how the data is to be stored.
if (persistedTypeEnum == null) persistedTypeEnum = converter.getPersistentType(value);
{
// Give the converter a chance to change the type it must be persisted as
persistedTypeEnum = converter.getPersistentType(value);
}
persistedType = persistedTypeEnum.getOrdinalNumber(); persistedType = persistedTypeEnum.getOrdinalNumber();
// Get the class to persist as // Get the class to persist as
switch (persistedTypeEnum) switch (persistedTypeEnum)
@@ -342,6 +299,10 @@ public class PropertyValueEntity
case STRING: case STRING:
stringValue = converter.convert(String.class, value); stringValue = converter.convert(String.class, value);
break; break;
case CONSTRUCTABLE:
// A special case. There is no conversion, so just Store the name of the class.
stringValue = value.getClass().getName();
break;
case SERIALIZABLE: case SERIALIZABLE:
serializableValue = value; serializableValue = value;
break; break;
@@ -359,9 +320,12 @@ public class PropertyValueEntity
* Helper method to determine how the given value will be stored. * Helper method to determine how the given value will be stored.
* *
* @param value the value to check * @param value the value to check
* @param converter the type converter
* @return Returns the persisted type * @return Returns the persisted type
*
* @see PropertyTypeConverter#getPersistentType(Serializable)
*/ */
public static PersistedType getPersistedTypeEnum(Serializable value) public static PersistedType getPersistedTypeEnum(Serializable value, PropertyTypeConverter converter)
{ {
PersistedType persistedTypeEnum; PersistedType persistedTypeEnum;
if (value == null) if (value == null)
@@ -370,12 +334,7 @@ public class PropertyValueEntity
} }
else else
{ {
Class<?> valueClazz = value.getClass(); persistedTypeEnum = converter.getPersistentType(value);
persistedTypeEnum = persistedTypesByClass.get(valueClazz);
if (persistedTypeEnum == null)
{
persistedTypeEnum = PersistedType.SERIALIZABLE;
}
} }
return persistedTypeEnum; return persistedTypeEnum;
} }

View File

@@ -34,6 +34,7 @@ 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.PropertyIdSearchRow;
import org.alfresco.repo.domain.propval.PropertyLinkEntity; import org.alfresco.repo.domain.propval.PropertyLinkEntity;
import org.alfresco.repo.domain.propval.PropertyRootEntity;
import org.alfresco.repo.domain.propval.PropertySerializableValueEntity; import org.alfresco.repo.domain.propval.PropertySerializableValueEntity;
import org.alfresco.repo.domain.propval.PropertyStringQueryEntity; import org.alfresco.repo.domain.propval.PropertyStringQueryEntity;
import org.alfresco.repo.domain.propval.PropertyStringValueEntity; import org.alfresco.repo.domain.propval.PropertyStringValueEntity;
@@ -75,7 +76,13 @@ public class PropertyValueDAOImpl extends AbstractPropertyValueDAOImpl
private static final String SELECT_PROPERTY_VALUE_BY_STRING_VALUE = "alfresco.propval.select_PropertyValueByStringValue"; private static final String SELECT_PROPERTY_VALUE_BY_STRING_VALUE = "alfresco.propval.select_PropertyValueByStringValue";
private static final String INSERT_PROPERTY_VALUE = "alfresco.propval.insert_PropertyValue"; private static final String INSERT_PROPERTY_VALUE = "alfresco.propval.insert_PropertyValue";
private static final String SELECT_PROPERTY_BY_ID = "alfresco.propval.select_PropertyById";
private static final String SELECT_PROPERTY_ROOT_BY_ID = "alfresco.propval.select_PropertyRootById";
private static final String INSERT_PROPERTY_ROOT = "alfresco.propval.insert_PropertyRoot";
private static final String UPDATE_PROPERTY_ROOT = "alfresco.propval.update_PropertyRoot";
private static final String DELETE_PROPERTY_ROOT_BY_ID = "alfresco.propval.delete_PropertyRootById";
private static final String INSERT_PROPERTY_LINK = "alfresco.propval.insert_PropertyLink"; private static final String INSERT_PROPERTY_LINK = "alfresco.propval.insert_PropertyLink";
private static final String DELETE_PROPERTY_LINKS_BY_ROOT_ID = "alfresco.propval.delete_PropertyLinksByRootId";
private SqlMapClientTemplate template; private SqlMapClientTemplate template;
@@ -287,18 +294,30 @@ public class PropertyValueDAOImpl extends AbstractPropertyValueDAOImpl
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
protected List<PropertyIdSearchRow> findPropertyValueById(Long id) protected PropertyValueEntity findPropertyValueById(Long id)
{ {
PropertyValueEntity entity = new PropertyValueEntity(); PropertyValueEntity entity = new PropertyValueEntity();
entity.setId(id); entity.setId(id);
List<PropertyIdSearchRow> results = (List<PropertyIdSearchRow>) template.queryForList( List<PropertyValueEntity> results = (List<PropertyValueEntity>) template.queryForList(
SELECT_PROPERTY_VALUE_BY_ID, SELECT_PROPERTY_VALUE_BY_ID,
entity); entity);
// Done // At most one of the results represents a real value
return results; int size = results.size();
if (size == 0)
{
return null;
}
else if (size == 1)
{
return results.get(0);
}
else
{
logger.error("Found property value linked to multiple raw types: " + results);
return results.get(0);
}
} }
@SuppressWarnings("unchecked")
@Override @Override
protected PropertyValueEntity findPropertyValueByValue(Serializable value) protected PropertyValueEntity findPropertyValueByValue(Serializable value)
{ {
@@ -336,6 +355,8 @@ public class PropertyValueDAOImpl extends AbstractPropertyValueDAOImpl
case DOUBLE: case DOUBLE:
query = SELECT_PROPERTY_VALUE_BY_DOUBLE_VALUE; query = SELECT_PROPERTY_VALUE_BY_DOUBLE_VALUE;
break; break;
case CONSTRUCTABLE:
// The string value is the name of the class (e.g. 'java.util.HashMap')
case STRING: case STRING:
// It's best to query using the CRC and short end-value // It's best to query using the CRC and short end-value
query = SELECT_PROPERTY_VALUE_BY_STRING_VALUE; query = SELECT_PROPERTY_VALUE_BY_STRING_VALUE;
@@ -355,15 +376,8 @@ public class PropertyValueDAOImpl extends AbstractPropertyValueDAOImpl
PropertyValueEntity result = null; PropertyValueEntity result = null;
if (query != null) if (query != null)
{ {
List<PropertyValueEntity> results = (List<PropertyValueEntity>) template.queryForList( // Uniqueness is guaranteed by the tables, so we get one value only
query, result = (PropertyValueEntity) template.queryForObject(query, queryObject);
queryObject,
0, 1); // Only want one result
for (PropertyValueEntity row : results)
{
result = row;
break;
}
} }
// Done // Done
@@ -416,19 +430,77 @@ public class PropertyValueDAOImpl extends AbstractPropertyValueDAOImpl
return insertEntity; return insertEntity;
} }
//================================
// 'alf_prop_root' accessors
//================================
@SuppressWarnings("unchecked")
@Override
protected List<PropertyIdSearchRow> findPropertyById(Long id)
{
PropertyValueEntity entity = new PropertyValueEntity();
entity.setId(id);
List<PropertyIdSearchRow> results = (List<PropertyIdSearchRow>) template.queryForList(
SELECT_PROPERTY_BY_ID,
entity);
return results;
}
@Override
protected Long createPropertyRoot()
{
PropertyRootEntity rootEntity = new PropertyRootEntity();
rootEntity.setVersion((short)0);
return (Long) template.insert(INSERT_PROPERTY_ROOT, rootEntity);
}
@Override
protected PropertyRootEntity getPropertyRoot(Long id)
{
PropertyRootEntity entity = new PropertyRootEntity();
entity.setId(id);
return (PropertyRootEntity) template.queryForObject(SELECT_PROPERTY_ROOT_BY_ID, entity);
}
@Override
protected PropertyRootEntity updatePropertyRoot(PropertyRootEntity entity)
{
entity.incrementVersion();
template.update(UPDATE_PROPERTY_ROOT, entity, 1);
return entity;
}
@Override
protected void deletePropertyRoot(Long id)
{
PropertyRootEntity entity = new PropertyRootEntity();
entity.setId(id);
template.delete(DELETE_PROPERTY_ROOT_BY_ID, entity);
}
@Override @Override
protected void createPropertyLink( protected void createPropertyLink(
Long rootPropId, Long rootPropId,
Long currentPropId, Long propIndex,
Long valueId, Long containedIn,
Long keyId) Long keyPropId,
Long valuePropId)
{ {
PropertyLinkEntity insertEntity = new PropertyLinkEntity(); PropertyLinkEntity insertEntity = new PropertyLinkEntity();
insertEntity.setRootPropId(rootPropId); insertEntity.setRootPropId(rootPropId);
insertEntity.setCurrentPropId(currentPropId); insertEntity.setPropIndex(propIndex);
insertEntity.setValuePropId(valueId); insertEntity.setContainedIn(containedIn);
insertEntity.setKeyPropId(keyId); insertEntity.setKeyPropId(keyPropId);
insertEntity.setValuePropId(valuePropId);
template.insert(INSERT_PROPERTY_LINK, insertEntity); template.insert(INSERT_PROPERTY_LINK, insertEntity);
// Done // Done
} }
@Override
protected int deletePropertyLinks(Long rootPropId)
{
PropertyRootEntity entity = new PropertyRootEntity();
entity.setId(rootPropId);
return template.delete(DELETE_PROPERTY_LINKS_BY_ROOT_ID, entity);
}
} }