diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 422ea4a933..f28cd1b9db 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -141,6 +141,7 @@ + diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index ca3a2d06f4..937c81c92a 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -1307,6 +1307,10 @@ + + + + diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-RepoTables.sql b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-RepoTables.sql index 7fe6f2a93c..7e4b49fad3 100644 --- a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-RepoTables.sql +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-RepoTables.sql @@ -23,6 +23,15 @@ CREATE TABLE alf_applied_patch PRIMARY KEY (id) ) ENGINE=InnoDB; +CREATE TABLE alf_locale +( + id BIGINT NOT NULL AUTO_INCREMENT, + version BIGINT NOT NULL, + locale_str VARCHAR(20) NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY locale_str (locale_str) +) ENGINE=InnoDB; + CREATE TABLE alf_namespace ( id BIGINT NOT NULL AUTO_INCREMENT, @@ -196,6 +205,7 @@ CREATE TABLE alf_node transaction_id BIGINT NOT NULL, node_deleted bit NOT NULL, type_qname_id BIGINT NOT NULL, + locale_id BIGINT NOT NULL, acl_id BIGINT, audit_creator VARCHAR(255), audit_created VARCHAR(30), @@ -209,10 +219,12 @@ CREATE TABLE alf_node KEY fk_alf_node_txn (transaction_id), KEY fk_alf_node_store (store_id), KEY fk_alf_node_tqn (type_qname_id), + KEY fk_alf_node_loc (locale_id), CONSTRAINT fk_alf_node_acl FOREIGN KEY (acl_id) REFERENCES alf_access_control_list (id), CONSTRAINT fk_alf_node_store FOREIGN KEY (store_id) REFERENCES alf_store (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_txn FOREIGN KEY (transaction_id) REFERENCES alf_transaction (id), + CONSTRAINT fk_alf_node_loc FOREIGN KEY (locale_id) REFERENCES alf_locale (id) ) ENGINE=InnoDB; ALTER TABLE alf_store ADD INDEX fk_alf_store_root (root_node_id), ADD CONSTRAINT fk_alf_store_root FOREIGN KEY (root_node_id) REFERENCES alf_node (id); @@ -245,15 +257,6 @@ CREATE TABLE alf_child_assoc CONSTRAINT fk_alf_cass_tqn FOREIGN KEY (type_qname_id) REFERENCES alf_qname (id) ) ENGINE=InnoDB; -CREATE TABLE alf_locale -( - id BIGINT NOT NULL AUTO_INCREMENT, - version BIGINT NOT NULL, - locale_str VARCHAR(20) NOT NULL, - PRIMARY KEY (id), - UNIQUE KEY locale_str (locale_str) -) ENGINE=InnoDB; - CREATE TABLE alf_node_aspects ( node_id BIGINT NOT NULL, diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-RepoTables.sql b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-RepoTables.sql index 5b04d17daf..0c2ca1fc38 100644 --- a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-RepoTables.sql +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-RepoTables.sql @@ -23,6 +23,16 @@ CREATE TABLE alf_applied_patch PRIMARY KEY (id) ); +CREATE SEQUENCE alf_locale_seq START WITH 1 INCREMENT BY 1; +CREATE TABLE alf_locale +( + id INT8 NOT NULL, + version INT8 NOT NULL, + locale_str VARCHAR(20) NOT NULL, + PRIMARY KEY (id) +); +CREATE UNIQUE INDEX locale_str ON alf_locale (locale_str); + CREATE SEQUENCE alf_namespace_seq START WITH 1 INCREMENT BY 1; CREATE TABLE alf_namespace ( @@ -210,6 +220,7 @@ CREATE TABLE alf_node transaction_id INT8 NOT NULL, node_deleted BOOL NOT NULL, type_qname_id INT8 NOT NULL, + locale_id INT8 NOT NULL, acl_id INT8, audit_creator VARCHAR(255), audit_created VARCHAR(30), @@ -220,7 +231,8 @@ CREATE TABLE alf_node CONSTRAINT fk_alf_node_acl FOREIGN KEY (acl_id) REFERENCES alf_access_control_list (id), CONSTRAINT fk_alf_node_store FOREIGN KEY (store_id) REFERENCES alf_store (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_txn FOREIGN KEY (transaction_id) REFERENCES alf_transaction (id), + CONSTRAINT fk_alf_node_loc FOREIGN KEY (locale_id) REFERENCES alf_locale (id) ); CREATE UNIQUE INDEX store_id ON alf_node (store_id, uuid); CREATE INDEX idx_alf_node_del ON alf_node (node_deleted); @@ -228,6 +240,7 @@ CREATE INDEX fk_alf_node_acl ON alf_node (acl_id); CREATE INDEX fk_alf_node_txn ON alf_node (transaction_id); CREATE INDEX fk_alf_node_store ON alf_node (store_id); CREATE INDEX fk_alf_node_tqn ON alf_node (type_qname_id); +CREATE INDEX fk_alf_node_loc ON alf_node (locale_id); CREATE INDEX fk_alf_store_root ON alf_store (root_node_id); ALTER TABLE alf_store ADD CONSTRAINT fk_alf_store_root FOREIGN KEY (root_node_id) REFERENCES alf_node (id); @@ -261,16 +274,6 @@ CREATE INDEX fk_alf_cass_qnns ON alf_child_assoc (qname_ns_id); CREATE INDEX idx_alf_cass_qncrc ON alf_child_assoc (qname_crc, type_qname_id, parent_node_id); CREATE INDEX idx_alf_cass_pri ON alf_child_assoc (parent_node_id, is_primary, child_node_id); -CREATE SEQUENCE alf_locale_seq START WITH 1 INCREMENT BY 1; -CREATE TABLE alf_locale -( - id INT8 NOT NULL, - version INT8 NOT NULL, - locale_str VARCHAR(20) NOT NULL, - PRIMARY KEY (id) -); -CREATE UNIQUE INDEX locale_str ON alf_locale (locale_str); - CREATE TABLE alf_node_aspects ( node_id INT8 NOT NULL, diff --git a/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.MySQLInnoDBDialect/Node-Locale.sql b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.MySQLInnoDBDialect/Node-Locale.sql new file mode 100644 index 0000000000..b222e08e8a --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.MySQLInnoDBDialect/Node-Locale.sql @@ -0,0 +1,30 @@ +-- +-- Title: Add 'locale_id' column to 'alf_node' +-- Database: MySQL +-- Since: V4.0 Schema 5010 +-- Author: Derek Hulley +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +--ASSIGN:def_locale_id=id +SELECT id FROM alf_locale WHERE locale_str = '.default'; + +-- Add the column, using a default to fill +ALTER TABLE alf_node + ADD COLUMN locale_id INT8 NOT NULL DEFAULT ${def_locale_id} AFTER type_qname_id, + ADD KEY fk_alf_node_loc (locale_id), + ADD CONSTRAINT fk_alf_node_loc FOREIGN KEY (locale_id) REFERENCES alf_locale (id) +; + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V4.0-Node-Locale'; +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-V4.0-Node-Locale', 'Manually executed script upgrade V4.0: Add locale_id column to alf_node', + 0, 5009, -1, 5010, null, 'UNKNOWN', ${TRUE}, ${TRUE}, 'Script completed' + ); \ No newline at end of file diff --git a/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.PostgreSQLDialect/Node-Locale.sql b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.PostgreSQLDialect/Node-Locale.sql new file mode 100644 index 0000000000..a617442cd1 --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.PostgreSQLDialect/Node-Locale.sql @@ -0,0 +1,32 @@ +-- +-- Title: Add 'locale_id' column to 'alf_node' +-- Database: PostgreSQL +-- Since: V4.0 Schema 5010 +-- Author: Derek Hulley +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +--ASSIGN:def_locale_id=id +SELECT id FROM alf_locale WHERE locale_str = '.default'; + +-- Add the column, using a default to fill +ALTER TABLE alf_node + ADD COLUMN locale_id INT8 NOT NULL DEFAULT ${def_locale_id} +; +ALTER TABLE alf_node + ADD CONSTRAINT fk_alf_node_loc FOREIGN KEY (locale_id) REFERENCES alf_locale (id) +; +CREATE INDEX fk_alf_node_loc ON alf_node (locale_id); + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V4.0-Node-Locale'; +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-V4.0-Node-Locale', 'Manually executed script upgrade V4.0: Add locale_id column to alf_node', + 0, 5009, -1, 5010, null, 'UNKNOWN', ${TRUE}, ${TRUE}, 'Script completed' + ); \ No newline at end of file diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml index e36c46f0a8..c3472c9710 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml @@ -64,6 +64,7 @@ + @@ -229,7 +230,7 @@ insert into alf_node ( - version, store_id, uuid, type_qname_id, acl_id, node_deleted, transaction_id + version, store_id, uuid, type_qname_id, locale_id, acl_id, node_deleted, transaction_id , audit_creator, audit_created, audit_modifier, audit_modified, @@ -238,7 +239,7 @@ ) values ( - #{version}, #{store.id}, #{uuid}, #{typeQNameId}, #{aclId,jdbcType=BIGINT}, #{deleted}, #{transaction.id} + #{version}, #{store.id}, #{uuid}, #{typeQNameId}, #{localeId}, #{aclId,jdbcType=BIGINT}, #{deleted}, #{transaction.id} , #{auditableProperties.auditCreator,jdbcType=VARCHAR}, #{auditableProperties.auditCreated,jdbcType=VARCHAR}, #{auditableProperties.auditModifier,jdbcType=VARCHAR}, #{auditableProperties.auditModified,jdbcType=VARCHAR}, @@ -250,7 +251,7 @@ insert into alf_node ( - id, version, store_id, uuid, type_qname_id, acl_id, node_deleted, transaction_id + id, version, store_id, uuid, type_qname_id, locale_id, acl_id, node_deleted, transaction_id , audit_creator, audit_created, audit_modifier, audit_modified, @@ -259,7 +260,7 @@ ) values ( - #{id}, #{version}, #{store.id}, #{uuid}, #{typeQNameId}, #{aclId,jdbcType=BIGINT}, #{deleted}, #{transaction.id} + #{id}, #{version}, #{store.id}, #{uuid}, #{typeQNameId}, #{localeId}, #{aclId,jdbcType=BIGINT}, #{deleted}, #{transaction.id} , #{auditableProperties.auditCreator,jdbcType=VARCHAR}, #{auditableProperties.auditCreated,jdbcType=VARCHAR}, #{auditableProperties.auditModifier,jdbcType=VARCHAR}, #{auditableProperties.auditModified,jdbcType=VARCHAR}, @@ -327,7 +328,6 @@ ?, ?, ?, ?, ?, ?, ?, ? ) - @@ -369,6 +369,7 @@ , store_id = #{store.id} , uuid = #{uuid} , type_qname_id = #{typeQNameId} + , locale_id = #{localeId} , acl_id = #{aclId,jdbcType=BIGINT} , node_deleted = #{deleted} , transaction_id = #{transaction.id} @@ -604,6 +605,7 @@ store.identifier as identifier, node.uuid as uuid, node.type_qname_id as type_qname_id, + node.locale_id as locale_id, node.acl_id as acl_id, node.node_deleted as node_deleted, txn.id as txn_id, diff --git a/config/alfresco/model/publishingModel.xml b/config/alfresco/model/publishingModel.xml index e8223ded08..063431a793 100644 --- a/config/alfresco/model/publishingModel.xml +++ b/config/alfresco/model/publishingModel.xml @@ -83,7 +83,7 @@ sys:base - + false false @@ -171,6 +171,14 @@ Comment for the event d:text + + Channel to publish to + d:text + + + The Id of the associated Publishing Event Workflow Instance + d:text + diff --git a/config/alfresco/model/systemModel.xml b/config/alfresco/model/systemModel.xml index 3e2792aaf7..81846d38ee 100644 --- a/config/alfresco/model/systemModel.xml +++ b/config/alfresco/model/systemModel.xml @@ -26,6 +26,7 @@ Base sys:referenceable + sys:localized diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 56d8e8b214..72db6397cb 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -2860,4 +2860,15 @@ + + + + + + + + classpath:alfresco/dbscripts/upgrade/4.0/${db.script.dialect}/Node-Locale.sql + + + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index f30ba929a4..9d859b5c64 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -149,7 +149,7 @@ system.acl.maxPermissionCheckTimeMillis=10000 # The maximum number of search results to perform permission checks against system.acl.maxPermissionChecks=1000 -# The maximum number of filefolder list results (note: also affected by system.acl.maxPermissionCheckTimeMillis) +# The maximum number of filefolder list results system.filefolderservice.defaultListMaxResults=5000 diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index c1358868da..875bcafa63 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=5009 +version.schema=5010 diff --git a/config/alfresco/web-publishing-context.xml b/config/alfresco/web-publishing-context.xml index e09611cc70..723742119c 100644 --- a/config/alfresco/web-publishing-context.xml +++ b/config/alfresco/web-publishing-context.xml @@ -48,7 +48,7 @@ - + @@ -61,6 +61,8 @@ + + @@ -81,10 +83,13 @@ + + + diff --git a/config/test/alfresco/test-web-publishing-context.xml b/config/test/alfresco/test-web-publishing-context.xml index b6bfa5e32b..4108c9140b 100644 --- a/config/test/alfresco/test-web-publishing-context.xml +++ b/config/test/alfresco/test-web-publishing-context.xml @@ -45,10 +45,15 @@ - + + + + + + diff --git a/source/java/org/alfresco/jcr/dictionary/DataTypeMap.java b/source/java/org/alfresco/jcr/dictionary/DataTypeMap.java index 6f08d11a9b..2002f76892 100644 --- a/source/java/org/alfresco/jcr/dictionary/DataTypeMap.java +++ b/source/java/org/alfresco/jcr/dictionary/DataTypeMap.java @@ -56,6 +56,7 @@ public class DataTypeMap dataTypeToPropertyType.put(DataTypeDefinition.NODE_REF, PropertyType.REFERENCE); dataTypeToPropertyType.put(DataTypeDefinition.PATH, PropertyType.PATH); dataTypeToPropertyType.put(DataTypeDefinition.ANY, PropertyType.UNDEFINED); + dataTypeToPropertyType.put(DataTypeDefinition.LOCALE, PropertyType.STRING); } /** Map of JCR Property Type to Alfresco Data Type */ diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisService.java b/source/java/org/alfresco/opencmis/AlfrescoCmisService.java index f133f64b75..8ca4f111b4 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisService.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisService.java @@ -597,11 +597,10 @@ public class AlfrescoCmisService extends AbstractCmisService } } - if (pageOfNodeInfos.hasMoreItems() != null) - { - result.setHasMoreItems(pageOfNodeInfos.hasMoreItems()); - } + /// has more ? + result.setHasMoreItems(pageOfNodeInfos.hasMoreItems()); + // total count ? Pair totalCounts = pageOfNodeInfos.getTotalResultCount(); if (totalCounts != null) { diff --git a/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanerScalabilityRunner.java b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanerScalabilityRunner.java index 4562c220b3..d748b1dfac 100644 --- a/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanerScalabilityRunner.java +++ b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanerScalabilityRunner.java @@ -47,6 +47,7 @@ import org.alfresco.util.TempFileProvider; import org.alfresco.util.VmShutdownListener; import org.apache.commons.lang.mutable.MutableInt; import org.springframework.context.ApplicationContext; +import org.springframework.extensions.surf.util.I18NUtil; /** * Loads the repository up with orphaned content and then runs the cleaner. @@ -266,6 +267,7 @@ public class ContentStoreCleanerScalabilityRunner extends Repository storeRef, null, ContentModel.TYPE_CONTENT, + I18NUtil.getLocale(), null, null); Long nodeId = assoc.getChildNode().getId(); diff --git a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java index 2daa06bd46..44036d2da7 100644 --- a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java @@ -29,6 +29,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.SortedSet; @@ -77,6 +78,7 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef.Status; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.ReadOnlyServerException; import org.alfresco.service.transaction.TransactionService; @@ -655,7 +657,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO Long aclId = aclDAO.createAccessControlList(); // Create a root node - NodeEntity rootNode = newNodeImpl(store, null, ContentModel.TYPE_STOREROOT, aclId, false, null); + NodeEntity rootNode = newNodeImpl(store, null, ContentModel.TYPE_STOREROOT, null, aclId, false, null); Long rootNodeId = rootNode.getId(); addNodeAspects(rootNodeId, Collections.singleton(ContentModel.ASPECT_ROOT)); @@ -859,6 +861,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO return node.getAclId(); } + @Override public ChildAssocEntity newNode( Long parentNodeId, QName assocTypeQName, @@ -866,6 +869,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO StoreRef storeRef, String uuid, QName nodeTypeQName, + Locale nodeLocale, String childNodeName, Map auditableProperties) throws InvalidTypeException { @@ -905,7 +909,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO // Get the store StoreEntity store = getStoreNotNull(storeRef); // Create the node (it is not a root node) - NodeEntity node = newNodeImpl(store, uuid, nodeTypeQName, childAclId, false, auditableProps); + NodeEntity node = newNodeImpl(store, uuid, nodeTypeQName, nodeLocale, childAclId, false, auditableProps); Long nodeId = node.getId(); // Protect the node's cm:auditable if it was explicitly set @@ -947,6 +951,8 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO /** * @param uuid the node UUID, or null to auto-generate + * @param nodeTypeQName the node's type + * @param nodeLocale the node's locale or null to use the default locale * @param aclId an ACL ID if available * @param auditableProps null to auto-generate or provide a value to explicitly set * @param deleted true to create an already-deleted node (used for leaving trails of moved nodes) @@ -955,6 +961,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO StoreEntity store, String uuid, QName nodeTypeQName, + Locale nodeLocale, Long aclId, boolean deleted, AuditablePropertiesEntity auditableProps) throws InvalidTypeException @@ -974,6 +981,9 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO // QName Long typeQNameId = qnameDAO.getOrCreateQName(nodeTypeQName).getFirst(); node.setTypeQNameId(typeQNameId); + // Locale + final Long localeId = localeDAO.getOrCreateLocalePair(nodeLocale).getFirst(); + node.setLocaleId(localeId); // ACL (may be null) node.setAclId(aclId); // Deleted @@ -1163,7 +1173,8 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO return assocPair; } - public void updateNode(Long nodeId, StoreRef storeRef, String uuid, QName nodeTypeQName) + @Override + public void updateNode(Long nodeId, StoreRef storeRef, String uuid, QName nodeTypeQName, Locale nodeLocale) { // Get the existing node; we need to check for a change in store or UUID Node oldNode = getNodeNotNull(nodeId); @@ -1176,10 +1187,23 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO { uuid = oldNode.getUuid(); } + final Long nodeTypeQNameId; if (nodeTypeQName == null) { - Long nodeTypeQNameId = oldNode.getTypeQNameId(); - nodeTypeQName = qnameDAO.getQName(nodeTypeQNameId).getSecond(); + nodeTypeQNameId = oldNode.getTypeQNameId(); + } + else + { + nodeTypeQNameId = qnameDAO.getOrCreateQName(nodeTypeQName).getFirst(); + } + final Long nodeLocaleId; + if (nodeLocale == null) + { + nodeLocaleId = oldNode.getLocaleId(); + } + else + { + nodeLocaleId = localeDAO.getOrCreateLocalePair(nodeLocale).getFirst(); } // Wrap all the updates into one @@ -1207,33 +1231,25 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO nodeUpdate.setUuid(oldNode.getUuid()); // Need node reference } // TypeQName (if necessary) - Long nodeTypeQNameId = qnameDAO.getOrCreateQName(nodeTypeQName).getFirst(); if (!nodeTypeQNameId.equals(oldNode.getTypeQNameId())) { nodeUpdate.setTypeQNameId(nodeTypeQNameId); nodeUpdate.setUpdateTypeQNameId(true); } + // Locale (if necessary) + if (!nodeLocaleId.equals(oldNode.getLocaleId())) + { + nodeUpdate.setLocaleId(nodeLocaleId); + nodeUpdate.setUpdateLocaleId(true); + } updateNodeImpl(oldNode, nodeUpdate); } /** * Updates the node's transaction and cm:auditable properties only. - * - * @see #touchNodeImpl(Long, AuditablePropertiesEntity) */ private void touchNodeImpl(Long nodeId) - { - touchNodeImpl(nodeId, null); - } - /** - * Updates the node's transaction and cm:auditable properties only. - * - * @param auditableProps optionally override the cm:auditable values - * - * @see #updateNodeImpl(NodeEntity, NodeUpdateEntity) - */ - private void touchNodeImpl(Long nodeId, AuditablePropertiesEntity auditableProps) { Node node = null; try @@ -1248,10 +1264,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO } NodeUpdateEntity nodeUpdate = new NodeUpdateEntity(); nodeUpdate.setId(nodeId); - if (auditableProps != null) - { - nodeUpdate.setAuditableProperties(auditableProps); - } + // Update it updateNodeImpl(node, nodeUpdate); } @@ -1290,6 +1303,10 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO { nodeUpdate.setTypeQNameId(oldNode.getTypeQNameId()); } + if (!nodeUpdate.isUpdateLocaleId()) + { + nodeUpdate.setLocaleId(oldNode.getLocaleId()); + } if (!nodeUpdate.isUpdateAclId()) { nodeUpdate.setAclId(oldNode.getAclId()); @@ -1392,7 +1409,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO { Long liveNodeId = liveNode.getId(); String liveNodeUuid = GUID.generate(); - updateNode(liveNodeId, null, liveNodeUuid, null); + updateNode(liveNodeId, null, liveNodeUuid, null, null); } NodeEntity deletedNode = selectNodeByNodeRef(targetNodeRef, true); // Only look for deleted nodes if (deletedNode != null) @@ -1443,7 +1460,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO { StoreEntity oldStore = oldNode.getStore(); String oldUuid = oldNode.getUuid(); - newNodeImpl(oldStore, oldUuid, ContentModel.TYPE_CMOBJECT, null, true, null); + newNodeImpl(oldStore, oldUuid, ContentModel.TYPE_CMOBJECT, null, null, true, null); } // Ensure that cm:auditable values are propagated, if required @@ -1731,6 +1748,8 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO Node node = getNodeNotNull(nodeId); // Handle sys:referenceable ReferenceablePropertiesEntity.addReferenceableProperties(node, props); + // Handle sys:localized + LocalizedPropertiesEntity.addLocalizedProperties(localeDAO, node, props); // Handle cm:auditable if (hasNodeAspect(nodeId, ContentModel.ASPECT_AUDITABLE)) { @@ -1770,6 +1789,11 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO Node node = getNodeNotNull(nodeId); value = ReferenceablePropertiesEntity.getReferenceableProperty(node, propertyQName); } + else if (LocalizedPropertiesEntity.isLocalizedProperty(propertyQName)) // sys:localized + { + Node node = getNodeNotNull(nodeId); + value = LocalizedPropertiesEntity.getLocalizedProperty(localeDAO, node, propertyQName); + } else { Map props = getNodePropertiesCached(nodeId); @@ -1811,16 +1835,20 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO { return false; // No point adding nothing } - + + // Get the current node Node node = getNodeNotNull(nodeId); + // Create an update node + NodeUpdateEntity nodeUpdate = new NodeUpdateEntity(); + nodeUpdate.setId(nodeId); + // Copy inbound values newProps = new HashMap(newProps); // Copy cm:auditable - AuditablePropertiesEntity auditableProps = null; if (!policyBehaviourFilter.isEnabled(node.getNodeRef(), ContentModel.ASPECT_AUDITABLE)) { - auditableProps = node.getAuditableProperties(); + AuditablePropertiesEntity auditableProps = node.getAuditableProperties(); if (auditableProps == null) { auditableProps = new AuditablePropertiesEntity(); @@ -1831,13 +1859,35 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO // The behaviour is disabled, but no audit properties were passed in auditableProps = null; } + nodeUpdate.setAuditableProperties(auditableProps); + // We DON'T set the update flag because the update depends on the aspect being enabled, etc. + // nodeUpdate.setUpdateAuditableProperties(true); } // Remove cm:auditable newProps.keySet().removeAll(AuditablePropertiesEntity.getAuditablePropertyQNames()); + + // Check if the sys:localized property is being changed + Long oldNodeLocaleId = node.getLocaleId(); + Locale newLocale = DefaultTypeConverter.INSTANCE.convert( + Locale.class, + newProps.get(ContentModel.PROP_LOCALE)); + if (newLocale != null) + { + Long newNodeLocaleId = localeDAO.getOrCreateLocalePair(newLocale).getFirst(); + if (!newNodeLocaleId.equals(oldNodeLocaleId)) + { + nodeUpdate.setLocaleId(newNodeLocaleId); + nodeUpdate.setUpdateLocaleId(true); + } + } + // else: a 'null' new locale is completely ignored. This is the behaviour we choose. + + // Remove sys:localized + LocalizedPropertiesEntity.removeLocalizedProperties(node, newProps); + // Remove sys:referenceable ReferenceablePropertiesEntity.removeReferenceableProperties(node, newProps); - // Load the current properties. // This means that we have to go to the DB during cold-write operations, // but usually a write occurs after a node has been fetched of viewed in @@ -1910,10 +1960,11 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO } } - boolean updated = propsToDelete.size() > 0 || propsToAdd.size() > 0; + boolean modifyProps = propsToDelete.size() > 0 || propsToAdd.size() > 0; + boolean updated = modifyProps || nodeUpdate.isUpdateAnything(); // Touch to bring into current txn - if (updated) + if (modifyProps) { // Clean up content properties try @@ -1976,9 +2027,9 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO setNodePropertiesCached(nodeId, propsToCache); } // Touch to bring into current transaction - if (updated || auditableProps != null) + if (updated) { - touchNodeImpl(nodeId, auditableProps); + updateNodeImpl(node, nodeUpdate); } // Done @@ -1986,8 +2037,9 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO { logger.debug( "Modified node properties: " + nodeId + "\n" + - " Removed: " + propsToDelete + "\n" + - " Added: " + propsToAdd); + " Removed: " + propsToDelete + "\n" + + " Added: " + propsToAdd + "\n" + + " Node Update: " + nodeUpdate); } return updated; } @@ -2030,6 +2082,11 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO { return false; // sys:referenceable properties cannot be removed } + LocalizedPropertiesEntity.removeLocalizedProperties(propertyQNames); + if (propertyQNames.size() == 0) + { + return false; // sys:localized properties cannot be removed + } Set qnameIds = qnameDAO.convertQNamesToIds(propertyQNames, false); int deleteCount = deleteNodeProperties(nodeId, qnameIds); @@ -2126,6 +2183,8 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO Set nodeAspects = getNodeAspectsCached(nodeId); // Nodes are always referenceable nodeAspects.add(ContentModel.ASPECT_REFERENCEABLE); + // Nodes are always localized + nodeAspects.add(ContentModel.ASPECT_LOCALIZED); return nodeAspects; } @@ -2136,6 +2195,11 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO // Nodes are always referenceable return true; } + if (aspectQName.equals(ContentModel.ASPECT_LOCALIZED)) + { + // Nodes are always localized + return true; + } Set nodeAspects = getNodeAspectsCached(nodeId); return nodeAspects.contains(aspectQName); } @@ -2153,6 +2217,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO // Find out what needs adding aspectQNamesToAdd.removeAll(existingAspectQNames); aspectQNamesToAdd.remove(ContentModel.ASPECT_REFERENCEABLE); // Implicit + aspectQNamesToAdd.remove(ContentModel.ASPECT_LOCALIZED); // Implicit if (aspectQNamesToAdd.isEmpty()) { // Nothing to do @@ -2533,7 +2598,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO // node into the current transaction for secondary associations if (!isPrimary) { - updateNode(childNodeId, null, null, null); + updateNode(childNodeId, null, null, null, null); } // Done diff --git a/source/java/org/alfresco/repo/domain/node/LocalizedPropertiesEntity.java b/source/java/org/alfresco/repo/domain/node/LocalizedPropertiesEntity.java new file mode 100644 index 0000000000..486eff57e3 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/node/LocalizedPropertiesEntity.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.node; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.locale.LocaleDAO; +import org.alfresco.service.namespace.QName; + +/** + * Class holding properties associated with the sys:localized aspect. + * This aspect is common enough to warrant direct inclusion on the Node entity. + * + * @author Derek Hulley + * @since 4.0 + */ +public class LocalizedPropertiesEntity +{ + private static final Set LOCALIZED_PROP_QNAMES; + static + { + LOCALIZED_PROP_QNAMES = new HashSet(8); + LOCALIZED_PROP_QNAMES.add(ContentModel.PROP_LOCALE); + } + + /** + * @return Returns true if the property belongs to the sys:localized aspect + */ + public static boolean isLocalizedProperty(QName qname) + { + return LOCALIZED_PROP_QNAMES.contains(qname); + } + + /** + * Remove all {@link ContentModel#ASPECT_LOCALIZED localized} properties + */ + public static void removeLocalizedProperties(Node node, Map properties) + { + properties.keySet().removeAll(LOCALIZED_PROP_QNAMES); + } + + /** + * Remove all {@link ContentModel#ASPECT_LOCALIZED localized} properties + */ + public static void removeLocalizedProperties(Set propertyQNames) + { + propertyQNames.removeAll(LOCALIZED_PROP_QNAMES); + } + + /** + * Adds all {@link ContentModel#ASPECT_LOCALIZED localized} properties. + */ + public static void addLocalizedProperties(LocaleDAO localeDAO, Node node, Map properties) + { + Long localeId = node.getLocaleId(); + Locale locale = localeDAO.getLocalePair(localeId).getSecond(); + properties.put(ContentModel.PROP_LOCALE, locale); + } + + public static Serializable getLocalizedProperty(LocaleDAO localeDAO, Node node, QName qname) + { + if (qname.equals(ContentModel.PROP_LOCALE)) + { + Long localeId = node.getLocaleId(); + return localeDAO.getLocalePair(localeId).getSecond(); + } + throw new IllegalArgumentException("Not sys:localized property: " + qname); + } +} diff --git a/source/java/org/alfresco/repo/domain/node/Node.java b/source/java/org/alfresco/repo/domain/node/Node.java index af05ca621d..2440f3006c 100644 --- a/source/java/org/alfresco/repo/domain/node/Node.java +++ b/source/java/org/alfresco/repo/domain/node/Node.java @@ -30,6 +30,8 @@ public interface Node extends NodeIdAndAclId { public abstract NodeRef getNodeRef(); + public NodeRef.Status getNodeStatus(); + public abstract Pair getNodePair(); public abstract Long getVersion(); @@ -39,6 +41,8 @@ public interface Node extends NodeIdAndAclId public abstract String getUuid(); public abstract Long getTypeQNameId(); + + public abstract Long getLocaleId(); public abstract Boolean getDeleted(); diff --git a/source/java/org/alfresco/repo/domain/node/NodeDAO.java b/source/java/org/alfresco/repo/domain/node/NodeDAO.java index 00c860ccd1..537302468c 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeDAO.java +++ b/source/java/org/alfresco/repo/domain/node/NodeDAO.java @@ -21,6 +21,7 @@ package org.alfresco.repo.domain.node; import java.io.Serializable; import java.util.Collection; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -148,6 +149,7 @@ public interface NodeDAO extends NodeBulkLoader * @param storeRef the store to which the node must belong * @param uuid the node store-unique identifier, or null to assign a GUID * @param nodeTypeQName the type of the node + * @parma nodeLocale the locale of the node * @param childNodeName the cm:name of the child node or null to use the node's UUID * @param auditableProperties a map containing any cm:auditable properties for the node * @return Returns the details of the child association created @@ -161,6 +163,7 @@ public interface NodeDAO extends NodeBulkLoader StoreRef storeRef, String uuid, QName nodeTypeQName, + Locale nodeLocale, String childNodeName, Map auditableProperties/*, Map ownableProperties*/) throws InvalidTypeException; @@ -188,8 +191,9 @@ public interface NodeDAO extends NodeBulkLoader * @param storeRef the new store or null to keep the existing one * @param uuid the new UUID for the node or null to keep it the same * @param nodeTypeQName the new type QName for the node or null to keep the existing one + * @param nodeLocale the new locale for the node or null to keep the existing one */ - public void updateNode(Long nodeId, StoreRef storeRef, String uuid, QName nodeTypeQName); + public void updateNode(Long nodeId, StoreRef storeRef, String uuid, QName nodeTypeQName, Locale nodeLocale); public void setNodeAclId(Long nodeId, Long aclId); diff --git a/source/java/org/alfresco/repo/domain/node/NodeEntity.java b/source/java/org/alfresco/repo/domain/node/NodeEntity.java index adccc4a602..ed37001c87 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeEntity.java +++ b/source/java/org/alfresco/repo/domain/node/NodeEntity.java @@ -36,6 +36,7 @@ public class NodeEntity implements Node private StoreEntity store; private String uuid; private Long typeQNameId; + private Long localeId; private Long aclId; private Boolean deleted; private TransactionEntity transaction; @@ -71,6 +72,7 @@ public class NodeEntity implements Node this.store = node.getStore(); this.uuid = node.getUuid(); this.typeQNameId = node.getTypeQNameId(); + this.localeId = node.getLocaleId(); this.aclId = node.getAclId(); this.deleted = node.getDeleted(); this.transaction = node.getTransaction(); @@ -93,6 +95,7 @@ public class NodeEntity implements Node } sb.append(", uuid=").append(uuid) .append(", typeQNameId=").append(typeQNameId) + .append(", localeId=").append(localeId) .append(", aclId=").append(aclId) .append(", deleted=").append(deleted) .append(", transaction=").append(transaction) @@ -129,22 +132,26 @@ public class NodeEntity implements Node } } + @Override public NodeRef getNodeRef() { return new NodeRef(store.getStoreRef(), uuid); } + @Override public NodeRef.Status getNodeStatus() { NodeRef nodeRef = new NodeRef(store.getStoreRef(), uuid); return new NodeRef.Status(nodeRef, transaction.getChangeTxnId(), transaction.getId(), deleted); } + @Override public Pair getNodePair() { return new Pair(id, getNodeRef()); } + @Override public Long getId() { return id; @@ -156,6 +163,7 @@ public class NodeEntity implements Node this.id = id; } + @Override public Long getVersion() { return version; @@ -167,6 +175,7 @@ public class NodeEntity implements Node this.version = version; } + @Override public StoreEntity getStore() { return store; @@ -178,6 +187,7 @@ public class NodeEntity implements Node this.store = store; } + @Override public String getUuid() { return uuid; @@ -189,6 +199,7 @@ public class NodeEntity implements Node this.uuid = uuid; } + @Override public Long getTypeQNameId() { return typeQNameId; @@ -200,6 +211,18 @@ public class NodeEntity implements Node this.typeQNameId = typeQNameId; } + @Override + public Long getLocaleId() + { + return localeId; + } + + public void setLocaleId(Long localeId) + { + this.localeId = localeId; + } + + @Override public Long getAclId() { return aclId; @@ -211,6 +234,7 @@ public class NodeEntity implements Node this.aclId = aclId; } + @Override public Boolean getDeleted() { return deleted; @@ -222,6 +246,7 @@ public class NodeEntity implements Node this.deleted = deleted; } + @Override public TransactionEntity getTransaction() { return transaction; @@ -233,6 +258,7 @@ public class NodeEntity implements Node this.transaction = transaction; } + @Override public AuditablePropertiesEntity getAuditableProperties() { return auditableProperties; diff --git a/source/java/org/alfresco/repo/domain/node/NodePropertyHelper.java b/source/java/org/alfresco/repo/domain/node/NodePropertyHelper.java index 43ae0c090a..cb54ec363f 100644 --- a/source/java/org/alfresco/repo/domain/node/NodePropertyHelper.java +++ b/source/java/org/alfresco/repo/domain/node/NodePropertyHelper.java @@ -44,7 +44,6 @@ import org.alfresco.util.EqualsHelper; import org.alfresco.util.Pair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.extensions.surf.util.I18NUtil; /** * This class provides services for translating exploded properties diff --git a/source/java/org/alfresco/repo/domain/node/NodeUpdateEntity.java b/source/java/org/alfresco/repo/domain/node/NodeUpdateEntity.java index da8cca8531..435e02671f 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeUpdateEntity.java +++ b/source/java/org/alfresco/repo/domain/node/NodeUpdateEntity.java @@ -30,6 +30,7 @@ public class NodeUpdateEntity extends NodeEntity private boolean updateStore; private boolean updateUuid; private boolean updateTypeQNameId; + private boolean updateLocaleId; private boolean updateAclId; private boolean updateDeleted; private boolean updateTransaction; @@ -48,7 +49,7 @@ public class NodeUpdateEntity extends NodeEntity public boolean isUpdateAnything() { return updateAuditableProperties || updateTransaction || updateDeleted - || updateAclId || updateStore || updateUuid || updateTypeQNameId; + || updateLocaleId || updateAclId || updateStore || updateUuid || updateTypeQNameId; } public boolean isUpdateStore() @@ -81,6 +82,16 @@ public class NodeUpdateEntity extends NodeEntity this.updateTypeQNameId = updateTypeQNameId; } + public boolean isUpdateLocaleId() + { + return updateLocaleId; + } + + public void setUpdateLocaleId(boolean updateLocaleId) + { + this.updateLocaleId = updateLocaleId; + } + public boolean isUpdateAclId() { return updateAclId; diff --git a/source/java/org/alfresco/repo/domain/node/ReferenceablePropertiesEntity.java b/source/java/org/alfresco/repo/domain/node/ReferenceablePropertiesEntity.java index d956b754cd..fac8eec182 100644 --- a/source/java/org/alfresco/repo/domain/node/ReferenceablePropertiesEntity.java +++ b/source/java/org/alfresco/repo/domain/node/ReferenceablePropertiesEntity.java @@ -98,7 +98,6 @@ public class ReferenceablePropertiesEntity public static Serializable getReferenceableProperty(Node node, QName qname) { - Long nodeId = node.getId(); NodeRef nodeRef = node.getNodeRef(); if (qname.equals(ContentModel.PROP_STORE_PROTOCOL)) { @@ -114,7 +113,7 @@ public class ReferenceablePropertiesEntity } else if (qname.equals(ContentModel.PROP_NODE_DBID)) { - return nodeId; + return node.getId(); } throw new IllegalArgumentException("Not sys:referenceable property: " + qname); } diff --git a/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java b/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java index 2de626c4dc..46b753b2cf 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java @@ -480,6 +480,7 @@ public class AclDAOImpl implements AclDAO { acl.setInheritsFrom(inheritsFrom); } + acl.setAclChangeSetId(getCurrentChangeSetId()); aclCrudDAO.updateAcl(acl); aclCache.remove(id); readersCache.remove(id); @@ -977,6 +978,7 @@ public class AclDAOImpl implements AclDAO if (acl.isVersioned()) { acl.setLatest(Boolean.FALSE); + acl.setAclChangeSetId(getCurrentChangeSetId()); aclCrudDAO.updateAcl(acl); } else @@ -1162,6 +1164,8 @@ public class AclDAOImpl implements AclDAO inheritedAclId = acl.getId(); } + // Does not cause the change set to change + //acl.setAclChangeSetId(getCurrentChangeSetId()); aclCrudDAO.updateAcl(acl); return inheritedAclId; } @@ -1328,6 +1332,7 @@ public class AclDAOImpl implements AclDAO throw new IllegalArgumentException("Fixed and global permissions can not inherit"); case OLD: acl.setInherits(Boolean.TRUE); + acl.setAclChangeSetId(getCurrentChangeSetId()); aclCrudDAO.updateAcl(acl); aclCache.remove(id); readersCache.remove(id); @@ -1346,6 +1351,7 @@ public class AclDAOImpl implements AclDAO getWritable(id, null, null, null, null, false, changes, WriteMode.COPY_ONLY); acl = aclCrudDAO.getAclForUpdate(changes.get(0).getAfter()); acl.setInherits(Boolean.TRUE); + acl.setAclChangeSetId(getCurrentChangeSetId()); aclCrudDAO.updateAcl(acl); } else @@ -1376,6 +1382,7 @@ public class AclDAOImpl implements AclDAO return Collections. singletonList(new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType())); case OLD: acl.setInherits(Boolean.FALSE); + acl.setAclChangeSetId(getCurrentChangeSetId()); aclCrudDAO.updateAcl(acl); aclCache.remove(id); readersCache.remove(id); @@ -1410,6 +1417,7 @@ public class AclDAOImpl implements AclDAO case COW: aclToCopy = aclCrudDAO.getAclForUpdate(toCopy); aclToCopy.setRequiresVersion(true); + aclToCopy.setAclChangeSetId(getCurrentChangeSetId()); aclCrudDAO.updateAcl(aclToCopy); aclCache.remove(toCopy); readersCache.remove(toCopy); @@ -1418,6 +1426,7 @@ public class AclDAOImpl implements AclDAO { AclUpdateEntity inheritedAcl = aclCrudDAO.getAclForUpdate(inheritedId); inheritedAcl.setRequiresVersion(true); + inheritedAcl.setAclChangeSetId(getCurrentChangeSetId()); aclCrudDAO.updateAcl(inheritedAcl); aclCache.remove(inheritedId); readersCache.remove(inheritedId); @@ -1594,6 +1603,7 @@ public class AclDAOImpl implements AclDAO AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(changes.get(0).getAfter()); final Long inheritsFrom = acl.getInheritsFrom(); acl.setInherits(Boolean.FALSE); + acl.setAclChangeSetId(getCurrentChangeSetId()); aclCrudDAO.updateAcl(acl); // Keep inherits from so we can reinstate if required diff --git a/source/java/org/alfresco/repo/domain/usage/UsageDAOTest.java b/source/java/org/alfresco/repo/domain/usage/UsageDAOTest.java index c175180c74..9609878269 100644 --- a/source/java/org/alfresco/repo/domain/usage/UsageDAOTest.java +++ b/source/java/org/alfresco/repo/domain/usage/UsageDAOTest.java @@ -32,6 +32,7 @@ import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.springframework.context.ApplicationContext; +import org.springframework.extensions.surf.util.I18NUtil; /** * @see UsageDAO @@ -80,6 +81,7 @@ public class UsageDAOTest extends TestCase storeRef, null, ContentModel.TYPE_CONTENT, + I18NUtil.getLocale(), null, null); diff --git a/source/java/org/alfresco/repo/forms/FormServiceImplTest.java b/source/java/org/alfresco/repo/forms/FormServiceImplTest.java index 1fb8e66c91..d2bd3615f4 100644 --- a/source/java/org/alfresco/repo/forms/FormServiceImplTest.java +++ b/source/java/org/alfresco/repo/forms/FormServiceImplTest.java @@ -252,7 +252,7 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest // check the field definitions Collection fieldDefs = form.getFieldDefinitions(); assertNotNull("Expecting to find fields", fieldDefs); - assertEquals("Expecting to find 18 fields", 18, fieldDefs.size()); + assertEquals("Wrong number of fields", 19, fieldDefs.size()); // create a Map of the field definitions // NOTE: we can safely do this as we know there are no duplicate field names and we're not @@ -650,7 +650,7 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest // check the field definitions Collection fieldDefs = form.getFieldDefinitions(); assertNotNull("Expecting to find fields", fieldDefs); - assertEquals("Expecting to find 7 fields", 7, fieldDefs.size()); + assertEquals("Wrong number of fields", 8, fieldDefs.size()); // create a Map of the field definitions // NOTE: we can safely do this as we know there are no duplicate field names and we're not @@ -829,7 +829,7 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest // check the field definitions Collection fieldDefs = form.getFieldDefinitions(); assertNotNull("Expecting to find fields", fieldDefs); - assertEquals("Expecting to find 7 fields", 7, fieldDefs.size()); + assertEquals("Wrong number of fields", 8, fieldDefs.size()); // create a Map of the field definitions // NOTE: we can safely do this as we know there are no duplicate field names and we're not diff --git a/source/java/org/alfresco/repo/forms/script/test_formService.js b/source/java/org/alfresco/repo/forms/script/test_formService.js index 3b999e48e5..ffe18df130 100644 --- a/source/java/org/alfresco/repo/forms/script/test_formService.js +++ b/source/java/org/alfresco/repo/forms/script/test_formService.js @@ -32,7 +32,7 @@ function testGetFormForContentNode() var fieldDefs = form.fieldDefinitions; test.assertNotNull(fieldDefs, "field definitions should not be null."); - test.assertEquals(18, fieldDefs.length); + test.assertEquals(19, fieldDefs.length); // as we know there are no duplicates we can safely create a map of the // field definitions for the purposes of this test @@ -154,7 +154,7 @@ function testGetFormForFolderNode() var fieldDefs = form.fieldDefinitions; test.assertNotNull(fieldDefs, "field definitions should not be null."); - test.assertEquals(7, fieldDefs.length); + test.assertEquals(8, fieldDefs.length); // as we know there are no duplicates we can safely create a map of the // field definitions for the purposes of this test diff --git a/source/java/org/alfresco/repo/forum/CommentsRollupAspect.java b/source/java/org/alfresco/repo/forum/CommentsRollupAspect.java new file mode 100644 index 0000000000..9a7c7f84c0 --- /dev/null +++ b/source/java/org/alfresco/repo/forum/CommentsRollupAspect.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.forum; + +import org.alfresco.model.ForumModel; +import org.alfresco.model.RenditionModel; +import org.alfresco.repo.copy.CopyBehaviourCallback; +import org.alfresco.repo.copy.CopyDetails; +import org.alfresco.repo.copy.CopyServicePolicies; +import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +/** + * {@link ForumModel#ASPECT_COMMENTS_ROLLUP comments rollup} aspect behaviour bean. + * This aspect should not be copied. + * + * @author Neil Mc Erlean + * @since 4.0 + */ +public class CommentsRollupAspect implements CopyServicePolicies.OnCopyNodePolicy +{ + private PolicyComponent policyComponent; + + /** + * Set the policy component + * + * @param policyComponent policy component + */ + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + /** + * Initialise method + */ + public void init() + { + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), + RenditionModel.ASPECT_RENDITIONED, + new JavaBehaviour(this, "getCopyCallback")); + } + + /** + * @return Returns {@link CommentsRollupAspectCopyBehaviourCallback} + */ + public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) + { + return CommentsRollupAspectCopyBehaviourCallback.INSTANCE; + } + + /** + * Behaviour for the {@link ForumModel#ASPECT_COMMENTS_ROLLUP fm:commentsRollup} aspect. + */ + private static class CommentsRollupAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback + { + private static final CopyBehaviourCallback INSTANCE = new CommentsRollupAspectCopyBehaviourCallback(); + + /** + * We do not copy the {@link ForumModel#ASPECT_COMMENTS_ROLLUP fm:commentsRollup} aspect. + */ + @Override + public boolean getMustCopy(QName classQName, CopyDetails copyDetails) + { + // Prevent the copying of the aspect. + return false; + } + } +} diff --git a/source/java/org/alfresco/repo/forum/ForumPostBehaviours.java b/source/java/org/alfresco/repo/forum/ForumPostBehaviours.java index 78ef889604..3e3503508f 100644 --- a/source/java/org/alfresco/repo/forum/ForumPostBehaviours.java +++ b/source/java/org/alfresco/repo/forum/ForumPostBehaviours.java @@ -20,13 +20,14 @@ package org.alfresco.repo.forum; import org.alfresco.model.ForumModel; import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy; import org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy; -import org.alfresco.repo.node.NodeServicePolicies.OnDeleteNodePolicy; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; /** * This class registers behaviours for the {@link ForumModel#TYPE_POST fm:post} content type. @@ -36,7 +37,7 @@ import org.alfresco.service.cmr.repository.NodeService; * @since 4.0 */ public class ForumPostBehaviours implements NodeServicePolicies.OnCreateNodePolicy, - NodeServicePolicies.OnDeleteNodePolicy + NodeServicePolicies.BeforeDeleteNodePolicy { private PolicyComponent policyComponent; private NodeService nodeService; @@ -61,9 +62,9 @@ public class ForumPostBehaviours implements NodeServicePolicies.OnCreateNodePoli ForumModel.TYPE_POST, new JavaBehaviour(this, "onCreateNode")); this.policyComponent.bindClassBehaviour( - OnDeleteNodePolicy.QNAME, + BeforeDeleteNodePolicy.QNAME, ForumModel.TYPE_POST, - new JavaBehaviour(this, "onDeleteNode")); + new JavaBehaviour(this, "beforeDeleteNode")); } @Override @@ -81,13 +82,12 @@ public class ForumPostBehaviours implements NodeServicePolicies.OnCreateNodePoli } @Override - public void onDeleteNode(ChildAssociationRef childAssocRef, - boolean isNodeArchived) + public void beforeDeleteNode(NodeRef nodeRef) { // We have one less comment under a discussable node. // We need to find the fm:commentsRollup ancestor to this comment node and decrement its commentCount - - NodeRef commentsRollupNode = getCommentsRollupAncestor(childAssocRef.getParentRef()); + NodeRef topicNode = nodeService.getPrimaryParent(nodeRef).getParentRef(); + NodeRef commentsRollupNode = getCommentsRollupAncestor(topicNode); if (commentsRollupNode != null) { @@ -102,19 +102,37 @@ public class ForumPostBehaviours implements NodeServicePolicies.OnCreateNodePoli * {@link ForumModel#ASPECT_COMMENTS_ROLLUP commentsRollup} aspect. * * @param topicNode - * @return the NodeRef of the commentsRollup ancestor if there is one (which there always should be), else null. + * @return the NodeRef of the commentsRollup ancestor if there is one, else null. */ private NodeRef getCommentsRollupAncestor(NodeRef topicNode) { + // We are specifically trying to roll up "comment" counts here. In other words the number of "comments" on a node + // as applied through the Share UI. + // We are not trying to roll up generic fm:post counts. Although, of course, comments are modelled as fm:post nodes. + // So there are two scenarios in which we do not want to roll up changes to the count. + // 1. When the fm:post node is not a Share comment. + // 2. When the node is being deleted as part of a cascade delete. + // If an ancestor node to an fm:post is deleted then the parent structure may have been flattened within the archive store. + // + NodeRef result = null; + NodeRef forumNode = nodeService.getPrimaryParent(topicNode).getParentRef(); - NodeRef commentsRollupNode = nodeService.getPrimaryParent(forumNode).getParentRef(); - if (! nodeService.hasAspect(commentsRollupNode, ForumModel.ASPECT_COMMENTS_ROLLUP)) + if (ForumModel.TYPE_FORUM.equals(nodeService.getType(forumNode)) && !forumNode.getStoreRef().equals(StoreRef.PROTOCOL_ARCHIVE)) { - return null; - } - else - { - return commentsRollupNode; + NodeRef commentsRollupNode = nodeService.getPrimaryParent(forumNode).getParentRef(); + + if (!commentsRollupNode.getStoreRef().equals(StoreRef.PROTOCOL_ARCHIVE)) + { + if (! nodeService.hasAspect(commentsRollupNode, ForumModel.ASPECT_COMMENTS_ROLLUP)) + { + result = null; + } + else + { + result = commentsRollupNode; + } + } } + return result; } } diff --git a/source/java/org/alfresco/repo/jscript/ValueConverter.java b/source/java/org/alfresco/repo/jscript/ValueConverter.java index 2c3b1fc396..b0dcb3e374 100644 --- a/source/java/org/alfresco/repo/jscript/ValueConverter.java +++ b/source/java/org/alfresco/repo/jscript/ValueConverter.java @@ -117,7 +117,8 @@ public class ValueConverter try { Context.enter(); - // convert array to a native JavaScript Array + // Convert array to a native JavaScript Array + // Note - a scope is usually required for this to work value = (Serializable)Context.getCurrentContext().newArray(scope, array); } finally diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderPerformanceTester.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderPerformanceTester.java index bf4c8cdc05..fe85aa6414 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderPerformanceTester.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderPerformanceTester.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -41,7 +41,6 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransacti import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -86,6 +85,9 @@ public class FileFolderPerformanceTester extends TestCase private NodeRef rootFolderRef; private File dataFile; + private String USERNAME = AuthenticationUtil.getAdminUserName(); // as admin + //private String USERNAME = AuthenticationUtil.getSystemUserName(); // as system (bypass permissions) + protected NodeService getNodeService() { @@ -103,14 +105,26 @@ public class FileFolderPerformanceTester extends TestCase searchService = serviceRegistry.getSearchService(); nodeService = getNodeService(); - // authenticate - authenticationComponent.setSystemUserAsCurrentUser(); + authenticate(USERNAME); rootFolderRef = getOrCreateRootFolder(); dataFile = AbstractContentTransformerTest.loadQuickTestFile("txt"); } + + private void authenticate(String userName) + { + if (AuthenticationUtil.getSystemUserName().equals(userName)) + { + authenticationComponent.setSystemUserAsCurrentUser(); + } + else + { + authenticationComponent.setCurrentUser(userName); + } + } + public void testSetUp() throws Exception { assertNotNull(dataFile); @@ -118,7 +132,7 @@ public class FileFolderPerformanceTester extends TestCase protected NodeRef getOrCreateRootFolder() { - // find the guest folder + // find the company home folder StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); ResultSet rs = searchService.query(storeRef, SearchService.LANGUAGE_XPATH, "/app:company_home"); try @@ -185,7 +199,7 @@ public class FileFolderPerformanceTester extends TestCase public void run() { // authenticate - authenticationComponent.setSystemUserAsCurrentUser(); + authenticate(USERNAME); // progress around the folders until they have been populated start = System.currentTimeMillis(); @@ -242,16 +256,20 @@ public class FileFolderPerformanceTester extends TestCase long time = (end - start); double average = (double) time / (double) (folderCount * currentBatchCount * filesPerBatch); double percentComplete = (double) currentBatchCount / (double) batchCount * 100.0; - logger.debug("\n" + - "[" + Thread.currentThread().getName() + "] \n" + - " Created " + (currentBatchCount*filesPerBatch) + " files in each of " + folderCount + - " folders (" + (randomOrder ? "shuffled" : "in order") + "): \n" + - " Progress: " + String.format("%9.2f", percentComplete) + " percent complete \n" + - " Average: " + String.format("%10.2f", average) + " ms per file \n" + - " Average: " + String.format("%10.2f", 1000.0/average) + " files per second"); + + if (percentComplete > 0) + { + logger.debug("\n" + + "[" + Thread.currentThread().getName() + "] \n" + + " Created " + (currentBatchCount*filesPerBatch) + " files in each of " + folderCount + + " folders (" + (randomOrder ? "shuffled" : "in order") + "): \n" + + " Progress: " + String.format("%9.2f", percentComplete) + " percent complete \n" + + " Average: " + String.format("%10.2f", average) + " ms per file \n" + + " Average: " + String.format("%10.2f", 1000.0/average) + " files per second"); + } } }; - + // kick off the required number of threads logger.debug("\n" + "Starting " + threadCount + @@ -282,35 +300,44 @@ public class FileFolderPerformanceTester extends TestCase } } - @SuppressWarnings("unused") private void readStructure( final NodeRef parentNodeRef, final int threadCount, final int repetitions, final double[] dumpPoints) { - final List children = nodeService.getChildAssocs(parentNodeRef); + final List children = fileFolderService.list(parentNodeRef); Runnable runnable = new Runnable() { public void run() { // authenticate - authenticationComponent.setSystemUserAsCurrentUser(); + authenticate(USERNAME); for (int i = 0; i < repetitions; i++) { // read the contents of each folder - for (ChildAssociationRef childAssociationRef : children) + for (final FileInfo fileInfo : children) { - final NodeRef folderRef = childAssociationRef.getChildRef(); + final NodeRef folderRef = fileInfo.getNodeRef(); RetryingTransactionCallback readCallback = new RetryingTransactionCallback() { public Object execute() throws Exception { - // read the child associations of the folder - nodeService.getChildAssocs(folderRef); - // get the type - nodeService.getType(folderRef); + List tmp = null; + if (fileInfo.isFolder()) + { + long start = System.currentTimeMillis(); + + // read the children of the folder + tmp = fileFolderService.list(folderRef); + + logger.debug("List "+tmp.size()+" items in "+(System.currentTimeMillis()-start)+" msecs"); + } + else + { + throw new AlfrescoRuntimeException("Not a folder: "+folderRef); + } // done return null; }; @@ -318,15 +345,18 @@ public class FileFolderPerformanceTester extends TestCase retryingTransactionHelper.doInTransaction(readCallback, true); } } - } + } }; - + // kick off the required number of threads logger.debug("\n" + "Starting " + threadCount + " threads reading properties and children of " + children.size() + - " folder " + repetitions + + " folders " + repetitions + " times."); + + long start = System.currentTimeMillis(); + ThreadGroup threadGroup = new ThreadGroup(getName()); Thread[] threads = new Thread[threadCount]; for (int i = 0; i < threadCount; i++) @@ -346,65 +376,138 @@ public class FileFolderPerformanceTester extends TestCase // not too serious - the worker threads are non-daemon } } + logger.debug("\nFinished reading in "+(System.currentTimeMillis()-start)+" msecs"); + } + +/* + // Load and read: 100 ordered files (into 1 folder) using 1 thread + public void test_1_ordered_1_1_100() throws Exception + { + buildStructure(rootFolderRef, 1, false, 1, 1, 100, new double[] {0.25, 0.50, 0.75}); + readStructure(rootFolderRef, 1, 3, new double[] {0.25, 0.50, 0.75}); } -// /** Load 5000 files into a single folder using 2 threads */ -// public void test_2_ordered_1_2500() throws Exception -// { -// buildStructure(rootFolderRef, 2, false, 1, 2500, new double[] {0.25, 0.50, 0.75}); -// } + // Load and read: 300 ordered files per folder (into 2 folders) using 1 thread + public void test_1_ordered_2_3_100() throws Exception + { + buildStructure(rootFolderRef, 1, false, 2, 3, 100, new double[] {0.25, 0.50, 0.75}); + readStructure(rootFolderRef, 1, 3, new double[] {0.25, 0.50, 0.75}); + } -// public void test_4_ordered_10_100() throws Exception -// { -// buildStructure(rootFolderRef, 4, false, 10, 100, new double[] {0.25, 0.50, 0.75}); -// } -// -// public void test_4_shuffled_10_100() throws Exception -// { -// buildStructure(rootFolderRef, 4, true, 10, 100, new double[] {0.25, 0.50, 0.75}); -// } -// public void test_1_ordered_100_100() throws Exception -// { -// buildStructure( -// rootFolderRef, -// 1, -// false, -// 100, -// 100, -// new double[] {0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); -// } -// public void test_1_shuffled_10_400() throws Exception -// { -// buildStructure( -// rootFolderRef, -// 1, -// true, -// 10, -// 400, -// new double[] {0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); -// } - public void test_4_shuffled_10_100() throws Exception + // Load and read: 5000 files per folder (into 1 folder) using 2 threads + public void test_2_ordered_1_2500_1() throws Exception + { + buildStructure(rootFolderRef, 2, false, 1, 2500, 1, new double[] {0.25, 0.50, 0.75}); + readStructure(rootFolderRef, 2, 3, new double[] {0.25, 0.50, 0.75}); + } + + // Load and read: 10000 files per folder (into 1 folder) using 2 threads + public void test_2_ordered_1_10_500() throws Exception + { + buildStructure(rootFolderRef, 2, false, 1, 10, 500, new double[] {0.25, 0.50, 0.75}); + readStructure(rootFolderRef, 2, 3, new double[] {0.25, 0.50, 0.75}); // note: will list each folder up to configured max items (eg. default 5000) + } + + // Load and read: 1000 ordered files per folder (into 10 folders) using 4 threads + public void test_1_ordered_10_1_100() throws Exception + { + buildStructure(rootFolderRef, 1, false, 10, 1, 100, new double[] {0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); + readStructure(rootFolderRef, 1, 3, new double[] {0.25, 0.50, 0.75}); + } + + // Load and read: 4000 ordered files per folder (into 10 folders) using 4 threads + public void test_4_ordered_10_1_100() throws Exception + { + buildStructure(rootFolderRef, 4, false, 10, 1, 100, new double[] {0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); + readStructure(rootFolderRef, 4, 3, new double[] {0.25, 0.50, 0.75}); + } + + // Load and read: 4000 shuffled files per folder (into 10 folders) using 4 threads + public void test_4_shuffled_10_1_100() throws Exception + { + buildStructure(rootFolderRef, 4, true, 10, 1, 100, new double[] {0.25, 0.50, 0.75}); + readStructure(rootFolderRef, 4, 1, new double[] {0.25, 0.50, 0.75}); + } + + // Load: 100 shuffled files per folder (into 100 folders) using 1 thread + public void test_1_ordered_100_1_100() throws Exception + { + buildStructure( + rootFolderRef, + 1, + false, + 100, + 1, + 100, + new double[] {0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); + readStructure(rootFolderRef, 1, 1, new double[] {0.25, 0.50, 0.75}); + } + + // Load: 400 shuffled files per folder (into 10 folders) using 1 thread + public void test_1_shuffled_10_1_400() throws Exception + { + buildStructure( + rootFolderRef, + 1, + true, + 10, + 1, + 400, + new double[] {0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); + + readStructure(rootFolderRef, 1, 1, new double[] {0.25, 0.50, 0.75}); + } +*/ + // Load: 800 ordered files per folder (into 3 folders) using 4 threads + public void test_4_ordered_3_2_100() throws Exception + { + buildStructure( + rootFolderRef, + 4, + false, + 3, + 2, + 100, + new double[] {0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); + + System.out.println("rootFolderRef: "+rootFolderRef); + + readStructure(rootFolderRef, 4, 5, new double[] {0.25, 0.50, 0.75}); + } + + // Load: 800 shuffled files per folder (into 3 folders) using 4 threads + public void test_4_shuffled_3_2_100() throws Exception { buildStructure( rootFolderRef, 4, true, - 10, + 3, + 2, 100, - 20, new double[] {0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); + + System.out.println("rootFolderRef: "+rootFolderRef); + + readStructure(rootFolderRef, 4, 5, new double[] {0.25, 0.50, 0.75}); } -// public void test_1_ordered_1_50000() throws Exception -// { -// buildStructure( -// rootFolderRef, -// 1, -// false, -// 1, -// 50000, -// new double[] {0.01, 0.02, 0.03, 0.04, 0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); -// } - + +/* + // Load: 50000 ordered files per folder (into 1 folder) using 1 thread + public void test_1_ordered_1_5000_10() throws Exception + { + buildStructure( + rootFolderRef, + 1, + false, + 1, + 5000, + 10, + new double[] {0.01, 0.02, 0.03, 0.04, 0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); + + readStructure(rootFolderRef, 1, 1, new double[] {0.25, 0.50, 0.75}); + } +*/ /** * Create a bunch of files and folders in a folder and then run multi-threaded directory @@ -573,7 +676,7 @@ public class FileFolderPerformanceTester extends TestCase { public List doWork() throws Exception { - return fileFolderService.search(folderNodeRef, "*", false); + return fileFolderService.list(folderNodeRef); } }; diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java index 41640773fb..5dc21cbc98 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java @@ -37,6 +37,7 @@ import org.alfresco.model.ContentModel; import org.alfresco.query.CannedQueryFactory; import org.alfresco.query.CannedQueryResults; import org.alfresco.repo.search.QueryParameterDefImpl; +import org.alfresco.repo.security.permissions.impl.acegi.WrappedList; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.model.FileExistsException; @@ -128,7 +129,7 @@ public class FileFolderServiceImpl implements FileFolderService // TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID) private List systemPaths; - // applies to list "all" methods - note: final result count also depends on "system.acl.maxPermissionCheckTimeMillis" + // default cutoff - applies to list "all" methods private int defaultListMaxResults = 5000; /** @@ -213,7 +214,7 @@ public class FileFolderServiceImpl implements FileFolderService public void init() { } - + /** * Helper method to convert node reference instances to file info * @@ -331,7 +332,16 @@ public class FileFolderServiceImpl implements FileFolderService public List list(NodeRef contextNodeRef) { - return list(contextNodeRef, true, true); + // execute the query + List results = listSimple(contextNodeRef, true, true); + // done + if (logger.isDebugEnabled()) + { + logger.debug("Shallow search for files and folders: \n" + + " context: " + contextNodeRef + "\n" + + " results: " + results); + } + return results; } /* (non-Javadoc) @@ -342,10 +352,20 @@ public class FileFolderServiceImpl implements FileFolderService ParameterCheck.mandatory("contextNodeRef", contextNodeRef); ParameterCheck.mandatory("pagingRequest", pagingRequest); - // execute query - CannedQueryResults results = listImpl(contextNodeRef, files, folders, ignoreQNameTypes, pagingRequest); + Set searchTypeQNames = buildTypes(files, folders, ignoreQNameTypes); - List nodeRefs = results.getPages().get(0); + // execute query + CannedQueryResults results = listImpl(contextNodeRef, searchTypeQNames, pagingRequest); + + List nodeRefs = null; + if (results.getPageCount() > 0) + { + nodeRefs = results.getPages().get(0); + } + else + { + nodeRefs = Collections.emptyList(); + } // set total count Pair totalCount = null; @@ -365,12 +385,22 @@ public class FileFolderServiceImpl implements FileFolderService return new PagingFileInfoResultsImpl(nodeInfos, hasMoreItems, totalCount, results.getQueryExecutionId(), true); } - private CannedQueryResults listImpl(NodeRef contextNodeRef, boolean files, boolean folders, Set ignoreQNameTypes, PagingFileInfoRequest pagingRequest) + private CannedQueryResults listImpl(NodeRef contextNodeRef, boolean files, boolean folders) + { + Set searchTypeQNames = buildTypes(files, folders, null); + return listImpl(contextNodeRef, searchTypeQNames); + } + + private CannedQueryResults listImpl(NodeRef contextNodeRef, Set searchTypeQNames) + { + return listImpl(contextNodeRef, searchTypeQNames, new PagingFileInfoRequest(defaultListMaxResults, null)); + } + + // note: similar to getChildAssocs(contextNodeRef, searchTypeQNames) but enables paging features, including max items, sorting etc (with permissions per-applied) + private CannedQueryResults listImpl(NodeRef contextNodeRef, Set searchTypeQNames, PagingFileInfoRequest pagingRequest) { long start = System.currentTimeMillis(); - Set searchTypeQNames = buildTypes(files, folders, ignoreQNameTypes); - // get canned query GetChildrenCannedQueryFactory getChildrenCannedQueryFactory = (GetChildrenCannedQueryFactory)cannedQueryRegistry.getNamedObject("getChildrenCannedQueryFactory"); @@ -394,28 +424,10 @@ public class FileFolderServiceImpl implements FileFolderService return results; } - private List list(NodeRef contextNodeRef, boolean files, boolean folders) - { - // execute the query - List nodeRefs = listSimple(contextNodeRef, files, folders); - // convert the noderefs - List results = toFileInfo(nodeRefs); - // done - if (logger.isDebugEnabled()) - { - logger.debug("Shallow search for files and folders: \n" + - " context: " + contextNodeRef + "\n" + - " results: " + results); - } - return results; - } - public List listFiles(NodeRef contextNodeRef) { // execute the query - List nodeRefs = listSimple(contextNodeRef, true, false); - // convert the noderefs - List results = toFileInfo(nodeRefs); + List results = listSimple(contextNodeRef, true, false); // done if (logger.isDebugEnabled()) { @@ -429,9 +441,7 @@ public class FileFolderServiceImpl implements FileFolderService public List listFolders(NodeRef contextNodeRef) { // execute the query - List nodeRefs = listSimple(contextNodeRef, false, true); - // convert the noderefs - List results = toFileInfo(nodeRefs); + List results = listSimple(contextNodeRef, false, true); // done if (logger.isDebugEnabled()) { @@ -529,6 +539,7 @@ public class FileFolderServiceImpl implements FileFolderService List nodeRefs = searchInternal(contextNodeRef, namePattern, fileSearch, folderSearch, includeSubFolders); List results = toFileInfo(nodeRefs); + // eliminate unwanted files/folders Iterator iterator = results.iterator(); while (iterator.hasNext()) @@ -592,10 +603,10 @@ public class FileFolderServiceImpl implements FileFolderService } else { - nodeRefs = listSimple(contextNodeRef, fileSearch, folderSearch); + nodeRefs = listImpl(contextNodeRef, fileSearch, folderSearch).getPage(); } } - else + else { // TODO - we need to get rid of this xpath stuff // if the name pattern is null, then we use the ANY pattern @@ -652,10 +663,15 @@ public class FileFolderServiceImpl implements FileFolderService return nodeRefs; } - private List listSimple(NodeRef contextNodeRef, boolean files, boolean folders) + private List listSimple(NodeRef contextNodeRef, boolean files, boolean folders) throws InvalidTypeException { - PagingFileInfoRequest pagingRequest = new PagingFileInfoRequest(0, defaultListMaxResults, null, null); - return listImpl(contextNodeRef, files, folders, null, pagingRequest).getPage(); + CannedQueryResults cq = listImpl(contextNodeRef, files, folders); + List nodeRefs = cq.getPage(); + + List results = toFileInfo(nodeRefs); + + // avoid re-applying permissions (for "list" canned queries) + return new WrappedList(results, cq.permissionsApplied(), cq.hasMoreItems()); } private Set buildTypes(boolean files, boolean folders, Set ignoreQNameTypes) @@ -665,23 +681,12 @@ public class FileFolderServiceImpl implements FileFolderService // Build a list of file and folder types if (folders) { - Collection qnames = dictionaryService.getSubTypes(ContentModel.TYPE_FOLDER, true); - searchTypeQNames.addAll(qnames); - searchTypeQNames.add(ContentModel.TYPE_FOLDER); + searchTypeQNames.addAll(buildFolderTypes()); } if (files) { - Collection qnames = dictionaryService.getSubTypes(ContentModel.TYPE_CONTENT, true); - searchTypeQNames.addAll(qnames); - searchTypeQNames.add(ContentModel.TYPE_CONTENT); - qnames = dictionaryService.getSubTypes(ContentModel.TYPE_LINK, true); - searchTypeQNames.addAll(qnames); - searchTypeQNames.add(ContentModel.TYPE_LINK); + searchTypeQNames.addAll(buildFileTypes()); } - // Remove 'system' folders - Collection qnames = dictionaryService.getSubTypes(ContentModel.TYPE_SYSTEM_FOLDER, true); - searchTypeQNames.removeAll(qnames); - searchTypeQNames.remove(ContentModel.TYPE_SYSTEM_FOLDER); if (ignoreQNameTypes != null) { @@ -691,6 +696,38 @@ public class FileFolderServiceImpl implements FileFolderService return searchTypeQNames; } + private Set buildFolderTypes() + { + Set folderTypeQNames = new HashSet(50); + + // Build a list of folder types + Collection qnames = dictionaryService.getSubTypes(ContentModel.TYPE_FOLDER, true); + folderTypeQNames.addAll(qnames); + folderTypeQNames.add(ContentModel.TYPE_FOLDER); + + // Remove 'system' folders + qnames = dictionaryService.getSubTypes(ContentModel.TYPE_SYSTEM_FOLDER, true); + folderTypeQNames.removeAll(qnames); + folderTypeQNames.remove(ContentModel.TYPE_SYSTEM_FOLDER); + + return folderTypeQNames; + } + + private Set buildFileTypes() + { + Set fileTypeQNames = new HashSet(50); + + // Build a list of file types + Collection qnames = dictionaryService.getSubTypes(ContentModel.TYPE_CONTENT, true); + fileTypeQNames.addAll(qnames); + fileTypeQNames.add(ContentModel.TYPE_CONTENT); + qnames = dictionaryService.getSubTypes(ContentModel.TYPE_LINK, true); + fileTypeQNames.addAll(qnames); + fileTypeQNames.add(ContentModel.TYPE_LINK); + + return fileTypeQNames; + } + /** * A deep version of listSimple. Which recursively walks down the tree from a given starting point, returning * the node refs of files or folders found along the way. @@ -717,31 +754,12 @@ public class FileFolderServiceImpl implements FileFolderService logger.debug("searchSimpleDeep contextNodeRef:" + contextNodeRef); } - Set folderTypeQNames = new HashSet(10); - Set fileTypeQNames = new HashSet(10); - // To hold the results. List result = new ArrayList(); // Build a list of folder types - Collection qnames = dictionaryService.getSubTypes(ContentModel.TYPE_FOLDER, true); - folderTypeQNames.addAll(qnames); - folderTypeQNames.add(ContentModel.TYPE_FOLDER); - - // Remove 'system' folders and all descendants - Collection systemFolderQNames = dictionaryService.getSubTypes(ContentModel.TYPE_SYSTEM_FOLDER, true); - folderTypeQNames.removeAll(systemFolderQNames); - folderTypeQNames.remove(ContentModel.TYPE_SYSTEM_FOLDER); - - if (files) - { - Collection fileQNames = dictionaryService.getSubTypes(ContentModel.TYPE_CONTENT, true); - fileTypeQNames.addAll(fileQNames); - fileTypeQNames.add(ContentModel.TYPE_CONTENT); - Collection linkQNames = dictionaryService.getSubTypes(ContentModel.TYPE_LINK, true); - fileTypeQNames.addAll(linkQNames); - fileTypeQNames.add(ContentModel.TYPE_LINK); - } + Set folderTypeQNames = buildFolderTypes(); + Set fileTypeQNames = (files ? buildFileTypes() : new HashSet(0)); if(!folders && !files) { @@ -1091,7 +1109,7 @@ public class FileFolderServiceImpl implements FileFolderService public FileInfo create(NodeRef parentNodeRef, String name, QName typeQName) throws FileExistsException { return createImpl(parentNodeRef, name, typeQName, null); - } + } public FileInfo create(NodeRef parentNodeRef, String name, QName typeQName, QName assocQName) throws FileExistsException { diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java index 49af5d73cc..d23443f26d 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java @@ -70,7 +70,6 @@ import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.GUID; -import org.alfresco.util.Pair; import org.springframework.context.ApplicationContext; import org.springframework.extensions.surf.util.I18NUtil; @@ -119,7 +118,6 @@ public class FileFolderServiceImplTest extends TestCase permissionService = serviceRegistry.getPermissionService(); authenticationService = (MutableAuthenticationService) ctx.getBean("AuthenticationService"); dictionaryDAO = (DictionaryDAO) ctx.getBean("dictionaryDAO"); - authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); // start the transaction txn = transactionService.getUserTransaction(); @@ -129,7 +127,7 @@ public class FileFolderServiceImplTest extends TestCase IntegrityChecker.setWarnInTransaction(); // authenticate - authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); // create a test store StoreRef storeRef = nodeService @@ -219,7 +217,7 @@ public class FileFolderServiceImplTest extends TestCase public void testListPage() throws Exception { - // sanity check only (see also GetChildrenCannedQueryTest) + // sanity checks only (see also GetChildrenCannedQueryTest) PagingFileInfoRequest pagingRequest = new PagingFileInfoRequest(100, null); PagingFileInfoResults pagingResults = fileFolderService.list(workingRootNodeRef, true, true, null, pagingRequest); @@ -236,6 +234,15 @@ public class FileFolderServiceImplTest extends TestCase { NAME_L0_FILE_A, NAME_L0_FILE_B, NAME_L0_FOLDER_A, NAME_L0_FOLDER_B, NAME_L0_FOLDER_C }; checkFileList(files, 2, 3, expectedNames); + + // empty list if skip count greater than number of results (ALF-7884) + pagingRequest = new PagingFileInfoRequest(1000, 3, null, null); + pagingResults = fileFolderService.list(workingRootNodeRef, true, true, null, pagingRequest); + + assertNotNull(pagingResults); + assertFalse(pagingResults.hasMoreItems()); + assertEquals(0, pagingResults.getPage().size()); + // TODO add more here } @@ -502,7 +509,6 @@ public class FileFolderServiceImplTest extends TestCase /** * ALF-7692 */ - @SuppressWarnings("deprecation") public void testMovePermissions() throws Exception { txn.commit(); diff --git a/source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQuery.java b/source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQuery.java index 43a85de1be..a5c85300f9 100644 --- a/source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQuery.java +++ b/source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQuery.java @@ -48,6 +48,7 @@ import org.alfresco.repo.domain.qname.QNameDAO; import org.alfresco.repo.domain.query.CannedQueryDAO; import org.alfresco.repo.security.permissions.impl.acegi.AbstractCannedQueryPermissions; import org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityInterceptor; +import org.alfresco.repo.security.permissions.impl.acegi.WrappedList; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.MLText; @@ -156,6 +157,7 @@ public class GetChildrenCannedQuery extends AbstractCannedQueryPermissions 0) { + // sorted - note: permissions will be applied post query final List children = new ArrayList(100); SortedChildQueryCallback callback = new SortedChildQueryCallback() @@ -183,30 +185,33 @@ public class GetChildrenCannedQuery extends AbstractCannedQueryPermissions(100); - - final int maxItems = requestedCount; + final WrappedList rawResult = new WrappedList(new ArrayList(100), requestedCount); UnsortedChildQueryCallback callback = new UnsortedChildQueryCallback() { public boolean handle(NodeRef nodeRef) { - result.add(nodeRef); + rawResult.add(nodeRef); // More results ? - return (result.size() < maxItems); + return (rawResult.size() < rawResult.getMaxChecks()); } }; UnsortedResultHandler resultHandler = new UnsortedResultHandler(callback, parameters.getAuthenticationToken()); cannedQueryDAO.executeQuery(QUERY_NAMESPACE, QUERY_SELECT_GET_CHILDREN, params, 0, Integer.MAX_VALUE, resultHandler); resultHandler.done(); + + // permissions have been applied + result = new WrappedList(rawResult.getWrapped(), true, (rawResult.size() == requestedCount)); } if (start != null) @@ -577,6 +582,7 @@ public class GetChildrenCannedQuery extends AbstractCannedQueryPermissions results = applyPermissions(nodeRefs, authenticationToken, nodeRefs.size()); for (NodeRef nodeRef : results.getPage()) diff --git a/source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQueryTest.java b/source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQueryTest.java index 750e8b86fd..ca0c619e10 100644 --- a/source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQueryTest.java +++ b/source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQueryTest.java @@ -26,8 +26,10 @@ import java.text.Collator; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import junit.framework.TestCase; @@ -52,9 +54,14 @@ import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.namespace.QName; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.Pair; +import org.alfresco.util.PropertyMap; import org.alfresco.util.registry.NamedObjectRegistry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -77,8 +84,17 @@ public class GetChildrenCannedQueryTest extends TestCase private ContentService contentService; private MimetypeService mimetypeService; + private PersonService personService; + private MutableAuthenticationService authenticationService; + private PermissionService permissionService; + private boolean setupOnce = false; + private static final String TEST_USER = "GC-CQ-User-"+System.currentTimeMillis(); + + private Set hits = new HashSet(100); + private Set misses = new HashSet(100); + @SuppressWarnings("unchecked") private NamedObjectRegistry cannedQueryRegistry; @@ -94,6 +110,10 @@ public class GetChildrenCannedQueryTest extends TestCase contentService = (ContentService)ctx.getBean("ContentService"); mimetypeService = (MimetypeService)ctx.getBean("MimetypeService"); + personService = (PersonService)ctx.getBean("PersonService"); + authenticationService = (MutableAuthenticationService)ctx.getBean("AuthenticationService"); + permissionService = (PermissionService)ctx.getBean("PermissionService"); + cannedQueryRegistry = new NamedObjectRegistry(); cannedQueryRegistry.setStorageType(CannedQueryFactory.class); @@ -117,34 +137,62 @@ public class GetChildrenCannedQueryTest extends TestCase AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); - load(repositoryHelper.getCompanyHome(), "quick.jpg", "", ""); - load(repositoryHelper.getCompanyHome(), "quick.txt", "ZZ title", "XX description"); - load(repositoryHelper.getCompanyHome(), "quick.bmp", null, null); - load(repositoryHelper.getCompanyHome(), "quick.doc", "BB title", "BB description"); - load(repositoryHelper.getCompanyHome(), "quick.pdf", "ZZ title", "YY description"); + createUser(TEST_USER); + + boolean canRead = true; + + load(repositoryHelper.getCompanyHome(), "quick.jpg", "", "", canRead, hits); + load(repositoryHelper.getCompanyHome(), "quick.txt", "ZZ title", "ZZ description 1", canRead, hits); + load(repositoryHelper.getCompanyHome(), "quick.bmp", null, null, canRead, hits); + load(repositoryHelper.getCompanyHome(), "quick.doc", "BB title", "BB description", canRead, hits); + load(repositoryHelper.getCompanyHome(), "quick.pdf", "ZZ title", "ZZ description 2", canRead, hits); + + canRead = false; + + load(repositoryHelper.getCompanyHome(), "quick.ppt", "CC title", "CC description", canRead, misses); + load(repositoryHelper.getCompanyHome(), "quick.xls", "AA title", "AA description", canRead, misses); + load(repositoryHelper.getCompanyHome(), "quick.gif", "YY title", "BB description", canRead, misses); setupOnce = true; + + // double-check permissions - see testPermissions + + AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER); + + for (NodeRef nodeRef : hits) + { + assertTrue(permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.ALLOWED); + } + + for (NodeRef nodeRef : misses) + { + // user CANNOT read + assertFalse(permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.ALLOWED); + } + + // belts-and-braces + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + for (NodeRef nodeRef : hits) + { + assertTrue(permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.ALLOWED); + } + + for (NodeRef nodeRef : misses) + { + // admin CAN read + assertTrue(permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.ALLOWED); + } } + + AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER); } public void testSetup() throws Exception { } - public void testSanityCheck() throws Exception - { - NodeRef parentNodeRef = repositoryHelper.getCompanyHome(); - - PagingResults results = list(parentNodeRef, -1, -1, 0, null); - assertTrue(results.getPage().size() > 0); - - if (logger.isInfoEnabled()) - { - logger.info("testSanityCheck: company home children = "+results.getPage().size()); - } - } - - public void testSimpleMaxItems() throws Exception + public void testMaxItems() throws Exception { NodeRef parentNodeRef = repositoryHelper.getCompanyHome(); @@ -169,7 +217,7 @@ public class GetChildrenCannedQueryTest extends TestCase } } - public void testSimplePaging() throws Exception + public void testPaging() throws Exception { NodeRef parentNodeRef = repositoryHelper.getCompanyHome(); @@ -180,7 +228,7 @@ public class GetChildrenCannedQueryTest extends TestCase int pageSize = 3; assertTrue(totalCnt > pageSize); - int pageCnt = (totalCnt / pageSize) + 1; + int pageCnt = new Double(totalCnt / pageSize).intValue(); // round-up assertTrue(pageCnt > 1); for (int i = 1; i <= pageCnt; i++) @@ -213,7 +261,7 @@ public class GetChildrenCannedQueryTest extends TestCase } } - public void testSimpleSorting() throws Exception + public void testSorting() throws Exception { NodeRef parentNodeRef = repositoryHelper.getCompanyHome(); @@ -271,6 +319,47 @@ public class GetChildrenCannedQueryTest extends TestCase } } + public void testPermissions() throws Exception + { + AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER); + + NodeRef parentNodeRef = repositoryHelper.getCompanyHome(); + + PagingResults results = list(parentNodeRef, -1, -1, 0, null); + assertFalse(results.hasMoreItems()); + assertTrue(results.permissionsApplied()); + + List nodeRefs = results.getPage(); + + for (NodeRef nodeRef : hits) + { + assertTrue(nodeRefs.contains(nodeRef)); + } + + for (NodeRef nodeRef : misses) + { + assertFalse(nodeRefs.contains(nodeRef)); + } + + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + results = list(parentNodeRef, -1, -1, 0, null); + assertFalse(results.hasMoreItems()); + assertTrue(results.permissionsApplied()); + + nodeRefs = results.getPage(); + + for (NodeRef nodeRef : hits) + { + assertTrue(nodeRefs.contains(nodeRef)); + } + + for (NodeRef nodeRef : misses) + { + assertTrue(nodeRefs.contains(nodeRef)); + } + } + private void sortAndCheck(NodeRef parentNodeRef, QName sortPropQName, boolean sortAscending) { List> sortPairs = new ArrayList>(1); @@ -424,16 +513,20 @@ public class GetChildrenCannedQueryTest extends TestCase private class PagingNodeRefResultsImpl implements PagingResults { private List nodeRefs; - private Boolean hasMorePages; // null => unknown + + private boolean hasMorePages; + private boolean permissionsApplied; + private Integer totalResultCount; // null => not requested (or unknown) private Boolean isTotalResultCountCutoff; // null => unknown - public PagingNodeRefResultsImpl(List nodeRefs, Boolean hasMore, Integer totalResultCount, Boolean isTotalResultCountCutoff, boolean permissionsApplied) + public PagingNodeRefResultsImpl(List nodeRefs, boolean hasMorePages, Integer totalResultCount, Boolean isTotalResultCountCutoff, boolean permissionsApplied) { this.nodeRefs = nodeRefs; - this.hasMorePages = hasMore; + this.hasMorePages = hasMorePages; this.totalResultCount= totalResultCount; this.isTotalResultCountCutoff = isTotalResultCountCutoff; + this.permissionsApplied = permissionsApplied; } public List getPage() @@ -441,11 +534,16 @@ public class GetChildrenCannedQueryTest extends TestCase return nodeRefs; } - public Boolean hasMoreItems() + public boolean hasMoreItems() { return hasMorePages; } + public boolean permissionsApplied() + { + return permissionsApplied; + } + public Pair getTotalResultCount() { return new Pair(totalResultCount, (isTotalResultCountCutoff ? null : totalResultCount)); @@ -457,7 +555,7 @@ public class GetChildrenCannedQueryTest extends TestCase } } - private void load(NodeRef parentNodeRef, String fileName, String title, String description) throws IOException + private void load(NodeRef parentNodeRef, String fileName, String title, String description, boolean readAllowed, Set results) throws IOException { // Create the node Map properties = new HashMap(); @@ -497,5 +595,33 @@ public class GetChildrenCannedQueryTest extends TestCase ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); writer.setMimetype(mimetypeService.guessMimetype(fileName)); writer.putContent(file); + + if (! readAllowed) + { + // deny read (by explicitly breaking inheritance) + permissionService.setInheritParentPermissions(nodeRef, false); + } + + results.add(nodeRef); + } + + private void createUser(String userName) + { + if (! authenticationService.authenticationExists(userName)) + { + authenticationService.createAuthentication(userName, "PWD".toCharArray()); + } + + if (! personService.personExists(userName)) + { + PropertyMap ppOne = new PropertyMap(4); + ppOne.put(ContentModel.PROP_USERNAME, userName); + ppOne.put(ContentModel.PROP_FIRSTNAME, "firstName"); + ppOne.put(ContentModel.PROP_LASTNAME, "lastName"); + ppOne.put(ContentModel.PROP_EMAIL, "email@email.com"); + ppOne.put(ContentModel.PROP_JOBTITLE, "jobTitle"); + + personService.createPerson(ppOne); + } } } diff --git a/source/java/org/alfresco/repo/model/filefolder/PagingFileInfoResultsImpl.java b/source/java/org/alfresco/repo/model/filefolder/PagingFileInfoResultsImpl.java index cfd3f4056f..a8ca305500 100644 --- a/source/java/org/alfresco/repo/model/filefolder/PagingFileInfoResultsImpl.java +++ b/source/java/org/alfresco/repo/model/filefolder/PagingFileInfoResultsImpl.java @@ -20,7 +20,7 @@ package org.alfresco.repo.model.filefolder; import java.util.List; -import org.alfresco.repo.security.permissions.impl.acegi.ResultsPermissionChecked; +import org.alfresco.query.PermissionedResults; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.PagingFileInfoResults; import org.alfresco.util.Pair; @@ -31,22 +31,22 @@ import org.alfresco.util.Pair; * @author janv * @since 4.0 */ -/* package */ class PagingFileInfoResultsImpl implements PagingFileInfoResults, ResultsPermissionChecked +/* package */ class PagingFileInfoResultsImpl implements PagingFileInfoResults, PermissionedResults { private List nodeInfos; - private Boolean hasMoreItems; + private boolean hasMoreItems; private Pair totalResultCount; private String queryExecutionId; - private boolean permissionChecked; + private boolean permissionsApplied; - public PagingFileInfoResultsImpl(List nodeInfos, Boolean hasMoreItems, Pair totalResultCount, String queryExecutionId, boolean permissionChecked) + public PagingFileInfoResultsImpl(List nodeInfos, boolean hasMoreItems, Pair totalResultCount, String queryExecutionId, boolean permissionsApplied) { this.nodeInfos = nodeInfos; this.hasMoreItems = hasMoreItems; this.totalResultCount = totalResultCount; this.queryExecutionId = queryExecutionId; - this.permissionChecked = permissionChecked; + this.permissionsApplied = permissionsApplied; } public List getPage() @@ -54,7 +54,7 @@ import org.alfresco.util.Pair; return nodeInfos; } - public Boolean hasMoreItems() + public boolean hasMoreItems() { return hasMoreItems; } @@ -69,8 +69,8 @@ import org.alfresco.util.Pair; return queryExecutionId; } - public boolean permissionsChecked() + public boolean permissionsApplied() { - return permissionChecked; + return permissionsApplied; } } diff --git a/source/java/org/alfresco/repo/model/ml/MLContainerType.java b/source/java/org/alfresco/repo/model/ml/MLContainerType.java index 83e5756c50..85b7885123 100644 --- a/source/java/org/alfresco/repo/model/ml/MLContainerType.java +++ b/source/java/org/alfresco/repo/model/ml/MLContainerType.java @@ -91,26 +91,19 @@ public class MLContainerType implements * * Since the pivot must be an existing translation and the pivot can t be empty, some tests must be performed when * the locale of the mlContainer is updated. - * - * @see org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy#onUpdateProperties(org.alfresco.service.cmr.repository.NodeRef, java.util.Map, java.util.Map) */ public void onUpdateProperties(NodeRef nodeRef, Map before, Map after) { - /* - * TODO: Move into MultilingualContentService - */ - Locale localeAfter = (Locale) after.get(ContentModel.PROP_LOCALE); Locale localeBefore = (Locale) before.get(ContentModel.PROP_LOCALE); - // The locale can be set as null if the container have no children. - // Normaly, it's ONLY the case at the creation of the container. - if(localeAfter == null && nodeService.getChildAssocs(nodeRef).size() != 0) + if (localeAfter == null) { - throw new IllegalArgumentException("A Locale property must be defined for a Multilingual Container and can't be null"); + throw new IllegalArgumentException("The ML container cannot have a null locale."); } - - if(localeAfter != null && !localeAfter.equals(localeBefore)) + + // If the locale is changing, then ensure that the pivot translation is present and matches + if (localeBefore != null && !localeAfter.equals(localeBefore)) { Map translations = multilingualContentService.getTranslations(nodeRef); diff --git a/source/java/org/alfresco/repo/model/ml/MultilingualDocumentAspect.java b/source/java/org/alfresco/repo/model/ml/MultilingualDocumentAspect.java index 9dfddd883d..9a71954a06 100644 --- a/source/java/org/alfresco/repo/model/ml/MultilingualDocumentAspect.java +++ b/source/java/org/alfresco/repo/model/ml/MultilingualDocumentAspect.java @@ -134,9 +134,6 @@ public class MultilingualDocumentAspect implements */ public void onUpdateProperties(NodeRef nodeRef, Map before, Map after) { - /* - * TODO: Move this into MultilingualContentService#setTranslationLocale - */ Locale localeBefore = (Locale)before.get(ContentModel.PROP_LOCALE); Locale localeAfter = null; diff --git a/source/java/org/alfresco/repo/model/ml/tools/MLContainerTypeTest.java b/source/java/org/alfresco/repo/model/ml/tools/MLContainerTypeTest.java index baa7c0b631..bc380ce6f6 100644 --- a/source/java/org/alfresco/repo/model/ml/tools/MLContainerTypeTest.java +++ b/source/java/org/alfresco/repo/model/ml/tools/MLContainerTypeTest.java @@ -51,8 +51,8 @@ public class MLContainerTypeTest extends AbstractMultilingualTestCases // 1. Locale as null - // Ensure that the setting of the locale of the mlContainer as null throws an excpetion - assertTrue("The setting of the locale of a mlContainer must throws an exception", + // Setting a null locale has no effect on any node + assertFalse("The setting of the locale of a mlContainer must throws an exception", setLocaleProp(mlContainer, null)); // Ensure that the locale of the mlContainer is not changed assertEquals("The locale of the mlContainer would not be changed", diff --git a/source/java/org/alfresco/repo/node/NodeServiceTest.java b/source/java/org/alfresco/repo/node/NodeServiceTest.java new file mode 100644 index 0000000000..fff9a2b549 --- /dev/null +++ b/source/java/org/alfresco/repo/node/NodeServiceTest.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.node; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Locale; +import java.util.Map; + +import junit.framework.TestCase; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.MLText; +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.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * Tests basic {@link NodeService} functionality + * + * @author Derek Hulley + * @since 4.0 + */ +public class NodeServiceTest extends TestCase +{ + public static final String NAMESPACE = "http://www.alfresco.org/test/BaseNodeServiceTest"; + public static final String TEST_PREFIX = "test"; + public static final QName TYPE_QNAME_TEST = QName.createQName(NAMESPACE, "multiprop"); + public static final QName PROP_QNAME_NAME = QName.createQName(NAMESPACE, "name"); + public static final QName ASSOC_QNAME_CHILDREN = QName.createQName(NAMESPACE, "child"); + + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + protected ServiceRegistry serviceRegistry; + protected NodeService nodeService; + private TransactionService txnService; + + /** populated during setup */ + protected NodeRef rootNodeRef; + + @Override + protected void setUp() throws Exception + { + I18NUtil.setLocale(null); + + serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); + nodeService = serviceRegistry.getNodeService(); + txnService = serviceRegistry.getTransactionService(); + + AuthenticationUtil.setRunAsUserSystem(); + + // create a first store directly + RetryingTransactionCallback createStoreWork = new RetryingTransactionCallback() + { + public NodeRef execute() + { + StoreRef storeRef = nodeService.createStore( + StoreRef.PROTOCOL_WORKSPACE, + "Test_" + System.nanoTime()); + return nodeService.getRootNode(storeRef); + } + }; + rootNodeRef = txnService.getRetryingTransactionHelper().doInTransaction(createStoreWork); + } + + /** + * Clean up the test thread + */ + @Override + protected void tearDown() + { + AuthenticationUtil.clearCurrentSecurityContext(); + I18NUtil.setLocale(null); + } + + public void testSetUp() throws Exception + { + assertNotNull(rootNodeRef); + } + + public void testLocaleSupport() throws Exception + { + // Ensure that the root node has the default locale + Locale locale = (Locale) nodeService.getProperty(rootNodeRef, ContentModel.PROP_LOCALE); + assertNotNull("Locale property must occur on every node", locale); + assertEquals("Expected default locale on the root node", I18NUtil.getLocale(), locale); + assertTrue("Every node must have sys:localized", nodeService.hasAspect(rootNodeRef, ContentModel.ASPECT_LOCALIZED)); + + // Now switch to a specific locale and create a new node + I18NUtil.setLocale(Locale.CANADA_FRENCH); + + // Create a node using an explicit locale + NodeRef nodeRef1 = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, getName()), + ContentModel.TYPE_CONTAINER, + Collections.singletonMap(ContentModel.PROP_LOCALE, (Serializable)Locale.GERMAN)).getChildRef(); + assertTrue("Every node must have sys:localized", nodeService.hasAspect(nodeRef1, ContentModel.ASPECT_LOCALIZED)); + assertEquals( + "Didn't set the explicit locale during create. ", + Locale.GERMAN, nodeService.getProperty(nodeRef1, ContentModel.PROP_LOCALE)); + + // Create a node using the thread's locale + NodeRef nodeRef2 = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, getName()), + ContentModel.TYPE_CONTAINER).getChildRef(); + assertTrue("Every node must have sys:localized", nodeService.hasAspect(nodeRef2, ContentModel.ASPECT_LOCALIZED)); + assertEquals( + "Didn't set the locale during create. ", + Locale.CANADA_FRENCH, nodeService.getProperty(nodeRef2, ContentModel.PROP_LOCALE)); + + // Switch Locale and modify ml:text property + I18NUtil.setLocale(Locale.CHINESE); + nodeService.setProperty(nodeRef2, ContentModel.PROP_DESCRIPTION, "Chinese description"); + I18NUtil.setLocale(Locale.FRENCH); + nodeService.setProperty(nodeRef2, ContentModel.PROP_DESCRIPTION, "French description"); + + // Expect that we have MLText (if we are ML aware) + boolean wasMLAware = MLPropertyInterceptor.setMLAware(true); + try + { + MLText checkDescription = (MLText) nodeService.getProperty(nodeRef2, ContentModel.PROP_DESCRIPTION); + assertEquals("Chinese description", checkDescription.getValue(Locale.CHINESE)); + assertEquals("French description", checkDescription.getValue(Locale.FRENCH)); + } + finally + { + MLPropertyInterceptor.setMLAware(wasMLAware); + } + // But the node locale must not have changed + assertEquals( + "Node modification should not affect node locale. ", + Locale.CANADA_FRENCH, nodeService.getProperty(nodeRef2, ContentModel.PROP_LOCALE)); + + // Now explicitly set the node's locale + nodeService.setProperty(nodeRef2, ContentModel.PROP_LOCALE, Locale.ITALY); + assertEquals( + "Node locale must be settable. ", + Locale.ITALY, nodeService.getProperty(nodeRef2, ContentModel.PROP_LOCALE)); + // But mltext must be unchanged + assertEquals( + "Canada-French must be closest to French. ", + "French description", nodeService.getProperty(nodeRef2, ContentModel.PROP_DESCRIPTION)); + + // Finally, ensure that setting Locale to 'null' is takes the node back to its original locale + nodeService.setProperty(nodeRef2, ContentModel.PROP_LOCALE, null); + assertEquals( + "Node locale set to 'null' does nothing. ", + Locale.ITALY, nodeService.getProperty(nodeRef2, ContentModel.PROP_LOCALE)); + nodeService.removeProperty(nodeRef2, ContentModel.PROP_LOCALE); + assertEquals( + "Node locale removal does nothing. ", + Locale.ITALY, nodeService.getProperty(nodeRef2, ContentModel.PROP_LOCALE)); + + // Mass-set the properties, changing the locale in the process + Map props = nodeService.getProperties(nodeRef2); + props.put(ContentModel.PROP_LOCALE, Locale.GERMAN); + nodeService.setProperties(nodeRef2, props); + assertEquals( + "Node locale not set in setProperties(). ", + Locale.GERMAN, nodeService.getProperty(nodeRef2, ContentModel.PROP_LOCALE)); + } +} diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index b56c42eda5..185f7c1765 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -27,6 +27,7 @@ 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.Stack; @@ -74,6 +75,7 @@ import org.alfresco.util.ParameterCheck; import org.alfresco.util.PropertyMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; /** * Node service using database persistence layer to fulfill functionality @@ -318,6 +320,9 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // Ensure child uniqueness String newName = extractNameProperty(properties); + // Get the thread's locale + Locale locale = I18NUtil.getLocale(); + // create the node instance ChildAssocEntity assoc = nodeDAO.newNode( parentNodePair.getFirst(), @@ -326,6 +331,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl parentStoreRef, newUuid, nodeTypeQName, + locale, newName, properties); ChildAssociationRef childAssocRef = assoc.getRef(qnameDAO); @@ -728,7 +734,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl invokeBeforeUpdateNode(nodeRef); // Set the type - nodeDAO.updateNode(nodePair.getFirst(), null, null, typeQName); + nodeDAO.updateNode(nodePair.getFirst(), null, null, typeQName, null); // Add the default aspects and properties required for the given type. Existing values will not be overridden. addAspectsAndProperties(nodePair, typeQName, null, null, null, null, false); diff --git a/source/java/org/alfresco/repo/publishing/ChannelImpl.java b/source/java/org/alfresco/repo/publishing/ChannelImpl.java index e547d61edf..f1e0d93c1e 100644 --- a/source/java/org/alfresco/repo/publishing/ChannelImpl.java +++ b/source/java/org/alfresco/repo/publishing/ChannelImpl.java @@ -19,9 +19,14 @@ package org.alfresco.repo.publishing; +import java.io.Serializable; +import java.util.Map; + import org.alfresco.service.cmr.publishing.channels.Channel; import org.alfresco.service.cmr.publishing.channels.ChannelType; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; /** * @author Brian @@ -33,18 +38,20 @@ public class ChannelImpl implements Channel private final ChannelType channelType; private final String name; private final ChannelServiceImpl channelService; + private final NodeService nodeService; /** * @param channelType * @param name * @param channelService */ - public ChannelImpl(ChannelType channelType, NodeRef nodeRef, String name, ChannelServiceImpl channelService) + public ChannelImpl(ChannelType channelType, NodeRef nodeRef, String name, ChannelServiceImpl channelService, NodeService nodeService) { this.nodeRef = nodeRef; this.channelType = channelType; this.name = name; this.channelService = channelService; + this.nodeService = nodeService; } /* (non-Javadoc) @@ -74,4 +81,13 @@ public class ChannelImpl implements Channel return nodeRef; } + /* (non-Javadoc) + * @see org.alfresco.service.cmr.publishing.channels.Channel#getProperties() + */ + @Override + public Map getProperties() + { + return nodeService.getProperties(nodeRef); + } + } diff --git a/source/java/org/alfresco/repo/publishing/ChannelServiceImpl.java b/source/java/org/alfresco/repo/publishing/ChannelServiceImpl.java index 77527a2c03..3564842c04 100644 --- a/source/java/org/alfresco/repo/publishing/ChannelServiceImpl.java +++ b/source/java/org/alfresco/repo/publishing/ChannelServiceImpl.java @@ -27,6 +27,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.TreeMap; import org.alfresco.model.ContentModel; @@ -48,8 +49,9 @@ import org.alfresco.util.ParameterCheck; /** * @author Nick Smith + * @author Brian * @since 4.0 - * + * */ public class ChannelServiceImpl implements ChannelService { @@ -62,15 +64,17 @@ public class ChannelServiceImpl implements ChannelService private EnvironmentHelper environmentHelper; /** - * @param siteService the siteService to set + * @param siteService + * the siteService to set */ public void setSiteService(SiteService siteService) { this.siteService = siteService; } - + /** - * @param nodeService the nodeService to set + * @param nodeService + * the nodeService to set */ public void setNodeService(NodeService nodeService) { @@ -78,7 +82,8 @@ public class ChannelServiceImpl implements ChannelService } /** - * @param dictionaryService the dictionaryService to set + * @param dictionaryService + * the dictionaryService to set */ public void setDictionaryService(DictionaryService dictionaryService) { @@ -86,38 +91,39 @@ public class ChannelServiceImpl implements ChannelService } /** - * @param environmentHelper the environmentHelper to set + * @param environmentHelper + * the environmentHelper to set */ public void setEnvironmentHelper(EnvironmentHelper environmentHelper) { this.environmentHelper = environmentHelper; } - + /** - * {@inheritDoc} - */ + * {@inheritDoc} + */ public void register(ChannelType channelType) { ParameterCheck.mandatory("channelType", channelType); String id = channelType.getId(); - if(channelTypes.containsKey(id)) + if (channelTypes.containsKey(id)) { - throw new IllegalArgumentException("Channel type "+id+" is already registered!"); + throw new IllegalArgumentException("Channel type " + id + " is already registered!"); } channelTypes.put(id, channelType); } /** - * {@inheritDoc} - */ + * {@inheritDoc} + */ public List getChannelTypes() { return new ArrayList(channelTypes.values()); } /** - * {@inheritDoc} - */ + * {@inheritDoc} + */ public Channel createChannel(String siteId, String channelTypeId, String name, Map properties) { NodeRef channelContainer = getChannelContainer(siteId); @@ -143,7 +149,8 @@ public class ChannelServiceImpl implements ChannelService // Now create the corresponding channel nodes in each of the // configured environments - //FIXME: BJR: 20110506: Should we provide a means for supplying separate properties for each environment? + // FIXME: BJR: 20110506: Should we provide a means for supplying + // separate properties for each environment? Map environments = environmentHelper.getEnvironments(siteId); for (NodeRef environment : environments.values()) { @@ -155,17 +162,27 @@ public class ChannelServiceImpl implements ChannelService } /** - * {@inheritDoc} - */ + * {@inheritDoc} + */ public void deleteChannel(String siteId, String channelName) { - // TODO Auto-generated method stub - + Map environments = environmentHelper.getEnvironments(siteId); + Set containers = new HashSet(); + containers.add(getChannelContainer(siteId)); + containers.addAll(environments.values()); + for (NodeRef channelContainer : containers) + { + NodeRef channel = nodeService.getChildByName(channelContainer, ContentModel.ASSOC_CONTAINS, channelName); + if (channel != null) + { + nodeService.deleteNode(channel); + } + } } /** - * {@inheritDoc} - */ + * {@inheritDoc} + */ public List getChannels(String siteId) { ParameterCheck.mandatory("siteId", siteId); @@ -215,25 +232,75 @@ public class ChannelServiceImpl implements ChannelService Serializable channelTypeId = props.get(PublishingModel.PROP_CHANNEL_TYPE_ID); ChannelType channelType = channelTypes.get(channelTypeId); String name = (String) props.get(ContentModel.PROP_NAME); - return new ChannelImpl(channelType, nodeRef, name, this); + return new ChannelImpl(channelType, nodeRef, name, this, nodeService); } /** - * {@inheritDoc} - */ + * {@inheritDoc} + */ public ChannelType getChannelType(String id) { return channelTypes.get(id); } - + public NodeFinder getChannelDependancyNodeFinder() { return new ChannelDependancyNodeFinder(this); } - + public NodeFilter getChannelDependancyNodeFilter() { return new ChannelDependancyNodeFilter(this); } + /* + * (non-Javadoc) + * + * @see + * org.alfresco.service.cmr.publishing.channels.ChannelService#renameChannel + * (java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void renameChannel(String siteId, String oldName, String newName) + { + Map environments = environmentHelper.getEnvironments(siteId); + Set containers = new HashSet(); + containers.add(getChannelContainer(siteId)); + containers.addAll(environments.values()); + for (NodeRef channelContainer : containers) + { + NodeRef channel = nodeService.getChildByName(channelContainer, ContentModel.ASSOC_CONTAINS, oldName); + if (channel != null) + { + nodeService.setProperty(channel, ContentModel.PROP_NAME, newName); + nodeService.moveNode(channel, channelContainer, ContentModel.ASSOC_CONTAINS, QName.createQName( + NamespaceService.APP_MODEL_1_0_URI, newName)); + } + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.service.cmr.publishing.channels.ChannelService#updateChannel + * (java.lang.String, java.lang.String, java.util.Map) + */ + @Override + public void updateChannel(String siteId, String channelName, Map properties) + { + Map environments = environmentHelper.getEnvironments(siteId); + Set containers = new HashSet(); + containers.add(getChannelContainer(siteId)); + containers.addAll(environments.values()); + for (NodeRef channelContainer : containers) + { + NodeRef channel = nodeService.getChildByName(channelContainer, ContentModel.ASSOC_CONTAINS, channelName); + if (channel != null) + { + nodeService.setProperties(channel, properties); + } + } + } + } diff --git a/source/java/org/alfresco/repo/publishing/ChannelServiceImplIntegratedTest.java b/source/java/org/alfresco/repo/publishing/ChannelServiceImplIntegratedTest.java index 078c20161f..3d4fd75cc2 100644 --- a/source/java/org/alfresco/repo/publishing/ChannelServiceImplIntegratedTest.java +++ b/source/java/org/alfresco/repo/publishing/ChannelServiceImplIntegratedTest.java @@ -21,11 +21,15 @@ package org.alfresco.repo.publishing; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.io.Serializable; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.alfresco.model.ContentModel; @@ -35,10 +39,12 @@ import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.publishing.channels.Channel; import org.alfresco.service.cmr.publishing.channels.ChannelType; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.cmr.site.SiteVisibility; import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.namespace.QName; import org.alfresco.util.GUID; import org.junit.Before; import org.junit.Test; @@ -61,6 +67,7 @@ import org.springframework.transaction.annotation.Transactional; public class ChannelServiceImplIntegratedTest { private static boolean channelTypeRegistered = false; + private static String channelName = "Test Channel - Name"; @Autowired protected ApplicationContext applicationContext; @@ -77,6 +84,8 @@ public class ChannelServiceImplIntegratedTest private ChannelType mockedChannelType = mock(ChannelType.class); private String channelTypeName; + private EnvironmentHelper environmentHelper; + /** * @throws java.lang.Exception */ @@ -92,6 +101,7 @@ public class ChannelServiceImplIntegratedTest nodeService = serviceRegistry.getNodeService(); siteService = serviceRegistry.getSiteService(); + environmentHelper = (EnvironmentHelper) applicationContext.getBean("environmentHelper"); channelService = (ChannelServiceImpl) applicationContext.getBean("channelService"); siteId = GUID.generate(); siteService.createSite("test", siteId, "Test site created by ChannelServiceImplIntegratedTest", @@ -116,11 +126,80 @@ public class ChannelServiceImplIntegratedTest List channels = channelService.getChannels(siteId); assertTrue(channels.isEmpty()); - String channelName = "Test Channel - Name"; Channel channel = channelService.createChannel(siteId, channelTypeName, channelName, null); assertEquals(channelTypeName, channel.getChannelType().getId()); assertEquals(channelName, channel.getName()); assertTrue(nodeService.exists(channel.getNodeRef())); + + Map environments = environmentHelper.getEnvironments(siteId); + assertTrue(environments.size() > 0); + for (NodeRef envNodeRef : environments.values()) + { + assertNotNull(nodeService.getChildByName(envNodeRef, ContentModel.ASSOC_CONTAINS, channelName)); + } + } + + @Test + public void testDeleteChannel() throws Exception + { + testCreateChannel(); + + channelService.deleteChannel(siteId, channelName); + + List channels = channelService.getChannels(siteId); + assertTrue(channels.isEmpty()); + + Map environments = environmentHelper.getEnvironments(siteId); + assertTrue(environments.size() > 0); + for (NodeRef envNodeRef : environments.values()) + { + assertNull(nodeService.getChildByName(envNodeRef, ContentModel.ASSOC_CONTAINS, channelName)); + } + } + + @Test + public void testRenameChannel() throws Exception + { + String newChannelName = "New Channel Name"; + testCreateChannel(); + List channels = channelService.getChannels(siteId); + assertEquals(1, channels.size()); + channelService.renameChannel(siteId, channelName, newChannelName); + + channels = channelService.getChannels(siteId); + assertEquals(1, channels.size()); + Channel channel = channels.get(0); + assertEquals(newChannelName, channel.getName()); + Map environments = environmentHelper.getEnvironments(siteId); + assertTrue(environments.size() > 0); + for (NodeRef envNodeRef : environments.values()) + { + assertNull(nodeService.getChildByName(envNodeRef, ContentModel.ASSOC_CONTAINS, channelName)); + assertNotNull(nodeService.getChildByName(envNodeRef, ContentModel.ASSOC_CONTAINS, newChannelName)); + } + } + + @Test + public void testUpdateChannel() throws Exception + { + String newTitle = "This is my title"; + testCreateChannel(); + List channels = channelService.getChannels(siteId); + assertEquals(1, channels.size()); + + Channel channel = channels.get(0); + Map props = channel.getProperties(); + assertNull(props.get(ContentModel.PROP_TITLE)); + + props.put(ContentModel.PROP_TITLE, newTitle); + channelService.updateChannel(siteId, channelName, props); + + channels = channelService.getChannels(siteId); + assertEquals(1, channels.size()); + channel = channels.get(0); + Serializable title = channel.getProperties().get(ContentModel.PROP_TITLE); + assertNotNull(title); + assertEquals(newTitle, title); } @Test diff --git a/source/java/org/alfresco/repo/publishing/EnvironmentHelper.java b/source/java/org/alfresco/repo/publishing/EnvironmentHelper.java index ca652394e8..e4a879812d 100644 --- a/source/java/org/alfresco/repo/publishing/EnvironmentHelper.java +++ b/source/java/org/alfresco/repo/publishing/EnvironmentHelper.java @@ -19,7 +19,10 @@ package org.alfresco.repo.publishing; +import static org.alfresco.repo.publishing.PublishingModel.*; + import java.io.Serializable; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -30,6 +33,7 @@ import java.util.TreeMap; import org.alfresco.model.ContentModel; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.service.cmr.publishing.PublishingEventFilter; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -167,8 +171,10 @@ public class EnvironmentHelper if (queueAssoc == null) { // No publishing queue - queueAssoc = nodeService.createNode(environment, PublishingModel.ASSOC_PUBLISHING_QUEUE, QName.createQName( - PublishingModel.NAMESPACE, "publishingQueue"), PublishingModel.TYPE_PUBLISHING_QUEUE); + queueAssoc = nodeService.createNode(environment, + PublishingModel.ASSOC_PUBLISHING_QUEUE, + QName.createQName(PublishingModel.NAMESPACE, "publishingQueue"), + PublishingModel.TYPE_PUBLISHING_QUEUE); } return queueAssoc.getChildRef(); } @@ -211,4 +217,5 @@ public class EnvironmentHelper }, AuthenticationUtil.getSystemUserName()); } + } diff --git a/source/java/org/alfresco/repo/publishing/EnvironmentHelperTest.java b/source/java/org/alfresco/repo/publishing/EnvironmentHelperTest.java index 885832018f..55bc3e77a0 100644 --- a/source/java/org/alfresco/repo/publishing/EnvironmentHelperTest.java +++ b/source/java/org/alfresco/repo/publishing/EnvironmentHelperTest.java @@ -26,8 +26,9 @@ import static junit.framework.Assert.assertTrue; import java.util.Map; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.transaction.RetryingTransactionHelper; +import javax.annotation.Resource; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.repository.NodeRef; @@ -40,8 +41,6 @@ import org.alfresco.util.GUID; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.transaction.TransactionConfiguration; @@ -57,18 +56,16 @@ import org.springframework.transaction.annotation.Transactional; @Transactional public class EnvironmentHelperTest { - @Autowired - protected ApplicationContext applicationContext; + @Resource(name = "ServiceRegistry") protected ServiceRegistry serviceRegistry; - protected RetryingTransactionHelper retryingTransactionHelper; protected NodeService nodeService; protected WorkflowService workflowService; protected FileFolderService fileFolderService; protected SiteService siteService; - protected AuthenticationComponent authenticationComponent; - private String siteId; + @Resource(name="environmentHelper") private EnvironmentHelper environmentHelper; + private String siteId; /** * @throws java.lang.Exception @@ -76,20 +73,16 @@ public class EnvironmentHelperTest @Before public void setUp() throws Exception { - serviceRegistry = (ServiceRegistry) applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); - serviceRegistry.getAuthenticationService().authenticate("admin", "admin".toCharArray()); + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); - retryingTransactionHelper = serviceRegistry.getRetryingTransactionHelper(); fileFolderService = serviceRegistry.getFileFolderService(); workflowService = serviceRegistry.getWorkflowService(); nodeService = serviceRegistry.getNodeService(); siteService = serviceRegistry.getSiteService(); - environmentHelper = (EnvironmentHelper) applicationContext.getBean("environmentHelper"); siteId = GUID.generate(); siteService.createSite("test", siteId, "Test site created by ChannelServiceImplIntegratedTest", "Test site created by ChannelServiceImplIntegratedTest", SiteVisibility.PUBLIC); - } @Test diff --git a/source/java/org/alfresco/repo/publishing/EnvironmentImpl.java b/source/java/org/alfresco/repo/publishing/EnvironmentImpl.java index dc5a1ee620..1519d48b05 100644 --- a/source/java/org/alfresco/repo/publishing/EnvironmentImpl.java +++ b/source/java/org/alfresco/repo/publishing/EnvironmentImpl.java @@ -32,6 +32,7 @@ import org.alfresco.service.cmr.repository.NodeRef; /** * @author Brian + * @author Nick Smith * */ public class EnvironmentImpl implements Environment @@ -40,45 +41,50 @@ public class EnvironmentImpl implements Environment private NodeRef nodeRef; private String id; private EnvironmentHelper environmentHelper; - - /* (non-Javadoc) - * @see org.alfresco.service.cmr.publishing.Environment#checkPublishStatus(java.util.Collection) + private PublishingEventHelper publishingEventHelper; + + /** + * {@inheritDoc} */ - @Override public Map checkPublishStatus(Collection nodes) { // TODO Auto-generated method stub return null; } - /* (non-Javadoc) - * @see org.alfresco.service.cmr.publishing.Environment#getId() + /** + * {@inheritDoc} */ - @Override public String getId() { return id; } - /* (non-Javadoc) - * @see org.alfresco.service.cmr.publishing.Environment#getPublishingEvents(org.alfresco.service.cmr.publishing.PublishingEventFilter) + /** + * {@inheritDoc} */ - @Override public List getPublishingEvents(PublishingEventFilter filter) { - // TODO Auto-generated method stub - return null; + NodeRef queue = environmentHelper.getPublishingQueue(nodeRef); + return publishingEventHelper.findPublishingEvents(queue, filter); } - /* (non-Javadoc) - * @see org.alfresco.service.cmr.publishing.Environment#getPublishingQueue() + /** + * {@inheritDoc} */ - @Override public PublishingQueue getPublishingQueue() { return publishingQueueFactory.createPublishingQueueObject(nodeRef); } + /** + * {@inheritDoc} + */ + public PublishingEventFilter createPublishingEventFilter() + { + return new PublishingEventFilterImpl(); + } + /** * @param node */ @@ -103,5 +109,13 @@ public class EnvironmentImpl implements Environment { this.environmentHelper = environmentHelper; } + + /** + * @param publishingEventHelper the publishingEventHelper to set + */ + public void setPublishingEventHelper(PublishingEventHelper publishingEventHelper) + { + this.publishingEventHelper = publishingEventHelper; + } } diff --git a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ResultsPermissionChecked.java b/source/java/org/alfresco/repo/publishing/EnvironmentImplTest.java similarity index 60% rename from source/java/org/alfresco/repo/security/permissions/impl/acegi/ResultsPermissionChecked.java rename to source/java/org/alfresco/repo/publishing/EnvironmentImplTest.java index df7f362555..2f3ace2e51 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ResultsPermissionChecked.java +++ b/source/java/org/alfresco/repo/publishing/EnvironmentImplTest.java @@ -16,14 +16,26 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ -package org.alfresco.repo.security.permissions.impl.acegi; +package org.alfresco.repo.publishing; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** - * @author janv + * @author Nick Smith * @since 4.0 + * */ -public interface ResultsPermissionChecked +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "classpath:alfresco/application-context.xml"}) +public class EnvironmentImplTest { - public boolean permissionsChecked(); + @Test + public void test1() + { + + } } diff --git a/source/java/org/alfresco/repo/publishing/MutablePublishingEventImpl.java b/source/java/org/alfresco/repo/publishing/MutablePublishingEventImpl.java new file mode 100644 index 0000000000..cdc7ee1de5 --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/MutablePublishingEventImpl.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.publishing; + +import java.util.Calendar; + +import org.alfresco.service.cmr.publishing.MutablePublishingEvent; + +/** + * @author Nick Smith + * @since 4.0 + * + */ +public class MutablePublishingEventImpl extends PublishingEventImpl implements MutablePublishingEvent +{ + + /** + * @param publishingEventImpl + */ + public MutablePublishingEventImpl(PublishingEventImpl event) + { + super(event); + } + + /** + * {@inheritDoc} + */ + @Override + public void setScheduledTime(Calendar time) + { + this.scheduledTime.setTimeInMillis(time.getTimeInMillis()); + } + + /** + * {@inheritDoc} + */ + @Override + public void setComment(String comment) + { + this.comment = comment; + } + +} diff --git a/source/java/org/alfresco/repo/publishing/MutablePublishingPackageImpl.java b/source/java/org/alfresco/repo/publishing/MutablePublishingPackageImpl.java index ddf503bcd9..7ade5af8c5 100644 --- a/source/java/org/alfresco/repo/publishing/MutablePublishingPackageImpl.java +++ b/source/java/org/alfresco/repo/publishing/MutablePublishingPackageImpl.java @@ -24,18 +24,19 @@ import java.util.Collection; import org.alfresco.repo.transfer.manifest.TransferManifestNode; import org.alfresco.repo.transfer.manifest.TransferManifestNodeFactory; +import org.alfresco.repo.transfer.manifest.TransferManifestNormalNode; import org.alfresco.service.cmr.publishing.MutablePublishingPackage; import org.alfresco.service.cmr.repository.NodeRef; /** * @author Brian - * + * */ public class MutablePublishingPackageImpl extends PublishingPackageImpl implements MutablePublishingPackage { private TransferManifestNodeFactory transferManifestNodeFactory; - + /** * @param transferManifestNodeFactory */ @@ -46,15 +47,19 @@ public class MutablePublishingPackageImpl extends PublishingPackageImpl implemen } /** - * @param transferManifestNodeFactory the transferManifestNodeFactory to set + * @param transferManifestNodeFactory + * the transferManifestNodeFactory to set */ public void setTransferManifestNodeFactory(TransferManifestNodeFactory transferManifestNodeFactory) { this.transferManifestNodeFactory = transferManifestNodeFactory; } - /* (non-Javadoc) - * @see org.alfresco.service.cmr.publishing.MutablePublishingPackage#addNodesToPublish(org.alfresco.service.cmr.repository.NodeRef[]) + /* + * (non-Javadoc) + * + * @seeorg.alfresco.service.cmr.publishing.MutablePublishingPackage# + * addNodesToPublish(org.alfresco.service.cmr.repository.NodeRef[]) */ @Override public void addNodesToPublish(NodeRef... nodesToPublish) @@ -62,8 +67,11 @@ public class MutablePublishingPackageImpl extends PublishingPackageImpl implemen addNodesToPublish(Arrays.asList(nodesToPublish)); } - /* (non-Javadoc) - * @see org.alfresco.service.cmr.publishing.MutablePublishingPackage#addNodesToPublish(java.util.Collection) + /* + * (non-Javadoc) + * + * @seeorg.alfresco.service.cmr.publishing.MutablePublishingPackage# + * addNodesToPublish(java.util.Collection) */ @Override public void addNodesToPublish(Collection nodesToPublish) @@ -71,12 +79,19 @@ public class MutablePublishingPackageImpl extends PublishingPackageImpl implemen for (NodeRef nodeRef : nodesToPublish) { TransferManifestNode payload = transferManifestNodeFactory.createTransferManifestNode(nodeRef, null); - getEntryMap().put(nodeRef, new PublishingPackageEntryImpl(true, payload)); + if (TransferManifestNormalNode.class.isAssignableFrom(payload.getClass())) + { + getEntryMap().put(nodeRef, + new PublishingPackageEntryImpl(true, nodeRef, (TransferManifestNormalNode) payload)); + } } } - /* (non-Javadoc) - * @see org.alfresco.service.cmr.publishing.MutablePublishingPackage#addNodesToUnpublish(org.alfresco.service.cmr.repository.NodeRef[]) + /* + * (non-Javadoc) + * + * @seeorg.alfresco.service.cmr.publishing.MutablePublishingPackage# + * addNodesToUnpublish(org.alfresco.service.cmr.repository.NodeRef[]) */ @Override public void addNodesToUnpublish(NodeRef... nodesToRemove) @@ -84,17 +99,18 @@ public class MutablePublishingPackageImpl extends PublishingPackageImpl implemen addNodesToUnpublish(Arrays.asList(nodesToRemove)); } - /* (non-Javadoc) - * @see org.alfresco.service.cmr.publishing.MutablePublishingPackage#addNodesToUnpublish(java.util.Collection) + /* + * (non-Javadoc) + * + * @seeorg.alfresco.service.cmr.publishing.MutablePublishingPackage# + * addNodesToUnpublish(java.util.Collection) */ @Override public void addNodesToUnpublish(Collection nodesToRemove) { for (NodeRef nodeRef : nodesToRemove) { - //FIXME: BJR: 20110513: Handle unpublish case correctly - TransferManifestNode payload = transferManifestNodeFactory.createTransferManifestNode(nodeRef, null); - getEntryMap().put(nodeRef, new PublishingPackageEntryImpl(false, payload)); + getEntryMap().put(nodeRef, new PublishingPackageEntryImpl(false, nodeRef, null)); } } } diff --git a/source/java/org/alfresco/repo/publishing/NodeSnapshotTransferImpl.java b/source/java/org/alfresco/repo/publishing/NodeSnapshotTransferImpl.java new file mode 100644 index 0000000000..9effbe655f --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/NodeSnapshotTransferImpl.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.publishing; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.repo.transfer.manifest.TransferManifestNormalNode; +import org.alfresco.service.cmr.publishing.NodeSnapshot; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.namespace.QName; + +/** + * @author Brian + * + */ +public class NodeSnapshotTransferImpl implements NodeSnapshot +{ + private final TransferManifestNormalNode transferNode; + + /** + * @param transferNode + */ + public NodeSnapshotTransferImpl(TransferManifestNormalNode transferNode) + { + super(); + this.transferNode = transferNode; + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.publishing.NodeSnapshot#getAllParentAssocs() + */ + @Override + public List getAllParentAssocs() + { + return transferNode.getParentAssocs(); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.publishing.NodeSnapshot#getAspects() + */ + @Override + public Set getAspects() + { + return transferNode.getAspects(); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.publishing.NodeSnapshot#getNodeRef() + */ + @Override + public NodeRef getNodeRef() + { + return transferNode.getNodeRef(); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.publishing.NodeSnapshot#getOutboundPeerAssociations() + */ + @Override + public List getOutboundPeerAssociations() + { + return transferNode.getTargetAssocs(); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.publishing.NodeSnapshot#getPrimaryParentAssoc() + */ + @Override + public ChildAssociationRef getPrimaryParentAssoc() + { + return transferNode.getPrimaryParentAssoc(); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.publishing.NodeSnapshot#getPrimaryPath() + */ + @Override + public Path getPrimaryPath() + { + return transferNode.getParentPath(); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.publishing.NodeSnapshot#getProperties() + */ + @Override + public Map getProperties() + { + return transferNode.getProperties(); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.publishing.NodeSnapshot#getType() + */ + @Override + public QName getType() + { + return transferNode.getType(); + } +} diff --git a/source/java/org/alfresco/repo/publishing/PublishQueueImplTest.java b/source/java/org/alfresco/repo/publishing/PublishQueueImplTest.java new file mode 100644 index 0000000000..d8aa2ef339 --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/PublishQueueImplTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.publishing; + +import javax.annotation.Resource; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.publishing.Environment; +import org.alfresco.service.cmr.publishing.PublishingQueue; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.cmr.site.SiteVisibility; +import org.alfresco.util.GUID; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.transaction.TransactionConfiguration; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author Nick Smith + * @since 4.0 + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "classpath:alfresco/application-context.xml" }) +@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true) +@Transactional +public class PublishQueueImplTest +{ + private static final String environmentName = "live"; + + @Resource(name="publishingObjectFactory") + private PublishingObjectFactory factory; + + @Resource(name="SiteService") + private SiteService siteService; + + private PublishingQueue queue; + + private String siteId; + + @Test + public void testSchedulePublishingEvent() throws Exception + { + + } + + @Before + public void setUp() + { + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + + this.siteId = GUID.generate(); + siteService.createSite("test", siteId, + "Test site created by ChannelServiceImplIntegratedTest", + "Test site created by ChannelServiceImplIntegratedTest", + SiteVisibility.PUBLIC); + + Environment environment = factory.createEnvironmentObject(siteId, environmentName); + this.queue = environment.getPublishingQueue(); + } + + @After + public void tearDown() + { + siteService.deleteSite(siteId); + } +} diff --git a/source/java/org/alfresco/repo/publishing/PublishServiceImpl.java b/source/java/org/alfresco/repo/publishing/PublishServiceImpl.java index ff5d052b60..c977ebed93 100644 --- a/source/java/org/alfresco/repo/publishing/PublishServiceImpl.java +++ b/source/java/org/alfresco/repo/publishing/PublishServiceImpl.java @@ -29,11 +29,13 @@ import org.alfresco.service.cmr.repository.NodeRef; /** * @author Brian + * @author Nick Smith * */ public class PublishServiceImpl implements PublishingService { private EnvironmentFactory environmentFactory; + private PublishingEventHelper publishingEventHelper; /** * @param environmentFactory the environmentFactory to set @@ -43,42 +45,47 @@ public class PublishServiceImpl implements PublishingService this.environmentFactory = environmentFactory; } - /* (non-Javadoc) - * @see org.alfresco.service.cmr.publishing.PublishingService#getEnvironment(java.lang.String, java.lang.String) + /** + * @param publishingEventHelper the publishingEventHelper to set + */ + public void setPublishingEventHelper(PublishingEventHelper publishingEventHelper) + { + this.publishingEventHelper = publishingEventHelper; + } + + /** + * + * {@inheritDoc} */ - @Override public Environment getEnvironment(String siteId, String environmentName) { return environmentFactory.createEnvironmentObject(siteId, environmentName); } - /* (non-Javadoc) - * @see org.alfresco.service.cmr.publishing.PublishingService#getEnvironments(java.lang.String) + /** + * + * {@inheritDoc} */ - @Override public List getEnvironments(String siteId) { return environmentFactory.createEnvironmentObjects(siteId); } - /* (non-Javadoc) - * @see org.alfresco.service.cmr.publishing.PublishingService#getPublishingDependencies(org.alfresco.service.cmr.repository.NodeRef) + /** + * + * {@inheritDoc} */ - @Override public Set getPublishingDependencies(NodeRef node) { // TODO Auto-generated method stub return null; } - /* (non-Javadoc) - * @see org.alfresco.service.cmr.publishing.PublishingService#getPublishingEvent(java.lang.String) + /** + * {@inheritDoc} */ - @Override - public PublishingEvent getPublishingEvent(String id) - { - // TODO Auto-generated method stub - return null; - } - + public PublishingEvent getPublishingEvent(String id) + { + return publishingEventHelper.getPublishingEvent(id); + } } diff --git a/source/java/org/alfresco/repo/publishing/PublishingEventFilterImpl.java b/source/java/org/alfresco/repo/publishing/PublishingEventFilterImpl.java new file mode 100644 index 0000000000..2e2101d6ce --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/PublishingEventFilterImpl.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.publishing; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.alfresco.service.cmr.publishing.PublishingEventFilter; + +/** + * @author Nick Smith + * @since 4.0 + * + */ +public class PublishingEventFilterImpl implements PublishingEventFilter +{ + private Set ids = new HashSet(); + + /** + * {@inheritDoc} + */ + public PublishingEventFilter setIds(String... ids) + { + if(ids != null && ids.length>0) + { + this.ids.addAll(Arrays.asList(ids)); + } + return this; + } + + /** + * {@inheritDoc} + */ + public Set getIds() + { + return Collections.unmodifiableSet(ids); + } + +} diff --git a/source/java/org/alfresco/repo/publishing/PublishingEventHelper.java b/source/java/org/alfresco/repo/publishing/PublishingEventHelper.java index 0583d68d18..6edc0e91c8 100644 --- a/source/java/org/alfresco/repo/publishing/PublishingEventHelper.java +++ b/source/java/org/alfresco/repo/publishing/PublishingEventHelper.java @@ -19,36 +19,62 @@ package org.alfresco.repo.publishing; +import static org.alfresco.repo.publishing.PublishingModel.*; + +import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; +import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; +import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TimeZone; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.publishing.PublishingEvent; +import org.alfresco.service.cmr.publishing.PublishingEventFilter; +import org.alfresco.service.cmr.publishing.PublishingEvent.Status; import org.alfresco.service.cmr.publishing.PublishingPackage; import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.cmr.workflow.WorkflowTask; import org.alfresco.service.namespace.QName; import org.alfresco.util.GUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import com.google.common.base.Function; +import com.google.common.collect.Lists; + /** * @author Brian + * @author Nick Smith * */ public class PublishingEventHelper { private static final Log log = LogFactory.getLog(PublishingEventHelper.class); + public static final String WORKFLOW_DEFINITION_NAME = "publishWebContent"; private NodeService nodeService; private ContentService contentService; + private WorkflowService workflowService; private PublishingPackageSerializer serializer; + private String workflowEngineId; + /** * @param nodeService * the nodeService to set @@ -67,6 +93,22 @@ public class PublishingEventHelper this.contentService = contentService; } + /** + * @param workflowService the workflowService to set + */ + public void setWorkflowService(WorkflowService workflowService) + { + this.workflowService = workflowService; + } + + /** + * @param workflowEngineId the workflowEngineId to set + */ + public void setWorkflowEngineId(String workflowEngineId) + { + this.workflowEngineId = workflowEngineId; + } + /** * @param serializer the serializer to set */ @@ -75,7 +117,48 @@ public class PublishingEventHelper this.serializer = serializer; } - public NodeRef create(NodeRef queueId, PublishingPackage publishingPackage, Calendar schedule, String comment) + public PublishingEvent getPublishingEvent(NodeRef eventNode) throws AlfrescoRuntimeException + { + if(eventNode == null) + { + return null; + } + + Map props = nodeService.getProperties(eventNode); + Status status = (Status) props.get(PROP_PUBLISHING_EVENT_STATUS); + PublishingPackage publishingPackage = getPayLoad(eventNode); + Date createdTime = (Date) props.get(ContentModel.PROP_CREATED); + String creator = (String) props.get(ContentModel.PROP_CREATOR); + Date modifiedTime = (Date) props.get(ContentModel.PROP_MODIFIED); + String modifier = (String) props.get(ContentModel.PROP_MODIFIER); + String comment = (String) props.get(PROP_PUBLISHING_EVENT_COMMENT); + Calendar scheduledTime = getScheduledTime(props); + + // TODO Implement PublishingEvent dependencies. + Set dependingEvents = Collections.emptySet(); + Set eventsDependedOn = Collections.emptySet(); + Set nodesDependedOn = Collections.emptySet(); + + return new PublishingEventImpl(eventNode.toString(), + status, publishingPackage, + createdTime, creator, + modifiedTime,modifier, + dependingEvents, eventsDependedOn, + nodesDependedOn, scheduledTime, comment); + } + + public List getPublishingEvents(List eventNodes) + { + return Lists.transform(eventNodes, new Function() + { + public PublishingEvent apply(NodeRef eventNode) + { + return getPublishingEvent(eventNode); + } + }); + } + + public NodeRef createNode(NodeRef queueNode, PublishingPackage publishingPackage, String channelName, Calendar schedule, String comment) throws Exception { if (schedule == null) @@ -85,19 +168,121 @@ public class PublishingEventHelper Map props = new HashMap(); String name = GUID.generate(); props.put(ContentModel.PROP_NAME, name); - props.put(PublishingModel.PROP_PUBLISHING_EVENT_TIME, schedule.getTime()); + props.put(PROP_PUBLISHING_EVENT_TIME, schedule.getTime()); props.put(PublishingModel.PROP_PUBLISHING_EVENT_TIME_ZONE, schedule.getTimeZone().getID()); + props.put(PublishingModel.PROP_PUBLISHING_EVENT_CHANNEL, channelName); if (comment != null) { - props.put(PublishingModel.PROP_PUBLISHING_EVENT_COMMENT, comment); + props.put(PROP_PUBLISHING_EVENT_COMMENT, comment); } - ChildAssociationRef newAssoc = nodeService.createNode(queueId, PublishingModel.ASSOC_PUBLISHING_EVENT, QName - .createQName(PublishingModel.NAMESPACE, name), PublishingModel.TYPE_PUBLISHING_EVENT, props); + ChildAssociationRef newAssoc = nodeService.createNode(queueNode, + ASSOC_PUBLISHING_EVENT, + QName.createQName(NAMESPACE, name), + TYPE_PUBLISHING_EVENT, props); + NodeRef eventNode = newAssoc.getChildRef(); + setPayload(eventNode, publishingPackage); + return eventNode; + } + public List findPublishingEventNodes(NodeRef queue, PublishingEventFilter filter) + { + List results = new ArrayList(); + Set ids = filter.getIds(); + if(ids != null) + { + for (String id : ids) + { + NodeRef eventNode = new NodeRef(id); + if (nodeService.exists(eventNode)) + { + ChildAssociationRef parentAssoc = nodeService.getPrimaryParent(eventNode); + if (parentAssoc.getParentRef().equals(queue) + && ASSOC_PUBLISHING_EVENT.equals(parentAssoc.getTypeQName())) + { + results.add(eventNode); + } + } + } + } + return results; + } + + public List findPublishingEvents(NodeRef queue, PublishingEventFilter filter) + { + List eventNodes = findPublishingEventNodes(queue, filter); + return getPublishingEvents(eventNodes); + } + + public PublishingEvent getPublishingEvent(String id) + { + NodeRef eventNode = getPublishingEventNode(id); + return getPublishingEvent(eventNode); + } + + public NodeRef getPublishingEventNode(String id) + { + if (id != null && NodeRef.isNodeRef(id)) + { + NodeRef eventNode = new NodeRef(id); + if (nodeService.exists(eventNode) && TYPE_PUBLISHING_EVENT.equals(nodeService.getType(eventNode))) + { + return eventNode; + } + } + return null; + } + + public String startPublishingWorkflow(NodeRef eventNode, Calendar scheduledTime) + { + //Set parameters + Map parameters = new HashMap(); + parameters.put(PROP_WF_PUBLISHING_EVENT, eventNode); + //TODO Will this handle the timezone? + parameters.put(PROP_WF_SCHEDULED_PUBLISH_DATE, scheduledTime.getTime()); + + //Start workflow + WorkflowPath path = workflowService.startWorkflow(getPublshingDefinitionId(), parameters); + String instanceId = path.getInstance().getId(); + + //Set the Workflow Id on the event node. + nodeService.setProperty(eventNode, PROP_PUBLISHING_EVENT_WORKFLOW_ID, instanceId); + + //End the start task. + //TODO Replace with endStartTask() call after merge to HEAD. + List tasks = workflowService.getTasksForWorkflowPath(path.getId()); + WorkflowTask startTask = tasks.get(0); + workflowService.endTask(startTask.getId(), null); + return instanceId; + } + + private String getPublshingDefinitionId() + { + String definitionName = workflowEngineId + "$" + WORKFLOW_DEFINITION_NAME; + WorkflowDefinition definition = workflowService.getDefinitionByName(definitionName); + if(definition == null) + { + String msg = "The Web publishing workflow definition does not exist! Definition name: " + definitionName; + throw new AlfrescoRuntimeException(msg); + } + return definition.getId(); + } + + private Calendar getScheduledTime(Map eventProperties) + { + Date time = (Date) eventProperties.get(PROP_PUBLISHING_EVENT_TIME); + String timezone= (String) eventProperties.get(PROP_PUBLISHING_EVENT_TIME_ZONE); + Calendar scheduledTime = Calendar.getInstance(); + scheduledTime.setTime(time); + scheduledTime.setTimeZone(TimeZone.getTimeZone(timezone)); + return scheduledTime; + } + + private void setPayload(NodeRef eventNode, PublishingPackage publishingPackage) throws Exception + { try { - ContentWriter contentWriter = contentService.getWriter(newAssoc.getChildRef(), - PublishingModel.PROP_PUBLISHING_EVENT_PAYLOAD, true); + ContentWriter contentWriter = contentService.getWriter(eventNode, + PROP_PUBLISHING_EVENT_PAYLOAD, true); contentWriter.setEncoding("UTF-8"); OutputStream os = contentWriter.getContentOutputStream(); serializer.serialize(publishingPackage, os); @@ -109,6 +294,21 @@ public class PublishingEventHelper log.warn("Failed to serialize publishing package", ex); throw ex; } - return newAssoc.getChildRef(); } + + private PublishingPackage getPayLoad(NodeRef eventNode) throws AlfrescoRuntimeException + { + ContentReader contentReader = contentService.getReader(eventNode, PROP_PUBLISHING_EVENT_PAYLOAD); + InputStream input = contentReader.getContentInputStream(); + try + { + return serializer.deserialize(input); + } + catch (Exception ex) + { + String msg ="Failed to deserialize publishing package for PublishingEvent: " +eventNode; + throw new AlfrescoRuntimeException(msg, ex); + } + } + } diff --git a/source/java/org/alfresco/repo/publishing/PublishingEventHelperTest.java b/source/java/org/alfresco/repo/publishing/PublishingEventHelperTest.java new file mode 100644 index 0000000000..e276c9553b --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/PublishingEventHelperTest.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.publishing; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static org.alfresco.repo.publishing.PublishingModel.ASSOC_PUBLISHING_EVENT; +import static org.alfresco.repo.publishing.PublishingModel.PROP_PUBLISHING_EVENT_CHANNEL; +import static org.alfresco.repo.publishing.PublishingModel.PROP_PUBLISHING_EVENT_COMMENT; +import static org.alfresco.repo.publishing.PublishingModel.PROP_PUBLISHING_EVENT_TIME; +import static org.alfresco.repo.publishing.PublishingModel.PROP_PUBLISHING_EVENT_TIME_ZONE; +import static org.alfresco.repo.publishing.PublishingModel.PROP_PUBLISHING_EVENT_PAYLOAD; +import static org.alfresco.repo.publishing.PublishingModel.*; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyMap; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; + +import javax.annotation.Resource; + +import org.alfresco.service.cmr.publishing.PublishingEvent.Status; +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.publishing.PublishingEvent; +import org.alfresco.service.cmr.publishing.PublishingPackage; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Nick Smith + * @since 4.0 + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "classpath:test/alfresco/test-web-publishing-context.xml"}) +public class PublishingEventHelperTest +{ + @Resource(name="publishingEventHelper") + PublishingEventHelper helper; + + @Resource(name="nodeService") + NodeService nodeService; + + @Resource(name="contentService") + ContentService contentService; + + @Test + public void testGetPublishingEventNode() throws Exception + { + NodeRef eventNode= helper.getPublishingEventNode(null); + assertNull("If id is null event shoudl be null!", eventNode); + + eventNode= helper.getPublishingEventNode("foo"); + assertNull("If id is invalid event shoudl be null!", eventNode); + + String nonExistantId = "foo://bar/nonExistantId"; + eventNode= helper.getPublishingEventNode(nonExistantId); + assertNull("If event node does not exist event shoudl be null!", eventNode); + + String nonPublishingEventId = "foo://bar/nonPublishingEventId"; + NodeRef nonPublishingEventNode = new NodeRef(nonPublishingEventId); + when(nodeService.exists(nonPublishingEventNode)).thenReturn(true); + + eventNode= helper.getPublishingEventNode(nonPublishingEventId); + assertNull("Event shoudl exist!", eventNode); + + String publishingEventId = "foo://bar/publishingEventId"; + NodeRef publishingEventNode = new NodeRef(publishingEventId); + when(nodeService.exists(publishingEventNode)).thenReturn(true); + when(nodeService.getType(publishingEventNode)).thenReturn(TYPE_PUBLISHING_EVENT); + + eventNode= helper.getPublishingEventNode(publishingEventId); + assertNotNull("Event shoudl exist!", eventNode); + } + + @Test + public void testGetPublishingEvent() throws Exception + { + // Mock up ContentReader to do nothing. Not testing payload deserialization. + ContentReader reader = mock(ContentReader.class); + InputStream inputStream = mock(InputStream.class); + when(reader.getContentInputStream()).thenReturn(inputStream); + when(contentService.getReader(any(NodeRef.class), any(QName.class))) + .thenReturn(reader); + PublishingPackageSerializer serializer = mock(PublishingPackageSerializer.class); + helper.setSerializer(serializer); + + PublishingEvent result = helper.getPublishingEvent((NodeRef)null); + assertNull(result); + + String comment = "The comment"; + Status status = Status.COMPLETE; + Date modified= new Date(); + Date created = new Date(modified.getTime()-3600000); + String creatorName = "The creator"; + String modifierName = "The modifier"; + Calendar schedule = Calendar.getInstance(); + schedule.add(Calendar.MONTH, 6); + Date scheduledTime = schedule.getTime(); + String scheduledTimeZone = schedule.getTimeZone().getID(); + + // Mock up node properties. + Map props = new HashMap(); + props.put(PROP_PUBLISHING_EVENT_COMMENT, comment); + props.put(PROP_PUBLISHING_EVENT_STATUS, status); + props.put(PROP_PUBLISHING_EVENT_TIME, scheduledTime); + props.put(PROP_PUBLISHING_EVENT_TIME_ZONE, scheduledTimeZone); + props.put(ContentModel.PROP_CREATED, created); + props.put(ContentModel.PROP_CREATOR, creatorName); + props.put(ContentModel.PROP_MODIFIED, modified); + props.put(ContentModel.PROP_MODIFIER, modifierName); + + NodeRef eventNode = new NodeRef("foo://bar/eventNode"); + when(nodeService.getProperties(eventNode)).thenReturn(props); + + result = helper.getPublishingEvent(eventNode); + assertEquals(eventNode.toString(), result.getId()); + assertEquals(comment, result.getComment()); + assertEquals(status, result.getStatus()); + assertEquals(schedule, result.getScheduledTime()); + assertEquals(created, result.getCreatedTime()); + assertEquals(creatorName, result.getCreator()); + assertEquals(modified, result.getModifiedTime()); + assertEquals(modifierName, result.getModifier()); + } + + @Test + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void testCreateNode() throws Exception + { + // Mock serializer since this behaviour is already tested in PublishingPackageSerializerTest. + ContentWriter writer = mock(ContentWriter.class); + when(contentService.getWriter(any(NodeRef.class), eq(PROP_PUBLISHING_EVENT_PAYLOAD), eq(true))) + .thenReturn(writer); + OutputStream outputStream = mock(OutputStream.class); + when(writer.getContentOutputStream()).thenReturn(outputStream); + PublishingPackageSerializer serializer = mock(PublishingPackageSerializer.class); + helper.setSerializer(serializer); + + NodeRef queue = new NodeRef("foo://bar/queue"); + NodeRef event = new NodeRef("foo://bar/event"); + + ChildAssociationRef childAssoc = new ChildAssociationRef(ASSOC_PUBLISHING_EVENT, queue, null, event); + when(nodeService.createNode(any(NodeRef.class), any(QName.class), any(QName.class), any(QName.class), anyMap())) + .thenReturn(childAssoc); + + PublishingPackage pckg = null; + String channelName = "The channel"; + Calendar schedule = Calendar.getInstance(); + String comment = "The comment"; + + NodeRef result = helper.createNode(queue, pckg, channelName, schedule, comment); + assertEquals(event, result); + + ArgumentCaptor argument = ArgumentCaptor.forClass(Map.class); + verify(nodeService) + .createNode(eq(queue), eq(ASSOC_PUBLISHING_EVENT), + any(QName.class), eq(TYPE_PUBLISHING_EVENT), + argument.capture()); + Map props = argument.getValue(); + + assertNotNull(props.get(ContentModel.PROP_NAME)); + assertEquals(channelName, props.get(PROP_PUBLISHING_EVENT_CHANNEL)); + assertEquals(comment, props.get(PROP_PUBLISHING_EVENT_COMMENT)); + assertEquals(schedule.getTime(), props.get(PROP_PUBLISHING_EVENT_TIME)); + assertEquals(schedule.getTimeZone().getID(), props.get(PROP_PUBLISHING_EVENT_TIME_ZONE)); + } +} diff --git a/source/java/org/alfresco/repo/publishing/PublishingEventImpl.java b/source/java/org/alfresco/repo/publishing/PublishingEventImpl.java new file mode 100644 index 0000000000..ca3bf9d1b6 --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/PublishingEventImpl.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.publishing; + +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.Set; + +import org.alfresco.service.cmr.publishing.MutablePublishingEvent; +import org.alfresco.service.cmr.publishing.PublishingEvent; +import org.alfresco.service.cmr.publishing.PublishingPackage; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * @author Nick Smith + * @since 4.0 + * + */ +public class PublishingEventImpl implements PublishingEvent +{ + private final String id; + private final Status status; + private final PublishingPackage publishingPackage; + private final Date createdTime; + private final String creator; + private final Date modifiedTime; + private final String modifier; + private final Set dependingEvents; + private final Set eventsDependedOn; + private final Set nodesDependedOn; + protected final Calendar scheduledTime; + protected String comment; + + + public PublishingEventImpl(String id, + Status status, PublishingPackage publishingPackage, + Date createdTime,String creator, + Date modifiedTime, String modifier, + Set dependingEvents, + Set eventsDependedOn, + Set nodesDependedOn, + Calendar scheduledTime, String comment) + { + this.id = id; + this.status = status; + this.publishingPackage = publishingPackage; + this.createdTime = createdTime; + this.creator = creator; + this.modifiedTime = modifiedTime; + this.modifier = modifier; + this.dependingEvents = Collections.unmodifiableSet(dependingEvents); + this.eventsDependedOn = Collections.unmodifiableSet(eventsDependedOn); + this.nodesDependedOn = Collections.unmodifiableSet(nodesDependedOn); + this.scheduledTime = scheduledTime; + this.comment = comment; + } + + public PublishingEventImpl(PublishingEvent event) + { + this(event.getId(), + event.getStatus(), event.getPackage(), + event.getCreatedTime(), event.getCreator(), + event.getModifiedTime(), event.getModifier(), + event.getDependingEvents(), event.getEventsDependedOn(), + event.getNodesDependedOn(), + event.getScheduledTime(), event.getComment()); + } + + /** + * {@inheritDoc} + */ + @Override + public String getId() + { + return id; + } + + /** + * {@inheritDoc} + */ + @Override + public Status getStatus() + { + return status; + } + + /** + * {@inheritDoc} + */ + @Override + public Calendar getScheduledTime() + { + return (Calendar) scheduledTime.clone(); + } + + /** + * {@inheritDoc} + */ + @Override + public PublishingPackage getPackage() + { + return publishingPackage; + } + + /** + * {@inheritDoc} + */ + @Override + public Date getCreatedTime() + { + return new Date(createdTime.getTime()); + } + + /** + * {@inheritDoc} + */ + @Override + public String getCreator() + { + return creator; + } + + /** + * {@inheritDoc} + */ + @Override + public Date getModifiedTime() + { + return new Date(modifiedTime.getTime()); + } + + /** + * {@inheritDoc} + */ + @Override + public String getModifier() + { + return modifier; + } + + /** + * {@inheritDoc} + */ + @Override + public String getComment() + { + return comment; + } + + /** + * {@inheritDoc} + */ + @Override + public Set getDependingEvents() + { + return dependingEvents; + } + + /** + * {@inheritDoc} + */ + @Override + public Set getEventsDependedOn() + { + return eventsDependedOn; + } + + /** + * {@inheritDoc} + */ + @Override + public Set getNodesDependedOn() + { + return nodesDependedOn; + } + + /** + * {@inheritDoc} + */ + @Override + public MutablePublishingEvent edit() + { + return new MutablePublishingEventImpl(this); + } + +} diff --git a/source/java/org/alfresco/repo/publishing/PublishingModel.java b/source/java/org/alfresco/repo/publishing/PublishingModel.java index d7a64bf7c4..150e666d9e 100644 --- a/source/java/org/alfresco/repo/publishing/PublishingModel.java +++ b/source/java/org/alfresco/repo/publishing/PublishingModel.java @@ -51,6 +51,8 @@ public interface PublishingModel public static final QName PROP_PUBLISHING_EVENT_TIME = QName.createQName(NAMESPACE, "publishingEventTime"); public static final QName PROP_PUBLISHING_EVENT_TIME_ZONE = QName.createQName(NAMESPACE, "publishingEventTimeZone"); public static final QName PROP_PUBLISHING_EVENT_COMMENT = QName.createQName(NAMESPACE, "publishingEventComment"); + public static final QName PROP_PUBLISHING_EVENT_CHANNEL= QName.createQName(NAMESPACE, "publishingEventChannel"); + public static final QName PROP_PUBLISHING_EVENT_WORKFLOW_ID= QName.createQName(NAMESPACE, "publishingEventWorkflowId"); public static final QName PROP_PUBLISHING_EVENT_PAYLOAD = QName.createQName(NAMESPACE, "publishingEventPayload"); public static final QName PROP_PUBLISHING_EVENT_NODES_TO_PUBLISH = QName.createQName(NAMESPACE, "publishingEventNodesToPublish"); @@ -63,7 +65,7 @@ public interface PublishingModel public static final String PROPVAL_PUBLISHING_EVENT_STATUS_FAILED = "FAILED"; public static final QName ASSOC_PUBLISHING_QUEUE = QName.createQName(NAMESPACE, "publishingQueueAssoc"); - public static final QName ASSOC_PUBLISHING_EVENT = QName.createQName(NAMESPACE, "publishingEvent"); + public static final QName ASSOC_PUBLISHING_EVENT = QName.createQName(NAMESPACE, "publishingEventAssoc"); // Workflow Properties public static final QName PROP_WF_PUBLISHING_EVENT= QName.createQName(WF_NAMESPACE, "publishingEvent"); diff --git a/source/java/org/alfresco/repo/publishing/PublishingObjectFactory.java b/source/java/org/alfresco/repo/publishing/PublishingObjectFactory.java index 0187a9639e..435f3fbdad 100644 --- a/source/java/org/alfresco/repo/publishing/PublishingObjectFactory.java +++ b/source/java/org/alfresco/repo/publishing/PublishingObjectFactory.java @@ -82,6 +82,7 @@ public class PublishingObjectFactory implements EnvironmentFactory, PublishingQu environment.setNodeRef(node); environment.setPublishingQueueFactory(this); environment.setEnvironmentHelper(environmentHelper); + environment.setPublishingEventHelper(publishingEventHelper); return environment; } diff --git a/source/java/org/alfresco/repo/publishing/PublishingPackageImpl.java b/source/java/org/alfresco/repo/publishing/PublishingPackageImpl.java index 588daf5558..d9acd9f4b5 100644 --- a/source/java/org/alfresco/repo/publishing/PublishingPackageImpl.java +++ b/source/java/org/alfresco/repo/publishing/PublishingPackageImpl.java @@ -25,6 +25,8 @@ import java.util.HashMap; import java.util.Map; import org.alfresco.repo.transfer.manifest.TransferManifestNode; +import org.alfresco.repo.transfer.manifest.TransferManifestNormalNode; +import org.alfresco.service.cmr.publishing.NodeSnapshot; import org.alfresco.service.cmr.publishing.PublishingPackage; import org.alfresco.service.cmr.publishing.PublishingPackageEntry; import org.alfresco.service.cmr.repository.NodeRef; @@ -61,15 +63,15 @@ public class PublishingPackageImpl implements PublishingPackage { private final boolean publish; private final NodeRef nodeRef; - private final TransferManifestNode payload; + private final TransferManifestNormalNode payload; /** * */ - public PublishingPackageEntryImpl(boolean publish, TransferManifestNode payload) + public PublishingPackageEntryImpl(boolean publish, NodeRef nodeRef, TransferManifestNormalNode payload) { this.publish = publish; - this.nodeRef = payload.getNodeRef(); + this.nodeRef = nodeRef; this.payload = payload; } @@ -83,9 +85,10 @@ public class PublishingPackageImpl implements PublishingPackage return nodeRef; } - /** - * @return the publish + /* (non-Javadoc) + * @see org.alfresco.service.cmr.publishing.PublishingPackageEntry#isPublish() */ + @Override public boolean isPublish() { return publish; @@ -98,5 +101,14 @@ public class PublishingPackageImpl implements PublishingPackage { return payload; } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.publishing.PublishingPackageEntry#getSnapshot() + */ + @Override + public NodeSnapshot getSnapshot() + { + return new NodeSnapshotTransferImpl(payload); + } } } diff --git a/source/java/org/alfresco/repo/publishing/PublishingPackageSerializerTest.java b/source/java/org/alfresco/repo/publishing/PublishingPackageSerializerTest.java index 484550bfb0..7021d72523 100644 --- a/source/java/org/alfresco/repo/publishing/PublishingPackageSerializerTest.java +++ b/source/java/org/alfresco/repo/publishing/PublishingPackageSerializerTest.java @@ -19,11 +19,11 @@ package org.alfresco.repo.publishing; -import static org.mockito.Mockito.*; import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertNotSame; import static junit.framework.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -35,11 +35,13 @@ import java.util.List; import java.util.Map; import java.util.Set; +import javax.annotation.Resource; + import org.alfresco.model.ContentModel; import org.alfresco.repo.publishing.PublishingPackageImpl.PublishingPackageEntryImpl; import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transfer.manifest.TransferManifestNode; import org.alfresco.repo.transfer.manifest.TransferManifestNodeFactory; import org.alfresco.repo.transfer.manifest.TransferManifestNormalNode; import org.alfresco.service.ServiceRegistry; @@ -63,10 +65,7 @@ import org.alfresco.util.GUID; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.transaction.TransactionConfiguration; @@ -83,7 +82,6 @@ import org.springframework.transaction.annotation.Transactional; public class PublishingPackageSerializerTest { @Autowired - protected ApplicationContext applicationContext; protected ServiceRegistry serviceRegistry; protected RetryingTransactionHelper retryingTransactionHelper; protected NodeService nodeService; @@ -91,12 +89,18 @@ public class PublishingPackageSerializerTest protected FileFolderService fileFolderService; protected SiteService siteService; - protected AuthenticationComponent authenticationComponent; - private String siteId; - private EnvironmentHelper environmentHelper; + @Resource(name="publishingService") private PublishingService publishingService; + + @Resource(name="authenticationComponent") + protected AuthenticationComponent authenticationComponent; + + @Resource(name="publishingPackageSerializer") private StandardPublishingPackageSerializer serializer; + + private String siteId; private TransferManifestNormalNode normalNode1; +// private EnvironmentHelper environmentHelper; /** * @throws java.lang.Exception @@ -104,18 +108,15 @@ public class PublishingPackageSerializerTest @Before public void setUp() throws Exception { - serviceRegistry = (ServiceRegistry) applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); - serviceRegistry.getAuthenticationService().authenticate("admin", "admin".toCharArray()); - + String adminUser = AuthenticationUtil.getAdminUserName(); + AuthenticationUtil.setFullyAuthenticatedUser(adminUser); retryingTransactionHelper = serviceRegistry.getRetryingTransactionHelper(); fileFolderService = serviceRegistry.getFileFolderService(); workflowService = serviceRegistry.getWorkflowService(); nodeService = serviceRegistry.getNodeService(); siteService = serviceRegistry.getSiteService(); - environmentHelper = (EnvironmentHelper) applicationContext.getBean("environmentHelper"); - publishingService = (PublishingService) applicationContext.getBean("publishingService"); - serializer = (StandardPublishingPackageSerializer) applicationContext.getBean("publishingPackageSerializer"); +// environmentHelper = (EnvironmentHelper) applicationContext.getBean("environmentHelper"); siteId = GUID.generate(); siteService.createSite("test", siteId, "Test site created by PublishingPackageSerializerTest", "Test site created by PublishingPackageSerializerTest", SiteVisibility.PUBLIC); @@ -176,14 +177,8 @@ public class PublishingPackageSerializerTest TransferManifestNodeFactory mockTMNFactory = mock(TransferManifestNodeFactory.class); packageImpl.setTransferManifestNodeFactory(mockTMNFactory); - doAnswer(new Answer() - { - @Override - public TransferManifestNode answer(InvocationOnMock invocation) throws Throwable - { - return normalNode1; - } - }).when(mockTMNFactory).createTransferManifestNode(any(NodeRef.class), any(TransferDefinition.class)); + when(mockTMNFactory.createTransferManifestNode(any(NodeRef.class), any(TransferDefinition.class))) + .thenReturn(normalNode1); packageImpl.addNodesToPublish(new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE,"Hello")); diff --git a/source/java/org/alfresco/repo/publishing/PublishingQueueImpl.java b/source/java/org/alfresco/repo/publishing/PublishingQueueImpl.java index 5725f44875..c405fc870c 100644 --- a/source/java/org/alfresco/repo/publishing/PublishingQueueImpl.java +++ b/source/java/org/alfresco/repo/publishing/PublishingQueueImpl.java @@ -75,11 +75,12 @@ public class PublishingQueueImpl implements PublishingQueue * java.util.Calendar, java.lang.String) */ @Override - public String scheduleNewEvent(PublishingPackage publishingPackage, Calendar schedule, String comment) + public String scheduleNewEvent(PublishingPackage publishingPackage, String channelName, Calendar schedule, String comment) { try { - NodeRef eventNode = publishingEventHelper.create(nodeRef, publishingPackage, schedule, comment); + NodeRef eventNode = publishingEventHelper.createNode(nodeRef, publishingPackage, channelName, schedule, comment); + publishingEventHelper.startPublishingWorkflow(eventNode, schedule); return eventNode.toString(); } catch (Exception ex) diff --git a/source/java/org/alfresco/repo/publishing/StandardPublishingPackageSerializer.java b/source/java/org/alfresco/repo/publishing/StandardPublishingPackageSerializer.java index 06d8b88e12..19a94c82ed 100644 --- a/source/java/org/alfresco/repo/publishing/StandardPublishingPackageSerializer.java +++ b/source/java/org/alfresco/repo/publishing/StandardPublishingPackageSerializer.java @@ -150,7 +150,7 @@ public class StandardPublishingPackageSerializer implements PublishingPackageSer @Override public void processTransferManifestNode(TransferManifestNormalNode node) { - entries.put(node.getNodeRef(), new PublishingPackageEntryImpl(true, node)); + entries.put(node.getNodeRef(), new PublishingPackageEntryImpl(true, node.getNodeRef(), node)); } /* (non-Javadoc) @@ -159,7 +159,7 @@ public class StandardPublishingPackageSerializer implements PublishingPackageSer @Override public void processTransferManifestNode(TransferManifestDeletedNode node) { - entries.put(node.getNodeRef(), new PublishingPackageEntryImpl(false, node)); + entries.put(node.getNodeRef(), new PublishingPackageEntryImpl(false, node.getNodeRef(), null)); } /* (non-Javadoc) diff --git a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java b/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java index 83cd7061fd..0eb67b714e 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java @@ -38,6 +38,7 @@ import net.sf.acegisecurity.afterinvocation.AfterInvocationProvider; import org.alfresco.cmis.CMISResultSet; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.query.PagingResults; +import org.alfresco.query.PermissionedResults; import org.alfresco.repo.search.SimpleResultSetMetaData; import org.alfresco.repo.search.impl.lucene.PagingLuceneResultSet; import org.alfresco.repo.search.impl.querymodel.QueryEngineResults; @@ -273,10 +274,10 @@ public class ACLEntryAfterInvocationProvider implements AfterInvocationProvider, } else if (PagingResults.class.isAssignableFrom(returnedObject.getClass())) { - if (ResultsPermissionChecked.class.isAssignableFrom(returnedObject.getClass()) && - (! ((ResultsPermissionChecked)returnedObject).permissionsChecked())) + if (PermissionedResults.class.isAssignableFrom(returnedObject.getClass()) && + (! ((PermissionedResults)returnedObject).permissionsApplied())) { - throw new AlfrescoRuntimeException("Not implemented yet"); + throw new AlfrescoRuntimeException("Not implemented"); /* if (log.isDebugEnabled()) { @@ -289,7 +290,7 @@ public class ACLEntryAfterInvocationProvider implements AfterInvocationProvider, { if (log.isDebugEnabled()) { - log.debug("Paging Results access - already checked permissions"); + log.debug("Paging Results access - already checked permissions for " + object.getClass().getName()); } return returnedObject; @@ -341,11 +342,20 @@ public class ACLEntryAfterInvocationProvider implements AfterInvocationProvider, } else if (Collection.class.isAssignableFrom(returnedObject.getClass())) { - if (log.isDebugEnabled()) + if (PermissionedResults.class.isAssignableFrom(returnedObject.getClass()) && + ((PermissionedResults)returnedObject).permissionsApplied()) { - log.debug("Collection Access"); + // Already checked - don't need to re-check (eg. WrappedList - used by unsorted GetChildren CQ) + return returnedObject; + } + else + { + if (log.isDebugEnabled()) + { + log.debug("Collection Access"); + } + return decide(authentication, object, config, (Collection) returnedObject); } - return decide(authentication, object, config, (Collection) returnedObject); } else if (returnedObject.getClass().isArray()) { @@ -873,9 +883,9 @@ public class ACLEntryAfterInvocationProvider implements AfterInvocationProvider, boolean cutoff = false; int maxChecks = Integer.MAX_VALUE; - if (returnedObject instanceof MaxChecksCollection) + if ((returnedObject instanceof WrappedList) && ((WrappedList)returnedObject).getMaxChecks() > 0) { - maxChecks = ((MaxChecksCollection)returnedObject).getMaxChecks(); + maxChecks = ((WrappedList)returnedObject).getMaxChecks(); } Iterator iterator = returnedObject.iterator(); @@ -999,9 +1009,10 @@ public class ACLEntryAfterInvocationProvider implements AfterInvocationProvider, ; } - if (cutoff && (returnedObject instanceof MaxChecksCollection)) + if (returnedObject instanceof WrappedList) { - ((MaxChecksCollection)returnedObject).setCutoff(cutoff); + ((WrappedList)returnedObject).setHasMoreItems(cutoff); + ((WrappedList)returnedObject).setPermissionsApplied(true); } return returnedObject; diff --git a/source/java/org/alfresco/repo/security/permissions/impl/acegi/AbstractCannedQueryPermissions.java b/source/java/org/alfresco/repo/security/permissions/impl/acegi/AbstractCannedQueryPermissions.java index 6bf2447e0f..09746681a9 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/acegi/AbstractCannedQueryPermissions.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/acegi/AbstractCannedQueryPermissions.java @@ -21,7 +21,6 @@ package org.alfresco.repo.security.permissions.impl.acegi; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import net.sf.acegisecurity.Authentication; @@ -94,7 +93,12 @@ public abstract class AbstractCannedQueryPermissions extends AbstractCannedQu } @Override - public Boolean hasMoreItems() + public boolean hasMoreItems() + { + return false; + } + + public boolean permissionsApplied() { return false; } @@ -109,12 +113,7 @@ public abstract class AbstractCannedQueryPermissions extends AbstractCannedQu Authentication authentication = (((SecureContext) context).getAuthentication()); ConfigAttributeDefinition cad = methodSecurityInterceptor.getObjectDefinitionSource().getAttributes(new InternalMethodInvocation(method)); - MaxChecksCollection c = (MaxChecksCollection)methodSecurityInterceptor.getAfterInvocationManager().decide(authentication, null, cad, new MaxChecksCollection((Collection)results, maxChecks)); - - final List permissionCheckedResults = (List) c.getWrapped(); - - final boolean cutoff = c.isCutoff(); - final int count = permissionCheckedResults.size(); + final WrappedList wl = (WrappedList)methodSecurityInterceptor.getAfterInvocationManager().decide(authentication, null, cad, new WrappedList(results, maxChecks)); // final result ret = new PagingResults() @@ -128,25 +127,32 @@ public abstract class AbstractCannedQueryPermissions extends AbstractCannedQu @Override public Pair getTotalResultCount() { - return new Pair(count, ((! cutoff) ? count : null)); + int count = wl.size(); + return new Pair(count, ((! wl.hasMoreItems()) ? count : null)); } @Override public List getPage() { - return permissionCheckedResults; + return wl; } @Override - public Boolean hasMoreItems() + public boolean hasMoreItems() { - return (! cutoff); + return wl.hasMoreItems(); // if hasMoreItems then implies cutoff + } + + @Override + public boolean permissionsApplied() + { + return wl.permissionsApplied(); } }; if (logger.isTraceEnabled()) { - logger.trace("applyPermissions: "+count+" items in "+(System.currentTimeMillis()-start)+" msecs"); + logger.trace("applyPermissions: "+wl.size()+" items in "+(System.currentTimeMillis()-start)+" msecs"); } return ret; diff --git a/source/java/org/alfresco/repo/security/permissions/impl/acegi/MaxChecksCollection.java b/source/java/org/alfresco/repo/security/permissions/impl/acegi/MaxChecksCollection.java deleted file mode 100644 index 0546656b11..0000000000 --- a/source/java/org/alfresco/repo/security/permissions/impl/acegi/MaxChecksCollection.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2005-2011 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ -package org.alfresco.repo.security.permissions.impl.acegi; - -import java.util.Collection; -import java.util.Iterator; - -/** - * Wrapped result set with max checks cutoff (ie. max items to permission check) - * - * Note: cutoff will be set to true if results have been cutoff (due to max permission checks count and/or time) - * - * @author janv - * @since 4.0 - */ -public class MaxChecksCollection implements Collection -{ - private Collection wrapped; - private int maxChecks; - private boolean cutoff; - - public MaxChecksCollection(Collection wrapped, int maxChecks) - { - this.wrapped = wrapped; - this.maxChecks = maxChecks; - } - - public int getMaxChecks() - { - return maxChecks; - } - - public boolean isCutoff() - { - return cutoff; - } - - public void setCutoff(boolean cutoff) - { - this.cutoff = cutoff; - } - - - public Collection getWrapped() - { - return wrapped; - } - - @Override - public Iterator iterator() - { - return wrapped.iterator(); - } - - @Override - public boolean add(Object e) - { - return wrapped.add(e); - } - - @SuppressWarnings("unchecked") - @Override - public boolean addAll(Collection c) - { - return wrapped.addAll(c); - } - - @Override - public void clear() - { - wrapped.clear(); - } - - @Override - public boolean contains(Object o) - { - return wrapped.contains(o); - } - - @Override - public boolean containsAll(Collection c) - { - return wrapped.containsAll(c); - } - - @Override - public boolean isEmpty() - { - return wrapped.isEmpty(); - } - - @Override - public boolean remove(Object o) - { - return wrapped.remove(o); - } - - @Override - public boolean removeAll(Collection c) - { - return wrapped.removeAll(c); - } - - @Override - public boolean retainAll(Collection c) - { - return wrapped.retainAll(c); - } - - @Override - public int size() - { - return wrapped.size(); - } - - @Override - public Object[] toArray() - { - return wrapped.toArray(); - } - - @SuppressWarnings("unchecked") - @Override - public Object[] toArray(Object[] a) - { - return wrapped.toArray(a); - } -} diff --git a/source/java/org/alfresco/repo/security/permissions/impl/acegi/WrappedList.java b/source/java/org/alfresco/repo/security/permissions/impl/acegi/WrappedList.java new file mode 100644 index 0000000000..5cae2ab3a4 --- /dev/null +++ b/source/java/org/alfresco/repo/security/permissions/impl/acegi/WrappedList.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.security.permissions.impl.acegi; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.alfresco.query.PermissionedResults; + +/** + * Wrapped list for permission checking (eg. used by canned queries) + * + * which can be used to indicate that: + * + * - input results should be optionally permission checked up to a given maximum number of items (and then cutoff - ie. hasMoreItems = true) + * - have permission been applied to output results and were the permission checks cut-off (ie. hasMoreItems = true) either due to max items or system-wide time limit + * + * @author janv + * @since 4.0 + */ +@SuppressWarnings("hiding") +public class WrappedList implements List, PermissionedResults +{ + private List wrapped; + + // input + private int maxChecks = -1; + + // output + private boolean permissionsApplied; + private boolean hasMoreItems; + + public WrappedList(List wrapped, int maxChecks) + { + this.wrapped = wrapped; + this.maxChecks = maxChecks; + } + + public WrappedList(List wrapped, boolean permissionsApplied, boolean hasMoreItems) + { + this.wrapped = wrapped; + this.permissionsApplied = permissionsApplied; + this.hasMoreItems = hasMoreItems; + } + + public int getMaxChecks() + { + return maxChecks; + } + + public boolean hasMoreItems() + { + return hasMoreItems; + } + + /* package */ void setHasMoreItems(boolean hasMoreItems) + { + this.hasMoreItems = hasMoreItems; + } + + public boolean permissionsApplied() + { + return permissionsApplied; + } + + /* package */ void setPermissionsApplied(boolean permissionsApplied) + { + this.permissionsApplied = permissionsApplied; + } + + public List getWrapped() + { + return wrapped; + } + + @Override + public Iterator iterator() + { + return wrapped.iterator(); + } + + @Override + public boolean add(T e) + { + return wrapped.add(e); + } + + @Override + public void clear() + { + wrapped.clear(); + } + + @Override + public boolean contains(Object o) + { + return wrapped.contains(o); + } + + @Override + public boolean containsAll(Collection c) + { + return wrapped.containsAll(c); + } + + @Override + public boolean isEmpty() + { + return wrapped.isEmpty(); + } + + @Override + public boolean remove(Object o) + { + return wrapped.remove(o); + } + + @Override + public boolean removeAll(Collection c) + { + return wrapped.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) + { + return wrapped.retainAll(c); + } + + @Override + public int size() + { + return wrapped.size(); + } + + @Override + public Object[] toArray() + { + return wrapped.toArray(); + } + + @Override + public void add(int index, T element) + { + wrapped.add(index, element); + } + + @Override + public boolean addAll(Collection c) + { + return wrapped.addAll(c); + } + + @Override + public boolean addAll(int index, Collection c) + { + return wrapped.addAll(index, c); + } + + @Override + public T get(int index) + { + return wrapped.get(index); + } + + @Override + public int indexOf(Object o) + { + return wrapped.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) + { + return wrapped.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() + { + return wrapped.listIterator(); + } + + @Override + public ListIterator listIterator(int index) + { + return wrapped.listIterator(index); + } + + @Override + public T remove(int index) + { + return wrapped.remove(index); + } + + @Override + public T set(int index, T element) + { + return wrapped.set(index, element); + } + + @Override + public List subList(int fromIndex, int toIndex) + { + return wrapped.subList(fromIndex, toIndex); + } + + @Override + public T[] toArray(T[] a) + { + return wrapped.toArray(a); + } +} diff --git a/source/java/org/alfresco/repo/site/script/Site.java b/source/java/org/alfresco/repo/site/script/Site.java index 73fd6be9a7..6ce26c5891 100644 --- a/source/java/org/alfresco/repo/site/script/Site.java +++ b/source/java/org/alfresco/repo/site/script/Site.java @@ -647,6 +647,8 @@ public class Site implements Serializable { // create the custom properties map ScriptNode siteNode = new ScriptNode(this.siteInfo.getNodeRef(), this.serviceRegistry); + // set the scope, for use when converting props to javascript objects + siteNode.setScope(scope); this.customProperties = new ContentAwareScriptableQNameMap(siteNode, this.serviceRegistry); Map props = siteInfo.getCustomProperties(); diff --git a/source/java/org/alfresco/service/cmr/publishing/Environment.java b/source/java/org/alfresco/service/cmr/publishing/Environment.java index d19ffd43ea..37e944c8d8 100644 --- a/source/java/org/alfresco/service/cmr/publishing/Environment.java +++ b/source/java/org/alfresco/service/cmr/publishing/Environment.java @@ -33,4 +33,10 @@ public interface Environment Map checkPublishStatus(Collection nodes); List getPublishingEvents(PublishingEventFilter filter); + + /** + * Creates a {@link PublishingEventFilter}. + * @return a new {@link PublishingEventFilter}. + */ + PublishingEventFilter createPublishingEventFilter(); } diff --git a/source/java/org/alfresco/service/cmr/publishing/NodeSnapshot.java b/source/java/org/alfresco/service/cmr/publishing/NodeSnapshot.java new file mode 100644 index 0000000000..84ba441852 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/publishing/NodeSnapshot.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.service.cmr.publishing; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.namespace.QName; + +/** + * @author Brian + * + */ +public interface NodeSnapshot +{ + NodeRef getNodeRef(); + ChildAssociationRef getPrimaryParentAssoc(); + Path getPrimaryPath(); + Map getProperties(); + List getAllParentAssocs(); + List getOutboundPeerAssociations(); + QName getType(); + Set getAspects(); +} diff --git a/source/java/org/alfresco/service/cmr/publishing/PublishingEventFilter.java b/source/java/org/alfresco/service/cmr/publishing/PublishingEventFilter.java index 7060819f4b..18d1e20978 100644 --- a/source/java/org/alfresco/service/cmr/publishing/PublishingEventFilter.java +++ b/source/java/org/alfresco/service/cmr/publishing/PublishingEventFilter.java @@ -19,11 +19,15 @@ package org.alfresco.service.cmr.publishing; +import java.util.Set; + /** * @author Brian * */ public interface PublishingEventFilter { + PublishingEventFilter setIds(String... ids); + Set getIds(); } diff --git a/source/java/org/alfresco/service/cmr/publishing/PublishingPackageEntry.java b/source/java/org/alfresco/service/cmr/publishing/PublishingPackageEntry.java index 03dce57f7e..15a678a4f5 100644 --- a/source/java/org/alfresco/service/cmr/publishing/PublishingPackageEntry.java +++ b/source/java/org/alfresco/service/cmr/publishing/PublishingPackageEntry.java @@ -20,7 +20,6 @@ package org.alfresco.service.cmr.publishing; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.version.Version; /** * @author Brian @@ -29,4 +28,6 @@ import org.alfresco.service.cmr.version.Version; public interface PublishingPackageEntry { NodeRef getNodeRef(); + NodeSnapshot getSnapshot(); + boolean isPublish(); } diff --git a/source/java/org/alfresco/service/cmr/publishing/PublishingQueue.java b/source/java/org/alfresco/service/cmr/publishing/PublishingQueue.java index 1b0eff3bd4..bfa527bc73 100644 --- a/source/java/org/alfresco/service/cmr/publishing/PublishingQueue.java +++ b/source/java/org/alfresco/service/cmr/publishing/PublishingQueue.java @@ -28,11 +28,12 @@ public interface PublishingQueue /** * Adds the supplied publishing package onto the queue. * @param publishingPackage The publishing package that is to be enqueued + * @param channelName The name of the channel to be published to. * @param schedule The time at which the new publishing event should be scheduled (optional - null indicates "as soon as possible") * @param comment A comment to be stored with this new event (optional - may be null) * @return The identifier of the newly scheduled event */ - String scheduleNewEvent(PublishingPackage publishingPackage, Calendar schedule, String comment); + String scheduleNewEvent(PublishingPackage publishingPackage, String channelName, Calendar schedule, String comment); void cancelEvent(String eventId); } diff --git a/source/java/org/alfresco/service/cmr/publishing/channels/Channel.java b/source/java/org/alfresco/service/cmr/publishing/channels/Channel.java index 39549a1c83..899aa46f19 100644 --- a/source/java/org/alfresco/service/cmr/publishing/channels/Channel.java +++ b/source/java/org/alfresco/service/cmr/publishing/channels/Channel.java @@ -19,7 +19,11 @@ package org.alfresco.service.cmr.publishing.channels; +import java.io.Serializable; +import java.util.Map; + import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; /** * @author Brian @@ -40,4 +44,6 @@ public interface Channel * @return */ String getName(); + + Map getProperties(); } diff --git a/source/java/org/alfresco/service/cmr/publishing/channels/ChannelService.java b/source/java/org/alfresco/service/cmr/publishing/channels/ChannelService.java index 78c244533f..d8b56b6b2f 100644 --- a/source/java/org/alfresco/service/cmr/publishing/channels/ChannelService.java +++ b/source/java/org/alfresco/service/cmr/publishing/channels/ChannelService.java @@ -36,5 +36,7 @@ public interface ChannelService List getChannelTypes(); Channel createChannel(String siteId, String channelTypeId, String name, Map properties); void deleteChannel(String siteId, String channelName); + void renameChannel(String siteId, String oldName, String newName); + void updateChannel(String siteId, String channelName, Map properties); List getChannels(String siteId); } diff --git a/source/test/org/alfresco/repo/publishing/ChannelServiceImplTest.java b/source/test/org/alfresco/repo/publishing/ChannelServiceImplTest.java index c9c68e06a4..a69af43c47 100644 --- a/source/test/org/alfresco/repo/publishing/ChannelServiceImplTest.java +++ b/source/test/org/alfresco/repo/publishing/ChannelServiceImplTest.java @@ -19,17 +19,18 @@ package org.alfresco.repo.publishing; -import static junit.framework.Assert.*; -import static org.mockito.Mockito.*; -import static org.alfresco.repo.publishing.PublishingModel.*; - -import java.util.Collections; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; +import static org.alfresco.repo.publishing.PublishingModel.PROP_CHANNEL_TYPE; +import static org.mockito.Mockito.when; + import java.util.List; import java.util.Set; import javax.annotation.Resource; -import org.alfresco.repo.transfer.AbstractNodeFilter; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.publishing.channels.ChannelType; import org.alfresco.service.cmr.repository.ChildAssociationRef;