mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-31 17:39:05 +00:00
Merged V3.2 to HEAD
17574: Merged in DEV work for ContentStoreCleaner: ETHREEOH-2813 17432: Build up for fix of ETHREEOH-2813: ContentStoreCleaner doesn't scale 17546: ContentStoreCleaner fixes and further tests 17524: Unit tests and bulk queries for orphaned content 17506: W.I.P. for content cleaner for V3.2: ETHREEOH-2813 17575: Missed check-in (other DB create scripts look OK) 17577: Re-activated 'contentStoreCleanerTrigger' - Added system property: system.content.orphanCleanup.cronExpression=0 0 4 * * ? - Other useful properties: system.content.eagerOrphanCleanup=false system.content.orphanProtectDays=14 17578: Fixed MT test and sample contexts after recent content cleaner changes 17579: Fixed DB2 unique index creation for content URLs 17580: First pass at fix for ETHREEOH-3454: Port enterprise upgrade scripts for ContentStoreCleaner changes ___________________________________________________________________ Modified: svn:mergeinfo Merged /alfresco/BRANCHES/V3.2:r17574-17575,17577-17580 git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@18151 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -44,6 +44,9 @@
|
|||||||
<property name="newAvmNodeLinksDAO">
|
<property name="newAvmNodeLinksDAO">
|
||||||
<ref bean="newAvmNodeLinksDAO"/>
|
<ref bean="newAvmNodeLinksDAO"/>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="contentDataDAO">
|
||||||
|
<ref bean="contentDataDAO"/>
|
||||||
|
</property>
|
||||||
<property name="avmStoreDAO">
|
<property name="avmStoreDAO">
|
||||||
<ref bean="avmStoreDAO"/>
|
<ref bean="avmStoreDAO"/>
|
||||||
</property>
|
</property>
|
||||||
|
@@ -103,6 +103,7 @@
|
|||||||
<ref bean="patch.db-V2.2-Person-3" />
|
<ref bean="patch.db-V2.2-Person-3" />
|
||||||
<ref bean="patch.db-V3.2-LockTables" />
|
<ref bean="patch.db-V3.2-LockTables" />
|
||||||
<ref bean="patch.db-V3.2-ContentTables" />
|
<ref bean="patch.db-V3.2-ContentTables" />
|
||||||
|
<ref bean="patch.db-V3.2-ContentTables2" />
|
||||||
<ref bean="patch.db-V3.2-PropertyValueTables" />
|
<ref bean="patch.db-V3.2-PropertyValueTables" />
|
||||||
<ref bean="patch.db-V3.2-AuditTables" />
|
<ref bean="patch.db-V3.2-AuditTables" />
|
||||||
<ref bean="patch.db-V3.2-Child-Assoc-QName-CRC" />
|
<ref bean="patch.db-V3.2-Child-Assoc-QName-CRC" />
|
||||||
|
@@ -39,13 +39,16 @@
|
|||||||
|
|
||||||
<!-- Abstract bean definition defining base definition for content store cleaner -->
|
<!-- Abstract bean definition defining base definition for content store cleaner -->
|
||||||
<!-- Performs the content cleanup -->
|
<!-- Performs the content cleanup -->
|
||||||
<bean id="baseContentStoreCleaner" class="org.alfresco.repo.content.cleanup.ContentStoreCleaner" abstract="true">
|
<bean id="contentStoreCleaner" class="org.alfresco.repo.content.cleanup.ContentStoreCleaner" init-method="init">
|
||||||
|
<property name="protectDays" >
|
||||||
|
<value>${system.content.orphanProtectDays}</value>
|
||||||
|
</property>
|
||||||
|
<property name="eagerContentStoreCleaner" >
|
||||||
|
<ref bean="eagerContentStoreCleaner" />
|
||||||
|
</property>
|
||||||
<property name="jobLockService">
|
<property name="jobLockService">
|
||||||
<ref bean="jobLockService" />
|
<ref bean="jobLockService" />
|
||||||
</property>
|
</property>
|
||||||
<property name="contentCleanDAO">
|
|
||||||
<ref bean="contentCleanDAO"/>
|
|
||||||
</property>
|
|
||||||
<property name="contentDataDAO">
|
<property name="contentDataDAO">
|
||||||
<ref bean="contentDataDAO"/>
|
<ref bean="contentDataDAO"/>
|
||||||
</property>
|
</property>
|
||||||
@@ -64,22 +67,8 @@
|
|||||||
<property name="transactionService" >
|
<property name="transactionService" >
|
||||||
<ref bean="transactionService" />
|
<ref bean="transactionService" />
|
||||||
</property>
|
</property>
|
||||||
<property name="protectDays" >
|
|
||||||
<value>14</value>
|
|
||||||
</property>
|
|
||||||
<property name="listeners" >
|
|
||||||
<ref bean="deletedContentBackupListeners" />
|
|
||||||
</property>
|
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="contentStoreCleaner" parent="baseContentStoreCleaner" init-method="init">
|
|
||||||
<property name="stores" >
|
|
||||||
<list>
|
|
||||||
<ref bean="fileContentStore" />
|
|
||||||
</list>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="eagerContentStoreCleaner" class="org.alfresco.repo.content.cleanup.EagerContentStoreCleaner" init-method="init">
|
<bean id="eagerContentStoreCleaner" class="org.alfresco.repo.content.cleanup.EagerContentStoreCleaner" init-method="init">
|
||||||
<property name="eagerOrphanCleanup" >
|
<property name="eagerOrphanCleanup" >
|
||||||
<value>${system.content.eagerOrphanCleanup}</value>
|
<value>${system.content.eagerOrphanCleanup}</value>
|
||||||
@@ -120,6 +109,9 @@
|
|||||||
<property name="imageMagickContentTransformer">
|
<property name="imageMagickContentTransformer">
|
||||||
<ref bean="transformer.ImageMagick" />
|
<ref bean="transformer.ImageMagick" />
|
||||||
</property>
|
</property>
|
||||||
|
<property name="eagerContentStoreCleaner" >
|
||||||
|
<ref bean="eagerContentStoreCleaner" />
|
||||||
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="contentService" parent="baseContentService">
|
<bean id="contentService" parent="baseContentService">
|
||||||
|
@@ -47,10 +47,6 @@
|
|||||||
<property name="contentStoreCleaner" ref="eagerContentStoreCleaner"/>
|
<property name="contentStoreCleaner" ref="eagerContentStoreCleaner"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="contentCleanDAO" class="org.alfresco.repo.domain.contentclean.ibatis.ContentCleanDAOImpl">
|
|
||||||
<property name="sqlMapClientTemplate" ref="contentSqlMapClientTemplate"/>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="propertyValueDAO" class="org.alfresco.repo.domain.propval.ibatis.PropertyValueDAOImpl">
|
<bean id="propertyValueDAO" class="org.alfresco.repo.domain.propval.ibatis.PropertyValueDAOImpl">
|
||||||
<property name="sqlMapClientTemplate" ref="propertyValueSqlMapClientTemplate"/>
|
<property name="sqlMapClientTemplate" ref="propertyValueSqlMapClientTemplate"/>
|
||||||
<property name="converter">
|
<property name="converter">
|
||||||
|
@@ -31,12 +31,13 @@ DROP TABLE alf_content_url; --(optional)
|
|||||||
CREATE TABLE alf_content_url
|
CREATE TABLE alf_content_url
|
||||||
(
|
(
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
version BIGINT NOT NULL,
|
|
||||||
content_url VARCHAR(255) NOT NULL,
|
content_url VARCHAR(255) NOT NULL,
|
||||||
content_url_short VARCHAR(12) NOT NULL,
|
content_url_short VARCHAR(12) NOT NULL,
|
||||||
content_url_crc BIGINT NOT NULL,
|
content_url_crc BIGINT NOT NULL,
|
||||||
content_size BIGINT NOT NULL,
|
content_size BIGINT NOT NULL,
|
||||||
UNIQUE INDEX idx_alf_cont_url_crc (content_url_short, content_url_crc),
|
orphan_time BIGINT NULL,
|
||||||
|
UNIQUE INDEX idx_alf_conturl_cr (content_url_short, content_url_crc),
|
||||||
|
INDEX idx_alf_conturl_ot (orphan_time),
|
||||||
PRIMARY KEY (id)
|
PRIMARY KEY (id)
|
||||||
) ENGINE=InnoDB;
|
) ENGINE=InnoDB;
|
||||||
|
|
||||||
@@ -55,12 +56,6 @@ CREATE TABLE alf_content_data
|
|||||||
PRIMARY KEY (id)
|
PRIMARY KEY (id)
|
||||||
) ENGINE=InnoDB;
|
) ENGINE=InnoDB;
|
||||||
|
|
||||||
CREATE TABLE alf_content_clean
|
|
||||||
(
|
|
||||||
content_url VARCHAR(255) NOT NULL,
|
|
||||||
INDEX idx_alf_contentclean_url (content_url)
|
|
||||||
) ENGINE=InnoDB;
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Record script finish
|
-- Record script finish
|
||||||
--
|
--
|
||||||
|
@@ -33,14 +33,15 @@ DROP TABLE alf_content_url; --(optional)
|
|||||||
CREATE TABLE alf_content_url
|
CREATE TABLE alf_content_url
|
||||||
(
|
(
|
||||||
id INT8 NOT NULL,
|
id INT8 NOT NULL,
|
||||||
version INT8 NOT NULL,
|
|
||||||
content_url VARCHAR(255) NOT NULL,
|
content_url VARCHAR(255) NOT NULL,
|
||||||
content_url_short VARCHAR(12) NOT NULL,
|
content_url_short VARCHAR(12) NOT NULL,
|
||||||
content_url_crc INT8 NOT NULL,
|
content_url_crc INT8 NOT NULL,
|
||||||
content_size INT8 NOT NULL,
|
content_size INT8 NOT NULL,
|
||||||
|
orphan_time INT8 NULL,
|
||||||
PRIMARY KEY (id)
|
PRIMARY KEY (id)
|
||||||
);
|
);
|
||||||
CREATE INDEX idx_alf_cont_url_crc ON alf_content_url (content_url_short, content_url_crc);
|
CREATE UNIQUE INDEX idx_alf_conturl_cr ON alf_content_url (content_url_short, content_url_crc);
|
||||||
|
CREATE INDEX idx_alf_conturl_ot ON alf_content_url (orphan_time);
|
||||||
CREATE SEQUENCE alf_content_url_seq START WITH 1 INCREMENT BY 1;
|
CREATE SEQUENCE alf_content_url_seq START WITH 1 INCREMENT BY 1;
|
||||||
|
|
||||||
CREATE TABLE alf_content_data
|
CREATE TABLE alf_content_data
|
||||||
@@ -59,12 +60,6 @@ CREATE TABLE alf_content_data
|
|||||||
);
|
);
|
||||||
CREATE SEQUENCE alf_content_data_seq START WITH 1 INCREMENT BY 1;
|
CREATE SEQUENCE alf_content_data_seq START WITH 1 INCREMENT BY 1;
|
||||||
|
|
||||||
CREATE TABLE alf_content_clean
|
|
||||||
(
|
|
||||||
content_url VARCHAR(255) NOT NULL
|
|
||||||
);
|
|
||||||
CREATE INDEX idx_alf_contentclean_url ON alf_content_clean (content_url);
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Record script finish
|
-- Record script finish
|
||||||
--
|
--
|
||||||
|
@@ -0,0 +1,38 @@
|
|||||||
|
--
|
||||||
|
-- Title: Update Content tables (pre 3.2 Enterprise Final)
|
||||||
|
-- Database: MySQL InnoDB
|
||||||
|
-- Since: V3.2 Schema 3009
|
||||||
|
-- Author: Derek Hulley
|
||||||
|
--
|
||||||
|
-- Please contact support@alfresco.com if you need assistance with the upgrade.
|
||||||
|
--
|
||||||
|
-- This update is required for installations that have run any of the early 3.2
|
||||||
|
-- codelines i.e. anything installed or upgraded to pre-3.2 Enterprise Final.
|
||||||
|
|
||||||
|
-- This is to (a) fix the naming convention and (b) to ensure that the index is UNIQUE
|
||||||
|
DROP INDEX idx_alf_cont_url_crc ON alf_content_url; --(optional)
|
||||||
|
DROP INDEX idx_alf_conturl_cr ON alf_content_url; --(optional)
|
||||||
|
CREATE UNIQUE INDEX idx_alf_conturl_cr ON alf_content_url (content_url_short, content_url_crc);
|
||||||
|
|
||||||
|
-- If this statement fails, it will be because the table already contains
|
||||||
|
-- the orphan column and index
|
||||||
|
ALTER TABLE alf_content_url
|
||||||
|
DROP COLUMN version,
|
||||||
|
ADD COLUMN orphan_time BIGINT NULL AFTER content_size,
|
||||||
|
ADD INDEX idx_alf_conturl_ot (orphan_time)
|
||||||
|
; --(optional)
|
||||||
|
|
||||||
|
-- This table will not exist for upgrades from pre 3.2 to 3.2 Enterprise Final
|
||||||
|
DROP TABLE alf_content_clean; --(optional)
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Record script finish
|
||||||
|
--
|
||||||
|
DELETE FROM alf_applied_patch WHERE id = 'patch.db-V3.2-ContentTables2';
|
||||||
|
INSERT INTO alf_applied_patch
|
||||||
|
(id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'patch.db-V3.2-ContentTables2', 'Manually executed script upgrade V3.2: Content Tables 2 (pre 3.2 Enterprise Final)',
|
||||||
|
0, 3008, -1, 3009, null, 'UNKOWN', ${TRUE}, ${TRUE}, 'Script completed'
|
||||||
|
);
|
@@ -0,0 +1,38 @@
|
|||||||
|
--
|
||||||
|
-- Title: Update Content tables (pre 3.2 Enterprise Final)
|
||||||
|
-- Database: PostgreSQLDialect
|
||||||
|
-- Since: V3.2 Schema 3009
|
||||||
|
-- Author: Derek Hulley
|
||||||
|
--
|
||||||
|
-- Please contact support@alfresco.com if you need assistance with the upgrade.
|
||||||
|
--
|
||||||
|
-- This update is required for installations that have run any of the early 3.2
|
||||||
|
-- codelines i.e. anything installed or upgraded to pre-3.2 Enterprise Final.
|
||||||
|
|
||||||
|
-- This is to (a) fix the naming convention and (b) to ensure that the index is UNIQUE
|
||||||
|
DROP INDEX idx_alf_cont_url_crc; --(optional)
|
||||||
|
DROP INDEX idx_alf_conturl_cr; --(optional)
|
||||||
|
CREATE UNIQUE INDEX idx_alf_conturl_cr ON alf_content_url (content_url_short, content_url_crc);
|
||||||
|
|
||||||
|
-- If this statement fails, it will be because the table already contains the orphan column
|
||||||
|
ALTER TABLE alf_content_url
|
||||||
|
DROP COLUMN version,
|
||||||
|
ADD COLUMN orphan_time INT8 NULL
|
||||||
|
; --(optional)
|
||||||
|
CREATE INDEX idx_alf_conturl_ot ON alf_content_url (orphan_time)
|
||||||
|
; --(optional)
|
||||||
|
|
||||||
|
-- This table will not exist for upgrades from pre 3.2 to 3.2 Enterprise Final
|
||||||
|
DROP TABLE alf_content_clean; --(optional)
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Record script finish
|
||||||
|
--
|
||||||
|
DELETE FROM alf_applied_patch WHERE id = 'patch.db-V3.2-ContentTables2';
|
||||||
|
INSERT INTO alf_applied_patch
|
||||||
|
(id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'patch.db-V3.2-ContentTables2', 'Manually executed script upgrade V3.2: Content Tables 2 (pre 3.2 Enterprise Final)',
|
||||||
|
0, 3008, -1, 3009, null, 'UNKOWN', ${TRUE}, ${TRUE}, 'Script completed'
|
||||||
|
);
|
@@ -38,12 +38,18 @@
|
|||||||
|
|
||||||
<!-- override content store cleaner to use tenant routing file content store -->
|
<!-- override content store cleaner to use tenant routing file content store -->
|
||||||
<!-- Performs the content cleanup -->
|
<!-- Performs the content cleanup -->
|
||||||
<bean id="contentStoreCleaner" parent="baseContentStoreCleaner">
|
<bean id="eagerContentStoreCleaner" class="org.alfresco.repo.content.cleanup.EagerContentStoreCleaner" init-method="init">
|
||||||
|
<property name="eagerOrphanCleanup" >
|
||||||
|
<value>${system.content.eagerOrphanCleanup}</value>
|
||||||
|
</property>
|
||||||
<property name="stores" >
|
<property name="stores" >
|
||||||
<list>
|
<list>
|
||||||
<ref bean="tenantFileContentStore" />
|
<ref bean="tenantFileContentStore" />
|
||||||
</list>
|
</list>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="listeners" >
|
||||||
|
<ref bean="deletedContentBackupListeners" />
|
||||||
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<!-- override content service to use tenant routing file content store -->
|
<!-- override content service to use tenant routing file content store -->
|
||||||
|
@@ -34,11 +34,11 @@
|
|||||||
|
|
||||||
<resultMap id="result_ContentUrl" class="ContentUrl">
|
<resultMap id="result_ContentUrl" class="ContentUrl">
|
||||||
<result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
<result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||||
<result property="version" column="version" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
|
||||||
<result property="contentUrl" column="content_url" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
<result property="contentUrl" column="content_url" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
||||||
<result property="contentUrlShort" column="content_url_short" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
<result property="contentUrlShort" column="content_url_short" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
||||||
<result property="contentUrlCrc" column="content_url_crc" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
<result property="contentUrlCrc" column="content_url_crc" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||||
<result property="size" column="content_size" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
<result property="size" column="content_size" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||||
|
<result property="orphanTime" column="orphan_time" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<resultMap id="result_ContentData" class="ContentData">
|
<resultMap id="result_ContentData" class="ContentData">
|
||||||
@@ -88,8 +88,8 @@
|
|||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<sql id="insert_ContentUrl_AutoIncrement">
|
<sql id="insert_ContentUrl_AutoIncrement">
|
||||||
insert into alf_content_url (version, content_url, content_url_short, content_url_crc, content_size)
|
insert into alf_content_url (content_url, content_url_short, content_url_crc, content_size, orphan_time)
|
||||||
values (#version#, #contentUrl#, #contentUrlShort#, #contentUrlCrc#, #size#)
|
values (#contentUrl#, #contentUrlShort#, #contentUrlCrc#, #size#, #orphanTime#)
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<sql id="insert_ContentData_AutoIncrement">
|
<sql id="insert_ContentData_AutoIncrement">
|
||||||
@@ -174,14 +174,50 @@
|
|||||||
content_url_crc = #contentUrlCrc# and
|
content_url_crc = #contentUrlCrc# and
|
||||||
cd.id is null
|
cd.id is null
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- Get content URL entities that were orphaned before a give time -->
|
||||||
|
<select id="select_ContentUrlByOrphanTime" parameterClass="ContentUrl" resultMap="result_ContentUrl">
|
||||||
|
<![CDATA[
|
||||||
|
select
|
||||||
|
cu.*
|
||||||
|
from
|
||||||
|
alf_content_url cu
|
||||||
|
where
|
||||||
|
cu.orphan_time <= #orphanTime#
|
||||||
|
]]>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- Mark a content URL entity as orphaned -->
|
||||||
|
<update id="update_ContentUrlOrphanTime" parameterClass="ContentUrl">
|
||||||
|
update
|
||||||
|
alf_content_url
|
||||||
|
set
|
||||||
|
orphan_time = #orphanTime#
|
||||||
|
where
|
||||||
|
id = #id#
|
||||||
|
</update>
|
||||||
|
|
||||||
<!-- Delete ContentUrl entity -->
|
<!-- Delete ContentUrl entity -->
|
||||||
<delete id="delete_ContentUrl" parameterMap="parameter_IdMap">
|
<delete id="delete_ContentUrls" parameterClass="list">
|
||||||
delete
|
delete
|
||||||
from
|
from
|
||||||
alf_content_url
|
alf_content_url
|
||||||
where
|
where
|
||||||
id = ?
|
id in
|
||||||
|
<iterate open="(" close=")" conjunction=",">
|
||||||
|
#[]#
|
||||||
|
</iterate>
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<!-- Delete ContentUrl entities orphaned before a given time -->
|
||||||
|
<delete id="delete_ContentUrlByOrphanTime" parameterClass="ContentUrl">
|
||||||
|
<![CDATA[
|
||||||
|
delete
|
||||||
|
from
|
||||||
|
alf_content_url
|
||||||
|
where
|
||||||
|
orphan_time <= #orphanTime#
|
||||||
|
]]>
|
||||||
</delete>
|
</delete>
|
||||||
|
|
||||||
<!-- Get the ContentData entity by ID -->
|
<!-- Get the ContentData entity by ID -->
|
||||||
@@ -214,6 +250,22 @@
|
|||||||
np.actual_type_n = 3 and
|
np.actual_type_n = 3 and
|
||||||
np.persisted_type_n = 3
|
np.persisted_type_n = 3
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<update id="update_ContentData" parameterClass="ContentData">
|
||||||
|
update
|
||||||
|
alf_content_data
|
||||||
|
set
|
||||||
|
version = #version#,
|
||||||
|
content_url_id = #contentUrlId#,
|
||||||
|
content_mimetype_id = #mimetypeId#,
|
||||||
|
content_encoding_id = #encodingId#,
|
||||||
|
content_locale_id = #localeId#
|
||||||
|
where
|
||||||
|
id = #id#
|
||||||
|
<isGreaterThan property="version" compareValue="1">
|
||||||
|
and version = (#version#-1)
|
||||||
|
</isGreaterThan>
|
||||||
|
</update>
|
||||||
|
|
||||||
<!-- Delete ContentData entity -->
|
<!-- Delete ContentData entity -->
|
||||||
<delete id="delete_ContentData" parameterMap="parameter_IdMap">
|
<delete id="delete_ContentData" parameterMap="parameter_IdMap">
|
||||||
@@ -224,41 +276,4 @@
|
|||||||
id = ?
|
id = ?
|
||||||
</delete>
|
</delete>
|
||||||
|
|
||||||
<!-- Select all content URLs -->
|
|
||||||
<select id="select_ContentUrls" resultClass="string">
|
|
||||||
select
|
|
||||||
content_url
|
|
||||||
from
|
|
||||||
alf_content_url
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<!-- Insert a Content Clean URL -->
|
|
||||||
<insert id="insert_ContentCleanUrl" parameterClass="ContentClean" >
|
|
||||||
insert into alf_content_clean (content_url) values (#contentUrl#)
|
|
||||||
</insert>
|
|
||||||
|
|
||||||
<!-- Select all content clean URLs -->
|
|
||||||
<select id="select_ContentCleanUrls" resultClass="string">
|
|
||||||
select
|
|
||||||
content_url
|
|
||||||
from
|
|
||||||
alf_content_clean
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<!-- Remove a Content Clean URL -->
|
|
||||||
<delete id="delete_ContentCleanUrl" parameterClass="ContentClean" >
|
|
||||||
delete
|
|
||||||
from
|
|
||||||
alf_content_clean
|
|
||||||
where
|
|
||||||
content_url = #contentUrl#
|
|
||||||
</delete>
|
|
||||||
|
|
||||||
<!-- Remove all Content Clean URLs -->
|
|
||||||
<delete id="delete_ContentCleanUrls" >
|
|
||||||
delete
|
|
||||||
from
|
|
||||||
alf_content_clean
|
|
||||||
</delete>
|
|
||||||
|
|
||||||
</sqlMap>
|
</sqlMap>
|
@@ -34,8 +34,8 @@
|
|||||||
select nextVal('alf_content_url_seq')
|
select nextVal('alf_content_url_seq')
|
||||||
</selectKey>
|
</selectKey>
|
||||||
|
|
||||||
insert into alf_content_url (id, version, content_url, content_url_short, content_url_crc, content_size)
|
insert into alf_content_url (id, content_url, content_url_short, content_url_crc, content_size, orphan_time)
|
||||||
values (#id#, #version#, #contentUrl#, #contentUrlShort#, #contentUrlCrc#, #size#)
|
values (#id#, #contentUrl#, #contentUrlShort#, #contentUrlCrc#, #size#, #orphanTime#)
|
||||||
|
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
|
@@ -1962,10 +1962,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<!-- -->
|
|
||||||
<!-- Patch definitions -->
|
|
||||||
<!-- -->
|
|
||||||
|
|
||||||
<bean id="patch.fixNameCrcValues-2" class="org.alfresco.repo.admin.patch.impl.FixNameCrcValuesPatch" parent="basePatch" >
|
<bean id="patch.fixNameCrcValues-2" class="org.alfresco.repo.admin.patch.impl.FixNameCrcValuesPatch" parent="basePatch" >
|
||||||
<property name="id"><value>patch.fixNameCrcValues-2</value></property>
|
<property name="id"><value>patch.fixNameCrcValues-2</value></property>
|
||||||
<property name="description"><value>patch.fixNameCrcValues.description</value></property>
|
<property name="description"><value>patch.fixNameCrcValues.description</value></property>
|
||||||
@@ -2009,4 +2005,21 @@
|
|||||||
</list>
|
</list>
|
||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
<bean id="patch.db-V3.2-ContentTables2" class="org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch" parent="basePatch">
|
||||||
|
<property name="id"><value>patch.db-V3.2-ContentTables2</value></property>
|
||||||
|
<property name="description"><value>patch.schemaUpgradeScript.description</value></property>
|
||||||
|
<property name="fixesFromSchema"><value>0</value></property>
|
||||||
|
<property name="fixesToSchema"><value>4001</value></property>
|
||||||
|
<property name="targetSchema"><value>4002</value></property>
|
||||||
|
<property name="scriptUrl">
|
||||||
|
<!-- Share a create script -->
|
||||||
|
<value>classpath:alfresco/dbscripts/upgrade/3.2/${db.script.dialect}/AlfrescoSchemaUpdate-3.2-ContentTables2.sql</value>
|
||||||
|
</property>
|
||||||
|
<property name="dependsOn" >
|
||||||
|
<list>
|
||||||
|
<ref bean="patch.db-V3.2-ContentTables" />
|
||||||
|
</list>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
|
@@ -137,6 +137,11 @@ system.enableTimestampPropagation=false
|
|||||||
# Decide if content should be removed from the system immediately after being orphaned.
|
# Decide if content should be removed from the system immediately after being orphaned.
|
||||||
# Do not change this unless you have examined the impact it has on your backup procedures.
|
# Do not change this unless you have examined the impact it has on your backup procedures.
|
||||||
system.content.eagerOrphanCleanup=false
|
system.content.eagerOrphanCleanup=false
|
||||||
|
# The number of days to keep orphaned content in the content stores.
|
||||||
|
# This has no effect on the 'deleted' content stores, which are not automatically emptied.
|
||||||
|
system.content.orphanProtectDays=14
|
||||||
|
# The CRON expression to trigger the deletion of resources associated with orphaned content.
|
||||||
|
system.content.orphanCleanup.cronExpression=0 0 4 * * ?
|
||||||
|
|
||||||
# #################### #
|
# #################### #
|
||||||
# Lucene configuration #
|
# Lucene configuration #
|
||||||
|
@@ -102,7 +102,7 @@
|
|||||||
</bean>
|
</bean>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<bean id="tempFileCleanerTrigger" class="org.alfresco.util.TriggerBean">
|
<bean id="tempFileCleanerTrigger" class="org.alfresco.util.CronTriggerBean">
|
||||||
<property name="jobDetail">
|
<property name="jobDetail">
|
||||||
<bean id="tempFileCleanerJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
|
<bean id="tempFileCleanerJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
|
||||||
<property name="jobClass">
|
<property name="jobClass">
|
||||||
@@ -120,17 +120,13 @@
|
|||||||
<property name="scheduler">
|
<property name="scheduler">
|
||||||
<ref bean="schedulerFactory" />
|
<ref bean="schedulerFactory" />
|
||||||
</property>
|
</property>
|
||||||
<!-- start after half an hour and repeat hourly -->
|
<!-- Repeat hourly on the half hour -->
|
||||||
<property name="startDelayMinutes">
|
<property name="cronExpression">
|
||||||
<value>30</value>
|
<value>0 30 * * * ?</value>
|
||||||
</property>
|
|
||||||
<property name="repeatIntervalMinutes">
|
|
||||||
<value>60</value>
|
|
||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<!--
|
<bean id="contentStoreCleanerJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
|
||||||
<bean id="fileContentStoreCleanerJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
|
|
||||||
<property name="jobClass">
|
<property name="jobClass">
|
||||||
<value>org.alfresco.repo.content.cleanup.ContentStoreCleanupJob</value>
|
<value>org.alfresco.repo.content.cleanup.ContentStoreCleanupJob</value>
|
||||||
</property>
|
</property>
|
||||||
@@ -144,16 +140,15 @@
|
|||||||
</bean>
|
</bean>
|
||||||
<bean id="contentStoreCleanerTrigger" class="org.alfresco.util.CronTriggerBean">
|
<bean id="contentStoreCleanerTrigger" class="org.alfresco.util.CronTriggerBean">
|
||||||
<property name="jobDetail">
|
<property name="jobDetail">
|
||||||
<ref bean="fileContentStoreCleanerJobDetail" />
|
<ref bean="contentStoreCleanerJobDetail" />
|
||||||
</property>
|
</property>
|
||||||
<property name="scheduler">
|
<property name="scheduler">
|
||||||
<ref bean="schedulerFactory" />
|
<ref bean="schedulerFactory" />
|
||||||
</property>
|
</property>
|
||||||
<property name="cronExpression">
|
<property name="cronExpression">
|
||||||
<value>0 0 4 * * ?</value>
|
<value>${system.content.orphanCleanup.cronExpression}</value>
|
||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
-->
|
|
||||||
|
|
||||||
<bean id="nodeServiceCleanupJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
|
<bean id="nodeServiceCleanupJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
|
||||||
<property name="jobClass">
|
<property name="jobClass">
|
||||||
|
@@ -19,4 +19,4 @@ version.build=@build-number@
|
|||||||
|
|
||||||
# Schema number
|
# Schema number
|
||||||
|
|
||||||
version.schema=4001
|
version.schema=4002
|
||||||
|
@@ -27,6 +27,7 @@ import org.alfresco.repo.attributes.AttributeDAO;
|
|||||||
import org.alfresco.repo.attributes.GlobalAttributeEntryDAO;
|
import org.alfresco.repo.attributes.GlobalAttributeEntryDAO;
|
||||||
import org.alfresco.repo.attributes.ListEntryDAO;
|
import org.alfresco.repo.attributes.ListEntryDAO;
|
||||||
import org.alfresco.repo.attributes.MapEntryDAO;
|
import org.alfresco.repo.attributes.MapEntryDAO;
|
||||||
|
import org.alfresco.repo.domain.contentdata.ContentDataDAO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the (shudder) global context for AVM. It a rendezvous
|
* This is the (shudder) global context for AVM. It a rendezvous
|
||||||
@@ -60,6 +61,7 @@ public class AVMDAOs
|
|||||||
|
|
||||||
public org.alfresco.repo.domain.avm.AVMNodeDAO newAVMNodeDAO;
|
public org.alfresco.repo.domain.avm.AVMNodeDAO newAVMNodeDAO;
|
||||||
public org.alfresco.repo.domain.avm.AVMNodeLinksDAO newAVMNodeLinksDAO;
|
public org.alfresco.repo.domain.avm.AVMNodeLinksDAO newAVMNodeLinksDAO;
|
||||||
|
public ContentDataDAO contentDataDAO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The AVMStore DAO.
|
* The AVMStore DAO.
|
||||||
@@ -123,6 +125,11 @@ public class AVMDAOs
|
|||||||
this.newAVMNodeLinksDAO = newAVMNodeLinksDAO;
|
this.newAVMNodeLinksDAO = newAVMNodeLinksDAO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setContentDataDAO(ContentDataDAO contentDataDAO)
|
||||||
|
{
|
||||||
|
this.contentDataDAO = contentDataDAO;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param childEntryDAO the fChildEntryDAO to set
|
* @param childEntryDAO the fChildEntryDAO to set
|
||||||
*/
|
*/
|
||||||
|
@@ -1900,7 +1900,9 @@ public class AVMStoreImpl implements AVMStore
|
|||||||
throw new AccessDeniedException("Not allowed to write properties: " + path);
|
throw new AccessDeniedException("Not allowed to write properties: " + path);
|
||||||
}
|
}
|
||||||
PlainFileNode file = (PlainFileNode)node;
|
PlainFileNode file = (PlainFileNode)node;
|
||||||
file.setEncoding(encoding);
|
ContentData contentData = file.getContentData();
|
||||||
|
contentData = ContentData.setEncoding(contentData, encoding);
|
||||||
|
file.setContentData(contentData);
|
||||||
|
|
||||||
AVMDAOs.Instance().fAVMNodeDAO.update(file);
|
AVMDAOs.Instance().fAVMNodeDAO.update(file);
|
||||||
}
|
}
|
||||||
@@ -1925,7 +1927,9 @@ public class AVMStoreImpl implements AVMStore
|
|||||||
throw new AccessDeniedException("Not allowed to write properties: " + path);
|
throw new AccessDeniedException("Not allowed to write properties: " + path);
|
||||||
}
|
}
|
||||||
PlainFileNode file = (PlainFileNode)node;
|
PlainFileNode file = (PlainFileNode)node;
|
||||||
file.setMimeType(mimeType);
|
ContentData contentData = file.getContentData();
|
||||||
|
contentData = ContentData.setMimetype(contentData, mimeType);
|
||||||
|
file.setContentData(contentData);
|
||||||
|
|
||||||
AVMDAOs.Instance().fAVMNodeDAO.update(file);
|
AVMDAOs.Instance().fAVMNodeDAO.update(file);
|
||||||
}
|
}
|
||||||
|
@@ -329,16 +329,20 @@ public class OrphanReaper
|
|||||||
// First get rid of all child entries for the node.
|
// First get rid of all child entries for the node.
|
||||||
AVMDAOs.Instance().fChildEntryDAO.deleteByParent(node);
|
AVMDAOs.Instance().fChildEntryDAO.deleteByParent(node);
|
||||||
}
|
}
|
||||||
// This is not on, since content urls can be shared.
|
else if (node.getType() == AVMNodeType.PLAIN_FILE)
|
||||||
// else if (node.getType() == AVMNodeType.PLAIN_FILE)
|
{
|
||||||
// {
|
PlainFileNode file = (PlainFileNode)node;
|
||||||
// PlainFileNode file = (PlainFileNode)node;
|
if (!file.isLegacyContentData())
|
||||||
// String url = file.getContentData(null).getContentUrl();
|
{
|
||||||
// if (url != null)
|
Long contentDataId = file.getContentDataId();
|
||||||
// {
|
if (contentDataId != null)
|
||||||
// RawServices.Instance().getContentStore().delete(url);
|
{
|
||||||
// }
|
// The ContentDataDAO will take care of dereferencing and cleanup
|
||||||
// }
|
AVMDAOs.Instance().contentDataDAO.deleteContentData(contentDataId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Finally, delete it
|
||||||
AVMDAOs.Instance().fAVMNodeDAO.delete(node);
|
AVMDAOs.Instance().fAVMNodeDAO.delete(node);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@@ -8,21 +8,26 @@ import org.alfresco.service.cmr.repository.ContentData;
|
|||||||
*/
|
*/
|
||||||
public interface PlainFileNode extends FileNode
|
public interface PlainFileNode extends FileNode
|
||||||
{
|
{
|
||||||
|
public ContentData getContentData();
|
||||||
|
public void setContentData(ContentData contentData);
|
||||||
|
|
||||||
|
public boolean isLegacyContentData();
|
||||||
|
public Long getContentDataId();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the encoding of this file.
|
* DAO accessor only. <b>DO NOT USE</b> in code.
|
||||||
* @param encoding
|
|
||||||
*/
|
*/
|
||||||
public void setEncoding(String encoding);
|
public String getContentURL();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the mime type of this file.
|
* DAO accessor only. <b>DO NOT USE</b> in code.
|
||||||
* @param mimeType
|
|
||||||
*/
|
*/
|
||||||
public void setMimeType(String mimeType);
|
public String getMimeType();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Special case.
|
* DAO accessor only. <b>DO NOT USE</b> in code.
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
public ContentData getContentData();
|
public String getEncoding();
|
||||||
|
/**
|
||||||
|
* DAO accessor only. <b>DO NOT USE</b> in code.
|
||||||
|
*/
|
||||||
|
public long getLength();
|
||||||
}
|
}
|
||||||
|
@@ -20,24 +20,21 @@
|
|||||||
* FLOSS exception. You should have recieved a copy of the text describing
|
* FLOSS exception. You should have recieved a copy of the text describing
|
||||||
* the FLOSS exception, and it is also available here:
|
* the FLOSS exception, and it is also available here:
|
||||||
* http://www.alfresco.com/legal/licensing" */
|
* http://www.alfresco.com/legal/licensing" */
|
||||||
|
|
||||||
package org.alfresco.repo.avm;
|
package org.alfresco.repo.avm;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.alfresco.repo.avm.util.RawServices;
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
import org.alfresco.repo.domain.DbAccessControlList;
|
import org.alfresco.repo.domain.DbAccessControlList;
|
||||||
import org.alfresco.repo.domain.PropertyValue;
|
import org.alfresco.repo.domain.PropertyValue;
|
||||||
|
import org.alfresco.repo.domain.contentdata.ContentDataDAO;
|
||||||
import org.alfresco.repo.security.permissions.ACLCopyMode;
|
import org.alfresco.repo.security.permissions.ACLCopyMode;
|
||||||
import org.alfresco.service.cmr.avm.AVMException;
|
|
||||||
import org.alfresco.service.cmr.avm.AVMNodeDescriptor;
|
import org.alfresco.service.cmr.avm.AVMNodeDescriptor;
|
||||||
import org.alfresco.service.cmr.repository.ContentData;
|
import org.alfresco.service.cmr.repository.ContentData;
|
||||||
import org.alfresco.service.cmr.repository.ContentReader;
|
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A plain old file. Contains a Content object.
|
* A plain old file. Contains a Content object.
|
||||||
* @author britt
|
* @author britt
|
||||||
@@ -45,26 +42,29 @@ import org.alfresco.service.namespace.QName;
|
|||||||
public class PlainFileNodeImpl extends FileNodeImpl implements PlainFileNode
|
public class PlainFileNodeImpl extends FileNodeImpl implements PlainFileNode
|
||||||
{
|
{
|
||||||
static final long serialVersionUID = 8720376837929735294L;
|
static final long serialVersionUID = 8720376837929735294L;
|
||||||
|
|
||||||
|
private static final String PREFIX_CONTENT_DATA_ID = "id:";
|
||||||
|
private static final String SUFFIX_CONTENT_DATA_NULL = "null";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Content URL.
|
* The content URL <b>OR</b> the ID of the ContentData entity
|
||||||
*/
|
*/
|
||||||
private String fContentURL;
|
private String contentURL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Mime type.
|
* The Mime type.
|
||||||
*/
|
*/
|
||||||
private String fMimeType;
|
private String mimeType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The character encoding.
|
* The character encoding.
|
||||||
*/
|
*/
|
||||||
private String fEncoding;
|
private String encoding;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The length of the file.
|
* The length of the file.
|
||||||
*/
|
*/
|
||||||
private long fLength;
|
private long length;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default constructor.
|
* Default constructor.
|
||||||
@@ -171,7 +171,6 @@ public class PlainFileNodeImpl extends FileNodeImpl implements PlainFileNode
|
|||||||
* @param lPath The Lookup.
|
* @param lPath The Lookup.
|
||||||
* @return A diagnostic String representation.
|
* @return A diagnostic String representation.
|
||||||
*/
|
*/
|
||||||
// @Override
|
|
||||||
public String toString(Lookup lPath)
|
public String toString(Lookup lPath)
|
||||||
{
|
{
|
||||||
return "[PF:" + getId() + "]";
|
return "[PF:" + getId() + "]";
|
||||||
@@ -194,6 +193,7 @@ public class PlainFileNodeImpl extends FileNodeImpl implements PlainFileNode
|
|||||||
{
|
{
|
||||||
path = path + "/" + name;
|
path = path + "/" + name;
|
||||||
}
|
}
|
||||||
|
ContentData contentData = getContentData();
|
||||||
return new AVMNodeDescriptor(path,
|
return new AVMNodeDescriptor(path,
|
||||||
name,
|
name,
|
||||||
AVMNodeType.PLAIN_FILE,
|
AVMNodeType.PLAIN_FILE,
|
||||||
@@ -211,7 +211,7 @@ public class PlainFileNodeImpl extends FileNodeImpl implements PlainFileNode
|
|||||||
false,
|
false,
|
||||||
-1,
|
-1,
|
||||||
false,
|
false,
|
||||||
getLength(),
|
contentData == null ? 0L : contentData.getSize(),
|
||||||
-1);
|
-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,6 +224,7 @@ public class PlainFileNodeImpl extends FileNodeImpl implements PlainFileNode
|
|||||||
{
|
{
|
||||||
BasicAttributes attrs = getBasicAttributes();
|
BasicAttributes attrs = getBasicAttributes();
|
||||||
String path = lPath.getRepresentedPath();
|
String path = lPath.getRepresentedPath();
|
||||||
|
ContentData contentData = getContentData();
|
||||||
return new AVMNodeDescriptor(path,
|
return new AVMNodeDescriptor(path,
|
||||||
path.substring(path.lastIndexOf("/") + 1),
|
path.substring(path.lastIndexOf("/") + 1),
|
||||||
AVMNodeType.PLAIN_FILE,
|
AVMNodeType.PLAIN_FILE,
|
||||||
@@ -241,7 +242,7 @@ public class PlainFileNodeImpl extends FileNodeImpl implements PlainFileNode
|
|||||||
false,
|
false,
|
||||||
-1,
|
-1,
|
||||||
false,
|
false,
|
||||||
getFileLength(),
|
contentData == null ? 0L : contentData.getSize(),
|
||||||
-1);
|
-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,6 +257,7 @@ public class PlainFileNodeImpl extends FileNodeImpl implements PlainFileNode
|
|||||||
{
|
{
|
||||||
BasicAttributes attrs = getBasicAttributes();
|
BasicAttributes attrs = getBasicAttributes();
|
||||||
String path = parentPath.endsWith("/") ? parentPath + name : parentPath + "/" + name;
|
String path = parentPath.endsWith("/") ? parentPath + name : parentPath + "/" + name;
|
||||||
|
ContentData contentData = getContentData();
|
||||||
return new AVMNodeDescriptor(path,
|
return new AVMNodeDescriptor(path,
|
||||||
name,
|
name,
|
||||||
AVMNodeType.PLAIN_FILE,
|
AVMNodeType.PLAIN_FILE,
|
||||||
@@ -273,110 +275,125 @@ public class PlainFileNodeImpl extends FileNodeImpl implements PlainFileNode
|
|||||||
false,
|
false,
|
||||||
-1,
|
-1,
|
||||||
false,
|
false,
|
||||||
getFileLength(),
|
contentData == null ? 0L : contentData.getSize(),
|
||||||
-1);
|
-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the Content URL.
|
* DAO accessor only. <b>DO NOT USE</b> in code.
|
||||||
* @return The content URL.
|
|
||||||
*/
|
*/
|
||||||
public String getContentURL()
|
public String getContentURL()
|
||||||
{
|
{
|
||||||
return fContentURL;
|
return contentURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the Content URL.
|
* DAO accessor only. <b>DO NOT USE</b> in code.
|
||||||
* @param contentURL
|
|
||||||
*/
|
*/
|
||||||
protected void setContentURL(String contentURL)
|
public void setContentURL(String contentURL)
|
||||||
{
|
{
|
||||||
fContentURL = contentURL;
|
this.contentURL = contentURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the character encoding.
|
* DAO accessor only. <b>DO NOT USE</b> in code.
|
||||||
* @return The encoding.
|
|
||||||
*/
|
|
||||||
public String getEncoding()
|
|
||||||
{
|
|
||||||
return fEncoding;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the character encoding.
|
|
||||||
* @param encoding The encoding to set.
|
|
||||||
*/
|
|
||||||
public void setEncoding(String encoding)
|
|
||||||
{
|
|
||||||
fEncoding = encoding;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the file length.
|
|
||||||
* @return The file length or null if unknown.
|
|
||||||
*/
|
|
||||||
public long getLength()
|
|
||||||
{
|
|
||||||
return fLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the actual file length.
|
|
||||||
* @return The actual file length;
|
|
||||||
*/
|
|
||||||
private long getFileLength()
|
|
||||||
{
|
|
||||||
if (getContentURL() == null)
|
|
||||||
{
|
|
||||||
return 0L;
|
|
||||||
}
|
|
||||||
ContentReader reader = RawServices.Instance().getContentStore().getReader(getContentURL());
|
|
||||||
return reader.getSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the file length.
|
|
||||||
* @param length The length of the file.
|
|
||||||
*/
|
|
||||||
protected void setLength(long length)
|
|
||||||
{
|
|
||||||
fLength = length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the mime type of the content.
|
|
||||||
* @return The Mime Type of the content.
|
|
||||||
*/
|
*/
|
||||||
public String getMimeType()
|
public String getMimeType()
|
||||||
{
|
{
|
||||||
return fMimeType;
|
return mimeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the Mime Type of the content.
|
* DAO accessor only. <b>DO NOT USE</b> in code.
|
||||||
* @param mimeType The Mime Type to set.
|
|
||||||
*/
|
*/
|
||||||
public void setMimeType(String mimeType)
|
public void setMimeType(String mimeType)
|
||||||
{
|
{
|
||||||
fMimeType = mimeType;
|
this.mimeType = mimeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DAO accessor only. <b>DO NOT USE</b> in code.
|
||||||
|
*/
|
||||||
|
public String getEncoding()
|
||||||
|
{
|
||||||
|
return encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DAO accessor only. <b>DO NOT USE</b> in code.
|
||||||
|
*/
|
||||||
|
public void setEncoding(String encoding)
|
||||||
|
{
|
||||||
|
this.encoding = encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DAO accessor only. <b>DO NOT USE</b> in code.
|
||||||
|
*/
|
||||||
|
public long getLength()
|
||||||
|
{
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DAO accessor only. <b>DO NOT USE</b> in code.
|
||||||
|
*/
|
||||||
|
public void setLength(long length)
|
||||||
|
{
|
||||||
|
this.length = length;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the ContentData for this file.
|
* Set the ContentData for this file.
|
||||||
* @param contentData The value to set.
|
* @param contentData The value to set.
|
||||||
*/
|
*/
|
||||||
public void setContentData(ContentData contentData)
|
public void setContentData(ContentData contentData)
|
||||||
{
|
{
|
||||||
setContentURL(contentData.getContentUrl());
|
// Remove any legacy-stored attributes to avoid confusion
|
||||||
setMimeType(contentData.getMimetype());
|
if (isLegacyContentData())
|
||||||
if (getMimeType() == null)
|
|
||||||
{
|
{
|
||||||
throw new AVMException("Null mime type.");
|
// Wipe over the old values
|
||||||
|
contentURL = PREFIX_CONTENT_DATA_ID + SUFFIX_CONTENT_DATA_NULL;
|
||||||
|
encoding = null;
|
||||||
|
length = 0L;
|
||||||
|
mimeType = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Long oldContentDataId = getContentDataId();
|
||||||
|
Long newContentDataId = null;
|
||||||
|
if (oldContentDataId == null)
|
||||||
|
{
|
||||||
|
if (contentData != null)
|
||||||
|
{
|
||||||
|
// There was no reference before, so just create a new one
|
||||||
|
newContentDataId = AVMDAOs.Instance().contentDataDAO.createContentData(contentData).getFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (contentData != null)
|
||||||
|
{
|
||||||
|
// Update it. The ID will remain the same.
|
||||||
|
AVMDAOs.Instance().contentDataDAO.updateContentData(oldContentDataId, contentData);
|
||||||
|
newContentDataId = oldContentDataId;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Delete the old instance
|
||||||
|
AVMDAOs.Instance().contentDataDAO.deleteContentData(oldContentDataId);
|
||||||
|
newContentDataId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the pointer to the ContentData instance
|
||||||
|
if (newContentDataId == null)
|
||||||
|
{
|
||||||
|
contentURL = PREFIX_CONTENT_DATA_ID + SUFFIX_CONTENT_DATA_NULL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
contentURL = PREFIX_CONTENT_DATA_ID + newContentDataId;
|
||||||
}
|
}
|
||||||
setEncoding(contentData.getEncoding());
|
|
||||||
setLength(contentData.getSize());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -389,12 +406,66 @@ public class PlainFileNodeImpl extends FileNodeImpl implements PlainFileNode
|
|||||||
return getContentData();
|
return getContentData();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/**
|
||||||
* @see org.alfresco.repo.avm.PlainFileNode#getContentData()
|
* {@inheritDoc}
|
||||||
|
* <p/>
|
||||||
|
* If the content URL contains the special prefix, <b>{@link PREFIX_CONTENT_DATA_ID}</b>,
|
||||||
|
* then the data is pulled directly from the {@link ContentDataDAO}.
|
||||||
*/
|
*/
|
||||||
public ContentData getContentData()
|
public ContentData getContentData()
|
||||||
{
|
{
|
||||||
return new ContentData(getContentURL(), getMimeType(), getLength(), getEncoding());
|
if (contentURL != null && contentURL.startsWith(PREFIX_CONTENT_DATA_ID))
|
||||||
|
{
|
||||||
|
Long contentDataId = getContentDataId();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return AVMDAOs.Instance().contentDataDAO.getContentData(contentDataId).getSecond();
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
throw new AlfrescoRuntimeException(
|
||||||
|
"AVM File node " + getId() + " has invalid ContentData id reference " + contentDataId,
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This deals with legacy data
|
||||||
|
return new ContentData(contentURL, mimeType, length, encoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the content URL and if it contains the {@link #PREFIX_CONTENT_DATA_ID prefix}
|
||||||
|
* indicating the an new ContentData storage ID, returns <tt>true</tt>.
|
||||||
|
*/
|
||||||
|
public boolean isLegacyContentData()
|
||||||
|
{
|
||||||
|
return (contentURL == null || !contentURL.startsWith(PREFIX_CONTENT_DATA_ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ID of the ContentData as given by the string in the ContentURL of
|
||||||
|
* form <b>ID:12345</b>
|
||||||
|
*/
|
||||||
|
public Long getContentDataId()
|
||||||
|
{
|
||||||
|
String idStr = contentURL.substring(3);
|
||||||
|
if (idStr.equals(SUFFIX_CONTENT_DATA_NULL))
|
||||||
|
{
|
||||||
|
// Nothing has been stored against this file
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Long.parseLong(idStr);
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
throw new AlfrescoRuntimeException(
|
||||||
|
"AVM File node " + getId() + " has malformed ContentData id reference " + idStr,
|
||||||
|
e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,7 +20,6 @@
|
|||||||
* FLOSS exception. You should have recieved a copy of the text describing
|
* FLOSS exception. You should have recieved a copy of the text describing
|
||||||
* the FLOSS exception, and it is also available here:
|
* the FLOSS exception, and it is also available here:
|
||||||
* http://www.alfresco.com/legal/licensing" */
|
* http://www.alfresco.com/legal/licensing" */
|
||||||
|
|
||||||
package org.alfresco.repo.avm.ibatis;
|
package org.alfresco.repo.avm.ibatis;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -56,7 +55,6 @@ import org.alfresco.repo.domain.PropertyValue;
|
|||||||
import org.alfresco.repo.domain.avm.AVMNodeEntity;
|
import org.alfresco.repo.domain.avm.AVMNodeEntity;
|
||||||
import org.alfresco.repo.domain.avm.AVMVersionRootEntity;
|
import org.alfresco.repo.domain.avm.AVMVersionRootEntity;
|
||||||
import org.alfresco.repo.domain.hibernate.DbAccessControlListImpl;
|
import org.alfresco.repo.domain.hibernate.DbAccessControlListImpl;
|
||||||
import org.alfresco.service.cmr.repository.ContentData;
|
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
|
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
|
||||||
|
|
||||||
@@ -68,7 +66,6 @@ import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
|
|||||||
*/
|
*/
|
||||||
class AVMNodeDAOIbatis extends HibernateDaoSupport implements AVMNodeDAO
|
class AVMNodeDAOIbatis extends HibernateDaoSupport implements AVMNodeDAO
|
||||||
{
|
{
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see org.alfresco.repo.avm.AVMNodeDAO#save(org.alfresco.repo.avm.AVMNode)
|
* @see org.alfresco.repo.avm.AVMNodeDAO#save(org.alfresco.repo.avm.AVMNode)
|
||||||
*/
|
*/
|
||||||
@@ -380,10 +377,10 @@ class AVMNodeDAOIbatis extends HibernateDaoSupport implements AVMNodeDAO
|
|||||||
if (node instanceof PlainFileNode)
|
if (node instanceof PlainFileNode)
|
||||||
{
|
{
|
||||||
PlainFileNode pfNode = (PlainFileNode)node;
|
PlainFileNode pfNode = (PlainFileNode)node;
|
||||||
nodeEntity.setEncoding(pfNode.getContentData().getEncoding());
|
nodeEntity.setEncoding(pfNode.getEncoding());
|
||||||
nodeEntity.setMimetype(pfNode.getContentData().getMimetype());
|
nodeEntity.setLength(pfNode.getLength());
|
||||||
nodeEntity.setContentUrl(pfNode.getContentData().getContentUrl());
|
nodeEntity.setMimetype(pfNode.getMimeType());
|
||||||
nodeEntity.setLength(pfNode.getContentData().getSize());
|
nodeEntity.setContentUrl(pfNode.getContentURL());
|
||||||
}
|
}
|
||||||
else if (node instanceof LayeredFileNode)
|
else if (node instanceof LayeredFileNode)
|
||||||
{
|
{
|
||||||
@@ -429,9 +426,11 @@ class AVMNodeDAOIbatis extends HibernateDaoSupport implements AVMNodeDAO
|
|||||||
if (nodeEntity.getType() == AVMNodeType.PLAIN_FILE)
|
if (nodeEntity.getType() == AVMNodeType.PLAIN_FILE)
|
||||||
{
|
{
|
||||||
node = new PlainFileNodeImpl();
|
node = new PlainFileNodeImpl();
|
||||||
|
PlainFileNodeImpl pfNode = (PlainFileNodeImpl) node;
|
||||||
ContentData cd = new ContentData(nodeEntity.getContentUrl(), nodeEntity.getMimetype(), nodeEntity.getLength(), nodeEntity.getEncoding());
|
pfNode.setMimeType(nodeEntity.getMimetype());
|
||||||
((PlainFileNodeImpl)node).setContentData(cd);
|
pfNode.setEncoding(nodeEntity.getEncoding());
|
||||||
|
pfNode.setLength(nodeEntity.getLength());
|
||||||
|
pfNode.setContentURL(nodeEntity.getContentUrl());
|
||||||
}
|
}
|
||||||
else if (nodeEntity.getType() == AVMNodeType.PLAIN_DIRECTORY)
|
else if (nodeEntity.getType() == AVMNodeType.PLAIN_DIRECTORY)
|
||||||
{
|
{
|
||||||
|
@@ -37,6 +37,7 @@ import org.alfresco.repo.avm.AVMNodeConverter;
|
|||||||
import org.alfresco.repo.content.ContentServicePolicies.OnContentPropertyUpdatePolicy;
|
import org.alfresco.repo.content.ContentServicePolicies.OnContentPropertyUpdatePolicy;
|
||||||
import org.alfresco.repo.content.ContentServicePolicies.OnContentReadPolicy;
|
import org.alfresco.repo.content.ContentServicePolicies.OnContentReadPolicy;
|
||||||
import org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy;
|
import org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy;
|
||||||
|
import org.alfresco.repo.content.cleanup.EagerContentStoreCleaner;
|
||||||
import org.alfresco.repo.content.filestore.FileContentStore;
|
import org.alfresco.repo.content.filestore.FileContentStore;
|
||||||
import org.alfresco.repo.content.transform.ContentTransformer;
|
import org.alfresco.repo.content.transform.ContentTransformer;
|
||||||
import org.alfresco.repo.content.transform.ContentTransformerRegistry;
|
import org.alfresco.repo.content.transform.ContentTransformerRegistry;
|
||||||
@@ -63,7 +64,6 @@ import org.alfresco.service.cmr.repository.TransformationOptions;
|
|||||||
import org.alfresco.service.cmr.usage.ContentQuotaException;
|
import org.alfresco.service.cmr.usage.ContentQuotaException;
|
||||||
import org.alfresco.service.namespace.NamespaceService;
|
import org.alfresco.service.namespace.NamespaceService;
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
import org.alfresco.service.transaction.TransactionService;
|
|
||||||
import org.alfresco.util.EqualsHelper;
|
import org.alfresco.util.EqualsHelper;
|
||||||
import org.springframework.extensions.surf.util.Pair;
|
import org.springframework.extensions.surf.util.Pair;
|
||||||
import org.alfresco.util.TempFileProvider;
|
import org.alfresco.util.TempFileProvider;
|
||||||
@@ -71,7 +71,6 @@ import org.apache.commons.logging.Log;
|
|||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service implementation acting as a level of indirection between the client
|
* Service implementation acting as a level of indirection between the client
|
||||||
* and the underlying content store.
|
* and the underlying content store.
|
||||||
@@ -95,6 +94,8 @@ public class ContentServiceImpl implements ContentService
|
|||||||
|
|
||||||
/** a registry of all available content transformers */
|
/** a registry of all available content transformers */
|
||||||
private ContentTransformerRegistry transformerRegistry;
|
private ContentTransformerRegistry transformerRegistry;
|
||||||
|
/** The cleaner that will ensure that rollbacks clean up after themselves */
|
||||||
|
private EagerContentStoreCleaner eagerContentStoreCleaner;
|
||||||
/** the store to use. Any multi-store support is provided by the store implementation. */
|
/** the store to use. Any multi-store support is provided by the store implementation. */
|
||||||
private ContentStore store;
|
private ContentStore store;
|
||||||
/** the store for all temporarily created content */
|
/** the store for all temporarily created content */
|
||||||
@@ -113,14 +114,6 @@ public class ContentServiceImpl implements ContentService
|
|||||||
ClassPolicyDelegate<ContentServicePolicies.OnContentPropertyUpdatePolicy> onContentPropertyUpdateDelegate;
|
ClassPolicyDelegate<ContentServicePolicies.OnContentPropertyUpdatePolicy> onContentPropertyUpdateDelegate;
|
||||||
ClassPolicyDelegate<ContentServicePolicies.OnContentReadPolicy> onContentReadDelegate;
|
ClassPolicyDelegate<ContentServicePolicies.OnContentReadPolicy> onContentReadDelegate;
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Replaced by {@link #setRetryingTransactionHelper(RetryingTransactionHelper)}
|
|
||||||
*/
|
|
||||||
public void setTransactionService(TransactionService transactionService)
|
|
||||||
{
|
|
||||||
logger.warn("Property 'transactionService' has been replaced by 'retryingTransactionHelper'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRetryingTransactionHelper(RetryingTransactionHelper helper)
|
public void setRetryingTransactionHelper(RetryingTransactionHelper helper)
|
||||||
{
|
{
|
||||||
this.transactionHelper = helper;
|
this.transactionHelper = helper;
|
||||||
@@ -141,6 +134,11 @@ public class ContentServiceImpl implements ContentService
|
|||||||
this.transformerRegistry = transformerRegistry;
|
this.transformerRegistry = transformerRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setEagerContentStoreCleaner(EagerContentStoreCleaner eagerContentStoreCleaner)
|
||||||
|
{
|
||||||
|
this.eagerContentStoreCleaner = eagerContentStoreCleaner;
|
||||||
|
}
|
||||||
|
|
||||||
public void setStore(ContentStore store)
|
public void setStore(ContentStore store)
|
||||||
{
|
{
|
||||||
this.store = store;
|
this.store = store;
|
||||||
@@ -428,6 +426,8 @@ public class ContentServiceImpl implements ContentService
|
|||||||
ContentContext ctx = new ContentContext(null, null);
|
ContentContext ctx = new ContentContext(null, null);
|
||||||
// for this case, we just give back a valid URL into the content store
|
// for this case, we just give back a valid URL into the content store
|
||||||
ContentWriter writer = store.getWriter(ctx);
|
ContentWriter writer = store.getWriter(ctx);
|
||||||
|
// Register the new URL for rollback cleanup
|
||||||
|
eagerContentStoreCleaner.registerNewContentUrl(writer.getContentUrl());
|
||||||
// done
|
// done
|
||||||
return writer;
|
return writer;
|
||||||
}
|
}
|
||||||
@@ -439,6 +439,8 @@ public class ContentServiceImpl implements ContentService
|
|||||||
// can be wherever the store decides.
|
// can be wherever the store decides.
|
||||||
ContentContext ctx = new NodeContentContext(existingContentReader, null, nodeRef, propertyQName);
|
ContentContext ctx = new NodeContentContext(existingContentReader, null, nodeRef, propertyQName);
|
||||||
ContentWriter writer = store.getWriter(ctx);
|
ContentWriter writer = store.getWriter(ctx);
|
||||||
|
// Register the new URL for rollback cleanup
|
||||||
|
eagerContentStoreCleaner.registerNewContentUrl(writer.getContentUrl());
|
||||||
|
|
||||||
// Special case for AVM repository.
|
// Special case for AVM repository.
|
||||||
Serializable contentValue = null;
|
Serializable contentValue = null;
|
||||||
|
@@ -24,39 +24,32 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.repo.content.cleanup;
|
package org.alfresco.repo.content.cleanup;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
import org.alfresco.repo.domain.avm.AVMNodeDAO;
|
import org.alfresco.repo.domain.avm.AVMNodeDAO;
|
||||||
import org.alfresco.repo.content.ContentStore;
|
|
||||||
import org.alfresco.repo.domain.contentclean.ContentCleanDAO;
|
|
||||||
import org.alfresco.repo.domain.contentclean.ContentCleanDAO.ContentUrlBatchProcessor;
|
|
||||||
import org.alfresco.repo.domain.contentdata.ContentDataDAO;
|
import org.alfresco.repo.domain.contentdata.ContentDataDAO;
|
||||||
|
import org.alfresco.repo.domain.contentdata.ContentDataDAO.ContentUrlHandler;
|
||||||
import org.alfresco.repo.lock.JobLockService;
|
import org.alfresco.repo.lock.JobLockService;
|
||||||
import org.alfresco.repo.node.db.NodeDaoService;
|
import org.alfresco.repo.node.db.NodeDaoService;
|
||||||
import org.alfresco.repo.node.db.NodeDaoService.NodePropertyHandler;
|
|
||||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
|
||||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||||
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
|
||||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||||
import org.alfresco.service.cmr.repository.ContentData;
|
|
||||||
import org.alfresco.service.cmr.repository.ContentService;
|
import org.alfresco.service.cmr.repository.ContentService;
|
||||||
import org.alfresco.service.cmr.repository.NodeRef;
|
|
||||||
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
|
|
||||||
import org.alfresco.service.namespace.NamespaceService;
|
import org.alfresco.service.namespace.NamespaceService;
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
import org.alfresco.service.transaction.TransactionService;
|
import org.alfresco.service.transaction.TransactionService;
|
||||||
import org.springframework.extensions.surf.util.PropertyCheck;
|
|
||||||
import org.alfresco.util.VmShutdownListener;
|
import org.alfresco.util.VmShutdownListener;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.extensions.surf.util.Pair;
|
||||||
|
import org.springframework.extensions.surf.util.PropertyCheck;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component is responsible cleaning up orphaned content.
|
* This component is responsible cleaning up orphaned content.
|
||||||
* <p/>
|
* <p/>
|
||||||
|
* <b>TODO: Fix up new comments</b>
|
||||||
|
*
|
||||||
* Clean-up happens at two levels.<p/>
|
* Clean-up happens at two levels.<p/>
|
||||||
* <u><b>Eager cleanup:</b></u> (since 3.2)<p/>
|
* <u><b>Eager cleanup:</b></u> (since 3.2)<p/>
|
||||||
* If {@link #setEagerOrphanCleanup(boolean) eager cleanup} is activated, then this
|
* If {@link #setEagerOrphanCleanup(boolean) eager cleanup} is activated, then this
|
||||||
@@ -88,30 +81,38 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
*/
|
*/
|
||||||
public class ContentStoreCleaner
|
public class ContentStoreCleaner
|
||||||
{
|
{
|
||||||
|
private static final QName LOCK_QNAME = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "ContentStoreCleaner");
|
||||||
|
private static final long LOCK_TTL = 30000L;
|
||||||
|
private static ThreadLocal<Pair<Long, String>> lockThreadLocal = new ThreadLocal<Pair<Long, String>>();
|
||||||
|
|
||||||
private static Log logger = LogFactory.getLog(ContentStoreCleaner.class);
|
private static Log logger = LogFactory.getLog(ContentStoreCleaner.class);
|
||||||
|
|
||||||
/** kept to notify the thread that it should quit */
|
/** kept to notify the thread that it should quit */
|
||||||
private static VmShutdownListener vmShutdownListener = new VmShutdownListener("ContentStoreCleaner");
|
private static VmShutdownListener vmShutdownListener = new VmShutdownListener("ContentStoreCleaner");
|
||||||
|
|
||||||
|
private EagerContentStoreCleaner eagerContentStoreCleaner;
|
||||||
private JobLockService jobLockService;
|
private JobLockService jobLockService;
|
||||||
private ContentCleanDAO contentCleanDAO;
|
|
||||||
private ContentDataDAO contentDataDAO;
|
private ContentDataDAO contentDataDAO;
|
||||||
private DictionaryService dictionaryService;
|
private DictionaryService dictionaryService;
|
||||||
private ContentService contentService;
|
private ContentService contentService;
|
||||||
private NodeDaoService nodeDaoService;
|
private NodeDaoService nodeDaoService;
|
||||||
private AVMNodeDAO avmNodeDAO;
|
private AVMNodeDAO avmNodeDAO;
|
||||||
private TransactionService transactionService;
|
private TransactionService transactionService;
|
||||||
private List<ContentStore> stores;
|
|
||||||
private List<ContentStoreCleanerListener> listeners;
|
|
||||||
private int protectDays;
|
private int protectDays;
|
||||||
|
|
||||||
public ContentStoreCleaner()
|
public ContentStoreCleaner()
|
||||||
{
|
{
|
||||||
this.stores = new ArrayList<ContentStore>(0);
|
|
||||||
this.listeners = new ArrayList<ContentStoreCleanerListener>(0);
|
|
||||||
this.protectDays = 7;
|
this.protectDays = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the component that will do the physical deleting
|
||||||
|
*/
|
||||||
|
public void setEagerContentStoreCleaner(EagerContentStoreCleaner eagerContentStoreCleaner)
|
||||||
|
{
|
||||||
|
this.eagerContentStoreCleaner = eagerContentStoreCleaner;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param jobLockService service used to ensure that cleanup runs are not duplicated
|
* @param jobLockService service used to ensure that cleanup runs are not duplicated
|
||||||
*/
|
*/
|
||||||
@@ -120,14 +121,6 @@ public class ContentStoreCleaner
|
|||||||
this.jobLockService = jobLockService;
|
this.jobLockService = jobLockService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param contentCleanDAO DAO used for manipulating content URLs
|
|
||||||
*/
|
|
||||||
public void setContentCleanDAO(ContentCleanDAO contentCleanDAO)
|
|
||||||
{
|
|
||||||
this.contentCleanDAO = contentCleanDAO;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param contentDataDAO DAO used for enumerating DM content URLs
|
* @param contentDataDAO DAO used for enumerating DM content URLs
|
||||||
*/
|
*/
|
||||||
@@ -176,22 +169,6 @@ public class ContentStoreCleaner
|
|||||||
this.transactionService = transactionService;
|
this.transactionService = transactionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param stores the content stores to clean
|
|
||||||
*/
|
|
||||||
public void setStores(List<ContentStore> stores)
|
|
||||||
{
|
|
||||||
this.stores = stores;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param listeners the listeners that can react to deletions
|
|
||||||
*/
|
|
||||||
public void setListeners(List<ContentStoreCleanerListener> listeners)
|
|
||||||
{
|
|
||||||
this.listeners = listeners;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the minimum number of days old that orphaned content must be
|
* Set the minimum number of days old that orphaned content must be
|
||||||
* before deletion is possible. The default is 7 days.
|
* before deletion is possible. The default is 7 days.
|
||||||
@@ -217,14 +194,13 @@ public class ContentStoreCleaner
|
|||||||
private void checkProperties()
|
private void checkProperties()
|
||||||
{
|
{
|
||||||
PropertyCheck.mandatory(this, "jobLockService", jobLockService);
|
PropertyCheck.mandatory(this, "jobLockService", jobLockService);
|
||||||
PropertyCheck.mandatory(this, "contentCleanerDAO", contentCleanDAO);
|
|
||||||
PropertyCheck.mandatory(this, "contentDataDAO", contentDataDAO);
|
PropertyCheck.mandatory(this, "contentDataDAO", contentDataDAO);
|
||||||
PropertyCheck.mandatory(this, "dictionaryService", dictionaryService);
|
PropertyCheck.mandatory(this, "dictionaryService", dictionaryService);
|
||||||
PropertyCheck.mandatory(this, "contentService", contentService);
|
PropertyCheck.mandatory(this, "contentService", contentService);
|
||||||
PropertyCheck.mandatory(this, "nodeDaoService", nodeDaoService);
|
PropertyCheck.mandatory(this, "nodeDaoService", nodeDaoService);
|
||||||
PropertyCheck.mandatory(this, "avmNodeDAO", avmNodeDAO);
|
PropertyCheck.mandatory(this, "avmNodeDAO", avmNodeDAO);
|
||||||
PropertyCheck.mandatory(this, "transactionService", transactionService);
|
PropertyCheck.mandatory(this, "transactionService", transactionService);
|
||||||
PropertyCheck.mandatory(this, "listeners", listeners);
|
PropertyCheck.mandatory(this, "eagerContentStoreCleaner", eagerContentStoreCleaner);
|
||||||
|
|
||||||
// check the protect days
|
// check the protect days
|
||||||
if (protectDays < 0)
|
if (protectDays < 0)
|
||||||
@@ -235,156 +211,70 @@ public class ContentStoreCleaner
|
|||||||
{
|
{
|
||||||
logger.warn(
|
logger.warn(
|
||||||
"Property 'protectDays' is set to 0. " +
|
"Property 'protectDays' is set to 0. " +
|
||||||
"It is possible that in-transaction content will be deleted.");
|
"Please ensure that your backup strategy is appropriate for this setting.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeContentUrlsPresentInMetadata(final ContentUrlBatchProcessor urlRemover)
|
/**
|
||||||
|
* Lazily update the job lock
|
||||||
|
*/
|
||||||
|
private void refreshLock()
|
||||||
{
|
{
|
||||||
RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
|
Pair<Long, String> lockPair = lockThreadLocal.get();
|
||||||
|
if (lockPair == null)
|
||||||
// Remove all the Content URLs for the ADM repository
|
|
||||||
// Handlers that record the URLs
|
|
||||||
final ContentDataDAO.ContentUrlHandler contentUrlHandler = new ContentDataDAO.ContentUrlHandler()
|
|
||||||
{
|
{
|
||||||
long lastLock = 0L;
|
String lockToken = jobLockService.getLock(LOCK_QNAME, LOCK_TTL);
|
||||||
public void handle(String contentUrl)
|
Long lastLock = new Long(System.currentTimeMillis());
|
||||||
|
// We have not locked before
|
||||||
|
lockPair = new Pair<Long, String>(lastLock, lockToken);
|
||||||
|
lockThreadLocal.set(lockPair);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
long lastLock = lockPair.getFirst().longValue();
|
||||||
|
String lockToken = lockPair.getSecond();
|
||||||
|
// Only refresh the lock if we are past a threshold
|
||||||
|
if (now - lastLock > (long)(LOCK_TTL/2L))
|
||||||
{
|
{
|
||||||
if (vmShutdownListener.isVmShuttingDown())
|
jobLockService.refreshLock(lockToken, LOCK_QNAME, LOCK_TTL);
|
||||||
{
|
lastLock = System.currentTimeMillis();
|
||||||
throw new VmShutdownException();
|
lockPair = new Pair<Long, String>(lastLock, lockToken);
|
||||||
}
|
|
||||||
urlRemover.processContentUrl(contentUrl);
|
|
||||||
// Check lock
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
if (now - lastLock > (long)(LOCK_TTL/2L))
|
|
||||||
{
|
|
||||||
jobLockService.getTransactionalLock(LOCK_QNAME, LOCK_TTL);
|
|
||||||
lastLock = now;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
final NodePropertyHandler nodePropertyHandler = new NodePropertyHandler()
|
|
||||||
{
|
|
||||||
long lastLock = 0L;
|
|
||||||
public void handle(NodeRef nodeRef, QName nodeTypeQName, QName propertyQName, Serializable value)
|
|
||||||
{
|
|
||||||
if (vmShutdownListener.isVmShuttingDown())
|
|
||||||
{
|
|
||||||
throw new VmShutdownException();
|
|
||||||
}
|
|
||||||
// Convert the values to ContentData and extract the URLs
|
|
||||||
ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value);
|
|
||||||
String contentUrl = contentData.getContentUrl();
|
|
||||||
if (contentUrl != null)
|
|
||||||
{
|
|
||||||
urlRemover.processContentUrl(contentUrl);
|
|
||||||
}
|
|
||||||
// Check lock
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
if (now - lastLock > (long)(LOCK_TTL/2L))
|
|
||||||
{
|
|
||||||
jobLockService.getTransactionalLock(LOCK_QNAME, LOCK_TTL);
|
|
||||||
lastLock = now;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
final DataTypeDefinition contentDataType = dictionaryService.getDataType(DataTypeDefinition.CONTENT);
|
|
||||||
// execute in READ-WRITE txn
|
|
||||||
RetryingTransactionCallback<Void> getUrlsCallback = new RetryingTransactionCallback<Void>()
|
|
||||||
{
|
|
||||||
public Void execute() throws Exception
|
|
||||||
{
|
|
||||||
contentDataDAO.getAllContentUrls(contentUrlHandler);
|
|
||||||
nodeDaoService.getPropertyValuesByActualType(contentDataType, nodePropertyHandler);
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
txnHelper.doInTransaction(getUrlsCallback);
|
|
||||||
|
|
||||||
// Do the same for the AVM repository.
|
|
||||||
final AVMNodeDAO.ContentUrlHandler handler = new AVMNodeDAO.ContentUrlHandler()
|
|
||||||
{
|
|
||||||
long lastLock = 0L;
|
|
||||||
public void handle(String contentUrl)
|
|
||||||
{
|
|
||||||
if (vmShutdownListener.isVmShuttingDown())
|
|
||||||
{
|
|
||||||
throw new VmShutdownException();
|
|
||||||
}
|
|
||||||
urlRemover.processContentUrl(contentUrl);
|
|
||||||
// Check lock
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
if (now - lastLock > (long)(LOCK_TTL/2L))
|
|
||||||
{
|
|
||||||
jobLockService.getTransactionalLock(LOCK_QNAME, LOCK_TTL);
|
|
||||||
lastLock = now;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// execute in READ-WRITE txn
|
|
||||||
RetryingTransactionCallback<Void> getAVMUrlsCallback = new RetryingTransactionCallback<Void>()
|
|
||||||
{
|
|
||||||
public Void execute() throws Exception
|
|
||||||
{
|
|
||||||
avmNodeDAO.getContentUrls(handler);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
txnHelper.doInTransaction(getAVMUrlsCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addContentUrlsPresentInStores(final ContentUrlBatchProcessor urlInserter)
|
|
||||||
{
|
|
||||||
org.alfresco.repo.content.ContentStore.ContentUrlHandler handler = new org.alfresco.repo.content.ContentStore.ContentUrlHandler()
|
|
||||||
{
|
|
||||||
long lastLock = 0L;
|
|
||||||
public void handle(String contentUrl)
|
|
||||||
{
|
|
||||||
if (vmShutdownListener.isVmShuttingDown())
|
|
||||||
{
|
|
||||||
throw new VmShutdownException();
|
|
||||||
}
|
|
||||||
urlInserter.processContentUrl(contentUrl);
|
|
||||||
// Check lock
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
if (now - lastLock > (long)(LOCK_TTL/2L))
|
|
||||||
{
|
|
||||||
jobLockService.getTransactionalLock(LOCK_QNAME, LOCK_TTL);
|
|
||||||
lastLock = now;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Date checkAllBeforeDate = new Date(System.currentTimeMillis() - (long) protectDays * 3600L * 1000L * 24L);
|
|
||||||
for (ContentStore store : stores)
|
|
||||||
{
|
|
||||||
store.getUrls(null, checkAllBeforeDate, handler);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final QName LOCK_QNAME = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "ContentStoreCleaner");
|
/**
|
||||||
private static final long LOCK_TTL = 30000L;
|
* Release the lock after the job completes
|
||||||
|
*/
|
||||||
|
private void releaseLock()
|
||||||
|
{
|
||||||
|
Pair<Long, String> lockPair = lockThreadLocal.get();
|
||||||
|
if (lockPair != null)
|
||||||
|
{
|
||||||
|
// We can't release without a token
|
||||||
|
try
|
||||||
|
{
|
||||||
|
jobLockService.releaseLock(lockPair.getSecond(), LOCK_QNAME);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Reset
|
||||||
|
lockThreadLocal.set(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// else: We can't release without a token
|
||||||
|
}
|
||||||
|
|
||||||
public void execute()
|
public void execute()
|
||||||
{
|
{
|
||||||
checkProperties();
|
checkProperties();
|
||||||
|
|
||||||
RetryingTransactionCallback<Void> executeCallback = new RetryingTransactionCallback<Void>()
|
|
||||||
{
|
|
||||||
public Void execute() throws Exception
|
|
||||||
{
|
|
||||||
logger.debug("Content store cleanup started.");
|
|
||||||
// Get the lock without any waiting
|
|
||||||
// The lock will be refreshed, but the first lock starts the process
|
|
||||||
jobLockService.getTransactionalLock(LOCK_QNAME, LOCK_TTL);
|
|
||||||
executeInternal();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
|
logger.debug("Content store cleanup started.");
|
||||||
txnHelper.setMaxRetries(0);
|
refreshLock();
|
||||||
txnHelper.doInTransaction(executeCallback);
|
executeInternal();
|
||||||
// Done
|
// Done
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isDebugEnabled())
|
||||||
{
|
{
|
||||||
@@ -399,103 +289,67 @@ public class ContentStoreCleaner
|
|||||||
logger.debug(" Content store cleanup aborted.");
|
logger.debug(" Content store cleanup aborted.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
releaseLock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void executeInternal()
|
private void executeInternal()
|
||||||
{
|
{
|
||||||
final ContentUrlBatchProcessor storeUrlDeleteHandler = new ContentUrlBatchProcessor()
|
|
||||||
{
|
|
||||||
long lastLock = 0L;
|
|
||||||
public void start()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
public void processContentUrl(String contentUrl)
|
|
||||||
{
|
|
||||||
for (ContentStore store : stores)
|
|
||||||
{
|
|
||||||
if (vmShutdownListener.isVmShuttingDown())
|
|
||||||
{
|
|
||||||
throw new VmShutdownException();
|
|
||||||
}
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
if (store.isWriteSupported())
|
|
||||||
{
|
|
||||||
logger.debug(" Deleting content URL: " + contentUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (ContentStoreCleanerListener listener : listeners)
|
|
||||||
{
|
|
||||||
listener.beforeDelete(store, contentUrl);
|
|
||||||
}
|
|
||||||
// Delete
|
|
||||||
store.delete(contentUrl);
|
|
||||||
// Check lock
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
if (now - lastLock > (long)(LOCK_TTL/2L))
|
|
||||||
{
|
|
||||||
jobLockService.getTransactionalLock(LOCK_QNAME, LOCK_TTL);
|
|
||||||
lastLock = now;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void end()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// execute in READ-WRITE txn
|
// execute in READ-WRITE txn
|
||||||
RetryingTransactionCallback<Void> executeCallback = new RetryingTransactionCallback<Void>()
|
RetryingTransactionCallback<Integer> getAndDeleteWork = new RetryingTransactionCallback<Integer>()
|
||||||
{
|
{
|
||||||
public Void execute() throws Exception
|
public Integer execute() throws Exception
|
||||||
{
|
{
|
||||||
// Clean up
|
return cleanBatch(1000);
|
||||||
contentCleanDAO.cleanUp();
|
|
||||||
// Push all store URLs in
|
|
||||||
ContentUrlBatchProcessor urlInserter = contentCleanDAO.getUrlInserter();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
urlInserter.start();
|
|
||||||
addContentUrlsPresentInStores(urlInserter);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
urlInserter.end();
|
|
||||||
}
|
|
||||||
// Delete all content URLs
|
|
||||||
ContentUrlBatchProcessor urlRemover = contentCleanDAO.getUrlRemover();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
urlRemover.start();
|
|
||||||
removeContentUrlsPresentInMetadata(urlRemover);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
urlRemover.end();
|
|
||||||
}
|
|
||||||
// Any remaining URLs are URls present in the stores but not in the metadata
|
|
||||||
contentCleanDAO.listAllUrls(storeUrlDeleteHandler);
|
|
||||||
// Clean up
|
|
||||||
contentCleanDAO.cleanUp();
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
try
|
while (true)
|
||||||
{
|
{
|
||||||
transactionService.getRetryingTransactionHelper().doInTransaction(executeCallback);
|
refreshLock();
|
||||||
// Done
|
Integer deleted = transactionService.getRetryingTransactionHelper().doInTransaction(getAndDeleteWork);
|
||||||
|
if (vmShutdownListener.isVmShuttingDown())
|
||||||
|
{
|
||||||
|
throw new VmShutdownException();
|
||||||
|
}
|
||||||
|
if (deleted.intValue() == 0)
|
||||||
|
{
|
||||||
|
// There is no more to process
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// There is still more to delete, so continue
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isDebugEnabled())
|
||||||
{
|
{
|
||||||
logger.debug(" Content store cleanup completed.");
|
logger.debug(" Removed " + deleted.intValue() + " orphaned content URLs.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (VmShutdownException e)
|
// Done
|
||||||
|
}
|
||||||
|
|
||||||
|
private int cleanBatch(final int batchSize)
|
||||||
|
{
|
||||||
|
final List<Long> idsToDelete = new ArrayList<Long>(batchSize);
|
||||||
|
ContentUrlHandler contentUrlHandler = new ContentUrlHandler()
|
||||||
{
|
{
|
||||||
// Aborted
|
public void handle(Long id, String contentUrl, Long orphanTime)
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
{
|
{
|
||||||
logger.debug(" Content store cleanup aborted.");
|
// Pass the content URL to the eager cleaner for post-commit handling
|
||||||
|
eagerContentStoreCleaner.registerOrphanedContentUrl(contentUrl, true);
|
||||||
|
idsToDelete.add(id);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
final long maxOrphanTime = System.currentTimeMillis() - (protectDays * 24 * 3600 * 1000);
|
||||||
|
contentDataDAO.getContentUrlsOrphaned(contentUrlHandler, maxOrphanTime, batchSize);
|
||||||
|
// All the URLs have been passed off for eventual deletion.
|
||||||
|
// Just delete the DB data
|
||||||
|
int size = idsToDelete.size();
|
||||||
|
if (size > 0)
|
||||||
|
{
|
||||||
|
contentDataDAO.deleteContentUrls(idsToDelete);
|
||||||
}
|
}
|
||||||
|
// Done
|
||||||
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -30,6 +30,7 @@ import java.lang.reflect.Method;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
import org.alfresco.model.ContentModel;
|
||||||
import org.alfresco.repo.content.AbstractContentStore;
|
import org.alfresco.repo.content.AbstractContentStore;
|
||||||
import org.alfresco.repo.content.ContentStore;
|
import org.alfresco.repo.content.ContentStore;
|
||||||
import org.alfresco.repo.content.EmptyContentReader;
|
import org.alfresco.repo.content.EmptyContentReader;
|
||||||
@@ -48,11 +49,13 @@ import org.alfresco.service.cmr.repository.ContentIOException;
|
|||||||
import org.alfresco.service.cmr.repository.ContentReader;
|
import org.alfresco.service.cmr.repository.ContentReader;
|
||||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||||
import org.alfresco.service.cmr.repository.NodeRef;
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
|
import org.alfresco.service.cmr.repository.StoreRef;
|
||||||
import org.alfresco.service.namespace.NamespaceService;
|
import org.alfresco.service.namespace.NamespaceService;
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
import org.alfresco.service.transaction.TransactionService;
|
import org.alfresco.service.transaction.TransactionService;
|
||||||
import org.alfresco.tools.Repository;
|
import org.alfresco.tools.Repository;
|
||||||
import org.alfresco.tools.ToolException;
|
import org.alfresco.tools.ToolException;
|
||||||
|
import org.alfresco.util.GUID;
|
||||||
import org.alfresco.util.TempFileProvider;
|
import org.alfresco.util.TempFileProvider;
|
||||||
import org.alfresco.util.VmShutdownListener;
|
import org.alfresco.util.VmShutdownListener;
|
||||||
import org.apache.commons.lang.mutable.MutableInt;
|
import org.apache.commons.lang.mutable.MutableInt;
|
||||||
@@ -105,9 +108,9 @@ public class ContentStoreCleanerScalabilityRunner extends Repository
|
|||||||
nodeDaoService = (NodeDaoService) ctx.getBean("nodeDaoService");
|
nodeDaoService = (NodeDaoService) ctx.getBean("nodeDaoService");
|
||||||
dictionaryService = (DictionaryService) ctx.getBean("dictionaryService");
|
dictionaryService = (DictionaryService) ctx.getBean("dictionaryService");
|
||||||
|
|
||||||
int orphanCount = 100000;
|
int orphanCount = 1000;
|
||||||
|
|
||||||
contentStore = new NullContentStore(orphanCount);
|
contentStore = new NullContentStore(10000);
|
||||||
|
|
||||||
loadData(orphanCount);
|
loadData(orphanCount);
|
||||||
|
|
||||||
@@ -220,11 +223,12 @@ public class ContentStoreCleanerScalabilityRunner extends Repository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// We use the default cleaner, but fix it up a bit
|
// We use the default cleaners, but fix them up a bit
|
||||||
|
EagerContentStoreCleaner eagerCleaner = (EagerContentStoreCleaner) ctx.getBean("eagerContentStoreCleaner");
|
||||||
|
eagerCleaner.setListeners(Collections.singletonList(listener));
|
||||||
|
eagerCleaner.setStores(Collections.singletonList(contentStore));
|
||||||
cleaner = (ContentStoreCleaner) ctx.getBean("contentStoreCleaner");
|
cleaner = (ContentStoreCleaner) ctx.getBean("contentStoreCleaner");
|
||||||
cleaner.setListeners(Collections.singletonList(listener));
|
|
||||||
cleaner.setProtectDays(0);
|
cleaner.setProtectDays(0);
|
||||||
cleaner.setStores(Collections.singletonList(contentStore));
|
|
||||||
|
|
||||||
// The cleaner has its own txns
|
// The cleaner has its own txns
|
||||||
cleaner.execute();
|
cleaner.execute();
|
||||||
@@ -301,22 +305,10 @@ public class ContentStoreCleanerScalabilityRunner extends Repository
|
|||||||
|
|
||||||
private class HibernateHelper extends HibernateDaoSupport
|
private class HibernateHelper extends HibernateDaoSupport
|
||||||
{
|
{
|
||||||
private Method methodMakeNode;
|
|
||||||
private QName dataTypeDefContent;
|
|
||||||
private QName contentQName;
|
private QName contentQName;
|
||||||
|
|
||||||
public HibernateHelper()
|
public HibernateHelper()
|
||||||
{
|
{
|
||||||
Class<HibernateHelper> clazz = HibernateHelper.class;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
methodMakeNode = clazz.getMethod("makeNode", new Class[] {ContentData.class});
|
|
||||||
}
|
|
||||||
catch (NoSuchMethodException e)
|
|
||||||
{
|
|
||||||
throw new RuntimeException("Failed to get methods");
|
|
||||||
}
|
|
||||||
dataTypeDefContent = DataTypeDefinition.CONTENT;
|
|
||||||
contentQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "realContent");
|
contentQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "realContent");
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -324,17 +316,9 @@ public class ContentStoreCleanerScalabilityRunner extends Repository
|
|||||||
*/
|
*/
|
||||||
public void makeNode(ContentData contentData)
|
public void makeNode(ContentData contentData)
|
||||||
{
|
{
|
||||||
throw new UnsupportedOperationException("Fix this method up");
|
StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
|
||||||
// StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
|
Long nodeId = nodeDaoService.newNode(storeRef, GUID.generate(), ContentModel.TYPE_CONTENT).getFirst();
|
||||||
// Long nodeId = nodeDaoService.newNode(storeRef, GUID.generate(), ContentModel.TYPE_CONTENT).getFirst();
|
nodeDaoService.addNodeProperty(nodeId, contentQName, contentData);
|
||||||
// Node node = (Node) getHibernateTemplate().get(NodeImpl.class, nodeId);
|
|
||||||
//
|
|
||||||
// PropertyValue propertyValue = new PropertyValue(dataTypeDefContent, contentData);
|
|
||||||
// node.getProperties().put(contentQName, propertyValue);
|
|
||||||
// // persist the node
|
|
||||||
// getHibernateTemplate().save(node);
|
|
||||||
//
|
|
||||||
// txnResourceInterceptor.performManualCheck(methodMakeNode, 10);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -34,11 +34,9 @@ import java.util.Map;
|
|||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
import org.alfresco.model.ContentModel;
|
import org.alfresco.model.ContentModel;
|
||||||
import org.alfresco.repo.domain.avm.AVMNodeDAO;
|
|
||||||
import org.alfresco.repo.content.ContentStore;
|
import org.alfresco.repo.content.ContentStore;
|
||||||
import org.alfresco.repo.content.MimetypeMap;
|
import org.alfresco.repo.content.MimetypeMap;
|
||||||
import org.alfresco.repo.content.filestore.FileContentStore;
|
import org.alfresco.repo.domain.avm.AVMNodeDAO;
|
||||||
import org.alfresco.repo.domain.contentclean.ContentCleanDAO;
|
|
||||||
import org.alfresco.repo.domain.contentdata.ContentDataDAO;
|
import org.alfresco.repo.domain.contentdata.ContentDataDAO;
|
||||||
import org.alfresco.repo.lock.JobLockService;
|
import org.alfresco.repo.lock.JobLockService;
|
||||||
import org.alfresco.repo.node.db.NodeDaoService;
|
import org.alfresco.repo.node.db.NodeDaoService;
|
||||||
@@ -58,9 +56,7 @@ import org.alfresco.service.namespace.QName;
|
|||||||
import org.alfresco.service.transaction.TransactionService;
|
import org.alfresco.service.transaction.TransactionService;
|
||||||
import org.alfresco.util.ApplicationContextHelper;
|
import org.alfresco.util.ApplicationContextHelper;
|
||||||
import org.alfresco.util.GUID;
|
import org.alfresco.util.GUID;
|
||||||
import org.alfresco.util.TempFileProvider;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.repo.content.cleanup.ContentStoreCleaner
|
* @see org.alfresco.repo.content.cleanup.ContentStoreCleaner
|
||||||
@@ -95,33 +91,30 @@ public class ContentStoreCleanerTest extends TestCase
|
|||||||
DictionaryService dictionaryService = serviceRegistry.getDictionaryService();
|
DictionaryService dictionaryService = serviceRegistry.getDictionaryService();
|
||||||
NodeDaoService nodeDaoService = (NodeDaoService) ctx.getBean("nodeDaoService");
|
NodeDaoService nodeDaoService = (NodeDaoService) ctx.getBean("nodeDaoService");
|
||||||
AVMNodeDAO avmNodeDAO = (AVMNodeDAO) ctx.getBean("newAvmNodeDAO");
|
AVMNodeDAO avmNodeDAO = (AVMNodeDAO) ctx.getBean("newAvmNodeDAO");
|
||||||
ContentCleanDAO contentCleanDAO = (ContentCleanDAO) ctx.getBean("contentCleanDAO");
|
|
||||||
ContentDataDAO contentDataDAO = (ContentDataDAO) ctx.getBean("contentDataDAO");
|
ContentDataDAO contentDataDAO = (ContentDataDAO) ctx.getBean("contentDataDAO");
|
||||||
ApplicationEventPublisher applicationEventPublisher = (ApplicationEventPublisher) ctx
|
|
||||||
.getBean("applicationEventPublisher");
|
|
||||||
|
|
||||||
eagerCleaner = (EagerContentStoreCleaner) ctx.getBean("eagerContentStoreCleaner");
|
|
||||||
eagerCleaner.setEagerOrphanCleanup(false);
|
|
||||||
|
|
||||||
// we need a store
|
// we need a store
|
||||||
store = new FileContentStore(applicationEventPublisher, TempFileProvider.getTempDir().getAbsolutePath());
|
store = (ContentStore) ctx.getBean("fileContentStore");
|
||||||
// and a listener
|
// and a listener
|
||||||
listener = new DummyCleanerListener();
|
listener = new DummyCleanerListener();
|
||||||
// initialise record of deleted URLs
|
// initialise record of deleted URLs
|
||||||
deletedUrls = new ArrayList<String>(5);
|
deletedUrls = new ArrayList<String>(5);
|
||||||
|
|
||||||
// construct the test cleaner
|
// Construct the test cleaners
|
||||||
|
eagerCleaner = (EagerContentStoreCleaner) ctx.getBean("eagerContentStoreCleaner");
|
||||||
|
eagerCleaner.setEagerOrphanCleanup(false);
|
||||||
|
eagerCleaner.setStores(Collections.singletonList(store));
|
||||||
|
eagerCleaner.setListeners(Collections.singletonList(listener));
|
||||||
|
|
||||||
cleaner = new ContentStoreCleaner();
|
cleaner = new ContentStoreCleaner();
|
||||||
|
cleaner.setEagerContentStoreCleaner(eagerCleaner);
|
||||||
cleaner.setJobLockService(jobLockService);
|
cleaner.setJobLockService(jobLockService);
|
||||||
cleaner.setContentCleanDAO(contentCleanDAO);
|
|
||||||
cleaner.setContentDataDAO(contentDataDAO);
|
cleaner.setContentDataDAO(contentDataDAO);
|
||||||
cleaner.setTransactionService(transactionService);
|
cleaner.setTransactionService(transactionService);
|
||||||
cleaner.setDictionaryService(dictionaryService);
|
cleaner.setDictionaryService(dictionaryService);
|
||||||
cleaner.setContentService(contentService);
|
cleaner.setContentService(contentService);
|
||||||
cleaner.setNodeDaoService(nodeDaoService);
|
cleaner.setNodeDaoService(nodeDaoService);
|
||||||
cleaner.setAvmNodeDAO(avmNodeDAO);
|
cleaner.setAvmNodeDAO(avmNodeDAO);
|
||||||
cleaner.setStores(Collections.singletonList(store));
|
|
||||||
cleaner.setListeners(Collections.singletonList(listener));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tearDown() throws Exception
|
public void tearDown() throws Exception
|
||||||
@@ -293,18 +286,49 @@ public class ContentStoreCleanerTest extends TestCase
|
|||||||
|
|
||||||
public void testImmediateRemoval() throws Exception
|
public void testImmediateRemoval() throws Exception
|
||||||
{
|
{
|
||||||
cleaner.setProtectDays(0);
|
eagerCleaner.setEagerOrphanCleanup(false);
|
||||||
// add some content to the store
|
|
||||||
ContentWriter writer = store.getWriter(ContentStore.NEW_CONTENT_CONTEXT);
|
final StoreRef storeRef = nodeService.createStore("test", getName() + "-" + GUID.generate());
|
||||||
writer.putContent("ABC");
|
RetryingTransactionCallback<ContentData> testCallback = new RetryingTransactionCallback<ContentData>()
|
||||||
String contentUrl = writer.getContentUrl();
|
{
|
||||||
|
public ContentData execute() throws Throwable
|
||||||
|
{
|
||||||
|
// Create some content
|
||||||
|
NodeRef rootNodeRef = nodeService.getRootNode(storeRef);
|
||||||
|
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(13);
|
||||||
|
properties.put(ContentModel.PROP_NAME, (Serializable)"test.txt");
|
||||||
|
NodeRef contentNodeRef = nodeService.createNode(
|
||||||
|
rootNodeRef,
|
||||||
|
ContentModel.ASSOC_CHILDREN,
|
||||||
|
ContentModel.ASSOC_CHILDREN,
|
||||||
|
ContentModel.TYPE_CONTENT,
|
||||||
|
properties).getChildRef();
|
||||||
|
ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
||||||
|
writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
|
||||||
|
writer.putContent("INITIAL CONTENT");
|
||||||
|
ContentData contentData = writer.getContentData();
|
||||||
|
|
||||||
|
// Delete the first node
|
||||||
|
nodeService.deleteNode(contentNodeRef);
|
||||||
|
|
||||||
|
// Done
|
||||||
|
return contentData;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ContentData contentData = transactionService.getRetryingTransactionHelper().doInTransaction(testCallback);
|
||||||
|
// Make sure that the content URL still exists
|
||||||
|
ContentReader reader = contentService.getRawReader(contentData.getContentUrl());
|
||||||
|
assertNotNull(reader);
|
||||||
|
assertTrue("Content should not have been eagerly deleted.", reader.exists());
|
||||||
|
|
||||||
// fire the cleaner
|
// fire the cleaner
|
||||||
|
cleaner.setProtectDays(0);
|
||||||
cleaner.execute();
|
cleaner.execute();
|
||||||
|
|
||||||
|
reader = contentService.getRawReader(contentData.getContentUrl());
|
||||||
// the content should have disappeared as it is not in the database
|
// the content should have disappeared as it is not in the database
|
||||||
assertFalse("Unprotected content was not deleted", store.exists(contentUrl));
|
assertFalse("Unprotected content was not deleted", reader.exists());
|
||||||
assertTrue("Content listener was not called", deletedUrls.contains(contentUrl));
|
assertTrue("Content listener was not called", deletedUrls.contains(reader.getContentUrl()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testProtectedRemoval() throws Exception
|
public void testProtectedRemoval() throws Exception
|
||||||
|
@@ -143,10 +143,6 @@ public class EagerContentStoreCleaner extends TransactionListenerAdapter
|
|||||||
*/
|
*/
|
||||||
public void registerNewContentUrl(String contentUrl)
|
public void registerNewContentUrl(String contentUrl)
|
||||||
{
|
{
|
||||||
if (!eagerOrphanCleanup)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Set<String> urlsToDelete = TransactionalResourceHelper.getSet(KEY_POST_ROLLBACK_DELETION_URLS);
|
Set<String> urlsToDelete = TransactionalResourceHelper.getSet(KEY_POST_ROLLBACK_DELETION_URLS);
|
||||||
urlsToDelete.add(contentUrl);
|
urlsToDelete.add(contentUrl);
|
||||||
// Register to listen for transaction rollback
|
// Register to listen for transaction rollback
|
||||||
@@ -158,7 +154,18 @@ public class EagerContentStoreCleaner extends TransactionListenerAdapter
|
|||||||
*/
|
*/
|
||||||
public void registerOrphanedContentUrl(String contentUrl)
|
public void registerOrphanedContentUrl(String contentUrl)
|
||||||
{
|
{
|
||||||
if (!eagerOrphanCleanup)
|
registerOrphanedContentUrl(contentUrl, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queues orphaned content for post-transaction removal
|
||||||
|
*
|
||||||
|
* @param force <tt>true</tt> for force the post-commit URL deletion
|
||||||
|
* regardless of the setting {@link #setEagerOrphanCleanup(boolean)}.
|
||||||
|
*/
|
||||||
|
public void registerOrphanedContentUrl(String contentUrl, boolean force)
|
||||||
|
{
|
||||||
|
if (!eagerOrphanCleanup && !force)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.contentclean;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DAO services for <b>alf_contentclean</b> table.
|
|
||||||
* This DAO is geared towards bulk processing of content URLs.
|
|
||||||
* <p>
|
|
||||||
* Content URLs are lowercased and CRC'ed
|
|
||||||
*
|
|
||||||
* @author Derek Hulley
|
|
||||||
* @since 3.2
|
|
||||||
*/
|
|
||||||
public interface ContentCleanDAO
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Interface callback for putting and getting content URL values
|
|
||||||
*
|
|
||||||
* @author Derek Hulley
|
|
||||||
* @since 3.2
|
|
||||||
*/
|
|
||||||
public interface ContentUrlBatchProcessor
|
|
||||||
{
|
|
||||||
void start();
|
|
||||||
void processContentUrl(String contentUrl);
|
|
||||||
void end();
|
|
||||||
}
|
|
||||||
|
|
||||||
void cleanUp();
|
|
||||||
|
|
||||||
ContentUrlBatchProcessor getUrlInserter();
|
|
||||||
|
|
||||||
ContentUrlBatchProcessor getUrlRemover();
|
|
||||||
|
|
||||||
void listAllUrls(ContentUrlBatchProcessor batchProcessor);
|
|
||||||
}
|
|
@@ -1,89 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.contentclean;
|
|
||||||
|
|
||||||
import org.alfresco.util.EqualsHelper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Entity bean for <b>alf_content_url</b> table.
|
|
||||||
* <p>
|
|
||||||
* These are unique (see {@link #equals(Object) equals} and {@link #hashCode() hashCode}) based
|
|
||||||
* on the {@link #getContentUrl() content URL} value.
|
|
||||||
*
|
|
||||||
* @author Derek Hulley
|
|
||||||
* @since 3.2
|
|
||||||
*/
|
|
||||||
public class ContentCleanEntity
|
|
||||||
{
|
|
||||||
private String contentUrl;
|
|
||||||
|
|
||||||
public ContentCleanEntity()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode()
|
|
||||||
{
|
|
||||||
return (contentUrl == null ? 0 : contentUrl.hashCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj)
|
|
||||||
{
|
|
||||||
if (this == obj)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (obj instanceof ContentCleanEntity)
|
|
||||||
{
|
|
||||||
ContentCleanEntity that = (ContentCleanEntity) obj;
|
|
||||||
return EqualsHelper.nullSafeEquals(this.contentUrl, that.contentUrl);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder(512);
|
|
||||||
sb.append("ContentCleanEntity")
|
|
||||||
.append("[ contentUrl=").append(contentUrl)
|
|
||||||
.append("]");
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContentUrl()
|
|
||||||
{
|
|
||||||
return contentUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setContentUrl(String contentUrl)
|
|
||||||
{
|
|
||||||
this.contentUrl = contentUrl;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,254 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.contentclean.ibatis;
|
|
||||||
|
|
||||||
import java.sql.SQLException;
|
|
||||||
|
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
|
||||||
import org.alfresco.repo.domain.contentclean.ContentCleanDAO;
|
|
||||||
import org.alfresco.repo.domain.contentclean.ContentCleanEntity;
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.springframework.orm.ibatis.SqlMapClientTemplate;
|
|
||||||
|
|
||||||
import com.ibatis.sqlmap.client.SqlMapClient;
|
|
||||||
import com.ibatis.sqlmap.client.event.RowHandler;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* iBatis-specific implementation of the Content Cleaner DAO.
|
|
||||||
*
|
|
||||||
* @author Derek Hulley
|
|
||||||
* @since 3.2
|
|
||||||
*/
|
|
||||||
public class ContentCleanDAOImpl implements ContentCleanDAO
|
|
||||||
{
|
|
||||||
private static Log logger = LogFactory.getLog(ContentCleanDAOImpl.class);
|
|
||||||
|
|
||||||
private static final int DEFAULT_BATCH_SIZE = 50;
|
|
||||||
|
|
||||||
private static final String INSERT_CONTENT_CLEAN = "alfresco.content.insert_ContentCleanUrl";
|
|
||||||
private static final String SELECT_CONTENT_CLEAN_URLS = "alfresco.content.select_ContentCleanUrls";
|
|
||||||
private static final String DELETE_CONTENT_CLEAN_BY_URL = "alfresco.content.delete_ContentCleanUrl";
|
|
||||||
private static final String DELETE_CONTENT_CLEAN = "alfresco.content.delete_ContentCleanUrls";
|
|
||||||
|
|
||||||
private SqlMapClientTemplate template;
|
|
||||||
|
|
||||||
public void setSqlMapClientTemplate(SqlMapClientTemplate sqlMapClientTemplate)
|
|
||||||
{
|
|
||||||
this.template = sqlMapClientTemplate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public ContentUrlBatchProcessor getUrlInserter()
|
|
||||||
{
|
|
||||||
final SqlMapClient sqlMapClient = template.getSqlMapClient();
|
|
||||||
ContentUrlBatchProcessor processor = new ContentUrlBatchProcessor()
|
|
||||||
{
|
|
||||||
private int count = 0;
|
|
||||||
private int total = 0;
|
|
||||||
|
|
||||||
public void start()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
sqlMapClient.startBatch();
|
|
||||||
count = 0;
|
|
||||||
}
|
|
||||||
catch (SQLException e)
|
|
||||||
{
|
|
||||||
// Batches not supported, so don't do batching
|
|
||||||
count = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void processContentUrl(String contentUrl)
|
|
||||||
{
|
|
||||||
ContentCleanEntity contentCleanEntity = new ContentCleanEntity();
|
|
||||||
contentCleanEntity.setContentUrl(contentUrl == null ? null : contentUrl.toLowerCase());
|
|
||||||
template.insert(INSERT_CONTENT_CLEAN, contentCleanEntity);
|
|
||||||
// Write the batch
|
|
||||||
executeBatch();
|
|
||||||
total++;
|
|
||||||
}
|
|
||||||
public void end()
|
|
||||||
{
|
|
||||||
// Write the batch
|
|
||||||
executeBatch();
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug(" Inserted " + total + " content URLs (FINISHED)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void executeBatch()
|
|
||||||
{
|
|
||||||
// Are we batching?
|
|
||||||
if (count > -1)
|
|
||||||
{
|
|
||||||
// Write the batch, if required
|
|
||||||
if (++count >= DEFAULT_BATCH_SIZE)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
sqlMapClient.executeBatch();
|
|
||||||
sqlMapClient.startBatch();
|
|
||||||
}
|
|
||||||
catch (SQLException e)
|
|
||||||
{
|
|
||||||
throw new AlfrescoRuntimeException("Failed to execute batch", e);
|
|
||||||
}
|
|
||||||
count = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (logger.isDebugEnabled() && (total == 0 || (total % 1000 == 0) ))
|
|
||||||
{
|
|
||||||
logger.debug(" Inserted " + total + " content URLs");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Done
|
|
||||||
return processor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public ContentUrlBatchProcessor getUrlRemover()
|
|
||||||
{
|
|
||||||
final SqlMapClient sqlMapClient = template.getSqlMapClient();
|
|
||||||
ContentUrlBatchProcessor processor = new ContentUrlBatchProcessor()
|
|
||||||
{
|
|
||||||
private int count = 0;
|
|
||||||
private int total = 0;
|
|
||||||
|
|
||||||
public void start()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
sqlMapClient.startBatch();
|
|
||||||
count = 0;
|
|
||||||
}
|
|
||||||
catch (SQLException e)
|
|
||||||
{
|
|
||||||
// Batches not supported, so don't do batching
|
|
||||||
count = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void processContentUrl(String contentUrl)
|
|
||||||
{
|
|
||||||
ContentCleanEntity contentCleanEntity = new ContentCleanEntity();
|
|
||||||
contentCleanEntity.setContentUrl(contentUrl);
|
|
||||||
template.delete(DELETE_CONTENT_CLEAN_BY_URL, contentCleanEntity);
|
|
||||||
// Write the batch
|
|
||||||
executeBatch();
|
|
||||||
total++;
|
|
||||||
}
|
|
||||||
public void end()
|
|
||||||
{
|
|
||||||
// Write the batch
|
|
||||||
executeBatch();
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug(" Removed " + total + " content URLs (FINISHED)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void executeBatch()
|
|
||||||
{
|
|
||||||
// Are we batching?
|
|
||||||
if (count > -1)
|
|
||||||
{
|
|
||||||
// Write the batch, if required
|
|
||||||
if (++count >= DEFAULT_BATCH_SIZE)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
sqlMapClient.executeBatch();
|
|
||||||
sqlMapClient.startBatch();
|
|
||||||
}
|
|
||||||
catch (SQLException e)
|
|
||||||
{
|
|
||||||
throw new AlfrescoRuntimeException("Failed to execute batch", e);
|
|
||||||
}
|
|
||||||
count = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (logger.isDebugEnabled() && (total == 0 || (total % 1000 == 0) ))
|
|
||||||
{
|
|
||||||
logger.debug(" Removed " + total + " content URLs");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Done
|
|
||||||
return processor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public void listAllUrls(ContentUrlBatchProcessor batchProcessor)
|
|
||||||
{
|
|
||||||
ListAllRowHandler rowHandler = new ListAllRowHandler(batchProcessor);
|
|
||||||
|
|
||||||
batchProcessor.start();
|
|
||||||
template.queryWithRowHandler(SELECT_CONTENT_CLEAN_URLS, rowHandler);
|
|
||||||
batchProcessor.end();
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug(" Listed " + rowHandler.total + " content URLs");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Row handler for listing all content clean URLs
|
|
||||||
* @author Derek Hulley
|
|
||||||
* @since 3.2
|
|
||||||
*/
|
|
||||||
private static class ListAllRowHandler implements RowHandler
|
|
||||||
{
|
|
||||||
private final ContentUrlBatchProcessor batchProcessor;
|
|
||||||
private int total = 0;
|
|
||||||
private ListAllRowHandler(ContentUrlBatchProcessor batchProcessor)
|
|
||||||
{
|
|
||||||
this.batchProcessor = batchProcessor;
|
|
||||||
}
|
|
||||||
public void handleRow(Object valueObject)
|
|
||||||
{
|
|
||||||
batchProcessor.processContentUrl((String)valueObject);
|
|
||||||
total++;
|
|
||||||
if (logger.isDebugEnabled() && (total == 0 || (total % 1000 == 0) ))
|
|
||||||
{
|
|
||||||
logger.debug(" Listed " + total + " content URLs");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public void cleanUp()
|
|
||||||
{
|
|
||||||
template.delete(DELETE_CONTENT_CLEAN);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -29,6 +29,8 @@ import java.util.Locale;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.alfresco.repo.cache.SimpleCache;
|
import org.alfresco.repo.cache.SimpleCache;
|
||||||
|
import org.alfresco.repo.cache.lookup.EntityLookupCache;
|
||||||
|
import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAOAdaptor;
|
||||||
import org.alfresco.repo.content.cleanup.EagerContentStoreCleaner;
|
import org.alfresco.repo.content.cleanup.EagerContentStoreCleaner;
|
||||||
import org.alfresco.repo.domain.LocaleDAO;
|
import org.alfresco.repo.domain.LocaleDAO;
|
||||||
import org.alfresco.repo.domain.encoding.EncodingDAO;
|
import org.alfresco.repo.domain.encoding.EncodingDAO;
|
||||||
@@ -37,10 +39,12 @@ import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
|||||||
import org.alfresco.repo.transaction.TransactionListenerAdapter;
|
import org.alfresco.repo.transaction.TransactionListenerAdapter;
|
||||||
import org.alfresco.repo.transaction.TransactionalResourceHelper;
|
import org.alfresco.repo.transaction.TransactionalResourceHelper;
|
||||||
import org.alfresco.service.cmr.repository.ContentData;
|
import org.alfresco.service.cmr.repository.ContentData;
|
||||||
import org.springframework.extensions.surf.util.Pair;
|
import org.alfresco.util.EqualsHelper;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.dao.ConcurrencyFailureException;
|
import org.springframework.dao.ConcurrencyFailureException;
|
||||||
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
|
import org.springframework.extensions.surf.util.Pair;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract implementation for ContentData DAO.
|
* Abstract implementation for ContentData DAO.
|
||||||
@@ -56,6 +60,7 @@ import org.springframework.dao.ConcurrencyFailureException;
|
|||||||
*/
|
*/
|
||||||
public abstract class AbstractContentDataDAOImpl implements ContentDataDAO
|
public abstract class AbstractContentDataDAOImpl implements ContentDataDAO
|
||||||
{
|
{
|
||||||
|
private static final String CACHE_REGION_CONTENT_DATA = "ContentData";
|
||||||
/**
|
/**
|
||||||
* Content URL IDs to delete before final commit.
|
* Content URL IDs to delete before final commit.
|
||||||
*/
|
*/
|
||||||
@@ -63,12 +68,29 @@ public abstract class AbstractContentDataDAOImpl implements ContentDataDAO
|
|||||||
|
|
||||||
private static Log logger = LogFactory.getLog(AbstractContentDataDAOImpl.class);
|
private static Log logger = LogFactory.getLog(AbstractContentDataDAOImpl.class);
|
||||||
|
|
||||||
|
private final ContentDataCallbackDAO contentDataCallbackDAO;
|
||||||
private MimetypeDAO mimetypeDAO;
|
private MimetypeDAO mimetypeDAO;
|
||||||
private EncodingDAO encodingDAO;
|
private EncodingDAO encodingDAO;
|
||||||
private LocaleDAO localeDAO;
|
private LocaleDAO localeDAO;
|
||||||
private EagerContentStoreCleaner contentStoreCleaner;
|
private EagerContentStoreCleaner contentStoreCleaner;
|
||||||
private SimpleCache<Serializable, Serializable> contentDataCache;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache for the ContentData class:<br/>
|
||||||
|
* KEY: ID<br/>
|
||||||
|
* VALUE: ContentData object<br/>
|
||||||
|
* VALUE KEY: NONE<br/>
|
||||||
|
*/
|
||||||
|
private EntityLookupCache<Long, ContentData, Serializable> contentDataCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor
|
||||||
|
*/
|
||||||
|
public AbstractContentDataDAOImpl()
|
||||||
|
{
|
||||||
|
this.contentDataCallbackDAO = new ContentDataCallbackDAO();
|
||||||
|
this.contentDataCache = new EntityLookupCache<Long, ContentData, Serializable>(contentDataCallbackDAO);
|
||||||
|
}
|
||||||
|
|
||||||
public void setMimetypeDAO(MimetypeDAO mimetypeDAO)
|
public void setMimetypeDAO(MimetypeDAO mimetypeDAO)
|
||||||
{
|
{
|
||||||
this.mimetypeDAO = mimetypeDAO;
|
this.mimetypeDAO = mimetypeDAO;
|
||||||
@@ -97,9 +119,12 @@ public abstract class AbstractContentDataDAOImpl implements ContentDataDAO
|
|||||||
/**
|
/**
|
||||||
* @param contentDataCache the cache of IDs to ContentData and vice versa
|
* @param contentDataCache the cache of IDs to ContentData and vice versa
|
||||||
*/
|
*/
|
||||||
public void setContentDataCache(SimpleCache<Serializable, Serializable> contentDataCache)
|
public void setContentDataCache(SimpleCache<Long, ContentData> contentDataCache)
|
||||||
{
|
{
|
||||||
this.contentDataCache = contentDataCache;
|
this.contentDataCache = new EntityLookupCache<Long, ContentData, Serializable>(
|
||||||
|
contentDataCache,
|
||||||
|
CACHE_REGION_CONTENT_DATA,
|
||||||
|
contentDataCallbackDAO);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -114,7 +139,7 @@ public abstract class AbstractContentDataDAOImpl implements ContentDataDAO
|
|||||||
* A <b>content_url</b> entity was dereferenced. This makes no assumptions about the
|
* A <b>content_url</b> entity was dereferenced. This makes no assumptions about the
|
||||||
* current references - dereference deletion is handled in the commit phase.
|
* current references - dereference deletion is handled in the commit phase.
|
||||||
*/
|
*/
|
||||||
protected void registerDereferenceContentUrl(String contentUrl)
|
protected void registerDereferencedContentUrl(String contentUrl)
|
||||||
{
|
{
|
||||||
Set<String> contentUrls = TransactionalResourceHelper.getSet(KEY_PRE_COMMIT_CONTENT_URL_DELETIONS);
|
Set<String> contentUrls = TransactionalResourceHelper.getSet(KEY_PRE_COMMIT_CONTENT_URL_DELETIONS);
|
||||||
if (contentUrls.size() == 0)
|
if (contentUrls.size() == 0)
|
||||||
@@ -130,12 +155,12 @@ public abstract class AbstractContentDataDAOImpl implements ContentDataDAO
|
|||||||
*/
|
*/
|
||||||
public Pair<Long, ContentData> createContentData(ContentData contentData)
|
public Pair<Long, ContentData> createContentData(ContentData contentData)
|
||||||
{
|
{
|
||||||
/*
|
if (contentData == null)
|
||||||
* TODO: Cache
|
{
|
||||||
*/
|
throw new IllegalArgumentException("ContentData values cannot be null");
|
||||||
ContentDataEntity contentDataEntity = createContentDataEntity(contentData);
|
}
|
||||||
// Done
|
Pair<Long, ContentData> entityPair = contentDataCache.getOrCreateByValue(contentData);
|
||||||
return new Pair<Long, ContentData>(contentDataEntity.getId(), contentData);
|
return entityPair;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -143,34 +168,98 @@ public abstract class AbstractContentDataDAOImpl implements ContentDataDAO
|
|||||||
*/
|
*/
|
||||||
public Pair<Long, ContentData> getContentData(Long id)
|
public Pair<Long, ContentData> getContentData(Long id)
|
||||||
{
|
{
|
||||||
/*
|
if (id == null)
|
||||||
* TODO: Cache
|
|
||||||
*/
|
|
||||||
ContentDataEntity contentDataEntity = getContentDataEntity(id);
|
|
||||||
if (contentDataEntity == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
throw new IllegalArgumentException("Cannot look up ContentData by null ID.");
|
||||||
}
|
}
|
||||||
// Convert back to ContentData
|
Pair<Long, ContentData> entityPair = contentDataCache.getByKey(id);
|
||||||
ContentData contentData = makeContentData(contentDataEntity);
|
if (entityPair == null)
|
||||||
// Done
|
{
|
||||||
return new Pair<Long, ContentData>(id, contentData);
|
throw new DataIntegrityViolationException("No ContentData value exists for ID " + id);
|
||||||
|
}
|
||||||
|
return entityPair;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
|
public void updateContentData(Long id, ContentData contentData)
|
||||||
|
{
|
||||||
|
if (id == null)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Cannot look up ContentData by null ID.");
|
||||||
|
}
|
||||||
|
if (contentData == null)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Cannot update ContentData with a null.");
|
||||||
|
}
|
||||||
|
int updated = contentDataCache.updateValue(id, contentData);
|
||||||
|
if (updated < 1)
|
||||||
|
{
|
||||||
|
throw new ConcurrencyFailureException("ContentData with ID " + id + " not updated");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
public void deleteContentData(Long id)
|
public void deleteContentData(Long id)
|
||||||
{
|
{
|
||||||
int deleted = deleteContentDataEntity(id);
|
if (id == null)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Cannot delete ContentData by null ID.");
|
||||||
|
}
|
||||||
|
int deleted = contentDataCache.deleteByKey(id);
|
||||||
if (deleted < 1)
|
if (deleted < 1)
|
||||||
{
|
{
|
||||||
throw new ConcurrencyFailureException("ContetntData with ID " + id + " no longer exists");
|
throw new ConcurrencyFailureException("ContentData with ID " + id + " no longer exists");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Callback for <b>alf_content_data</b> DAO.
|
||||||
|
*/
|
||||||
|
private class ContentDataCallbackDAO extends EntityLookupCallbackDAOAdaptor<Long, ContentData, Serializable>
|
||||||
|
{
|
||||||
|
public Pair<Long, ContentData> createValue(ContentData value)
|
||||||
|
{
|
||||||
|
ContentDataEntity contentDataEntity = createContentDataEntity(value);
|
||||||
|
// Done
|
||||||
|
return new Pair<Long, ContentData>(contentDataEntity.getId(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pair<Long, ContentData> findByKey(Long key)
|
||||||
|
{
|
||||||
|
ContentDataEntity contentDataEntity = getContentDataEntity(key);
|
||||||
|
if (contentDataEntity == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ContentData contentData = makeContentData(contentDataEntity);
|
||||||
|
// Done
|
||||||
|
return new Pair<Long, ContentData>(key, contentData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int updateValue(Long key, ContentData value)
|
||||||
|
{
|
||||||
|
ContentDataEntity contentDataEntity = getContentDataEntity(key);
|
||||||
|
if (contentDataEntity == null)
|
||||||
|
{
|
||||||
|
return 0; // The client (outer-level code) will decide if this is an error
|
||||||
|
}
|
||||||
|
return updateContentDataEntity(contentDataEntity, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int deleteByKey(Long key)
|
||||||
|
{
|
||||||
|
return deleteContentDataEntity(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
* Translates this instance into an externally-usable <code>ContentData</code> instance.
|
* Translates this instance into an externally-usable <code>ContentData</code> instance.
|
||||||
*/
|
*/
|
||||||
private ContentData makeContentData(ContentDataEntity contentDataEntity)
|
private ContentData makeContentData(ContentDataEntity contentDataEntity)
|
||||||
@@ -248,13 +337,66 @@ public abstract class AbstractContentDataDAOImpl implements ContentDataDAO
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Translates the {@link ContentData} into persistable values using the helper DAOs
|
||||||
|
*/
|
||||||
|
private int updateContentDataEntity(ContentDataEntity contentDataEntity, ContentData contentData)
|
||||||
|
{
|
||||||
|
// Resolve the content URL
|
||||||
|
String oldContentUrl = contentDataEntity.getContentUrl();
|
||||||
|
String newContentUrl = contentData.getContentUrl();
|
||||||
|
if (!EqualsHelper.nullSafeEquals(oldContentUrl, newContentUrl))
|
||||||
|
{
|
||||||
|
if (oldContentUrl != null)
|
||||||
|
{
|
||||||
|
// We have a changed value. The old content URL has been dereferenced.
|
||||||
|
registerDereferencedContentUrl(oldContentUrl);
|
||||||
|
}
|
||||||
|
if (newContentUrl != null)
|
||||||
|
{
|
||||||
|
Long contentUrlId = getOrCreateContentUrlEntity(newContentUrl, contentData.getSize()).getId();
|
||||||
|
contentDataEntity.setContentUrlId(contentUrlId);
|
||||||
|
contentDataEntity.setContentUrl(newContentUrl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
contentDataEntity.setContentUrlId(null);
|
||||||
|
contentDataEntity.setContentUrl(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Resolve the mimetype
|
||||||
|
Long mimetypeId = null;
|
||||||
|
String mimetype = contentData.getMimetype();
|
||||||
|
if (mimetype != null)
|
||||||
|
{
|
||||||
|
mimetypeId = mimetypeDAO.getOrCreateMimetype(mimetype).getFirst();
|
||||||
|
}
|
||||||
|
// Resolve the encoding
|
||||||
|
Long encodingId = null;
|
||||||
|
String encoding = contentData.getEncoding();
|
||||||
|
if (encoding != null)
|
||||||
|
{
|
||||||
|
encodingId = encodingDAO.getOrCreateEncoding(encoding).getFirst();
|
||||||
|
}
|
||||||
|
// Resolve the locale
|
||||||
|
Long localeId = null;
|
||||||
|
Locale locale = contentData.getLocale();
|
||||||
|
if (locale != null)
|
||||||
|
{
|
||||||
|
localeId = localeDAO.getOrCreateLocalePair(locale).getFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
contentDataEntity.setMimetypeId(mimetypeId);
|
||||||
|
contentDataEntity.setEncodingId(encodingId);
|
||||||
|
contentDataEntity.setLocaleId(localeId);
|
||||||
|
|
||||||
|
return updateContentDataEntity(contentDataEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
* Caching method that creates an entity for <b>content_url_entity</b>.
|
* Caching method that creates an entity for <b>content_url_entity</b>.
|
||||||
*/
|
*/
|
||||||
private ContentUrlEntity getOrCreateContentUrlEntity(String contentUrl, long size)
|
private ContentUrlEntity getOrCreateContentUrlEntity(String contentUrl, long size)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
* TODO: Check for cache requirements
|
|
||||||
*/
|
|
||||||
// Create the content URL entity
|
// Create the content URL entity
|
||||||
ContentUrlEntity contentUrlEntity = getContentUrlEntity(contentUrl);
|
ContentUrlEntity contentUrlEntity = getContentUrlEntity(contentUrl);
|
||||||
// If it exists, then we can just re-use it, but check that the size is consistent
|
// If it exists, then we can just re-use it, but check that the size is consistent
|
||||||
@@ -304,10 +446,13 @@ public abstract class AbstractContentDataDAOImpl implements ContentDataDAO
|
|||||||
protected abstract ContentUrlEntity getContentUrlEntityUnreferenced(String contentUrl);
|
protected abstract ContentUrlEntity getContentUrlEntityUnreferenced(String contentUrl);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the entity with the given ID
|
* Update a content URL with the given orphan time
|
||||||
* @return Returns the number of rows deleted
|
*
|
||||||
|
* @param id the unique ID of the entity
|
||||||
|
* @param orphanTime the time (ms since epoch) that the entity was orphaned
|
||||||
|
* @return Returns the number of rows updated
|
||||||
*/
|
*/
|
||||||
protected abstract int deleteContentUrlEntity(Long id);
|
protected abstract int updateContentUrlOrphanTime(Long id, long orphanTime);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the row for the <b>alf_content_data<b>
|
* Create the row for the <b>alf_content_data<b>
|
||||||
@@ -325,6 +470,14 @@ public abstract class AbstractContentDataDAOImpl implements ContentDataDAO
|
|||||||
protected abstract ContentDataEntity getContentDataEntity(Long id);
|
protected abstract ContentDataEntity getContentDataEntity(Long id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Update an existing <b>alf_content_data</b> entity
|
||||||
|
*
|
||||||
|
* @param entity the existing entity that will be updated
|
||||||
|
* @return Returns the number of rows updated (should be 1)
|
||||||
|
*/
|
||||||
|
protected abstract int updateContentDataEntity(ContentDataEntity entity);
|
||||||
|
|
||||||
|
/**
|
||||||
* Delete the entity with the given ID
|
* Delete the entity with the given ID
|
||||||
*
|
*
|
||||||
* @return Returns the number of rows deleted
|
* @return Returns the number of rows deleted
|
||||||
@@ -347,6 +500,7 @@ public abstract class AbstractContentDataDAOImpl implements ContentDataDAO
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Set<String> contentUrls = TransactionalResourceHelper.getSet(KEY_PRE_COMMIT_CONTENT_URL_DELETIONS);
|
Set<String> contentUrls = TransactionalResourceHelper.getSet(KEY_PRE_COMMIT_CONTENT_URL_DELETIONS);
|
||||||
|
long orphanTime = System.currentTimeMillis();
|
||||||
for (String contentUrl : contentUrls)
|
for (String contentUrl : contentUrls)
|
||||||
{
|
{
|
||||||
ContentUrlEntity contentUrlEntity = getContentUrlEntityUnreferenced(contentUrl);
|
ContentUrlEntity contentUrlEntity = getContentUrlEntityUnreferenced(contentUrl);
|
||||||
@@ -355,9 +509,9 @@ public abstract class AbstractContentDataDAOImpl implements ContentDataDAO
|
|||||||
// It is still referenced, so ignore it
|
// It is still referenced, so ignore it
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// It needs to be deleted
|
// We mark the URL as orphaned.
|
||||||
Long contentUrlId = contentUrlEntity.getId();
|
Long contentUrlId = contentUrlEntity.getId();
|
||||||
deleteContentUrlEntity(contentUrlId);
|
updateContentUrlOrphanTime(contentUrlId, orphanTime);
|
||||||
// Pop this in the queue for deletion from the content store
|
// Pop this in the queue for deletion from the content store
|
||||||
contentStoreCleaner.registerOrphanedContentUrl(contentUrl);
|
contentStoreCleaner.registerOrphanedContentUrl(contentUrl);
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.repo.domain.contentdata;
|
package org.alfresco.repo.domain.contentdata;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
@@ -46,6 +47,14 @@ public interface ContentDataDAO
|
|||||||
* @return the ContentData pair (id, ContentData) (never null)
|
* @return the ContentData pair (id, ContentData) (never null)
|
||||||
*/
|
*/
|
||||||
Pair<Long, ContentData> createContentData(ContentData contentData);
|
Pair<Long, ContentData> createContentData(ContentData contentData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a content data instance
|
||||||
|
*
|
||||||
|
* @param id the unique ID of the entity
|
||||||
|
* @param contentData the new data
|
||||||
|
*/
|
||||||
|
void updateContentData(Long id, ContentData contentData);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param id the unique ID of the entity
|
* @param id the unique ID of the entity
|
||||||
@@ -77,13 +86,28 @@ public interface ContentDataDAO
|
|||||||
*/
|
*/
|
||||||
public static interface ContentUrlHandler
|
public static interface ContentUrlHandler
|
||||||
{
|
{
|
||||||
void handle(String contentUrl);
|
void handle(Long id, String contentUrl, Long orphanTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enumerate all available content URLs
|
* Enumerate all available content URLs that were orphaned on or before the given time
|
||||||
*
|
*
|
||||||
* @param contentUrlHandler
|
* @param contentUrlHandler the callback object to process the rows
|
||||||
|
* @param maxOrphanTime the maximum orphan time
|
||||||
*/
|
*/
|
||||||
void getAllContentUrls(ContentUrlHandler contentUrlHandler);
|
void getContentUrlsOrphaned(ContentUrlHandler contentUrlHandler, long maxOrphanTime);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumerate all available content URLs that were orphaned on or before the given time
|
||||||
|
*
|
||||||
|
* @param contentUrlHandler the callback object to process the rows
|
||||||
|
* @param maxOrphanTime the maximum orphan time
|
||||||
|
* @param maxResults the maximum number of results (1 or greater)
|
||||||
|
*/
|
||||||
|
void getContentUrlsOrphaned(ContentUrlHandler contentUrlHandler, long maxOrphanTime, int maxResults);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a batch of content URL entities.
|
||||||
|
*/
|
||||||
|
int deleteContentUrls(List<Long> ids);
|
||||||
}
|
}
|
||||||
|
@@ -34,6 +34,7 @@ import org.alfresco.repo.content.ContentContext;
|
|||||||
import org.alfresco.repo.content.ContentStore;
|
import org.alfresco.repo.content.ContentStore;
|
||||||
import org.alfresco.repo.content.MimetypeMap;
|
import org.alfresco.repo.content.MimetypeMap;
|
||||||
import org.alfresco.repo.content.filestore.FileContentStore;
|
import org.alfresco.repo.content.filestore.FileContentStore;
|
||||||
|
import org.alfresco.repo.domain.contentdata.ContentDataDAO.ContentUrlHandler;
|
||||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||||
import org.alfresco.service.ServiceRegistry;
|
import org.alfresco.service.ServiceRegistry;
|
||||||
@@ -44,6 +45,7 @@ import org.springframework.extensions.surf.util.Pair;
|
|||||||
import org.alfresco.util.TempFileProvider;
|
import org.alfresco.util.TempFileProvider;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see ContentDataDAO
|
* @see ContentDataDAO
|
||||||
@@ -85,6 +87,32 @@ public class ContentDataDAOTest extends TestCase
|
|||||||
return txnHelper.doInTransaction(callback, false, false);
|
return txnHelper.doInTransaction(callback, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Pair<Long, ContentData> update(final Long id, final ContentData contentData)
|
||||||
|
{
|
||||||
|
RetryingTransactionCallback<Pair<Long, ContentData>> callback = new RetryingTransactionCallback<Pair<Long, ContentData>>()
|
||||||
|
{
|
||||||
|
public Pair<Long, ContentData> execute() throws Throwable
|
||||||
|
{
|
||||||
|
contentDataDAO.updateContentData(id, contentData);
|
||||||
|
return new Pair<Long, ContentData>(id, contentData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return txnHelper.doInTransaction(callback, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void delete(final Long id)
|
||||||
|
{
|
||||||
|
RetryingTransactionCallback<Void> callback = new RetryingTransactionCallback<Void>()
|
||||||
|
{
|
||||||
|
public Void execute() throws Throwable
|
||||||
|
{
|
||||||
|
contentDataDAO.deleteContentData(id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
txnHelper.doInTransaction(callback, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves and checks the ContentData for equality
|
* Retrieves and checks the ContentData for equality
|
||||||
*/
|
*/
|
||||||
@@ -118,7 +146,15 @@ public class ContentDataDAOTest extends TestCase
|
|||||||
|
|
||||||
public void testGetWithInvalidId()
|
public void testGetWithInvalidId()
|
||||||
{
|
{
|
||||||
assertNull("Expected null for invalid ID", contentDataDAO.getContentData(-1L));
|
try
|
||||||
|
{
|
||||||
|
contentDataDAO.getContentData(-1L);
|
||||||
|
fail("Invalid ContentData IDs must generate DataIntegrityViolationException.");
|
||||||
|
}
|
||||||
|
catch (DataIntegrityViolationException e)
|
||||||
|
{
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -164,13 +200,27 @@ public class ContentDataDAOTest extends TestCase
|
|||||||
getAndCheck(resultPairLower.getFirst(), contentDataLower);
|
getAndCheck(resultPairLower.getFirst(), contentDataLower);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testUpdate() throws Exception
|
||||||
|
{
|
||||||
|
ContentData contentData = getContentData();
|
||||||
|
Pair<Long, ContentData> resultPair = create(contentData);
|
||||||
|
Long id = resultPair.getFirst();
|
||||||
|
// Update
|
||||||
|
contentData = ContentData.setMimetype(contentData, MimetypeMap.MIMETYPE_HTML);
|
||||||
|
contentData = ContentData.setEncoding(contentData, "UTF-16");
|
||||||
|
// Don't update the content itself
|
||||||
|
update(id, contentData);
|
||||||
|
// Check
|
||||||
|
getAndCheck(id, contentData);
|
||||||
|
}
|
||||||
|
|
||||||
public void testDelete() throws Exception
|
public void testDelete() throws Exception
|
||||||
{
|
{
|
||||||
ContentData contentData = getContentData();
|
ContentData contentData = getContentData();
|
||||||
|
|
||||||
Pair<Long, ContentData> resultPair = create(contentData);
|
Pair<Long, ContentData> resultPair = create(contentData);
|
||||||
getAndCheck(resultPair.getFirst(), contentData);
|
getAndCheck(resultPair.getFirst(), contentData);
|
||||||
contentDataDAO.deleteContentData(resultPair.getFirst());
|
delete(resultPair.getFirst());
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
getAndCheck(resultPair.getFirst(), contentData);
|
getAndCheck(resultPair.getFirst(), contentData);
|
||||||
@@ -182,6 +232,66 @@ public class ContentDataDAOTest extends TestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testContentUrl_FetchingOrphansNoLimit() throws Exception
|
||||||
|
{
|
||||||
|
ContentData contentData = getContentData();
|
||||||
|
Pair<Long, ContentData> resultPair = create(contentData);
|
||||||
|
getAndCheck(resultPair.getFirst(), contentData);
|
||||||
|
delete(resultPair.getFirst());
|
||||||
|
// The content URL is orphaned
|
||||||
|
final String contentUrlOrphaned = contentData.getContentUrl();
|
||||||
|
final boolean[] found = new boolean[] {false};
|
||||||
|
|
||||||
|
// Iterate over all orphaned content URLs and ensure that we hit the one we just orphaned
|
||||||
|
ContentUrlHandler handler = new ContentUrlHandler()
|
||||||
|
{
|
||||||
|
public void handle(Long id, String contentUrl, Long orphanTime)
|
||||||
|
{
|
||||||
|
// Check
|
||||||
|
if (id == null || contentUrl == null || orphanTime == null)
|
||||||
|
{
|
||||||
|
fail("Invalid orphan data returned to handler: " + id + "-" + contentUrl + "-" + orphanTime);
|
||||||
|
}
|
||||||
|
// Did we get the one we wanted?
|
||||||
|
if (contentUrl.equals(contentUrlOrphaned))
|
||||||
|
{
|
||||||
|
found[0] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
contentDataDAO.getContentUrlsOrphaned(handler, Long.MAX_VALUE);
|
||||||
|
assertTrue("Newly-orphaned content URL not found", found[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testContentUrl_FetchingOrphansWithLimit() throws Exception
|
||||||
|
{
|
||||||
|
// Orphan some content
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
ContentData contentData = getContentData();
|
||||||
|
Pair<Long, ContentData> resultPair = create(contentData);
|
||||||
|
getAndCheck(resultPair.getFirst(), contentData);
|
||||||
|
delete(resultPair.getFirst());
|
||||||
|
}
|
||||||
|
final int[] count = new int[] {0};
|
||||||
|
|
||||||
|
// Iterate over all orphaned content URLs and ensure that we hit the one we just orphaned
|
||||||
|
ContentUrlHandler handler = new ContentUrlHandler()
|
||||||
|
{
|
||||||
|
public void handle(Long id, String contentUrl, Long orphanTime)
|
||||||
|
{
|
||||||
|
// Check
|
||||||
|
if (id == null || contentUrl == null || orphanTime == null)
|
||||||
|
{
|
||||||
|
fail("Invalid orphan data returned to handler: " + id + "-" + contentUrl + "-" + orphanTime);
|
||||||
|
}
|
||||||
|
count[0]++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
contentDataDAO.getContentUrlsOrphaned(handler, Long.MAX_VALUE, 5);
|
||||||
|
assertEquals("Expected exactly 5 results callbacks", 5, count[0]);
|
||||||
|
}
|
||||||
|
|
||||||
private static final String[] MIMETYPES = new String[]
|
private static final String[] MIMETYPES = new String[]
|
||||||
{
|
{
|
||||||
MimetypeMap.MIMETYPE_ACP,
|
MimetypeMap.MIMETYPE_ACP,
|
||||||
|
@@ -92,6 +92,18 @@ public class ContentDataEntity
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void incrementVersion()
|
||||||
|
{
|
||||||
|
if (version >= Short.MAX_VALUE)
|
||||||
|
{
|
||||||
|
this.version = 0L;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.version++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Long getId()
|
public Long getId()
|
||||||
{
|
{
|
||||||
return id;
|
return id;
|
||||||
|
@@ -43,11 +43,11 @@ public class ContentUrlEntity
|
|||||||
public static final String EMPTY_URL = "empty";
|
public static final String EMPTY_URL = "empty";
|
||||||
|
|
||||||
private Long id;
|
private Long id;
|
||||||
private Long version;
|
|
||||||
private String contentUrl;
|
private String contentUrl;
|
||||||
private String contentUrlShort;
|
private String contentUrlShort;
|
||||||
private long contentUrlCrc;
|
private long contentUrlCrc;
|
||||||
private long size;
|
private long size;
|
||||||
|
private Long orphanTime;
|
||||||
|
|
||||||
public ContentUrlEntity()
|
public ContentUrlEntity()
|
||||||
{
|
{
|
||||||
@@ -86,6 +86,7 @@ public class ContentUrlEntity
|
|||||||
.append("[ ID=").append(id)
|
.append("[ ID=").append(id)
|
||||||
.append(", contentUrl=").append(contentUrl)
|
.append(", contentUrl=").append(contentUrl)
|
||||||
.append(", size=").append(size)
|
.append(", size=").append(size)
|
||||||
|
.append(", orphanTime=").append(orphanTime)
|
||||||
.append("]");
|
.append("]");
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
@@ -129,16 +130,6 @@ public class ContentUrlEntity
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getVersion()
|
|
||||||
{
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVersion(Long version)
|
|
||||||
{
|
|
||||||
this.version = version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContentUrl()
|
public String getContentUrl()
|
||||||
{
|
{
|
||||||
// Convert the persisted content URL to an external value
|
// Convert the persisted content URL to an external value
|
||||||
@@ -195,4 +186,14 @@ public class ContentUrlEntity
|
|||||||
{
|
{
|
||||||
this.size = size;
|
this.size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getOrphanTime()
|
||||||
|
{
|
||||||
|
return orphanTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrphanTime(Long orphanTime)
|
||||||
|
{
|
||||||
|
this.orphanTime = orphanTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -47,13 +47,15 @@ public class ContentDataDAOImpl extends AbstractContentDataDAOImpl
|
|||||||
private static final String SELECT_CONTENT_URL_BY_ID = "alfresco.content.select_ContentUrlById";
|
private static final String SELECT_CONTENT_URL_BY_ID = "alfresco.content.select_ContentUrlById";
|
||||||
private static final String SELECT_CONTENT_URL_BY_KEY = "alfresco.content.select_ContentUrlByKey";
|
private static final String SELECT_CONTENT_URL_BY_KEY = "alfresco.content.select_ContentUrlByKey";
|
||||||
private static final String SELECT_CONTENT_URL_BY_KEY_UNREFERENCED = "alfresco.content.select_ContentUrlByKeyUnreferenced";
|
private static final String SELECT_CONTENT_URL_BY_KEY_UNREFERENCED = "alfresco.content.select_ContentUrlByKeyUnreferenced";
|
||||||
private static final String SELECT_CONTENT_URLS = "alfresco.content.select_ContentUrls";
|
private static final String SELECT_CONTENT_URLS_BY_ORPHAN_TIME = "alfresco.content.select_ContentUrlByOrphanTime";
|
||||||
private static final String SELECT_CONTENT_DATA_BY_ID = "alfresco.content.select_ContentDataById";
|
private static final String SELECT_CONTENT_DATA_BY_ID = "alfresco.content.select_ContentDataById";
|
||||||
private static final String SELECT_CONTENT_DATA_BY_NODE_AND_QNAME = "alfresco.content.select_ContentDataByNodeAndQName";
|
private static final String SELECT_CONTENT_DATA_BY_NODE_AND_QNAME = "alfresco.content.select_ContentDataByNodeAndQName";
|
||||||
private static final String INSERT_CONTENT_URL = "alfresco.content.insert_ContentUrl";
|
private static final String INSERT_CONTENT_URL = "alfresco.content.insert_ContentUrl";
|
||||||
private static final String INSERT_CONTENT_DATA = "alfresco.content.insert_ContentData";
|
private static final String INSERT_CONTENT_DATA = "alfresco.content.insert_ContentData";
|
||||||
|
private static final String UPDATE_CONTENT_URL_ORPHAN_TIME = "alfresco.content.update_ContentUrlOrphanTime";
|
||||||
|
private static final String UPDATE_CONTENT_DATA = "alfresco.content.update_ContentData";
|
||||||
private static final String DELETE_CONTENT_DATA = "alfresco.content.delete_ContentData";
|
private static final String DELETE_CONTENT_DATA = "alfresco.content.delete_ContentData";
|
||||||
private static final String DELETE_CONTENT_URL = "alfresco.content.delete_ContentUrl";
|
private static final String DELETE_CONTENT_URLS = "alfresco.content.delete_ContentUrls";
|
||||||
|
|
||||||
private SqlMapClientTemplate template;
|
private SqlMapClientTemplate template;
|
||||||
|
|
||||||
@@ -66,9 +68,9 @@ public class ContentDataDAOImpl extends AbstractContentDataDAOImpl
|
|||||||
protected ContentUrlEntity createContentUrlEntity(String contentUrl, long size)
|
protected ContentUrlEntity createContentUrlEntity(String contentUrl, long size)
|
||||||
{
|
{
|
||||||
ContentUrlEntity contentUrlEntity = new ContentUrlEntity();
|
ContentUrlEntity contentUrlEntity = new ContentUrlEntity();
|
||||||
contentUrlEntity.setVersion(ContentUrlEntity.CONST_LONG_ZERO);
|
|
||||||
contentUrlEntity.setContentUrl(contentUrl);
|
contentUrlEntity.setContentUrl(contentUrl);
|
||||||
contentUrlEntity.setSize(size);
|
contentUrlEntity.setSize(size);
|
||||||
|
contentUrlEntity.setOrphanTime(null);
|
||||||
/* Long id = (Long) */ template.insert(INSERT_CONTENT_URL, contentUrlEntity);
|
/* Long id = (Long) */ template.insert(INSERT_CONTENT_URL, contentUrlEntity);
|
||||||
/*contentUrlEntity.setId(id);*/
|
/*contentUrlEntity.setId(id);*/
|
||||||
// Register the url as new
|
// Register the url as new
|
||||||
@@ -101,12 +103,56 @@ public class ContentDataDAOImpl extends AbstractContentDataDAOImpl
|
|||||||
return contentUrlEntity;
|
return contentUrlEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void getContentUrlsOrphaned(final ContentUrlHandler contentUrlHandler, long maxOrphanTime)
|
||||||
protected int deleteContentUrlEntity(Long id)
|
|
||||||
{
|
{
|
||||||
Map<String, Object> params = new HashMap<String, Object>(11);
|
RowHandler rowHandler = new RowHandler()
|
||||||
params.put("id", id);
|
{
|
||||||
return template.delete(DELETE_CONTENT_URL, params);
|
public void handleRow(Object valueObject)
|
||||||
|
{
|
||||||
|
ContentUrlEntity contentUrlEntity = (ContentUrlEntity) valueObject;
|
||||||
|
contentUrlHandler.handle(
|
||||||
|
contentUrlEntity.getId(),
|
||||||
|
contentUrlEntity.getContentUrl(),
|
||||||
|
contentUrlEntity.getOrphanTime());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ContentUrlEntity contentUrlEntity = new ContentUrlEntity();
|
||||||
|
contentUrlEntity.setOrphanTime(maxOrphanTime);
|
||||||
|
template.queryWithRowHandler(SELECT_CONTENT_URLS_BY_ORPHAN_TIME, contentUrlEntity, rowHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void getContentUrlsOrphaned(final ContentUrlHandler contentUrlHandler, long maxOrphanTime, int maxResults)
|
||||||
|
{
|
||||||
|
ContentUrlEntity contentUrlEntity = new ContentUrlEntity();
|
||||||
|
contentUrlEntity.setOrphanTime(maxOrphanTime);
|
||||||
|
List<ContentUrlEntity> results = template.queryForList(
|
||||||
|
SELECT_CONTENT_URLS_BY_ORPHAN_TIME,
|
||||||
|
contentUrlEntity, 0, maxResults);
|
||||||
|
// Pass the result to the callback
|
||||||
|
for (ContentUrlEntity result : results)
|
||||||
|
{
|
||||||
|
contentUrlHandler.handle(
|
||||||
|
result.getId(),
|
||||||
|
result.getContentUrl(),
|
||||||
|
result.getOrphanTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int updateContentUrlOrphanTime(Long id, long orphanTime)
|
||||||
|
{
|
||||||
|
ContentUrlEntity contentUrlEntity = new ContentUrlEntity();
|
||||||
|
contentUrlEntity.setId(id);
|
||||||
|
contentUrlEntity.setOrphanTime(orphanTime);
|
||||||
|
return template.update(UPDATE_CONTENT_URL_ORPHAN_TIME, contentUrlEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public int deleteContentUrls(List<Long> ids)
|
||||||
|
{
|
||||||
|
return template.delete(DELETE_CONTENT_URLS, ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -151,9 +197,30 @@ public class ContentDataDAOImpl extends AbstractContentDataDAOImpl
|
|||||||
return contentDataEntity;
|
return contentDataEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int updateContentDataEntity(ContentDataEntity entity)
|
||||||
|
{
|
||||||
|
entity.incrementVersion();
|
||||||
|
return template.update(UPDATE_CONTENT_DATA, entity);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int deleteContentDataEntity(Long id)
|
protected int deleteContentDataEntity(Long id)
|
||||||
{
|
{
|
||||||
|
// Get the content urls
|
||||||
|
ContentDataEntity contentDataEntity = getContentDataEntity(id);
|
||||||
|
// This might be null as there is no constraint ensuring that the node points to a valid ContentData entity
|
||||||
|
if (contentDataEntity != null)
|
||||||
|
{
|
||||||
|
// Register the content URL for a later orphan-check
|
||||||
|
String contentUrl = contentDataEntity.getContentUrl();
|
||||||
|
if (contentUrl != null)
|
||||||
|
{
|
||||||
|
// It has been dereferenced and may be orphaned - we'll check later
|
||||||
|
registerDereferencedContentUrl(contentUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Issue the delete statement
|
||||||
Map<String, Object> params = new HashMap<String, Object>(11);
|
Map<String, Object> params = new HashMap<String, Object>(11);
|
||||||
params.put("id", id);
|
params.put("id", id);
|
||||||
return template.delete(DELETE_CONTENT_DATA, params);
|
return template.delete(DELETE_CONTENT_DATA, params);
|
||||||
@@ -175,36 +242,9 @@ public class ContentDataDAOImpl extends AbstractContentDataDAOImpl
|
|||||||
// Delete each one
|
// Delete each one
|
||||||
for (Long id : ids)
|
for (Long id : ids)
|
||||||
{
|
{
|
||||||
// Get the content urls
|
|
||||||
ContentDataEntity contentDataEntity = getContentDataEntity(id);
|
|
||||||
// This might be null as there is no constraint ensuring that the node points to a valid ContentData entity
|
|
||||||
if (contentDataEntity == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Only check the content URLs if one is present
|
|
||||||
String contentUrl = contentDataEntity.getContentUrl();
|
|
||||||
// Delete the ContentData entity
|
// Delete the ContentData entity
|
||||||
deleteContentData(id);
|
deleteContentData(id);
|
||||||
// Check if the content URL was orphaned
|
|
||||||
if (contentUrl != null)
|
|
||||||
{
|
|
||||||
// It has been dereferenced and may be orphaned - we'll check later
|
|
||||||
registerDereferenceContentUrl(contentUrl);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void getAllContentUrls(final ContentUrlHandler contentUrlHandler)
|
|
||||||
{
|
|
||||||
RowHandler rowHandler = new RowHandler()
|
|
||||||
{
|
|
||||||
public void handleRow(Object valueObject)
|
|
||||||
{
|
|
||||||
contentUrlHandler.handle((String)valueObject);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
template.queryWithRowHandler(SELECT_CONTENT_URLS, rowHandler);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -31,6 +31,7 @@ import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
|||||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||||
import org.alfresco.repo.transaction.TransactionListenerAdapter;
|
import org.alfresco.repo.transaction.TransactionListenerAdapter;
|
||||||
import org.alfresco.repo.transaction.TransactionalResourceHelper;
|
import org.alfresco.repo.transaction.TransactionalResourceHelper;
|
||||||
|
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
|
||||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
import org.alfresco.util.GUID;
|
import org.alfresco.util.GUID;
|
||||||
@@ -265,8 +266,11 @@ public class JobLockServiceImpl implements JobLockService
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
int iterations = doWithRetry(getLockCallback, retryWait, retryCount);
|
int iterations = doWithRetry(getLockCallback, retryWait, retryCount);
|
||||||
// Bind in a listener
|
// Bind in a listener, if we are in a transaction
|
||||||
AlfrescoTransactionSupport.bindListener(txnListener);
|
if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE)
|
||||||
|
{
|
||||||
|
AlfrescoTransactionSupport.bindListener(txnListener);
|
||||||
|
}
|
||||||
// Success
|
// Success
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isDebugEnabled())
|
||||||
{
|
{
|
||||||
|
@@ -41,12 +41,18 @@
|
|||||||
|
|
||||||
<!-- override content store cleaner to use tenant routing file content store -->
|
<!-- override content store cleaner to use tenant routing file content store -->
|
||||||
<!-- Performs the content cleanup -->
|
<!-- Performs the content cleanup -->
|
||||||
<bean id="contentStoreCleaner" parent="baseContentStoreCleaner">
|
<bean id="eagerContentStoreCleaner" class="org.alfresco.repo.content.cleanup.EagerContentStoreCleaner" init-method="init">
|
||||||
|
<property name="eagerOrphanCleanup" >
|
||||||
|
<value>${system.content.eagerOrphanCleanup}</value>
|
||||||
|
</property>
|
||||||
<property name="stores" >
|
<property name="stores" >
|
||||||
<list>
|
<list>
|
||||||
<ref bean="tenantFileContentStore" />
|
<ref bean="tenantFileContentStore" />
|
||||||
</list>
|
</list>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="listeners" >
|
||||||
|
<ref bean="deletedContentBackupListeners" />
|
||||||
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<!-- override content service to use tenant routing file content store -->
|
<!-- override content service to use tenant routing file content store -->
|
||||||
|
Reference in New Issue
Block a user