mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-31 17:39:05 +00:00
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:
@@ -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">
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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;
|
||||
|
||||
--
|
||||
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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();
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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.
|
||||
|
@@ -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);
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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();
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user