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">
<bean class="org.alfresco.repo.domain.propval.DefaultPropertyTypeConverter"/>
</property>
<!--
<property name="propertyClassCache" ref="immutableEntityCache"/>
<property name="propertyDateValueCache" ref="immutableEntityCache"/>
<property name="propertyStringValueCache" ref="immutableEntityCache"/>
<property name="propertyDoubleValueCache" ref="immutableEntityCache"/>
<property name="propertySerializableValueCache" ref="immutableEntityCache"/>
-->
<property name="propertyValueCache" ref="immutableEntityCache"/>
<property name="propertyCache" ref="immutableEntityCache"/>
</bean>
<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
(
id BIGINT NOT NULL AUTO_INCREMENT,
audit_model_id BIGINT NOT NULL,
version TINYINT 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 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)
) 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,
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_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)
) ENGINE=InnoDB;

View File

@@ -38,10 +38,11 @@ CREATE TABLE alf_prop_double_value
(
id BIGINT NOT NULL AUTO_INCREMENT,
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)
) ENGINE=InnoDB;
-- Stores unique, case-sensitive string values --
CREATE TABLE alf_prop_string_value
(
id BIGINT NOT NULL AUTO_INCREMENT,
@@ -49,7 +50,7 @@ CREATE TABLE alf_prop_string_value
string_end_lower VARCHAR(16) NOT NULL,
string_crc BIGINT NOT NULL,
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)
) ENGINE=InnoDB;
@@ -67,19 +68,29 @@ CREATE TABLE alf_prop_value
persisted_type TINYINT NOT NULL,
long_value BIGINT NOT NULL,
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)
) ENGINE=InnoDB;
CREATE TABLE alf_prop_link
(
root_prop_id BIGINT NOT NULL,
current_prop_id BIGINT NOT NULL,
value_prop_id BIGINT NOT NULL,
prop_index BIGINT NOT NULL,
contained_in BIGINT NOT NULL,
key_prop_id BIGINT NOT NULL,
INDEX idx_alf_prop_coll_rev (value_prop_id, root_prop_id),
CONSTRAINT fk_alf_prop_link_root FOREIGN KEY (root_prop_id) REFERENCES alf_prop_value (id) ON DELETE CASCADE,
PRIMARY KEY (root_prop_id, current_prop_id, value_prop_id, key_prop_id)
value_prop_id BIGINT NOT NULL,
CONSTRAINT fk_alf_prop_link_root FOREIGN KEY (root_prop_id) REFERENCES alf_prop_root (id) ON DELETE CASCADE,
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;
--

View File

@@ -27,8 +27,10 @@
</resultMap>
<resultMap id="result_AuditApplication" class="AuditApplication">
<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="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 id="result_AuditEntry" class="AuditEntry">
<result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/>
@@ -76,8 +78,8 @@
</sql>
<sql id="insert_AuditApplication_AutoIncrement">
insert into alf_audit_app (audit_model_id, app_name_id)
values (#auditModelId#, #applicationNameId#)
insert into alf_audit_app (version, app_name_id, audit_model_id, disabled_paths_id)
values (#version#, #applicationNameId#, #auditModelId#, #disabledPathsId#)
</sql>
<sql id="insert_AuditEntry_AutoIncrement">
@@ -99,65 +101,60 @@
content_crc = #contentCrc#
</select>
<!-- Get the audit application by model ID -->
<select id="select_AuditApplicationByModelId" parameterMap="parameter_IdMap" resultMap="result_AuditApplication">
<!-- Get the audit application by ID -->
<select id="select_AuditApplicationById" parameterMap="parameter_IdMap" resultMap="result_AuditApplication">
select
*
from
alf_audit_app
where
audit_model_id = ?
id = ?
</select>
<!-- Get audit entries -->
<!-- TODO: Use compound parameters for string searching, possibly namespaced from propval -->
<select id="select_AuditEntriesSimple" parameterClass="AuditQueryParameters" resultMap="result_AuditQueryNoValues">
<!-- Get the audit application by name ID -->
<select id="select_AuditApplicationByNameId" parameterMap="parameter_IdMap" resultMap="result_AuditApplication">
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
alf_audit_app app
join alf_prop_value app_pv on (app_pv.id = app.app_name_id)
join alf_prop_string_value app_sv on (app_sv.id = app_pv.long_value and app_pv.persisted_type = 3)
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
alf_audit_app
where
app_name_id = ?
</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 -->
<!-- TODO: Use compound parameters for string searching, possibly namespaced from propval -->
<select id="select_AuditEntriesWithValues" parameterClass="AuditQueryParameters" resultMap="result_AuditQueryAllValues">
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,
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,
pl.root_prop_id, pl.current_prop_id, pl.value_prop_id, pl.key_prop_id,
v.actual_type_id, v.persisted_type,
v.long_value, dv.double_value, sv.string_value, serv.serializable_value
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_audit_app app
join alf_prop_value app_pv on (app_pv.id = app.app_name_id)
@@ -168,20 +165,20 @@
<isNotNull property="searchKey">
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_string_value sp_ksv on (sp_ksv.id = sp_kv.long_value and sp_kv.persisted_type = 3)
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_kpv.long_value and sp_kpv.persisted_type = 3)
</isNotNull>
<isNotNull property="searchValueString">
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_string_value sp_msv on (sp_msv.id = sp_mv.long_value and sp_mv.persisted_type = 3)
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_mpv.long_value and sp_mpv.persisted_type = 3)
</isNotNull>
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)
left join alf_prop_double_value dv on (dv.id = v.long_value and v.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_serializable_value serv on (serv.id = v.long_value and v.persisted_type = 4)
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)
<dynamic prepend="where">
<isNotNull prepend="and" property="auditAppNameShort">
app_sv.string_end_lower = #auditAppNameShort# and
@@ -204,8 +201,6 @@
sp_msv.string_value = #searchValueString#
</isNotNull>
</dynamic>
order by
entry.id
</select>
</sqlMap>

View File

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

View File

@@ -41,4 +41,11 @@
</selectKey>
</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>

View File

@@ -39,6 +39,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
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.MimetypeMap;
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.transaction.TransactionalDao;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
@@ -670,7 +672,40 @@ public class HibernateAuditDAO extends HibernateDaoSupport implements AuditDAO,
* @throws UnsupportedOperationException always
* @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();
}

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.ObjectFactory;
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.service.cmr.repository.NodeRef;
import org.alfresco.service.transaction.TransactionService;
@@ -99,6 +100,10 @@ public class AuditModelRegistry
* Used to lookup a reference to the application
*/
private final Map<String, Long> auditApplicationIdsByApplicationsName;
/**
* Used to lookup application disabled paths
*/
private final Map<String, Set<String>> auditDisabledPathsByApplicationsName;
/**
* Default constructor
@@ -115,6 +120,7 @@ public class AuditModelRegistry
auditModels = new ArrayList<Audit>(7);
auditApplicationsByName = new HashMap<String, AuditApplication>(7);
auditApplicationIdsByApplicationsName = new HashMap<String, Long>(7);
auditDisabledPathsByApplicationsName = new HashMap<String, Set<String>>(7);
}
/**
@@ -246,8 +252,8 @@ public class AuditModelRegistry
clearCaches();
throw new AuditModelException(
"Failed to load audit model: " + auditModelUrl + "\n" +
e.getMessage());
"Failed to load audit model: " + auditModelUrl,
e);
}
}
// 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.
*
@@ -516,11 +541,21 @@ public class AuditModelRegistry
}
// 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);
auditApplicationsByName.put(name, wrapperApp);
auditApplicationIdsByApplicationsName.put(name, appId);
auditApplicationIdsByApplicationsName.put(name, appInfo.getId());
auditDisabledPathsByApplicationsName.put(name, appInfo.getDisabledPaths());
}
// Store the model itself
auditModels.add(audit);

View File

@@ -29,8 +29,10 @@ import java.io.InputStream;
import java.io.Serializable;
import java.net.URL;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.CRC32;
import org.alfresco.error.AlfrescoRuntimeException;
@@ -49,6 +51,7 @@ import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.util.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.DataIntegrityViolationException;
/**
* Abstract helper DAO for <b>alf_audit_XXX</b> tables.
@@ -206,33 +209,105 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO
* alf_audit_application
*/
public Long getOrCreateAuditApplication(Long modelId, String application)
@SuppressWarnings("unchecked")
public AuditApplicationInfo getAuditApplication(String application)
{
// Search for it
AuditApplicationEntity entity = getAuditApplicationByModelIdAndName(modelId, application);
AuditApplicationEntity entity = getAuditApplicationByName(application);
if (entity == null)
{
// Create it
// Persist the string
Long appNameId = propertyValueDAO.getOrCreatePropertyValue(application).getFirst();
// Create the audit session
entity = createAuditApplication(modelId, appNameId);
return null;
}
else
{
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
if (logger.isDebugEnabled())
{
logger.debug(
"Created new audit application: \n" +
" Model: " + modelId + "\n" +
" App: " + application + "\n" +
" Result: " + entity);
"Found existing audit application: \n" +
" " + appInfo);
}
return appInfo;
}
// Done
return entity.getId();
}
protected abstract AuditApplicationEntity getAuditApplicationByModelIdAndName(Long modelId, String appName);
protected abstract AuditApplicationEntity createAuditApplication(Long modelId, Long appNameId);
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
if (logger.isDebugEnabled())
{
logger.debug(
"Created new audit application: \n" +
" Model: " + modelId + "\n" +
" App: " + application + "\n" +
" Result: " + entity);
}
return appInfo;
}
public void updateAuditApplicationModel(Long id, Long modelId)
{
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
@@ -253,7 +328,7 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO
Long valuesId = null;
if (values != null && values.size() > 0)
{
valuesId = propertyValueDAO.getOrCreatePropertyValue((Serializable)values).getFirst();
valuesId = propertyValueDAO.createProperty((Serializable)values);
}
// Create the audit entry

View File

@@ -33,8 +33,10 @@ package org.alfresco.repo.domain.audit;
public class AuditApplicationEntity
{
private Long id;
private Long auditModelId;
private short version;
private Long applicationNameId;
private Long auditModelId;
private Long disabledPathsId;
public AuditApplicationEntity()
{
@@ -46,12 +48,26 @@ public class AuditApplicationEntity
StringBuilder sb = new StringBuilder(512);
sb.append("AuditApplicationEntity")
.append("[ ID=").append(id)
.append(", auditModelId=").append(auditModelId)
.append(", version=").append(version)
.append(", applicationNameId=").append(applicationNameId)
.append(", auditModelId=").append(auditModelId)
.append(", disabledPathsId=").append(disabledPathsId)
.append("]");
return sb.toString();
}
public void incrementVersion()
{
if (version >= Short.MAX_VALUE)
{
this.version = 0;
}
else
{
this.version++;
}
}
public Long getId()
{
return id;
@@ -62,14 +78,14 @@ public class AuditApplicationEntity
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()
@@ -81,4 +97,24 @@ public class AuditApplicationEntity
{
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.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.repo.audit.AuditState;
import org.alfresco.service.cmr.audit.AuditInfo;
@@ -66,6 +67,66 @@ public interface AuditDAO
* 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
*
@@ -76,13 +137,38 @@ public interface AuditDAO
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
* @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.

View File

@@ -33,6 +33,7 @@ import java.util.Map;
import junit.framework.TestCase;
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.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
@@ -111,7 +112,11 @@ public class AuditDAOTest extends TestCase
{
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;
}
@@ -142,8 +147,13 @@ public class AuditDAOTest extends TestCase
{
public Long execute() throws Throwable
{
Long modelId = auditDAO.getOrCreateAuditModel(url).getFirst();
return auditDAO.getOrCreateAuditApplication(modelId, appName);
AuditApplicationInfo appInfo = auditDAO.getAuditApplication(appName);
if (appInfo == null)
{
Long modelId = auditDAO.getOrCreateAuditModel(url).getFirst();
appInfo = auditDAO.createAuditApplication(appName, modelId);
}
return appInfo.getId();
}
};
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 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 UPDATE_APPLICATION = "alfresco.audit.update_AuditApplication";
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_WITH_VALUES = "alfresco.audit.select_AuditEntriesWithValues";
@@ -90,54 +93,75 @@ public class AuditDAOImpl extends AbstractAuditDAOImpl
return entity;
}
@SuppressWarnings("unchecked")
@Override
protected AuditApplicationEntity getAuditApplicationByModelIdAndName(Long modelId, String appName)
protected AuditApplicationEntity getAuditApplicationById(Long id)
{
Map<String, Object> params = new HashMap<String, Object>(11);
params.put("id", modelId);
List<AuditApplicationEntity> results = (List<AuditApplicationEntity>) template.queryForList(
SELECT_APPLICATION_BY_MODEL_ID,
params.put("id", id);
AuditApplicationEntity entity = (AuditApplicationEntity) template.queryForObject(
SELECT_APPLICATION_BY_ID,
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
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
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();
entity.setAuditModelId(modelId);
entity.setVersion((short)0);
entity.setApplicationNameId(appNameId);
entity.setAuditModelId(modelId);
entity.setDisabledPathsId(disabledPathsId);
Long id = (Long) template.insert(INSERT_APPLICATION, entity);
entity.setId(id);
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
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.Map;
import java.util.Set;
import java.util.TreeMap;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.cache.lookup.EntityLookupCache;
import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAOAdaptor;
import org.alfresco.repo.domain.CrcHelper;
import org.alfresco.repo.domain.propval.PropertyValueEntity.PersistedType;
import org.alfresco.service.cmr.repository.MLText;
import org.alfresco.util.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.DataIntegrityViolationException;
/**
* 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_SERIALIZABLE_VALUE = "PropertySerializableValue";
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;
@@ -73,6 +74,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
private final PropertyDoubleValueCallbackDAO propertyDoubleValueCallback;
private final PropertySerializableValueCallbackDAO propertySerializableValueCallback;
private final PropertyValueCallbackDAO propertyValueCallback;
private final PropertyCallbackDAO propertyCallback;
/**
* Cache for the property class:<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/>
*/
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.
@@ -130,6 +139,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
this.propertyDoubleValueCallback = new PropertyDoubleValueCallbackDAO();
this.propertySerializableValueCallback = new PropertySerializableValueCallbackDAO();
this.propertyValueCallback = new PropertyValueCallbackDAO();
this.propertyCallback = new PropertyCallbackDAO();
this.propertyClassCache = new EntityLookupCache<Long, Class<?>, String>(propertyClassDaoCallback);
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.propertySerializableValueCache = new EntityLookupCache<Long, Serializable, Serializable>(propertySerializableValueCallback);
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);
}
/**
* 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
//================================
@@ -238,7 +262,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
Pair<Long, Class<?>> entityPair = propertyClassCache.getByKey(id);
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;
}
@@ -321,7 +345,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
Pair<Long, Date> entityPair = propertyDateValueCache.getByKey(id);
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;
}
@@ -422,7 +446,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
Pair<Long, String> entityPair = propertyStringValueCache.getByKey(id);
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;
}
@@ -507,7 +531,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
Pair<Long, Double> entityPair = propertyDoubleValueCache.getByKey(id);
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;
}
@@ -590,7 +614,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
Pair<Long, Serializable> entityPair = propertySerializableValueCache.getByKey(id);
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;
}
@@ -651,7 +675,7 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
Pair<Long, Serializable> entityPair = propertyValueCache.getByKey(id);
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;
}
@@ -662,64 +686,10 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
return entityPair;
}
/**
* {@inheritDoc}
* @see #getOrCreatePropertyValueImpl(Serializable, int, int)
*/
public Pair<Long, Serializable> getOrCreatePropertyValue(Serializable value)
{
return getOrCreatePropertyValueImpl(value, null, Integer.MAX_VALUE, 0);
}
/**
* {@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;
}
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>
{
@SuppressWarnings("unchecked")
private final Serializable convertToValue(PropertyValueEntity entity)
{
if (entity == null)
{
return null;
}
final Serializable actualValue;
if (entity.getPersistedTypeEnum() == PersistedType.CONSTRUCTABLE)
{
actualValue = entity.getPersistedValue();
}
else
{
Long actualTypeId = entity.getActualTypeId();
Class<?> actualType = getPropertyClassById(actualTypeId).getSecond();
Serializable entityValue = entity.getPersistedValue();
actualValue = (Serializable) converter.convert(actualType, entityValue);
}
Long actualTypeId = entity.getActualTypeId();
final Class<Serializable> actualType = (Class<Serializable>) getPropertyClassById(actualTypeId).getSecond();
final Serializable actualValue = entity.getValue(actualType, converter);
// Done
return actualValue;
}
@@ -764,11 +725,11 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
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
if (persistedType == PersistedType.SERIALIZABLE)
{
// It will be Serialized, so no key
// It will be Serialized, so no search key
return null;
}
else if (value instanceof String)
@@ -785,34 +746,18 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
public Pair<Long, Serializable> createValue(Serializable value)
{
PropertyValueEntity entity = createPropertyValue(value);
Long entityId = entity.getId();
// Create the link entry for the property
createPropertyLink(entityId, entityId, entityId, 0L);
// Done
return new Pair<Long, Serializable>(entity.getId(), value);
}
public Pair<Long, Serializable> findByKey(Long key)
{
List<PropertyIdSearchRow> rows = findPropertyValueById(key);
if (rows.size() == 0)
{
// No results
return null;
}
Serializable value = convertPropertyIdSearchRows(rows);
return new Pair<Long, Serializable>(key, value);
PropertyValueEntity entity = findPropertyValueById(key);
return convertEntityToPair(entity);
}
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);
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 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")
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.
* However, for safety (data patching, etc) we take a first pass to create the
* basic properties before hooking them all together in a second pass.
* The results all share the same root property. Pass through the results and construct all
* instances, storing them ordered by prop_index.
*/
final Map<Long, Serializable> values = new HashMap<Long, Serializable>(rows.size());
List<PropertyLinkEntity> keyRows = new ArrayList<PropertyLinkEntity>(5);
Serializable result = null;
Long rootPropId = null;
Map<Long, Serializable> valuesByPropIndex = new HashMap<Long, Serializable>(7);
TreeMap<Long, PropertyLinkEntity> linkEntitiesByPropIndex = new TreeMap<Long, PropertyLinkEntity>();
Long rootPropId = null; // Keep this to ensure the root_prop_id is common
for (PropertyIdSearchRow row : rows)
{
// Check that we are handling a single root property
@@ -859,216 +1086,74 @@ public abstract class AbstractPropertyValueDAOImpl implements PropertyValueDAO
}
PropertyLinkEntity linkEntity = row.getLinkEntity();
Long propIndex = linkEntity.getPropIndex();
Long valuePropId = linkEntity.getValuePropId();
PropertyValueEntity valueEntity = row.getValueEntity();
// Construct the value (Maps and Collections should be CONSTRUCTABLE)
Serializable value = propertyValueCallback.convertToValue(valueEntity);
// Keep it
values.put(new Long(linkEntity.getValuePropId()), value);
// If this row is a mapping row (the property value ID does not match the current property ID)
// then store it for quicker use later
if (linkEntity.getCurrentPropId() != linkEntity.getValuePropId())
// Get the value
Serializable value;
if (valueEntity != null)
{
keyRows.add(linkEntity);
value = propertyValueCallback.convertToValue(valueEntity);
}
if (linkEntity.getRootPropId() == linkEntity.getValuePropId())
else
{
// We found the root
result = value;
// Go N+1 if the value entity was not retrieved
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
if (result == null)
Serializable 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;
}
// Now we have all our constructed values and the mapping rows: build the collections
for (PropertyLinkEntity propertyLinkEntity : keyRows)
{
Serializable value = values.get(propertyLinkEntity.getValuePropId());
Serializable currentProp = values.get(propertyLinkEntity.getCurrentPropId());
if (value == null)
PropertyLinkEntity linkEntity = entry.getValue();
Long propIndex = linkEntity.getPropIndex();
Long containedIn = linkEntity.getContainedIn();
Long keyPropId = linkEntity.getKeyPropId();
Serializable value = valuesByPropIndex.get(propIndex);
// Check if this is the root property
if (propIndex.equals(containedIn))
{
logger.error("No value found for link property: " + propertyLinkEntity);
continue;
}
if (currentProp == null)
{
logger.error("No current property found for link property: " + propertyLinkEntity);
continue;
}
Long keyId = propertyLinkEntity.getKeyPropId();
// put the value into the container
if (currentProp instanceof Map<?, ?>)
{
Pair<Long, Serializable> keyPair = getPropertyValueById(keyId);
if (keyPair == null)
if (result != null)
{
logger.error("Current property (map) has key without a value: " + propertyLinkEntity);
logger.error("Found inconsistent property root data: " + linkEntity);
continue;
}
Serializable key = keyPair.getSecond();
Map<Serializable, Serializable> map = (Map<Serializable, Serializable>) currentProp;
map.put(key, value);
// This property is contained in itself i.e. it's the root
result = value;
}
else if (currentProp instanceof Set<?>)
else
{
// We can ignore the key - it won't make a difference
Set<Serializable> set = (Set<Serializable>) currentProp;
set.add(value);
}
// Results 'should' be ordered by key
// else if (currentProp instanceof List<?>)
// {
// // The order is important
// List<Serializable> collection = (List<Serializable>) currentProp;
// collection.add(keyId.intValue(), value);
// }
else if (currentProp instanceof Collection<?>)
{
// The order is important
Collection<Serializable> collection = (Collection<Serializable>) currentProp;
collection.add(value);
// Add the value to the container to which it belongs.
// The ordering is irrelevant for some containers; but where it is important,
// ordering given by the prop_index will ensure that values are added back
// in the order in which the container originally iterated over them
Serializable container = valuesByPropIndex.get(containedIn);
if (container == null)
{
logger.error("Found container ID that doesn't have a value: " + linkEntity);
}
else if (container instanceof Map<?, ?>)
{
Map<Serializable, Serializable> map = (Map<Serializable, Serializable>) container;
Serializable mapKey = getPropertyValueById(keyPropId).getSecond();
map.put(mapKey, 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
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;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.domain.propval.PropertyValueEntity.PersistedType;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
@@ -59,12 +63,23 @@ public class DefaultPropertyTypeConverter implements PropertyTypeConverter
{
// 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(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(Period.class, PersistedType.STRING);
mapClass.put(Locale.class, PersistedType.STRING);
mapClass.put(AssociationRef.class, PersistedType.STRING);
mapClass.put(ChildAssociationRef.class, PersistedType.STRING);
// Everything else is just Serializable
defaultPersistedTypesByClass = Collections.unmodifiableMap(mapClass);
}
private Map<Class<?>, PersistedType> persistenceMapping;
@@ -89,6 +104,72 @@ public class DefaultPropertyTypeConverter implements PropertyTypeConverter
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}
*/
@@ -98,20 +179,38 @@ public class DefaultPropertyTypeConverter implements PropertyTypeConverter
Class<?> clazz = value.getClass();
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
{
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
*/
public <T> T convert(Class<T> targetClass, Object value)
public <T> T convert(Class<T> targetClass, Serializable value)
{
return DefaultTypeConverter.INSTANCE.convert(targetClass, value);
}

View File

@@ -64,26 +64,31 @@ public class PropertyIdSearchRow
return valueEntity;
}
public void setRootPropId(long rootPropId)
public void setRootPropId(Long 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);
}
public void setValuePropId(Long valuePropId)
{
linkEntity.setValuePropId(valuePropId);
}
public void setActualTypeId(Long actualTypeId)
{
valueEntity.setActualTypeId(actualTypeId);

View File

@@ -32,93 +32,77 @@ package org.alfresco.repo.domain.propval;
*/
public class PropertyLinkEntity
{
private long rootPropId;
private long currentPropId;
private long valuePropId;
private long keyPropId;
private Long rootPropId;
private Long propIndex;
private Long containedIn;
private Long keyPropId;
private Long valuePropId;
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
public String toString()
{
StringBuilder sb = new StringBuilder(512);
sb.append("PropertyLinkEntity")
.append("[ rootPropId=").append(rootPropId)
.append(", currentPropId=").append(currentPropId)
.append(", valuePropId=").append(valuePropId)
.append(", propIndex=").append(propIndex)
.append(", containedIn=").append(containedIn)
.append(", keyPropId=").append(keyPropId)
.append(", valuePropId=").append(valuePropId)
.append("]");
return sb.toString();
}
public long getRootPropId()
public Long getRootPropId()
{
return rootPropId;
}
public void setRootPropId(long rootPropId)
public void setRootPropId(Long 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;
}
public void setKeyPropId(long keyPropId)
public void setKeyPropId(Long 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#STRING}</li>
* <li>{@link PersistedType#SERIALIZABLE}</li>
* <li>{@link PersistedType#CONSTRUCTABLE}</li>
* </ul>
* 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
* 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
* @return Returns the type of persistence to use
@@ -64,5 +68,13 @@ public interface PropertyTypeConverter
* @param value the value to convert
* @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>)
*/
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>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.
* <b>alf_prop_root</b> accessor: get a property based on the database ID
*
* @param value the value to find the ID for (may be <tt>null</tt>)
* @param maxDepth the maximum depth of collections and maps to iterate into
* @param id the ID (may not be <tt>null</tt>)
* @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

View File

@@ -28,6 +28,7 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
@@ -65,6 +66,7 @@ public class PropertyValueDAOTest extends TestCase
ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
transactionService = serviceRegistry.getTransactionService();
txnHelper = transactionService.getRetryingTransactionHelper();
txnHelper.setMaxRetries(0);
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
{
final HashMap<String, String> map = new HashMap<String, String>(15);
@@ -446,7 +466,53 @@ public class PropertyValueDAOTest extends TestCase
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);
for (int i = 0; i < 20; i++)
@@ -461,10 +527,10 @@ public class PropertyValueDAOTest extends TestCase
String key = "OUTERMAP-KEY-" + i;
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);
for (int i = 0; i < 20; i++)
@@ -479,10 +545,10 @@ public class PropertyValueDAOTest extends TestCase
String key = "OUTERMAP-KEY-" + i;
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);
for (int i = 0; i < 20; i++)
@@ -490,9 +556,77 @@ public class PropertyValueDAOTest extends TestCase
String value = "COLL-VALUE-" + i;
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
{
removeCaches();

View File

@@ -25,9 +25,7 @@
package org.alfresco.repo.domain.propval;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@@ -171,11 +169,6 @@ public class PropertyValueEntity
*/
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
{
// Create a pair for null values
@@ -187,20 +180,6 @@ public class PropertyValueEntity
mapOrdinal.put(persistedType.getOrdinalNumber(), persistedType);
}
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);
@@ -259,40 +238,29 @@ public class PropertyValueEntity
}
/**
* Gets the value based on the persisted type.
* Note that this is the value <b>as persisted</b> and not the original, client-required
* value.
* @return Returns the persisted value
* Helper method to get the value based on the persisted type.
*
* @param actualType the type to convert to
* @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)
{
case NULL:
return null;
case LONG:
return longValue;
return converter.convert(actualType, Long.valueOf(longValue));
case DOUBLE:
return doubleValue;
return converter.convert(actualType, Double.valueOf(doubleValue));
case STRING:
return stringValue;
return converter.convert(actualType, stringValue);
case SERIALIZABLE:
return serializableValue;
return converter.convert(actualType, serializableValue);
case CONSTRUCTABLE:
// Construct an instance
try
{
Class<?> clazz = Class.forName(stringValue);
return (Serializable) clazz.newInstance();
}
catch (ClassNotFoundException e)
{
throw new RuntimeException("Unable to construct instance of class " + stringValue, e);
}
catch (Throwable e)
{
throw new RuntimeException("Unable to create new instance of " + stringValue, e);
}
// Construct an instance using the converter (it knows best!)
return converter.constructInstance(stringValue);
default:
throw new IllegalStateException("Should not be able to get through switch");
}
@@ -313,22 +281,11 @@ public class PropertyValueEntity
this.persistedTypeEnum = PersistedType.NULL;
this.longValue = LONG_ZERO;
}
else if (value instanceof Class<?>)
{
Class<?> clazz = (Class<?>) value;
stringValue = clazz.getName();
persistedTypeEnum = PersistedType.CONSTRUCTABLE;
persistedType = persistedTypeEnum.getOrdinalNumber();
}
else
{
Class<?> valueClazz = value.getClass();
persistedTypeEnum = persistedTypesByClass.get(valueClazz);
if (persistedTypeEnum == null)
{
// Give the converter a chance to change the type it must be persisted as
persistedTypeEnum = converter.getPersistentType(value);
}
// The converter will be responsible for deserializing, so let it choose
// how the data is to be stored.
persistedTypeEnum = converter.getPersistentType(value);
persistedType = persistedTypeEnum.getOrdinalNumber();
// Get the class to persist as
switch (persistedTypeEnum)
@@ -342,6 +299,10 @@ public class PropertyValueEntity
case STRING:
stringValue = converter.convert(String.class, value);
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:
serializableValue = value;
break;
@@ -359,9 +320,12 @@ public class PropertyValueEntity
* Helper method to determine how the given value will be stored.
*
* @param value the value to check
* @param converter the type converter
* @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;
if (value == null)
@@ -370,12 +334,7 @@ public class PropertyValueEntity
}
else
{
Class<?> valueClazz = value.getClass();
persistedTypeEnum = persistedTypesByClass.get(valueClazz);
if (persistedTypeEnum == null)
{
persistedTypeEnum = PersistedType.SERIALIZABLE;
}
persistedTypeEnum = converter.getPersistentType(value);
}
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.PropertyIdSearchRow;
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.PropertyStringQueryEntity;
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 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 DELETE_PROPERTY_LINKS_BY_ROOT_ID = "alfresco.propval.delete_PropertyLinksByRootId";
private SqlMapClientTemplate template;
@@ -287,18 +294,30 @@ public class PropertyValueDAOImpl extends AbstractPropertyValueDAOImpl
@SuppressWarnings("unchecked")
@Override
protected List<PropertyIdSearchRow> findPropertyValueById(Long id)
protected PropertyValueEntity findPropertyValueById(Long id)
{
PropertyValueEntity entity = new PropertyValueEntity();
entity.setId(id);
List<PropertyIdSearchRow> results = (List<PropertyIdSearchRow>) template.queryForList(
List<PropertyValueEntity> results = (List<PropertyValueEntity>) template.queryForList(
SELECT_PROPERTY_VALUE_BY_ID,
entity);
// Done
return results;
// At most one of the results represents a real value
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
protected PropertyValueEntity findPropertyValueByValue(Serializable value)
{
@@ -336,6 +355,8 @@ public class PropertyValueDAOImpl extends AbstractPropertyValueDAOImpl
case DOUBLE:
query = SELECT_PROPERTY_VALUE_BY_DOUBLE_VALUE;
break;
case CONSTRUCTABLE:
// The string value is the name of the class (e.g. 'java.util.HashMap')
case STRING:
// It's best to query using the CRC and short end-value
query = SELECT_PROPERTY_VALUE_BY_STRING_VALUE;
@@ -355,15 +376,8 @@ public class PropertyValueDAOImpl extends AbstractPropertyValueDAOImpl
PropertyValueEntity result = null;
if (query != null)
{
List<PropertyValueEntity> results = (List<PropertyValueEntity>) template.queryForList(
query,
queryObject,
0, 1); // Only want one result
for (PropertyValueEntity row : results)
{
result = row;
break;
}
// Uniqueness is guaranteed by the tables, so we get one value only
result = (PropertyValueEntity) template.queryForObject(query, queryObject);
}
// Done
@@ -416,19 +430,77 @@ public class PropertyValueDAOImpl extends AbstractPropertyValueDAOImpl
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
protected void createPropertyLink(
Long rootPropId,
Long currentPropId,
Long valueId,
Long keyId)
Long propIndex,
Long containedIn,
Long keyPropId,
Long valuePropId)
{
PropertyLinkEntity insertEntity = new PropertyLinkEntity();
insertEntity.setRootPropId(rootPropId);
insertEntity.setCurrentPropId(currentPropId);
insertEntity.setValuePropId(valueId);
insertEntity.setKeyPropId(keyId);
insertEntity.setPropIndex(propIndex);
insertEntity.setContainedIn(containedIn);
insertEntity.setKeyPropId(keyPropId);
insertEntity.setValuePropId(valuePropId);
template.insert(INSERT_PROPERTY_LINK, insertEntity);
// Done
}
@Override
protected int deletePropertyLinks(Long rootPropId)
{
PropertyRootEntity entity = new PropertyRootEntity();
entity.setId(rootPropId);
return template.delete(DELETE_PROPERTY_LINKS_BY_ROOT_ID, entity);
}
}