diff --git a/.externalToolBuilders/JibX.launch b/.externalToolBuilders/JibX.launch index a5578dd881..24b9b9b84e 100644 --- a/.externalToolBuilders/JibX.launch +++ b/.externalToolBuilders/JibX.launch @@ -21,11 +21,12 @@ - + + diff --git a/config/alfresco/authentication-services-context.xml b/config/alfresco/authentication-services-context.xml index 846c2642b6..19c1d3e0a4 100644 --- a/config/alfresco/authentication-services-context.xml +++ b/config/alfresco/authentication-services-context.xml @@ -208,6 +208,20 @@ --> + + + + + + + + + + + + + + @@ -242,6 +256,9 @@ + + + diff --git a/config/alfresco/avm-services-context.xml b/config/alfresco/avm-services-context.xml index e1ef29c112..518779a809 100644 --- a/config/alfresco/avm-services-context.xml +++ b/config/alfresco/avm-services-context.xml @@ -91,12 +91,6 @@ - - - - - - @@ -106,12 +100,6 @@ - - - - - - @@ -149,15 +137,9 @@ - - - - - - diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 5a1a605d7a..2886791b52 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -64,15 +64,23 @@ + + + + + + @@ -518,8 +526,6 @@ - - @@ -529,15 +535,6 @@ - - - - - - - - - @@ -578,5 +575,22 @@ ${spaces.store} /${spaces.company_home.childname} - + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/bootstrap/system.xml b/config/alfresco/bootstrap/system.xml index 018740746d..774b0c54d5 100644 --- a/config/alfresco/bootstrap/system.xml +++ b/config/alfresco/bootstrap/system.xml @@ -31,7 +31,7 @@ - + ${alfresco_user_store.adminusername} Administrator @@ -41,7 +41,7 @@ bootstrapHomeFolderProvider - + guest diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml index d3036e2a74..99d52afe19 100644 --- a/config/alfresco/cache-context.xml +++ b/config/alfresco/cache-context.xml @@ -41,9 +41,6 @@ defaultCache - @@ -66,6 +63,78 @@ + + + + + + + + + + + + + + defaultCache + + + + + + + + + + + + + + + + org.alfresco.cache.localeEntityTransactionalCache + + + 100 + + + + + + + + + + + + + + + + + org.alfresco.cache.storeAndNodeIdCache + + + + + + + + + + + + + + + + org.alfresco.storeAndNodeIdTransactionalCache + + + 500 + + + diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 9063e686b5..210b6e07d5 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -1102,22 +1102,6 @@ - - - - - - - - - - - - - - - - diff --git a/config/alfresco/dbscripts/create/2.2/org.hibernate.dialect.Dialect/AlfrescoPostCreate-2.2-Extra.sql b/config/alfresco/dbscripts/create/2.2/org.hibernate.dialect.Dialect/AlfrescoPostCreate-2.2-Extra.sql index a188f1d2f4..4d4ceb5396 100644 --- a/config/alfresco/dbscripts/create/2.2/org.hibernate.dialect.Dialect/AlfrescoPostCreate-2.2-Extra.sql +++ b/config/alfresco/dbscripts/create/2.2/org.hibernate.dialect.Dialect/AlfrescoPostCreate-2.2-Extra.sql @@ -15,17 +15,20 @@ -- Explicit indexes and constraints not declared in the mappings -- -CREATE INDEX fk_alf_na_qn ON alf_node_aspects (qname_id); -ALTER TABLE alf_node_aspects ADD CONSTRAINT fk_alf_na_qn FOREIGN KEY (qname_id) REFERENCES alf_qname (id); +CREATE INDEX fk_alf_nasp_qn ON alf_node_aspects (qname_id); +ALTER TABLE alf_node_aspects ADD CONSTRAINT fk_alf_nasp_qn FOREIGN KEY (qname_id) REFERENCES alf_qname (id); -CREATE INDEX fk_alf_np_qn ON alf_node_properties (qname_id); -ALTER TABLE alf_node_properties ADD CONSTRAINT fk_alf_np_qn FOREIGN KEY (qname_id) REFERENCES alf_qname (id); +CREATE INDEX fk_alf_nprop_qn ON alf_node_properties (qname_id); +ALTER TABLE alf_node_properties ADD CONSTRAINT fk_alf_nprop_qn FOREIGN KEY (qname_id) REFERENCES alf_qname (id); -CREATE INDEX fk_avm_na_qn ON avm_aspects_new (qname_id); -ALTER TABLE avm_aspects_new ADD CONSTRAINT fk_avm_na_qn FOREIGN KEY (qname_id) REFERENCES alf_qname (id); +CREATE INDEX fk_alf_nprop_loc ON alf_node_properties (locale_id); +ALTER TABLE alf_node_properties ADD CONSTRAINT fk_alf_nprop_loc FOREIGN KEY (locale_id) REFERENCES alf_locale (id); -CREATE INDEX fk_avm_np_qn ON avm_node_properties_new (qname_id); -ALTER TABLE avm_node_properties_new ADD CONSTRAINT fk_avm_np_qn FOREIGN KEY (qname_id) REFERENCES alf_qname (id); +CREATE INDEX fk_avm_nasp_qn ON avm_aspects (qname_id); +ALTER TABLE avm_aspects ADD CONSTRAINT fk_avm_nasp_qn FOREIGN KEY (qname_id) REFERENCES alf_qname (id); + +CREATE INDEX fk_avm_nprop_qn ON avm_node_properties (qname_id); +ALTER TABLE avm_node_properties ADD CONSTRAINT fk_avm_nprop_qn FOREIGN KEY (qname_id) REFERENCES alf_qname (id); CREATE INDEX idx_avm_hl_revpk ON avm_history_links (descendent, ancestor); diff --git a/config/alfresco/dbscripts/create/2.2/org.hibernate.dialect.Dialect/AlfrescoPostCreate-2.2-MappedFKIndexes.sql b/config/alfresco/dbscripts/create/2.2/org.hibernate.dialect.Dialect/AlfrescoPostCreate-2.2-MappedFKIndexes.sql index 86cd9ed914..e70b358cd9 100644 --- a/config/alfresco/dbscripts/create/2.2/org.hibernate.dialect.Dialect/AlfrescoPostCreate-2.2-MappedFKIndexes.sql +++ b/config/alfresco/dbscripts/create/2.2/org.hibernate.dialect.Dialect/AlfrescoPostCreate-2.2-MappedFKIndexes.sql @@ -54,8 +54,7 @@ CREATE INDEX fk_alf_na_tnode ON alf_node_assoc (target_node_id); CREATE INDEX fk_alf_perm_tqn ON alf_permission (type_qname_id); -CREATE INDEX fk_alf_n_prop ON alf_node_properties (node_id); -CREATE INDEX fk_alf_np_attr ON alf_node_properties (attribute_value); +CREATE INDEX fk_alf_nprop_n ON alf_node_properties (node_id); CREATE INDEX fk_alf_ns_node ON alf_node_status (node_id); CREATE INDEX fk_alf_ns_trans ON alf_node_status (transaction_id); diff --git a/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Dialect/AlfrescoSchemaUpdate-OrclBLOB.sql b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Dialect/AlfrescoSchemaUpdate-OrclBLOB.sql new file mode 100644 index 0000000000..0e0ef4983a --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Dialect/AlfrescoSchemaUpdate-OrclBLOB.sql @@ -0,0 +1,21 @@ +-- +-- Title: Change Oracle LONG RAW columns to BLOB +-- Database: Generic +-- Since: V2.2 Schema 92 +-- Author: Derek Hulley +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- +-- No effect on non-Oracle DBs + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V2.2-OrclBLOB'; +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-V2.2-OrclBLOB', 'Did nothing for non-Oracle DB.', + 0, 91, -1, 92, null, 'UNKOWN', 1, 1, 'Script completed' + ); \ No newline at end of file diff --git a/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Dialect/AlfrescoSchemaUpdate-Person.sql b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Dialect/AlfrescoSchemaUpdate-Person.sql new file mode 100644 index 0000000000..ee3adbd85f --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Dialect/AlfrescoSchemaUpdate-Person.sql @@ -0,0 +1,65 @@ +-- +-- Title: Move user name to be part of the association QNAME +-- Database: Generic +-- Since: V2.2 Schema 91 +-- Author: Andy Hind +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- +-- Path was previously unused and unindex - new we use it the index is required. + +UPDATE + alf_child_assoc c + SET + c.qname_ns_id = + ( + SELECT + id + FROM + alf_namespace n + WHERE + n.uri = 'http://www.alfresco.org/model/content/1.0'), + c.qname_localname = + ( + SELECT + p.string_value + FROM + alf_node_properties p + JOIN alf_qname q on p.qname_id = q.id + JOIN alf_namespace n on q.ns_id = n.id + WHERE + p.node_id = c.child_node_id AND + q.local_name ='userName' AND n.uri = 'http://www.alfresco.org/model/content/1.0' + ) + WHERE exists + ( + SELECT + 0 + FROM alf_node_properties pp + JOIN alf_qname qq on pp.qname_id = qq.id + JOIN alf_namespace nn on qq.ns_id = nn.id + WHERE + pp.node_id = c.child_node_id AND + qq.local_name ='userName' AND + nn.uri = 'http://www.alfresco.org/model/content/1.0' + ) +; + +-- Validation query +-- select count(*) from alf_child_assoc c +-- JOIN alf_node_properties pp ON c.child_node_id = pp.node_id AND c.qname_localname = pp.string_value +-- JOIN alf_qname qq on pp.qname_id = qq.id +-- JOIN alf_namespace nn on qq.ns_id = nn.id AND c.qname_ns_id = nn.id +-- WHERE qq.local_name ='userName' AND nn.uri = 'http://www.alfresco.org/model/content/1.0' + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V2.2-Person'; +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-V2.2-Person', 'Manually executed script upgrade V2.2: Person user name also in the association qname', + 0, 90, -1, 91, null, 'UNKOWN', 1, 1, 'Script completed' + ); diff --git a/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Dialect/upgrade-4-extra-indexes-and-constraints.sql b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Dialect/upgrade-4-extra-indexes-and-constraints.sql index 38ccfa48d3..904b865aaa 100644 --- a/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Dialect/upgrade-4-extra-indexes-and-constraints.sql +++ b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Dialect/upgrade-4-extra-indexes-and-constraints.sql @@ -57,8 +57,11 @@ CREATE INDEX idx_alf_acl_inh ON alf_access_control_list (inherits, inherits_from CREATE INDEX fk_alf_na_qn ON alf_node_aspects (qname_id); ALTER TABLE alf_node_aspects ADD CONSTRAINT fk_alf_na_qn FOREIGN KEY (qname_id) REFERENCES alf_qname (id); -CREATE INDEX fk_alf_np_qn ON alf_node_properties (qname_id); -ALTER TABLE alf_node_properties ADD CONSTRAINT fk_alf_np_qn FOREIGN KEY (qname_id) REFERENCES alf_qname (id); +-- alf_node_properties is fully rebuilt in another script +-- CREATE INDEX fk_alf_nprop_qn ON alf_node_properties (qname_id); +-- ALTER TABLE alf_node_properties ADD CONSTRAINT fk_alf_nprop_qn FOREIGN KEY (qname_id) REFERENCES alf_qname (id); +-- CREATE INDEX fk_alf_nprop_loc ON alf_node_properties (locale_id); +-- ALTER TABLE alf_node_properties ADD CONSTRAINT fk_alf_nprop_loc FOREIGN KEY (locale_id) REFERENCES alf_locale (id); CREATE INDEX fk_avm_na_qn ON avm_aspects_new (qname_id); ALTER TABLE avm_aspects_new ADD CONSTRAINT fk_avm_na_qn FOREIGN KEY (qname_id) REFERENCES alf_qname (id); diff --git a/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Dialect/upgrade-5-mapped-fk-indexes.sql b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Dialect/upgrade-5-mapped-fk-indexes.sql index 96c3f249f8..41e0b40b8f 100644 --- a/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Dialect/upgrade-5-mapped-fk-indexes.sql +++ b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Dialect/upgrade-5-mapped-fk-indexes.sql @@ -54,8 +54,8 @@ CREATE INDEX fk_alf_na_tnode ON alf_node_assoc (target_node_id); CREATE INDEX fk_alf_perm_tqn ON alf_permission (type_qname_id); -CREATE INDEX fk_alf_n_prop ON alf_node_properties (node_id); -CREATE INDEX fk_alf_np_attr ON alf_node_properties (attribute_value); +-- alf_node_properties is fully rebuilt in another script +-- CREATE INDEX fk_alf_nprop_n ON alf_node_properties (node_id); CREATE INDEX fk_alf_ns_node ON alf_node_status (node_id); CREATE INDEX fk_alf_ns_trans ON alf_node_status (transaction_id); diff --git a/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoSchemaUpdate-2.2-ACL.sql b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoSchemaUpdate-2.2-ACL.sql index 4fea339df4..3fdb3faa33 100644 --- a/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoSchemaUpdate-2.2-ACL.sql +++ b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoSchemaUpdate-2.2-ACL.sql @@ -112,17 +112,18 @@ ALTER TABLE alf_authority_alias ADD CONSTRAINT fk_alf_autha_aut FOREIGN KEY (aut -- Tidy up unused cols on ace table and add the FK contstraint back -- finish take out of ACL_ID -ALTER TABLE alf_access_control_entry DROP INDEX FKFFF41F99B9553F6C, DROP FOREIGN KEY FKFFF41F99B9553F6C; -ALTER TABLE alf_access_control_entry DROP INDEX FKFFF41F9960601995, DROP FOREIGN KEY FKFFF41F9960601995; -ALTER TABLE alf_access_control_entry DROP COLUMN acl_id, DROP COLUMN authority_id; ALTER TABLE alf_access_control_entry - CHANGE auth_id authority_id BIGINT NOT NULL; -CREATE INDEX fk_alf_ace_auth ON alf_access_control_entry (authority_id); -ALTER TABLE alf_access_control_entry ADD CONSTRAINT fk_alf_ace_auth FOREIGN KEY (authority_id) REFERENCES alf_authority (id); -CREATE INDEX fk_alf_ace_perm ON alf_access_control_entry (permission_id); -ALTER TABLE alf_access_control_entry ADD CONSTRAINT fk_alf_ace_perm FOREIGN KEY (permission_id) REFERENCES alf_permission (id); -CREATE INDEX fk_alf_ace_ctx ON alf_access_control_entry (context_id); -ALTER TABLE alf_access_control_entry ADD CONSTRAINT fk_alf_ace_ctx FOREIGN KEY (context_id) REFERENCES alf_ace_context (id); + DROP INDEX FKFFF41F99B9553F6C, DROP FOREIGN KEY FKFFF41F99B9553F6C, + DROP INDEX FKFFF41F9960601995, DROP FOREIGN KEY FKFFF41F9960601995, + DROP COLUMN acl_id, DROP COLUMN authority_id, + CHANGE auth_id authority_id BIGINT NOT NULL, + ADD INDEX fk_alf_ace_auth (authority_id), + ADD CONSTRAINT fk_alf_ace_auth FOREIGN KEY (authority_id) REFERENCES alf_authority (id), + ADD INDEX fk_alf_ace_perm (permission_id), + ADD CONSTRAINT fk_alf_ace_perm FOREIGN KEY (permission_id) REFERENCES alf_permission (id), + ADD INDEX fk_alf_ace_ctx (context_id), + ADD CONSTRAINT fk_alf_ace_ctx FOREIGN KEY (context_id) REFERENCES alf_ace_context (id) +; CREATE TABLE alf_tmp_min_ace ( @@ -135,7 +136,17 @@ CREATE TABLE alf_tmp_min_ace ( ) ENGINE=InnoDB; INSERT INTO alf_tmp_min_ace (min, permission_id, authority_id, allowed, applies) - SELECT min(ace1.id), ace1.permission_id, ace1.authority_id, ace1.allowed, ace1.applies FROM alf_access_control_entry ace1 group by ace1.permission_id, ace1.authority_id, ace1.allowed, ace1.applies; + SELECT + min(ace1.id), + ace1.permission_id, + ace1.authority_id, + ace1.allowed, + ace1.applies + FROM + alf_access_control_entry ace1 + GROUP BY + ace1.permission_id, ace1.authority_id, ace1.allowed, ace1.applies +; -- Update members to point to the first use of an access control entry diff --git a/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.MySQLInnoDBDialect/upgrade-1-fulldm.sql b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.MySQLInnoDBDialect/upgrade-1-fulldm.sql new file mode 100644 index 0000000000..3296e91af0 --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.MySQLInnoDBDialect/upgrade-1-fulldm.sql @@ -0,0 +1,815 @@ +-- +-- Title: Apply all DM schema modifications +-- Database: MySQL +-- Since: V2.2 Schema 91 +-- Author: Derek Hulley +-- +-- In order to streamline the upgrade, all modifications to large tables need to +-- be handled in as few steps as possible. This usually involves as few ALTER TABLE +-- statements as possible. The general approach is: +-- Create a table with the correct structure, including indexes and CONSTRAINTs +-- Copy pristine data into the new table +-- Drop the old table +-- Rename the new table +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +--------------------------------- +-- Build Namespaces and QNames -- +--------------------------------- + +CREATE TABLE alf_namespace +( + id BIGINT NOT NULL AUTO_INCREMENT, + version BIGINT NOT NULL, + uri VARCHAR(100) NOT NULL, + PRIMARY KEY (id), + UNIQUE (uri) +) ENGINE=InnoDB; + +CREATE TABLE alf_qname +( + id BIGINT NOT NULL AUTO_INCREMENT, + version BIGINT NOT NULL, + ns_id BIGINT NOT NULL, + local_name VARCHAR(200) NOT NULL, + INDEX fk_alf_qname_ns (ns_id), + CONSTRAINT fk_alf_qname_ns FOREIGN KEY (ns_id) REFERENCES alf_namespace (id), + PRIMARY KEY (id), + UNIQUE (ns_id, local_name) +) ENGINE=InnoDB; + +-- Create temporary table for dynamic (child) QNames +CREATE TABLE t_qnames_dyn +( + qname VARCHAR(100) NOT NULL, + namespace VARCHAR(100), + namespace_id BIGINT, + local_name VARCHAR(100), + INDEX tidx_qnd_qn (qname), + INDEX tidx_qnd_ns (namespace) +) ENGINE=InnoDB; + +-- Populate the table with the child association paths +-- Query OK, 415312 rows affected (1 min 11.91 sec) +INSERT INTO t_qnames_dyn (qname) +( + SELECT distinct(qname) FROM alf_child_assoc +); + +-- Extract the Namespace +-- Query OK, 415312 rows affected (20.03 sec) +UPDATE t_qnames_dyn SET namespace = CONCAT('FILLER-', SUBSTR(SUBSTRING_INDEX(qname, '}', 1), 2)); + +-- Extract the Localname +-- Query OK, 415312 rows affected (16.22 sec) +UPDATE t_qnames_dyn SET local_name = SUBSTRING_INDEX(qname, '}', -1); + +-- Move the namespaces to the their new home +-- Query OK, 4 rows affected (34.59 sec) +INSERT INTO alf_namespace (uri, version) +( + SELECT + distinct(x.namespace), 1 + FROM + ( + SELECT t.namespace, n.uri FROM t_qnames_dyn t LEFT OUTER JOIN alf_namespace n ON (n.uri = t.namespace) + ) x + WHERE + x.uri IS NULL +); + +-- Record the new namespace IDs +-- Query OK, 415312 rows affected (10.41 sec) +UPDATE t_qnames_dyn t SET t.namespace_id = (SELECT ns.id FROM alf_namespace ns WHERE ns.uri = t.namespace); + +-- Recoup some storage +ALTER TABLE t_qnames_dyn DROP COLUMN namespace; +OPTIMIZE TABLE t_qnames_dyn; + +-- Create temporary table to hold static QNames +CREATE TABLE t_qnames +( + qname VARCHAR(200) NOT NULL, + namespace VARCHAR(100), + localname VARCHAR(100), + qname_id BIGINT, + INDEX tidx_tqn_qn (qname), + INDEX tidx_tqn_ns (namespace), + INDEX tidx_tqn_ln (localname) +) ENGINE=InnoDB; + +-- Populate the table with all known static QNames +INSERT INTO t_qnames (qname) +( + SELECT DISTINCT s.type_qname FROM alf_node s LEFT OUTER JOIN t_qnames t ON (s.type_qname = t.qname) WHERE t.qname IS NULL +); +INSERT INTO t_qnames (qname) +( + SELECT DISTINCT s.qname FROM alf_node_aspects s LEFT OUTER JOIN t_qnames t ON (s.qname = t.qname) WHERE t.qname IS NULL +); +INSERT INTO t_qnames (qname) +( + SELECT DISTINCT s.qname FROM alf_node_properties s LEFT OUTER JOIN t_qnames t ON (s.qname = t.qname) WHERE t.qname IS NULL +); +INSERT INTO t_qnames (qname) +( + SELECT DISTINCT s.qname FROM avm_aspects s LEFT OUTER JOIN t_qnames t ON (s.qname = t.qname) WHERE t.qname IS NULL +); +INSERT INTO t_qnames (qname) +( + SELECT DISTINCT s.name FROM avm_aspects_new s LEFT OUTER JOIN t_qnames t ON (s.name = t.qname) WHERE t.qname IS NULL +); +INSERT INTO t_qnames (qname) +( + SELECT DISTINCT s.qname FROM avm_node_properties s LEFT OUTER JOIN t_qnames t ON (s.qname = t.qname) WHERE t.qname IS NULL +); +INSERT INTO t_qnames (qname) +( + SELECT DISTINCT s.qname FROM avm_node_properties_new s LEFT OUTER JOIN t_qnames t ON (s.qname = t.qname) WHERE t.qname IS NULL +); +INSERT INTO t_qnames (qname) +( + SELECT DISTINCT s.qname FROM avm_store_properties s LEFT OUTER JOIN t_qnames t ON (s.qname = t.qname) WHERE t.qname IS NULL +); +INSERT INTO t_qnames (qname) +( + SELECT DISTINCT s.type_qname FROM alf_node_assoc s LEFT OUTER JOIN t_qnames t ON (s.type_qname = t.qname) WHERE t.qname IS NULL +); +INSERT INTO t_qnames (qname) +( + SELECT DISTINCT s.type_qname FROM alf_child_assoc s LEFT OUTER JOIN t_qnames t ON (s.type_qname = t.qname) WHERE t.qname IS NULL +); +INSERT INTO t_qnames (qname) +( + SELECT DISTINCT s.type_qname FROM alf_permission s LEFT OUTER JOIN t_qnames t ON (s.type_qname = t.qname) WHERE t.qname IS NULL +); + +-- Extract the namespace and localnames from the QNames +UPDATE t_qnames SET namespace = CONCAT('FILLER-', SUBSTR(SUBSTRING_INDEX(qname, '}', 1), 2)); +UPDATE t_qnames SET localname = SUBSTRING_INDEX(qname, '}', -1); + +-- Move the Namespaces to their new home +INSERT INTO alf_namespace (uri, version) +( + SELECT + distinct(x.namespace), 1 + FROM + ( + SELECT t.namespace, n.uri FROM t_qnames t LEFT OUTER JOIN alf_namespace n ON (n.uri = t.namespace) + ) x + WHERE + x.uri IS NULL +); + +-- Move the Localnames to their new home +INSERT INTO alf_qname (ns_id, local_name, version) +( + SELECT + x.ns_id, x.t_localname, 1 + FROM + ( + SELECT n.id AS ns_id, t.localname AS t_localname, q.local_name AS q_localname + FROM t_qnames t + JOIN alf_namespace n ON (n.uri = t.namespace) + LEFT OUTER JOIN alf_qname q ON (q.local_name = t.localname) + ) x + WHERE + q_localname IS NULL + GROUP BY x.ns_id, x.t_localname +); + +-- Record the new qname IDs +UPDATE t_qnames t SET t.qname_id = +( + SELECT q.id FROM alf_qname q + JOIN alf_namespace ns ON (q.ns_id = ns.id) + WHERE ns.uri = t.namespace AND q.local_name = t.localname +); + +------------------------------ +-- Populate the Permissions -- +------------------------------ + +-- This is a small table so we change it in place +ALTER TABLE alf_permission DROP INDEX type_qname; +ALTER TABLE alf_permission ADD COLUMN type_qname_id BIGINT NULL AFTER id; +UPDATE alf_permission p SET p.type_qname_id = +( + SELECT q.id + FROM alf_qname q + JOIN alf_namespace ns ON (q.ns_id = ns.id) + WHERE CONCAT('{', SUBSTR(ns.uri, 8), '}', q.local_name) = p.type_qname +); +ALTER TABLE alf_permission DROP COLUMN type_qname; +ALTER TABLE alf_permission MODIFY COLUMN type_qname_id BIGINT NOT NULL AFTER id; +ALTER TABLE alf_permission ADD UNIQUE (type_qname_id, name); + +--------------------- +-- Build new Store -- +--------------------- + +CREATE TABLE t_alf_store +( + id BIGINT NOT NULL AUTO_INCREMENT, + version BIGINT NOT NULL, + protocol VARCHAR(50) NOT NULL, + identifier VARCHAR(100) NOT NULL, + root_node_id BIGINT, + PRIMARY KEY (id), + UNIQUE (protocol, identifier) +) TYPE=InnoDB; + +CREATE TABLE t_alf_node ( + id BIGINT NOT NULL AUTO_INCREMENT, + version BIGINT NOT NULL, + store_id BIGINT NOT NULL, + uuid VARCHAR(36) NOT NULL, + transaction_id BIGINT NOT NULL, + node_deleted bit NOT NULL, + type_qname_id BIGINT NOT NULL, + acl_id BIGINT, + audit_creator VARCHAR(255) NOT NULL, + audit_created VARCHAR(30) NOT NULL, + audit_modifier VARCHAR(255) NOT NULL, + audit_modified VARCHAR(30) NOT NULL, + audit_accessed VARCHAR(30), + INDEX idx_alf_node_del (node_deleted), + INDEX fk_alf_node_acl (acl_id), + INDEX fk_alf_node_tqn (type_qname_id), + INDEX fk_alf_node_txn (transaction_id), + INDEX fk_alf_node_store (store_id), + CONSTRAINT fk_alf_node_acl FOREIGN KEY (acl_id) REFERENCES alf_access_control_list (id), + CONSTRAINT fk_alf_node_tqn FOREIGN KEY (type_qname_id) REFERENCES alf_qname (id), + CONSTRAINT fk_alf_node_txn FOREIGN KEY (transaction_id) REFERENCES alf_transaction (id), + CONSTRAINT fk_alf_node_store FOREIGN KEY (store_id) REFERENCES t_alf_store (id), + PRIMARY KEY (id), + UNIQUE (store_id, uuid) +) TYPE=InnoDB; + +-- Fill the store table +INSERT INTO t_alf_store (version, protocol, identifier, root_node_id) + SELECT 1, protocol, identifier, root_node_id FROM alf_store +; + +---------------------------- +-- Populate the new nodes -- +---------------------------- + +-- Query OK, 830222 rows affected (2 min 18.96 sec) +INSERT INTO t_alf_node + ( + id, version, store_id, uuid, transaction_id, node_deleted, type_qname_id, + audit_creator, audit_created, audit_modifier, audit_modified + ) + SELECT + n.id, 1, s.id, n.uuid, nstat.transaction_id, false, q.qname_id, + 'unknown', '2008-09-17T02:23:37.212+01:00', 'unkown', '2008-09-17T02:23:37.212+01:00' + FROM + t_qnames q + JOIN alf_node n ON (q.qname = n.type_qname) + JOIN alf_node_status nstat ON (nstat.node_id = n.id) + JOIN t_alf_store s ON (s.protocol = nstat.protocol AND s.identifier = nstat.identifier) +; + +-- Hook the store up to the root node +ALTER TABLE t_alf_store + ADD INDEX fk_alf_store_root (root_node_id), + ADD CONSTRAINT fk_alf_store_root FOREIGN KEY (root_node_id) REFERENCES t_alf_node (id) +; + +------------------------------- +-- Populate the Child Assocs -- +------------------------------- + +CREATE TABLE t_alf_child_assoc +( + id BIGINT NOT NULL AUTO_INCREMENT, + version BIGINT NOT NULL, + parent_node_id BIGINT NOT NULL, + type_qname_id BIGINT NOT NULL, + child_node_name VARCHAR(50) NOT NULL, + child_node_name_crc BIGINT NOT NULL, + child_node_id BIGINT NOT NULL, + qname_ns_id BIGINT NOT NULL, + qname_localname VARCHAR(100) NOT NULL, + is_primary BIT, + assoc_index INTEGER, + INDEX idx_alf_cass_qnln (qname_localname), + INDEX fk_alf_cass_pnode (parent_node_id), + INDEX fk_alf_cass_cnode (child_node_id), + INDEX fk_alf_cass_tqn (type_qname_id), + INDEX fk_alf_cass_qnns (qname_ns_id), + CONSTRAINT fk_alf_cass_pnode foreign key (parent_node_id) REFERENCES t_alf_node (id), + CONSTRAINT fk_alf_cass_cnode foreign key (child_node_id) REFERENCES t_alf_node (id), + CONSTRAINT fk_alf_cass_tqn foreign key (type_qname_id) REFERENCES alf_qname (id), + CONSTRAINT fk_alf_cass_qnns foreign key (qname_ns_id) REFERENCES alf_namespace (id), + PRIMARY KEY (id), + UNIQUE (parent_node_id, type_qname_id, child_node_name, child_node_name_crc) +) TYPE=InnoDB; + +-- Query OK, 830217 rows affected (11 min 59.10 sec) +INSERT INTO t_alf_child_assoc + ( + id, version, + parent_node_id, child_node_id, + child_node_name, child_node_name_crc, + type_qname_id, + qname_ns_id, qname_localname, + is_primary, assoc_index + ) + SELECT + ca.id, 1, + ca.parent_node_id, ca.child_node_id, + ca.child_node_name, child_node_name_crc, + tqn.qname_id, + tqndyn.namespace_id, tqndyn.local_name, + ca.is_primary, ca.assoc_index + FROM + alf_child_assoc ca + JOIN t_qnames_dyn tqndyn ON (ca.qname = tqndyn.qname) + JOIN t_qnames tqn ON (ca.type_qname = tqn.qname) +; + +-- Clean up +DROP TABLE t_qnames_dyn; +DROP TABLE alf_child_assoc; +ALTER TABLE t_alf_child_assoc RENAME TO alf_child_assoc; + +------------------------------ +-- Populate the Node Assocs -- +------------------------------ + +CREATE TABLE t_alf_node_assoc +( + id BIGINT NOT NULL AUTO_INCREMENT, + version BIGINT NOT NULL, + source_node_id BIGINT NOT NULL, + target_node_id BIGINT NOT NULL, + type_qname_id BIGINT NOT NULL, + INDEX fk_alf_nass_snode (source_node_id), + INDEX fk_alf_nass_tnode (target_node_id), + INDEX fk_alf_nass_tqn (type_qname_id), + CONSTRAINT fk_alf_nass_snode FOREIGN KEY (source_node_id) REFERENCES t_alf_node (id), + CONSTRAINT fk_alf_nass_tnode FOREIGN KEY (target_node_id) REFERENCES t_alf_node (id), + CONSTRAINT fk_alf_nass_tqn FOREIGN KEY (type_qname_id) REFERENCES alf_qname (id), + PRIMARY KEY (id), + UNIQUE (source_node_id, target_node_id, type_qname_id) +) TYPE=InnoDB; + +INSERT INTO t_alf_node_assoc + ( + id, version, + source_node_id, target_node_id, + type_qname_id + ) + SELECT + na.id, 1, + na.source_node_id, na.source_node_id, + tqn.qname_id + FROM + alf_node_assoc na + JOIN t_qnames tqn ON (na.type_qname = tqn.qname) +; + +-- Clean up +DROP TABLE alf_node_assoc; +ALTER TABLE t_alf_node_assoc RENAME TO alf_node_assoc; + +------------------------------- +-- Populate the Node Aspects -- +------------------------------- + +CREATE TABLE t_alf_node_aspects +( + node_id BIGINT NOT NULL, + qname_id BIGINT NOT NULL, + INDEX fk_alf_nasp_n (node_id), + INDEX fk_alf_nasp_qn (qname_id), + CONSTRAINT fk_alf_nasp_n FOREIGN KEY (node_id) REFERENCES t_alf_node (id), + CONSTRAINT fk_alf_nasp_qn FOREIGN KEY (qname_id) REFERENCES alf_qname (id), + PRIMARY KEY (node_id, qname_id) +) TYPE=InnoDB; + +-- Note the omission of sys:referencable and cm:auditable. These are implicit. +-- Query OK, 415051 rows affected (17.59 sec) +INSERT INTO t_alf_node_aspects + ( + node_id, qname_id + ) + SELECT + na.node_id, + tqn.qname_id + FROM + alf_node_aspects na + JOIN t_qnames tqn ON (na.qname = tqn.qname) + WHERE + tqn.qname NOT IN + ( + '{http://www.alfresco.org/model/system/1.0}referenceable', + '{http://www.alfresco.org/model/content/1.0}auditable' + ) +; + +-- Clean up +DROP TABLE alf_node_aspects; +ALTER TABLE t_alf_node_aspects RENAME TO alf_node_aspects; + +----------------------------------- +-- Populate the AVM Node Aspects -- +----------------------------------- + +CREATE TABLE t_avm_aspects +( + node_id BIGINT NOT NULL, + qname_id BIGINT NOT NULL, + INDEX fk_avm_nasp_n (node_id), + INDEX fk_avm_nasp_qn (qname_id), + CONSTRAINT fk_avm_nasp_n FOREIGN KEY (node_id) REFERENCES avm_nodes (id), + CONSTRAINT fk_avm_nasp_qn FOREIGN KEY (qname_id) REFERENCES alf_qname (id), + PRIMARY KEY (node_id, qname_id) +) TYPE=InnoDB; + +INSERT INTO t_avm_aspects + ( + node_id, qname_id + ) + SELECT + aspects_old.node_id, + tqn.qname_id + FROM + avm_aspects aspects_old + JOIN t_qnames tqn ON (aspects_old.qname = tqn.qname) +; +INSERT INTO t_avm_aspects + ( + node_id, qname_id + ) + SELECT + anew.id, + tqn.qname_id + FROM + avm_aspects_new anew + JOIN t_qnames tqn ON (anew.name = tqn.qname) + LEFT JOIN avm_aspects aold ON (anew.id = aold.node_id AND anew.name = aold.qname) + WHERE + aold.id IS NULL +; + +-- Clean up +DROP TABLE avm_aspects; +DROP TABLE avm_aspects_new; +ALTER TABLE t_avm_aspects RENAME TO avm_aspects; + +------------------------------------ +-- Migrate Sundry Property Tables -- +------------------------------------ + +-- Create temporary mapping for property types +CREATE TABLE t_prop_types +( + type_name VARCHAR(15) NOT NULL, + type_id INTEGER NOT NULL, + PRIMARY KEY (type_name) +); +INSERT INTO t_prop_types values ('NULL', 0); +INSERT INTO t_prop_types values ('BOOLEAN', 1); +INSERT INTO t_prop_types values ('INTEGER', 2); +INSERT INTO t_prop_types values ('LONG', 3); +INSERT INTO t_prop_types values ('FLOAT', 4); +INSERT INTO t_prop_types values ('DOUBLE', 5); +INSERT INTO t_prop_types values ('STRING', 6); +INSERT INTO t_prop_types values ('DATE', 7); +INSERT INTO t_prop_types values ('DB_ATTRIBUTE', 8); +INSERT INTO t_prop_types values ('SERIALIZABLE', 9); +INSERT INTO t_prop_types values ('MLTEXT', 10); +INSERT INTO t_prop_types values ('CONTENT', 11); +INSERT INTO t_prop_types values ('NODEREF', 12); +INSERT INTO t_prop_types values ('CHILD_ASSOC_REF', 13); +INSERT INTO t_prop_types values ('ASSOC_REF', 14); +INSERT INTO t_prop_types values ('QNAME', 15); +INSERT INTO t_prop_types values ('PATH', 16); +INSERT INTO t_prop_types values ('LOCALE', 17); +INSERT INTO t_prop_types values ('VERSION_NUMBER', 18); + +-- Modify the avm_node_properties_new table +CREATE TABLE t_avm_node_properties_new +( + node_id BIGINT NOT NULL, + actual_type_n INTEGER NOT NULL, + persisted_type_n INTEGER NOT NULL, + multi_valued BIT NOT NULL, + boolean_value BIT, + long_value BIGINT, + float_value FLOAT, + double_value DOUBLE PRECISION, + string_value TEXT, + serializable_value BLOB, + qname_id BIGINT NOT NULL, + INDEX fk_avm_nprop_n (node_id), + INDEX fk_avm_nprop_qn (qname_id), + CONSTRAINT fk_avm_nprop_n FOREIGN KEY (node_id) REFERENCES avm_nodes (id), + CONSTRAINT fk_avm_nprop_qn FOREIGN KEY (qname_id) REFERENCES alf_qname (id), + PRIMARY KEY (node_id, qname_id) +) TYPE=InnoDB; +INSERT INTO t_avm_node_properties_new + ( + node_id, + qname_id, + actual_type_n, persisted_type_n, + multi_valued, boolean_value, long_value, float_value, double_value, string_value, serializable_value + ) + SELECT + p.node_id, + tqn.qname_id, + ptypes_actual.type_id, ptypes_persisted.type_id, + p.multi_valued, p.boolean_value, p.long_value, p.float_value, p.double_value, p.string_value, p.serializable_value + FROM + avm_node_properties_new p + JOIN t_qnames tqn ON (p.qname = tqn.qname) + JOIN t_prop_types ptypes_actual ON (ptypes_actual.type_name = p.actual_type) + JOIN t_prop_types ptypes_persisted ON (ptypes_persisted.type_name = p.persisted_type) +; +DROP TABLE avm_node_properties_new; +ALTER TABLE t_avm_node_properties_new RENAME TO avm_node_properties_new; + +-- Modify the avm_store_properties table +CREATE TABLE t_avm_store_properties +( + id BIGINT NOT NULL AUTO_INCREMENT, + avm_store_id BIGINT, + qname_id BIGINT NOT NULL, + actual_type_n integer NOT NULL, + persisted_type_n integer NOT NULL, + multi_valued bit NOT NULL, + boolean_value bit, + long_value BIGINT, + float_value float, + double_value DOUBLE PRECISION, + string_value TEXT, + serializable_value blob, + INDEX fk_avm_sprop_store (avm_store_id), + INDEX fk_avm_sprop_qname (qname_id), + CONSTRAINT fk_avm_sprop_store FOREIGN KEY (avm_store_id) REFERENCES avm_stores (id), + CONSTRAINT fk_avm_sprop_qname FOREIGN KEY (qname_id) REFERENCES alf_qname (id), + PRIMARY KEY (id) +) TYPE=InnoDB; +INSERT INTO t_avm_store_properties + ( + avm_store_id, + qname_id, + actual_type_n, persisted_type_n, + multi_valued, boolean_value, long_value, float_value, double_value, string_value, serializable_value + ) + SELECT + p.avm_store_id, + tqn.qname_id, + ptypes_actual.type_id, ptypes_persisted.type_id, + p.multi_valued, p.boolean_value, p.long_value, p.float_value, p.double_value, p.string_value, p.serializable_value + FROM + avm_store_properties p + JOIN t_qnames tqn ON (p.qname = tqn.qname) + JOIN t_prop_types ptypes_actual ON (ptypes_actual.type_name = p.actual_type) + JOIN t_prop_types ptypes_persisted ON (ptypes_persisted.type_name = p.persisted_type) +; +DROP TABLE avm_store_properties; +ALTER TABLE t_avm_store_properties RENAME TO avm_store_properties; + +-- Modify the avm_node_properties table +-- This table is old, so the data will be extracte and it will be replaced +CREATE TABLE t_avm_node_properties +( + node_id BIGINT NOT NULL, + actual_type_n INTEGER NOT NULL, + persisted_type_n INTEGER NOT NULL, + multi_valued BIT NOT NULL, + boolean_value BIT, + long_value BIGINT, + float_value FLOAT, + double_value DOUBLE PRECISION, + string_value TEXT, + serializable_value BLOB, + qname_id BIGINT NOT NULL, + PRIMARY KEY (node_id, qname_id) +) TYPE=InnoDB; +INSERT INTO t_avm_node_properties + ( + node_id, + qname_id, + actual_type_n, persisted_type_n, + multi_valued, boolean_value, long_value, float_value, double_value, string_value, serializable_value + ) + SELECT + p.node_id, + tqn.qname_id, + ptypes_actual.type_id, ptypes_persisted.type_id, + p.multi_valued, p.boolean_value, p.long_value, p.float_value, p.double_value, p.string_value, p.serializable_value + FROM + avm_node_properties p + JOIN t_qnames tqn ON (p.qname = tqn.qname) + JOIN t_prop_types ptypes_actual ON (ptypes_actual.type_name = p.actual_type) + JOIN t_prop_types ptypes_persisted ON (ptypes_persisted.type_name = p.persisted_type) +; +-- Copy values to new table. Duplicates are avoided just in case. +INSERT INTO avm_node_properties_new + ( + node_id, + qname_id, + actual_type_n, persisted_type_n, + multi_valued, boolean_value, long_value, float_value, double_value, string_value, serializable_value + ) + SELECT + p.node_id, + p.qname_id, + p.actual_type_n, p.persisted_type_n, + p.multi_valued, p.boolean_value, p.long_value, p.float_value, p.double_value, p.string_value, p.serializable_value + FROM + t_avm_node_properties p + LEFT OUTER JOIN avm_node_properties_new pnew ON (pnew.node_id = p.node_id AND pnew.qname_id = p.qname_id) + WHERE + pnew.qname_id is null +; +DROP TABLE t_avm_node_properties; +DROP TABLE avm_node_properties; +ALTER TABLE avm_node_properties_new RENAME TO avm_node_properties; + + +------------------- +-- Build Locales -- +------------------- + +CREATE TABLE alf_locale +( + id BIGINT NOT NULL AUTO_INCREMENT, + version BIGINT NOT NULL DEFAULT 1, + locale_str VARCHAR(20) NOT NULL, + PRIMARY KEY (id), + UNIQUE (locale_str) +) TYPE=InnoDB; + +INSERT INTO alf_locale (id, locale_str) VALUES (1, '.default'); + +-- Locales come from the attribute table which was used to support MLText persistence +-- Query OK, 0 rows affected (17.22 sec) +INSERT INTO alf_locale (locale_str) + SELECT DISTINCT(ma.mkey) + FROM alf_node_properties np + JOIN alf_attributes a1 ON (np.attribute_value = a1.id) + JOIN alf_map_attribute_entries ma ON (ma.map_id = a1.id) +; + +--------------------------------- +-- Migrate ADM Property Tables -- +--------------------------------- + +CREATE TABLE t_alf_node_properties +( + node_id BIGINT NOT NULL, + qname_id BIGINT NOT NULL, + locale_id BIGINT NOT NULL, + list_index smallint NOT NULL, + actual_type_n INTEGER NOT NULL, + persisted_type_n INTEGER NOT NULL, + boolean_value BIT, + long_value BIGINT, + float_value FLOAT, + double_value DOUBLE PRECISION, + string_value TEXT, + serializable_value BLOB, + INDEX fk_alf_nprop_n (node_id), + INDEX fk_alf_nprop_qn (qname_id), + INDEX fk_alf_nprop_loc (locale_id), + CONSTRAINT fk_alf_nprop_n FOREIGN KEY (node_id) REFERENCES t_alf_node (id), + CONSTRAINT fk_alf_nprop_qn FOREIGN KEY (qname_id) REFERENCES alf_qname (id), + CONSTRAINT fk_alf_nprop_loc FOREIGN KEY (locale_id) REFERENCES alf_locale (id), + PRIMARY KEY (node_id, qname_id, list_index, locale_id) +) TYPE=InnoDB; + +-- Copy all simple values over +-- Query OK, 2905008 rows affected (7 min 11.49 sec) +INSERT INTO t_alf_node_properties + ( + node_id, qname_id, list_index, locale_id, + actual_type_n, persisted_type_n, + boolean_value, long_value, float_value, double_value, + string_value, + serializable_value + ) + SELECT + np.node_id, tqn.qname_id, -1, 1, + ptypes_actual.type_id, ptypes_persisted.type_id, + np.boolean_value, np.long_value, np.float_value, np.double_value, + np.string_value, + np.serializable_value + FROM + alf_node_properties np + JOIN t_qnames tqn ON (np.qname = tqn.qname) + JOIN t_prop_types ptypes_actual ON (ptypes_actual.type_name = np.actual_type) + JOIN t_prop_types ptypes_persisted ON (ptypes_persisted.type_name = np.persisted_type) + WHERE + np.attribute_value is null AND + tqn.qname NOT IN + ( + '{http://www.alfresco.org/model/content/1.0}created', + '{http://www.alfresco.org/model/content/1.0}creator', + '{http://www.alfresco.org/model/content/1.0}modified', + '{http://www.alfresco.org/model/content/1.0}modifier' + ) +; + +-- Copy all MLText values over +INSERT INTO t_alf_node_properties + ( + node_id, qname_id, list_index, locale_id, + actual_type_n, persisted_type_n, + boolean_value, long_value, float_value, double_value, + string_value, + serializable_value + ) + SELECT + np.node_id, tqn.qname_id, -1, loc.id, + -1, 0, + FALSE, 0, 0, 0, + a2.string_value, + a2.serializable_value + FROM + alf_node_properties np + JOIN t_qnames tqn ON (np.qname = tqn.qname) + JOIN alf_attributes a1 ON (np.attribute_value = a1.id) + JOIN alf_map_attribute_entries ma ON (ma.map_id = a1.id) + JOIN alf_locale loc ON (ma.mkey = loc.locale_str) + JOIN alf_attributes a2 ON (ma.attribute_id = a2.id) +; -- (OPTIONAL) +UPDATE t_alf_node_properties + SET actual_type_n = 6, persisted_type_n = 6, serializable_value = NULL + WHERE actual_type_n = -1 AND string_value IS NOT NULL +; +UPDATE t_alf_node_properties + SET actual_type_n = 9, persisted_type_n = 9 + WHERE actual_type_n = -1 AND serializable_value IS NOT NULL +; + +-- Delete the node properties and move the fixed values over +DROP TABLE alf_node_properties; +ALTER TABLE t_alf_node_properties RENAME TO alf_node_properties; + +CREATE TABLE t_del_attributes +( + id BIGINT NOT NULL, + PRIMARY KEY (id) +); +INSERT INTO t_del_attributes + SELECT id FROM alf_attributes WHERE type = 'M' +; +DELETE t_del_attributes + FROM t_del_attributes + JOIN alf_map_attribute_entries ma ON (ma.attribute_id = t_del_attributes.id) +; +DELETE t_del_attributes + FROM t_del_attributes + JOIN alf_list_attribute_entries la ON (la.attribute_id = t_del_attributes.id) +; +DELETE t_del_attributes + FROM t_del_attributes + JOIN alf_global_attributes ga ON (ga.attribute = t_del_attributes.id) +; +INSERT INTO t_del_attributes + SELECT a.id FROM t_del_attributes t + JOIN alf_map_attribute_entries ma ON (ma.map_id = t.id) + JOIN alf_attributes a ON (ma.attribute_id = a.id) +; +DELETE alf_map_attribute_entries + FROM alf_map_attribute_entries + JOIN t_del_attributes t ON (alf_map_attribute_entries.map_id = t.id) +; +DELETE alf_attributes + FROM alf_attributes + JOIN t_del_attributes t ON (alf_attributes.id = t.id) +; +DROP TABLE t_del_attributes; + +-------------------- +-- Final clean up -- +-------------------- +DROP TABLE t_qnames; +DROP TABLE t_prop_types; +DROP TABLE alf_node_status; +ALTER TABLE alf_store DROP INDEX FKBD4FF53D22DBA5BA, DROP FOREIGN KEY FKBD4FF53D22DBA5BA; -- (OPTIONAL) +ALTER TABLE alf_store DROP FOREIGN KEY alf_store_root; -- (OPTIONAL) +DROP TABLE alf_node; +ALTER TABLE t_alf_node RENAME TO alf_node; +DROP TABLE alf_store; +ALTER TABLE t_alf_store RENAME TO alf_store; + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V2.2-1-FullDmUpgrade'; +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-V2.2-1-FullDmUpgrade', 'Manually executed script upgrade V2.2: ADM ', + 0, 85, -1, 91, null, 'UNKOWN', 1, 1, 'Script completed' + ); diff --git a/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.MySQLInnoDBDialect/upgrade-1-mltext.sql b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.MySQLInnoDBDialect/upgrade-1-mltext.sql new file mode 100644 index 0000000000..3fd6b5e70b --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.MySQLInnoDBDialect/upgrade-1-mltext.sql @@ -0,0 +1,157 @@ +-- +-- Title: Pull MLText Values into Node Properties +-- Database: MySQL +-- Since: V2.2 Schema 91 +-- Author: Derek Hulley +-- +-- MLText values must be pulled back from attributes into localizable properties. +-- Several statements are not relevant to upgrades from below 77. These are optional. +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +CREATE TABLE alf_locale +( + id BIGINT NOT NULL AUTO_INCREMENT, + version BIGINT NOT NULL DEFAULT 1, + locale_str VARCHAR(20) NOT NULL, + PRIMARY KEY (id), + UNIQUE (locale_str) +) TYPE=InnoDB; + +INSERT INTO alf_locale (id, locale_str) VALUES (1, '.default'); + +INSERT INTO alf_locale (locale_str) + SELECT DISTINCT(ma.mkey) + FROM alf_node_properties np + JOIN alf_attributes a1 ON (np.attribute_value = a1.id) + JOIN alf_map_attribute_entries ma ON (ma.map_id = a1.id) +; -- (OPTIONAL) + +-- Create a temporary table to hold the attribute_value information that needs replacing +CREATE TABLE t_alf_node_properties +( + node_id BIGINT NOT NULL, + qname_id BIGINT NOT NULL, + list_index integer NOT NULL, + locale_id BIGINT NOT NULL, + actual_type_n integer NOT NULL, + persisted_type_n integer NOT NULL, + boolean_value BIT, + long_value BIGINT, + float_value FLOAT, + double_value DOUBLE PRECISION, + string_value TEXT, + serializable_value BLOB, + INDEX fk_alf_nprop_n (node_id), + CONSTRAINT fk_alf_nprop_n FOREIGN KEY (node_id) REFERENCES alf_node (id), + INDEX fk_alf_nprop_qn (qname_id), + CONSTRAINT fk_alf_nprop_qn FOREIGN KEY (qname_id) REFERENCES alf_qname (id), + INDEX fk_alf_nprop_loc (locale_id), + CONSTRAINT fk_alf_nprop_loc FOREIGN KEY (locale_id) REFERENCES alf_locale (id), + PRIMARY KEY (node_id, qname_id, list_index, locale_id) +) TYPE=InnoDB; + +-- Copy all simple values over +INSERT INTO t_alf_node_properties + ( + node_id, qname_id, list_index, locale_id, + actual_type_n, persisted_type_n, + boolean_value, long_value, float_value, double_value, + string_value, + serializable_value + ) + SELECT + np.node_id, np.qname_id, -1, 1, + np.actual_type_n, np.persisted_type_n, + np.boolean_value, np.long_value, np.float_value, np.double_value, + np.string_value, + np.serializable_value + FROM alf_node_properties np + WHERE + np.attribute_value is null +; + +-- Copy all MLText values over +INSERT INTO t_alf_node_properties + ( + node_id, qname_id, list_index, locale_id, + actual_type_n, persisted_type_n, + boolean_value, long_value, float_value, double_value, + string_value, + serializable_value + ) + SELECT + np.node_id, np.qname_id, -1, loc.id, + -1, 0, + FALSE, 0, 0, 0, + a2.string_value, + a2.serializable_value + FROM alf_node_properties np + JOIN alf_attributes a1 ON (np.attribute_value = a1.id) + JOIN alf_map_attribute_entries ma ON (ma.map_id = a1.id) + JOIN alf_locale loc ON (ma.mkey = loc.locale_str) + JOIN alf_attributes a2 ON (ma.attribute_id = a2.id) +; -- (OPTIONAL) +UPDATE t_alf_node_properties + SET actual_type_n = 6, persisted_type_n = 6, serializable_value = NULL + WHERE actual_type_n = -1 AND string_value IS NOT NULL +; +UPDATE t_alf_node_properties + SET actual_type_n = 9, persisted_type_n = 9 + WHERE actual_type_n = -1 AND serializable_value IS NOT NULL +; + +-- Delete the node properties and move the fixed values over +DROP TABLE alf_node_properties; +ALTER TABLE t_alf_node_properties RENAME TO alf_node_properties; + +-- Clean up unused attribute values + +CREATE TABLE t_del_attributes +( + id BIGINT NOT NULL, + PRIMARY KEY (id) +); +INSERT INTO t_del_attributes + SELECT id FROM alf_attributes WHERE type = 'M' +; +DELETE t_del_attributes + FROM t_del_attributes + JOIN alf_map_attribute_entries ma ON (ma.attribute_id = t_del_attributes.id) +; +DELETE t_del_attributes + FROM t_del_attributes + JOIN alf_list_attribute_entries la ON (la.attribute_id = t_del_attributes.id) +; +DELETE t_del_attributes + FROM t_del_attributes + JOIN alf_global_attributes ga ON (ga.attribute = t_del_attributes.id) +; +INSERT INTO t_del_attributes + SELECT a.id FROM t_del_attributes t + JOIN alf_map_attribute_entries ma ON (ma.map_id = t.id) + JOIN alf_attributes a ON (ma.attribute_id = a.id) +; +DELETE alf_map_attribute_entries + FROM alf_map_attribute_entries + JOIN t_del_attributes t ON (alf_map_attribute_entries.map_id = t.id) +; +DELETE alf_attributes + FROM alf_attributes + JOIN t_del_attributes t ON (alf_attributes.id = t.id) +; +DROP TABLE t_del_attributes; + + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V2.2-1-MLText'; +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-V2.2-1-MLText', 'Manually executed script upgrade V2.2: Moved MLText values', + 86, 90, -1, 91, null, 'UNKOWN', 1, 1, 'Script completed' + ); diff --git a/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Oracle9Dialect/AlfrescoSchemaUpdate-OrclBLOB.sql b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Oracle9Dialect/AlfrescoSchemaUpdate-OrclBLOB.sql new file mode 100644 index 0000000000..556d918402 --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Oracle9Dialect/AlfrescoSchemaUpdate-OrclBLOB.sql @@ -0,0 +1,27 @@ +-- +-- Title: Change Oracle LONG RAW columns to BLOB +-- Database: Generic +-- Since: V2.2 Schema 92 +-- Author: Derek Hulley +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. + +-- TODO: This needs to be replaced with a creation of new tables, copying values over with TO_LOB and then +-- renaming the values back. + +ALTER TABLE alf_attributes MODIFY (serializable_value BLOB NULL); +ALTER TABLE avm_node_properties MODIFY (serializable_value BLOB NULL); +ALTER TABLE avm_node_properties_new MODIFY (serializable_value BLOB NULL); +ALTER TABLE avm_store_properties MODIFY (serializable_value BLOB NULL); + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V2.2-OrclBLOB'; +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-V2.2-OrclBLOB', 'Modified serializable_value columns from LONG RAW to BLOB.', + 0, 91, -1, 92, null, 'UNKOWN', 1, 1, 'Script completed' + ); \ No newline at end of file diff --git a/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Oracle9Dialect/upgrade-1-mltext.sql b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Oracle9Dialect/upgrade-1-mltext.sql new file mode 100644 index 0000000000..35857f5bb9 --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Oracle9Dialect/upgrade-1-mltext.sql @@ -0,0 +1,177 @@ +-- +-- Title: Pull MLText Values into Node Properties +-- Database: Oracle +-- Since: V2.2 Schema 90 +-- Author: Derek Hulley +-- +-- MLText values must be pulled back from attributes into localizable properties. +-- Several statements are not relevant to upgrades from below 77. These are optional. +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +CREATE TABLE alf_locale +( + id NUMBER(19,0) DEFAULT 0 NOT NULL, + version NUMBER(19,0) DEFAULT 1 NOT NULL, + locale_str VARCHAR2(20 CHAR) NOT NULL, + UNIQUE (locale_str) +); + +INSERT INTO alf_locale (id, locale_str) VALUES (1, '.default'); + +INSERT INTO alf_locale (locale_str) +( + SELECT DISTINCT(ma.mkey) + FROM alf_node_properties np + JOIN alf_attributes a1 ON (np.attribute_value = a1.id) + JOIN alf_map_attribute_entries ma ON (ma.map_id = a1.id) +); -- (OPTIONAL) +UPDATE alf_locale SET id = hibernate_sequence.nextval WHERE id != 1; +ALTER TABLE alf_locale ADD PRIMARY KEY (id); + +-- Create a temporary table to hold the attribute_value information that needs replacing +CREATE TABLE t_alf_node_properties +( + node_id NUMBER(19,0) NOT NULL, + qname_id NUMBER(19,0) NOT NULL, + list_index NUMBER(10,0) NOT NULL, + locale_id NUMBER(19,0) NOT NULL, + actual_type_n NUMBER(10,0) NOT NULL, + persisted_type_n NUMBER(10,0) NOT NULL, + boolean_value NUMBER(1,0), + long_value NUMBER(19,0), + float_value FLOAT, + double_value DOUBLE PRECISION, + string_value VARCHAR2(1024 char), + serializable_value BLOB, + CONSTRAINT fk_alf_nprop_n FOREIGN KEY (node_id) REFERENCES alf_node, + CONSTRAINT fk_alf_nprop_qn FOREIGN KEY (qname_id) REFERENCES alf_qname, + CONSTRAINT fk_alf_nprop_loc FOREIGN KEY (locale_id) REFERENCES alf_locale, + PRIMARY KEY (node_id, qname_id, list_index, locale_id) +); +CREATE INDEX fk_alf_nprop_n ON t_alf_node_properties (node_id); +CREATE INDEX fk_alf_nprop_qn ON t_alf_node_properties (qname_id); +CREATE INDEX fk_alf_nprop_loc ON t_alf_node_properties (locale_id); + +-- Copy all simple values over +INSERT INTO t_alf_node_properties + ( + node_id, qname_id, list_index, locale_id, + actual_type_n, persisted_type_n, + boolean_value, long_value, float_value, double_value, + string_value, + serializable_value + ) + SELECT + np.node_id, np.qname_id, -1, 1, + np.actual_type_n, np.persisted_type_n, + np.boolean_value, np.long_value, np.float_value, np.double_value, + np.string_value, + TO_LOB(np.serializable_value) + FROM alf_node_properties np + WHERE + np.attribute_value is null +; + +-- Copy all MLText values over +INSERT INTO t_alf_node_properties + ( + node_id, qname_id, list_index, locale_id, + actual_type_n, persisted_type_n, + boolean_value, long_value, float_value, double_value, + string_value, + serializable_value + ) + SELECT + np.node_id, np.qname_id, -1, loc.id, + -1, 0, + 0, 0, 0, 0, + a2.string_value, + TO_LOB(a2.serializable_value) + FROM alf_node_properties np + JOIN alf_attributes a1 ON (np.attribute_value = a1.id) + JOIN alf_map_attribute_entries ma ON (ma.map_id = a1.id) + JOIN alf_locale loc ON (ma.mkey = loc.locale_str) + JOIN alf_attributes a2 ON (ma.attribute_id = a2.id) +; -- (OPTIONAL) +UPDATE t_alf_node_properties + SET actual_type_n = 6, persisted_type_n = 6, serializable_value = NULL + WHERE actual_type_n = -1 AND string_value IS NOT NULL +; +UPDATE t_alf_node_properties + SET actual_type_n = 9, persisted_type_n = 9 + WHERE actual_type_n = -1 AND serializable_value IS NOT NULL +; + +-- Delete the node properties and move the fixed values over +DROP TABLE alf_node_properties; +ALTER TABLE t_alf_node_properties RENAME TO alf_node_properties; + +-- Clean up unused attribute values + +CREATE TABLE t_del_attributes +( + id NUMBER(19, 0) NOT NULL, + PRIMARY KEY (id) +); +INSERT INTO t_del_attributes +( + SELECT id FROM alf_attributes WHERE type = 'M' +) +; +DELETE + FROM t_del_attributes t + WHERE t.id = + ( + SELECT ma.attribute_id FROM alf_map_attribute_entries ma WHERE ma.attribute_id = t.id + ) +; +DELETE + FROM t_del_attributes t + WHERE t.id = + ( + SELECT la.attribute_id FROM alf_list_attribute_entries la WHERE la.attribute_id = t.id + ) +; +DELETE + FROM t_del_attributes t + WHERE t.id = + ( + SELECT ga.attribute FROM alf_global_attributes ga WHERE ga.attribute = t.id + ) +; +INSERT INTO t_del_attributes +( + SELECT a.id FROM t_del_attributes t + JOIN alf_map_attribute_entries ma ON (ma.map_id = t.id) + JOIN alf_attributes a ON (ma.attribute_id = a.id) +); +DELETE + FROM alf_map_attribute_entries ma + WHERE ma.map_id = + ( + SELECT t.id FROM t_del_attributes t WHERE t.id = ma.map_id + ) +; +DELETE + FROM alf_attributes a + WHERE a.id = + ( + SELECT t.id FROM t_del_attributes t WHERE t.id = a.id + ) +; +DROP TABLE t_del_attributes; + + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V2.2-1-MLText'; +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-V2.2-1-MLText', 'Manually executed script upgrade V2.2: Moved MLText values', + 0, 90, -1, 91, null, 'UNKOWN', 1, 1, 'Script completed' + ); \ No newline at end of file diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml index 61444af599..31d0627fee 100644 --- a/config/alfresco/ehcache-default.xml +++ b/config/alfresco/ehcache-default.xml @@ -43,21 +43,14 @@ - - @@ -289,6 +282,12 @@ timeToLiveSeconds="300" overflowToDisk="false" /> + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + org.alfresco.repo.security.permissions.PermissionServiceSPI + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + alfresco/model/permissionDefinitions.xml + + + + + + + + + + + + + + + + + + + + ROLE_ + + + + + + + + + + GROUP_ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${system.acl.maxPermissionCheckTimeMillis} + + + ${system.acl.maxPermissionChecks} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/hibernate-context.xml b/config/alfresco/hibernate-context.xml index 5a7daf70d1..8348be39c8 100644 --- a/config/alfresco/hibernate-context.xml +++ b/config/alfresco/hibernate-context.xml @@ -54,9 +54,9 @@ + org/alfresco/repo/domain/hibernate/Locale.hbm.xml org/alfresco/repo/domain/hibernate/QName.hbm.xml org/alfresco/repo/domain/hibernate/Node.hbm.xml - org/alfresco/repo/domain/hibernate/Store.hbm.xml org/alfresco/repo/domain/hibernate/Transaction.hbm.xml org/alfresco/repo/domain/hibernate/VersionCount.hbm.xml org/alfresco/repo/domain/hibernate/AppliedPatch.hbm.xml @@ -172,7 +172,6 @@ ${cache.strategy} ${cache.strategy} ${cache.strategy} - ${cache.strategy} ${cache.strategy} ${cache.strategy} ${cache.strategy} @@ -341,9 +340,18 @@ + + + + + + + + + @@ -355,8 +363,7 @@ - - + @@ -364,12 +371,32 @@ - dbNodeDaoServiceDirtySessionInterceptor + daoServiceDirtySessionInterceptor dbNodeDaoServiceTxnRegistration + + + + + + + + + + + + + + + + daoServiceDirtySessionInterceptor + + + + diff --git a/config/alfresco/messages/schema-update.properties b/config/alfresco/messages/schema-update.properties index 36a1824267..3843c26bdb 100644 --- a/config/alfresco/messages/schema-update.properties +++ b/config/alfresco/messages/schema-update.properties @@ -11,6 +11,7 @@ schema.update.msg.optional_statement_failed=Optional statement execution failed: schema.update.warn.dialect_unsupported=Alfresco should not be used with database dialect {0}. schema.update.warn.dialect_hsql=Alfresco is using the HSQL default database. Please only use this while evaluating Alfresco, it is NOT recommended for production or deployment! schema.update.warn.dialect_derby=Alfresco is using the Apache Derby default database. Please only use this while evaluating Alfresco, it is NOT recommended for production or deployment! +schema.update.warn.dialect_substituting=The dialect ''{0}'' is being changed to ''{1}''. schema.update.err.found_multiple=\nMore than one Alfresco schema was found when querying the database metadata.\n Limit the database user's permissions or set the 'hibernate.default_schema' property in 'custom-hibernate-dialect.properties'. schema.update.err.previous_failed=A previous schema upgrade failed or was not completed. Revert to the original database before attempting the upgrade again. schema.update.err.statement_failed=Statement execution failed:\n SQL: {0}\n Error: {1}\n File: {2}\n Line: {3} diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 3c48f650e6..420d4baed0 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -827,34 +827,6 @@ - - patch.AVMAspects - patch.AVMAspects.description - 0 - 60 - 61 - - - - - - - - - - patch.AVMProperties - patch.AVMProperties.description - 0 - 61 - 62 - - - - - - - - patch.db-V2.1-JBPMProcessKey patch.schemaUpgradeScript.description @@ -1237,6 +1209,7 @@ + + + + + patch.db-V2.2-1-MLText + patch.schemaUpgradeScript.description + 86 + 90 + 91 + + classpath:alfresco/dbscripts/upgrade/2.2/${db.script.dialect}/upgrade-1-mltext.sql + + + + + + + + + patch.db-V2.2-1-FullDmUpgrade + patch.schemaUpgradeScript.description + 0 + 85 + 91 + + classpath:alfresco/dbscripts/upgrade/2.2/${db.script.dialect}/upgrade-1-fulldm.sql + + + + + + + patch.wcmPermissionPatch @@ -1391,12 +1409,12 @@ - + - - + + patch.updateDmPermissions patch.updateDmPermissions.description 0 @@ -1565,4 +1583,32 @@ - + + + patch.db-V2.2-Person + patch.schemaUpgradeScript.description + 0 + 134 + 135 + + classpath:alfresco/dbscripts/upgrade/2.2/${db.script.dialect}/AlfrescoSchemaUpdate-Person.sql + + + + + patch.db-V2.2-OrclBLOB + patch.schemaUpgradeScript.description + 0 + 134 + 135 + + classpath:alfresco/dbscripts/upgrade/2.2/${db.script.dialect}/AlfrescoSchemaUpdate-OrclBLOB.sql + + + + + + + + + diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index aa01ee725f..932fee6c1f 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=134 +version.schema=135 diff --git a/source/java/org/alfresco/repo/admin/patch/impl/AVMAspectsPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/AVMAspectsPatch.java deleted file mode 100644 index e41d4e0184..0000000000 --- a/source/java/org/alfresco/repo/admin/patch/impl/AVMAspectsPatch.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2005-2007 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.admin.patch.impl; - -import java.util.Iterator; - -import org.alfresco.i18n.I18NUtil; -import org.alfresco.repo.admin.patch.AbstractPatch; -import org.alfresco.repo.avm.AVMAspectName; -import org.alfresco.repo.avm.AVMAspectNameDAO; -import org.alfresco.repo.domain.QNameDAO; -import org.alfresco.repo.domain.QNameEntity; -import org.alfresco.service.namespace.QName; - -/** - * Patches from old style aspect storage for AVM to new style. - * @author britt - */ -public class AVMAspectsPatch extends AbstractPatch -{ - private static final String MSG_SUCCESS = "patch.AVMAspects.result"; - - private AVMAspectNameDAO fAVMAspectDAO; - private QNameDAO qnameDAO; - - public void setAvmAspectNameDAO(AVMAspectNameDAO dao) - { - fAVMAspectDAO = dao; - } - - public void setQnameDAO(QNameDAO qnameDAO) - { - this.qnameDAO = qnameDAO; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.admin.patch.AbstractPatch#applyInternal() - */ - @Override - protected String applyInternal() throws Exception - { - Iterator iter = fAVMAspectDAO.iterator(); - while (iter.hasNext()) - { - AVMAspectName aspect = iter.next(); - QName aspectQName = aspect.getName(); - QNameEntity aspectQNameEntity = qnameDAO.getOrCreateQNameEntity(aspectQName); - aspect.getNode().getAspects().add(aspectQNameEntity.getId()); - fAVMAspectDAO.delete(aspect); - } - return I18NUtil.getMessage(MSG_SUCCESS); - } -} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/AVMPropertiesPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/AVMPropertiesPatch.java deleted file mode 100644 index 40192e26ce..0000000000 --- a/source/java/org/alfresco/repo/admin/patch/impl/AVMPropertiesPatch.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2005-2007 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.admin.patch.impl; - -import java.util.Iterator; - -import org.alfresco.i18n.I18NUtil; -import org.alfresco.repo.admin.patch.AbstractPatch; -import org.alfresco.repo.avm.AVMNodeProperty; -import org.alfresco.repo.avm.AVMNodePropertyDAO; -import org.alfresco.repo.domain.QNameDAO; -import org.alfresco.service.namespace.QName; - -/** - * Patch more remapping AVM properties. - * @author britt - */ -public class AVMPropertiesPatch extends AbstractPatch -{ - private static final String MSG_SUCCESS = "patch.AVMProperties.result"; - - private QNameDAO qnameDAO; - private AVMNodePropertyDAO fAVMNodePropertyDAO; - - public void setQnameDAO(QNameDAO qnameDAO) - { - this.qnameDAO = qnameDAO; - } - - public void setAvmNodePropertyDAO(AVMNodePropertyDAO dao) - { - fAVMNodePropertyDAO = dao; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.admin.patch.AbstractPatch#applyInternal() - */ - @Override - protected String applyInternal() throws Exception - { - Iterator iter = fAVMNodePropertyDAO.iterate(); - while (iter.hasNext()) - { - AVMNodeProperty prop = iter.next(); - QName propertyQName = prop.getName(); - Long propertyQNameEntityId = qnameDAO.getOrCreateQNameEntity(propertyQName).getId(); - prop.getNode().getProperties().put(propertyQNameEntityId, prop.getValue()); - fAVMNodePropertyDAO.delete(prop.getNode(), prop.getName()); - } - return I18NUtil.getMessage(MSG_SUCCESS); - } -} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/NodePropertySerializablePatch.java b/source/java/org/alfresco/repo/admin/patch/impl/NodePropertySerializablePatch.java index 2007dcdfb2..ce8874187f 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/NodePropertySerializablePatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/NodePropertySerializablePatch.java @@ -31,7 +31,8 @@ import java.util.Map; import org.alfresco.i18n.I18NUtil; import org.alfresco.repo.admin.patch.AbstractPatch; import org.alfresco.repo.domain.Node; -import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.domain.NodePropertyValue; +import org.alfresco.repo.domain.PropertyMapKey; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.hibernate.Query; import org.hibernate.Session; @@ -103,22 +104,17 @@ public class NodePropertySerializablePatch extends AbstractPatch { Node node = iterator.next(); // retrieve the node properties - Map properties = node.getProperties(); + Map properties = node.getProperties(); // check each property - for (Map.Entry entry : properties.entrySet()) + for (Map.Entry entry : properties.entrySet()) { - PropertyValue propertyValue = entry.getValue(); + NodePropertyValue propertyValue = entry.getValue(); if (propertyValue.getSerializableValue() == null) { // the property was not persisted as a serializable - nothing to do continue; } - else if (propertyValue.isMultiValued()) - { - // this is a persisted collection - nothing to do - continue; - } - else if (!"SERIALIZABLE".equals(propertyValue.getActualType())) + else if (!"SERIALIZABLE".equals(propertyValue.getActualTypeString())) { // only handle actual types that were pushed in as any old type continue; @@ -126,7 +122,7 @@ public class NodePropertySerializablePatch extends AbstractPatch // make sure that this value is persisted correctly Serializable value = propertyValue.getSerializableValue(); // put it back - PropertyValue newPropertyValue = new PropertyValue(DataTypeDefinition.ANY, value); + NodePropertyValue newPropertyValue = new NodePropertyValue(DataTypeDefinition.ANY, value); entry.setValue(newPropertyValue); count++; } diff --git a/source/java/org/alfresco/repo/audit/AuditableAspect.java b/source/java/org/alfresco/repo/audit/AuditableAspect.java deleted file mode 100644 index 5808869cfa..0000000000 --- a/source/java/org/alfresco/repo/audit/AuditableAspect.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (C) 2005-2007 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.audit; - -import java.io.Serializable; -import java.util.Date; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.policy.Behaviour; -import org.alfresco.repo.policy.JavaBehaviour; -import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.policy.PolicyScope; -import org.alfresco.repo.policy.Behaviour.NotificationFrequency; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.security.AuthenticationService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.PropertyMap; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - - -/** - * This aspect maintains the audit properties of the Auditable aspect. - * - * @author David Caruana - */ -public class AuditableAspect -{ - // Logger - private static final Log logger = LogFactory.getLog(AuditableAspect.class); - - // Unknown user, for when authentication has not occured - private static final String USERNAME_UNKNOWN = "unknown"; - - // Dependencies - private NodeService nodeService; - private AuthenticationService authenticationService; - private PolicyComponent policyComponent; - private TenantService tenantService; - - // Behaviours - private Behaviour onCreateAudit; - private Behaviour onAddAudit; - private Behaviour onUpdateAudit; - - - /** - * @param nodeService the node service to use for audit property maintenance - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * @param policyComponent the policy component - */ - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } - - /** - * @param authenticationService the authentication service - */ - public void setAuthenticationService(AuthenticationService authenticationService) - { - this.authenticationService = authenticationService; - } - - /** - * @param tenantService the tenant service - */ - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - - - /** - * Initialise the Auditable Aspect - */ - public void init() - { - // Create behaviours - onCreateAudit = new JavaBehaviour(this, "onCreateAudit", NotificationFrequency.FIRST_EVENT); - onAddAudit = new JavaBehaviour(this, "onAddAudit", NotificationFrequency.FIRST_EVENT); - onUpdateAudit = new JavaBehaviour(this, "onUpdateAudit", NotificationFrequency.TRANSACTION_COMMIT); - - // Bind behaviours to node policies - policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), ContentModel.ASPECT_AUDITABLE, onCreateAudit); - policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), ContentModel.ASPECT_AUDITABLE, onAddAudit); - policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateNode"), ContentModel.ASPECT_AUDITABLE, onUpdateAudit); - - // Register onCopy class behaviour - policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"), ContentModel.ASPECT_AUDITABLE, new JavaBehaviour(this, "onCopy")); - } - - /** - * Maintain audit properties on creation of Node - * - * @param childAssocRef the association to the child created - */ - public void onCreateAudit(ChildAssociationRef childAssocRef) - { - NodeRef nodeRef = childAssocRef.getChildRef(); - onAddAudit(nodeRef, null); - } - - /** - * Maintain audit properties on addition of audit aspect to a node - * - * @param nodeRef the node to which auditing has been added - * @param aspect the aspect added - */ - public void onAddAudit(NodeRef nodeRef, QName aspect) - { - // Get the current properties - PropertyMap properties = new PropertyMap(); - - // Set created / updated date - Date now = new Date(System.currentTimeMillis()); - properties.put(ContentModel.PROP_CREATED, now); - properties.put(ContentModel.PROP_MODIFIED, now); - - // Set creator (but do not override, if explicitly set) - String creator = (String)properties.get(ContentModel.PROP_CREATOR); - if (creator == null || creator.length() == 0) - { - creator = getUsername(); - properties.put(ContentModel.PROP_CREATOR, creator); - } - properties.put(ContentModel.PROP_MODIFIER, creator); - - try - { - // Set the updated property values (but do not cascade to update audit behaviour) - onUpdateAudit.disable(); - - // note: in MT case, this will run in System context of user's domain ... checkForLock requires System - AuthenticationUtil.runAs(new SetAuditProperties(nodeService, nodeRef, properties), AuthenticationUtil.getSystemUserName()); - } - finally - { - onUpdateAudit.enable(); - } - - if (logger.isDebugEnabled()) - logger.debug("Auditable node " + nodeRef + " created [created,modified=" + now + ";creator,modifier=" + creator + "]"); - } - - /** - * Maintain audit properties on update of node - * - * @param nodeRef the updated node - */ - public void onUpdateAudit(NodeRef nodeRef) - { - // Get the current properties - try - { - PropertyMap properties = new PropertyMap(); - - // Set updated date - Date now = new Date(System.currentTimeMillis()); - properties.put(ContentModel.PROP_MODIFIED, now); - - // Set modifier - String modifier = getUsername(); - properties.put(ContentModel.PROP_MODIFIER, modifier); - - // Set the updated property values - - // note: in MT case, this will run in System context of user's domain ... checkForLock requires System - AuthenticationUtil.runAs(new SetAuditProperties(nodeService, nodeRef, properties), AuthenticationUtil.getSystemUserName()); - - if (logger.isDebugEnabled()) - logger.debug("Auditable node " + nodeRef + " updated [modified=" + now + ";modifier=" + modifier + "]"); - } - catch(InvalidNodeRefException e) - { - if (logger.isDebugEnabled()) - logger.debug("Warning: Auditable node " + nodeRef + " no longer exists - cannot update"); - } - } - - /** - * @return the current username (or unknown, if unknown) - */ - private String getUsername() - { - String currentUserName = authenticationService.getCurrentUserName(); - if (currentUserName != null) - { - if (tenantService.isEnabled() && authenticationService.isCurrentUserTheSystemUser()) - { - return tenantService.getBaseNameUser(currentUserName); - } - return currentUserName; - } - return USERNAME_UNKNOWN; - } - - /** - * OnCopy behaviour implementation for the lock aspect. - *

- * Ensures that the propety values of the lock aspect are not copied onto - * the destination node. - * - * @see org.alfresco.repo.copy.CopyServicePolicies.OnCopyNodePolicy#onCopyNode(QName, NodeRef, StoreRef, boolean, PolicyScope) - */ - public void onCopy( - QName sourceClassRef, - NodeRef sourceNodeRef, - StoreRef destinationStoreRef, - boolean copyToNewNode, - PolicyScope copyDetails) - { - // The auditable aspect should not be copied - } - - - /** - * Helper to set Audit Properties as System User - */ - private static class SetAuditProperties implements AuthenticationUtil.RunAsWork - { - private NodeService nodeService; - private NodeRef nodeRef; - private Map properties; - - /** - * Construct - */ - private SetAuditProperties(NodeService nodeService, NodeRef nodeRef, Map properties) - { - this.nodeService = nodeService; - this.nodeRef = nodeRef; - this.properties = properties; - } - - public Boolean doWork() throws Exception - { - for (Map.Entry entry : properties.entrySet()) - { - QName propertyQName = entry.getKey(); - Serializable propertyValue = entry.getValue(); - nodeService.setProperty(nodeRef, propertyQName, propertyValue); - } - return Boolean.TRUE; - - } - } - - -} diff --git a/source/java/org/alfresco/repo/audit/AuditableAspectTest.java b/source/java/org/alfresco/repo/audit/AuditableAspectTest.java index e13006c33f..fcf96a36f4 100644 --- a/source/java/org/alfresco/repo/audit/AuditableAspectTest.java +++ b/source/java/org/alfresco/repo/audit/AuditableAspectTest.java @@ -25,9 +25,9 @@ package org.alfresco.repo.audit; import java.io.Serializable; +import java.util.Date; import java.util.HashMap; import java.util.Map; -import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -88,68 +88,21 @@ public class AuditableAspectTest extends BaseSpringTest System.out.println(NodeStoreInspector.dumpNodeStore(nodeService, storeRef)); } - + /** + * @deprecated cm:auditable is always present (2.2.2) + */ public void testNoAudit() { - // Create a person (which doesn't have auditable capability by default) - Map personProps = new HashMap(); - personProps.put(ContentModel.PROP_USERNAME, "test person"); - personProps.put(ContentModel.PROP_HOMEFOLDER, rootNodeRef); - personProps.put(ContentModel.PROP_FIRSTNAME, "test first name"); - personProps.put(ContentModel.PROP_LASTNAME, "test last name"); - - ChildAssociationRef childAssocRef = nodeService.createNode( - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}testperson"), - ContentModel.TYPE_PERSON, - personProps); - - // Assert the person is not auditable - Set aspects = nodeService.getAspects(childAssocRef.getChildRef()); - assertFalse(aspects.contains(ContentModel.ASPECT_AUDITABLE)); - - System.out.println(NodeStoreInspector.dumpNodeStore(nodeService, storeRef)); } - + /** + * @deprecated cm:auditable is always present (2.2.2) + */ public void testAddAudit() { - // Create a person - Map personProps = new HashMap(); - personProps.put(ContentModel.PROP_USERNAME, "test person"); - personProps.put(ContentModel.PROP_HOMEFOLDER, rootNodeRef); - personProps.put(ContentModel.PROP_FIRSTNAME, "test first name"); - personProps.put(ContentModel.PROP_LASTNAME, "test last name"); - - ChildAssociationRef childAssocRef = nodeService.createNode( - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}testperson"), - ContentModel.TYPE_PERSON, - personProps); - - // Assert the person is not auditable - Set aspects = nodeService.getAspects(childAssocRef.getChildRef()); - assertFalse(aspects.contains(ContentModel.ASPECT_AUDITABLE)); - - // Add auditable capability - nodeService.addAspect(childAssocRef.getChildRef(), ContentModel.ASPECT_AUDITABLE, null); - - nodeService.addAspect(childAssocRef.getChildRef(), ContentModel.ASPECT_TITLED, null); - - // Assert the person is now audiable - aspects = nodeService.getAspects(childAssocRef.getChildRef()); - assertTrue(aspects.contains(ContentModel.ASPECT_AUDITABLE)); - - // Assert the person's auditable property - assertAuditableProperties(childAssocRef.getChildRef()); - - System.out.println(NodeStoreInspector.dumpNodeStore(nodeService, storeRef)); } - - public void testAddAspect() + public synchronized void testAddAspect() throws Exception { // Create a person (which doesn't have auditable capability by default) Map personProps = new HashMap(); @@ -158,20 +111,45 @@ public class AuditableAspectTest extends BaseSpringTest personProps.put(ContentModel.PROP_FIRSTNAME, "test first name "); personProps.put(ContentModel.PROP_LASTNAME, "test last name"); + long t1 = System.currentTimeMillis(); + this.wait(100); + ChildAssociationRef childAssocRef = nodeService.createNode( rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}testperson"), ContentModel.TYPE_PERSON, personProps); - + NodeRef nodeRef = childAssocRef.getChildRef(); + + assertAuditableProperties(nodeRef); + + long t2 = System.currentTimeMillis(); + + // Check that the dates were set correctly + Date aspectCreatedDate1 = (Date) nodeService.getProperty(nodeRef, ContentModel.PROP_CREATED); + Date aspectModifiedDate1 = (Date) nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED); + assertTrue("Created date should be later than t1", t1 < aspectCreatedDate1.getTime()); + assertTrue( + "Modified date must be after or on creation date", + aspectCreatedDate1.getTime() <= aspectModifiedDate1.getTime() && + aspectModifiedDate1.getTime() < t2); + + long t3 = System.currentTimeMillis(); + this.wait(100); + // Add auditable capability - nodeService.addAspect(childAssocRef.getChildRef(), ContentModel.ASPECT_TITLED, null); + nodeService.addAspect(nodeRef, ContentModel.ASPECT_TITLED, null); + + // Check that the dates were set correctly + Date aspectCreatedDate2 = (Date) nodeService.getProperty(nodeRef, ContentModel.PROP_CREATED); + Date aspectModifiedDate2 = (Date) nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED); + assertEquals("The created date must not change", aspectCreatedDate1, aspectCreatedDate2); + assertTrue("New modified date should be later than t3", t3 < aspectModifiedDate2.getTime()); System.out.println(NodeStoreInspector.dumpNodeStore(nodeService, storeRef)); } - private void assertAuditableProperties(NodeRef nodeRef) { Map props = nodeService.getProperties(nodeRef); diff --git a/source/java/org/alfresco/repo/avm/AVMAspectNameDAO.java b/source/java/org/alfresco/repo/avm/AVMAspectNameDAO.java deleted file mode 100644 index 265d699780..0000000000 --- a/source/java/org/alfresco/repo/avm/AVMAspectNameDAO.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2005-2007 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.avm; - -import java.util.Iterator; -import java.util.List; - -import org.alfresco.service.namespace.QName; - -/** - * DAO for AVMAspectNames. - * @author britt - */ -public interface AVMAspectNameDAO -{ - /** - * Persist an aspect name. - * @param aspectName The item to persist. - */ - public void save(AVMAspectName aspectName); - - /** - * Delete an Aspect Name. - * @param aspectName The item to delete. - */ - public void delete(AVMAspectName aspectName); - - /** - * Delete a single aspect name from a node. - * @param node The node. - * @param aspectName The aspect name. - */ - public void delete(AVMNode node, QName aspectName); - - /** - * Delete all Aspect Names on a given node. - * @param node The given node. - */ - public void delete(AVMNode node); - - /** - * Get all Aspect Names for a given node. - * @param node The AVM Node. - * @return A List of AVMAspectNames. - */ - public List get(AVMNode node); - - /** - * Does the given node have the given asset. - * @param node The AVM node. - * @param name The QName of the Aspect. - * @return Whether the aspect is there. - */ - public boolean exists(AVMNode node, QName name); - - /** - * Get an iterator over all aspect instances. - * @return - */ - public Iterator iterator(); -} diff --git a/source/java/org/alfresco/repo/avm/AVMAspectNameImpl.java b/source/java/org/alfresco/repo/avm/AVMAspectNameImpl.java deleted file mode 100644 index 2877651b22..0000000000 --- a/source/java/org/alfresco/repo/avm/AVMAspectNameImpl.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2005-2007 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.avm; - -import java.io.Serializable; - -import org.alfresco.service.namespace.QName; - -/** - * Simple bean that implements AVMAspectName. - * @author britt - */ -class AVMAspectNameImpl implements AVMAspectName, Serializable -{ - private static final long serialVersionUID = -6282415309583571934L; - - /** - * The Primary Key. - */ - private Long fID; - - /** - * The Node that has the named aspect. - */ - private AVMNode fNode; - - /** - * The name of the Aspect. - */ - private QName fName; - - /** - * Default constructor. - */ - public AVMAspectNameImpl() - { - } - - /** - * Set the node that has the Aspect. - * @param node The node. - */ - public void setNode(AVMNode node) - { - fNode = node; - } - - /** - * Get the node that has this Aspect name. - * @return The AVM Node. - */ - public AVMNode getNode() - { - return fNode; - } - - /** - * Set the name of the Aspect. - * @param name The QName of the Aspect. - */ - public void setName(QName name) - { - fName = name; - } - - /** - * Get the name of this Aspect. - * @return The QName of this aspect. - */ - public QName getName() - { - return fName; - } - - /** - * Set the primary key (For Hibernate) - * @param id The primary key. - */ - protected void setId(Long id) - { - fID = id; - } - - /** - * Get the primary key (For Hibernate) - * @return The primary key. - */ - protected Long getId() - { - return fID; - } - - @Override - public boolean equals(Object obj) - { - if (this == obj) - { - return true; - } - if (!(obj instanceof AVMAspectName)) - { - return false; - } - AVMAspectName o = (AVMAspectName)obj; - return fNode.equals(o.getNode()) && fName.equals(o.getName()); - } - - @Override - public int hashCode() - { - return fNode.hashCode() + fName.hashCode(); - } -} diff --git a/source/java/org/alfresco/repo/avm/AVMDAOs.java b/source/java/org/alfresco/repo/avm/AVMDAOs.java index 2eca72438b..d42812fef9 100644 --- a/source/java/org/alfresco/repo/avm/AVMDAOs.java +++ b/source/java/org/alfresco/repo/avm/AVMDAOs.java @@ -74,21 +74,11 @@ public class AVMDAOs */ public MergeLinkDAO fMergeLinkDAO; - /** - * The AVMNodePropertyDAO - */ - public AVMNodePropertyDAO fAVMNodePropertyDAO; - /** * The AVMStorePropertyDAO */ public AVMStorePropertyDAO fAVMStorePropertyDAO; - /** - * The AVMAspectNameDAO - */ - public AVMAspectNameDAO fAVMAspectNameDAO; - public AttributeDAO fAttributeDAO; public MapEntryDAO fMapEntryDAO; @@ -160,21 +150,11 @@ public class AVMDAOs fIssuerDAO = issuerDAO; } - public void setAvmNodePropertyDAO(AVMNodePropertyDAO avmNodePropertyDAO) - { - fAVMNodePropertyDAO = avmNodePropertyDAO; - } - public void setAvmStorePropertyDAO(AVMStorePropertyDAO avmStorePropertyDAO) { fAVMStorePropertyDAO = avmStorePropertyDAO; } - public void setAvmAspectNameDAO(AVMAspectNameDAO avmAspectNameDAO) - { - fAVMAspectNameDAO = avmAspectNameDAO; - } - public void setAttributeDAO(AttributeDAO dao) { fAttributeDAO = dao; diff --git a/source/java/org/alfresco/repo/avm/AVMNodeProperty.java b/source/java/org/alfresco/repo/avm/AVMNodeProperty.java deleted file mode 100644 index 5555cd83f9..0000000000 --- a/source/java/org/alfresco/repo/avm/AVMNodeProperty.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2005-2007 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.avm; - -import org.alfresco.repo.domain.PropertyValue; -import org.alfresco.service.namespace.QName; - -/** - * Alfresco Properties for AVM.. - * @author britt - */ -public interface AVMNodeProperty -{ - /** - * Set the node that owns this property. - * @param node The AVMNode. - */ - public void setNode(AVMNode node); - - /** - * Get the node that owns this property. - * @return An AVMNode. - */ - public AVMNode getNode(); - - /** - * Get the name for this property. - * @return A QName. - */ - public QName getName(); - - /** - * Set the name for the property. - * @param id A QName. - */ - public void setName(QName id); - - /** - * Get the actual property value. - * @return A PropertyValue. - */ - public PropertyValue getValue(); - - /** - * Set the value of this property. - * @param value A PropertyValue. - */ - public void setValue(PropertyValue value); -} diff --git a/source/java/org/alfresco/repo/avm/AVMNodePropertyDAO.java b/source/java/org/alfresco/repo/avm/AVMNodePropertyDAO.java deleted file mode 100644 index 3ef9580bf0..0000000000 --- a/source/java/org/alfresco/repo/avm/AVMNodePropertyDAO.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2005-2007 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.avm; - -import java.util.Iterator; -import java.util.List; - -import org.alfresco.service.namespace.QName; - -/** - * DAO for AVMNodeProperty. - * @author britt - */ -public interface AVMNodePropertyDAO -{ - /** - * Save the given AVMNodeProperty. - * @param prop - */ - public void save(AVMNodeProperty prop); - - /** - * Get an AVMNodeProperty by owner and name. - * @param owner An AVMNode. - * @param name The QName. - * @return The found AVMNodeProperty or null if not found. - */ - public AVMNodeProperty get(AVMNode owner, QName name); - - /** - * Get a List of all properties for an owning node. - * @param node The owning node. - * @return A List of properties belonging to the given node. - */ - public List get(AVMNode node); - - /** - * Update a property entry. - * @param prop The property. - */ - public void update(AVMNodeProperty prop); - - /** - * Delete all properties associated with a node. - * @param node The AVMNode whose properties should be deleted. - */ - public void deleteAll(AVMNode node); - - /** - * Delete the given property from the given node. - * @param node The node to delete the property to delete. - * @param name The name of the property to delete. - */ - public void delete(AVMNode node, QName name); - - /** - * Get an iterator over all properties. - * @return - */ - public Iterator iterate(); -} diff --git a/source/java/org/alfresco/repo/avm/AVMNodePropertyImpl.java b/source/java/org/alfresco/repo/avm/AVMNodePropertyImpl.java deleted file mode 100644 index 08f843f141..0000000000 --- a/source/java/org/alfresco/repo/avm/AVMNodePropertyImpl.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2005-2007 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.avm; - -import java.io.Serializable; - -import org.alfresco.repo.domain.PropertyValue; -import org.alfresco.service.namespace.QName; - -/** - * A Property attached to an AVMNode. - * @author britt - */ -class AVMNodePropertyImpl implements AVMNodeProperty, Serializable -{ - private static final long serialVersionUID = -7194228119659288619L; - - /** - * The primary key. - */ - private Long fID; - - /** - * The node that owns this. - */ - private AVMNode fNode; - - /** - * The QName of this property. - */ - private QName fName; - - /** - * The PropertyValue. - */ - private PropertyValue fValue; - - /** - * Default constructor. - */ - public AVMNodePropertyImpl() - { - } - - /** - * Get the owning node. - * @return The AVMNode. - */ - public AVMNode getNode() - { - return fNode; - } - - /** - * Set the owning node. - * @param node The AVMNode to set. - */ - public void setNode(AVMNode node) - { - fNode = node; - } - - /** - * Get the name, a QName - * @return A QName. - */ - public QName getName() - { - return fName; - } - - /** - * Set the name, a QName. - * @param name The QName. - */ - public void setName(QName name) - { - fName = name; - } - - /** - * Get the value. - * @return A PropertyValue - */ - public PropertyValue getValue() - { - return fValue; - } - - /** - * Set the value. - * @param value A PropertyValue. - */ - public void setValue(PropertyValue value) - { - fValue = value; - } - - /** - * Set the primary key. (For Hibernate) - * @param id The primary key. - */ - protected void setId(Long id) - { - fID = id; - } - - /** - * Get the primary key. (For Hibernate) - * @return The primary key. - */ - protected Long getId() - { - return fID; - } - - @Override - public boolean equals(Object other) - { - if (this == other) - { - return true; - } - if (!(other instanceof AVMNodeProperty)) - { - return false; - } - AVMNodeProperty o = (AVMNodeProperty)other; - return fNode.equals(o.getNode()) && fName.equals(o.getName()); - } - - @Override - public int hashCode() - { - return fNode.hashCode() + fName.hashCode(); - } -} diff --git a/source/java/org/alfresco/repo/avm/AVMNodeService.java b/source/java/org/alfresco/repo/avm/AVMNodeService.java index c4a4abb9fd..528b0e20c3 100644 --- a/source/java/org/alfresco/repo/avm/AVMNodeService.java +++ b/source/java/org/alfresco/repo/avm/AVMNodeService.java @@ -25,6 +25,7 @@ package org.alfresco.repo.avm; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -46,6 +47,8 @@ import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.avm.AVMStoreDescriptor; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryException; import org.alfresco.service.cmr.dictionary.InvalidAspectException; import org.alfresco.service.cmr.dictionary.InvalidTypeException; import org.alfresco.service.cmr.dictionary.PropertyDefinition; @@ -63,6 +66,7 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.StoreExistsException; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.datatype.TypeConversionException; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QNamePattern; @@ -108,6 +112,115 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi { fInvokePolicies = invoke; } + + /** + * Helper method to convert the Serializable value into a full, + * persistable {@link PropertyValue}. + *

+ * Where the property definition is null, the value will take on the + * {@link DataTypeDefinition#ANY generic ANY} value. + *

+ * Where the property definition specifies a multi-valued property but the + * value provided is not a collection, the value will be wrapped in a collection. + * + * @param propertyDef the property dictionary definition, may be null + * @param value the value, which will be converted according to the definition - + * may be null + * @return Returns the persistable property value + */ + protected PropertyValue makePropertyValue(PropertyDefinition propertyDef, Serializable value) + { + // get property attributes + QName propertyTypeQName = null; + if (propertyDef == null) // property not recognised + { + // allow it for now - persisting excess properties can be useful sometimes + propertyTypeQName = DataTypeDefinition.ANY; + } + else + { + propertyTypeQName = propertyDef.getDataType().getName(); + // check that multi-valued properties are allowed + boolean isMultiValued = propertyDef.isMultiValued(); + if (isMultiValued && !(value instanceof Collection)) + { + if (value != null) + { + // put the value into a collection + // the implementation gives back a Serializable list + value = (Serializable) Collections.singletonList(value); + } + } + else if (!isMultiValued && (value instanceof Collection)) + { + // we only allow this case if the property type is ANY + if (!propertyTypeQName.equals(DataTypeDefinition.ANY)) + { + throw new DictionaryException( + "A single-valued property of this type may not be a collection: \n" + + " Property: " + propertyDef + "\n" + + " Type: " + propertyTypeQName + "\n" + + " Value: " + value); + } + } + } + try + { + PropertyValue propertyValue = new PropertyValue(propertyTypeQName, value); + // done + return propertyValue; + } + catch (TypeConversionException e) + { + throw new TypeConversionException( + "The property value is not compatible with the type defined for the property: \n" + + " property: " + (propertyDef == null ? "unknown" : propertyDef) + "\n" + + " value: " + value + "\n" + + " value type: " + value.getClass(), + e); + } + } + + /** + * Extracts the externally-visible property from the {@link PropertyValue propertyValue}. + * + * @param propertyDef the model property definition - may be null + * @param propertyValue the persisted property + * @return Returns the value of the property in the format dictated by the property + * definition, or null if the property value is null + */ + protected Serializable makeSerializableValue(PropertyDefinition propertyDef, PropertyValue propertyValue) + { + if (propertyValue == null) + { + return null; + } + // get property attributes + QName propertyTypeQName = null; + if (propertyDef == null) + { + // allow this for now + propertyTypeQName = DataTypeDefinition.ANY; + } + else + { + propertyTypeQName = propertyDef.getDataType().getName(); + } + try + { + Serializable value = propertyValue.getValue(propertyTypeQName); + // done + return value; + } + catch (TypeConversionException e) + { + throw new TypeConversionException( + "The property value is not compatible with the type defined for the property: \n" + + " property: " + (propertyDef == null ? "unknown" : propertyDef) + "\n" + + " property value: " + propertyValue, + e); + } + } /** * Gets a list of all available node store references diff --git a/source/java/org/alfresco/repo/avm/hibernate/AVM.hbm.xml b/source/java/org/alfresco/repo/avm/hibernate/AVM.hbm.xml index 510e222072..d3766d6c22 100644 --- a/source/java/org/alfresco/repo/avm/hibernate/AVM.hbm.xml +++ b/source/java/org/alfresco/repo/avm/hibernate/AVM.hbm.xml @@ -46,14 +46,14 @@ - + - + - + - + @@ -150,12 +150,12 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/source/java/org/alfresco/repo/avm/hibernate/AVMAspectNameDAOHibernate.java b/source/java/org/alfresco/repo/avm/hibernate/AVMAspectNameDAOHibernate.java deleted file mode 100644 index 0742909444..0000000000 --- a/source/java/org/alfresco/repo/avm/hibernate/AVMAspectNameDAOHibernate.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2005-2007 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.avm.hibernate; - -import java.util.Iterator; -import java.util.List; - -import org.alfresco.repo.avm.AVMAspectName; -import org.alfresco.repo.avm.AVMAspectNameDAO; -import org.alfresco.repo.avm.AVMNode; -import org.alfresco.service.namespace.QName; -import org.hibernate.Query; -import org.springframework.orm.hibernate3.support.HibernateDaoSupport; - -/** - * Hibernate implementation of AVMAspectNameDAO. - * @author britt - */ -public class AVMAspectNameDAOHibernate extends HibernateDaoSupport - implements AVMAspectNameDAO -{ - /** - * Persist an aspect name. - * @param aspectName The item to persist. - */ - public void save(AVMAspectName aspectName) - { - getSession().save(aspectName); - } - - /** - * Delete an Aspect Name. - * @param aspectName The item to delete. - */ - public void delete(AVMAspectName aspectName) - { - getSession().delete(aspectName); - } - - /** - * Delete a single aspect name from a node. - * @param node The node. - * @param aspectName The aspect name. - */ - public void delete(AVMNode node, QName aspectName) - { - Query delete = - getSession().createQuery( - "delete from AVMAspectNameImpl aa where aa.node = :node and aa.name = :name"); - delete.setEntity("node", node); - delete.setParameter("name", aspectName); - delete.executeUpdate(); - } - - /** - * Delete all Aspect Names on a given node. - * @param node The given node. - */ - public void delete(AVMNode node) - { - Query delete = - getSession().createQuery("delete from AVMAspectNameImpl aa where aa.node = :node"); - delete.setEntity("node", node); - delete.executeUpdate(); - } - - /** - * Get all Aspect Names for a given node. - * @param node The AVM Node. - * @return A List of AVMAspectNames. - */ - @SuppressWarnings("unchecked") - public List get(AVMNode node) - { - Query query = - getSession().createQuery("from AVMAspectNameImpl aa where aa.node = :node"); - query.setEntity("node", node); - return (List)query.list(); - } - - /** - * Does the given node have the given asset. - * @param node The AVM node. - * @param name The QName of the Aspect. - * @return Whether the aspect is there. - */ - public boolean exists(AVMNode node, QName name) - { - Query query = - getSession().createQuery( - "from AVMAspectNameImpl aa where aa.node = :node and aa.name = :name"); - query.setEntity("node", node); - query.setParameter("name", name); - return query.uniqueResult() != null; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMAspectNameDAO#iterator() - */ - @SuppressWarnings("unchecked") - public Iterator iterator() - { - Query query = - getSession().createQuery("from AVMAspectNameImpl aa"); - return (Iterator)query.iterate(); - } -} diff --git a/source/java/org/alfresco/repo/avm/hibernate/AVMNodePropertyDAOHibernate.java b/source/java/org/alfresco/repo/avm/hibernate/AVMNodePropertyDAOHibernate.java deleted file mode 100644 index 620942ba20..0000000000 --- a/source/java/org/alfresco/repo/avm/hibernate/AVMNodePropertyDAOHibernate.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.alfresco.repo.avm.hibernate; - -import java.util.Iterator; -import java.util.List; - -import org.alfresco.repo.avm.AVMNode; -import org.alfresco.repo.avm.AVMNodeProperty; -import org.alfresco.repo.avm.AVMNodePropertyDAO; -import org.alfresco.service.namespace.QName; -import org.hibernate.Query; -import org.springframework.orm.hibernate3.support.HibernateDaoSupport; - -/** - * Hibernate implemenation for DAO for AVMNodeProperties. - * @author britt - */ -class AVMNodePropertyDAOHibernate extends HibernateDaoSupport - implements AVMNodePropertyDAO -{ - /** - * Get a property by node and name. - * @param node The AVMNode. - * @param name The QName. - * @return The found property or null. - */ - public AVMNodeProperty get(AVMNode node, QName name) - { - Query query = - getSession().createQuery( - "from AVMNodePropertyImpl anp where anp.node = :node and anp.name = :name"); - query.setEntity("node", node); - query.setParameter("name", name); - return (AVMNodeProperty)query.uniqueResult(); - } - - /** - * Get all properties owned by the given node. - * @param node The AVMNode. - * @return A List of properties. - */ - @SuppressWarnings("unchecked") - public List get(AVMNode node) - { - Query query = - getSession().createQuery( - "from AVMNodePropertyImpl anp where anp.node = :node"); - query.setEntity("node", node); - return (List)query.list(); - } - - /** - * Save a property. - * @param prop The property to save. - */ - public void save(AVMNodeProperty prop) - { - getSession().save(prop); - } - - /** - * Update a property entry. - * @param prop The property. - */ - public void update(AVMNodeProperty prop) - { - // Do nothing for Hibernate. - } - - /** - * Delete all properties associated with a node. - * @param node The AVMNode whose properties should be deleted. - */ - public void deleteAll(AVMNode node) - { - Query delete = - getSession().createQuery("delete from AVMNodePropertyImpl anp where anp.node = :node"); - delete.setEntity("node", node); - delete.executeUpdate(); - } - - /** - * Delete the given property from the given node. - * @param node The node to delete the property to delete. - * @param name The name of the property to delete. - */ - public void delete(AVMNode node, QName name) - { - Query delete = - getSession().createQuery("delete from AVMNodePropertyImpl anp where anp.node = :node " + - "and name = :name"); - delete.setEntity("node", node); - delete.setParameter("name", name); - delete.executeUpdate(); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMNodePropertyDAO#iterate() - */ - @SuppressWarnings("unchecked") - public Iterator iterate() - { - Query query = - getSession().createQuery("from AVMNodePropertyImpl anp"); - return (Iterator)query.iterate(); - } -} diff --git a/source/java/org/alfresco/repo/cache/CacheTest.java b/source/java/org/alfresco/repo/cache/CacheTest.java index 51c24381d0..69aba592af 100644 --- a/source/java/org/alfresco/repo/cache/CacheTest.java +++ b/source/java/org/alfresco/repo/cache/CacheTest.java @@ -44,7 +44,6 @@ import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.dao.DataAccessException; /** * @see org.alfresco.repo.cache.EhCacheAdapter diff --git a/source/java/org/alfresco/repo/cache/TransactionalCache.java b/source/java/org/alfresco/repo/cache/TransactionalCache.java index 6306066975..cd99cbe852 100644 --- a/source/java/org/alfresco/repo/cache/TransactionalCache.java +++ b/source/java/org/alfresco/repo/cache/TransactionalCache.java @@ -127,6 +127,7 @@ public class TransactionalCache { return false; } + @SuppressWarnings("unchecked") TransactionalCache that = (TransactionalCache) obj; return EqualsHelper.nullSafeEquals(this.name, that.name); } @@ -565,14 +566,22 @@ public class TransactionalCache } /** - * Merge the transactional caches into the shared cache + * NO-OP */ @SuppressWarnings("unchecked") public void beforeCommit(boolean readOnly) + { + } + + /** + * Merge the transactional caches into the shared cache + */ + @SuppressWarnings("unchecked") + public void afterCommit() { if (isDebugEnabled) { - logger.debug("Processing before-commit"); + logger.debug("Processing after-commit"); } TransactionData txnData = getTransactionData(); @@ -609,7 +618,7 @@ public class TransactionalCache { Element element = txnData.updatedItemsCache.get(key); CacheBucket bucket = (CacheBucket) element.getObjectValue(); - sharedCache.put(key, bucket.getValue()); + bucket.doPostCommit(sharedCache, key); } if (isDebugEnabled) { @@ -626,14 +635,6 @@ public class TransactionalCache } } - /** - * NO-OP - */ - @SuppressWarnings("unchecked") - public void afterCommit() - { - } - /** * Just allow the transactional caches to be thrown away */ @@ -769,15 +770,8 @@ public class TransactionalCache } public void doPostCommit(SimpleCache sharedCache, Serializable key) { - if (sharedCache.contains(key)) - { - // We remove the shared entry whether it has moved on or not - sharedCache.remove(key); - } - else - { - // The shared cache no longer has a value - } + // We remove the shared entry whether it has moved on or not + sharedCache.remove(key); } } diff --git a/source/java/org/alfresco/repo/domain/AuditableProperties.java b/source/java/org/alfresco/repo/domain/AuditableProperties.java new file mode 100644 index 0000000000..fe294af887 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/AuditableProperties.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.domain; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.QName; + +/** + * Class holding properties associated with the cm:auditable aspect. + * This aspect is common enough to warrant direct inclusion on the Node entity. + * + * @author Derek Hulley + * @since 2.2 SP2 + */ +public class AuditableProperties +{ + private static Set auditablePropertyQNames; + static + { + auditablePropertyQNames = new HashSet(13); + auditablePropertyQNames.add(ContentModel.PROP_CREATOR); + auditablePropertyQNames.add(ContentModel.PROP_CREATED); + auditablePropertyQNames.add(ContentModel.PROP_MODIFIER); + auditablePropertyQNames.add(ContentModel.PROP_MODIFIED); + auditablePropertyQNames.add(ContentModel.PROP_ACCESSED); + } + + /** + * @return Returns true if the property belongs to the cm:auditable aspect + */ + public static boolean isAuditableProperty(QName qname) + { + return auditablePropertyQNames.contains(qname); + } + + private String auditCreator; + private String auditCreated; + private String auditModifier; + private String auditModified; + private String auditAccessed; + + /** + * Default constructor with all null values. + */ + public AuditableProperties() + { + } + + /** + * @param qname the property name + * @return Returns the value of the cm:auditable property or null + */ + public Serializable getAuditableProperty(QName qname) + { + if (qname.equals(ContentModel.PROP_CREATOR)) + { + return auditCreator; + } + else if (qname.equals(ContentModel.PROP_CREATED)) + { + return DefaultTypeConverter.INSTANCE.convert(Date.class, auditCreated); + } + else if (qname.equals(ContentModel.PROP_MODIFIER)) + { + return auditModifier; + } + else if (qname.equals(ContentModel.PROP_MODIFIED)) + { + return DefaultTypeConverter.INSTANCE.convert(Date.class, auditModified); + } + else if (qname.equals(ContentModel.PROP_ACCESSED)) + { + return DefaultTypeConverter.INSTANCE.convert(Date.class, auditAccessed); + } + else + { + return null; + } + } + + /** + * @param qname the property name + * @param value the property value + * @return Returns true if the property was used + * @deprecated Deprecated from the start, but possibly useful code + */ + @SuppressWarnings("unused") + private boolean setAuditableProperty(QName qname, Serializable value) + { + if (qname.equals(ContentModel.PROP_CREATOR)) + { + auditCreator = DefaultTypeConverter.INSTANCE.convert(String.class, value); + return true; + } + if (qname.equals(ContentModel.PROP_MODIFIER)) + { + auditModifier = DefaultTypeConverter.INSTANCE.convert(String.class, value); + return true; + } + if (qname.equals(ContentModel.PROP_CREATED)) + { + auditCreated = DefaultTypeConverter.INSTANCE.convert(String.class, value); + return true; + } + if (qname.equals(ContentModel.PROP_MODIFIED)) + { + auditModified = DefaultTypeConverter.INSTANCE.convert(String.class, value); + return true; + } + if (qname.equals(ContentModel.PROP_ACCESSED)) + { + auditAccessed = DefaultTypeConverter.INSTANCE.convert(String.class, value); + return true; + } + else + { + return false; + } + } + + /** + * @return Returns a Map of auditable properties + */ + public Map getAuditableProperties() + { + Map properties = new HashMap(7); + properties.put(ContentModel.PROP_CREATOR, auditCreator); + properties.put(ContentModel.PROP_CREATED, DefaultTypeConverter.INSTANCE.convert(Date.class, auditCreated)); + // cm:modifier - use cm:creator if not set + if (auditModifier != null) + { + properties.put(ContentModel.PROP_MODIFIER, auditModifier); + } + else + { + properties.put(ContentModel.PROP_MODIFIER, auditCreator); + } + // cm:modified - use cm:created if not set + if (auditModified != null) + { + properties.put(ContentModel.PROP_MODIFIED, DefaultTypeConverter.INSTANCE.convert(Date.class, auditModified)); + } + else + { + properties.put(ContentModel.PROP_MODIFIED, DefaultTypeConverter.INSTANCE.convert(Date.class, auditCreated)); + } + // Usually null + if (auditAccessed != null) + { + properties.put(ContentModel.PROP_ACCESSED, DefaultTypeConverter.INSTANCE.convert(Date.class, auditAccessed)); + } + return properties; + } + + /** + * Set all cm:auditable parameters as required. Where possible, the creation and modification data + * will be shared so as to reduce data duplication. + * + * @param user the username + * @param date the creation or modification date + * @param force true to force the values to overwrite any pre-existing values + */ + public void setAuditValues(String user, Date date, boolean force) + { + String dateStr = DefaultTypeConverter.INSTANCE.convert(String.class, date); + + // Always set cm:creator and cm:created + if (force || auditCreator == null) + { + auditCreator = user; + } + if (force || auditCreated == null) + { + auditCreated = dateStr; + } + auditModifier = user; + auditModified = dateStr; + } + + /** + * For persistance use + */ + @SuppressWarnings("unused") + private String getAuditCreator() + { + return auditCreator; + } + + /** + * For persistance use + */ + @SuppressWarnings("unused") + private void setAuditCreator(String auditCreator) + { + this.auditCreator = auditCreator; + } + + /** + * For persistance use + */ + @SuppressWarnings("unused") + private String getAuditCreated() + { + return auditCreated; + } + + /** + * For persistance use + */ + @SuppressWarnings("unused") + private void setAuditCreated(String auditCreated) + { + this.auditCreated = auditCreated; + } + + /** + * For persistance use + */ + @SuppressWarnings("unused") + private String getAuditModifier() + { + return auditModifier; + } + + /** + * For persistance use + */ + @SuppressWarnings("unused") + private void setAuditModifier(String auditModifier) + { + this.auditModifier = auditModifier; + } + + /** + * For persistance use + */ + @SuppressWarnings("unused") + private String getAuditModified() + { + return auditModified; + } + + /** + * For persistance use + */ + @SuppressWarnings("unused") + private void setAuditModified(String auditModified) + { + this.auditModified = auditModified; + } + + /** + * For persistance use + */ + @SuppressWarnings("unused") + private String getAuditAccessed() + { + return auditAccessed; + } + + /** + * For persistance use + */ + @SuppressWarnings("unused") + private void setAuditAccessed(String auditAccessed) + { + this.auditAccessed = auditAccessed; + } +} diff --git a/source/java/org/alfresco/repo/domain/LocaleDAO.java b/source/java/org/alfresco/repo/domain/LocaleDAO.java new file mode 100644 index 0000000000..b225348c3c --- /dev/null +++ b/source/java/org/alfresco/repo/domain/LocaleDAO.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.domain; + +import java.util.Locale; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.domain.hibernate.DirtySessionAnnotation; +import org.alfresco.util.Pair; + +/** + * Data abstraction layer for Locale entities. + * + * @author Derek Hulley + * @since 2.2.1 + */ +public interface LocaleDAO +{ + /** + * @param id the unique ID of the entity + * @return the locale (never null) + * @throws AlfrescoRuntimeException if the ID provided is invalid + */ + @DirtySessionAnnotation(markDirty=false) + Pair getLocalePair(Long id); + + /** + * @param id the locale to fetch or null to get the default locale + * @return the locale or null if no such locale exists + */ + @DirtySessionAnnotation(markDirty=false) + Pair getLocalePair(Locale locale); + + /** + * @return the locale pair for the default locale. Although the Locale + * object will be populated, the ID will point to an instance that generically + * refers to the system's default locale i.e. the value returned can vary + * depending on the executing thread's default locale. + */ + @DirtySessionAnnotation(markDirty=false) + Pair getDefaultLocalePair(); + + /** + * Gets the locale ID for an existing instance or creates a new entity if + * one doesn't exist. + * + * @param id the locale to fetch or null to get or create the default + * locale. + * @return the locale - never null + */ + @DirtySessionAnnotation(markDirty=true) + Pair getOrCreateLocalePair(Locale locale); + + /** + * Find or create the details representing the default locale. + * + * @return the locale pair for the default locale. Although the Locale + * object will be populated, the ID will point to an instance that generically + * refers to the system's default locale i.e. the value returned can vary + * depending on the executing thread's default locale. + */ + @DirtySessionAnnotation(markDirty=true) + Pair getOrCreateDefaultLocalePair(); +} diff --git a/source/java/org/alfresco/repo/domain/LocaleDAOTest.java b/source/java/org/alfresco/repo/domain/LocaleDAOTest.java new file mode 100644 index 0000000000..eedbabcb7f --- /dev/null +++ b/source/java/org/alfresco/repo/domain/LocaleDAOTest.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import junit.framework.TestCase; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.Pair; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationContext; + +/** + * @see LocaleDAO + * + * + * @author Derek Hulley + * @since 2.2.1 + */ +public class LocaleDAOTest extends TestCase +{ + private static Log logger = LogFactory.getLog(QNameDAOTest.class); + + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private RetryingTransactionHelper retryingTransactionHelper; + private LocaleDAO dao; + + @Override + public void setUp() throws Exception + { + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); + retryingTransactionHelper = serviceRegistry.getTransactionService().getRetryingTransactionHelper(); + dao = (LocaleDAO) ctx.getBean("localeDAO"); + } + + @Override + public void tearDown() throws Exception + { + + } + + public void testDefaultLocale() throws Exception + { + RetryingTransactionCallback> callback = new RetryingTransactionCallback>() + { + public Pair execute() throws Throwable + { + // What is the thread's default locale? + Locale defaultLocale = I18NUtil.getLocale(); + // Now make it + Pair localePair = dao.getOrCreateDefaultLocalePair(); + assertNotNull("Default locale should now exist", localePair); + assertEquals( + "The default locale returned must match the current thread's default locale", + defaultLocale, localePair.getSecond()); + // Done + return localePair; + } + }; + + // Check that the default locale is handled properly + retryingTransactionHelper.doInTransaction(callback); + + // Now change the default locale + I18NUtil.setLocale(Locale.CANADA_FRENCH); + // Repeat + retryingTransactionHelper.doInTransaction(callback); + } + + /** + * Forces a bunch of threads to attempt Locale creation. + */ + public void testConcurrentLocale() throws Throwable + { + final Locale locale = Locale.SIMPLIFIED_CHINESE; + + int threadCount = 50; + final CountDownLatch readyLatch = new CountDownLatch(threadCount); + final CountDownLatch startLatch = new CountDownLatch(1); + final CountDownLatch doneLatch = new CountDownLatch(threadCount); + final List errors = Collections.synchronizedList(new ArrayList(0)); + final RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Long execute() throws Throwable + { + String threadName = Thread.currentThread().getName(); + + // Notify that we are ready + logger.debug("Thread " + threadName + " is READY"); + readyLatch.countDown(); + // Wait for start signal + startLatch.await(); + logger.debug("Thread " + threadName + " is GO"); + // Go + Pair localePair = null; + try + { + // This could fail with concurrency, but that's what we're testing + logger.debug("Thread " + threadName + " is CREATING " + locale); + localePair = dao.getOrCreateLocalePair(locale); + } + catch (Throwable e) + { + logger.debug("Failed to create LocaleEntity. Might retry.", e); + throw e; + } + // Notify the counter that this thread is done + logger.debug("Thread " + threadName + " is DONE"); + doneLatch.countDown(); + // Done + return localePair.getFirst(); + } + }; + Runnable runnable = new Runnable() + { + public void run() + { + try + { + retryingTransactionHelper.doInTransaction(callback); + } + catch (Throwable e) + { + logger.error("Error escaped from retry", e); + errors.add(e); + } + } + }; + // Fire a bunch of threads off + for (int i = 0; i < threadCount; i++) + { + Thread thread = new Thread(runnable, getName() + "-" + i); + thread.setDaemon(true); // Just in case there are complications + thread.start(); + } + // Wait for threads to be ready + readyLatch.await(5, TimeUnit.SECONDS); + // Let the threads go + startLatch.countDown(); + // Wait for them all to be done (within limit of 10 seconds per thread) + if (doneLatch.await(threadCount * 10, TimeUnit.SECONDS)) + { + logger.warn("Still waiting for threads to finish after " + threadCount + " seconds."); + } + // Check if there are errors + if (errors.size() > 0) + { + throw errors.get(0); + } + } +} diff --git a/source/java/org/alfresco/repo/domain/NodeStatus.java b/source/java/org/alfresco/repo/domain/LocaleEntity.java similarity index 62% rename from source/java/org/alfresco/repo/domain/NodeStatus.java rename to source/java/org/alfresco/repo/domain/LocaleEntity.java index 1a58036ce7..d5880ae433 100644 --- a/source/java/org/alfresco/repo/domain/NodeStatus.java +++ b/source/java/org/alfresco/repo/domain/LocaleEntity.java @@ -1,62 +1,47 @@ -/* - * Copyright (C) 2005-2007 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; - -/** - * Interface for persistent node status objects. - *

- * The node status records the liveness and change times of a node. It follows - * that a node might not exist (have been deleted) when the - * node status still exists. - * - * @author Derek Hulley - */ -public interface NodeStatus -{ - /** - * @return Returns the unique key for this node status - */ - public NodeKey getKey(); - - /** - * @param key the unique key - */ - public void setKey(NodeKey key); - - /** - * @return Returns the current version number - */ - public Long getVersion(); - - public Node getNode(); - - public void setNode(Node node); - - public Transaction getTransaction(); - - public void setTransaction(Transaction transaction); - - public boolean isDeleted(); -} +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.domain; + +import java.util.Locale; + +/** + * Represents a persistable Locale entity. + * + * @author Derek Hulley + * @since 2.2.1 + */ +public interface LocaleEntity +{ + Long getId(); + + Locale getLocale(); + + /** + * @param locale the locale to set or null to represent the default locale + */ + void setLocale(Locale locale); + + public String getLocaleStr(); +} diff --git a/source/java/org/alfresco/repo/domain/Node.java b/source/java/org/alfresco/repo/domain/Node.java index d95854a79f..c403ffe960 100644 --- a/source/java/org/alfresco/repo/domain/Node.java +++ b/source/java/org/alfresco/repo/domain/Node.java @@ -62,16 +62,28 @@ public interface Node public String getUuid(); public void setUuid(String uuid); + + public Transaction getTransaction(); + + public void setTransaction(Transaction transaction); + + public boolean getDeleted(); + + public void setDeleted(boolean deleted); public QNameEntity getTypeQName(); public void setTypeQName(QNameEntity typeQName); - public Set getAspects(); - - public Map getProperties(); - public DbAccessControlList getAccessControlList(); public void setAccessControlList(DbAccessControlList accessControlList); + + public Set getAspects(); + + public Map getProperties(); + + public AuditableProperties getAuditableProperties(); + + public void setAuditableProperties(AuditableProperties auditableProperties); } diff --git a/source/java/org/alfresco/repo/domain/NodeKey.java b/source/java/org/alfresco/repo/domain/NodeKey.java deleted file mode 100644 index d036dfb210..0000000000 --- a/source/java/org/alfresco/repo/domain/NodeKey.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2005-2007 Alfresco Software Limited. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - * As a special exception to the terms and conditions of version 2.0 of - * the GPL, you may redistribute this Program in connection with Free/Libre - * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have recieved a copy of the text describing - * the FLOSS exception, and it is also available here: - * http://www.alfresco.com/legal/licensing" - */ -package org.alfresco.repo.domain; - -import java.io.Serializable; - -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.util.EqualsHelper; - -/** - * Compound key for persistence of {@link org.alfresco.repo.domain.Node} - * - * @author Derek Hulley - */ -public class NodeKey implements Serializable -{ - private static final long serialVersionUID = 3258695403221300023L; - - private String guid; - private String protocol; - private String identifier; - - public NodeKey() - { - } - - public NodeKey(NodeRef nodeRef) - { - this(nodeRef.getStoreRef(), nodeRef.getId()); - } - - public NodeKey(StoreRef storeRef, String guid) - { - setGuid(guid); - setProtocol(storeRef.getProtocol()); - setIdentifier(storeRef.getIdentifier()); - } - - public NodeKey(StoreKey storeKey, String guid) - { - setGuid(guid); - setProtocol(storeKey.getProtocol()); - setIdentifier(storeKey.getIdentifier()); - } - - public NodeKey(String protocol, String identifier, String guid) - { - setGuid(guid); - setProtocol(protocol); - setIdentifier(identifier); - } - - public String toString() - { - return ("NodeKey[" + - " id=" + guid + - ", protocol=" + protocol + - ", identifier=" + identifier + - "]"); - } - - public int hashCode() - { - return this.guid.hashCode(); - } - - public boolean equals(Object obj) - { - if (this == obj) - { - return true; - } - else if (!(obj instanceof NodeKey)) - { - return false; - } - NodeKey that = (NodeKey) obj; - return (EqualsHelper.nullSafeEquals(this.guid, that.guid) && - EqualsHelper.nullSafeEquals(this.protocol, that.protocol) && - EqualsHelper.nullSafeEquals(this.identifier, that.identifier) - ); - } - - public String getGuid() - { - return guid; - } - - /** - * Tamper-proof method only to be used by introspectors - */ - private void setGuid(String id) - { - this.guid = id; - } - - public String getProtocol() - { - return protocol; - } - - /** - * Tamper-proof method only to be used by introspectors - */ - private void setProtocol(String protocol) - { - this.protocol = protocol; - } - - public String getIdentifier() - { - return identifier; - } - - /** - * Tamper-proof method only to be used by introspectors - */ - private void setIdentifier(String identifier) - { - this.identifier = identifier; - } -} diff --git a/source/java/org/alfresco/repo/domain/NodePropertyValue.java b/source/java/org/alfresco/repo/domain/NodePropertyValue.java new file mode 100644 index 0000000000..d084ce92d6 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/NodePropertyValue.java @@ -0,0 +1,1066 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.domain; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.domain.schema.SchemaBootstrap; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.MLText; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.EqualsHelper; +import org.alfresco.util.ParameterCheck; +import org.alfresco.util.VersionNumber; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Immutable property value storage class. + * + * @author Derek Hulley + */ +public class NodePropertyValue implements Cloneable, Serializable +{ + private static final long serialVersionUID = -497902497351493075L; + + /** used to take care of empty strings being converted to nulls by the database */ + private static final String STRING_EMPTY = ""; + /** used to provide empty collection values in and out */ + public static final Serializable EMPTY_COLLECTION_VALUE = (Serializable) Collections.emptyList(); + + private static Log logger = LogFactory.getLog(NodePropertyValue.class); + private static Log loggerOracle = LogFactory.getLog(NodePropertyValue.class.getName() + ".oracle"); + + /** potential value types */ + private static enum ValueType + { + NULL + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(0); + } + + @Override + Serializable convert(Serializable value) + { + return null; + } + }, + BOOLEAN + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(1); + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(Boolean.class, value); + } + }, + INTEGER + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(2); + } + + @Override + protected ValueType getPersistedType(Serializable value) + { + return ValueType.LONG; + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(Integer.class, value); + } + }, + LONG + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(3); + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(Long.class, value); + } + }, + FLOAT + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(4); + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(Float.class, value); + } + }, + DOUBLE + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(5); + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(Double.class, value); + } + }, + STRING + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(6); + } + + /** + * Strings longer than the maximum of {@link NodePropertyValue#DEFAULT_MAX_STRING_LENGTH} + * characters will be serialized. + */ + @Override + protected ValueType getPersistedType(Serializable value) + { + if (value instanceof String) + { + String valueStr = (String) value; + // Check how long the String can be + if (valueStr.length() > SchemaBootstrap.getMaxStringLength()) + { + return ValueType.SERIALIZABLE; + } + } + return ValueType.STRING; + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(String.class, value); + } + }, + DATE + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(7); + } + + @Override + protected ValueType getPersistedType(Serializable value) + { + return ValueType.STRING; + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(Date.class, value); + } + }, + SERIALIZABLE + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(9); + } + + @Override + Serializable convert(Serializable value) + { + return value; + } + }, + MLTEXT + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(10); + } + + @Override + protected ValueType getPersistedType(Serializable value) + { + if (value instanceof MLText) + { + throw new IllegalArgumentException("MLText must be split up before persistence."); + } + return ValueType.STRING; + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(String.class, value); + } + }, + CONTENT + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(11); + } + + @Override + protected ValueType getPersistedType(Serializable value) + { + return ValueType.STRING; + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(ContentData.class, value); + } + }, + NODEREF + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(12); + } + + @Override + protected ValueType getPersistedType(Serializable value) + { + return ValueType.STRING; + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(NodeRef.class, value); + } + }, + CHILD_ASSOC_REF + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(13); + } + + @Override + protected ValueType getPersistedType(Serializable value) + { + return ValueType.STRING; + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(ChildAssociationRef.class, value); + } + }, + ASSOC_REF + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(14); + } + + @Override + protected ValueType getPersistedType(Serializable value) + { + return ValueType.STRING; + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(AssociationRef.class, value); + } + }, + QNAME + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(15); + } + + @Override + protected ValueType getPersistedType(Serializable value) + { + return ValueType.STRING; + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(QName.class, value); + } + }, + PATH + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(16); + } + + @Override + protected ValueType getPersistedType(Serializable value) + { + return ValueType.SERIALIZABLE; + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(Path.class, value); + } + }, + LOCALE + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(17); + } + + @Override + protected ValueType getPersistedType(Serializable value) + { + return ValueType.STRING; + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(Locale.class, value); + } + }, + VERSION_NUMBER + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(18); + } + + @Override + protected ValueType getPersistedType(Serializable value) + { + return ValueType.STRING; + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(VersionNumber.class, value); + } + }, + COLLECTION + { + @Override + public Integer getOrdinalNumber() + { + return Integer.valueOf(19); + } + + @SuppressWarnings("unchecked") + @Override + protected ValueType getPersistedType(Serializable value) + { + if (value instanceof Collection) + { + Collection collectionValue = (Collection) value; + if (collectionValue.size() == 0) + { + return ValueType.NULL; + } + } + return ValueType.SERIALIZABLE; + } + + /** + * @return Returns and empty Collection if the value is null + * otherwise it just returns the original value + */ + @Override + Serializable convert(Serializable value) + { + if (value == null) + { + return (Serializable) Collections.emptyList(); + } + else + { + return value; + } + } + }; + + /** + * @return Returns the manually-maintained ordinal number for the value + */ + public abstract Integer getOrdinalNumber(); + + /** + * Override if the type gets persisted in a different format. + * + * @param value the actual value that is to be persisted. May not be null. + */ + protected ValueType getPersistedType(Serializable value) + { + return this; + } + + /** + * Converts a value to this type. The implementation must be able to cope with any legitimate + * source value. + * + * @see DefaultTypeConverter.INSTANCE#convert(Class, Object) + */ + abstract Serializable convert(Serializable value); + } + + /** + * Determine the actual value type to aid in more concise persistence. + * + * @param value the value that is to be persisted + * @return Returns the value type equivalent of the + */ + private static ValueType getActualType(Serializable value) + { + if (value == null) + { + return ValueType.NULL; + } + else if (value instanceof Boolean) + { + return ValueType.BOOLEAN; + } + else if ((value instanceof Integer) || (value instanceof Long)) + { + return ValueType.LONG; + } + else if (value instanceof Float) + { + return ValueType.FLOAT; + } + else if (value instanceof Double) + { + return ValueType.DOUBLE; + } + else if (value instanceof String) + { + return ValueType.STRING; + } + else if (value instanceof Date) + { + return ValueType.DATE; + } + else if (value instanceof ContentData) + { + return ValueType.CONTENT; + } + else if (value instanceof NodeRef) + { + return ValueType.NODEREF; + } + else if (value instanceof ChildAssociationRef) + { + return ValueType.CHILD_ASSOC_REF; + } + else if (value instanceof AssociationRef) + { + return ValueType.ASSOC_REF; + } + else if (value instanceof QName) + { + return ValueType.QNAME; + } + else if (value instanceof Path) + { + return ValueType.PATH; + } + else if (value instanceof Locale) + { + return ValueType.LOCALE; + } + else if (value instanceof VersionNumber) + { + return ValueType.VERSION_NUMBER; + } + else if (value instanceof Collection) + { + return ValueType.COLLECTION; + } + else if (value instanceof MLText) + { + return ValueType.MLTEXT; + } + else + { + // type is not recognised as belonging to any particular slot + return ValueType.SERIALIZABLE; + } + } + + /** a mapping from a property type QName to the corresponding value type */ + private static Map valueTypesByPropertyType; + /** + * a mapping of {@link ValueType} ordinal number to the enum. This is manually maintained + * and MUST NOT BE CHANGED FOR EXISTING VALUES. + */ + private static Map valueTypesByOrdinalNumber; + static + { + valueTypesByPropertyType = new HashMap(37); + valueTypesByPropertyType.put(DataTypeDefinition.ANY, ValueType.SERIALIZABLE); + valueTypesByPropertyType.put(DataTypeDefinition.BOOLEAN, ValueType.BOOLEAN); + valueTypesByPropertyType.put(DataTypeDefinition.INT, ValueType.INTEGER); + valueTypesByPropertyType.put(DataTypeDefinition.LONG, ValueType.LONG); + valueTypesByPropertyType.put(DataTypeDefinition.DOUBLE, ValueType.DOUBLE); + valueTypesByPropertyType.put(DataTypeDefinition.FLOAT, ValueType.FLOAT); + valueTypesByPropertyType.put(DataTypeDefinition.DATE, ValueType.DATE); + valueTypesByPropertyType.put(DataTypeDefinition.DATETIME, ValueType.DATE); + valueTypesByPropertyType.put(DataTypeDefinition.CATEGORY, ValueType.NODEREF); + valueTypesByPropertyType.put(DataTypeDefinition.CONTENT, ValueType.CONTENT); + valueTypesByPropertyType.put(DataTypeDefinition.TEXT, ValueType.STRING); + valueTypesByPropertyType.put(DataTypeDefinition.MLTEXT, ValueType.MLTEXT); + valueTypesByPropertyType.put(DataTypeDefinition.NODE_REF, ValueType.NODEREF); + valueTypesByPropertyType.put(DataTypeDefinition.CHILD_ASSOC_REF, ValueType.CHILD_ASSOC_REF); + valueTypesByPropertyType.put(DataTypeDefinition.ASSOC_REF, ValueType.ASSOC_REF); + valueTypesByPropertyType.put(DataTypeDefinition.PATH, ValueType.PATH); + valueTypesByPropertyType.put(DataTypeDefinition.QNAME, ValueType.QNAME); + valueTypesByPropertyType.put(DataTypeDefinition.LOCALE, ValueType.LOCALE); + + valueTypesByOrdinalNumber = new HashMap(37); + for (ValueType valueType : ValueType.values()) + { + Integer ordinalNumber = valueType.getOrdinalNumber(); + if (valueTypesByOrdinalNumber.containsKey(ordinalNumber)) + { + throw new RuntimeException("ValueType has duplicate ordinal number: " + valueType); + } + else if (ordinalNumber.intValue() == -1) + { + throw new RuntimeException("ValueType doesn't have an ordinal number: " + valueType); + } + valueTypesByOrdinalNumber.put(ordinalNumber, valueType); + } + } + + /** + * Helper method to convert the type QName into a ValueType + * + * @return Returns the ValueType - never null + */ + private static ValueType makeValueType(QName typeQName) + { + ValueType valueType = valueTypesByPropertyType.get(typeQName); + if (valueType == null) + { + throw new AlfrescoRuntimeException( + "Property type not recognised: \n" + + " type: " + typeQName); + } + return valueType; + } + + /** + * Given an actual type qualified name, returns the int ordinal number + * that represents it in the database. + * + * @param typeQName the type qualified name + * @return Returns the int representation of the type, + * e.g. CONTENT.getOrdinalNumber() for type d:content. + */ + public static int convertToTypeOrdinal(QName typeQName) + { + ValueType valueType = makeValueType(typeQName); + return valueType.getOrdinalNumber(); + } + + /** the type of the property, prior to serialization persistence */ + private ValueType actualType; + /** the type of persistence used */ + private ValueType persistedType; + + private Boolean booleanValue; + private Long longValue; + private Float floatValue; + private Double doubleValue; + private String stringValue; + private Serializable serializableValue; + + /** + * default constructor + */ + public NodePropertyValue() + { + } + + /** + * Construct a new property value. + * + * @param typeQName the dictionary-defined property type to store the property as + * @param value the value to store. This will be converted into a format compatible + * with the type given + * + * @throws java.lang.UnsupportedOperationException if the value cannot be converted to the type given + */ + public NodePropertyValue(QName typeQName, Serializable value) + { + ParameterCheck.mandatory("typeQName", typeQName); + + this.actualType = NodePropertyValue.getActualType(value); + if (value == null) + { + setPersistedValue(ValueType.NULL, null); + } + else + { + // Convert the value to the type required. This ensures that any type conversion issues + // are caught early and prevent the scenario where the data in the DB cannot be given + // back out because it is unconvertable. + ValueType valueType = makeValueType(typeQName); + value = valueType.convert(value); + // get the persisted type + ValueType persistedValueType = this.actualType.getPersistedType(value); + // convert to the persistent type + value = persistedValueType.convert(value); + setPersistedValue(persistedValueType, value); + } + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (obj instanceof NodePropertyValue) + { + NodePropertyValue that = (NodePropertyValue) obj; + return (this.actualType.equals(that.actualType) && + EqualsHelper.nullSafeEquals(this.booleanValue, that.booleanValue) && + EqualsHelper.nullSafeEquals(this.longValue, that.longValue) && + EqualsHelper.nullSafeEquals(this.floatValue, that.floatValue) && + EqualsHelper.nullSafeEquals(this.doubleValue, that.doubleValue) && + EqualsHelper.nullSafeEquals(this.stringValue, that.stringValue) && + EqualsHelper.nullSafeEquals(this.serializableValue, that.serializableValue) + ); + + } + else + { + return false; + } + } + + @Override + public int hashCode() + { + int h = 0; + if (actualType != null) + h = actualType.hashCode(); + Serializable persistedValue = getPersistedValue(); + if (persistedValue != null) + h += 17 * persistedValue.hashCode(); + return h; + } + + @Override + public Object clone() throws CloneNotSupportedException + { + return super.clone(); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(128); + sb.append("PropertyValue") + .append("[actual-type=").append(actualType) + .append(", value-type=").append(persistedType) + .append(", value=").append(getPersistedValue()) + .append("]"); + return sb.toString(); + } + + public Integer getActualType() + { + return actualType == null ? null : actualType.getOrdinalNumber(); + } + + /** + * @return Returns the actual type's String representation + */ + public String getActualTypeString() + { + return actualType == null ? null : actualType.toString(); + } + + public void setActualType(Integer actualType) + { + ValueType type = NodePropertyValue.valueTypesByOrdinalNumber.get(actualType); + if (type == null) + { + logger.error("Unknown property actual type ordinal number: " + actualType); + } + this.actualType = type; + } + + public Integer getPersistedType() + { + return persistedType == null ? null : persistedType.getOrdinalNumber(); + } + public void setPersistedType(Integer persistedType) + { + ValueType type = NodePropertyValue.valueTypesByOrdinalNumber.get(persistedType); + if (type == null) + { + logger.error("Unknown property persisted type ordinal number: " + persistedType); + } + this.persistedType = type; + } + + /** + * Stores the value in the correct slot based on the type of persistence requested. + * No conversion is done. + * + * @param persistedType the value type + * @param value the value - it may only be null if the persisted type is {@link ValueType#NULL} + */ + public void setPersistedValue(ValueType persistedType, Serializable value) + { + switch (persistedType) + { + case NULL: + if (value != null) + { + throw new AlfrescoRuntimeException("Value must be null for persisted type: " + persistedType); + } + break; + case BOOLEAN: + this.booleanValue = (Boolean) value; + break; + case LONG: + this.longValue = (Long) value; + break; + case FLOAT: + this.floatValue = (Float) value; + break; + case DOUBLE: + this.doubleValue = (Double) value; + break; + case STRING: + this.stringValue = (String) value; + break; + case SERIALIZABLE: + this.serializableValue = cloneSerializable(value); + break; + default: + throw new AlfrescoRuntimeException("Unrecognised value type: " + persistedType); + } + // we store the type that we persisted as + this.persistedType = persistedType; + } + + /** + * Clones a serializable object to disconnect the original instance from the persisted instance. + * + * @param original the original object + * @return the new cloned object + */ + private Serializable cloneSerializable(Serializable original) + { + ObjectOutputStream objectOut = null; + ByteArrayOutputStream byteOut = null; + ObjectInputStream objectIn = null; + try + { + // Write the object out to a byte array + byteOut = new ByteArrayOutputStream(); + objectOut = new ObjectOutputStream(byteOut); + objectOut.writeObject(original); + objectOut.flush(); + + objectIn = new ObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray())); + Object target = objectIn.readObject(); + // Done + return (Serializable) target; + } + catch (Throwable e) + { + throw new AlfrescoRuntimeException("Failed to clone serializable object: " + original, e); + } + finally + { + if (objectOut != null) + { + try { objectOut.close(); } catch (Throwable e) {} + } + if (byteOut != null) + { + try { byteOut.close(); } catch (Throwable e) {} + } + if (objectIn != null) + { + try { objectIn.close(); } catch (Throwable e) {} + } + } + } + + /** + * @return Returns the persisted value, keying off the persisted value type + */ + private Serializable getPersistedValue() + { + switch (persistedType) + { + case NULL: + return null; + case BOOLEAN: + return this.booleanValue; + case LONG: + return this.longValue; + case FLOAT: + return this.floatValue; + case DOUBLE: + return this.doubleValue; + case STRING: + // Oracle stores empty strings as 'null'... + if (this.stringValue == null) + { + // We know that we stored a non-null string, but now it is null. + // It can only mean one thing - Oracle + if (loggerOracle.isDebugEnabled()) + { + logger.debug("string_value is 'null'. Forcing to empty String"); + } + return NodePropertyValue.STRING_EMPTY; + } + else + { + return this.stringValue; + } + case SERIALIZABLE: + return this.serializableValue; + default: + throw new AlfrescoRuntimeException("Unrecognised value type: " + persistedType); + } + } + + /** + * Fetches the value as a desired type. Collections (i.e. multi-valued properties) + * will be converted as a whole to ensure that all the values returned within the + * collection match the given type. + * + * @param typeQName the type required for the return value + * @return Returns the value of this property as the desired type, or a Collection + * of values of the required type + * + * @throws AlfrescoRuntimeException + * if the type given is not recognized + * @throws org.alfresco.service.cmr.repository.datatype.TypeConversionException + * if the conversion to the required type fails + * + * @see DataTypeDefinition#ANY The static qualified names for the types + */ + public Serializable getValue(QName typeQName) + { + // first check for null + ValueType requiredType = makeValueType(typeQName); + if (requiredType == ValueType.SERIALIZABLE) + { + // the required type must be the actual type + requiredType = this.actualType; + } + + // we need to convert + Serializable ret = null; + if (actualType == ValueType.COLLECTION && persistedType == ValueType.NULL) + { + // This is a special case of an empty collection + ret = (Serializable) Collections.emptyList(); + } + else if (persistedType == ValueType.NULL) + { + ret = null; + } + else + { + Serializable persistedValue = getPersistedValue(); + // convert the type + // In order to cope with historical data, where collections were serialized + // regardless of type. + if (persistedValue instanceof Collection) + { + // We assume that the collection contained the correct type values. They would + // have been converted on the way in. + ret = (Serializable) persistedValue; + } + else + { + ret = requiredType.convert(persistedValue); + } + } + // done + if (logger.isDebugEnabled()) + { + logger.debug("Fetched value: \n" + + " property value: " + this + "\n" + + " requested type: " + requiredType + "\n" + + " result: " + ret); + } + return ret; + } + + /** + * Gets the value or values as a guaranteed collection. + * + * @see #getValue(QName) + */ + @SuppressWarnings("unchecked") + public Collection getCollection(QName typeQName) + { + Serializable value = getValue(typeQName); + if (value instanceof Collection) + { + return (Collection) value; + } + else + { + return Collections.singletonList(value); + } + } + + public boolean getBooleanValue() + { + if (booleanValue == null) + return false; + else + return booleanValue.booleanValue(); + } + public void setBooleanValue(boolean value) + { + this.booleanValue = Boolean.valueOf(value); + } + + public long getLongValue() + { + if (longValue == null) + return 0; + else + return longValue.longValue(); + } + public void setLongValue(long value) + { + this.longValue = Long.valueOf(value); + } + + public float getFloatValue() + { + if (floatValue == null) + return 0.0F; + else + return floatValue.floatValue(); + } + public void setFloatValue(float value) + { + this.floatValue = Float.valueOf(value); + } + + public double getDoubleValue() + { + if (doubleValue == null) + return 0.0; + else + return doubleValue.doubleValue(); + } + public void setDoubleValue(double value) + { + this.doubleValue = Double.valueOf(value); + } + + public String getStringValue() + { + return stringValue; + } + public void setStringValue(String value) + { + this.stringValue = value; + } + + public Serializable getSerializableValue() + { + return serializableValue; + } + public void setSerializableValue(Serializable value) + { + this.serializableValue = value; + } +} diff --git a/source/java/org/alfresco/repo/domain/PropertyMapKey.java b/source/java/org/alfresco/repo/domain/PropertyMapKey.java new file mode 100644 index 0000000000..3ad93f2eb7 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/PropertyMapKey.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.domain; + +import java.io.Serializable; + +import org.alfresco.util.EqualsHelper; + +/** + * Compound key for persistence of {@link org.alfresco.repo.domain.Node} + * + * @author Derek Hulley + */ +public class PropertyMapKey implements Serializable, Comparable +{ + private static final long serialVersionUID = 3258695403221300023L; + + private Long qnameId; + private Long localeId; + private Short listIndex; + + public PropertyMapKey() + { + } + + public String toString() + { + return ("PropertymapKey[" + + " qnameId=" + qnameId + + ", localeId=" + localeId + + ", listIndex=" + listIndex + + "]"); + } + + public int hashCode() + { + return + (qnameId == null ? 0 : qnameId.hashCode()) + + (listIndex == null ? 0 : listIndex.hashCode()); + } + + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + else if (!(obj instanceof PropertyMapKey)) + { + return false; + } + PropertyMapKey that = (PropertyMapKey) obj; + return (EqualsHelper.nullSafeEquals(this.qnameId, that.qnameId) && + EqualsHelper.nullSafeEquals(this.listIndex, that.listIndex) && + EqualsHelper.nullSafeEquals(this.localeId, that.localeId) + ); + } + + /** + * throws ClassCastException if the object is not of the correct type + */ + public int compareTo(PropertyMapKey that) + { + // Comparision by priority: qnameId, listIndex, localeId + if (this.qnameId.equals(that.qnameId)) + { + if (this.listIndex.equals(that.listIndex)) + { + return this.localeId.compareTo(that.localeId); + } + else + { + return this.listIndex.compareTo(that.listIndex); + } + } + else + { + return this.qnameId.compareTo(that.qnameId); + } + } + + public Long getQnameId() + { + return qnameId; + } + + public void setQnameId(Long qnameId) + { + this.qnameId = qnameId; + } + + public Long getLocaleId() + { + return localeId; + } + + public void setLocaleId(Long localeId) + { + this.localeId = localeId; + } + + public Short getListIndex() + { + return listIndex; + } + + public void setListIndex(Short listIndex) + { + this.listIndex = listIndex; + } +} diff --git a/source/java/org/alfresco/repo/domain/PropertyValue.java b/source/java/org/alfresco/repo/domain/PropertyValue.java index aa828c1392..fa8c9a0cac 100644 --- a/source/java/org/alfresco/repo/domain/PropertyValue.java +++ b/source/java/org/alfresco/repo/domain/PropertyValue.java @@ -57,6 +57,8 @@ import org.apache.commons.logging.LogFactory; /** * Immutable property value storage class. + *

+ * As of 2.2.1, this class is only used by the AVM persistence layers. * * @author Derek Hulley */ diff --git a/source/java/org/alfresco/repo/domain/QNameDAO.java b/source/java/org/alfresco/repo/domain/QNameDAO.java index 5f18857890..06f7f7aa44 100644 --- a/source/java/org/alfresco/repo/domain/QNameDAO.java +++ b/source/java/org/alfresco/repo/domain/QNameDAO.java @@ -29,6 +29,7 @@ import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; /** * Data abstraction layer for QName and Namespace entities. @@ -107,6 +108,8 @@ public interface QNameDAO */ QNameEntity getOrCreateQNameEntity(QName qname); + Pair getOrCreateQNamePair(QName qname); + /** * @param qname the QName to create * @return the new instance @@ -116,4 +119,6 @@ public interface QNameDAO Set convertIdsToQNames(Set ids); Map convertIdMapToQNameMap(Map idMap); + + Set convertQNamesToIds(Set qnames, boolean create); } diff --git a/source/java/org/alfresco/repo/domain/QNameDAOTest.java b/source/java/org/alfresco/repo/domain/QNameDAOTest.java index 08f8c80ffb..61d07b5249 100644 --- a/source/java/org/alfresco/repo/domain/QNameDAOTest.java +++ b/source/java/org/alfresco/repo/domain/QNameDAOTest.java @@ -141,6 +141,39 @@ public class QNameDAOTest extends TestCase retryingTransactionHelper.doInTransaction(callback); } + public void testGetQNameManyTimes() throws Exception + { + final String namespaceUri = GUID.generate(); + final String localName = GUID.generate(); + final QName qname = QName.createQName(namespaceUri, localName); + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public QNameEntity execute() throws Throwable + { + QNameEntity qnameEntity = dao.getQNameEntity(qname); + assertNull("QName should not exist yet", qnameEntity); + // Now make it + qnameEntity = dao.newQNameEntity(qname); + assertNotNull("QName should now exist", dao.getQNameEntity(qname)); + // Done + return qnameEntity; + } + }; + retryingTransactionHelper.doInTransaction(callback); + callback = new RetryingTransactionCallback() + { + public QNameEntity execute() throws Throwable + { + for (int i = 0; i < 1000; i++) + { + dao.getQNameEntity(qname); + } + return null; + } + }; + retryingTransactionHelper.doInTransaction(callback); + } + /** * Forces a bunch of threads to attempt QName creation at exactly the same time * for their first attempt. The subsequent retries should all succeed by diff --git a/source/java/org/alfresco/repo/domain/Store.java b/source/java/org/alfresco/repo/domain/Store.java index c05c7aa91d..cb0abc10ff 100644 --- a/source/java/org/alfresco/repo/domain/Store.java +++ b/source/java/org/alfresco/repo/domain/Store.java @@ -24,7 +24,6 @@ */ package org.alfresco.repo.domain; -import org.alfresco.repo.domain.StoreKey; import org.alfresco.service.cmr.repository.StoreRef; /** @@ -34,21 +33,36 @@ import org.alfresco.service.cmr.repository.StoreRef; */ public interface Store { - /** - * @return Returns the key for the class - */ - public StoreKey getKey(); - /** * @return Returns the current version number used for optimistic locking */ public Long getVersion(); + + /** + * @return Returns the unique ID of the object + */ + public Long getId(); /** - * @param key the key uniquely identifying this store + * @return the store protocol */ - public void setKey(StoreKey key); + public String getProtocol(); + /** + * @param protocol the store protocol + */ + public void setProtocol(String protocol); + + /** + * @return the store identifier + */ + public String getIdentifier(); + + /** + * @param identifier the store identifier + */ + public void setIdentifier(String identifier); + /** * @return Returns the root of the store */ diff --git a/source/java/org/alfresco/repo/domain/StoreKey.java b/source/java/org/alfresco/repo/domain/StoreKey.java deleted file mode 100644 index 6eb839a3fa..0000000000 --- a/source/java/org/alfresco/repo/domain/StoreKey.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2005-2007 Alfresco Software Limited. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - * As a special exception to the terms and conditions of version 2.0 of - * the GPL, you may redistribute this Program in connection with Free/Libre - * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have recieved a copy of the text describing - * the FLOSS exception, and it is also available here: - * http://www.alfresco.com/legal/licensing" - */ -package org.alfresco.repo.domain; - -import java.io.Serializable; - -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.util.EqualsHelper; - -/** - * Compound key for persistence of {@link org.alfresco.repo.domain.Store} - * - * @author Derek Hulley - */ -public class StoreKey implements Serializable -{ - private static final long serialVersionUID = 3618140052220096569L; - - private String protocol; - private String identifier; - - public StoreKey() - { - } - - public StoreKey(String protocol, String identifier) - { - setProtocol(protocol); - setIdentifier(identifier); - } - - public StoreKey(StoreRef storeRef) - { - this(storeRef.getProtocol(), storeRef.getIdentifier()); - } - - public String toString() - { - return ("StoreKey[" + - " protocol=" + protocol + - ", identifier=" + identifier + - "]"); - } - - public int hashCode() - { - return (this.protocol.hashCode() + this.identifier.hashCode()); - } - - public boolean equals(Object obj) - { - if (this == obj) - { - return true; - } - else if (!(obj instanceof StoreKey)) - { - return false; - } - StoreKey that = (StoreKey) obj; - return (EqualsHelper.nullSafeEquals(this.protocol, that.protocol) && - EqualsHelper.nullSafeEquals(this.identifier, that.identifier)); - } - - public String getProtocol() - { - return protocol; - } - - /** - * Tamper-proof method only to be used by introspectors - */ - private void setProtocol(String protocol) - { - this.protocol = protocol; - } - - public String getIdentifier() - { - return identifier; - } - - /** - * Tamper-proof method only to be used by introspectors - */ - private void setIdentifier(String identifier) - { - this.identifier = identifier; - } -} diff --git a/source/java/org/alfresco/repo/domain/VersionCount.java b/source/java/org/alfresco/repo/domain/VersionCount.java index bf843ab552..be2fd616c2 100644 --- a/source/java/org/alfresco/repo/domain/VersionCount.java +++ b/source/java/org/alfresco/repo/domain/VersionCount.java @@ -32,15 +32,17 @@ package org.alfresco.repo.domain; public interface VersionCount { /** - * @return Returns the key for the version counter + * @return Returns the auto-generated ID */ - public StoreKey getKey(); - - /** - * @param key the key uniquely identifying this version counter - */ - public void setKey(StoreKey key); + public Long getId(); + /** + * @return Returns the associated store + */ + public Store getStore(); + + public void setStore(Store store); + /** * Increments and returns the next version counter associated with this * store. diff --git a/source/java/org/alfresco/repo/domain/hibernate/DirtySessionMethodInterceptor.java b/source/java/org/alfresco/repo/domain/hibernate/DirtySessionMethodInterceptor.java index aecb4c0036..22c5b9e5f3 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/DirtySessionMethodInterceptor.java +++ b/source/java/org/alfresco/repo/domain/hibernate/DirtySessionMethodInterceptor.java @@ -262,6 +262,15 @@ public class DirtySessionMethodInterceptor implements MethodInterceptor query.setFlushMode(FlushMode.MANUAL); } + /** + * Manually mark the session as dirty. + */ + public static void setSessionDirty() + { + FlushData flushData = DirtySessionMethodInterceptor.getFlushData(); + flushData.incrementDirtyCount(); + } + /** * Flush and reset the dirty count for the current transaction. The session is * only flushed if it currently dirty. diff --git a/source/java/org/alfresco/repo/domain/hibernate/HibernateL1CacheBulkLoader.java b/source/java/org/alfresco/repo/domain/hibernate/HibernateL1CacheBulkLoader.java index 1d19bb660a..c2b15cacb5 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/HibernateL1CacheBulkLoader.java +++ b/source/java/org/alfresco/repo/domain/hibernate/HibernateL1CacheBulkLoader.java @@ -25,12 +25,8 @@ package org.alfresco.repo.domain.hibernate; import java.util.Collection; -import java.util.HashSet; -import java.util.List; import java.util.Map; -import java.util.Set; -import org.alfresco.repo.domain.Node; import org.alfresco.service.cmr.repository.NodeRef; import org.hibernate.CacheMode; import org.hibernate.Criteria; @@ -38,25 +34,23 @@ import org.hibernate.EntityMode; import org.hibernate.FetchMode; import org.hibernate.FlushMode; import org.hibernate.Session; -import org.hibernate.criterion.Projections; -import org.hibernate.criterion.Property; import org.hibernate.criterion.Restrictions; -import org.hibernate.engine.EntityKey; import org.hibernate.metadata.ClassMetadata; import org.hibernate.metadata.CollectionMetadata; -import org.hibernate.stat.SessionStatistics; -import org.hibernate.stat.Statistics; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; /** + * Pre-populates Node entities for a given set of references. + * * @author andyh + * @since 3.0 */ public class HibernateL1CacheBulkLoader extends HibernateDaoSupport implements BulkLoader { public void loadIntoCache(Collection nodeRefs) { - // TODO: only do if dirty. - //getSession().flush(); + Session session = getSession(); + DirtySessionMethodInterceptor.flushSession(session); String[] guids = new String[nodeRefs.size()]; int index = 0; @@ -65,10 +59,10 @@ public class HibernateL1CacheBulkLoader extends HibernateDaoSupport implements B guids[index++] = nodeRef.getId(); } - Criteria criteria = getSession().createCriteria(NodeStatusImpl.class, "status"); + Criteria criteria = getSession().createCriteria(NodeImpl.class, "node"); criteria.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP); - criteria.add(Restrictions.in("key.guid", guids)); - criteria.createAlias("status.node", "node"); + criteria.add(Restrictions.in("uuid", guids)); + criteria.createAlias("node.store", "store"); criteria.setFetchMode("node.aspects", FetchMode.SELECT); criteria.setFetchMode("node.properties", FetchMode.JOIN); criteria.setFetchMode("node.store", FetchMode.SELECT); @@ -77,10 +71,10 @@ public class HibernateL1CacheBulkLoader extends HibernateDaoSupport implements B criteria.list(); - criteria = getSession().createCriteria(NodeStatusImpl.class, "status"); + criteria = getSession().createCriteria(NodeImpl.class, "node"); criteria.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP); - criteria.add(Restrictions.in("key.guid", guids)); - criteria.createAlias("status.node", "node"); + criteria.add(Restrictions.in("uuid", guids)); + criteria.createAlias("node.store", "store"); criteria.setFetchMode("node.aspects", FetchMode.JOIN); criteria.setFetchMode("node.properties", FetchMode.SELECT); criteria.setFetchMode("node.store", FetchMode.SELECT); @@ -88,10 +82,9 @@ public class HibernateL1CacheBulkLoader extends HibernateDaoSupport implements B criteria.setFlushMode(FlushMode.MANUAL); criteria.list(); - - } + @SuppressWarnings("unchecked") public void clear() { getSession().flush(); diff --git a/source/java/org/alfresco/repo/domain/hibernate/HibernateLocaleDAOImpl.java b/source/java/org/alfresco/repo/domain/hibernate/HibernateLocaleDAOImpl.java new file mode 100644 index 0000000000..d37f9985cc --- /dev/null +++ b/source/java/org/alfresco/repo/domain/hibernate/HibernateLocaleDAOImpl.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2005-2007 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.hibernate; + +import java.io.Serializable; +import java.util.Locale; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.domain.LocaleDAO; +import org.alfresco.repo.domain.LocaleEntity; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.util.Pair; +import org.alfresco.util.ParameterCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hibernate.Query; +import org.hibernate.Session; +import org.springframework.orm.hibernate3.HibernateCallback; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; + +/** + * Hibernate-specific implementation of the Locale DAO interface. + *

+ * Since Locales are system-wide and immutable, we can cache lookups in both + * directions. + * + * @author Derek Hulley + * @since 2.2.1 + */ +public class HibernateLocaleDAOImpl extends HibernateDaoSupport implements LocaleDAO +{ + private static Log logger = LogFactory.getLog(HibernateLocaleDAOImpl.class); + + private static final String QUERY_GET_LOCALE_BY_VALUE = "locale.GetLocaleByValue"; + private static final Long CACHE_ID_MISS = Long.valueOf(-1L); + + /** + * A bi-directional cache of LocaleStr->ID and ID->LocaleStr. + */ + private SimpleCache localeIdCache; + + public void setLocaleIdCache(SimpleCache localeIdCache) + { + this.localeIdCache = localeIdCache; + } + + public Pair getLocalePair(Locale locale) + { + ParameterCheck.mandatory("locale", locale); + return getLocalePairImpl(locale); + } + + public Pair getDefaultLocalePair() + { + return getLocalePairImpl(null); + } + + public Pair getLocalePair(Long id) + { + ParameterCheck.mandatory("id", id); + + // First check the cache + String localeStr = (String) localeIdCache.get(id); + if (localeStr == null) + { + // Search for it + LocaleEntity localeEntity = (LocaleEntity) getHibernateTemplate().load(LocaleEntityImpl.class, id); + if (localeEntity == null) + { + throw new IllegalArgumentException("Locale entity ID " + id + " is not valid."); + } + localeStr = localeEntity.getLocaleStr(); + localeIdCache.put(id, localeStr); + localeIdCache.put(localeStr, id); + } + + // Convert the locale string to a locale + Locale locale = DefaultTypeConverter.INSTANCE.convert(Locale.class, localeStr); + Pair localePair = new Pair(id, locale); + // done + return localePair; + } + + public Pair getOrCreateLocalePair(Locale locale) + { + ParameterCheck.mandatory("locale", locale); + + String localeStr = DefaultTypeConverter.INSTANCE.convert(String.class, locale); + Pair localePair = getLocalePairImpl(locale); + if (localePair != null) + { + return localePair; + } + LocaleEntity localeEntity = new LocaleEntityImpl(); + localeEntity.setLocale(locale); + Long id = (Long) getHibernateTemplate().save(localeEntity); + // Add the cache entry + localeIdCache.put(id, localeStr); + localeIdCache.put(localeStr, id); + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Persisted locale entity: " + localeEntity); + } + return new Pair(id, locale); + } + + public Pair getOrCreateDefaultLocalePair() + { + String localeStr = LocaleEntityImpl.DEFAULT_LOCALE_SUBSTITUTE; + Pair localePair = getDefaultLocalePair(); + if (localePair != null) + { + return localePair; + } + LocaleEntity localeEntity = new LocaleEntityImpl(); + localeEntity.setLocale(null); + Long id = (Long) getHibernateTemplate().save(localeEntity); + // Add the cache entry + localeIdCache.put(id, localeStr); + localeIdCache.put(localeStr, id); + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Persisted locale entity: " + localeEntity); + } + Locale locale = localeEntity.getLocale(); + return new Pair(id, locale); + } + + private Pair getLocalePairImpl(Locale locale) + { + // Null means look for the default + final String localeStr; + if (locale == null) + { + localeStr = LocaleEntityImpl.DEFAULT_LOCALE_SUBSTITUTE; + locale = I18NUtil.getLocale(); + } + else + { + localeStr = DefaultTypeConverter.INSTANCE.convert(String.class, locale); + } + + Pair localePair; + // First see if it is cached + Long id = (Long) localeIdCache.get(localeStr); + if (id == null) + { + // Look it up from the DB + // It's not in the cache, so query + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateLocaleDAOImpl.QUERY_GET_LOCALE_BY_VALUE) + .setString("localeStr", localeStr); + DirtySessionMethodInterceptor.flushSession(session); + return query.uniqueResult(); + } + }; + LocaleEntity entity = (LocaleEntity) getHibernateTemplate().execute(callback); + if (entity != null) + { + id = entity.getId(); + // Add this to the cache + localeIdCache.put(localeStr, id); + localeIdCache.put(id, localeStr); + localePair = new Pair(id, locale); + } + else + { + // We did a search but it is not there + localeIdCache.put(localeStr, HibernateLocaleDAOImpl.CACHE_ID_MISS); + localePair = null; + } + } + else if (id.equals(HibernateLocaleDAOImpl.CACHE_ID_MISS)) + { + // We have searched before and it is not present + localePair = null; + } + else + { + // We have searched before and found something. + localePair = new Pair(id, locale); + } + // Done + return localePair; + } +} diff --git a/source/java/org/alfresco/repo/domain/hibernate/HibernateQNameDAOImpl.java b/source/java/org/alfresco/repo/domain/hibernate/HibernateQNameDAOImpl.java index 380c7c505c..617cd36b3f 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/HibernateQNameDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/HibernateQNameDAOImpl.java @@ -35,6 +35,7 @@ import org.alfresco.repo.domain.NamespaceEntity; import org.alfresco.repo.domain.QNameDAO; import org.alfresco.repo.domain.QNameEntity; import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.Query; @@ -196,7 +197,7 @@ public class HibernateQNameDAOImpl extends HibernateDaoSupport implements QNameD qnameEntityCache.put(qname, -1L); } } - else if(id == -1L) + else if (id == -1L) { return null; } @@ -228,6 +229,20 @@ public class HibernateQNameDAOImpl extends HibernateDaoSupport implements QNameD return result; } + public Pair getOrCreateQNamePair(QName qname) + { + Long id = qnameEntityCache.get(qname); + if (id == null) + { + // It is not cached + QNameEntity qnameEntity = getOrCreateQNameEntity(qname); + id = qnameEntity.getId(); + } + Pair qnamePair = new Pair(id, qname); + // Done + return qnamePair; + } + public QNameEntity newQNameEntity(QName qname) { if (logger.isDebugEnabled()) @@ -273,4 +288,42 @@ public class HibernateQNameDAOImpl extends HibernateDaoSupport implements QNameD } return qnameMap; } + + /** + * @return Returns a set of IDs mapping to the QNames provided. If create is false + * then there will not be corresponding entries for the QNames that don't exist. + * So there is no guarantee that the returned set will be ordered the same or even + * contain the same number of elements as the original unless create is true. + */ + public Set convertQNamesToIds(Set qnames, boolean create) + { + Set qnameIds = new HashSet(qnames.size(), 1.0F); + for (QName qname : qnames) + { + Long qnameEntityId = null; + if (create) + { + qnameEntityId = getOrCreateQNameEntity(qname).getId(); + } + else + { + QNameEntity qnameEntity = getQNameEntity(qname); + if (qnameEntity == null) + { + // No such qname and we are not creating one + continue; + } + else + { + qnameEntityId = qnameEntity.getId(); + } + } + if (qnameEntityId != null) + { + qnameIds.add(qnameEntityId); + } + } + // Done + return qnameIds; + } } diff --git a/source/java/org/alfresco/repo/domain/hibernate/HibernateSessionHelperTest.java b/source/java/org/alfresco/repo/domain/hibernate/HibernateSessionHelperTest.java index 46b37d39cd..e6bcfbfab4 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/HibernateSessionHelperTest.java +++ b/source/java/org/alfresco/repo/domain/hibernate/HibernateSessionHelperTest.java @@ -1,18 +1,18 @@ package org.alfresco.repo.domain.hibernate; import java.io.Serializable; -import java.util.HashSet; import java.util.Set; -import org.alfresco.repo.domain.NodeKey; -import org.alfresco.repo.domain.NodeStatus; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.Node; +import org.alfresco.repo.domain.QNameDAO; +import org.alfresco.repo.domain.QNameEntity; import org.alfresco.repo.domain.Server; -import org.alfresco.repo.domain.StoreKey; +import org.alfresco.repo.domain.Store; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.util.BaseSpringTest; import org.hibernate.engine.EntityKey; -import org.springframework.orm.toplink.SessionReadCallback; public class HibernateSessionHelperTest extends BaseSpringTest { @@ -31,9 +31,8 @@ public class HibernateSessionHelperTest extends BaseSpringTest assertFalse(SessionSizeResourceManager.isDisableInTransaction()); StoreImpl store = new StoreImpl(); - StoreKey storeKey = new StoreKey(StoreRef.PROTOCOL_WORKSPACE, - "TestWorkspace@" + getName() + " - " + System.currentTimeMillis()); - store.setKey(storeKey); + store.setProtocol(StoreRef.PROTOCOL_WORKSPACE); + store.setIdentifier("TestWorkspace@" + getName() + " - " + System.currentTimeMillis()); // persist so that it is present in the hibernate cache getSession().save(store); @@ -81,17 +80,20 @@ public class HibernateSessionHelperTest extends BaseSpringTest public void testNestedMarks() { + assertEquals(0, getSession().getStatistics().getEntityCount()); assertFalse(SessionSizeResourceManager.isDisableInTransaction()); + QNameDAO qnameDAO = (QNameDAO) getApplicationContext().getBean("qnameDAO"); + QNameEntity baseQNameEntity = qnameDAO.getOrCreateQNameEntity(ContentModel.TYPE_BASE); + StoreImpl store = new StoreImpl(); - StoreKey storeKey = new StoreKey(StoreRef.PROTOCOL_WORKSPACE, - "TestWorkspace@" + getName() + " - " + System.currentTimeMillis()); - store.setKey(storeKey); + store.setProtocol(StoreRef.PROTOCOL_WORKSPACE); + store.setIdentifier("TestWorkspace@" + getName() + " - " + System.currentTimeMillis()); // persist so that it is present in the hibernate cache getSession().save(store); - assertEquals(1, getSession().getStatistics().getEntityCount()); + assertEquals(2, getSession().getStatistics().getEntityCount()); Server server = (Server) getSession().get(ServerImpl.class, new Long(1)); if (server == null) @@ -104,9 +106,9 @@ public class HibernateSessionHelperTest extends BaseSpringTest TransactionImpl transaction = new TransactionImpl(); transaction.setServer(server); transaction.setChangeTxnId(AlfrescoTransactionSupport.getTransactionId()); - Serializable txID = getSession().save(transaction); + getSession().save(transaction); - assertEquals(3, getSession().getStatistics().getEntityCount()); + assertEquals(4, getSession().getStatistics().getEntityCount()); HibernateSessionHelper helper = (HibernateSessionHelper)getApplicationContext().getBean("hibernateSessionHelper"); assertFalse(SessionSizeResourceManager.isDisableInTransaction()); @@ -114,136 +116,131 @@ public class HibernateSessionHelperTest extends BaseSpringTest assertTrue(SessionSizeResourceManager.isDisableInTransaction()); assertEquals(1, helper.getMarks().size()); - NodeKey key1 = new NodeKey(store.getKey(), "1"); - createNodeStatus(transaction, key1); + Node n1 = createNode(transaction, store, "1", baseQNameEntity); - assertEquals(4, getSession().getStatistics().getEntityCount()); + assertEquals(5, getSession().getStatistics().getEntityCount()); helper.mark(); assertTrue(SessionSizeResourceManager.isDisableInTransaction()); assertEquals(2, helper.getMarks().size()); - NodeKey key2 = new NodeKey(store.getKey(), "2"); - createNodeStatus(transaction, key2); - - assertEquals(5, getSession().getStatistics().getEntityCount()); - helper.mark(); - assertTrue(SessionSizeResourceManager.isDisableInTransaction()); - assertEquals(3, helper.getMarks().size()); - - NodeKey key3 = new NodeKey(store.getKey(), "3"); - createNodeStatus(transaction, key3); + Node n2 = createNode(transaction, store, "2", baseQNameEntity); assertEquals(6, getSession().getStatistics().getEntityCount()); helper.mark(); assertTrue(SessionSizeResourceManager.isDisableInTransaction()); - assertEquals(4, helper.getMarks().size()); + assertEquals(3, helper.getMarks().size()); - NodeKey key4 = new NodeKey(store.getKey(), "4"); - createNodeStatus(transaction, key4); + Node n3 = createNode(transaction, store, "3", baseQNameEntity); assertEquals(7, getSession().getStatistics().getEntityCount()); helper.mark(); assertTrue(SessionSizeResourceManager.isDisableInTransaction()); - assertEquals(5, helper.getMarks().size()); + assertEquals(4, helper.getMarks().size()); - NodeKey key5 = new NodeKey(store.getKey(), "5"); - createNodeStatus(transaction, key5); + Node n4 = createNode(transaction, store, "4", baseQNameEntity); assertEquals(8, getSession().getStatistics().getEntityCount()); + helper.mark(); + assertTrue(SessionSizeResourceManager.isDisableInTransaction()); + assertEquals(5, helper.getMarks().size()); + + Node n5 = createNode(transaction, store, "5", baseQNameEntity); + + assertEquals(9, getSession().getStatistics().getEntityCount()); helper.reset(); - assertEquals(7, getSession().getStatistics().getEntityCount()); - assertTrue(SessionSizeResourceManager.isDisableInTransaction()); - assertEquals(5, helper.getMarks().size()); - assertFalse(sessionContainsNodeStatus(key5)); - assertTrue(sessionContainsNodeStatus(key4)); - assertTrue(sessionContainsNodeStatus(key3)); - assertTrue(sessionContainsNodeStatus(key2)); - assertTrue(sessionContainsNodeStatus(key1)); - getSession().get(NodeStatusImpl.class, key5); assertEquals(8, getSession().getStatistics().getEntityCount()); assertTrue(SessionSizeResourceManager.isDisableInTransaction()); assertEquals(5, helper.getMarks().size()); - assertTrue(sessionContainsNodeStatus(key5)); - assertTrue(sessionContainsNodeStatus(key4)); - assertTrue(sessionContainsNodeStatus(key3)); - assertTrue(sessionContainsNodeStatus(key2)); - assertTrue(sessionContainsNodeStatus(key1)); + assertFalse(sessionContainsNode(n5)); + assertTrue(sessionContainsNode(n4)); + assertTrue(sessionContainsNode(n3)); + assertTrue(sessionContainsNode(n2)); + assertTrue(sessionContainsNode(n1)); + getSession().get(NodeImpl.class, n5.getId()); + assertEquals(9, getSession().getStatistics().getEntityCount()); + assertTrue(SessionSizeResourceManager.isDisableInTransaction()); + assertEquals(5, helper.getMarks().size()); + assertTrue(sessionContainsNode(n5)); + assertTrue(sessionContainsNode(n4)); + assertTrue(sessionContainsNode(n3)); + assertTrue(sessionContainsNode(n2)); + assertTrue(sessionContainsNode(n1)); helper.reset(); - assertEquals(7, getSession().getStatistics().getEntityCount()); - assertTrue(SessionSizeResourceManager.isDisableInTransaction()); - assertEquals(5, helper.getMarks().size()); - assertFalse(sessionContainsNodeStatus(key5)); - assertTrue(sessionContainsNodeStatus(key4)); - assertTrue(sessionContainsNodeStatus(key3)); - assertTrue(sessionContainsNodeStatus(key2)); - assertTrue(sessionContainsNodeStatus(key1)); - getSession().get(NodeStatusImpl.class, key5); assertEquals(8, getSession().getStatistics().getEntityCount()); assertTrue(SessionSizeResourceManager.isDisableInTransaction()); assertEquals(5, helper.getMarks().size()); - assertTrue(sessionContainsNodeStatus(key5)); - assertTrue(sessionContainsNodeStatus(key4)); - assertTrue(sessionContainsNodeStatus(key3)); - assertTrue(sessionContainsNodeStatus(key2)); - assertTrue(sessionContainsNodeStatus(key1)); + assertFalse(sessionContainsNode(n5)); + assertTrue(sessionContainsNode(n4)); + assertTrue(sessionContainsNode(n3)); + assertTrue(sessionContainsNode(n2)); + assertTrue(sessionContainsNode(n1)); + getSession().get(NodeImpl.class, n5.getId()); + assertEquals(9, getSession().getStatistics().getEntityCount()); + assertTrue(SessionSizeResourceManager.isDisableInTransaction()); + assertEquals(5, helper.getMarks().size()); + assertTrue(sessionContainsNode(n5)); + assertTrue(sessionContainsNode(n4)); + assertTrue(sessionContainsNode(n3)); + assertTrue(sessionContainsNode(n2)); + assertTrue(sessionContainsNode(n1)); + + helper.resetAndRemoveMark(); + + assertEquals(8, getSession().getStatistics().getEntityCount()); + assertTrue(SessionSizeResourceManager.isDisableInTransaction()); + assertEquals(4, helper.getMarks().size()); + assertFalse(sessionContainsNode(n5)); + assertTrue(sessionContainsNode(n4)); + assertTrue(sessionContainsNode(n3)); + assertTrue(sessionContainsNode(n2)); + assertTrue(sessionContainsNode(n1)); helper.resetAndRemoveMark(); assertEquals(7, getSession().getStatistics().getEntityCount()); assertTrue(SessionSizeResourceManager.isDisableInTransaction()); - assertEquals(4, helper.getMarks().size()); - assertFalse(sessionContainsNodeStatus(key5)); - assertTrue(sessionContainsNodeStatus(key4)); - assertTrue(sessionContainsNodeStatus(key3)); - assertTrue(sessionContainsNodeStatus(key2)); - assertTrue(sessionContainsNodeStatus(key1)); + assertEquals(3, helper.getMarks().size()); + assertFalse(sessionContainsNode(n5)); + assertFalse(sessionContainsNode(n4)); + assertTrue(sessionContainsNode(n3)); + assertTrue(sessionContainsNode(n2)); + assertTrue(sessionContainsNode(n1)); + helper.resetAndRemoveMark(); assertEquals(6, getSession().getStatistics().getEntityCount()); assertTrue(SessionSizeResourceManager.isDisableInTransaction()); - assertEquals(3, helper.getMarks().size()); - assertFalse(sessionContainsNodeStatus(key5)); - assertFalse(sessionContainsNodeStatus(key4)); - assertTrue(sessionContainsNodeStatus(key3)); - assertTrue(sessionContainsNodeStatus(key2)); - assertTrue(sessionContainsNodeStatus(key1)); - + assertEquals(2, helper.getMarks().size()); + assertFalse(sessionContainsNode(n5)); + assertFalse(sessionContainsNode(n4)); + assertFalse(sessionContainsNode(n3)); + assertTrue(sessionContainsNode(n2)); + assertTrue(sessionContainsNode(n1)); helper.resetAndRemoveMark(); assertEquals(5, getSession().getStatistics().getEntityCount()); assertTrue(SessionSizeResourceManager.isDisableInTransaction()); - assertEquals(2, helper.getMarks().size()); - assertFalse(sessionContainsNodeStatus(key5)); - assertFalse(sessionContainsNodeStatus(key4)); - assertFalse(sessionContainsNodeStatus(key3)); - assertTrue(sessionContainsNodeStatus(key2)); - assertTrue(sessionContainsNodeStatus(key1)); - - helper.resetAndRemoveMark(); - - assertEquals(4, getSession().getStatistics().getEntityCount()); - assertTrue(SessionSizeResourceManager.isDisableInTransaction()); assertEquals(1, helper.getMarks().size()); - assertFalse(sessionContainsNodeStatus(key5)); - assertFalse(sessionContainsNodeStatus(key4)); - assertFalse(sessionContainsNodeStatus(key3)); - assertFalse(sessionContainsNodeStatus(key2)); - assertTrue(sessionContainsNodeStatus(key1)); + assertFalse(sessionContainsNode(n5)); + assertFalse(sessionContainsNode(n4)); + assertFalse(sessionContainsNode(n3)); + assertFalse(sessionContainsNode(n2)); + assertTrue(sessionContainsNode(n1)); helper.resetAndRemoveMark(); - assertEquals(3, getSession().getStatistics().getEntityCount()); + assertEquals(4, getSession().getStatistics().getEntityCount()); assertFalse(SessionSizeResourceManager.isDisableInTransaction()); assertEquals(0, helper.getMarks().size()); - assertFalse(sessionContainsNodeStatus(key5)); - assertFalse(sessionContainsNodeStatus(key4)); - assertFalse(sessionContainsNodeStatus(key3)); - assertFalse(sessionContainsNodeStatus(key2)); - assertFalse(sessionContainsNodeStatus(key1)); + assertFalse(sessionContainsNode(n5)); + assertFalse(sessionContainsNode(n4)); + assertFalse(sessionContainsNode(n3)); + assertFalse(sessionContainsNode(n2)); + assertFalse(sessionContainsNode(n1)); try { @@ -262,9 +259,8 @@ public class HibernateSessionHelperTest extends BaseSpringTest assertFalse(SessionSizeResourceManager.isDisableInTransaction()); StoreImpl store = new StoreImpl(); - StoreKey storeKey = new StoreKey(StoreRef.PROTOCOL_WORKSPACE, - "TestWorkspace@" + getName() + " - " + System.currentTimeMillis()); - store.setKey(storeKey); + store.setProtocol(StoreRef.PROTOCOL_WORKSPACE); + store.setIdentifier("TestWorkspace@" + getName() + " - " + System.currentTimeMillis()); // persist so that it is present in the hibernate cache getSession().save(store); @@ -316,14 +312,16 @@ public class HibernateSessionHelperTest extends BaseSpringTest assertEquals(0, getSession().getStatistics().getEntityCount()); assertFalse(SessionSizeResourceManager.isDisableInTransaction()); + QNameDAO qnameDAO = (QNameDAO) getApplicationContext().getBean("qnameDAO"); + QNameEntity baseQNameEntity = qnameDAO.getOrCreateQNameEntity(ContentModel.TYPE_BASE); + StoreImpl store = new StoreImpl(); - StoreKey storeKey = new StoreKey(StoreRef.PROTOCOL_WORKSPACE, - "TestWorkspace@" + getName() + " - " + System.currentTimeMillis()); - store.setKey(storeKey); + store.setProtocol(StoreRef.PROTOCOL_WORKSPACE); + store.setIdentifier("TestWorkspace@" + getName() + " - " + System.currentTimeMillis()); // persist so that it is present in the hibernate cache getSession().save(store); - assertEquals(1, getSession().getStatistics().getEntityCount()); + assertEquals(2, getSession().getStatistics().getEntityCount()); Server server = (Server) getSession().get(ServerImpl.class, new Long(1)); if (server == null) @@ -336,9 +334,9 @@ public class HibernateSessionHelperTest extends BaseSpringTest TransactionImpl transaction = new TransactionImpl(); transaction.setServer(server); transaction.setChangeTxnId(AlfrescoTransactionSupport.getTransactionId()); - Serializable txID = getSession().save(transaction); + getSession().save(transaction); - assertEquals(3, getSession().getStatistics().getEntityCount()); + assertEquals(4, getSession().getStatistics().getEntityCount()); HibernateSessionHelper helper = (HibernateSessionHelper)getApplicationContext().getBean("hibernateSessionHelper"); assertNull(helper.getCurrentMark()); @@ -348,121 +346,116 @@ public class HibernateSessionHelperTest extends BaseSpringTest assertTrue(SessionSizeResourceManager.isDisableInTransaction()); assertEquals(1, helper.getMarks().size()); - NodeKey key1 = new NodeKey(store.getKey(), "1"); - createNodeStatus(transaction, key1); + Node n1 = createNode(transaction, store, "1", baseQNameEntity); - assertEquals(4, getSession().getStatistics().getEntityCount()); + assertEquals(5, getSession().getStatistics().getEntityCount()); helper.mark("Two"); assertEquals("Two", helper.getCurrentMark()); assertTrue(SessionSizeResourceManager.isDisableInTransaction()); assertEquals(2, helper.getMarks().size()); - NodeKey key2 = new NodeKey(store.getKey(), "2"); - createNodeStatus(transaction, key2); + Node n2 = createNode(transaction, store, "2", baseQNameEntity); - assertEquals(5, getSession().getStatistics().getEntityCount()); + assertEquals(6, getSession().getStatistics().getEntityCount()); helper.mark("Three"); assertEquals("Three", helper.getCurrentMark()); assertTrue(SessionSizeResourceManager.isDisableInTransaction()); assertEquals(3, helper.getMarks().size()); - NodeKey key3 = new NodeKey(store.getKey(), "3"); - createNodeStatus(transaction, key3); + Node n3 = createNode(transaction, store, "3", baseQNameEntity); - assertEquals(6, getSession().getStatistics().getEntityCount()); + assertEquals(7, getSession().getStatistics().getEntityCount()); helper.mark("Four"); assertEquals("Four", helper.getCurrentMark()); assertTrue(SessionSizeResourceManager.isDisableInTransaction()); assertEquals(4, helper.getMarks().size()); - NodeKey key4 = new NodeKey(store.getKey(), "4"); - createNodeStatus(transaction, key4); + Node n4 = createNode(transaction, store, "4", baseQNameEntity); - assertEquals(7, getSession().getStatistics().getEntityCount()); + assertEquals(8, getSession().getStatistics().getEntityCount()); helper.mark("Five"); assertEquals("Five", helper.getCurrentMark()); assertTrue(SessionSizeResourceManager.isDisableInTransaction()); assertEquals(5, helper.getMarks().size()); - NodeKey key5 = new NodeKey(store.getKey(), "5"); - createNodeStatus(transaction, key5); + Node n5 = createNode(transaction, store, "5", baseQNameEntity); - assertEquals(8, getSession().getStatistics().getEntityCount()); + assertEquals(9, getSession().getStatistics().getEntityCount()); helper.reset("Five"); - assertEquals(7, getSession().getStatistics().getEntityCount()); - assertTrue(SessionSizeResourceManager.isDisableInTransaction()); - assertEquals(5, helper.getMarks().size()); - assertFalse(sessionContainsNodeStatus(key5)); - assertTrue(sessionContainsNodeStatus(key4)); - assertTrue(sessionContainsNodeStatus(key3)); - assertTrue(sessionContainsNodeStatus(key2)); - assertTrue(sessionContainsNodeStatus(key1)); - getSession().get(NodeStatusImpl.class, key5); assertEquals(8, getSession().getStatistics().getEntityCount()); assertTrue(SessionSizeResourceManager.isDisableInTransaction()); assertEquals(5, helper.getMarks().size()); - assertTrue(sessionContainsNodeStatus(key5)); - assertTrue(sessionContainsNodeStatus(key4)); - assertTrue(sessionContainsNodeStatus(key3)); - assertTrue(sessionContainsNodeStatus(key2)); - assertTrue(sessionContainsNodeStatus(key1)); + assertFalse(sessionContainsNode(n5)); + assertTrue(sessionContainsNode(n4)); + assertTrue(sessionContainsNode(n3)); + assertTrue(sessionContainsNode(n2)); + assertTrue(sessionContainsNode(n1)); + getSession().get(NodeImpl.class, n5.getId()); + assertEquals(9, getSession().getStatistics().getEntityCount()); + assertTrue(SessionSizeResourceManager.isDisableInTransaction()); + assertEquals(5, helper.getMarks().size()); + assertTrue(sessionContainsNode(n5)); + assertTrue(sessionContainsNode(n4)); + assertTrue(sessionContainsNode(n3)); + assertTrue(sessionContainsNode(n2)); + assertTrue(sessionContainsNode(n1)); helper.reset("Five"); - assertEquals(7, getSession().getStatistics().getEntityCount()); - assertTrue(SessionSizeResourceManager.isDisableInTransaction()); - assertEquals(5, helper.getMarks().size()); - assertFalse(sessionContainsNodeStatus(key5)); - assertTrue(sessionContainsNodeStatus(key4)); - assertTrue(sessionContainsNodeStatus(key3)); - assertTrue(sessionContainsNodeStatus(key2)); - assertTrue(sessionContainsNodeStatus(key1)); - getSession().get(NodeStatusImpl.class, key5); assertEquals(8, getSession().getStatistics().getEntityCount()); assertTrue(SessionSizeResourceManager.isDisableInTransaction()); assertEquals(5, helper.getMarks().size()); - assertTrue(sessionContainsNodeStatus(key5)); - assertTrue(sessionContainsNodeStatus(key4)); - assertTrue(sessionContainsNodeStatus(key3)); - assertTrue(sessionContainsNodeStatus(key2)); - assertTrue(sessionContainsNodeStatus(key1)); + assertFalse(sessionContainsNode(n5)); + assertTrue(sessionContainsNode(n4)); + assertTrue(sessionContainsNode(n3)); + assertTrue(sessionContainsNode(n2)); + assertTrue(sessionContainsNode(n1)); + getSession().get(NodeImpl.class, n5.getId()); + assertEquals(9, getSession().getStatistics().getEntityCount()); + assertTrue(SessionSizeResourceManager.isDisableInTransaction()); + assertEquals(5, helper.getMarks().size()); + assertTrue(sessionContainsNode(n5)); + assertTrue(sessionContainsNode(n4)); + assertTrue(sessionContainsNode(n3)); + assertTrue(sessionContainsNode(n2)); + assertTrue(sessionContainsNode(n1)); assertEquals("Five", helper.getCurrentMark()); helper.resetAndRemoveMark("Five"); assertEquals("Four", helper.getCurrentMark()); - assertEquals(7, getSession().getStatistics().getEntityCount()); + assertEquals(8, getSession().getStatistics().getEntityCount()); assertTrue(SessionSizeResourceManager.isDisableInTransaction()); assertEquals(4, helper.getMarks().size()); - assertFalse(sessionContainsNodeStatus(key5)); - assertTrue(sessionContainsNodeStatus(key4)); - assertTrue(sessionContainsNodeStatus(key3)); - assertTrue(sessionContainsNodeStatus(key2)); - assertTrue(sessionContainsNodeStatus(key1)); + assertFalse(sessionContainsNode(n5)); + assertTrue(sessionContainsNode(n4)); + assertTrue(sessionContainsNode(n3)); + assertTrue(sessionContainsNode(n2)); + assertTrue(sessionContainsNode(n1)); helper.resetAndRemoveMark("Three"); assertEquals("Two", helper.getCurrentMark()); - assertEquals(5, getSession().getStatistics().getEntityCount()); + assertEquals(6, getSession().getStatistics().getEntityCount()); assertTrue(SessionSizeResourceManager.isDisableInTransaction()); assertEquals(2, helper.getMarks().size()); - assertFalse(sessionContainsNodeStatus(key5)); - assertFalse(sessionContainsNodeStatus(key4)); - assertFalse(sessionContainsNodeStatus(key3)); - assertTrue(sessionContainsNodeStatus(key2)); - assertTrue(sessionContainsNodeStatus(key1)); + assertFalse(sessionContainsNode(n5)); + assertFalse(sessionContainsNode(n4)); + assertFalse(sessionContainsNode(n3)); + assertTrue(sessionContainsNode(n2)); + assertTrue(sessionContainsNode(n1)); helper.resetAndRemoveMark("One"); assertNull(helper.getCurrentMark()); - assertEquals(3, getSession().getStatistics().getEntityCount()); + assertEquals(4, getSession().getStatistics().getEntityCount()); assertFalse(SessionSizeResourceManager.isDisableInTransaction()); assertEquals(0, helper.getMarks().size()); - assertFalse(sessionContainsNodeStatus(key5)); - assertFalse(sessionContainsNodeStatus(key4)); - assertFalse(sessionContainsNodeStatus(key3)); - assertFalse(sessionContainsNodeStatus(key2)); - assertFalse(sessionContainsNodeStatus(key1)); + assertFalse(sessionContainsNode(n5)); + assertFalse(sessionContainsNode(n4)); + assertFalse(sessionContainsNode(n3)); + assertFalse(sessionContainsNode(n2)); + assertFalse(sessionContainsNode(n1)); try { @@ -481,9 +474,8 @@ public class HibernateSessionHelperTest extends BaseSpringTest assertFalse(SessionSizeResourceManager.isDisableInTransaction()); StoreImpl store = new StoreImpl(); - StoreKey storeKey = new StoreKey(StoreRef.PROTOCOL_WORKSPACE, - "TestWorkspace@" + getName() + " - " + System.currentTimeMillis()); - store.setKey(storeKey); + store.setProtocol(StoreRef.PROTOCOL_WORKSPACE); + store.setIdentifier("TestWorkspace@" + getName() + " - " + System.currentTimeMillis()); // persist so that it is present in the hibernate cache getSession().save(store); @@ -500,7 +492,7 @@ public class HibernateSessionHelperTest extends BaseSpringTest TransactionImpl transaction = new TransactionImpl(); transaction.setServer(server); transaction.setChangeTxnId(AlfrescoTransactionSupport.getTransactionId()); - Serializable txID = getSession().save(transaction); + getSession().save(transaction); assertEquals(3, getSession().getStatistics().getEntityCount()); @@ -541,24 +533,30 @@ public class HibernateSessionHelperTest extends BaseSpringTest assertNull(helper.getCurrentMark()); } - private NodeStatus createNodeStatus(TransactionImpl transaction, NodeKey key) + private Node createNode(TransactionImpl transaction, Store store, String uuid, QNameEntity typeQNameEntity) { - NodeStatus nodeStatus = new NodeStatusImpl(); - nodeStatus.setKey(key); - nodeStatus.setTransaction(transaction); - getSession().save(nodeStatus); - return nodeStatus; + // Create the Node + Node node = new NodeImpl(); + node.setStore(store); + node.setUuid(uuid); + node.setTypeQName(typeQNameEntity); + node.setTransaction(transaction); + node.setDeleted(false); + getSession().save(node); + + return node; } @SuppressWarnings("unchecked") - private boolean sessionContainsNodeStatus(NodeKey nodeKey) + private boolean sessionContainsNode(Node node) { + Long nodeId = node.getId(); Set keys = (Set)getSession().getStatistics().getEntityKeys(); for(EntityKey key : keys) { - if(key.getEntityName().equals(NodeStatusImpl.class.getName())) + if(key.getEntityName().equals(NodeImpl.class.getName())) { - if(key.getIdentifier().equals(nodeKey)) + if(key.getIdentifier().equals(nodeId)) { return true; } @@ -566,5 +564,4 @@ public class HibernateSessionHelperTest extends BaseSpringTest } return false; } - } diff --git a/source/java/org/alfresco/repo/domain/hibernate/Locale.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Locale.hbm.xml new file mode 100644 index 0000000000..bdcab5fbf5 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/hibernate/Locale.hbm.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + select + locale + from + org.alfresco.repo.domain.hibernate.LocaleEntityImpl as locale + where + locale.localeStr = :localeStr + + + diff --git a/source/java/org/alfresco/repo/domain/hibernate/LocaleEntityImpl.java b/source/java/org/alfresco/repo/domain/hibernate/LocaleEntityImpl.java new file mode 100644 index 0000000000..d95eef557c --- /dev/null +++ b/source/java/org/alfresco/repo/domain/hibernate/LocaleEntityImpl.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2005-2007 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.hibernate; + +import java.io.Serializable; +import java.util.Locale; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.domain.LocaleEntity; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; + +/** + * Hibernate-specific implementation of the domain entity LocaleEntity. + * + * @author Derek Hulley + * @since 2.2.1 + */ +public class LocaleEntityImpl implements LocaleEntity, Serializable +{ + private static final long serialVersionUID = -1436739054926548300L; + + public static final String DEFAULT_LOCALE_SUBSTITUTE = ".default"; + + private Long id; + private Long version; + private String localeStr; + + private transient ReadLock refReadLock; + private transient WriteLock refWriteLock; + private transient Locale locale; + + public LocaleEntityImpl() + { + ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + refReadLock = lock.readLock(); + refWriteLock = lock.writeLock(); + } + + /** + * Lazily constructs a Locale instance referencing this entity + */ + public Locale getLocale() + { + // The default locale cannot be cached as it depends on the running thread's locale + if (localeStr == null || localeStr.equals(LocaleEntityImpl.DEFAULT_LOCALE_SUBSTITUTE)) + { + return I18NUtil.getLocale(); + } + // first check if it is available + refReadLock.lock(); + try + { + if (locale != null) + { + return locale; + } + } + finally + { + refReadLock.unlock(); + } + // get write lock + refWriteLock.lock(); + try + { + // double check + if (locale == null ) + { + locale = DefaultTypeConverter.INSTANCE.convert(Locale.class, localeStr); + } + return locale; + } + finally + { + refWriteLock.unlock(); + } + } + + public void setLocale(Locale locale) + { + refWriteLock.lock(); + try + { + if (locale == null) + { + this.localeStr = LocaleEntityImpl.DEFAULT_LOCALE_SUBSTITUTE; + this.locale = null; + } + else + { + this.localeStr = DefaultTypeConverter.INSTANCE.convert(String.class, locale); + this.locale = locale; + } + } + finally + { + refWriteLock.unlock(); + } + } + + /** + * @see #getStoreRef()() + */ + public String toString() + { + return "" + localeStr; + } + + /** + * @see #getKey() + */ + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + else if (obj == this) + { + return true; + } + else if (!(obj instanceof LocaleEntity)) + { + return false; + } + LocaleEntity that = (LocaleEntity) obj; + return (this.getLocale().equals(that.getLocale())); + } + + /** + * @see #getKey() + */ + public int hashCode() + { + return getLocale().hashCode(); + } + + public Long getId() + { + return id; + } + + /** + * For Hibernate use. + */ + @SuppressWarnings("unused") + private void setId(Long id) + { + this.id = id; + } + + public Long getVersion() + { + return version; + } + + /** + * For Hibernate use + */ + @SuppressWarnings("unused") + private void setVersion(Long version) + { + this.version = version; + } + + public String getLocaleStr() + { + return localeStr; + } + + /** + * For Hibernate use + */ + @SuppressWarnings("unused") + private void setLocaleStr(String localeStr) + { + refWriteLock.lock(); + try + { + this.localeStr = localeStr; + this.locale = null; + } + finally + { + refWriteLock.unlock(); + } + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml index 7b9649eb85..4bde121f6b 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml @@ -6,8 +6,37 @@ - - + + + + + + + + + + + + + + + + + + - + - - - + fetch="join" /> + + + + - - - + + + + + + + - - @@ -100,51 +141,19 @@ sort="unsorted" optimistic-lock="false" cascade="delete" > - + + + + + + + + + - - - - - - - - - - - - - - - - - + - - + + - + @@ -226,7 +235,7 @@ name="source" class="org.alfresco.repo.domain.hibernate.NodeImpl" optimistic-lock="false" - foreign-key="fk_alf_na_snode" + foreign-key="fk_alf_nass_snode" lazy="false" fetch="join" not-null="true" > @@ -237,7 +246,7 @@ name="target" class="org.alfresco.repo.domain.hibernate.NodeImpl" optimistic-lock="false" - foreign-key="fk_alf_na_tnode" + foreign-key="fk_alf_nass_tnode" lazy="false" fetch="join" not-null="true" > @@ -248,7 +257,7 @@ name="typeQName" class="org.alfresco.repo.domain.hibernate.QNameEntityImpl" column="type_qname_id" - foreign-key="fk_alf_na_tqn" + foreign-key="fk_alf_nass_tqn" lazy="proxy" fetch="select" unique="false" @@ -259,6 +268,17 @@ + + select + store + from + org.alfresco.repo.domain.hibernate.StoreImpl as store + join store.rootNode + where + store.protocol = :protocol and + store.identifier = :identifier + + select store @@ -266,6 +286,16 @@ org.alfresco.repo.domain.hibernate.StoreImpl as store + + select + node + from + org.alfresco.repo.domain.hibernate.NodeImpl as node + where + node.store.id = :storeId and + node.uuid = :uuid + + update org.alfresco.repo.domain.hibernate.ChildAssocImpl assoc @@ -313,19 +343,6 @@ assoc.target.id = :nodeId - - select - status - from - org.alfresco.repo.domain.hibernate.NodeStatusImpl as status, - org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc - join assoc.child as child - where - assoc.parent.id = :parentId and - assoc.isPrimary = true and - status.node.id = childId - - select child.id @@ -402,13 +419,14 @@ assoc.isPrimary, assoc.index, child.id, - child.store.key.protocol, - child.store.key.identifier, - child.uuid as parentUuid + store.protocol, + store.identifier, + child.uuid from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc join assoc.parent as parent join assoc.child as child + join child.store as store where assoc.parent.id = :parentId order by @@ -425,13 +443,14 @@ assoc.isPrimary, assoc.index, child.id, - child.store.key.protocol, - child.store.key.identifier, - child.uuid as parentUuid + store.protocol, + store.identifier, + child.uuid from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc join assoc.parent as parent join assoc.child as child + join child.store as store where assoc.parent.id = :parentId and assoc.qnameNamespace = :qnameNamespace and @@ -450,13 +469,14 @@ assoc.isPrimary, assoc.index, child.id, - child.store.key.protocol, - child.store.key.identifier, + store.protocol, + store.identifier, child.uuid from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc join assoc.parent as parent join assoc.child as child + join child.store as store where assoc.parent.id = :parentId and assoc.typeQName.id in (:childAssocTypeQNameIds) @@ -474,13 +494,14 @@ assoc.isPrimary, assoc.index, child.id, - child.store.key.protocol, - child.store.key.identifier, + store.protocol, + store.identifier, child.uuid from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc join assoc.parent as parent join assoc.child as child + join child.store as store where assoc.parent.id = :parentId and assoc.typeQName = :typeQName and @@ -500,13 +521,14 @@ assoc.isPrimary, assoc.index, child.id, - child.store.key.protocol, - child.store.key.identifier, + store.protocol, + store.identifier, child.uuid from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc join assoc.parent as parent join assoc.child as child + join child.store as store where assoc.parent.id = :parentId and assoc.isPrimary = true @@ -524,20 +546,18 @@ assoc.isPrimary, assoc.index, child.id, - child.store.key.protocol, - child.store.key.identifier, + store.protocol, + store.identifier, child.uuid from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc join assoc.parent as parent join assoc.child as child + join child.store as store where assoc.parent.id = :parentId and assoc.isPrimary = true and - ( - child.store.key.protocol != parent.store.key.protocol or - child.store.key.identifier != parent.store.key.identifier - ) + child.store.id != parent.store.id order by assoc.index, assoc.id @@ -546,20 +566,18 @@ select parent.id, - parent.store.key.protocol, - parent.store.key.identifier, + parentStore.protocol, + parentStore.identifier, parent.uuid from org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc join assoc.parent as parent + join parent.store as parentStore join assoc.child as child where + child.store.id != parent.store.id and parent.id > :minNodeId and - assoc.isPrimary = true and - ( - child.store.key.protocol != parent.store.key.protocol or - child.store.key.identifier != parent.store.key.identifier - ) + assoc.isPrimary = true order by parent.id @@ -567,8 +585,8 @@ select node.id, - node.store.key.protocol, - node.store.key.identifier, + node.store.protocol, + node.store.identifier, node.uuid from org.alfresco.repo.domain.hibernate.NodeImpl as node @@ -629,11 +647,12 @@ node.typeQName from org.alfresco.repo.domain.hibernate.NodeImpl as node + join node.store as store join node.properties prop where - node.store.key.protocol = :protocol and - node.store.key.identifier = :identifier and - index(prop) = :propQNameId and + store.protocol = :protocol and + store.identifier = :identifier and + index(prop) = :propKey and prop.stringValue = :propStringValue @@ -659,8 +678,7 @@ org.alfresco.repo.domain.hibernate.NodeImpl as node join node.properties as props where - props.serializableValue is not null and - props.multiValued = false + props.serializableValue is not null @@ -679,63 +697,4 @@ ]]> - - select - status - from - org.alfresco.repo.domain.hibernate.NodeStatusImpl as status - where - status.key.protocol = :protocol and - status.key.identifier = :identifier - - - - select - assoc - from - org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc - where - assoc.parent.id in (select - node.id - from - org.alfresco.repo.domain.hibernate.NodeImpl node - where - node.store.key.protocol = :protocol and - node.store.key.identifier = :identifier) - - - - select - node - from - org.alfresco.repo.domain.hibernate.NodeImpl as node - where - node.store.key.protocol = :nodeProtocol and - node.store.key.identifier = :nodeIdentifier and - node.id != (select - rootNode.id - from - org.alfresco.repo.domain.hibernate.StoreImpl store - where - store.key.protocol = :storeProtocol and - store.key.identifier = :storeIdentifier) - - - - select - count(node.id) - from - org.alfresco.repo.domain.hibernate.NodeImpl as node - - - - select - count(node.id) - from - org.alfresco.repo.domain.hibernate.NodeImpl as node - where - node.store.key.protocol = :protocol and - node.store.key.identifier = :identifier - - diff --git a/source/java/org/alfresco/repo/domain/hibernate/NodeImpl.java b/source/java/org/alfresco/repo/domain/hibernate/NodeImpl.java index c651827328..8ee5e30a71 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/NodeImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/NodeImpl.java @@ -25,7 +25,6 @@ package org.alfresco.repo.domain.hibernate; import java.io.Serializable; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -34,12 +33,14 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; -import org.alfresco.repo.domain.ChildAssoc; +import org.alfresco.repo.domain.AuditableProperties; import org.alfresco.repo.domain.DbAccessControlList; import org.alfresco.repo.domain.Node; -import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.domain.NodePropertyValue; +import org.alfresco.repo.domain.PropertyMapKey; import org.alfresco.repo.domain.QNameEntity; import org.alfresco.repo.domain.Store; +import org.alfresco.repo.domain.Transaction; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.util.EqualsHelper; @@ -60,10 +61,12 @@ public class NodeImpl extends LifecycleAdapter implements Node, Serializable private Store store; private String uuid; private QNameEntity typeQName; - private Set aspects; - private Collection parentAssocs; - private Map properties; + private Transaction transaction; + private boolean deleted; private DbAccessControlList accessControlList; + private Set aspects; + private Map properties; + private AuditableProperties auditableProperties; private transient ReadLock refReadLock; private transient WriteLock refWriteLock; @@ -71,13 +74,13 @@ public class NodeImpl extends LifecycleAdapter implements Node, Serializable public NodeImpl() { - aspects = new HashSet(5); - parentAssocs = new HashSet(5); - properties = new HashMap(5); - ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); refReadLock = lock.readLock(); refWriteLock = lock.writeLock(); + + aspects = new HashSet(5); + properties = new HashMap(5); + auditableProperties = new AuditableProperties(); } /** @@ -120,7 +123,14 @@ public class NodeImpl extends LifecycleAdapter implements Node, Serializable */ public String toString() { - return getNodeRef().toString(); + StringBuilder sb = new StringBuilder(50); + sb.append("Node") + .append("[id=").append(id) + .append(", ref=").append(getNodeRef()) + .append(", txn=").append(transaction) + .append(", deleted=").append(deleted) + .append("]"); + return sb.toString(); } public boolean equals(Object obj) @@ -167,20 +177,6 @@ public class NodeImpl extends LifecycleAdapter implements Node, Serializable this.id = id; } - public Long getVersion() - { - return version; - } - - /** - * For Hibernate use - */ - @SuppressWarnings("unused") - private void setVersion(Long version) - { - this.version = version; - } - public Store getStore() { return store; @@ -219,6 +215,40 @@ public class NodeImpl extends LifecycleAdapter implements Node, Serializable } } + public Long getVersion() + { + return version; + } + + /** + * For Hibernate use + */ + @SuppressWarnings("unused") + private void setVersion(Long version) + { + this.version = version; + } + + public Transaction getTransaction() + { + return transaction; + } + + public void setTransaction(Transaction transaction) + { + this.transaction = transaction; + } + + public boolean getDeleted() + { + return deleted; + } + + public void setDeleted(boolean deleted) + { + this.deleted = deleted; + } + public QNameEntity getTypeQName() { return typeQName; @@ -229,6 +259,16 @@ public class NodeImpl extends LifecycleAdapter implements Node, Serializable this.typeQName = typeQName; } + public DbAccessControlList getAccessControlList() + { + return accessControlList; + } + + public void setAccessControlList(DbAccessControlList accessControlList) + { + this.accessControlList = accessControlList; + } + public Set getAspects() { return aspects; @@ -243,21 +283,7 @@ public class NodeImpl extends LifecycleAdapter implements Node, Serializable this.aspects = aspects; } - public Collection getParentAssocs() - { - return parentAssocs; - } - - /** - * For Hibernate use - */ - @SuppressWarnings("unused") - private void setParentAssocs(Collection parentAssocs) - { - this.parentAssocs = parentAssocs; - } - - public Map getProperties() + public Map getProperties() { return properties; } @@ -266,18 +292,18 @@ public class NodeImpl extends LifecycleAdapter implements Node, Serializable * For Hibernate use */ @SuppressWarnings("unused") - private void setProperties(Map properties) + private void setProperties(Map properties) { this.properties = properties; } - public DbAccessControlList getAccessControlList() + public AuditableProperties getAuditableProperties() { - return accessControlList; + return auditableProperties; } - public void setAccessControlList(DbAccessControlList accessControlList) + public void setAuditableProperties(AuditableProperties auditableProperties) { - this.accessControlList = accessControlList; + this.auditableProperties = (auditableProperties == null ? new AuditableProperties() : auditableProperties); } } diff --git a/source/java/org/alfresco/repo/domain/hibernate/NodeStatusImpl.java b/source/java/org/alfresco/repo/domain/hibernate/NodeStatusImpl.java deleted file mode 100644 index e4319f22e8..0000000000 --- a/source/java/org/alfresco/repo/domain/hibernate/NodeStatusImpl.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2005-2007 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.hibernate; - -import java.io.Serializable; - -import org.alfresco.repo.domain.Node; -import org.alfresco.repo.domain.NodeKey; -import org.alfresco.repo.domain.NodeStatus; -import org.alfresco.repo.domain.Transaction; -import org.alfresco.util.EqualsHelper; - -/** - * Hibernate implementation of a node status - * - * @author Derek Hulley - */ -public class NodeStatusImpl implements NodeStatus, Serializable -{ - private static final long serialVersionUID = -802747893314715639L; - - private NodeKey key; - private Long version; - private Node node; - private Transaction transaction; - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(50); - sb.append("NodeStatus") - .append("[key=").append(key) - .append(", node=").append(node == null ? null : node.getNodeRef()) - .append(", txn=").append(transaction) - .append("]"); - return sb.toString(); - } - - public int hashCode() - { - return (key == null) ? 0 : key.hashCode(); - } - - public boolean equals(Object obj) - { - if (obj == this) - return true; - else if (obj == null) - return false; - else if (!(obj instanceof NodeStatusImpl)) - return false; - NodeStatus that = (NodeStatus) obj; - return (EqualsHelper.nullSafeEquals(this.key, that.getKey())); - - } - - public NodeKey getKey() - { - return key; - } - - public void setKey(NodeKey key) - { - this.key = key; - } - - public Long getVersion() - { - return version; - } - - /** - * For Hibernate use - */ - @SuppressWarnings("unused") - private void setVersion(Long version) - { - this.version = version; - } - - public Node getNode() - { - return node; - } - - public void setNode(Node node) - { - this.node = node; - } - - public Transaction getTransaction() - { - return transaction; - } - - public void setTransaction(Transaction transaction) - { - this.transaction = transaction; - } - - public boolean isDeleted() - { - return (node == null); - } -} diff --git a/source/java/org/alfresco/repo/domain/hibernate/Store.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Store.hbm.xml deleted file mode 100644 index 4890153a0a..0000000000 --- a/source/java/org/alfresco/repo/domain/hibernate/Store.hbm.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/source/java/org/alfresco/repo/domain/hibernate/StoreImpl.java b/source/java/org/alfresco/repo/domain/hibernate/StoreImpl.java index 856791cbdf..4dcb4a4e31 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/StoreImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/StoreImpl.java @@ -31,8 +31,8 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import org.alfresco.repo.domain.Node; import org.alfresco.repo.domain.Store; -import org.alfresco.repo.domain.StoreKey; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.util.EqualsHelper; /** * Hibernate-specific implementation of the domain entity store. @@ -41,9 +41,11 @@ import org.alfresco.service.cmr.repository.StoreRef; */ public class StoreImpl implements Store, Serializable { - private static final long serialVersionUID = -6135740209100885890L; - - private StoreKey key; + private static final long serialVersionUID = -5501292033972362796L; + + private Long id; + private String protocol; + private String identifier; private Long version; private Node rootNode; @@ -83,7 +85,7 @@ public class StoreImpl implements Store, Serializable // double check if (storeRef == null ) { - storeRef = new StoreRef(getKey().getProtocol(), getKey().getIdentifier()); + storeRef = new StoreRef(protocol, identifier); } return storeRef; } @@ -119,7 +121,7 @@ public class StoreImpl implements Store, Serializable return false; } Store that = (Store) obj; - return (this.getKey().equals(that.getKey())); + return EqualsHelper.nullSafeEquals(this.getStoreRef(), that.getStoreRef()); } /** @@ -127,20 +129,53 @@ public class StoreImpl implements Store, Serializable */ public int hashCode() { - return getKey().hashCode(); + return protocol.hashCode() + identifier.hashCode(); } - public StoreKey getKey() + public Long getId() { - return key; + return id; } - public void setKey(StoreKey key) + /** + * For Hibernate use + */ + @SuppressWarnings("unused") + private void setId(Long id) + { + this.id = id; + } + + public String getProtocol() + { + return protocol; + } + + public void setProtocol(String protocol) { refWriteLock.lock(); try { - this.key = key; + this.protocol = protocol; + this.storeRef = null; + } + finally + { + refWriteLock.unlock(); + } + } + + public String getIdentifier() + { + return identifier; + } + + public void setIdentifier(String identifier) + { + refWriteLock.lock(); + try + { + this.identifier = identifier; this.storeRef = null; } finally diff --git a/source/java/org/alfresco/repo/domain/hibernate/Transaction.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Transaction.hbm.xml index 095362b8ff..207bb6f43f 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/Transaction.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/Transaction.hbm.xml @@ -122,53 +122,46 @@ ]]> - - select - count(txn.id) - from - org.alfresco.repo.domain.hibernate.TransactionImpl as txn - - select - count(status.key.guid) + count(node.uuid) from - org.alfresco.repo.domain.hibernate.NodeStatusImpl as status - join status.transaction as txn + org.alfresco.repo.domain.hibernate.NodeImpl as node + join node.transaction as txn where txn.id = :txnId and - status.node is not null + node.deleted = false select - count(status.key.guid) + count(node.uuid) from - org.alfresco.repo.domain.hibernate.NodeStatusImpl as status - join status.transaction as txn + org.alfresco.repo.domain.hibernate.NodeImpl as node + join node.transaction as txn where txn.id = :txnId and - status.node is null + node.deleted = true select - status + node from - org.alfresco.repo.domain.hibernate.NodeStatusImpl as status + org.alfresco.repo.domain.hibernate.NodeImpl as node where - status.transaction.id = :txnId and - status.key.protocol = :protocol and - status.key.identifier = :identifier + node.transaction.id = :txnId and + node.store.protocol = :protocol and + node.store.identifier = :identifier select - status + node from - org.alfresco.repo.domain.hibernate.NodeStatusImpl as status + org.alfresco.repo.domain.hibernate.NodeImpl as node where - status.transaction.id = :txnId + node.transaction.id = :txnId diff --git a/source/java/org/alfresco/repo/domain/hibernate/VersionCount.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/VersionCount.hbm.xml index 4a0c2a102c..aa6a521934 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/VersionCount.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/VersionCount.hbm.xml @@ -15,17 +15,37 @@ select-before-update="false" optimistic-lock="version" > - - - - - - + + + + + + + + + select + cnt + from + org.alfresco.repo.domain.hibernate.VersionCountImpl as cnt + where + cnt.store.protocol = :protocol and + cnt.store.identifier = :identifier + + diff --git a/source/java/org/alfresco/repo/domain/hibernate/VersionCountImpl.java b/source/java/org/alfresco/repo/domain/hibernate/VersionCountImpl.java index 97fa223638..4c3023dd5a 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/VersionCountImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/VersionCountImpl.java @@ -26,7 +26,7 @@ package org.alfresco.repo.domain.hibernate; import java.io.Serializable; -import org.alfresco.repo.domain.StoreKey; +import org.alfresco.repo.domain.Store; import org.alfresco.repo.domain.VersionCount; /** @@ -36,9 +36,10 @@ import org.alfresco.repo.domain.VersionCount; */ public class VersionCountImpl implements VersionCount, Serializable { - private static final long serialVersionUID = 6420375860928877809L; + private static final long serialVersionUID = 7778431129424069297L; - private StoreKey key; + private Long id; + private Store store; private long version; private int versionCount; @@ -65,7 +66,7 @@ public class VersionCountImpl implements VersionCount, Serializable return false; } VersionCount that = (VersionCount) obj; - return (this.getKey().equals(that.getKey())); + return (this.getStore().equals(that.getStore())); } /** @@ -73,7 +74,7 @@ public class VersionCountImpl implements VersionCount, Serializable */ public int hashCode() { - return getKey().hashCode(); + return getStore().hashCode(); } /** @@ -81,16 +82,31 @@ public class VersionCountImpl implements VersionCount, Serializable */ public String toString() { - return getKey().toString(); + return getStore().toString(); } - public StoreKey getKey() { - return key; + public Long getId() + { + return id; + } + + /** + * For Hibernate use + */ + @SuppressWarnings("unused") + private void setId(Long id) + { + this.id = id; + } + + public Store getStore() + { + return store; } - public void setKey(StoreKey key) + public void setStore(Store store) { - this.key = key; + this.store = store; } public Long getVersion() diff --git a/source/java/org/alfresco/repo/domain/hibernate/VersionCounterDaoComponentImpl.java b/source/java/org/alfresco/repo/domain/hibernate/VersionCounterDaoComponentImpl.java index 0803035f88..ba7c3a4831 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/VersionCounterDaoComponentImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/VersionCounterDaoComponentImpl.java @@ -24,16 +24,14 @@ */ package org.alfresco.repo.domain.hibernate; -import org.alfresco.repo.domain.StoreKey; +import org.alfresco.repo.domain.Store; import org.alfresco.repo.domain.VersionCount; -import org.alfresco.repo.node.NodeServicePolicies; -import org.alfresco.repo.policy.JavaBehaviour; -import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.version.common.counter.VersionCounterService; +import org.alfresco.service.cmr.repository.InvalidStoreRefException; import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.hibernate.LockMode; +import org.hibernate.Query; +import org.hibernate.Session; +import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; /** @@ -50,48 +48,80 @@ public class VersionCounterDaoComponentImpl extends HibernateDaoSupport implements VersionCounterService { + private static final String QUERY_GET_VERSION_COUNT_FOR_STORE = "versionCount.GetVersionCountForStore"; + private static final String QUERY_GET_STORE_BY_ALL = "store.GetStoreByAll"; + /** * Retrieves or creates a version counter. This locks the counter against updates for the * current transaction. * - * @param storeKey the primary key of the counter - * @return Returns a current or new version counter + * @param storeKey the primary key of the counter + * @param create true to create on demand + * @return Returns a current or new version counter */ - private VersionCount getVersionCounter(StoreRef storeRef) + private VersionCount getVersionCounter(final StoreRef storeRef, boolean create) { - final StoreKey storeKey = new StoreKey(storeRef.getProtocol(), storeRef.getIdentifier()); - - // check if it exists - VersionCount versionCount = (VersionCount) getHibernateTemplate().get( - VersionCountImpl.class, - storeKey, - LockMode.UPGRADE); - if (versionCount == null) + HibernateCallback callback = new HibernateCallback() { - // This could fail on some databases with concurrent adds. But it is only those databases - // that don't lock the index against an addition of the row, and then it will only fail once. - versionCount = new VersionCountImpl(); - versionCount.setKey(storeKey); - getHibernateTemplate().save(versionCount); - // debug - if (logger.isDebugEnabled()) + public Object doInHibernate(Session session) { - logger.debug("Created version counter: \n" + - " Thread: " + Thread.currentThread().getName() + "\n" + - " Version count: " + versionCount.getVersionCount()); + Query query = session + .getNamedQuery(VersionCounterDaoComponentImpl.QUERY_GET_VERSION_COUNT_FOR_STORE) + .setString("protocol", storeRef.getProtocol()) + .setString("identifier", storeRef.getIdentifier()); + return query.uniqueResult(); } - } - else + }; + VersionCount versionCount = (VersionCount) getHibernateTemplate().execute(callback); + + // Done if it exists + if (versionCount != null) { - // debug + // Debug if (logger.isDebugEnabled()) { logger.debug("Got version counter: \n" + " Thread: " + Thread.currentThread().getName() + "\n" + " Version count: " + versionCount.getVersionCount()); } + // Done + return versionCount; } - // done + else if (!create) + { + return null; + } + + // We have permission to create + callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(VersionCounterDaoComponentImpl.QUERY_GET_STORE_BY_ALL) + .setString("protocol", storeRef.getProtocol()) + .setString("identifier", storeRef.getIdentifier()); + return query.uniqueResult(); + } + }; + Store store = (Store) getHibernateTemplate().execute(callback); + if (store == null) + { + throw new InvalidStoreRefException(storeRef); + } + versionCount = new VersionCountImpl(); + versionCount.setStore(store); + getHibernateTemplate().save(versionCount); + + // Debug + if (logger.isDebugEnabled()) + { + logger.debug("Created version counter: \n" + + " Thread: " + Thread.currentThread().getName() + "\n" + + " Version count: " + versionCount.getVersionCount()); + } + + // Done return versionCount; } @@ -104,7 +134,7 @@ public class VersionCounterDaoComponentImpl public int nextVersionNumber(StoreRef storeRef) { // get the version counter - VersionCount versionCount = getVersionCounter(storeRef); + VersionCount versionCount = getVersionCounter(storeRef, true); // get an incremented count int nextCount = versionCount.incrementVersionCount(); @@ -127,9 +157,9 @@ public class VersionCounterDaoComponentImpl public int currentVersionNumber(StoreRef storeRef) { // get the version counter - VersionCount versionCounter = getVersionCounter(storeRef); + VersionCount versionCounter = getVersionCounter(storeRef, false); // get an incremented count - return versionCounter.getVersionCount(); + return versionCounter == null ? 0 : versionCounter.getVersionCount(); } /** @@ -143,7 +173,7 @@ public class VersionCounterDaoComponentImpl public synchronized void resetVersionNumber(StoreRef storeRef) { // get the version counter - VersionCount versionCounter = getVersionCounter(storeRef); + VersionCount versionCounter = getVersionCounter(storeRef, true); // get an incremented count versionCounter.resetVersionCount(); } @@ -160,9 +190,8 @@ public class VersionCounterDaoComponentImpl public synchronized void setVersionNumber(StoreRef storeRef, int versionCount) { // get the version counter - VersionCount versionCounter = getVersionCounter(storeRef); + VersionCount versionCounter = getVersionCounter(storeRef, true); // get an incremented count versionCounter.setVersionCount(versionCount); } - } diff --git a/source/java/org/alfresco/repo/domain/hibernate/dialect/AlfrescoOracle10gDialect.java b/source/java/org/alfresco/repo/domain/hibernate/dialect/AlfrescoOracle10gDialect.java new file mode 100644 index 0000000000..8f4e70793a --- /dev/null +++ b/source/java/org/alfresco/repo/domain/hibernate/dialect/AlfrescoOracle10gDialect.java @@ -0,0 +1,20 @@ +package org.alfresco.repo.domain.hibernate.dialect; + +import java.sql.Types; + +import org.hibernate.dialect.Oracle10gDialect; + +/** + * Does away with the deprecated LONG datatype. + * + * @author Derek Hulley + * @since 2.2.2 + */ +public class AlfrescoOracle10gDialect extends Oracle10gDialect +{ + public AlfrescoOracle10gDialect() + { + super(); + registerColumnType( Types.VARCHAR, "blob" ); + } +} diff --git a/source/java/org/alfresco/repo/domain/hibernate/dialect/AlfrescoOracle9iDialect.java b/source/java/org/alfresco/repo/domain/hibernate/dialect/AlfrescoOracle9iDialect.java new file mode 100644 index 0000000000..199eb8d140 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/hibernate/dialect/AlfrescoOracle9iDialect.java @@ -0,0 +1,20 @@ +package org.alfresco.repo.domain.hibernate.dialect; + +import java.sql.Types; + +import org.hibernate.dialect.Oracle9iDialect; + +/** + * Does away with the deprecated LONG datatype. + * + * @author Derek Hulley + * @since 2.2.2 + */ +public class AlfrescoOracle9iDialect extends Oracle9iDialect +{ + public AlfrescoOracle9iDialect() + { + super(); + registerColumnType( Types.VARCHAR, "blob" ); + } +} diff --git a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java index 7a55e720a6..60e31e435e 100644 --- a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java +++ b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java @@ -46,6 +46,8 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch; import org.alfresco.repo.content.filestore.FileContentWriter; import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.domain.hibernate.dialect.AlfrescoOracle10gDialect; +import org.alfresco.repo.domain.hibernate.dialect.AlfrescoOracle9iDialect; import org.alfresco.repo.domain.hibernate.dialect.AlfrescoSQLServerDialect; import org.alfresco.repo.domain.hibernate.dialect.AlfrescoSybaseAnywhereDialect; import org.alfresco.service.ServiceRegistry; @@ -71,7 +73,10 @@ import org.hibernate.dialect.HSQLDialect; import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.MySQLInnoDBDialect; +import org.hibernate.dialect.Oracle10gDialect; import org.hibernate.dialect.Oracle9Dialect; +import org.hibernate.dialect.Oracle9iDialect; +import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.engine.ActionQueue; import org.hibernate.tool.hbm2ddl.DatabaseMetadata; @@ -103,6 +108,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean private static final String MSG_EXECUTING_STATEMENT = "schema.update.msg.executing_statement"; private static final String MSG_OPTIONAL_STATEMENT_FAILED = "schema.update.msg.optional_statement_failed"; private static final String WARN_DIALECT_UNSUPPORTED = "schema.update.warn.dialect_unsupported"; + private static final String WARN_DIALECT_SUBSTITUTING = "schema.update.warn.dialect_substituting"; private static final String WARN_DIALECT_HSQL = "schema.update.warn.dialect_hsql"; private static final String WARN_DIALECT_DERBY = "schema.update.warn.dialect_derby"; private static final String ERR_MULTIPLE_SCHEMAS = "schema.update.err.found_multiple"; @@ -509,6 +515,40 @@ public class SchemaBootstrap extends AbstractLifecycleBean } } + /** + * Finds the version.properties file and determines the installed version.schema.
+ * The only way to determine the original installed schema number is by quering the for the minimum value in + * alf_applied_patch.applied_to_schema. This might not work if an upgrade is attempted straight from + * Alfresco v1.0! + * + * @return the installed schema number or -1 if the installation is new. + */ + private int getInstalledSchemaNumber(Connection connection) throws Exception + { + Statement stmt = connection.createStatement(); + try + { + ResultSet rs = stmt.executeQuery( + "select min(applied_to_schema) from alf_applied_patch where applied_to_schema > -1;"); + if (!rs.next()) + { + // Nothing in the table + return -1; + } + if (rs.getObject(1) == null) + { + // Nothing in the table + return -1; + } + int installedSchema = rs.getInt(1); + return installedSchema; + } + finally + { + try { stmt.close(); } catch (Throwable e) {} + } + } + private static class LockFailedException extends Exception { private static final long serialVersionUID = -6676398230191205456L; @@ -655,6 +695,8 @@ public class SchemaBootstrap extends AbstractLifecycleBean // and patches will not have been applied yet return; } + // Retrieve the first installed schema number + int installedSchema = getInstalledSchemaNumber(connection); for (SchemaUpgradeScriptPatch patch : scriptPatches) { @@ -669,6 +711,11 @@ public class SchemaBootstrap extends AbstractLifecycleBean // with the patch bean present. continue; } + else if (!patch.applies(installedSchema)) + { + // Patch does not apply to the installed schema number + continue; + } else if (!apply) { // the script was not run and may not be run automatically @@ -820,7 +867,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean { // Get the end of statement int endIndex = sql.lastIndexOf(';'); - if (endIndex > 0) + if (endIndex > -1) { sql = sql.substring(0, endIndex); execute = true; @@ -892,6 +939,42 @@ public class SchemaBootstrap extends AbstractLifecycleBean } } + /** + * Substitute the dialect with an alternative, if possible. + */ + private void changeDialect(Configuration cfg) + { + String dialectName = cfg.getProperty(Environment.DIALECT); + if (dialectName == null) + { + return; + } + else if (dialectName.equals(Oracle9iDialect.class.getName())) + { + String subst = AlfrescoOracle9iDialect.class.getName(); + LogUtil.warn(logger, WARN_DIALECT_SUBSTITUTING, dialectName, subst); + cfg.setProperty(Environment.DIALECT, subst); + } + else if (dialectName.equals(Oracle10gDialect.class.getName())) + { + String subst = AlfrescoOracle10gDialect.class.getName(); + LogUtil.warn(logger, WARN_DIALECT_SUBSTITUTING, dialectName, subst); + cfg.setProperty(Environment.DIALECT, subst); + } + else if (dialectName.equals(MySQLDialect.class.getName())) + { + String subst = MySQLInnoDBDialect.class.getName(); + LogUtil.warn(logger, WARN_DIALECT_SUBSTITUTING, dialectName, subst); + cfg.setProperty(Environment.DIALECT, subst); + } + else if (dialectName.equals(MySQL5Dialect.class.getName())) + { + String subst = MySQLInnoDBDialect.class.getName(); + LogUtil.warn(logger, WARN_DIALECT_SUBSTITUTING, dialectName, subst); + cfg.setProperty(Environment.DIALECT, subst); + } + } + /** * Performs dialect-specific checking. This includes checking for InnoDB, dumping the dialect being used * as well as setting any runtime, dialect-specific properties. @@ -904,14 +987,18 @@ public class SchemaBootstrap extends AbstractLifecycleBean { LogUtil.warn(logger, WARN_DIALECT_UNSUPPORTED, dialectClazz.getName()); } - if (dialectClazz.equals(HSQLDialect.class)) + else if (dialectClazz.equals(HSQLDialect.class)) { LogUtil.info(logger, WARN_DIALECT_HSQL); } - if (dialectClazz.equals(DerbyDialect.class)) + else if (dialectClazz.equals(DerbyDialect.class)) { LogUtil.info(logger, WARN_DIALECT_DERBY); } + else if (dialectClazz.equals(OracleDialect.class) || dialectClazz.equals(Oracle9Dialect.class)) + { + LogUtil.warn(logger, WARN_DIALECT_UNSUPPORTED, dialectClazz.getName()); + } int maxStringLength = SchemaBootstrap.DEFAULT_MAX_STRING_LENGTH; // Adjust the maximum allowable String length according to the dialect @@ -945,10 +1032,10 @@ public class SchemaBootstrap extends AbstractLifecycleBean // serializable_value blob, maxStringLength = Integer.MAX_VALUE; } - else if (dialect instanceof Oracle9Dialect) + else if (dialect instanceof OracleDialect) { // string_value varchar2(1024 char), - // serializable_value long raw, + // serializable_value blob, maxStringLength = SchemaBootstrap.DEFAULT_MAX_STRING_LENGTH; } else if (dialect instanceof PostgreSQLDialect) @@ -980,6 +1067,9 @@ public class SchemaBootstrap extends AbstractLifecycleBean Configuration cfg = localSessionFactory.getConfiguration(); + // Fix the dialect + changeDialect(cfg); + // Check and dump the dialect being used Dialect dialect = Dialect.getDialect(cfg.getProperties()); checkDialect(dialect); diff --git a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java index 86ee356f9d..eb631a8992 100644 --- a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java @@ -25,7 +25,6 @@ package org.alfresco.repo.node; import java.io.Serializable; -import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -34,7 +33,6 @@ import java.util.Set; import java.util.concurrent.locks.ReentrantLock; import org.alfresco.model.ContentModel; -import org.alfresco.repo.domain.PropertyValue; import org.alfresco.repo.node.NodeServicePolicies.BeforeAddAspectPolicy; import org.alfresco.repo.node.NodeServicePolicies.BeforeCreateChildAssociationPolicy; import org.alfresco.repo.node.NodeServicePolicies.BeforeCreateNodeAssociationPolicy; @@ -65,7 +63,6 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryException; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.repository.AssociationRef; @@ -74,7 +71,6 @@ import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.repository.datatype.TypeConversionException; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QNamePattern; import org.alfresco.service.namespace.RegexQNamePattern; @@ -605,115 +601,6 @@ public abstract class AbstractNodeServiceImpl implements NodeService { return getChildAssocs(nodeRef, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL); } - - /** - * Helper method to convert the Serializable value into a full, - * persistable {@link PropertyValue}. - *

- * Where the property definition is null, the value will take on the - * {@link DataTypeDefinition#ANY generic ANY} value. - *

- * Where the property definition specifies a multi-valued property but the - * value provided is not a collection, the value will be wrapped in a collection. - * - * @param propertyDef the property dictionary definition, may be null - * @param value the value, which will be converted according to the definition - - * may be null - * @return Returns the persistable property value - */ - protected PropertyValue makePropertyValue(PropertyDefinition propertyDef, Serializable value) - { - // get property attributes - QName propertyTypeQName = null; - if (propertyDef == null) // property not recognised - { - // allow it for now - persisting excess properties can be useful sometimes - propertyTypeQName = DataTypeDefinition.ANY; - } - else - { - propertyTypeQName = propertyDef.getDataType().getName(); - // check that multi-valued properties are allowed - boolean isMultiValued = propertyDef.isMultiValued(); - if (isMultiValued && !(value instanceof Collection)) - { - if (value != null) - { - // put the value into a collection - // the implementation gives back a Serializable list - value = (Serializable) Collections.singletonList(value); - } - } - else if (!isMultiValued && (value instanceof Collection)) - { - // we only allow this case if the property type is ANY - if (!propertyTypeQName.equals(DataTypeDefinition.ANY)) - { - throw new DictionaryException( - "A single-valued property of this type may not be a collection: \n" + - " Property: " + propertyDef + "\n" + - " Type: " + propertyTypeQName + "\n" + - " Value: " + value); - } - } - } - try - { - PropertyValue propertyValue = new PropertyValue(propertyTypeQName, value); - // done - return propertyValue; - } - catch (TypeConversionException e) - { - throw new TypeConversionException( - "The property value is not compatible with the type defined for the property: \n" + - " property: " + (propertyDef == null ? "unknown" : propertyDef) + "\n" + - " value: " + value + "\n" + - " value type: " + value.getClass(), - e); - } - } - - /** - * Extracts the externally-visible property from the {@link PropertyValue propertyValue}. - * - * @param propertyDef the model property definition - may be null - * @param propertyValue the persisted property - * @return Returns the value of the property in the format dictated by the property - * definition, or null if the property value is null - */ - protected Serializable makeSerializableValue(PropertyDefinition propertyDef, PropertyValue propertyValue) - { - if (propertyValue == null) - { - return null; - } - // get property attributes - QName propertyTypeQName = null; - if (propertyDef == null) - { - // allow this for now - propertyTypeQName = DataTypeDefinition.ANY; - } - else - { - propertyTypeQName = propertyDef.getDataType().getName(); - } - try - { - Serializable value = propertyValue.getValue(propertyTypeQName); - // done - return value; - } - catch (TypeConversionException e) - { - throw new TypeConversionException( - "The property value is not compatible with the type defined for the property: \n" + - " property: " + (propertyDef == null ? "unknown" : propertyDef) + "\n" + - " property value: " + propertyValue, - e); - } - } protected Map getDefaultProperties(QName typeQName) { diff --git a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java index d6a5498028..de37f923e3 100644 --- a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java +++ b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java @@ -102,6 +102,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest public static final String TEST_PREFIX = "test"; public static final QName TYPE_QNAME_TEST_CONTENT = QName.createQName(NAMESPACE, "content"); public static final QName TYPE_QNAME_TEST_MANY_PROPERTIES = QName.createQName(NAMESPACE, "many-properties"); + public static final QName TYPE_QNAME_TEST_MANY_ML_PROPERTIES = QName.createQName(NAMESPACE, "many-ml-properties"); public static final QName TYPE_QNAME_EXTENDED_CONTENT = QName.createQName(NAMESPACE, "extendedcontent"); public static final QName ASPECT_QNAME_TEST_TITLED = QName.createQName(NAMESPACE, "titled"); public static final QName ASPECT_QNAME_TEST_MARKER = QName.createQName(NAMESPACE, "marker"); @@ -127,6 +128,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest public static final QName PROP_QNAME_LOCALE_VALUE = QName.createQName(NAMESPACE, "localeValue"); public static final QName PROP_QNAME_NULL_VALUE = QName.createQName(NAMESPACE, "nullValue"); public static final QName PROP_QNAME_MULTI_VALUE = QName.createQName(NAMESPACE, "multiValue"); + public static final QName PROP_QNAME_MULTI_ML_VALUE = QName.createQName(NAMESPACE, "multiMLValue"); public static final QName PROP_QNAME_PROP1 = QName.createQName(NAMESPACE, "prop1"); public static final QName PROP_QNAME_PROP2 = QName.createQName(NAMESPACE, "prop2"); public static final QName ASSOC_TYPE_QNAME_TEST_CHILDREN = ContentModel.ASSOC_CHILDREN; @@ -414,6 +416,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest return ret; } + @SuppressWarnings("unchecked") private int countNodesByReference(NodeRef nodeRef) { String query = @@ -422,8 +425,9 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest NodeImpl.class.getName() + " node" + " where" + " node.uuid = ? and" + - " node.store.key.protocol = ? and" + - " node.store.key.identifier = ?"; + " node.deleted = false and" + + " node.store.protocol = ? and" + + " node.store.identifier = ?"; Session session = getSession(); List results = session.createQuery(query) .setString(0, nodeRef.getId()) @@ -944,6 +948,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest endTransaction(); } + @SuppressWarnings("unchecked") private int countChildrenOfNode(NodeRef nodeRef) { String query = @@ -951,7 +956,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest " from " + ChildAssocImpl.class.getName() + " childAssoc" + " join childAssoc.parent node" + - " where node.uuid = ? and node.store.key.protocol = ? and node.store.key.identifier = ?"; + " where node.uuid = ? and node.store.protocol = ? and node.store.identifier = ?"; Session session = getSession(); List results = session.createQuery(query) .setString(0, nodeRef.getId()) @@ -1356,6 +1361,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest * Check that properties go in and come out in the correct format. * @see #getCheckPropertyValues(Map) */ + @SuppressWarnings("unchecked") public void testPropertyTypes() throws Exception { ArrayList listProperty = new ArrayList(2); @@ -1415,6 +1421,127 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest assertTrue("Collection doesn't contain value", ((Collection)checkProperty).contains("GHI")); } + /** + * Checks that empty collections can be persisted + */ + @SuppressWarnings("unchecked") + public void testEmptyCollections() throws Exception + { + NodeRef nodeRef = nodeService.createNode( + rootNodeRef, + ASSOC_TYPE_QNAME_TEST_CHILDREN, + QName.createQName("pathA"), + TYPE_QNAME_TEST_MANY_PROPERTIES).getChildRef(); + + List filledCollection = new ArrayList(2); + filledCollection.add("ABC"); + filledCollection.add("DEF"); + List emptyCollection = Collections.emptyList(); + + nodeService.setProperty(nodeRef, PROP_QNAME_MULTI_VALUE, (Serializable) filledCollection); + List checkFilledCollection = (List) nodeService.getProperty(nodeRef, PROP_QNAME_MULTI_VALUE); + assertEquals("Filled collection didn't come back with correct values", filledCollection, checkFilledCollection); + + nodeService.setProperty(nodeRef, PROP_QNAME_MULTI_VALUE, (Serializable) emptyCollection); + List checkEmptyCollection = (List) nodeService.getProperty(nodeRef, PROP_QNAME_MULTI_VALUE); + assertEquals("Empty collection didn't come back with correct values", emptyCollection, checkEmptyCollection); + + // Check that a null value is returned as null + nodeService.setProperty(nodeRef, PROP_QNAME_MULTI_VALUE, null); + List checkNullCollection = (List) nodeService.getProperty(nodeRef, PROP_QNAME_MULTI_VALUE); + assertNull("Null property should stay null", checkNullCollection); + } + + /** + * Checks that large collections can be persisted + */ + @SuppressWarnings("unchecked") + public void testBigCollections() throws Exception + { + NodeRef nodeRef = nodeService.createNode( + rootNodeRef, + ASSOC_TYPE_QNAME_TEST_CHILDREN, + QName.createQName("pathA"), + TYPE_QNAME_TEST_MANY_PROPERTIES).getChildRef(); + + for (int inc = 0; inc < 5; inc++) + { + System.out.println("----------------------------------------------"); + int collectionSize = (int) Math.pow(10, inc); + List largeCollection = new ArrayList(collectionSize); + for (int i = 0; i < collectionSize; i++) + { + largeCollection.add(String.format("Large-collection-value-%05d", i)); + } + List emptyCollection = Collections.emptyList(); + + long t1 = System.nanoTime(); + nodeService.setProperty(nodeRef, PROP_QNAME_MULTI_VALUE, (Serializable) largeCollection); + double tDelta = (double)(System.nanoTime() - t1)/1E6; + System.out.println("Setting " + collectionSize + " multi-valued property took: " + tDelta + "ms"); + // Now get it back + t1 = System.nanoTime(); + List checkLargeCollection = (List) nodeService.getProperty(nodeRef, PROP_QNAME_MULTI_VALUE); + tDelta = (double)(System.nanoTime() - t1)/1E6; + System.out.println("First fetch of " + collectionSize + " multi-valued property took: " + tDelta + "ms"); + assertEquals("Large collection didn't come back with correct values", largeCollection, checkLargeCollection); + + // Get it back again + t1 = System.nanoTime(); + checkLargeCollection = (List) nodeService.getProperty(nodeRef, PROP_QNAME_MULTI_VALUE); + tDelta = (double)(System.nanoTime() - t1)/1E6; + System.out.println("Second fetch of " + collectionSize + " multi-valued property took: " + tDelta + "ms"); + + // Add a value + largeCollection.add("First addition"); + t1 = System.nanoTime(); + nodeService.setProperty(nodeRef, PROP_QNAME_MULTI_VALUE, (Serializable) largeCollection); + tDelta = (double)(System.nanoTime() - t1)/1E6; + System.out.println("Re-setting " + largeCollection.size() + " multi-valued property took: " + tDelta + "ms"); + + // Add another value + largeCollection.add("Second addition"); + t1 = System.nanoTime(); + nodeService.setProperty(nodeRef, PROP_QNAME_MULTI_VALUE, (Serializable) largeCollection); + tDelta = (double)(System.nanoTime() - t1)/1E6; + System.out.println("Re-setting " + largeCollection.size() + " multi-valued property took: " + tDelta + "ms"); + + nodeService.setProperty(nodeRef, PROP_QNAME_MULTI_VALUE, (Serializable) emptyCollection); + List checkEmptyCollection = (List) nodeService.getProperty(nodeRef, PROP_QNAME_MULTI_VALUE); + assertEquals("Empty collection didn't come back with correct values", emptyCollection, checkEmptyCollection); + + // Check that a null value is returned as null + nodeService.setProperty(nodeRef, PROP_QNAME_MULTI_VALUE, null); + List checkNullCollection = (List) nodeService.getProperty(nodeRef, PROP_QNAME_MULTI_VALUE); + assertNull("Null property should stay null", checkNullCollection); + } + } + + @SuppressWarnings("unchecked") + public void testMultiValueMLTextProperties() throws Exception + { + NodeRef nodeRef = nodeService.createNode( + rootNodeRef, + ASSOC_TYPE_QNAME_TEST_CHILDREN, + QName.createQName("pathA"), + TYPE_QNAME_TEST_MANY_ML_PROPERTIES).getChildRef(); + + // Create MLText properties and add to a collection + List mlTextCollection = new ArrayList(2); + MLText mlText0 = new MLText(); + mlText0.addValue(Locale.ENGLISH, "Hello"); + mlText0.addValue(Locale.FRENCH, "Bonjour"); + mlTextCollection.add(mlText0); + MLText mlText1 = new MLText(); + mlText1.addValue(Locale.ENGLISH, "Bye bye"); + mlText1.addValue(Locale.FRENCH, "Au revoir"); + mlTextCollection.add(mlText1); + + nodeService.setProperty(nodeRef, PROP_QNAME_MULTI_ML_VALUE, (Serializable) mlTextCollection); + Collection mlTextCollectionCheck = (Collection) nodeService.getProperty(nodeRef, PROP_QNAME_MULTI_ML_VALUE); + assertEquals("MLText collection didn't come back correctly.", mlTextCollection, mlTextCollectionCheck); + } + /** * Checks that the {@link ContentModel#ASPECT_REFERENCEABLE referencable} properties * are present @@ -2083,13 +2210,13 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest public void testAR782() throws Exception { Map properties = nodeService.getProperties(rootNodeRef); - // Set cm:created correctly - properties.put(ContentModel.PROP_CREATED, new Date()); + // Set usr:accountExpiryDate correctly + properties.put(ContentModel.PROP_ACCOUNT_EXPIRY_DATE, new Date()); nodeService.setProperties(rootNodeRef, properties); try { - // Set cm:created using something that can't be converted to a Date - properties.put(ContentModel.PROP_CREATED, "blah"); + // Set usr:accountExpiryDate using something that can't be converted to a Date + properties.put(ContentModel.PROP_ACCOUNT_EXPIRY_DATE, "blah"); nodeService.setProperties(rootNodeRef, properties); fail("Failed to catch type conversion issue early."); } diff --git a/source/java/org/alfresco/repo/node/BaseNodeServiceTest_model.xml b/source/java/org/alfresco/repo/node/BaseNodeServiceTest_model.xml index a5a6c057fe..49f5f11b5a 100644 --- a/source/java/org/alfresco/repo/node/BaseNodeServiceTest_model.xml +++ b/source/java/org/alfresco/repo/node/BaseNodeServiceTest_model.xml @@ -275,6 +275,18 @@ + + Busy2 + sys:base + + + d:mltext + true + true + + + + Rendition Page sys:base diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index 38bd762ac0..314395e9d3 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -40,7 +40,6 @@ import java.util.Stack; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.Node; -import org.alfresco.repo.domain.PropertyValue; import org.alfresco.repo.node.AbstractNodeServiceImpl; import org.alfresco.repo.node.StoreArchiveMap; import org.alfresco.repo.node.db.NodeDaoService.NodeRefQueryCallback; @@ -52,7 +51,6 @@ import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.InvalidAspectException; import org.alfresco.service.cmr.dictionary.InvalidTypeException; import org.alfresco.service.cmr.dictionary.PropertyDefinition; @@ -313,21 +311,19 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // set the properties passed in if (properties.size() > 0) { - Map propertiesConverted = convertProperties(properties); - nodeDaoService.addNodeProperties(childNodePair.getFirst(), propertiesConverted); + nodeDaoService.addNodeProperties(childNodePair.getFirst(), properties); } - Map propertiesAfterValues = nodeDaoService.getNodeProperties(childNodePair.getFirst()); + Map propertiesAfter = nodeDaoService.getNodeProperties(childNodePair.getFirst()); // Ensure child uniqueness - String newName = extractNameProperty(propertiesAfterValues); + String newName = extractNameProperty(propertiesAfter); // Ensure uniqueness. Note that the cm:name may be null, in which case the uniqueness is still setChildNameUnique(childAssocPair, newName, null); // ensure uniqueness // Invoke policy behaviour invokeOnCreateNode(childAssocRef); invokeOnCreateChildAssociation(childAssocRef, true); - Map propertiesAfter = convertPropertyValues(propertiesAfterValues); addIntrinsicProperties(childNodePair, propertiesAfter); invokeOnUpdateProperties( childAssocRef.getChildRef(), @@ -364,16 +360,20 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl } // Get the existing values Long nodeId = nodePair.getFirst(); - Map existingPropertyValues = nodeDaoService.getNodeProperties(nodeId); + Map existingProperties = nodeDaoService.getNodeProperties(nodeId); Set existingAspects = nodeDaoService.getNodeAspects(nodeId); - return addDefaultAspects(nodePair, existingAspects, existingPropertyValues, typeQName); + return addDefaultAspects(nodePair, existingAspects, existingProperties, typeQName); } /** * Add the default aspects to a given node * @return Returns true if any aspects were added */ - private boolean addDefaultAspects(Pair nodePair, Set existingAspects, Map existingPropertyValues, QName typeQName) + private boolean addDefaultAspects( + Pair nodePair, + Set existingAspects, + Map existingProperties, + QName typeQName) { ClassDefinition classDefinition = dictionaryService.getClass(typeQName); if (classDefinition == null) @@ -428,33 +428,32 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl } // Get the existing values Long nodeId = nodePair.getFirst(); - Map existingPropertyValues = nodeDaoService.getNodeProperties(nodeId); - return addDefaultProperties(nodePair, existingPropertyValues, typeQName); + Map existingProperties = nodeDaoService.getNodeProperties(nodeId); + return addDefaultProperties(nodePair, existingProperties, typeQName); } /** * Adds default properties for the given type to the node. Default values will not be set if there are existing values. */ - private boolean addDefaultProperties(Pair nodePair, Map existingPropertyValues, QName typeQName) + private boolean addDefaultProperties(Pair nodePair, Map existingProperties, QName typeQName) { Long nodeId = nodePair.getFirst(); // Get the default properties for this aspect Map defaultProperties = getDefaultProperties(typeQName); - Map defaultPropertyValues = this.convertProperties(defaultProperties); // Remove all default values where a value already exists - for (Map.Entry entry : existingPropertyValues.entrySet()) + for (Map.Entry entry : existingProperties.entrySet()) { QName existingPropertyQName = entry.getKey(); - PropertyValue existingPropertyValue = entry.getValue(); - if (existingPropertyValue != null) + Serializable existingProperty = entry.getValue(); + if (existingProperty != null) { - defaultPropertyValues.remove(existingPropertyQName); + defaultProperties.remove(existingPropertyQName); } } // Add the properties to the node - but only if there is anything to set - if (defaultPropertyValues.size() > 0) + if (defaultProperties.size() > 0) { - nodeDaoService.addNodeProperties(nodeId, defaultPropertyValues); + nodeDaoService.addNodeProperties(nodeId, defaultProperties); return true; } else @@ -567,8 +566,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl if (aspectProperties.size() > 0) { - Map aspectPropertyValues = convertProperties(aspectProperties); - nodeDaoService.addNodeProperties(nodeId, aspectPropertyValues); + nodeDaoService.addNodeProperties(nodeId, aspectProperties); } if (!nodeDaoService.hasNodeAspect(nodeId, aspectTypeQName)) @@ -995,20 +993,16 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl return nodeId; } - PropertyValue propertyValue = nodeDaoService.getNodeProperty(nodeId, qname); + Serializable property = nodeDaoService.getNodeProperty(nodeId, qname); // check if we need to provide a spoofed name - if (propertyValue == null && qname.equals(ContentModel.PROP_NAME)) + if (property == null && qname.equals(ContentModel.PROP_NAME)) { return nodeRef.getId(); } - // get the property definition - PropertyDefinition propertyDef = dictionaryService.getProperty(qname); - // convert to the correct type - Serializable value = makeSerializableValue(propertyDef, propertyValue); // done - return value; + return property; } public Map getProperties(NodeRef nodeRef) throws InvalidNodeRefException @@ -1023,24 +1017,11 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl private Map getPropertiesImpl(Pair nodePair) throws InvalidNodeRefException { Long nodeId = nodePair.getFirst(); - Map nodeProperties = nodeDaoService.getNodeProperties(nodeId); - Map ret = new HashMap(nodeProperties.size()); - // copy values - for (Map.Entry entry: nodeProperties.entrySet()) - { - QName propertyQName = entry.getKey(); - PropertyValue propertyValue = entry.getValue(); - // get the property definition - PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); - // convert to the correct type - Serializable value = makeSerializableValue(propertyDef, propertyValue); - // copy across - ret.put(propertyQName, value); - } + Map nodeProperties = nodeDaoService.getNodeProperties(nodeId); // spoof referencable properties - addIntrinsicProperties(nodePair, ret); + addIntrinsicProperties(nodePair, nodeProperties); // done - return ret; + return nodeProperties; } /** @@ -1099,10 +1080,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl } } // Set the property - PropertyDefinition propertyDef = dictionaryService.getProperty(qname); - // get a persistable value - PropertyValue propertyValue = makePropertyValue(propertyDef, value); - nodeDaoService.addNodeProperty(nodeId, qname, propertyValue); + nodeDaoService.addNodeProperty(nodeId, qname, value); } } @@ -1155,10 +1133,8 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl } // Now remove special properties extractIntrinsicProperties(properties); - // convert the map - Map propertyValues = convertProperties(properties); // Update the node - nodeDaoService.setNodeProperties(nodeId, propertyValues); + nodeDaoService.setNodeProperties(nodeId, properties); } public void removeProperty(NodeRef nodeRef, QName qname) throws InvalidNodeRefException @@ -1194,42 +1170,42 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl nodeIndexer.indexUpdateNode(nodeRef); } - private Map convertProperties(Map properties) throws InvalidNodeRefException - { - Map convertedProperties = new HashMap(17); - - // check the property type and copy the values across - for (QName propertyQName : properties.keySet()) - { - PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); - Serializable value = properties.get(propertyQName); - // get a persistable value - PropertyValue propertyValue = makePropertyValue(propertyDef, value); - convertedProperties.put(propertyQName, propertyValue); - } - - // Return the converted properties - return convertedProperties; - } - - private Map convertPropertyValues(Map propertyValues) throws InvalidNodeRefException - { - Map convertedProperties = new HashMap(17); - - // check the property type and copy the values across - for (Map.Entry entry : propertyValues.entrySet()) - { - QName propertyQName = entry.getKey(); - PropertyValue propertyValue = entry.getValue(); - PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); - Serializable property = makeSerializableValue(propertyDef, propertyValue); - convertedProperties.put(propertyQName, property); - } - - // Return the converted properties - return convertedProperties; - } - +// private Map convertProperties(Map properties) throws InvalidNodeRefException +// { +// Map convertedProperties = new HashMap(17); +// +// // check the property type and copy the values across +// for (QName propertyQName : properties.keySet()) +// { +// PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); +// Serializable value = properties.get(propertyQName); +// // get a persistable value +// PropertyValue propertyValue = makePropertyValue(propertyDef, value); +// convertedProperties.put(propertyQName, propertyValue); +// } +// +// // Return the converted properties +// return convertedProperties; +// } +// +// private Map convertPropertyValues(Map propertyValues) throws InvalidNodeRefException +// { +// Map convertedProperties = new HashMap(17); +// +// // check the property type and copy the values across +// for (Map.Entry entry : propertyValues.entrySet()) +// { +// QName propertyQName = entry.getKey(); +// PropertyValue propertyValue = entry.getValue(); +// PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); +// Serializable property = makeSerializableValue(propertyDef, propertyValue); +// convertedProperties.put(propertyQName, property); +// } +// +// // Return the converted properties +// return convertedProperties; +// } +// public Collection getParents(NodeRef nodeRef) throws InvalidNodeRefException { // Get the node @@ -1708,41 +1684,29 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl Long nodeId = nodePair.getFirst(); Pair primaryParentAssocPair = nodeDaoService.getPrimaryParentAssoc(nodeId); Set newAspects = new HashSet(5); - Map existingPropertyValues = nodeDaoService.getNodeProperties(nodeId); - Map newPropertyValues = new HashMap(11); + Map existingProperties = nodeDaoService.getNodeProperties(nodeId); + Map newProperties = new HashMap(11); // add the aspect newAspects.add(ContentModel.ASPECT_ARCHIVED); - PropertyValue archivedByProperty = makePropertyValue( - dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_BY), - AuthenticationUtil.getCurrentUserName()); - newPropertyValues.put(ContentModel.PROP_ARCHIVED_BY, archivedByProperty); - PropertyValue archivedDateProperty = makePropertyValue( - dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_DATE), - new Date()); - newPropertyValues.put(ContentModel.PROP_ARCHIVED_DATE, archivedDateProperty); - PropertyValue archivedPrimaryParentNodeRefProperty = makePropertyValue( - dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC), - primaryParentAssocPair.getSecond()); - newPropertyValues.put(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC, archivedPrimaryParentNodeRefProperty); - PropertyValue originalOwnerProperty = existingPropertyValues.get(ContentModel.PROP_OWNER); - PropertyValue originalCreatorProperty = existingPropertyValues.get(ContentModel.PROP_CREATOR); - if (originalOwnerProperty != null || originalCreatorProperty != null) + newProperties.put(ContentModel.PROP_ARCHIVED_BY, AuthenticationUtil.getCurrentUserName()); + newProperties.put(ContentModel.PROP_ARCHIVED_DATE, new Date()); + newProperties.put(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC, primaryParentAssocPair.getSecond()); + Serializable originalOwner = existingProperties.get(ContentModel.PROP_OWNER); + Serializable originalCreator = existingProperties.get(ContentModel.PROP_CREATOR); + if (originalOwner != null || originalCreator != null) { - newPropertyValues.put( + newProperties.put( ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER, - originalOwnerProperty != null ? originalOwnerProperty : originalCreatorProperty); + originalOwner != null ? originalOwner : originalCreator); } // change the node ownership newAspects.add(ContentModel.ASPECT_OWNABLE); - PropertyValue newOwnerProperty = makePropertyValue( - dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER), - AuthenticationUtil.getCurrentUserName()); - newPropertyValues.put(ContentModel.PROP_OWNER, newOwnerProperty); + newProperties.put(ContentModel.PROP_OWNER, AuthenticationUtil.getCurrentUserName()); // Set the aspects and properties - nodeDaoService.addNodeProperties(nodeId, newPropertyValues); + nodeDaoService.addNodeProperties(nodeId, newProperties); nodeDaoService.addNodeAspects(nodeId, newAspects); // move the node @@ -1760,18 +1724,17 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl Long archivedNodeId = archivedNodePair.getFirst(); Set existingAspects = nodeDaoService.getNodeAspects(archivedNodeId); Set newAspects = new HashSet(5); - Map existingPropertyValues = nodeDaoService.getNodeProperties(archivedNodeId); - Map newPropertyValues = new HashMap(11); + Map existingProperties = nodeDaoService.getNodeProperties(archivedNodeId); + Map newProperties = new HashMap(11); // the node must be a top-level archive node if (!existingAspects.contains(ContentModel.ASPECT_ARCHIVED)) { throw new AlfrescoRuntimeException("The node to restore is not an archive node"); } - ChildAssociationRef originalPrimaryParentAssocRef = (ChildAssociationRef) makeSerializableValue( - dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC), - existingPropertyValues.get(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC)); - PropertyValue originalOwnerProperty = existingPropertyValues.get(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER); + ChildAssociationRef originalPrimaryParentAssocRef = (ChildAssociationRef) existingProperties.get( + ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC); + Serializable originalOwner = existingProperties.get(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER); // remove the archived aspect Set removePropertyQNames = new HashSet(11); removePropertyQNames.add(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC); @@ -1782,10 +1745,10 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl nodeDaoService.removeNodeAspects(archivedNodeId, Collections.singleton(ContentModel.ASPECT_ARCHIVED)); // restore the original ownership - if (originalOwnerProperty != null) + if (originalOwner != null) { newAspects.add(ContentModel.ASPECT_OWNABLE); - newPropertyValues.put(ContentModel.PROP_OWNER, originalOwnerProperty); + newProperties.put(ContentModel.PROP_OWNER, originalOwner); } if (destinationParentNodeRef == null) @@ -2100,27 +2063,19 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl } } - private String extractNameProperty(Map propertyValues) + private String extractNameProperty(Map properties) { - PropertyValue nameValue = propertyValues.get(ContentModel.PROP_NAME); - if (nameValue == null) - { - return null; - } - String name = (String) nameValue.getValue(DataTypeDefinition.TEXT); + Serializable nameValue = properties.get(ContentModel.PROP_NAME); + String name = (String) DefaultTypeConverter.INSTANCE.convert(String.class, nameValue); return name; } private void setChildNameUnique(Pair childAssocPair, Pair childNodePair) { // Get the node's existing name - PropertyValue namePropertyValue = nodeDaoService.getNodeProperty(childNodePair.getFirst(), ContentModel.PROP_NAME); - String nameValue = null; - if (namePropertyValue != null) - { - nameValue = (String) namePropertyValue.getValue(DataTypeDefinition.TEXT); - } - setChildNameUnique(childAssocPair, nameValue, null); + Serializable nameValue = nodeDaoService.getNodeProperty(childNodePair.getFirst(), ContentModel.PROP_NAME); + String name = (String) DefaultTypeConverter.INSTANCE.convert(String.class, nameValue); + setChildNameUnique(childAssocPair, name, null); } /** diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java index 47633e8f1b..6c4d5fa83e 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java @@ -26,6 +26,7 @@ package org.alfresco.repo.node.db; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Locale; @@ -33,11 +34,11 @@ import java.util.Map; import javax.transaction.UserTransaction; +import org.alfresco.i18n.I18NUtil; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.domain.ChildAssoc; import org.alfresco.repo.domain.Node; -import org.alfresco.repo.domain.NodeStatus; import org.alfresco.repo.node.BaseNodeServiceTest; import org.alfresco.repo.node.StoreArchiveMap; import org.alfresco.repo.node.db.NodeDaoService.NodePropertyHandler; @@ -352,6 +353,38 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest mlTextProperty, propertiesDirect.get(BaseNodeServiceTest.PROP_QNAME_ML_TEXT_VALUE)); } + + /** + * Ensure that plain strings going into MLText properties is handled + */ + @SuppressWarnings("unchecked") + public void testStringIntoMLTextProperty() throws Exception + { + String text = "Hello"; + nodeService.setProperty(rootNodeRef, PROP_QNAME_ML_TEXT_VALUE, text); + Serializable mlTextCheck = nodeService.getProperty(rootNodeRef, PROP_QNAME_ML_TEXT_VALUE); + assertTrue("Plain string insertion should be returned as MLText", mlTextCheck instanceof MLText); + Locale defaultLocale = I18NUtil.getLocale(); + MLText mlTextCheck2 = (MLText) mlTextCheck; + String mlTextDefaultCheck = mlTextCheck2.getDefaultValue(); + assertEquals("Default MLText value was not set correctly", text, mlTextDefaultCheck); + + // Reset the property + nodeService.setProperty(rootNodeRef, PROP_QNAME_ML_TEXT_VALUE, null); + Serializable nullValueCheck = nodeService.getProperty(rootNodeRef, PROP_QNAME_ML_TEXT_VALUE); + + // Now, just pass a String in + nodeService.setProperty(rootNodeRef, PROP_QNAME_ML_TEXT_VALUE, text); + // Now update the property with some MLText + MLText mlText = new MLText(); + mlText.addValue(Locale.ENGLISH, "Very good!"); + mlText.addValue(Locale.FRENCH, "Très bon!"); + mlText.addValue(Locale.GERMAN, "Sehr gut!"); + nodeService.setProperty(rootNodeRef, PROP_QNAME_ML_TEXT_VALUE, mlText); + // Get it back and check + mlTextCheck = nodeService.getProperty(rootNodeRef, PROP_QNAME_ML_TEXT_VALUE); + assertEquals("Setting of MLText over String failed.", mlText, mlTextCheck); + } public void testDuplicatePrimaryParentHandling() throws Exception { diff --git a/source/java/org/alfresco/repo/node/db/NodeDaoService.java b/source/java/org/alfresco/repo/node/db/NodeDaoService.java index 64d08c3bd6..a982f8bc62 100644 --- a/source/java/org/alfresco/repo/node/db/NodeDaoService.java +++ b/source/java/org/alfresco/repo/node/db/NodeDaoService.java @@ -32,7 +32,6 @@ import java.util.Set; import org.alfresco.repo.domain.ChildAssoc; import org.alfresco.repo.domain.NodeAssoc; -import org.alfresco.repo.domain.PropertyValue; import org.alfresco.repo.domain.Transaction; import org.alfresco.repo.domain.hibernate.DirtySessionAnnotation; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; @@ -132,22 +131,22 @@ public interface NodeDaoService public void updateNode(Long nodeId, StoreRef storeRef, String uuid, QName nodeTypeQName); @DirtySessionAnnotation(markDirty=false) - public PropertyValue getNodeProperty(Long nodeId, QName propertyQName); + public Serializable getNodeProperty(Long nodeId, QName propertyQName); @DirtySessionAnnotation(markDirty=false) - public Map getNodeProperties(Long nodeId); + public Map getNodeProperties(Long nodeId); @DirtySessionAnnotation(markDirty=true) - public void addNodeProperty(Long nodeId, QName qname, PropertyValue propertyValue); + public void addNodeProperty(Long nodeId, QName qname, Serializable value); @DirtySessionAnnotation(markDirty=true) - public void addNodeProperties(Long nodeId, Map properties); + public void addNodeProperties(Long nodeId, Map properties); @DirtySessionAnnotation(markDirty=true) public void removeNodeProperties(Long nodeId, Set propertyQNames); @DirtySessionAnnotation(markDirty=true) - public void setNodeProperties(Long nodeId, Map properties); + public void setNodeProperties(Long nodeId, Map properties); @DirtySessionAnnotation(markDirty=false) public Set getNodeAspects(Long nodeId); @@ -399,17 +398,6 @@ public interface NodeDaoService @DirtySessionAnnotation(markDirty=true) public void getPropertyValuesByActualType(DataTypeDefinition actualDataTypeDefinition, NodePropertyHandler handler); - /** - * @return Returns the total number of nodes in the ADM repository - */ - @DirtySessionAnnotation(markDirty=false) - public int getNodeCount(); - /** - * @return Returns the total number of nodes in the ADM store - */ - @DirtySessionAnnotation(markDirty=false) - public int getNodeCount(final StoreRef storeRef); - /** * Iterface to handle callbacks when iterating over properties * diff --git a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java index c804bc431a..db13bc6f91 100644 --- a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java @@ -25,34 +25,41 @@ package org.alfresco.repo.node.db.hibernate; import java.io.Serializable; +import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.UnknownHostException; -import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; import java.util.zip.CRC32; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.domain.AuditableProperties; import org.alfresco.repo.domain.ChildAssoc; import org.alfresco.repo.domain.DbAccessControlList; +import org.alfresco.repo.domain.LocaleDAO; import org.alfresco.repo.domain.NamespaceEntity; import org.alfresco.repo.domain.Node; import org.alfresco.repo.domain.NodeAssoc; -import org.alfresco.repo.domain.NodeKey; -import org.alfresco.repo.domain.NodeStatus; +import org.alfresco.repo.domain.NodePropertyValue; +import org.alfresco.repo.domain.PropertyMapKey; import org.alfresco.repo.domain.PropertyValue; import org.alfresco.repo.domain.QNameDAO; import org.alfresco.repo.domain.QNameEntity; import org.alfresco.repo.domain.Server; import org.alfresco.repo.domain.Store; -import org.alfresco.repo.domain.StoreKey; import org.alfresco.repo.domain.Transaction; import org.alfresco.repo.domain.UsageDeltaDAO; import org.alfresco.repo.domain.hibernate.ChildAssocImpl; @@ -61,7 +68,6 @@ import org.alfresco.repo.domain.hibernate.DbAccessControlListImpl; import org.alfresco.repo.domain.hibernate.DirtySessionMethodInterceptor; import org.alfresco.repo.domain.hibernate.NodeAssocImpl; import org.alfresco.repo.domain.hibernate.NodeImpl; -import org.alfresco.repo.domain.hibernate.NodeStatusImpl; import org.alfresco.repo.domain.hibernate.ServerImpl; import org.alfresco.repo.domain.hibernate.StoreImpl; import org.alfresco.repo.domain.hibernate.TransactionImpl; @@ -71,34 +77,39 @@ import org.alfresco.repo.security.permissions.AccessControlListProperties; import org.alfresco.repo.security.permissions.SimpleAccessControlListProperties; import org.alfresco.repo.security.permissions.impl.AclChange; import org.alfresco.repo.security.permissions.impl.AclDaoComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TransactionAwareSingleton; import org.alfresco.repo.transaction.TransactionalDao; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryException; +import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.InvalidTypeException; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.repository.AssociationExistsException; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; +import org.alfresco.service.cmr.repository.EntityRef; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.InvalidStoreRefException; +import org.alfresco.service.cmr.repository.MLText; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreExistsException; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.repository.datatype.TypeConversionException; import org.alfresco.service.cmr.repository.datatype.TypeConverter; import org.alfresco.service.namespace.QName; +import org.alfresco.util.EqualsHelper; import org.alfresco.util.GUID; import org.alfresco.util.Pair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.hibernate.HibernateException; -import org.hibernate.ObjectDeletedException; import org.hibernate.Query; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; import org.hibernate.Session; -import org.hibernate.StaleStateException; -import org.springframework.dao.DataAccessException; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; @@ -110,7 +121,9 @@ import org.springframework.orm.hibernate3.support.HibernateDaoSupport; */ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements NodeDaoService, TransactionalDao { + private static final String QUERY_GET_STORE_BY_ALL = "store.GetStoreByAll"; private static final String QUERY_GET_ALL_STORES = "store.GetAllStores"; + private static final String QUERY_GET_NODE_BY_STORE_ID_AND_UUID = "node.GetNodeByStoreIdAndUuid"; private static final String QUERY_GET_CHILD_NODE_IDS = "node.GetChildNodeIds"; private static final String QUERY_GET_CHILD_ASSOCS_BY_ALL = "node.GetChildAssocsByAll"; private static final String QUERY_GET_CHILD_ASSOC_BY_TYPE_AND_NAME = "node.GetChildAssocByTypeAndName"; @@ -130,10 +143,9 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements private static final String QUERY_GET_NODES_WITH_PROPERTY_VALUES_BY_STRING_AND_STORE = "node.GetNodesWithPropertyValuesByStringAndStore"; private static final String QUERY_GET_NODES_WITH_PROPERTY_VALUES_BY_ACTUAL_TYPE = "node.GetNodesWithPropertyValuesByActualType"; private static final String QUERY_GET_SERVER_BY_IPADDRESS = "server.getServerByIpAddress"; - - private static final String QUERY_GET_NODE_COUNT = "node.GetNodeCount"; - private static final String QUERY_GET_NODE_COUNT_FOR_STORE = "node.GetNodeCountForStore"; + private static final Long NULL_CACHE_VALUE = new Long(-1); + private static Log logger = LogFactory.getLog(HibernateNodeDaoServiceImpl.class); /** Log to trace parent association caching: classname + .ParentAssocsCache */ private static Log loggerParentAssocsCache = LogFactory.getLog(HibernateNodeDaoServiceImpl.class.getName() + ".ParentAssocsCache"); @@ -141,6 +153,10 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements private QNameDAO qnameDAO; private UsageDeltaDAO usageDeltaDAO; private AclDaoComponent aclDaoComponent; + private LocaleDAO localeDAO; + private DictionaryService dictionaryService; + /** A cache mapping StoreRef and NodeRef instances to the entity IDs (primary key) */ + private SimpleCache storeAndNodeIdCache; /** A cache for more performant lookups of the parent associations */ private SimpleCache> parentAssocsCache; private boolean isDebugEnabled = logger.isDebugEnabled(); @@ -216,6 +232,32 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements this.aclDaoComponent = aclDaoComponent; } + /** + * Set the component for creating Locale entities + */ + public void setLocaleDAO(LocaleDAO localeDAO) + { + this.localeDAO = localeDAO; + } + + /** + * Set the component for querying the dictionary model + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * Ste the transaction-aware cache to store Store and Root Node IDs by Store Reference + * + * @param storeAndNodeIdCache the cache + */ + public void setStoreAndNodeIdCache(SimpleCache storeAndNodeIdCache) + { + this.storeAndNodeIdCache = storeAndNodeIdCache; + } + /** * Set the transaction-aware cache to store parent associations by child node id * @@ -398,18 +440,66 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements getSession().flush(); } - private Store getStore(StoreRef storeRef) + /** + * @return Returns the Store entity or null + */ + private Store getStore(final StoreRef storeRef) { - StoreKey storeKey = new StoreKey(storeRef); - Store store = (Store) getHibernateTemplate().get(StoreImpl.class, storeKey); + // Look it up in the cache + Long storeId = storeAndNodeIdCache.get(storeRef); + // Load it + if (storeId != null) + { + // Check for null persistence (previously missed value) + if (storeId.equals(NULL_CACHE_VALUE)) + { + // There is no such value matching + return null; + } + // Don't use the method that throws an exception as the cache might be invalid. + Store store = (Store) getSession().get(StoreImpl.class, storeId); + if (store == null) + { + // It is not available, so we need to go the query route. + // But first remove the cache entry + storeAndNodeIdCache.remove(storeRef); + // Recurse, but this time there is no cache entry + return getStore(storeRef); + } + else + { + return store; + } + } + // Query for it + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_STORE_BY_ALL) + .setString("protocol", storeRef.getProtocol()) + .setString("identifier", storeRef.getIdentifier()); + return query.uniqueResult(); + } + }; + Store store = (Store) getHibernateTemplate().execute(callback); + if (store == null) + { + // Persist the null entry + storeAndNodeIdCache.put(storeRef, NULL_CACHE_VALUE); + } + else + { + storeAndNodeIdCache.put(storeRef, store.getId()); + } // done return store; } private Store getStoreNotNull(StoreRef storeRef) { - StoreKey storeKey = new StoreKey(storeRef); - Store store = (Store) getHibernateTemplate().get(StoreImpl.class, storeKey); + Store store = getStore(storeRef); if (store == null) { throw new InvalidStoreRefException(storeRef); @@ -525,9 +615,10 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements } store = new StoreImpl(); - // set key - store.setKey(new StoreKey(storeRef)); - // persist so that it is present in the hibernate cache + // set key values + store.setProtocol(storeRef.getProtocol()); + store.setIdentifier(storeRef.getIdentifier()); + // The root node may be null exactly because the Store needs an ID before it can be assigned to a node getHibernateTemplate().save(store); // create and assign a root node Node rootNode = newNode( @@ -545,186 +636,236 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements DbAccessControlList acl = aclDaoComponent.getDbAccessControlList(id); rootNode.setAccessControlList(acl); - // done + // Cache the value + storeAndNodeIdCache.put(storeRef, store.getId()); + // Done return new Pair(rootNode.getId(), rootNode.getNodeRef()); } public NodeRef.Status getNodeRefStatus(NodeRef nodeRef) { - NodeStatus nodeStatus = getNodeStatusOrNull(nodeRef); - if (nodeStatus == null) // node never existed + // Get the store + StoreRef storeRef = nodeRef.getStoreRef(); + Store store = getStore(storeRef); + if (store == null) + { + // No such store therefore no such node reference + return null; + } + Node node = getNodeOrNull(store, nodeRef.getId()); + if (node == null) // node never existed { return null; } else { return new NodeRef.Status( - nodeStatus.getTransaction().getChangeTxnId(), - nodeStatus.isDeleted()); + node.getTransaction().getChangeTxnId(), + node.getDeleted()); } } - private NodeStatus getNodeStatusOrNull(NodeRef nodeRef) + private Node getNodeOrNull(final Store store, final String uuid) { - NodeKey nodeKey = new NodeKey(nodeRef); - NodeStatus status = null; - try + NodeRef nodeRef = new NodeRef(store.getStoreRef(), uuid); + // Look it up in the cache + Long nodeId = storeAndNodeIdCache.get(nodeRef); + // Load it + if (nodeId != null) { - status = (NodeStatus) getHibernateTemplate().get(NodeStatusImpl.class, nodeKey); - } - catch (DataAccessException e) - { - if (e.contains(ObjectDeletedException.class)) + // Check for null persistence (previously missed value) + if (nodeId.equals(NULL_CACHE_VALUE)) { - throw new StaleStateException("Node status was deleted: " + nodeKey); + // There is no such value matching + return null; + } + // Don't use the method that throws an exception as the cache might be invalid. + Node node = (Node) getSession().get(NodeImpl.class, nodeId); + if (node == null) + { + // It is not available, so we need to go the query route. + // But first remove the cache entry + storeAndNodeIdCache.remove(nodeRef); + // Recurse, but this time there is no cache entry + return getNodeOrNull(store, uuid); + } + else + { + return node; } - throw e; } - return status; - } - - private void recordNodeUpdate(Node node) - { - NodeRef nodeRef = node.getNodeRef(); - Transaction currentTxn = getCurrentTransaction(); - NodeStatus status = getNodeStatusOrNull(nodeRef); - if (status == null) + // Query for it + HibernateCallback callback = new HibernateCallback() { - NodeKey key = new NodeKey(nodeRef); - // We need to to create a status entry for it - status = new NodeStatusImpl(); - status.setKey(key); - status.setNode(node); - status.setTransaction(currentTxn); - getHibernateTemplate().save(status); + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODE_BY_STORE_ID_AND_UUID) + .setLong("storeId", store.getId()) + .setString("uuid", uuid); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); + return query.uniqueResult(); + } + }; + Node node = (Node) getHibernateTemplate().execute(callback); + // Cache the value + if (node == null) + { + storeAndNodeIdCache.put(nodeRef, NULL_CACHE_VALUE); } else { - status.setNode(node); - status.setTransaction(currentTxn); + storeAndNodeIdCache.put(nodeRef, node.getId()); } + // TODO: Fill cache here + return node; + } + + private void updateNodeStatus(Node node, boolean deleted) + { + Transaction currentTxn = getCurrentTransaction(); + // Update it if required + if (!EqualsHelper.nullSafeEquals(node.getTransaction(), currentTxn)) + { + // Txn has changed + DirtySessionMethodInterceptor.setSessionDirty(); + node.setTransaction(currentTxn); + } + if (node.getDeleted() != deleted) + { + DirtySessionMethodInterceptor.setSessionDirty(); + node.setDeleted(deleted); + } + } + + private static final String UNKOWN_USER = "unkown"; + private String getCurrentUser() + { + String user = AuthenticationUtil.getCurrentUserName(); + return (user == null) ? UNKOWN_USER : user; + } + + private void recordNodeCreate(Node node) + { + updateNodeStatus(node, false); + // Handle cm:auditable + String currentUser = getCurrentUser(); + Date currentDate = new Date(); + AuditableProperties auditableProperties = node.getAuditableProperties(); + auditableProperties.setAuditValues(currentUser, currentDate, true); } - private void recordNodeDelete(NodeRef nodeRef) + private void recordNodeUpdate(Node node) { - Transaction currentTxn = getCurrentTransaction(); - NodeStatus status = getNodeStatusOrNull(nodeRef); - if (status == null) - { - NodeKey key = new NodeKey(nodeRef); - // We need to to create a status entry for it - status = new NodeStatusImpl(); - status.setKey(key); - status.setNode(null); - status.setTransaction(currentTxn); - getHibernateTemplate().save(status); - } - else - { - status.setNode(null); - status.setTransaction(currentTxn); - } + updateNodeStatus(node, false); + // Handle cm:auditable + String currentUser = getCurrentUser(); + Date currentDate = new Date(); + AuditableProperties auditableProperties = node.getAuditableProperties(); + auditableProperties.setAuditValues(currentUser, currentDate, false); + } + + private void recordNodeDelete(Node node) + { + updateNodeStatus(node, true); + // Handle cm:auditable + String currentUser = getCurrentUser(); + Date currentDate = new Date(); + AuditableProperties auditableProperties = node.getAuditableProperties(); + auditableProperties.setAuditValues(currentUser, currentDate, false); } public Pair newNode(StoreRef storeRef, String uuid, QName nodeTypeQName) throws InvalidTypeException { - Store store = (Store) getHibernateTemplate().load(StoreImpl.class, new StoreKey(storeRef)); + Store store = (Store) getStoreNotNull(storeRef); Node newNode = newNode(store, uuid, nodeTypeQName); - return new Pair(newNode.getId(), newNode.getNodeRef()); + Long nodeId = newNode.getId(); + NodeRef nodeRef = newNode.getNodeRef(); + return new Pair(nodeId, nodeRef); } private Node newNode(Store store, String uuid, QName nodeTypeQName) throws InvalidTypeException { - NodeKey key = new NodeKey(store.getKey(), uuid); - - // create (or reuse) the mandatory node status - NodeStatus status = (NodeStatus) getHibernateTemplate().get(NodeStatusImpl.class, key); - if (status != null) - { - // The node existed at some point. - // Although unlikely, it is possible that the node was deleted in this transaction. - // If that is the case, then the session has to be flushed so that the database - // constraints aren't violated as the node creation will write to the database to - // get an ID - if (status.getTransaction().getChangeTxnId().equals(AlfrescoTransactionSupport.getTransactionId())) - { - // flush - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) throws HibernateException, SQLException - { - DirtySessionMethodInterceptor.flushSession(session); - return null; - } - }; - getHibernateTemplate().execute(callback); - } - } - // Get the qname for the node type QNameEntity nodeTypeQNameEntity = qnameDAO.getOrCreateQNameEntity(nodeTypeQName); - // build a concrete node based on a bootstrap type - Node node = new NodeImpl(); - // set other required properties - node.setStore(store); - node.setUuid(uuid); - node.setTypeQName(nodeTypeQNameEntity); - // persist the node - getHibernateTemplate().save(node); - - // Record change ID - recordNodeUpdate(node); + // Get any existing Node. A node with this UUID may have existed before, but must be marked + // deleted; otherwise it will be considered live and valid + Node node = getNodeOrNull(store, uuid); + // If there is already a node attached, then there is a clash + if (node != null) + { + if (!node.getDeleted()) + { + throw new InvalidNodeRefException("Live Node exists: " + node.getNodeRef(), node.getNodeRef()); + } + // Set clean values + node.setTypeQName(nodeTypeQNameEntity); + node.setDeleted(false); + node.setAccessControlList(null); + // Record node change + recordNodeCreate(node); + } + else + { + // There is no existing node, deleted or otherwise. + node = new NodeImpl(); + node.setStore(store); + node.setUuid(uuid); + node.setTypeQName(nodeTypeQNameEntity); + node.setDeleted(false); + node.setAccessControlList(null); + // Record node change + recordNodeCreate(node); + // Persist it + getHibernateTemplate().save(node); + + // Update the cache + storeAndNodeIdCache.put(node.getNodeRef(), node.getId()); + } - // done + // Done return node; } + /** + * This moves the entire node, ensuring that a trail is left behind. It is more + * efficient to move the node and recreate a deleted node in it's wake because of + * the other properties and aspects that need to go with the node. + */ public Pair moveNodeToStore(Long nodeId, StoreRef storeRef) { - Store store = getStoreNotNull(storeRef); Node node = getNodeNotNull(nodeId); - // Only do anything if the store has changed - Store oldStore = node.getStore(); - if (oldStore.getKey().equals(store.getKey())) - { - // No change - return new Pair(node.getId(), node.getNodeRef()); - } - NodeRef oldNodeRef = node.getNodeRef(); + // Update the node + updateNode(nodeId, storeRef, null, null); + NodeRef nodeRef = node.getNodeRef(); - // Set the store - node.setStore(store); - - // Record change ID - recordNodeDelete(oldNodeRef); - recordNodeUpdate(node); - - return new Pair(node.getId(), node.getNodeRef()); + return new Pair(node.getId(), nodeRef); } public Pair getNodePair(NodeRef nodeRef) { - // get it via the node status - NodeStatus status = getNodeStatusOrNull(nodeRef); - if (status == null) + Store store = getStore(nodeRef.getStoreRef()); + if (store == null) { - // no status implies no node + return null; + } + // Get the node: none, deleted or live + Node node = getNodeOrNull(store, nodeRef.getId()); + if (node == null) + { + // The node doesn't exist even as a deleted reference + return null; + } + else if (node.getDeleted()) + { + // The reference exists, but only as a deleted node return null; } else { - // a status may have a node - Node node = status.getNode(); - // The node might be null (a deleted node) - if (node != null) - { - return new Pair(node.getId(), nodeRef); - } - else - { - return null; - } + // The node is live + return new Pair(node.getId(), nodeRef); } } @@ -735,6 +876,11 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements { return null; } + else if (node.getDeleted()) + { + // The node reference exists, but it is officially deleted + return null; + } else { return new Pair(nodeId, node.getNodeRef()); @@ -786,132 +932,277 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements } } - public void updateNode(Long nodeId, StoreRef storeRef, String uuid, QName nodeTypeQName) + public void updateNode(Long nodeId, StoreRef storeRefAfter, String uuidAfter, QName nodeTypeQName) { Node node = getNodeNotNull(nodeId); + Store storeBefore = node.getStore(); + String uuidBefore = node.getUuid(); NodeRef nodeRefBefore = node.getNodeRef(); - if (storeRef != null && storeRef.equals(node.getStore().getStoreRef())) - { - Store store = getStoreNotNull(storeRef); - node.setStore(store); - } - if (uuid != null) - { - node.setUuid(uuid); - } - if (nodeTypeQName != null) - { - QNameEntity nodeTypeQNameEntity = qnameDAO.getOrCreateQNameEntity(nodeTypeQName); - node.setTypeQName(nodeTypeQNameEntity); - } - NodeRef nodeRefAfter = node.getNodeRef(); - // Record change ID - if (nodeRefBefore.equals(nodeRefAfter)) + final Store storeAfter; + if (storeRefAfter == null) { - recordNodeUpdate(node); + storeAfter = storeBefore; + storeRefAfter = storeBefore.getStoreRef(); } else { - recordNodeDelete(nodeRefBefore); + storeAfter = getStoreNotNull(storeRefAfter); + } + if (uuidAfter == null) + { + uuidAfter = uuidBefore; + } + + NodeRef nodeRefAfter = new NodeRef(storeRefAfter, uuidAfter); + if (!nodeRefAfter.equals(nodeRefBefore)) + { + Node conflictingNode = getNodeOrNull(storeAfter, uuidAfter); + if (conflictingNode != null) + { + if (!conflictingNode.getDeleted()) + { + throw new InvalidNodeRefException("Live Node exists: " + node.getNodeRef(), node.getNodeRef()); + } + // It is a deleted node so just remove the conflict + getHibernateTemplate().delete(conflictingNode); + // Flush immediately to ensure that the record is deleted + DirtySessionMethodInterceptor.flushSession(getSession(), true); + // The cache entry will be overwritten so we don't need to do it here + } + + // Change the store + node.setStore(storeAfter); + node.setUuid(uuidAfter); + // We will need to record the change for the new node + recordNodeUpdate(node); + // Flush immediately to ensure that the record changes + DirtySessionMethodInterceptor.flushSession(getSession(), true); + + // We need to create a dummy reference for the node that was just moved away + Node oldNodeDummy = new NodeImpl(); + oldNodeDummy.setStore(storeBefore); + oldNodeDummy.setUuid(uuidBefore); + oldNodeDummy.setTypeQName(node.getTypeQName()); + recordNodeDelete(oldNodeDummy); + // Persist + getHibernateTemplate().save(oldNodeDummy); + + // Update cache entries + NodeRef nodeRef = node.getNodeRef(); + storeAndNodeIdCache.put(nodeRef, node.getId()); + storeAndNodeIdCache.put(nodeRefBefore, oldNodeDummy.getId()); + } + + if (nodeTypeQName != null && !nodeTypeQName.equals(node.getTypeQName().getQName())) + { + QNameEntity nodeTypeQNameEntity = qnameDAO.getOrCreateQNameEntity(nodeTypeQName); + node.setTypeQName(nodeTypeQNameEntity); + // We will need to record the change recordNodeUpdate(node); } } - public PropertyValue getNodeProperty(Long nodeId, QName propertyQName) + public Serializable getNodeProperty(Long nodeId, QName propertyQName) { + Node node = getNodeNotNull(nodeId); + + // Handle cm:auditable + if (AuditableProperties.isAuditableProperty(propertyQName)) + { + AuditableProperties auditableProperties = node.getAuditableProperties(); + return auditableProperties.getAuditableProperty(propertyQName); + } + QNameEntity propertyQNameEntity = qnameDAO.getQNameEntity(propertyQName); if (propertyQNameEntity == null) { return null; } - Node node = getNodeNotNull(nodeId); - Map nodeProperties = node.getProperties(); - return nodeProperties.get(propertyQNameEntity.getId()); + Map nodeProperties = node.getProperties(); + Serializable propertyValue = HibernateNodeDaoServiceImpl.getPublicProperty( + nodeProperties, + propertyQName, + qnameDAO, localeDAO, dictionaryService); + return propertyValue; } - public Map getNodeProperties(Long nodeId) + public Map getNodeProperties(Long nodeId) { Node node = getNodeNotNull(nodeId); - Map nodeProperties = node.getProperties(); + Map nodeProperties = node.getProperties(); // Convert the QName IDs - Map converted = new HashMap(nodeProperties.size(), 1.0F); - for (Map.Entry entry : nodeProperties.entrySet()) - { - Long qnameEntityId = entry.getKey(); - QName qname = qnameDAO.getQName(qnameEntityId); - converted.put(qname, entry.getValue()); - } + Map converted = HibernateNodeDaoServiceImpl.convertToPublicProperties( + nodeProperties, + qnameDAO, + localeDAO, + dictionaryService); - // Make immutable + // Handle cm:auditable + AuditableProperties auditableProperties = node.getAuditableProperties(); + converted.putAll(auditableProperties.getAuditableProperties()); + + // Done return converted; } - public void addNodeProperty(Long nodeId, QName qname, PropertyValue propertyValue) + private void addNodePropertyImpl(Node node, QName qname, Serializable value, Long localeId) + { + // Handle cm:auditable + if (AuditableProperties.isAuditableProperty(qname)) + { + // This is never set manually + return; + } + + PropertyDefinition propertyDef = dictionaryService.getProperty(qname); + Long qnameId = qnameDAO.getOrCreateQNameEntity(qname).getId(); + + Map persistableProperties = new HashMap(3); + + HibernateNodeDaoServiceImpl.addValueToPersistedProperties( + persistableProperties, + propertyDef, + (short)-1, + qnameId, + localeId, + value, + localeDAO); + + Map nodeProperties = node.getProperties(); + + Iterator oldPropertyKeysIterator = nodeProperties.keySet().iterator(); + while (oldPropertyKeysIterator.hasNext()) + { + PropertyMapKey oldPropertyKey = oldPropertyKeysIterator.next(); + // If the qname doesn't match, then ignore + if (!oldPropertyKey.getQnameId().equals(qnameId)) + { + continue; + } + // The qname matches, but is the key present in the new values + if (persistableProperties.containsKey(oldPropertyKey)) + { + // The key is present in both maps so it'll be updated + continue; + } + // Remove the entry from the node's properties + oldPropertyKeysIterator.remove(); + } + + // Now add all the new properties. The will overwrite and/or add values. + nodeProperties.putAll(persistableProperties); + } + + public void addNodeProperty(Long nodeId, QName qname, Serializable propertyValue) { Node node = getNodeNotNull(nodeId); - QNameEntity qnameEntity = qnameDAO.getOrCreateQNameEntity(qname); - Map nodeProperties = node.getProperties(); - nodeProperties.put(qnameEntity.getId(), propertyValue); + Long localeId = localeDAO.getOrCreateDefaultLocalePair().getFirst(); + addNodePropertyImpl(node, qname, propertyValue, localeId); // Record change ID recordNodeUpdate(node); } - - public void addNodeProperties(Long nodeId, Map properties) + + @SuppressWarnings("unchecked") + public void addNodeProperties(Long nodeId, Map properties) { Node node = getNodeNotNull(nodeId); - Map nodeProperties = node.getProperties(); - for (Map.Entry entry : properties.entrySet()) + Long localeId = localeDAO.getOrCreateDefaultLocalePair().getFirst(); + for (Map.Entry entry : properties.entrySet()) { - QNameEntity qnameEntity = qnameDAO.getOrCreateQNameEntity(entry.getKey()); - nodeProperties.put(qnameEntity.getId(), entry.getValue()); + QName qname = entry.getKey(); + Serializable value = entry.getValue(); + addNodePropertyImpl(node, qname, value, localeId); } // Record change ID recordNodeUpdate(node); } - public void removeNodeProperties(Long nodeId, Set propertyQNames) + public void setNodeProperties(Long nodeId, Map propertiesIncl) { Node node = getNodeNotNull(nodeId); - Map nodeProperties = node.getProperties(); - - for (QName qname : propertyQNames) + + // Handle cm:auditable. These need to be removed from the properties. + Map properties = new HashMap(propertiesIncl.size()); + for (Map.Entry entry : propertiesIncl.entrySet()) { - QNameEntity qnameEntity = qnameDAO.getOrCreateQNameEntity(qname); - nodeProperties.remove(qnameEntity.getId()); + QName propertyQName = entry.getKey(); + Serializable value = entry.getValue(); + if (AuditableProperties.isAuditableProperty(propertyQName)) + { + continue; + } + // The value was NOT an auditable value + properties.put(propertyQName, value); + } + + // Convert + Map persistableProperties = HibernateNodeDaoServiceImpl.convertToPersistentProperties( + properties, + qnameDAO, + localeDAO, + dictionaryService); + + // Get the persistent map attached to the node + Map nodeProperties = node.getProperties(); + + // In order to make as few changes as possible, we need to update existing properties wherever possible. + // This means keeping track of map keys that weren't updated + Set toRemove = new HashSet(nodeProperties.keySet()); + + // Loop over the converted values and update the persisted node properties map + for (Map.Entry entry : persistableProperties.entrySet()) + { + PropertyMapKey key = entry.getKey(); + toRemove.remove(key); + // Add the value to the node's map + nodeProperties.put(key, entry.getValue()); + } + + // Now remove all untouched keys + for (PropertyMapKey key : toRemove) + { + nodeProperties.remove(key); } // Record change ID recordNodeUpdate(node); } - public void setNodeProperties(Long nodeId, Map properties) + public void removeNodeProperties(Long nodeId, Set propertyQNamesIncl) { Node node = getNodeNotNull(nodeId); - Map nodeProperties = node.getProperties(); - nodeProperties.clear(); - - Set toRemove = new HashSet(nodeProperties.keySet()); - - for (Map.Entry entry : properties.entrySet()) + // Handle cm:auditable. These need to be removed from the list. + Set propertyQNames = new HashSet(propertyQNamesIncl.size()); + for (QName propertyQName : propertyQNamesIncl) { - QNameEntity qnameEntity = qnameDAO.getOrCreateQNameEntity(entry.getKey()); - Long qnameEntityId = qnameEntity.getId(); - nodeProperties.put(qnameEntityId, entry.getValue()); - // It's live - toRemove.remove(qnameEntityId); + if (AuditableProperties.isAuditableProperty(propertyQName)) + { + continue; + } + propertyQNames.add(propertyQName); } - // Remove all entries that weren't in the updated set - for (Long qnameEntityIdToRemove : toRemove) + Map nodeProperties = node.getProperties(); + + Set propertyQNameIds = qnameDAO.convertQNamesToIds(propertyQNames, true); + + // Loop over the current properties and remove any that have the same qname. + // Make a copy as we will modify the original map. + Set entrySet = new HashSet(nodeProperties.keySet()); + for (PropertyMapKey propertyMapKey : entrySet) { - nodeProperties.remove(qnameEntityIdToRemove); + Long propertyQNameId = propertyMapKey.getQnameId(); + if (propertyQNameIds.contains(propertyQNameId)) + { + nodeProperties.remove(propertyMapKey); + } } // Record change ID @@ -933,6 +1224,9 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements // Add sys:referenceable nodeAspectQNames.add(ContentModel.ASPECT_REFERENCEABLE); + // Add cm:auditable + nodeAspectQNames.add(ContentModel.ASPECT_AUDITABLE); + // Make immutable return nodeAspectQNames; } @@ -941,12 +1235,11 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements { Node node = getNodeNotNull(nodeId); + aspectQNames = new HashSet(aspectQNames); // Remove sys:referenceable - if (aspectQNames.contains(ContentModel.ASPECT_REFERENCEABLE)) - { - aspectQNames = new HashSet(aspectQNames); - aspectQNames.remove(ContentModel.ASPECT_REFERENCEABLE); - } + aspectQNames.remove(ContentModel.ASPECT_REFERENCEABLE); + // Handle cm:auditable + aspectQNames.remove(ContentModel.ASPECT_AUDITABLE); Set nodeAspects = node.getAspects(); @@ -964,12 +1257,11 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements { Node node = getNodeNotNull(nodeId); + aspectQNames = new HashSet(aspectQNames); // Remove sys:referenceable - if (aspectQNames.contains(ContentModel.ASPECT_REFERENCEABLE)) - { - aspectQNames = new HashSet(aspectQNames); - aspectQNames.remove(ContentModel.ASPECT_REFERENCEABLE); - } + aspectQNames.remove(ContentModel.ASPECT_REFERENCEABLE); + // Handle cm:auditable + aspectQNames.remove(ContentModel.ASPECT_AUDITABLE); Set nodeAspects = node.getAspects(); @@ -992,6 +1284,11 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements { return true; } + // Shortcut cm:auditable + else if (aspectQName.equals(ContentModel.ASPECT_AUDITABLE)) + { + return true; + } QNameEntity aspectQNameEntity = qnameDAO.getQNameEntity(aspectQName); if (aspectQNameEntity == null) @@ -1013,7 +1310,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements deleteNodeInternal(node, false, deletedChildAssocIds); // Record change ID - recordNodeDelete(node.getNodeRef()); + recordNodeDelete(node); } private static final String QUERY_DELETE_PARENT_ASSOCS = "node.DeleteParentAssocs"; @@ -1021,6 +1318,10 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements private static final String QUERY_DELETE_NODE_ASSOCS = "node.DeleteNodeAssocs"; /** + * Does a full cleanup of the node if the deleted flag is off. If + * the node is marked as deleted then the cleanup is assumed to be + * unnecessary and the node entry itself is cleaned up. + * * @param node the node to delete * @param cascade true to cascade delete * @param deletedChildAssocIds previously deleted child associations @@ -1109,8 +1410,10 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements // Delete deltas usageDeltaDAO.deleteDeltas(nodeId); - // finally delete the node - getHibernateTemplate().delete(node); +// // finally delete the node +// getHibernateTemplate().delete(node); + node.setDeleted(true); + // Remove node from cache parentAssocsCache.remove(nodeId); if (isDebugParentAssocCacheEnabled) @@ -1125,7 +1428,14 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements private long getCrc(String str) { CRC32 crc = new CRC32(); - crc.update(str.getBytes()); + try + { + crc.update(str.getBytes("UTF-8")); // https://issues.alfresco.com/jira/browse/ALFCOM-1335 + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException("UTF-8 encoding is not supported"); + } return crc.getValue(); } @@ -1349,7 +1659,6 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements } else { - recordNodeDelete(oldChildNodeRef); recordNodeUpdate(newChildNode); } @@ -2332,6 +2641,11 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements return; } final Long propQNameEntityId = propQNameEntity.getId(); + final Long defaultLocaleEntityId = localeDAO.getDefaultLocalePair().getFirst(); + final PropertyMapKey propKey = new PropertyMapKey(); + propKey.setQnameId(propQNameEntityId); + propKey.setLocaleId(defaultLocaleEntityId); + propKey.setListIndex((short)0); // Run the query HibernateCallback callback = new HibernateCallback() { @@ -2341,7 +2655,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODES_WITH_PROPERTY_VALUES_BY_STRING_AND_STORE) .setString("protocol", storeRef.getProtocol()) .setString("identifier", storeRef.getIdentifier()) - .setLong("propQNameId", propQNameEntityId) + .setParameter("propKey", propKey) .setString("propStringValue", value) ; DirtySessionMethodInterceptor.setQueryFlushMode(session, query); @@ -2387,16 +2701,17 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements { Node node = (Node) results.get()[0]; // loop through all the node properties - Map properties = node.getProperties(); - for (Map.Entry entry : properties.entrySet()) + Map properties = node.getProperties(); + for (Map.Entry entry : properties.entrySet()) { - Long propertyQNameId = entry.getKey(); - PropertyValue propertyValue = entry.getValue(); + PropertyMapKey propertyKey = entry.getKey(); + NodePropertyValue propertyValue = entry.getValue(); // ignore nulls if (propertyValue == null) { continue; } + Long propertyQNameId = propertyKey.getQnameId(); // Get the actual value(s) as a collection Collection values = propertyValue.getCollection(DataTypeDefinition.ANY); // attempt to convert instance in the collection @@ -2435,50 +2750,6 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements } } - /** - * {@inheritDoc} - */ - public int getNodeCount() - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session.getNamedQuery(QUERY_GET_NODE_COUNT); - query.setMaxResults(1) - .setReadOnly(true); - DirtySessionMethodInterceptor.setQueryFlushMode(session, query); - return query.uniqueResult(); - } - }; - Long count = (Long) getHibernateTemplate().execute(callback); - // done - return count.intValue(); - } - - /** - * {@inheritDoc} - */ - public int getNodeCount(final StoreRef storeRef) - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session.getNamedQuery(QUERY_GET_NODE_COUNT_FOR_STORE); - query.setString("protocol", storeRef.getProtocol()) - .setString("identifier", storeRef.getIdentifier()) - .setMaxResults(1) - .setReadOnly(true); - DirtySessionMethodInterceptor.setQueryFlushMode(session, query); - return query.uniqueResult(); - } - }; - Long count = (Long) getHibernateTemplate().execute(callback); - // done - return count.intValue(); - } - /* * Queries for transactions */ @@ -2722,12 +2993,12 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements return query.list(); } }; - List results = (List) getHibernateTemplate().execute(callback); + List results = (List) getHibernateTemplate().execute(callback); // transform into a simpler form List nodeRefs = new ArrayList(results.size()); - for (NodeStatus nodeStatus : results) + for (Node node : results) { - NodeRef nodeRef = new NodeRef(storeRef, nodeStatus.getKey().getGuid()); + NodeRef nodeRef = node.getNodeRef(); nodeRefs.add(nodeRef); } // done @@ -2748,18 +3019,575 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements return query.list(); } }; - List results = (List) getHibernateTemplate().execute(callback); + List results = (List) getHibernateTemplate().execute(callback); // transform into a simpler form List nodeRefs = new ArrayList(results.size()); - for (NodeStatus nodeStatus : results) + for (Node node : results) { - NodeRef nodeRef = new NodeRef( - nodeStatus.getKey().getProtocol(), - nodeStatus.getKey().getIdentifier(), - nodeStatus.getKey().getGuid()); + NodeRef nodeRef = node.getNodeRef(); nodeRefs.add(nodeRef); } // done return nodeRefs; } + + //============ PROPERTY HELPER METHODS =================// + + public static Map convertToPersistentProperties( + Map in, + QNameDAO qnameDAO, + LocaleDAO localeDAO, + DictionaryService dictionaryService) + { + Map propertyMap = new HashMap(in.size() + 5); + for (Map.Entry entry : in.entrySet()) + { + Serializable value = entry.getValue(); + // Get the qname ID + QName propertyQName = entry.getKey(); + Long propertyQNameId = qnameDAO.getOrCreateQNameEntity(propertyQName).getId(); + // Get the locale ID + Long propertylocaleId = localeDAO.getOrCreateDefaultLocalePair().getFirst(); + // Get the property definition, if available + PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); + // Add it to the map + HibernateNodeDaoServiceImpl.addValueToPersistedProperties( + propertyMap, + propertyDef, + HibernateNodeDaoServiceImpl.IDX_NO_COLLECTION, + propertyQNameId, + propertylocaleId, + value, + localeDAO); + } + // Done + return propertyMap; + } + + /** + * The collection index used to indicate that the value is not part of a collection. + * All values from zero up are used for real collection indexes. + */ + private static final short IDX_NO_COLLECTION = -1; + + /** + * A method that adds properties to the given map. It copes with collections. + * + * @param propertyDef the property definition (null is allowed) + * @param collectionIndex the index of the property in the collection or -1 if + * we are not yet processing a collection + */ + private static void addValueToPersistedProperties( + Map propertyMap, + PropertyDefinition propertyDef, + short collectionIndex, + Long propertyQNameId, + Long propertyLocaleId, + Serializable value, + LocaleDAO localeDAO) + { + if (value == null) + { + // The property is null. Null is null and cannot be massaged any other way. + NodePropertyValue npValue = HibernateNodeDaoServiceImpl.makeNodePropertyValue(propertyDef, null); + PropertyMapKey npKey = new PropertyMapKey(); + npKey.setListIndex(collectionIndex); + npKey.setQnameId(propertyQNameId); + npKey.setLocaleId(propertyLocaleId); + // Add it to the map + propertyMap.put(npKey, npValue); + // Done + return; + } + + // Get or spoof the property datatype + QName propertyTypeQName; + if (propertyDef == null) // property not recognised + { + // allow it for now - persisting excess properties can be useful sometimes + propertyTypeQName = DataTypeDefinition.ANY; + } + else + { + propertyTypeQName = propertyDef.getDataType().getName(); + } + + // A property may appear to be multi-valued if the model definition is loose and + // an unexploded collection is passed in. Otherwise, use the model-defined behaviour + // strictly. + boolean isMultiValued; + if (propertyTypeQName.equals(DataTypeDefinition.ANY)) + { + // It is multi-valued if required (we are not in a collection and the property is a new collection) + isMultiValued = (value != null) && (value instanceof Collection) && (collectionIndex == IDX_NO_COLLECTION); + } + else + { + isMultiValued = propertyDef.isMultiValued(); + } + + // Handle different scenarios. + // - Do we need to explode a collection? + // - Does the property allow collections? + if (collectionIndex == IDX_NO_COLLECTION && isMultiValued && !(value instanceof Collection)) + { + // We are not (yet) processing a collection but the property should be part of a collection + HibernateNodeDaoServiceImpl.addValueToPersistedProperties( + propertyMap, + propertyDef, + (short) 0, + propertyQNameId, + propertyLocaleId, + value, + localeDAO); + } + else if (collectionIndex == IDX_NO_COLLECTION && value instanceof Collection) + { + // We are not (yet) processing a collection and the property is a collection i.e. needs exploding + // Check that multi-valued properties are supported if the property is a collection + if (!isMultiValued) + { + throw new DictionaryException( + "A single-valued property of this type may not be a collection: \n" + + " Property: " + propertyDef + "\n" + + " Type: " + propertyTypeQName + "\n" + + " Value: " + value); + } + // We have an allowable collection. + @SuppressWarnings("unchecked") + Collection collectionValues = (Collection) value; + // Persist empty collections directly. This is handled by the NodePropertyValue. + if (collectionValues.size() == 0) + { + NodePropertyValue npValue = HibernateNodeDaoServiceImpl.makeNodePropertyValue( + null, + (Serializable) collectionValues); + PropertyMapKey npKey = new PropertyMapKey(); + npKey.setListIndex(HibernateNodeDaoServiceImpl.IDX_NO_COLLECTION); + npKey.setQnameId(propertyQNameId); + npKey.setLocaleId(propertyLocaleId); + // Add it to the map + propertyMap.put(npKey, npValue); + } + // Break it up and recurse to persist the values. + collectionIndex = -1; + for (Object collectionValueObj : collectionValues) + { + collectionIndex++; + if (collectionValueObj != null && !(collectionValueObj instanceof Serializable)) + { + throw new IllegalArgumentException( + "Node properties must be fully serializable, " + + "including values contained in collections. \n" + + " Property: " + propertyDef + "\n" + + " Index: " + collectionIndex + "\n" + + " Value: " + collectionValueObj); + } + Serializable collectionValue = (Serializable) collectionValueObj; + try + { + HibernateNodeDaoServiceImpl.addValueToPersistedProperties( + propertyMap, + propertyDef, + collectionIndex, + propertyQNameId, + propertyLocaleId, + collectionValue, + localeDAO); + } + catch (Throwable e) + { + throw new AlfrescoRuntimeException( + "Failed to persist collection entry: \n" + + " Property: " + propertyDef + "\n" + + " Index: " + collectionIndex + "\n" + + " Value: " + collectionValue, + e); + } + } + } + else + { + // We are either processing collection elements OR the property is not a collection + // Collections of collections are only supported by type d:any + if (value instanceof Collection && !propertyTypeQName.equals(DataTypeDefinition.ANY)) + { + throw new DictionaryException( + "Collections of collections (Serializable) are only supported by type 'd:any': \n" + + " Property: " + propertyDef + "\n" + + " Type: " + propertyTypeQName + "\n" + + " Value: " + value); + } + // Handle MLText + if (value instanceof MLText) + { + // This needs to be split up into individual strings + MLText mlTextValue = (MLText) value; + for (Map.Entry mlTextEntry : mlTextValue.entrySet()) + { + Locale mlTextLocale = mlTextEntry.getKey(); + String mlTextStr = mlTextEntry.getValue(); + // Get the Locale ID for the text + Long mlTextLocaleId = localeDAO.getOrCreateLocalePair(mlTextLocale).getFirst(); + // This is persisted against the current locale, but as a d:text instance + NodePropertyValue npValue = new NodePropertyValue(DataTypeDefinition.TEXT, mlTextStr); + PropertyMapKey npKey = new PropertyMapKey(); + npKey.setListIndex(collectionIndex); + npKey.setQnameId(propertyQNameId); + npKey.setLocaleId(mlTextLocaleId); + // Add it to the map + propertyMap.put(npKey, npValue); + } + } + else + { + NodePropertyValue npValue = HibernateNodeDaoServiceImpl.makeNodePropertyValue(propertyDef, value); + PropertyMapKey npKey = new PropertyMapKey(); + npKey.setListIndex(collectionIndex); + npKey.setQnameId(propertyQNameId); + npKey.setLocaleId(propertyLocaleId); + // Add it to the map + propertyMap.put(npKey, npValue); + } + } + } + + /** + * Helper method to convert the Serializable value into a full, persistable {@link NodePropertyValue}. + *

+ * Where the property definition is null, the value will take on the + * {@link DataTypeDefinition#ANY generic ANY} value. + *

+ * Collections are NOT supported. These must be split up by the calling code before + * calling this method. Map instances are supported as plain serializable instances. + * + * @param propertyDef the property dictionary definition, may be null + * @param value the value, which will be converted according to the definition - may be null + * @return Returns the persistable property value + */ + private static NodePropertyValue makeNodePropertyValue(PropertyDefinition propertyDef, Serializable value) + { + // get property attributes + final QName propertyTypeQName; + if (propertyDef == null) // property not recognised + { + // allow it for now - persisting excess properties can be useful sometimes + propertyTypeQName = DataTypeDefinition.ANY; + } + else + { + propertyTypeQName = propertyDef.getDataType().getName(); + } + try + { + NodePropertyValue propertyValue = new NodePropertyValue(propertyTypeQName, value); + // done + return propertyValue; + } + catch (TypeConversionException e) + { + throw new TypeConversionException( + "The property value is not compatible with the type defined for the property: \n" + + " property: " + (propertyDef == null ? "unknown" : propertyDef) + "\n" + + " value: " + value + "\n" + + " value type: " + value.getClass(), + e); + } + } + + public static Serializable getPublicProperty( + Map propertyValues, + QName propertyQName, + QNameDAO qnameDAO, + LocaleDAO localeDAO, + DictionaryService dictionaryService) + { + // Get the qname ID + QNameEntity qnameEntity = qnameDAO.getQNameEntity(propertyQName); + if (qnameEntity == null) + { + // There is no persisted property with that QName, so we can't match anything + return null; + } + Long qnameId = qnameEntity.getId(); + // Now loop over the properties and extract those with the given qname ID + SortedMap scratch = new TreeMap(); + for (Map.Entry entry : propertyValues.entrySet()) + { + PropertyMapKey propertyKey = entry.getKey(); + if (propertyKey.getQnameId().equals(qnameId)) + { + scratch.put(propertyKey, entry.getValue()); + } + } + // If we found anything, then collapse the properties to a Serializable + if (scratch.size() > 0) + { + PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); + Serializable collapsedValue = HibernateNodeDaoServiceImpl.collapsePropertiesWithSameQName( + propertyDef, + scratch, + localeDAO); + return collapsedValue; + } + else + { + return null; + } + } + + public static Map convertToPublicProperties( + Map propertyValues, + QNameDAO qnameDAO, + LocaleDAO localeDAO, + DictionaryService dictionaryService) + { + Map propertyMap = new HashMap(propertyValues.size(), 1.0F); + // Shortcut + if (propertyValues.size() == 0) + { + return propertyMap; + } + // We need to process the properties in order + SortedMap sortedPropertyValues = new TreeMap(propertyValues); + // A working map. Ordering is important. + SortedMap scratch = new TreeMap(); + // Iterate (sorted) over the map entries and extract values with the same qname + Long currentQNameId = Long.MIN_VALUE; + Iterator> iterator = sortedPropertyValues.entrySet().iterator(); + while (true) + { + Long nextQNameId = null; + PropertyMapKey nextPropertyKey = null; + NodePropertyValue nextPropertyValue = null; + // Record the next entry's values + if (iterator.hasNext()) + { + Map.Entry entry = iterator.next(); + nextPropertyKey = entry.getKey(); + nextPropertyValue = entry.getValue(); + nextQNameId = nextPropertyKey.getQnameId(); + } + // If the QName is going to change, and we have some entries to process, then process them. + if (scratch.size() > 0 && (nextQNameId == null || !nextQNameId.equals(currentQNameId))) + { + QName currentQName = qnameDAO.getQName(currentQNameId); + PropertyDefinition currentPropertyDef = dictionaryService.getProperty(currentQName); + // We have added something to the scratch properties but the qname has just changed + Serializable collapsedValue = null; + // We can shortcut if there is only one value + if (scratch.size() == 1) + { + // There is no need to collapse list indexes + collapsedValue = HibernateNodeDaoServiceImpl.collapsePropertiesWithSameQNameAndListIndex( + currentPropertyDef, + scratch, + localeDAO); + } + else + { + // There is more than one value so the list indexes need to be collapsed + collapsedValue = HibernateNodeDaoServiceImpl.collapsePropertiesWithSameQName( + currentPropertyDef, + scratch, + localeDAO); + } + // If the property is multi-valued then the output property must be a collection + if (currentPropertyDef != null && currentPropertyDef.isMultiValued()) + { + if (collapsedValue != null && !(collapsedValue instanceof Collection)) + { + collapsedValue = (Serializable) Collections.singletonList(collapsedValue); + } + } + // Store the value + propertyMap.put(currentQName, collapsedValue); + // Reset + scratch.clear(); + } + if (nextQNameId != null) + { + // Add to the current entries + scratch.put(nextPropertyKey, nextPropertyValue); + currentQNameId = nextQNameId; + } + else + { + // There is no next value to process + break; + } + } + // Done + return propertyMap; + } + + private static Serializable collapsePropertiesWithSameQName( + PropertyDefinition propertyDef, + SortedMap sortedPropertyValues, + LocaleDAO localeDAO) + { + Serializable result = null; + // A working map. Ordering is not important for this map. + Map scratch = new HashMap(3); + // Iterate (sorted) over the map entries and extract values with the same list index + Short currentListIndex = Short.MIN_VALUE; + Iterator> iterator = sortedPropertyValues.entrySet().iterator(); + while (true) + { + Short nextListIndex = null; + PropertyMapKey nextPropertyKey = null; + NodePropertyValue nextPropertyValue = null; + // Record the next entry's values + if (iterator.hasNext()) + { + Map.Entry entry = iterator.next(); + nextPropertyKey = entry.getKey(); + nextPropertyValue = entry.getValue(); + nextListIndex = nextPropertyKey.getListIndex(); + } + // If the list index is going to change, and we have some entries to process, then process them. + if (scratch.size() > 0 && (nextListIndex == null || !nextListIndex.equals(currentListIndex))) + { + // We have added something to the scratch properties but the index has just changed + Serializable collapsedValue = HibernateNodeDaoServiceImpl.collapsePropertiesWithSameQNameAndListIndex( + propertyDef, + scratch, + localeDAO); + // Store. If there is a value already, then we must build a collection. + if (result == null) + { + result = collapsedValue; + } + else if (result instanceof Collection) + { + @SuppressWarnings("unchecked") + Collection collectionResult = (Collection) result; + collectionResult.add(collapsedValue); + } + else + { + Collection collectionResult = new ArrayList(20); + collectionResult.add(result); // Add the first result + collectionResult.add(collapsedValue); // Add the new value + result = (Serializable) collectionResult; + } + // Reset + scratch.clear(); + } + if (nextListIndex != null) + { + // Add to the current entries + scratch.put(nextPropertyKey, nextPropertyValue); + currentListIndex = nextListIndex; + } + else + { + // There is no next value to process + break; + } + } + // Make sure that multi-valued properties are returned as a collection + if (propertyDef != null && propertyDef.isMultiValued() && result != null && !(result instanceof Collection)) + { + result = (Serializable) Collections.singletonList(result); + } + // Done + return result; + } + + /** + * At this level, the properties have the same qname and list index. They can only be separated + * by locale. Typically, MLText will fall into this category as only. + *

+ * If there are multiple values then they can only be separated by locale. If they are separated + * by locale, then they have to be text-based. This means that the only way to store them is via + * MLText. Any other multi-locale properties cannot be deserialized. + */ + private static Serializable collapsePropertiesWithSameQNameAndListIndex( + PropertyDefinition propertyDef, + Map propertyValues, + LocaleDAO localeDAO) + { + int propertyValuesSize = propertyValues.size(); + Serializable value = null; + if (propertyValuesSize == 0) + { + // Nothing to do + } + for (Map.Entry entry : propertyValues.entrySet()) + { + PropertyMapKey propertyKey = entry.getKey(); + NodePropertyValue propertyValue = entry.getValue(); + + if (propertyValuesSize == 1 && + (propertyDef == null || !propertyDef.getDataType().getName().equals(DataTypeDefinition.MLTEXT))) + { + // This is the only value and it is NOT to be converted to MLText + value = HibernateNodeDaoServiceImpl.makeSerializableValue(propertyDef, propertyValue); + } + else + { + // There are multiple values, so add them to MLText + MLText mltext = (value == null) ? new MLText() : (MLText) value; + try + { + String mlString = (String) propertyValue.getValue(DataTypeDefinition.TEXT); + // Get the locale + Long localeId = propertyKey.getLocaleId(); + Locale locale = localeDAO.getLocalePair(localeId).getSecond(); + // Add to the MLText object + mltext.addValue(locale, mlString); + } + catch (TypeConversionException e) + { + // Ignore + logger.warn("Unable to add property value to MLText instance: " + propertyValue); + } + value = mltext; + } + } + // Done + return value; + } + + /** + * Extracts the externally-visible property from the persistable value. + * + * @param propertyDef the model property definition - may be null + * @param propertyValue the persisted property + * @return Returns the value of the property in the format dictated by the property + * definition, or null if the property value is null + */ + private static Serializable makeSerializableValue(PropertyDefinition propertyDef, NodePropertyValue propertyValue) + { + if (propertyValue == null) + { + return null; + } + // get property attributes + final QName propertyTypeQName; + if (propertyDef == null) + { + // allow this for now + propertyTypeQName = DataTypeDefinition.ANY; + } + else + { + propertyTypeQName = propertyDef.getDataType().getName(); + } + try + { + Serializable value = propertyValue.getValue(propertyTypeQName); + // done + return value; + } + catch (TypeConversionException e) + { + throw new TypeConversionException( + "The property value is not compatible with the type defined for the property: \n" + + " property: " + (propertyDef == null ? "unknown" : propertyDef) + "\n" + + " property value: " + propertyValue, + e); + } + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/node/integrity/PropertiesIntegrityEvent.java b/source/java/org/alfresco/repo/node/integrity/PropertiesIntegrityEvent.java index d5761cf6c0..33dec6cd51 100644 --- a/source/java/org/alfresco/repo/node/integrity/PropertiesIntegrityEvent.java +++ b/source/java/org/alfresco/repo/node/integrity/PropertiesIntegrityEvent.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.alfresco.model.ContentModel; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.Constraint; import org.alfresco.service.cmr.dictionary.ConstraintDefinition; @@ -102,6 +103,17 @@ public class PropertiesIntegrityEvent extends AbstractIntegrityEvent Set aspectTypeQNames = nodeService.getAspects(nodeRef); for (QName aspectTypeQName : aspectTypeQNames) { + // Shortcut sys:referencable + if (aspectTypeQName.equals(ContentModel.ASPECT_REFERENCEABLE)) + { + continue; + } + // Shortcut cm:auditable + if (aspectTypeQName.equals(ContentModel.ASPECT_AUDITABLE)) + { + continue; + } + // get property definitions for the aspect AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName); propertyDefs = aspectDef.getProperties().values(); diff --git a/source/java/org/alfresco/repo/remote/LoaderRemoteServer.java b/source/java/org/alfresco/repo/remote/LoaderRemoteServer.java index b2389d843b..39d6f951b5 100644 --- a/source/java/org/alfresco/repo/remote/LoaderRemoteServer.java +++ b/source/java/org/alfresco/repo/remote/LoaderRemoteServer.java @@ -217,24 +217,7 @@ public class LoaderRemoteServer implements LoaderRemote */ public int getNodeCount(String ticket) { - Authentication authentication = AuthenticationUtil.getCurrentAuthentication(); - try - { - authenticationService.validate(ticket); - // Make the call - RetryingTransactionCallback callback = new RetryingTransactionCallback() - { - public Integer execute() throws Throwable - { - return nodeDaoService.getNodeCount(); - } - }; - return retryingTransactionHelper.doInTransaction(callback, false, true); - } - finally - { - AuthenticationUtil.setCurrentAuthentication(authentication); - } + throw new UnsupportedOperationException("getNodeCount cannot be accurately determined at low cost."); } /** @@ -242,24 +225,7 @@ public class LoaderRemoteServer implements LoaderRemote */ public int getNodeCount(String ticket, final StoreRef storeRef) { - Authentication authentication = AuthenticationUtil.getCurrentAuthentication(); - try - { - authenticationService.validate(ticket); - // Make the call - RetryingTransactionCallback callback = new RetryingTransactionCallback() - { - public Integer execute() throws Throwable - { - return nodeDaoService.getNodeCount(storeRef); - } - }; - return retryingTransactionHelper.doInTransaction(callback, false, true); - } - finally - { - AuthenticationUtil.setCurrentAuthentication(authentication); - } + throw new UnsupportedOperationException("getNodeCount cannot be accurately determined at low cost."); } public FileInfo[] uploadContent( diff --git a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java index da7e8a8fe3..8e78c4ce37 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java @@ -69,6 +69,7 @@ import org.alfresco.service.cmr.action.ActionCondition; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.lock.LockService; import org.alfresco.service.cmr.lock.LockStatus; import org.alfresco.service.cmr.model.FileFolderService; @@ -127,7 +128,7 @@ public class RuleServiceCoverageTest extends TestCase * Category related values */ private static final String TEST_NAMESPACE = "http://www.alfresco.org/test/rulesystemtest"; - private static final QName CAT_PROP_QNAME = QName.createQName(TEST_NAMESPACE, "Region"); + private static final QName CAT_PROP_QNAME = QName.createQName(TEST_NAMESPACE, "region"); private QName regionCategorisationQName; private NodeRef catContainer; private NodeRef catRoot; @@ -696,6 +697,7 @@ public class RuleServiceCoverageTest extends TestCase * condition: no-condition * action: link-category */ + @SuppressWarnings("unchecked") public void testLinkCategoryAction() { // Create categories used in tests @@ -722,6 +724,12 @@ public class RuleServiceCoverageTest extends TestCase getContentProperties()).getChildRef(); addContentToNode(newNodeRef2); + PropertyDefinition catPropDef = this.dictionaryDAO.getProperty(CAT_PROP_QNAME); + if (catPropDef == null) + { + // Why is it undefined? + } + // Check that the category value has been set // It has been declared as a multi-value property, so we expect that here Collection setValue = (Collection) this.nodeService.getProperty(newNodeRef2, CAT_PROP_QNAME); diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java index b5082e8f0f..5a56ec0ad0 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java @@ -296,8 +296,8 @@ public class ADMLuceneTest extends TestCase testProperties.put(QName.createQName(TEST_NAMESPACE, "float-ista"), Float.valueOf(3.4f)); testProperties.put(QName.createQName(TEST_NAMESPACE, "double-ista"), Double.valueOf(5.6)); testDate = new Date(((new Date().getTime() - 10000))); - //testDate = new Date(((new Date().getTime() - 10000)/1000*1000)+668); - //System.out.println(testDate.getTime()); + // testDate = new Date(((new Date().getTime() - 10000)/1000*1000)+668); + // System.out.println(testDate.getTime()); testProperties.put(QName.createQName(TEST_NAMESPACE, "date-ista"), testDate); testProperties.put(QName.createQName(TEST_NAMESPACE, "datetime-ista"), testDate); testProperties.put(QName.createQName(TEST_NAMESPACE, "boolean-ista"), Boolean.valueOf(true)); @@ -475,6 +475,123 @@ public class ADMLuceneTest extends TestCase super(arg0); } + public void restManyReaders() throws Exception + { + NodeRef base = rootNodeRef; + for (int i = 0; i < 10; i++) + { + NodeRef dir = nodeService.createNode(base, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}d-" + i), testSuperType, null).getChildRef(); + for (int j = 0; j < 10; j++) + { + NodeRef file = nodeService.createNode(dir, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}meep"), testSuperType, null).getChildRef(); + } + } + testTX.commit(); + testTX = transactionService.getUserTransaction(); + testTX.begin(); + + ADMLuceneSearcherImpl searcher = ADMLuceneSearcherImpl.getSearcher(rootNodeRef.getStoreRef(), indexerAndSearcher); + searcher.setNodeService(nodeService); + searcher.setDictionaryService(dictionaryService); + searcher.setNamespacePrefixResolver(getNamespacePrefixReolsver("namespace")); + searcher.setQueryRegister(queryRegisterComponent); + + ResultSet results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"//meep\""); + int count = results.length(); + assertTrue(count > 0); + results.close(); + testTX.commit(); + + Thread runner = null; + + for (int i = 0; i < 20; i++) + { + runner = new QueryThread("Concurrent-" + i, runner, searcher); + } + if (runner != null) + { + runner.start(); + + try + { + runner.join(); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + } + + class QueryThread extends Thread + { + Thread waiter; + + ADMLuceneSearcherImpl searcher; + + QueryThread(String name, Thread waiter, ADMLuceneSearcherImpl searcher) + { + super(name); + this.setDaemon(true); + this.waiter = waiter; + this.searcher = searcher; + } + + public void run() + { + authenticationComponent.setSystemUserAsCurrentUser(); + if (waiter != null) + { + waiter.start(); + } + try + { + System.out.println("Start " + this.getName()); + + RetryingTransactionCallback createAndDeleteCallback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + SessionSizeResourceManager.setDisableInTransaction(); + for (int i = 0; i < 100; i++) + { + ResultSet results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"//meep\""); + int count = results.length(); + for(ResultSetRow row : results) + { + NodeRef nr = row.getNodeRef(); + } + results.close(); + } + return null; + } + }; + retryingTransactionHelper.doInTransaction(createAndDeleteCallback); + + System.out.println("End " + this.getName()); + } + catch (Exception e) + { + System.out.println("End " + this.getName() + " with error " + e.getMessage()); + e.printStackTrace(); + } + finally + { + authenticationComponent.clearCurrentSecurityContext(); + } + if (waiter != null) + { + try + { + waiter.join(); + } + catch (InterruptedException e) + { + } + } + } + + } public void testOverWritetoZeroSize() throws Exception { @@ -502,7 +619,7 @@ public class ADMLuceneTest extends TestCase nodeService.setProperty(n7, QName.createQName("{namespace}property-A"), "A"); runBaseTests(); testTX.commit(); - + testTX = transactionService.getUserTransaction(); testTX.begin(); runBaseTests(); @@ -2697,13 +2814,13 @@ public class ADMLuceneTest extends TestCase assertEquals(1, results.length()); assertNotNull(results.getRow(0).getValue(qname)); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "int-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":[0 TO 2]", null, null); assertEquals(1, results.length()); assertNotNull(results.getRow(0).getValue(qname)); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "int-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":[0 TO A]", null, null); assertEquals(1, results.length()); @@ -2714,12 +2831,12 @@ public class ADMLuceneTest extends TestCase results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{A TO 1}", null, null); assertEquals(0, results.length()); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "int-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{0 TO 1}", null, null); assertEquals(0, results.length()); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "int-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{0 TO A}", null, null); assertEquals(1, results.length()); @@ -2729,12 +2846,12 @@ public class ADMLuceneTest extends TestCase results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{A TO 2}", null, null); assertEquals(1, results.length()); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "int-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{1 TO 2}", null, null); assertEquals(0, results.length()); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "int-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{1 TO A}", null, null); assertEquals(0, results.length()); @@ -2769,13 +2886,13 @@ public class ADMLuceneTest extends TestCase assertEquals(1, results.length()); assertNotNull(results.getRow(0).getValue(qname)); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "long-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":[0 TO 2]", null, null); assertEquals(1, results.length()); assertNotNull(results.getRow(0).getValue(qname)); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "long-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":[0 TO A]", null, null); assertEquals(1, results.length()); @@ -2786,12 +2903,12 @@ public class ADMLuceneTest extends TestCase results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{A TO 2}", null, null); assertEquals(0, results.length()); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "long-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{0 TO 2}", null, null); assertEquals(0, results.length()); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "long-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{0 TO A}", null, null); assertEquals(1, results.length()); @@ -2801,12 +2918,12 @@ public class ADMLuceneTest extends TestCase results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{A TO 3}", null, null); assertEquals(1, results.length()); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "long-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{2 TO 3}", null, null); assertEquals(0, results.length()); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "long-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{2 TO A}", null, null); assertEquals(0, results.length()); @@ -2823,13 +2940,13 @@ public class ADMLuceneTest extends TestCase assertEquals(1, results.length()); assertNotNull(results.getRow(0).getValue(qname)); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "float-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":[3 TO 4]", null, null); assertEquals(1, results.length()); assertNotNull(results.getRow(0).getValue(qname)); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "float-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":[3 TO A]", null, null); assertEquals(1, results.length()); @@ -2841,13 +2958,13 @@ public class ADMLuceneTest extends TestCase assertEquals(1, results.length()); assertNotNull(results.getRow(0).getValue(qname)); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "float-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":[3.3 TO 3.4]", null, null); assertEquals(1, results.length()); assertNotNull(results.getRow(0).getValue(qname)); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "float-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":[3.3 TO A]", null, null); assertEquals(1, results.length()); @@ -2858,13 +2975,13 @@ public class ADMLuceneTest extends TestCase results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{A TO 3.4}", null, null); assertEquals(0, results.length()); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "float-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":[3.3 TO 3.4]", null, null); assertEquals(1, results.length()); assertNotNull(results.getRow(0).getValue(qname)); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "float-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":[3.3 TO A]", null, null); assertEquals(1, results.length()); @@ -2910,13 +3027,13 @@ public class ADMLuceneTest extends TestCase assertEquals(1, results.length()); assertNotNull(results.getRow(0).getValue(qname)); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "double-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":[5.5 TO 5.7]", null, null); assertEquals(1, results.length()); assertNotNull(results.getRow(0).getValue(qname)); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "double-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":[5.5 TO A]", null, null); assertEquals(1, results.length()); @@ -2927,12 +3044,12 @@ public class ADMLuceneTest extends TestCase results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{A TO 5.6}", null, null); assertEquals(0, results.length()); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "double-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{5.5 TO 5.6}", null, null); assertEquals(0, results.length()); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "double-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{5.5 TO A}", null, null); assertEquals(1, results.length()); @@ -2942,12 +3059,12 @@ public class ADMLuceneTest extends TestCase results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{A TO 5.7}", null, null); assertEquals(1, results.length()); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "double-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{5.6 TO 5.7}", null, null); assertEquals(0, results.length()); results.close(); - + qname = QName.createQName(TEST_NAMESPACE, "double-ista"); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":{5.6 TO A}", null, null); assertEquals(0, results.length()); @@ -3004,16 +3121,14 @@ public class ADMLuceneTest extends TestCase null, null); assertEquals(1, results.length()); results.close(); - + sDate = df.format(date); - results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(QName.createQName(TEST_NAMESPACE, "date-ista")) + ":[MIN TO " + sDate + "]", - null, null); + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(QName.createQName(TEST_NAMESPACE, "date-ista")) + ":[MIN TO " + sDate + "]", null, null); assertEquals(1, results.length()); results.close(); - + sDate = df.format(date); - results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(QName.createQName(TEST_NAMESPACE, "date-ista")) + ":[" + sDate + " TO MAX]", - null, null); + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(QName.createQName(TEST_NAMESPACE, "date-ista")) + ":[" + sDate + " TO MAX]", null, null); assertEquals(1, results.length()); results.close(); @@ -3022,16 +3137,16 @@ public class ADMLuceneTest extends TestCase "\\@" + escapeQName(QName.createQName(TEST_NAMESPACE, "datetime-ista")) + ":[" + sDate + " TO " + sDate + "]", null, null); assertEquals(usesDateTimeAnalyser ? 0 : 1, results.length()); results.close(); - + sDate = CachingDateFormat.getDateFormat().format(date); - results = searcher.query(rootNodeRef.getStoreRef(), "lucene", - "\\@" + escapeQName(QName.createQName(TEST_NAMESPACE, "datetime-ista")) + ":[MIN TO " + sDate + "]", null, null); - assertEquals(1, results.length()); + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(QName.createQName(TEST_NAMESPACE, "datetime-ista")) + ":[MIN TO " + sDate + "]", null, + null); + assertEquals(1, results.length()); results.close(); - + sDate = CachingDateFormat.getDateFormat().format(date); - results = searcher.query(rootNodeRef.getStoreRef(), "lucene", - "\\@" + escapeQName(QName.createQName(TEST_NAMESPACE, "datetime-ista")) + ":[" + sDate + " TO MAX]", null, null); + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(QName.createQName(TEST_NAMESPACE, "datetime-ista")) + ":[" + sDate + " TO MAX]", null, + null); assertEquals(usesDateTimeAnalyser ? 0 : 1, results.length()); results.close(); @@ -4370,7 +4485,7 @@ public class ADMLuceneTest extends TestCase results = searcher.query(sp); assertEquals(1, results.length()); results.close(); - + sp = new SearchParameters(); sp.addStore(rootNodeRef.getStoreRef()); sp.setLanguage("lucene"); @@ -4474,19 +4589,19 @@ public class ADMLuceneTest extends TestCase results.close(); // Open ended ranges - + qname = QName.createQName("{namespace}property-1"); - results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname)+":[v TO w]", null, null); + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":[v TO w]", null, null); assertEquals(2, results.length()); results.close(); - + qname = QName.createQName("{namespace}property-1"); - results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname)+":[v TO \uFFFF]", null, null); + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":[v TO \uFFFF]", null, null); assertEquals(2, results.length()); results.close(); - + qname = QName.createQName("{namespace}property-1"); - results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname)+":[\u0000 TO w]", null, null); + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(qname) + ":[\u0000 TO w]", null, null); assertEquals(2, results.length()); results.close(); } diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneResultSet.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneResultSet.java index b54a38b603..81724fb396 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/LuceneResultSet.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneResultSet.java @@ -33,6 +33,7 @@ import org.alfresco.repo.search.ResultSetRowIterator; import org.alfresco.repo.search.SearcherException; import org.alfresco.repo.search.SimpleResultSetMetaData; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.search.impl.lucene.index.CachingIndexReader; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -43,6 +44,7 @@ import org.alfresco.service.cmr.search.ResultSetMetaData; import org.alfresco.service.cmr.search.ResultSetRow; import org.alfresco.service.cmr.search.SearchParameters; import org.apache.lucene.document.Document; +import org.apache.lucene.index.IndexReader; import org.apache.lucene.search.Hits; import org.apache.lucene.search.Searcher; @@ -69,7 +71,7 @@ public class LuceneResultSet extends AbstractResultSet private LuceneConfig config; private BitSet prefetch; - + /** * Wrap a lucene seach result with node support @@ -106,11 +108,31 @@ public class LuceneResultSet extends AbstractResultSet public NodeRef getNodeRef(int n) { - // We have to get the document to resolve this - // It is possible the store ref is also stored in the index - Document doc = getDocument(n); - String id = doc.get("ID"); - return tenantService.getBaseName(new NodeRef(id)); + try + { + // We have to get the document to resolve this + // It is possible the store ref is also stored in the index + if (searcher instanceof ClosingIndexSearcher) + { + ClosingIndexSearcher cis = (ClosingIndexSearcher) searcher; + IndexReader reader = cis.getReader(); + if (reader instanceof CachingIndexReader) + { + int id = hits.id(n); + CachingIndexReader cir = (CachingIndexReader) reader; + String sid = cir.getId(id); + return tenantService.getBaseName(new NodeRef(sid)); + } + } + + Document doc = hits.doc(n); + String id = doc.get("ID"); + return tenantService.getBaseName(new NodeRef(id)); + } + catch (IOException e) + { + throw new SearcherException("IO Error reading reading node ref from the result set", e); + } } public float getScore(int n) throws SearcherException diff --git a/source/java/org/alfresco/repo/avm/AVMAspectName.java b/source/java/org/alfresco/repo/search/impl/lucene/index/CachingIndexReader.java similarity index 56% rename from source/java/org/alfresco/repo/avm/AVMAspectName.java rename to source/java/org/alfresco/repo/search/impl/lucene/index/CachingIndexReader.java index 2aadcde4c3..0ff42b01e7 100644 --- a/source/java/org/alfresco/repo/avm/AVMAspectName.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/CachingIndexReader.java @@ -1,57 +1,50 @@ -/* - * Copyright (C) 2005-2007 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.avm; - -import org.alfresco.service.namespace.QName; - -/** - * Interface to Aspect names on AVM nodes. - * @author britt - */ -public interface AVMAspectName -{ - /** - * Set the node that has the Aspect. - * @param node The node. - */ - public void setNode(AVMNode node); - - /** - * Get the node that has this Aspect name. - * @return The AVM Node. - */ - public AVMNode getNode(); - - /** - * Set the name of the Aspect. - * @param name The QName of the Aspect. - */ - public void setName(QName name); - - /** - * Get the name of this Aspect. - * @return The QName of this aspect. - */ - public QName getName(); -} +/* + * Copyright (C) 2005-2007 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.search.impl.lucene.index; + +import java.io.IOException; + +/** + * + * @author andyh + * + */ +public interface CachingIndexReader +{ + public String getId(int n) throws IOException; + + public String[] getIds(int n) throws IOException; + + public String getIsCategory(int n) throws IOException; + + public String getPath(int n) throws IOException; + + public String[] getParents(int n) throws IOException; + + public String[] getLinkAspects(int n) throws IOException; + + public String getType(int n) throws IOException; + +} diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java index 23e5c63a96..ff74d91c0d 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java @@ -48,7 +48,6 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Random; import java.util.Set; import java.util.Timer; import java.util.TimerTask; @@ -121,6 +120,8 @@ import org.safehaus.uuid.UUID; */ public class IndexInfo { + public static final String MAIN_READER = "MainReader"; + private static Timer timer = new Timer(true); /** @@ -370,7 +371,7 @@ public class IndexInfo { super(); initialiseTransitions(); - + if (config != null) { this.maxFieldLength = config.getIndexerMaxFieldLength(); @@ -1047,6 +1048,11 @@ public class IndexInfo } return mainIndexReader; } + catch(RuntimeException e) + { + e.printStackTrace(); + throw e; + } finally { releaseReadLock(); @@ -1141,7 +1147,7 @@ public class IndexInfo { reader = new MultiReader(new IndexReader[] { new FilterIndexReaderByStringId("main+id", mainIndexReader, deletions, deleteOnlyNodes), deltaReader }); } - reader = ReferenceCountingReadOnlyIndexReaderFactory.createReader("MainReader" + id, reader); + reader = ReferenceCountingReadOnlyIndexReaderFactory.createReader(MAIN_READER + id, reader, false); ReferenceCounting refCounting = (ReferenceCounting) reader; refCounting.incrementReferenceCount(); refCounting.setInvalidForReuse(); @@ -1819,7 +1825,7 @@ public class IndexInfo { reader = IndexReader.open(emptyIndex); } - reader = ReferenceCountingReadOnlyIndexReaderFactory.createReader("MainReader", reader); + reader = ReferenceCountingReadOnlyIndexReaderFactory.createReader(MAIN_READER, reader, false); return reader; } @@ -1857,7 +1863,7 @@ public class IndexInfo { reader = IndexReader.open(emptyIndex); } - reader = ReferenceCountingReadOnlyIndexReaderFactory.createReader(id, reader); + reader = ReferenceCountingReadOnlyIndexReaderFactory.createReader(id, reader, true); return reader; } diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCounting.java b/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCounting.java index 2b8efd573c..99df416a38 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCounting.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCounting.java @@ -26,17 +26,51 @@ package org.alfresco.repo.search.impl.lucene.index; import java.io.IOException; +/** + * Reference counting and caching for read only index access. + * + * When this object is invalid for reuse and all referees have gone the implementation should release all + * resources held (release the caches, close the index readers etc) + * + * @author andyh + * + */ public interface ReferenceCounting { + /** + * Add a new reference + */ public void incrementReferenceCount(); + /** + * Release a references + * @throws IOException + */ public void decrementReferenceCount() throws IOException; + /** + * Get the number of references + * @return + */ public int getReferenceCount(); + /** + * Mark is invalid for reuse. + * @throws IOException + */ public void setInvalidForReuse() throws IOException; + /** + * Determine if valid for reuse + * @return + */ public boolean isInvalidForReuse(); + /** + * Get the id for this reader. + * @return + */ public String getId(); + + } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java b/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java index 744f715019..42ac9d0de8 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java @@ -25,13 +25,32 @@ package org.alfresco.repo.search.impl.lucene.index; import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import net.sf.ehcache.CacheManager; -import org.alfresco.util.EqualsHelper; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.FieldSelector; +import org.apache.lucene.document.FieldSelectorResult; +import org.apache.lucene.document.Field.Index; +import org.apache.lucene.document.Field.Store; import org.apache.lucene.index.FilterIndexReader; import org.apache.lucene.index.IndexReader; @@ -41,16 +60,16 @@ public class ReferenceCountingReadOnlyIndexReaderFactory private static HashMap log = new HashMap(); - public static IndexReader createReader(String id, IndexReader indexReader) + public static IndexReader createReader(String id, IndexReader indexReader, boolean enableCaching) { - ReferenceCountingReadOnlyIndexReader rc = new ReferenceCountingReadOnlyIndexReader(id, indexReader); + ReferenceCountingReadOnlyIndexReader rc = new ReferenceCountingReadOnlyIndexReader(id, indexReader, enableCaching); if (s_logger.isDebugEnabled()) { if (log.containsKey(id)) { - s_logger.debug("Replacing ref counting reader for " + id ); + s_logger.debug("Replacing ref counting reader for " + id); } - s_logger.debug("Created ref counting reader for " + id +" "+rc.toString()); + s_logger.debug("Created ref counting reader for " + id + " " + rc.toString()); log.put(id, rc); } return rc; @@ -64,10 +83,7 @@ public class ReferenceCountingReadOnlyIndexReaderFactory if (rc != null) { StringBuilder builder = new StringBuilder(); - builder - .append("Id = " - + rc.getId() + " Invalid = " + rc.getReferenceCount() + " invalid = " - + rc.getInvalidForReuse() + " closed="+rc.getClosed()); + builder.append("Id = " + rc.getId() + " Invalid = " + rc.getReferenceCount() + " invalid = " + rc.getInvalidForReuse() + " closed=" + rc.getClosed()); return builder.toString(); } @@ -76,9 +92,9 @@ public class ReferenceCountingReadOnlyIndexReaderFactory } - public static class ReferenceCountingReadOnlyIndexReader extends FilterIndexReader implements ReferenceCounting + public static class ReferenceCountingReadOnlyIndexReader extends FilterIndexReader implements ReferenceCounting, CachingIndexReader { - private static Log s_logger = LogFactory.getLog(ReferenceCountingReadOnlyIndexReader.class); + private static Log s_logger = LogFactory.getLog(ReferenceCountingReadOnlyIndexReader.class); private static final long serialVersionUID = 7693185658022810428L; @@ -89,26 +105,41 @@ public class ReferenceCountingReadOnlyIndexReaderFactory boolean invalidForReuse = false; boolean allowsDeletions; - + boolean closed = false; - ReferenceCountingReadOnlyIndexReader(String id, IndexReader indexReader) + // CacheManager cacheManager; + + ConcurrentMap documentCache = concurrentMap(new LinkedHashMap()); // new + + ConcurrentSet documentKeys = (ConcurrentSet) documentCache.keySet(); + + ConcurrentMap idCache = concurrentMap(new LinkedHashMap()); // new + + ConcurrentSet idKeys = (ConcurrentSet) idCache.keySet(); + + ConcurrentHashMap isCategory = new ConcurrentHashMap(); + + boolean enableCaching; + + ReferenceCountingReadOnlyIndexReader(String id, IndexReader indexReader, boolean enableCaching) { super(indexReader); this.id = id; + this.enableCaching = enableCaching; + } public synchronized void incrementReferenceCount() { - if(closed) + if (closed) { - throw new IllegalStateException(Thread.currentThread().getName() + "Indexer is closed "+id); + throw new IllegalStateException(Thread.currentThread().getName() + "Indexer is closed " + id); } refCount++; if (s_logger.isDebugEnabled()) { - s_logger.debug(Thread.currentThread().getName() - + ": Reader " + id + " - increment - ref count is " + refCount + " ... "+super.toString()); + s_logger.debug(Thread.currentThread().getName() + ": Reader " + id + " - increment - ref count is " + refCount + " ... " + super.toString()); } } @@ -117,13 +148,12 @@ public class ReferenceCountingReadOnlyIndexReaderFactory refCount--; if (s_logger.isDebugEnabled()) { - s_logger.debug(Thread.currentThread().getName() - + ": Reader " + id + " - decrement - ref count is " + refCount + " ... "+super.toString()); + s_logger.debug(Thread.currentThread().getName() + ": Reader " + id + " - decrement - ref count is " + refCount + " ... " + super.toString()); } closeIfRequired(); if (refCount < 0) { - s_logger.error("Invalid reference count for Reader " + id + " is " + refCount + " ... "+super.toString()); + s_logger.error("Invalid reference count for Reader " + id + " is " + refCount + " ... " + super.toString()); } } @@ -133,7 +163,11 @@ public class ReferenceCountingReadOnlyIndexReaderFactory { if (s_logger.isDebugEnabled()) { - s_logger.debug(Thread.currentThread().getName() + ": Reader " + id + " closed." + " ... "+super.toString()); + s_logger.debug(Thread.currentThread().getName() + ": Reader " + id + " closed." + " ... " + super.toString()); + } + if (enableCaching) + { + // No tidy up } in.close(); closed = true; @@ -143,8 +177,7 @@ public class ReferenceCountingReadOnlyIndexReaderFactory if (s_logger.isDebugEnabled()) { s_logger.debug(Thread.currentThread().getName() - + ": Reader " + id + " still open .... ref = " + refCount + " invalidForReuse = " - + invalidForReuse + " ... "+super.toString()); + + ": Reader " + id + " still open .... ref = " + refCount + " invalidForReuse = " + invalidForReuse + " ... " + super.toString()); } } } @@ -163,17 +196,17 @@ public class ReferenceCountingReadOnlyIndexReaderFactory { return closed; } - + public synchronized void setInvalidForReuse() throws IOException { - if(closed) + if (closed) { - throw new IllegalStateException(Thread.currentThread().getName() +"Indexer is closed "+id); + throw new IllegalStateException(Thread.currentThread().getName() + "Indexer is closed " + id); } invalidForReuse = true; if (s_logger.isDebugEnabled()) { - s_logger.debug(Thread.currentThread().getName() + ": Reader " + id + " set invalid for reuse" + " ... "+super.toString()); + s_logger.debug(Thread.currentThread().getName() + ": Reader " + id + " set invalid for reuse" + " ... " + super.toString()); } closeIfRequired(); } @@ -183,11 +216,11 @@ public class ReferenceCountingReadOnlyIndexReaderFactory { if (s_logger.isDebugEnabled()) { - s_logger.debug(Thread.currentThread().getName() + ": Reader " + id + " closing" + " ... "+super.toString()); + s_logger.debug(Thread.currentThread().getName() + ": Reader " + id + " closing" + " ... " + super.toString()); } - if(closed) + if (closed) { - throw new IllegalStateException(Thread.currentThread().getName() +"Indexer is closed "+id); + throw new IllegalStateException(Thread.currentThread().getName() + "Indexer is closed " + id); } decrementReferenceCount(); } @@ -198,6 +231,113 @@ public class ReferenceCountingReadOnlyIndexReaderFactory throw new UnsupportedOperationException("Delete is not supported by read only index readers"); } + public Document document(int n, FieldSelector fieldSelector) throws IOException + { + if ((fieldSelector == null) && enableCaching) + { + Integer key = Integer.valueOf(n); + DocumentWithInstanceCount document = documentCache.get(key); + if (document == null) + { + + document = new DocumentWithInstanceCount(super.document(n, fieldSelector)); + if (document.instance < 0) + { + DocumentWithInstanceCount.instanceCount = 0; + new DocumentWithInstanceCount(document.document); + documentCache.clear(); + } + documentCache.put(key, document); + + if (documentCache.size() > 100) + { + documentCache.resize(50); + } + } + else + { + if (document.instance < DocumentWithInstanceCount.instanceCount - 50) + { + document = new DocumentWithInstanceCount(document.document); + if (document.instance < 0) + { + DocumentWithInstanceCount.instanceCount = 0; + new DocumentWithInstanceCount(document.document); + documentCache.clear(); + } + documentCache.replace(key, new DocumentWithInstanceCount(document.document)); + } + } + return document.document; + } + else + { + if (enableCaching && (fieldSelector instanceof SingleFieldSelector)) + { + SingleFieldSelector sfs = (SingleFieldSelector) fieldSelector; + + if (sfs.field.equals("ID") && sfs.last) + { + Integer key = Integer.valueOf(n); + IdWithInstanceCount id = idCache.get(key); + if (id == null) + { + + id = new IdWithInstanceCount(getLastStringValue(n, "ID")); + if (id.instance < 0) + { + IdWithInstanceCount.instanceCount = 0; + new IdWithInstanceCount(id.id); + idCache.clear(); + } + idCache.put(key, id); + + if (idCache.size() > 10000) + { + idCache.resize(5000); + } + } + else + { + if (id.instance < IdWithInstanceCount.instanceCount - 5000) + { + id = new IdWithInstanceCount(id.id); + if (id.instance < 0) + { + IdWithInstanceCount.instanceCount = 0; + new IdWithInstanceCount(id.id); + idCache.clear(); + } + idCache.replace(key, new IdWithInstanceCount(id.id)); + } + } + Document d = new Document(); + d.add(new Field("ID", id.id, Store.NO, Index.UN_TOKENIZED)); + return d; + } + if (sfs.field.equals("ISCATEGORY") && sfs.last) + { + Integer key = Integer.valueOf(n); + Boolean isCat = isCategory.get(key); + if (isCat == null) + { + isCat = (getStringValue(n, "ISCATEGORY") != null); + isCategory.put(key, isCat); + } + Document d = new Document(); + if (isCat) + { + d.add(new Field("ISCATEGORY", "T", Store.NO, Index.UN_TOKENIZED)); + } + return d; + } + + } + } + + return super.document(n, fieldSelector); + } + public String getId() { return id; @@ -205,7 +345,871 @@ public class ReferenceCountingReadOnlyIndexReaderFactory public boolean isInvalidForReuse() { - return invalidForReuse; + return invalidForReuse; + } + + public String getId(int n) throws IOException + { + Document d = document(n, new SingleFieldSelector("ID", true)); + return d.getField("ID").stringValue(); + } + + public String[] getIds(int n) throws IOException + { + return getStringValues(n, "ID"); + } + + public String[] getLinkAspects(int n) throws IOException + { + return getStringValues(n, "LINKASPECT"); + } + + public String[] getParents(int n) throws IOException + { + return getStringValues(n, "PARENT"); + } + + public String getPath(int n) throws IOException + { + return getStringValue(n, "PATH"); + } + + public String getType(int n) throws IOException + { + return getStringValue(n, "TYPE"); + } + + public String getIsCategory(int n) throws IOException + { + Document d = document(n, new SingleFieldSelector("ISCATEGORY", true)); + Field f = d.getField("ISCATEGORY"); + return f == null ? null : f.stringValue(); + } + + private String getLastStringValue(int n, String fieldName) throws IOException + { + Document document = document(n); + Field[] fields = document.getFields(fieldName); + Field field = fields[fields.length - 1]; + return (field == null) ? null : field.stringValue(); + } + + private String getStringValue(int n, String fieldName) throws IOException + { + Document document = document(n); + Field field = document.getField(fieldName); + return (field == null) ? null : field.stringValue(); + } + + private String[] getStringValues(int n, String fieldName) throws IOException + { + Document document = document(n); + Field[] fields = document.getFields(fieldName); + if (fields != null) + { + String[] answer = new String[fields.length]; + int i = 0; + for (Field field : fields) + { + answer[i++] = (field == null) ? null : field.stringValue(); + } + return answer; + } + else + { + return null; + } } } + + public static ConcurrentMap concurrentMap(Map m) + { + return new ConcurrentMap(m); + } + + private static class ConcurrentMap implements Map, Serializable + { + private final Map m; // Backing Map + + final private ReadWriteLock readWriteLock; + + ConcurrentMap(Map m) + { + if (m == null) + throw new NullPointerException(); + this.m = m; + this.readWriteLock = new ReentrantReadWriteLock(); + } + + ConcurrentMap(Map m, ReadWriteLock readWriteLock) + { + this.m = m; + this.readWriteLock = readWriteLock; + } + + public int size() + { + readWriteLock.readLock().lock(); + try + { + return m.size(); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public boolean isEmpty() + { + readWriteLock.readLock().lock(); + try + { + return m.isEmpty(); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public boolean containsKey(Object key) + { + readWriteLock.readLock().lock(); + try + { + return m.containsKey(key); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public boolean containsValue(Object value) + { + readWriteLock.readLock().lock(); + try + { + return m.containsValue(value); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public V get(Object key) + { + readWriteLock.readLock().lock(); + try + { + return m.get(key); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public V put(K key, V value) + { + + readWriteLock.writeLock().lock(); + try + { + return m.put(key, value); + } + finally + { + readWriteLock.writeLock().unlock(); + } + } + + public V remove(Object key) + { + readWriteLock.writeLock().lock(); + try + { + return m.remove(key); + } + finally + { + readWriteLock.writeLock().unlock(); + } + } + + public void putAll(Map map) + { + readWriteLock.writeLock().lock(); + try + { + m.putAll(map); + } + finally + { + readWriteLock.writeLock().unlock(); + } + } + + public void clear() + { + readWriteLock.writeLock().lock(); + try + { + m.clear(); + } + finally + { + readWriteLock.writeLock().unlock(); + } + } + + private transient Set keySet = null; + + private transient Set> entrySet = null; + + private transient Collection values = null; + + public Set keySet() + { + + readWriteLock.readLock().lock(); + try + { + if (keySet == null) + keySet = new ConcurrentSet(m.keySet(), readWriteLock); + return keySet; + } + finally + { + readWriteLock.readLock().unlock(); + } + + } + + public Set> entrySet() + { + readWriteLock.readLock().lock(); + try + { + if (entrySet == null) + entrySet = new ConcurrentSet>(m.entrySet(), readWriteLock); + return entrySet; + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public Collection values() + { + readWriteLock.readLock().lock(); + try + { + if (values == null) + values = new ConcurrentCollection(m.values(), readWriteLock); + return values; + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public boolean equals(Object o) + { + readWriteLock.readLock().lock(); + try + { + return m.equals(o); + } + finally + { + readWriteLock.readLock().unlock(); + } + + } + + public int hashCode() + { + readWriteLock.readLock().lock(); + try + { + return m.hashCode(); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public String toString() + { + readWriteLock.readLock().lock(); + try + { + return m.toString(); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + private void writeObject(ObjectOutputStream s) throws IOException + { + readWriteLock.readLock().lock(); + try + { + s.defaultWriteObject(); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public void resize(int size) + { + + ArrayList toRemove = null; + readWriteLock.writeLock().lock(); + try + { + int excess = m.size() - size; + toRemove = new ArrayList(excess); + if (excess > 0) + { + + Iterator it = keySet.iterator(); + while (toRemove.size() < excess) + { + K key = it.next(); + toRemove.add(key); + } + } + + } + finally + { + readWriteLock.writeLock().unlock(); + } + + if ((toRemove != null) && (toRemove.size() > 0)) + { + readWriteLock.writeLock().lock(); + try + { + for (K key : toRemove) + { + m.remove(key); + } + } + finally + { + readWriteLock.writeLock().unlock(); + } + } + } + + public void replace(K key, V value) + { + readWriteLock.writeLock().lock(); + try + { + m.remove(key); + m.put(key, value); + } + finally + { + readWriteLock.writeLock().unlock(); + } + } + } + + /** + * @serial include + */ + static class ConcurrentCollection implements Collection, Serializable + { + final Collection c; // Backing Collection + + final ReadWriteLock readWriteLock; + + ConcurrentCollection(Collection c) + { + if (c == null) + throw new NullPointerException(); + this.c = c; + this.readWriteLock = new ReentrantReadWriteLock(); + } + + ConcurrentCollection(Collection c, ReadWriteLock readWriteLock) + { + this.c = c; + this.readWriteLock = readWriteLock; + } + + public int size() + { + readWriteLock.readLock().lock(); + try + { + return c.size(); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public boolean isEmpty() + { + readWriteLock.readLock().lock(); + try + { + return c.isEmpty(); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public boolean contains(Object o) + { + readWriteLock.readLock().lock(); + try + { + return c.contains(o); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public Object[] toArray() + { + readWriteLock.readLock().lock(); + try + { + return c.toArray(); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public T[] toArray(T[] a) + { + readWriteLock.readLock().lock(); + try + { + return c.toArray(a); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public Iterator iterator() + { + return c.iterator(); // Must be manually synched by user! + } + + public boolean add(E e) + { + readWriteLock.writeLock().lock(); + try + { + return c.add(e); + } + finally + { + readWriteLock.writeLock().unlock(); + } + + } + + public boolean remove(Object o) + { + readWriteLock.writeLock().lock(); + try + { + return c.remove(o); + } + finally + { + readWriteLock.writeLock().unlock(); + } + } + + public boolean containsAll(Collection coll) + { + readWriteLock.readLock().lock(); + try + { + return c.containsAll(coll); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public boolean addAll(Collection coll) + { + readWriteLock.writeLock().lock(); + try + { + return c.addAll(coll); + } + finally + { + readWriteLock.writeLock().unlock(); + } + } + + public boolean removeAll(Collection coll) + { + readWriteLock.writeLock().lock(); + try + { + return c.removeAll(coll); + } + finally + { + readWriteLock.writeLock().unlock(); + } + } + + public boolean retainAll(Collection coll) + { + readWriteLock.writeLock().lock(); + try + { + return c.retainAll(coll); + } + finally + { + readWriteLock.writeLock().unlock(); + } + + } + + public void clear() + { + readWriteLock.writeLock().lock(); + try + { + c.clear(); + } + finally + { + readWriteLock.writeLock().unlock(); + } + } + + public String toString() + { + readWriteLock.readLock().lock(); + try + { + return c.toString(); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + private void writeObject(ObjectOutputStream s) throws IOException + { + readWriteLock.readLock().lock(); + try + { + s.defaultWriteObject(); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + } + + /** + * @serial include + */ + static class ConcurrentSet extends ConcurrentCollection implements Set + { + ConcurrentSet(Set s) + { + super(s); + } + + ConcurrentSet(Set s, ReadWriteLock readWriteLock) + { + super(s, readWriteLock); + } + + public boolean equals(Object o) + { + readWriteLock.readLock().lock(); + try + { + return c.equals(o); + } + finally + { + readWriteLock.readLock().unlock(); + } + + } + + public int hashCode() + { + readWriteLock.readLock().lock(); + try + { + return c.hashCode(); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + } + + static class ConcurrentList extends ConcurrentCollection implements List + { + static final long serialVersionUID = -7754090372962971524L; + + final List list; + + ConcurrentList(List list) + { + super(list); + this.list = list; + } + + ConcurrentList(List list, ReadWriteLock readWriteLock) + { + super(list, readWriteLock); + this.list = list; + } + + public boolean equals(Object o) + { + readWriteLock.readLock().lock(); + try + { + return c.equals(o); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public int hashCode() + { + readWriteLock.readLock().lock(); + try + { + return c.hashCode(); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public E get(int index) + { + readWriteLock.readLock().lock(); + try + { + return list.get(index); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public E set(int index, E element) + { + readWriteLock.readLock().lock(); + try + { + return list.set(index, element); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public void add(int index, E element) + { + readWriteLock.readLock().lock(); + try + { + list.add(index, element); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public E remove(int index) + { + readWriteLock.readLock().lock(); + try + { + return list.remove(index); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public int indexOf(Object o) + { + readWriteLock.readLock().lock(); + try + { + return list.indexOf(o); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public int lastIndexOf(Object o) + { + readWriteLock.readLock().lock(); + try + { + return list.lastIndexOf(o); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public boolean addAll(int index, Collection c) + { + readWriteLock.readLock().lock(); + try + { + return list.addAll(index, c); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + public ListIterator listIterator() + { + return list.listIterator(); // Must be manually synched by user + } + + public ListIterator listIterator(int index) + { + return list.listIterator(index); // Must be manually synched by user + } + + public List subList(int fromIndex, int toIndex) + { + readWriteLock.readLock().lock(); + try + { + return new ConcurrentList(list.subList(fromIndex, toIndex), readWriteLock); + } + finally + { + readWriteLock.readLock().unlock(); + } + } + + /** + * SynchronizedRandomAccessList instances are serialized as SynchronizedList instances to allow them to be + * deserialized in pre-1.4 JREs (which do not have SynchronizedRandomAccessList). This method inverts the + * transformation. As a beneficial side-effect, it also grafts the RandomAccess marker onto SynchronizedList + * instances that were serialized in pre-1.4 JREs. Note: Unfortunately, SynchronizedRandomAccessList instances + * serialized in 1.4.1 and deserialized in 1.4 will become SynchronizedList instances, as this method was + * missing in 1.4. + */ + // private Object readResolve() + // { + // return (list instanceof RandomAccess ? new SynchronizedRandomAccessList(list) : this); + // } + } + + static class DocumentWithInstanceCount + { + volatile static int instanceCount = 0; + + int instance; + + Document document; + + DocumentWithInstanceCount(Document document) + { + this.document = document; + instance = instanceCount++; + } + } + + static class IdWithInstanceCount + { + volatile static int instanceCount = 0; + + int instance; + + String id; + + IdWithInstanceCount(String id) + { + this.id = id; + instance = instanceCount++; + } + } + + private static class SingleFieldSelector implements FieldSelector + { + String field; + + boolean last; + + SingleFieldSelector(String field, boolean last) + { + this.field = field; + this.last = last; + } + + public FieldSelectorResult accept(String fieldName) + { + if (fieldName.equals(field)) + { + return FieldSelectorResult.LOAD; + } + else + { + return FieldSelectorResult.NO_LOAD; + } + } + + } } diff --git a/source/java/org/alfresco/repo/search/impl/lucene/query/LeafScorer.java b/source/java/org/alfresco/repo/search/impl/lucene/query/LeafScorer.java index fcb1f63fda..1dad04db8f 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/query/LeafScorer.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/query/LeafScorer.java @@ -34,6 +34,7 @@ import java.util.List; import org.alfresco.model.ContentModel; import org.alfresco.repo.search.SearcherException; +import org.alfresco.repo.search.impl.lucene.index.CachingIndexReader; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; @@ -52,8 +53,8 @@ import org.apache.lucene.search.Weight; /** * Leaf scorer to complete path queries + * * @author andyh - * */ public class LeafScorer extends Scorer { @@ -134,10 +135,8 @@ public class LeafScorer extends Scorer * @param repeat * @param tp */ - public LeafScorer(Weight weight, TermPositions root, TermPositions level0, ContainerScorer containerScorer, - StructuredFieldPosition[] sfps, TermPositions allNodes, HashMap selfIds, - IndexReader reader, Similarity similarity, byte[] norms, DictionaryService dictionaryService, - boolean repeat, TermPositions tp) + public LeafScorer(Weight weight, TermPositions root, TermPositions level0, ContainerScorer containerScorer, StructuredFieldPosition[] sfps, TermPositions allNodes, + HashMap selfIds, IndexReader reader, Similarity similarity, byte[] norms, DictionaryService dictionaryService, boolean repeat, TermPositions tp) { super(similarity); this.root = root; @@ -170,6 +169,132 @@ public class LeafScorer extends Scorer } + private String getId(IndexReader reader, int n) throws IOException + { + if (reader instanceof CachingIndexReader) + { + CachingIndexReader cachingIndexReader = (CachingIndexReader) reader; + return cachingIndexReader.getId(n); + } + else + { + Document document = reader.document(n); + Field[] fields = document.getFields("ID"); + if (fields != null) + { + Field id = fields[fields.length - 1]; + return (id == null) ? null : id.stringValue(); + } + else + { + return null; + } + } + } + + private String getIsCategory(IndexReader reader, int n) throws IOException + { + if (reader instanceof CachingIndexReader) + { + CachingIndexReader cachingIndexReader = (CachingIndexReader) reader; + return cachingIndexReader.getIsCategory(n); + } + else + { + Document document = reader.document(n); + Field isCategory = document.getField("ISCATEGORY"); + return (isCategory == null) ? null : isCategory.stringValue(); + } + } + + private String getPath(IndexReader reader, int n) throws IOException + { + if (reader instanceof CachingIndexReader) + { + CachingIndexReader cachingIndexReader = (CachingIndexReader) reader; + return cachingIndexReader.getPath(n); + } + else + { + Document document = reader.document(n); + Field path = document.getField("PATH"); + return (path == null) ? null : path.stringValue(); + } + } + + private String getType(IndexReader reader, int n) throws IOException + { + if (reader instanceof CachingIndexReader) + { + CachingIndexReader cachingIndexReader = (CachingIndexReader) reader; + return cachingIndexReader.getType(n); + } + else + { + Document document = reader.document(n); + Field path = document.getField("TYPE"); + return (path == null) ? null : path.stringValue(); + } + } + + private String[] getParents(IndexReader reader, int n) throws IOException + { + if (reader instanceof CachingIndexReader) + { + CachingIndexReader cachingIndexReader = (CachingIndexReader) reader; + return cachingIndexReader.getParents(n); + } + else + { + Document document = reader.document(n); + Field[] fields = document.getFields("PARENT"); + if (fields != null) + { + String[] answer = new String[fields.length]; + int i = 0; + for (Field field : fields) + { + answer[i++] = (field == null) ? null : field.stringValue(); + } + return answer; + } + else + { + return null; + } + } + } + + private String[] getlinkAspects(IndexReader reader, int n) throws IOException + { + if (reader instanceof CachingIndexReader) + { + CachingIndexReader cachingIndexReader = (CachingIndexReader) reader; + return cachingIndexReader.getLinkAspects(n); + } + else + { + Document document = reader.document(n); + Field[] fields = document.getFields("LINKASPECT"); + if (fields != null) + { + String[] answer = new String[fields.length]; + int i = 0; + for (Field field : fields) + { + answer[i++] = (field == null) ? null : field.stringValue(); + } + return answer; + } + else + { + return null; + } + } + } + + + private void initialise() throws IOException { if (containerScorer != null) @@ -178,42 +303,40 @@ public class LeafScorer extends Scorer while (containerScorer.next()) { int doc = containerScorer.doc(); - Document document = reader.document(doc); - Field[] fields = document.getFields("ID"); - Field id = fields[fields.length-1]; - Counter counter = parentIds.get(id.stringValue()); + + String id = getId(reader, doc); + Counter counter = parentIds.get(id); if (counter == null) { counter = new Counter(); - parentIds.put(id.stringValue(), counter); + parentIds.put(id, counter); } counter.count++; if (!hasSelfScorer) { - counter = selfIds.get(id.stringValue()); + counter = selfIds.get(id); if (counter == null) { counter = new Counter(); - selfIds.put(id.stringValue(), counter); + selfIds.put(id, counter); } counter.count++; } - Field isCategory = document.getField("ISCATEGORY"); + String isCategory = getIsCategory(reader, doc); if (isCategory != null) { - Field path = document.getField("PATH"); - String pathString = path.stringValue(); + String pathString = getPath(reader, doc); if ((pathString.length() > 0) && (pathString.charAt(0) == '/')) { pathString = pathString.substring(1); } - List list = categories.get(id.stringValue()); + List list = categories.get(id); if (list == null) { list = new ArrayList(); - categories.put(id.stringValue(), list); + categories.put(id, list); } list.add(pathString); } @@ -225,24 +348,22 @@ public class LeafScorer extends Scorer while (level0.next()) { int doc = level0.doc(); - Document document = reader.document(doc); - Field[] fields = document.getFields("ID"); - Field id = fields[fields.length-1]; + String id = getId(reader, doc); if (id != null) { - Counter counter = parentIds.get(id.stringValue()); + Counter counter = parentIds.get(id); if (counter == null) { counter = new Counter(); - parentIds.put(id.stringValue(), counter); + parentIds.put(id, counter); } counter.count++; - counter = selfIds.get(id.stringValue()); + counter = selfIds.get(id); if (counter == null) { counter = new Counter(); - selfIds.put(id.stringValue(), counter); + selfIds.put(id, counter); } counter.count++; } @@ -269,20 +390,20 @@ public class LeafScorer extends Scorer { for (int i = 0, l = tp.freq(); i < l; i++) { - for(int j = 0; j < counter.count; j++) + for (int j = 0; j < counter.count; j++) { - parents[position++] = tp.doc(); - if (position == parents.length) - { - int[] old = parents; - parents = new int[old.length * 2]; - System.arraycopy(old, 0, parents, 0, old.length); - } + parents[position++] = tp.doc(); + if (position == parents.length) + { + int[] old = parents; + parents = new int[old.length * 2]; + System.arraycopy(old, 0, parents, 0, old.length); + } } - + } } - + } int[] old = parents; parents = new int[position]; @@ -297,18 +418,18 @@ public class LeafScorer extends Scorer while (tp.next()) { Counter counter = selfIds.get(id); - for(int i = 0; i < counter.count; i++) + for (int i = 0; i < counter.count; i++) { - self[position++] = tp.doc(); - if (position == self.length) - { - old = self; - self = new int[old.length * 2]; - System.arraycopy(old, 0, self, 0, old.length); - } + self[position++] = tp.doc(); + if (position == self.length) + { + old = self; + self = new int[old.length * 2]; + System.arraycopy(old, 0, self, 0, old.length); + } } } - + } old = self; self = new int[position]; @@ -342,7 +463,7 @@ public class LeafScorer extends Scorer } } } - + } } } @@ -698,26 +819,26 @@ public class LeafScorer extends Scorer } } - Document doc = reader.document(doc()); - Field[] parentFields = doc.getFields("PARENT"); - Field[] linkFields = doc.getFields("LINKASPECT"); + //Document doc = reader.document(doc()); + String[] parentFields = getParents(reader, doc()); + String[] linkFields = getlinkAspects(reader, doc()); String parentID = null; String linkAspect = null; if ((parentFields != null) && (parentFields.length > position) && (parentFields[position] != null)) { - parentID = parentFields[position].stringValue(); + parentID = parentFields[position]; } if ((linkFields != null) && (linkFields.length > position) && (linkFields[position] != null)) { - linkAspect = linkFields[position].stringValue(); + linkAspect = linkFields[position]; } - containersIncludeCurrent(doc, parentID, linkAspect); + containersIncludeCurrent(parentID, linkAspect); } - private void containersIncludeCurrent(Document document, String parentID, String aspectQName) throws IOException + private void containersIncludeCurrent(String parentID, String aspectQName) throws IOException { if ((containerScorer != null) || (level0 != null)) { @@ -725,8 +846,7 @@ public class LeafScorer extends Scorer { return; } - Field[] fields = document.getFields("ID"); - String id = fields[fields.length-1].stringValue(); + String id = getId(reader, doc()); StructuredFieldPosition last = sfps[sfps.length - 1]; if ((last.linkSelf() && selfIds.containsKey(id))) { @@ -747,10 +867,10 @@ public class LeafScorer extends Scorer { if (categories.containsKey(parentID)) { - Field typeField = document.getField("TYPE"); - if ((typeField != null) && (typeField.stringValue() != null)) + String type = getType(reader, doc()); + if (type != null) { - QName typeRef = QName.createQName(typeField.stringValue()); + QName typeRef = QName.createQName(type); if (isCategory(typeRef)) { Counter counter = parentIds.get(parentID); @@ -775,6 +895,7 @@ public class LeafScorer extends Scorer // get field and compare to ID // Check in path as QName // somewhere + Document document = reader.document(doc()); Field[] categoryFields = document.getFields("@" + propDef.getName()); if (categoryFields != null) { diff --git a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java index 5a540c45e2..35835274b4 100644 --- a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java +++ b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java @@ -138,10 +138,6 @@ public class LDAPPersonExportSource implements ExportSource try { - AttributesImpl attrs = new AttributesImpl(); - attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName - .toPrefixString(), null, ContentModel.TYPE_PERSON.toPrefixString(namespaceService)); - writer.startDocument(); for (String prefix : prefixes) @@ -207,6 +203,10 @@ public class LDAPPersonExportSource implements ExportSource s_logger.debug("Adding user for " + uid); } + AttributesImpl attrs = new AttributesImpl(); + attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName + .toPrefixString(), null, QName.createQName("cm", uid, namespaceService).toPrefixString(namespaceService)); + writer.startElement(ContentModel.TYPE_PERSON.getNamespaceURI(), ContentModel.TYPE_PERSON .getLocalName(), ContentModel.TYPE_PERSON.toPrefixString(namespaceService), attrs); diff --git a/source/java/org/alfresco/repo/security/person/PersonDao.java b/source/java/org/alfresco/repo/security/person/PersonDao.java new file mode 100644 index 0000000000..c29b76bcc9 --- /dev/null +++ b/source/java/org/alfresco/repo/security/person/PersonDao.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2005-2007 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.security.person; + +import java.util.List; +import java.util.Set; + +import org.alfresco.service.cmr.repository.NodeRef; + +public interface PersonDao +{ + public List getPersonOrNull(String searchUserName, boolean userNamesAreCaseSensitive); + + public Set getAllPeople(); +} diff --git a/source/java/org/alfresco/repo/security/person/PersonDaoBootstrap.java b/source/java/org/alfresco/repo/security/person/PersonDaoBootstrap.java new file mode 100644 index 0000000000..2f4f3fea69 --- /dev/null +++ b/source/java/org/alfresco/repo/security/person/PersonDaoBootstrap.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2005-2007 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.security.person; + +import org.alfresco.util.AbstractLifecycleBean; +import org.springframework.context.ApplicationEvent; + +public class PersonDaoBootstrap extends AbstractLifecycleBean +{ + private PersonDaoImpl personDaoImpl; + + public void setPersonDaoImpl(PersonDaoImpl personDaoImpl) + { + this.personDaoImpl = personDaoImpl; + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + personDaoImpl.init(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // nothing to do + + } + +} diff --git a/source/java/org/alfresco/repo/security/person/PersonDaoImpl.java b/source/java/org/alfresco/repo/security/person/PersonDaoImpl.java new file mode 100644 index 0000000000..821e7ee65c --- /dev/null +++ b/source/java/org/alfresco/repo/security/person/PersonDaoImpl.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2005-2007 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.security.person; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.LocaleDAO; +import org.alfresco.repo.domain.Node; +import org.alfresco.repo.domain.NodePropertyValue; +import org.alfresco.repo.domain.PropertyMapKey; +import org.alfresco.repo.domain.QNameDAO; +import org.alfresco.repo.domain.hibernate.NodeImpl; +import org.alfresco.repo.node.db.hibernate.HibernateNodeDaoServiceImpl; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.QName; +import org.hibernate.SQLQuery; +import org.hibernate.Session; +import org.springframework.orm.hibernate3.HibernateCallback; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; + +public class PersonDaoImpl extends HibernateDaoSupport implements PersonDao +{ + private static final String PERSON_GET_PERSON = "person.getPerson"; + + private static final String PERSON_GET_ALL_PEOPLE = "person.getAllPeople"; + + private QNameDAO qnameDAO; + + private Long qNameId; + + private LocaleDAO localeDAO; + + private DictionaryService dictionaryService; + + @SuppressWarnings("unchecked") + public List getPersonOrNull(final String searchUserName, boolean userNamesAreCaseSensitive) + { + List answer = new ArrayList(); + + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + SQLQuery query = getSession().createSQLQuery("SELECT {n.*} FROM alf_node n JOIN alf_node_properties p ON n.id = p.node_id JOIN alf_child_assoc c on c.child_node_id = n.id WHERE c.qname_localname = :userName1 AND p.qname_id = :qnameId AND p.string_value = :userName2"); + query.addEntity("n", NodeImpl.class); + query.setParameter("qnameId", qNameId); + query.setParameter("userName1", searchUserName); + query.setParameter("userName2", searchUserName); + return query.list(); + } + }; + + List results = (List) getHibernateTemplate().execute(callback); + + for (Node node : results) + { + NodeRef nodeRef = node.getNodeRef(); + Map nodeProperties = node.getProperties(); + + // Convert the QName IDs + Map converted = HibernateNodeDaoServiceImpl.convertToPublicProperties(nodeProperties, qnameDAO, localeDAO, dictionaryService); + + Serializable value = converted.get(ContentModel.PROP_USERNAME); + String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, value); + + if (userNamesAreCaseSensitive) + { + if (realUserName.equals(searchUserName)) + { + answer.add(nodeRef); + } + } + else + { + if (realUserName.equalsIgnoreCase(searchUserName)) + { + answer.add(nodeRef); + } + } + + } + return answer; + + } + + public void init() + { + qNameId = qnameDAO.getOrCreateQNameEntity(ContentModel.PROP_USERNAME).getId(); + } + + @SuppressWarnings("unchecked") + public Set getAllPeople() + { + Set answer = new HashSet(); + + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + SQLQuery query = getSession().createSQLQuery("SELECT {n.*} FROM alf_node n JOIN alf_node_properties p ON n.id = p.node_id WHERE p.qname_id = :qnameId"); + query.addEntity("n", NodeImpl.class); + query.setParameter("qnameId", qNameId); + return query.list(); + } + }; + + List results = (List) getHibernateTemplate().execute(callback); + + for (Node node : results) + { + NodeRef nodeRef = node.getNodeRef(); + answer.add(nodeRef); + } + return answer; + + } + + public void setQnameDAO(QNameDAO qnameDAO) + { + this.qnameDAO = qnameDAO; + } + + public void setLocaleDAO(LocaleDAO localeDAO) + { + this.localeDAO = localeDAO; + } + + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + +} diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java index 138c6b7249..d0cbc60553 100644 --- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java +++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java @@ -26,6 +26,8 @@ package org.alfresco.repo.security.person; import java.io.Serializable; import java.util.Collections; +import java.util.Comparator; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; @@ -61,15 +63,14 @@ import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.GUID; import org.alfresco.util.PropertyCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -public class PersonServiceImpl - extends TransactionListenerAdapter - implements PersonService, NodeServicePolicies.OnCreateNodePolicy, NodeServicePolicies.BeforeDeleteNodePolicy +public class PersonServiceImpl extends TransactionListenerAdapter implements PersonService, NodeServicePolicies.OnCreateNodePolicy, NodeServicePolicies.BeforeDeleteNodePolicy { private static Log s_logger = LogFactory.getLog(PersonServiceImpl.class); @@ -79,16 +80,16 @@ public class PersonServiceImpl private static final String LEAVE = "LEAVE"; - public static final String SYSTEM_FOLDER = "/sys:system"; + public static final String SYSTEM_FOLDER_SHORT_QNAME = "sys:system"; - public static final String PEOPLE_FOLDER = SYSTEM_FOLDER + "/sys:people"; + public static final String PEOPLE_FOLDER_SHORT_QNAME = "sys:people"; // IOC private StoreRef storeRef; private TransactionService transactionService; - + private NodeService nodeService; private TenantService tenantService; @@ -102,7 +103,7 @@ public class PersonServiceImpl private PermissionServiceSPI permissionServiceSPI; private NamespacePrefixResolver namespacePrefixResolver; - + private PolicyComponent policyComponent; private boolean createMissingPeople; @@ -120,7 +121,9 @@ public class PersonServiceImpl private boolean lastIsBest = true; private boolean includeAutoCreated = false; - + + private PersonDao personDao; + /** a transactionally-safe cache to be injected */ private SimpleCache personCache; @@ -135,12 +138,13 @@ public class PersonServiceImpl props.add(ContentModel.PROP_ORGID); mutableProperties = Collections.unmodifiableSet(props); } - + @Override public boolean equals(Object obj) { return this == obj; } + @Override public int hashCode() { @@ -161,15 +165,12 @@ public class PersonServiceImpl PropertyCheck.mandatory(this, "namespacePrefixResolver", namespacePrefixResolver); PropertyCheck.mandatory(this, "policyComponent", policyComponent); PropertyCheck.mandatory(this, "personCache", personCache); - - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), - ContentModel.TYPE_PERSON, - new JavaBehaviour(this, "onCreateNode")); - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"), - ContentModel.TYPE_PERSON, - new JavaBehaviour(this, "beforeDeleteNode")); + PropertyCheck.mandatory(this, "personDao", personDao); + + this.policyComponent + .bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), ContentModel.TYPE_PERSON, new JavaBehaviour(this, "onCreateNode")); + this.policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"), ContentModel.TYPE_PERSON, new JavaBehaviour(this, + "beforeDeleteNode")); } public boolean getUserNamesAreCaseSensitive() @@ -207,10 +208,16 @@ public class PersonServiceImpl this.processDuplicates = processDuplicates; } + public void setPersonDao(PersonDao personDao) + { + this.personDao = personDao; + } + /** * Set the username to person cache. * - * @param personCache a transactionally safe cache + * @param personCache + * a transactionally safe cache */ public void setPersonCache(SimpleCache personCache) { @@ -218,13 +225,12 @@ public class PersonServiceImpl } /** - * Retrieve the person NodeRef for a username key. Depending on configuration missing people - * will be created if not found, else a NoSuchPersonException exception will be thrown. - * - * @param userName of the person NodeRef to retrieve + * Retrieve the person NodeRef for a username key. Depending on configuration missing people will be created if not + * found, else a NoSuchPersonException exception will be thrown. * + * @param userName + * of the person NodeRef to retrieve * @return NodeRef of the person as specified by the username - * * @throws NoSuchPersonException */ public NodeRef getPerson(String userName) @@ -233,7 +239,7 @@ public class PersonServiceImpl if (personNode == null) { TxnReadState txnReadState = AlfrescoTransactionSupport.getTransactionReadState(); - if (createMissingPeople() && txnReadState == TxnReadState.TXN_READ_WRITE) + if (createMissingPeople() && txnReadState == TxnReadState.TXN_READ_WRITE) { // We create missing people AND are in a read-write txn return createMissingPerson(userName); @@ -259,127 +265,75 @@ public class PersonServiceImpl NodeRef returnRef = this.personCache.get(searchUserName); if (returnRef == null) { - SearchParameters sp = new SearchParameters(); - sp.setLanguage(SearchService.LANGUAGE_LUCENE); - sp.setQuery("@cm\\:userName:\"" + searchUserName + "\""); - sp.addStore(tenantService.getName(storeRef)); - sp.excludeDataInTheCurrentTransaction(false); - - ResultSet rs = null; - - boolean singleton = true; - - try + List refs = personDao.getPersonOrNull(searchUserName, userNamesAreCaseSensitive); + if (refs.size() > 1) { - rs = searchService.query(sp); - - for (ResultSetRow row : rs) - { - NodeRef nodeRef = row.getNodeRef(); - if (nodeService.exists(nodeRef)) - { - String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty( - nodeRef, ContentModel.PROP_USERNAME)); - - if (userNamesAreCaseSensitive) - { - if (realUserName.equals(searchUserName)) - { - if (returnRef == null) - { - returnRef = nodeRef; - } - else - { - singleton = false; - break; - } - } - } - else - { - if (realUserName.equalsIgnoreCase(searchUserName)) - { - if (returnRef == null) - { - returnRef = nodeRef; - } - else - { - singleton = false; - break; - } - } - } - } - } + returnRef = handleDuplicates(refs, searchUserName); } - finally + else if (refs.size() == 1) { - if (rs != null) - { - rs.close(); - } + returnRef = refs.get(0); } - if (!singleton) - { - returnRef = handleDuplicates(searchUserName); - } - + // add to cache this.personCache.put(searchUserName, returnRef); } return returnRef; } - private NodeRef handleDuplicates(String searchUserName) + + private NodeRef handleDuplicates(List refs, String searchUserName) { if (processDuplicates) { - NodeRef best = findBest(searchUserName); - addDuplicateUserNameToHandle(searchUserName, best); + NodeRef best = findBest(refs); + HashSet toHandle = new HashSet(); + toHandle.addAll(refs); + toHandle.remove(best); + addDuplicateNodeRefsToHandle(toHandle); return best; } else { if (userNamesAreCaseSensitive) { - throw new AlfrescoRuntimeException("Found more than one user for " - + searchUserName + " (case sensitive)"); + throw new AlfrescoRuntimeException("Found more than one user for " + searchUserName + " (case sensitive)"); } else { - throw new AlfrescoRuntimeException("Found more than one user for " - + searchUserName + " (case insensitive)"); + throw new AlfrescoRuntimeException("Found more than one user for " + searchUserName + " (case insensitive)"); } } } private static final String KEY_POST_TXN_DUPLICATES = "PersonServiceImpl.KEY_POST_TXN_DUPLICATES"; + /** * Get the txn-bound usernames that need cleaning up */ - private Map getPostTxnDuplicates() + private Set getPostTxnDuplicates() { @SuppressWarnings("unchecked") - Map postTxnDuplicates = (Map) AlfrescoTransactionSupport.getResource(KEY_POST_TXN_DUPLICATES); + Set postTxnDuplicates = (Set) AlfrescoTransactionSupport.getResource(KEY_POST_TXN_DUPLICATES); if (postTxnDuplicates == null) { - postTxnDuplicates = new HashMap(7); + postTxnDuplicates = new HashSet(); AlfrescoTransactionSupport.bindResource(KEY_POST_TXN_DUPLICATES, postTxnDuplicates); } return postTxnDuplicates; } + /** * Flag a username for cleanup after the transaction. */ - private void addDuplicateUserNameToHandle(String searchUserName, NodeRef best) + private void addDuplicateNodeRefsToHandle(Set refs) { // Firstly, bind this service to the transaction AlfrescoTransactionSupport.bindListener(this); // Now get the post txn duplicate list - Map postTxnDuplicates = getPostTxnDuplicates(); - postTxnDuplicates.put(searchUserName, best); + Set postTxnDuplicates = getPostTxnDuplicates(); + postTxnDuplicates.addAll(refs); } + /** * Process clean up any duplicates that were flagged during the transaction. */ @@ -387,34 +341,31 @@ public class PersonServiceImpl public void afterCommit() { // Get the duplicates in a form that can be read by the transaction work anonymous instance - final Map postTxnDuplicates = getPostTxnDuplicates(); + final Set postTxnDuplicates = getPostTxnDuplicates(); RetryingTransactionCallback processDuplicateWork = new RetryingTransactionCallback() { public Object execute() throws Throwable { - for (Map.Entry entry : postTxnDuplicates.entrySet()) + + if (duplicateMode.equalsIgnoreCase(SPLIT)) { - String username = entry.getKey(); - NodeRef best = entry.getValue(); - if (duplicateMode.equalsIgnoreCase(SPLIT)) + split(postTxnDuplicates); + s_logger.info("Split duplicate person objects"); + } + else if (duplicateMode.equalsIgnoreCase(DELETE)) + { + delete(postTxnDuplicates); + s_logger.info("Deleted duplicate person objects"); + } + else + { + if (s_logger.isDebugEnabled()) { - split(username, best); - s_logger.info("Split duplicate person objects for uid " + username); - } - else if (duplicateMode.equalsIgnoreCase(DELETE)) - { - delete(username, best); - s_logger.info("Deleted duplicate person objects for uid " + username); - } - else - { - if (s_logger.isDebugEnabled()) - { - s_logger.debug("Duplicate person objects exist for uid " + username); - } + s_logger.debug("Duplicate person objects exist"); } } + // Done return null; } @@ -422,210 +373,81 @@ public class PersonServiceImpl transactionService.getRetryingTransactionHelper().doInTransaction(processDuplicateWork, false, true); } - private void delete(String searchUserName, NodeRef best) + private void delete(Set toDelete) { - SearchParameters sp = new SearchParameters(); - sp.setLanguage(SearchService.LANGUAGE_LUCENE); - sp.setQuery("@cm\\:userName:\"" + searchUserName + "\""); - sp.addStore(tenantService.getName(storeRef)); - sp.excludeDataInTheCurrentTransaction(false); - - ResultSet rs = null; - - try + for (NodeRef nodeRef : toDelete) { - rs = searchService.query(sp); - - for (ResultSetRow row : rs) - { - NodeRef nodeRef = row.getNodeRef(); - // Do not delete the best - if ((!best.equals(nodeRef)) && (nodeService.exists(nodeRef))) - { - String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty( - nodeRef, ContentModel.PROP_USERNAME)); - - if (userNamesAreCaseSensitive) - { - if (realUserName.equals(searchUserName)) - { - nodeService.deleteNode(nodeRef); - } - } - else - { - if (realUserName.equalsIgnoreCase(searchUserName)) - { - nodeService.deleteNode(nodeRef); - } - } - } - } + nodeService.deleteNode(nodeRef); } - finally - { - if (rs != null) - { - rs.close(); - } - } - } - private void split(String searchUserName, NodeRef best) + private void split(Set toSplit) { - SearchParameters sp = new SearchParameters(); - sp.setLanguage(SearchService.LANGUAGE_LUCENE); - sp.setQuery("@cm\\:userName:\"" + searchUserName + "\""); - sp.addStore(tenantService.getName(storeRef)); - sp.excludeDataInTheCurrentTransaction(false); - - ResultSet rs = null; - - try + for (NodeRef nodeRef : toSplit) { - rs = searchService.query(sp); - - for (ResultSetRow row : rs) - { - NodeRef nodeRef = row.getNodeRef(); - // Do not delete the best - if ((!best.equals(nodeRef)) && (nodeService.exists(nodeRef))) - { - String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty( - nodeRef, ContentModel.PROP_USERNAME)); - - if (userNamesAreCaseSensitive) - { - if (realUserName.equals(searchUserName)) - { - nodeService.setProperty(nodeRef, ContentModel.PROP_USERNAME, searchUserName - + "(" + GUID.generate() + ")"); - } - } - else - { - if (realUserName.equalsIgnoreCase(searchUserName)) - { - nodeService.setProperty(nodeRef, ContentModel.PROP_USERNAME, searchUserName - + GUID.generate()); - } - } - } - } + String userName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME)); + nodeService.setProperty(nodeRef, ContentModel.PROP_USERNAME, userName + GUID.generate()); } - finally - { - if (rs != null) - { - rs.close(); - } - } - } - private NodeRef findBest(String searchUserName) + private NodeRef findBest(List refs) { - SearchParameters sp = new SearchParameters(); - sp.setLanguage(SearchService.LANGUAGE_LUCENE); - sp.setQuery("@cm\\:userName:\"" + searchUserName + "\""); - sp.addStore(tenantService.getName(storeRef)); - sp.excludeDataInTheCurrentTransaction(false); if (lastIsBest) { - sp.addSort(SearchParameters.SORT_IN_DOCUMENT_ORDER_DESCENDING); + Collections.sort(refs, new CreationDateComparator(nodeService, false)); } else { - sp.addSort(SearchParameters.SORT_IN_DOCUMENT_ORDER_ASCENDING); + Collections.sort(refs, new CreationDateComparator(nodeService, true)); } - ResultSet rs = null; - NodeRef fallBack = null; - try + for (NodeRef nodeRef : refs) { - rs = searchService.query(sp); - - for (ResultSetRow row : rs) + if (fallBack == null) { - NodeRef nodeRef = row.getNodeRef(); - if (fallBack == null) - { - fallBack = nodeRef; - } - // Do not delete the best - if (nodeService.exists(nodeRef)) - { - String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty( - nodeRef, ContentModel.PROP_USERNAME)); + fallBack = nodeRef; + } - if (userNamesAreCaseSensitive) - { - if (realUserName.equals(searchUserName)) - { - if (includeAutoCreated || !wasAutoCreated(nodeRef, searchUserName)) - { - return nodeRef; - } - } - } - else - { - if (realUserName.equalsIgnoreCase(searchUserName)) - { - if (includeAutoCreated || !wasAutoCreated(nodeRef, searchUserName)) - { - return nodeRef; - } - } - } - } - } - } - finally - { - if (rs != null) - { - rs.close(); + if (includeAutoCreated || !wasAutoCreated(nodeRef)) + { + return nodeRef; } } + return fallBack; } - private boolean wasAutoCreated(NodeRef nodeRef, String userName) + private boolean wasAutoCreated(NodeRef nodeRef) { - String testString = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, - ContentModel.PROP_FIRSTNAME)); + String userName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME)); + + String testString = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_FIRSTNAME)); if ((testString == null) || !testString.equals(userName)) { return false; } - testString = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, - ContentModel.PROP_LASTNAME)); + testString = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_LASTNAME)); if ((testString == null) || !testString.equals("")) { return false; } - testString = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, - ContentModel.PROP_EMAIL)); + testString = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_EMAIL)); if ((testString == null) || !testString.equals("")) { return false; } - testString = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, - ContentModel.PROP_ORGID)); + testString = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_ORGID)); if ((testString == null) || !testString.equals("")) { return false; } - testString = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, - ContentModel.PROP_HOME_FOLDER_PROVIDER)); + testString = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_HOME_FOLDER_PROVIDER)); if ((testString == null) || !testString.equals(defaultHomeFolderProvider)) { return false; @@ -661,8 +483,7 @@ public class PersonServiceImpl } else { - String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty( - personNode, ContentModel.PROP_USERNAME)); + String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personNode, ContentModel.PROP_USERNAME)); properties.put(ContentModel.PROP_USERNAME, realUserName); } @@ -698,32 +519,41 @@ public class PersonServiceImpl public NodeRef createPerson(Map properties) { - String userName = DefaultTypeConverter.INSTANCE.convert(String.class, properties - .get(ContentModel.PROP_USERNAME)); + String userName = DefaultTypeConverter.INSTANCE.convert(String.class, properties.get(ContentModel.PROP_USERNAME)); tenantService.checkDomainUser(userName); properties.put(ContentModel.PROP_USERNAME, userName); - properties.put(ContentModel.PROP_SIZE_CURRENT, 0L); - return nodeService.createNode(getPeopleContainer(), ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_PERSON, + return nodeService.createNode( + getPeopleContainer(), + ContentModel.ASSOC_CHILDREN, + QName.createQName("cm", userName, namespacePrefixResolver), ContentModel.TYPE_PERSON, properties).getChildRef(); } public NodeRef getPeopleContainer() { NodeRef rootNodeRef = nodeService.getRootNode(tenantService.getName(storeRef)); - List results = searchService.selectNodes(rootNodeRef, PEOPLE_FOLDER, null, namespacePrefixResolver, - false); - if (results.size() == 0) + List children = nodeService.getChildAssocs(rootNodeRef, RegexQNamePattern.MATCH_ALL, QName.createQName(SYSTEM_FOLDER_SHORT_QNAME, namespacePrefixResolver)); + + if (children.size() == 0) { - throw new AlfrescoRuntimeException("Required people system path not found: " + PEOPLE_FOLDER); + throw new AlfrescoRuntimeException("Required people system path not found: " + SYSTEM_FOLDER_SHORT_QNAME); } - else + + NodeRef systemNodeRef = children.get(0).getChildRef(); + + children = nodeService.getChildAssocs(systemNodeRef, RegexQNamePattern.MATCH_ALL, QName.createQName(PEOPLE_FOLDER_SHORT_QNAME, namespacePrefixResolver)); + + if (children.size() == 0) { - return results.get(0); + throw new AlfrescoRuntimeException("Required people system path not found: " + PEOPLE_FOLDER_SHORT_QNAME); } + + NodeRef peopleNodeRef = children.get(0).getChildRef(); + return peopleNodeRef; } public void deletePerson(String userName) @@ -749,39 +579,9 @@ public class PersonServiceImpl public Set getAllPeople() { - SearchParameters sp = new SearchParameters(); - sp.setLanguage(SearchService.LANGUAGE_LUCENE); - sp.setQuery("TYPE:\"" + ContentModel.TYPE_PERSON + "\""); - sp.addStore(tenantService.getName(storeRef)); - sp.excludeDataInTheCurrentTransaction(false); - - LinkedHashSet nodes = new LinkedHashSet(); - ResultSet rs = null; - - try - { - rs = searchService.query(sp); - - for (ResultSetRow row : rs) - { - - NodeRef nodeRef = row.getNodeRef(); - if (nodeService.exists(nodeRef)) - { - nodes.add(nodeRef); - } - } - } - finally - { - if (rs != null) - { - rs.close(); - } - } - return nodes; + return personDao.getAllPeople(); } - + public Set getPeopleFilteredByProperty(QName propertyKey, Serializable propertyValue) { // check that given property key is defined for content model type 'cm:person' @@ -832,27 +632,31 @@ public class PersonServiceImpl // Policies - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy#onCreateNode(org.alfresco.service.cmr.repository.ChildAssociationRef) */ public void onCreateNode(ChildAssociationRef childAssocRef) { NodeRef personRef = childAssocRef.getChildRef(); - String username = (String)this.nodeService.getProperty(personRef, ContentModel.PROP_USERNAME); + String username = (String) this.nodeService.getProperty(personRef, ContentModel.PROP_USERNAME); this.personCache.put(username, personRef); } - - /* (non-Javadoc) + + /* + * (non-Javadoc) + * * @see org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy#beforeDeleteNode(org.alfresco.service.cmr.repository.NodeRef) */ public void beforeDeleteNode(NodeRef nodeRef) { - String username = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME); + String username = (String) this.nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME); this.personCache.remove(username); } // IOC Setters - + public void setCreateMissingPeople(boolean createMissingPeople) { this.createMissingPeople = createMissingPeople; @@ -913,10 +717,52 @@ public class PersonServiceImpl NodeRef nodeRef = getPersonOrNull(caseSensitiveUserName); if ((nodeRef != null) && nodeService.exists(nodeRef)) { - String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, - ContentModel.PROP_USERNAME)); + String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME)); return realUserName; } return null; } + + public static class CreationDateComparator implements Comparator + { + private NodeService nodeService; + + boolean ascending; + + CreationDateComparator(NodeService nodeService, boolean ascending) + { + this.nodeService = nodeService; + this.ascending = ascending; + } + + public int compare(NodeRef first, NodeRef second) + { + Date firstDate = DefaultTypeConverter.INSTANCE.convert(Date.class, nodeService.getProperty(first, ContentModel.PROP_CREATED)); + Date secondDate = DefaultTypeConverter.INSTANCE.convert(Date.class, nodeService.getProperty(second, ContentModel.PROP_CREATED)); + + if (firstDate != null) + { + if (secondDate != null) + { + return firstDate.compareTo(secondDate) * (ascending ? 1 : -1); + } + else + { + return ascending ? -1 : 1; + } + } + else + { + if (secondDate != null) + { + return ascending ? 1 : -1; + } + else + { + return 0; + } + } + + } + } } diff --git a/source/java/org/alfresco/repo/security/person/PersonTest.java b/source/java/org/alfresco/repo/security/person/PersonTest.java index 4762173f0c..21b3255eda 100644 --- a/source/java/org/alfresco/repo/security/person/PersonTest.java +++ b/source/java/org/alfresco/repo/security/person/PersonTest.java @@ -174,6 +174,8 @@ public class PersonTest extends BaseSpringTest public void testCreateMissingPeople2() { + System.out.print(personService.getAllPeople()); + personService.setCreateMissingPeople(false); assertFalse(personService.createMissingPeople()); @@ -184,6 +186,13 @@ public class PersonTest extends BaseSpringTest assertNotNull(nodeRef); testProperties(nodeRef, "andy", "andy", "", "", ""); + nodeRef = personService.getPerson("andy"); + testProperties(nodeRef, "andy", "andy", "", "", ""); + + nodeRef = personService.getPerson("Andy"); + testProperties(nodeRef, "andy", "andy", "", "", ""); + + assertEquals(nodeRef, personService.getPerson("Andy")); nodeRef = personService.getPerson("Andy"); assertNotNull(nodeRef); if (personService.getUserIdentifier("Andy").equals("Andy")) diff --git a/source/java/org/alfresco/service/cmr/repository/EntityRef.java b/source/java/org/alfresco/service/cmr/repository/EntityRef.java index afb6353180..0ebd703c7e 100644 --- a/source/java/org/alfresco/service/cmr/repository/EntityRef.java +++ b/source/java/org/alfresco/service/cmr/repository/EntityRef.java @@ -24,6 +24,8 @@ */ package org.alfresco.service.cmr.repository; +import java.io.Serializable; + /** * A marker interface for entity reference classes. *

@@ -34,6 +36,6 @@ package org.alfresco.service.cmr.repository; * * @author Derek Hulley */ -public interface EntityRef +public interface EntityRef extends Serializable { }