From 2c563871d8edcdf5c3cbe089ce639d26ad9b3888 Mon Sep 17 00:00:00 2001 From: Derek Hulley Date: Wed, 11 Mar 2009 04:21:44 +0000 Subject: [PATCH] Merged V3.1 to HEAD 13033: Back end support for ETHREEOH-1179 13038: JAWS-436 - refactor WCM submit dialog to use WCM sandbox service + update unit tests 13046: Merged V3.0 to V3.1 13043: Merged V2.2 to V3.0 13016: Fix for ETWOTWO-1088 (reset layer using flatten rather than delete & add, users can flatten in stores they own but not delete) 13049: Build/test fix (WCM AssetTest) 13057: Merged V2.1-A to V3.1 8770: Added Flex SDK module 8771: Added Flex SDK binary (swc) DH: I'm not sure about the svn:eol-style property appearing here. SVN Clients? 13059: Added 'AIX' as a platform type, from Adobe V2.1A, missed checkin. 13060: [no comments] 13061: [no comments] 13063: [no comments] 13064: [no comments] 13066: [no comments] 13067: Add NodeService.getChildrenByName 13072: Added new NodeService.getChildrenByName() method to public-services-security-context. ___________________________________________________________________ Modified: svn:mergeinfo Merged /alfresco/BRANCHES/V3.0:r13043 Merged /alfresco/BRANCHES/V2.2:r13016 Merged /alfresco/BRANCHES/V3.1:r13033,13038,13046,13049,13057,13059-13061,13063-13064,13066-13067,13072 git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@13552 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../public-services-security-context.xml | 1 + config/alfresco/wcm-services-context.xml | 2 +- .../filesys/ServerConfigurationBean.java | 5 +- .../auth/cifs/PassthruCifsAuthenticator.java | 35 +- .../auth/ftp/AlfrescoFtpAuthenticator.java | 65 +-- .../auth/ftp/FTPAuthenticatorBase.java | 14 + ...MChildNamePatternMatchPerformanceTest.java | 80 ++++ .../repo/avm/AVMLockingAwareService.java | 9 + .../org/alfresco/repo/avm/AVMNodeService.java | 5 + .../org/alfresco/repo/avm/AVMRepository.java | 36 ++ .../org/alfresco/repo/avm/AVMServiceImpl.java | 15 + .../alfresco/repo/avm/AVMSyncServiceImpl.java | 12 +- .../org/alfresco/repo/avm/ChildEntryDAO.java | 4 +- .../org/alfresco/repo/avm/DirectoryNode.java | 18 + .../repo/avm/LayeredDirectoryNodeImpl.java | 44 +- .../alfresco/repo/avm/MultiTAVMService.java | 5 + .../repo/avm/PlainDirectoryNodeImpl.java | 31 +- .../avm/hibernate/ChildEntryDAOHibernate.java | 16 +- .../repo/domain/hibernate/Node.hbm.xml | 42 ++ .../repo/node/BaseNodeServiceTest.java | 17 + .../repo/node/db/DbNodeServiceImpl.java | 31 +- .../alfresco/repo/node/db/NodeDaoService.java | 42 +- .../HibernateNodeDaoServiceImpl.java | 117 ++++- .../repo/version/NodeServiceImpl.java | 10 + .../alfresco/service/cmr/avm/AVMService.java | 15 + .../service/cmr/repository/NodeService.java | 16 + .../wcm/AbstractWCMServiceImplTest.java | 104 +++++ .../java/org/alfresco/wcm/WCMTestSuite.java | 2 +- .../wcm/asset/AssetServiceImplTest.java | 67 +-- .../alfresco/wcm/sandbox/SandboxService.java | 36 +- .../wcm/sandbox/SandboxServiceImpl.java | 398 ++++++++++++++---- .../wcm/sandbox/SandboxServiceImplTest.java | 187 +++++--- .../java/org/alfresco/wcm/util/WCMUtil.java | 6 +- .../alfresco/wcm/util/WCMWorkflowUtil.java | 29 +- .../webproject/WebProjectServiceImplTest.java | 47 +-- .../script/ScriptWebProjectsTest.java | 59 +-- source/test-resources/wcm/jbpm.cfg.xml | 43 ++ .../test-resources/wcm/wcm-jbpm-context.xml | 11 + 38 files changed, 1264 insertions(+), 412 deletions(-) create mode 100644 source/java/org/alfresco/repo/avm/AVMChildNamePatternMatchPerformanceTest.java create mode 100644 source/java/org/alfresco/wcm/AbstractWCMServiceImplTest.java create mode 100644 source/test-resources/wcm/jbpm.cfg.xml create mode 100644 source/test-resources/wcm/wcm-jbpm-context.xml diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml index 722098322f..5aa8d33a37 100644 --- a/config/alfresco/public-services-security-context.xml +++ b/config/alfresco/public-services-security-context.xml @@ -372,6 +372,7 @@ org.alfresco.service.cmr.repository.NodeService.getParentAssocs=ACL_NODE.0.sys:base.ReadProperties org.alfresco.service.cmr.repository.NodeService.getChildAssocs=ACL_NODE.0.sys:base.ReadChildren,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.repository.NodeService.getChildByName=ACL_NODE.0.sys:base.ReadChildren,AFTER_ACL_NODE.sys:base.ReadProperties + org.alfresco.service.cmr.repository.NodeService.getChildrenByName=ACL_NODE.0.sys:base.ReadChildren,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.repository.NodeService.getPrimaryParent=ACL_NODE.0.sys:base.ReadProperties org.alfresco.service.cmr.repository.NodeService.createAssociation=ROLE_AUTHENTICATED org.alfresco.service.cmr.repository.NodeService.removeAssociation=ROLE_AUTHENTICATED diff --git a/config/alfresco/wcm-services-context.xml b/config/alfresco/wcm-services-context.xml index 6b9b2cabe2..0905175503 100644 --- a/config/alfresco/wcm-services-context.xml +++ b/config/alfresco/wcm-services-context.xml @@ -196,7 +196,7 @@ - + diff --git a/source/java/org/alfresco/filesys/ServerConfigurationBean.java b/source/java/org/alfresco/filesys/ServerConfigurationBean.java index a39608ffb9..9b4ea8183c 100644 --- a/source/java/org/alfresco/filesys/ServerConfigurationBean.java +++ b/source/java/org/alfresco/filesys/ServerConfigurationBean.java @@ -1875,10 +1875,7 @@ public class ServerConfigurationBean extends ServerConfiguration implements Appl } else if (authType.equalsIgnoreCase("alfresco")) { - // Standard authenticator requires MD4 or passthru based authentication - -// if ( ntlmMode == NTLMMode.NONE) -// throw new AlfrescoRuntimeException("Wrong authentication setup for alfresco authenticator"); + // Do nothing, uses the authentication component as it has the plaintext password from the user } else if ( authType.equalsIgnoreCase( "custom")) { diff --git a/source/java/org/alfresco/filesys/auth/cifs/PassthruCifsAuthenticator.java b/source/java/org/alfresco/filesys/auth/cifs/PassthruCifsAuthenticator.java index 7000fc9151..98e369aac6 100644 --- a/source/java/org/alfresco/filesys/auth/cifs/PassthruCifsAuthenticator.java +++ b/source/java/org/alfresco/filesys/auth/cifs/PassthruCifsAuthenticator.java @@ -167,14 +167,14 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements // Debug if ( logger.isDebugEnabled()) - logger.debug("Null CIFS logon allowed"); + logger.debug("Null CIFS logon allowed, sess = " + sess.getUniqueId()); return CifsAuthenticator.AUTH_ALLOW; } // Start a transaction - UserTransaction tx = getTransactionService().getUserTransaction( true); + UserTransaction tx = getTransactionService().getUserTransaction( false); int authSts = AUTH_DISALLOW; try @@ -303,7 +303,7 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements // Debug - logger.error(ex.getMessage()); + logger.error(ex); } // Keep the authentication session if the user session is an SMB session, else close the @@ -399,17 +399,15 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements * * @return AuthContext */ - public AuthContext getAuthContext( SrvSession sess) + public AuthContext getAuthContext( SMBSrvSession sess) { // Make sure the SMB server listener is installed - if ( m_server == null && sess instanceof SMBSrvSession) + if ( m_server == null) { - SMBSrvSession smbSess = (SMBSrvSession) sess; - m_server = smbSess.getSMBServer(); - // Install the server listener + m_server = sess.getSMBServer(); m_server.addSessionListener(this); } @@ -423,6 +421,11 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements String domain = mapClientAddressToDomain( sess.getRemoteAddress()); + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Mapped client " + sess.getRemoteAddress() + " to domain " + domain); + AuthenticateSession authSess = m_passthruServers.openSession( false, domain); if (authSess != null) { @@ -442,6 +445,8 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements if (logger.isDebugEnabled()) logger.debug("Passthru sessId=" + authSess.getSessionId() + ", auth ctx=" + authCtx); } + else if ( logger.isDebugEnabled()) + logger.debug("Failed to open a passthru session, mapped domain = " + domain); } catch (Exception ex) { @@ -1128,7 +1133,7 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements // DEBUG if (logger.isDebugEnabled()) - logger.debug(" No PassthruDetails for " + sess.getUniqueId()); + logger.debug(" No PassthruDetails for " + sess.getUniqueId() + ", check server list/domain mappings"); // Indicate logon failure @@ -1154,6 +1159,11 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements m_passthruServers = new PassthruServers(); + // Propagate the debug setting + + if ( logger.isDebugEnabled()) + m_passthruServers.setDebug( true); + // Check if the session timeout has been specified ConfigElement sessTmoElem = params.getChild("Timeout"); @@ -1426,6 +1436,13 @@ public class PassthruCifsAuthenticator extends CifsAuthenticatorBase implements */ public void sessionLoggedOn(SrvSession sess) { + // Check the client information for the session + + ClientInfo cInfo = sess.getClientInformation(); + + if ( cInfo == null || cInfo.isNullSession()) + return; + // Check if there is an active session to the authentication server for this local // session diff --git a/source/java/org/alfresco/filesys/auth/ftp/AlfrescoFtpAuthenticator.java b/source/java/org/alfresco/filesys/auth/ftp/AlfrescoFtpAuthenticator.java index 1921ba5083..520d164165 100644 --- a/source/java/org/alfresco/filesys/auth/ftp/AlfrescoFtpAuthenticator.java +++ b/source/java/org/alfresco/filesys/auth/ftp/AlfrescoFtpAuthenticator.java @@ -108,12 +108,13 @@ public class AlfrescoFtpAuthenticator extends FTPAuthenticatorBase { // Start a transaction - tx = getTransactionService().getUserTransaction( false); + tx = createTransaction(); tx.begin(); - // Perform local MD4 password check + // Authenitcate using the authentication component, as we have the plaintext password - authSts = doMD4UserAuthentication(client, sess); + getAuthenticationComponent().authenticate( client.getUserName(), client.getPasswordAsString().toCharArray()); + authSts = true; // Check if the user has been logged on successfully @@ -193,62 +194,4 @@ public class AlfrescoFtpAuthenticator extends FTPAuthenticatorBase { client.setGuest( true); } - - /** - * Perform MD4 user authentication - * - * @param client Client information - * @param sess Server session - * @return boolean - */ - private final boolean doMD4UserAuthentication(ClientInfo client, SrvSession sess) - { - // Get the stored MD4 hashed password for the user, or null if the user does not exist - - String md4hash = getAuthenticationComponent().getMD4HashedPassword(client.getUserName()); - - if ( md4hash != null) - { - // Check if the client has supplied a password, if not then do not allow access - - if ( client.getPassword() == null) - return false; - - // Encode the user supplied password - - String userMd4 = m_md4Encoder.encodePassword( client.getPasswordAsString(), null); - - // Compare the hashed passwords - - if ( md4hash.equals( userMd4) == false) - { - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Password mismatch for user " + client.getUserName()); - - // Access not allowed - - return false; - } - - // Logging - - if ( logger.isInfoEnabled()) - logger.info( "Logged on user " + client.getUserName() + " ( address " + sess.getRemoteAddress() + ")"); - - // Set the current user to be authenticated, save the authentication token - - AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; - alfClient.setAuthenticationToken( getAuthenticationComponent().setCurrentUser(client.getUserName())); - - // Passwords match, grant access - - return true; - } - - // User does not exist - - return false; - } } diff --git a/source/java/org/alfresco/filesys/auth/ftp/FTPAuthenticatorBase.java b/source/java/org/alfresco/filesys/auth/ftp/FTPAuthenticatorBase.java index 2fcfef9e04..ed0fc898ad 100644 --- a/source/java/org/alfresco/filesys/auth/ftp/FTPAuthenticatorBase.java +++ b/source/java/org/alfresco/filesys/auth/ftp/FTPAuthenticatorBase.java @@ -174,4 +174,18 @@ public abstract class FTPAuthenticatorBase implements FTPAuthenticator { } } } + + /** + * Create a transaction, this will be a wrteable transaction unless the system is in read-only mode. + * + * return UserTransaction + */ + protected final UserTransaction createTransaction() + { + // Get the transaction service + + TransactionService txService = getTransactionService(); + + return txService.getUserTransaction( txService.isReadOnly() ? true : false); + } } diff --git a/source/java/org/alfresco/repo/avm/AVMChildNamePatternMatchPerformanceTest.java b/source/java/org/alfresco/repo/avm/AVMChildNamePatternMatchPerformanceTest.java new file mode 100644 index 0000000000..c3e7dd23ca --- /dev/null +++ b/source/java/org/alfresco/repo/avm/AVMChildNamePatternMatchPerformanceTest.java @@ -0,0 +1,80 @@ +package org.alfresco.repo.avm; + +import java.util.List; +import java.util.SortedMap; + +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.service.cmr.avmsync.AVMDifference; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.util.GUID; + +public class AVMChildNamePatternMatchPerformanceTest extends AVMServiceTestBase +{ + + public void test_1000() throws Exception + { + fTransactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + + public Object execute() throws Throwable + { + try + { + fService.createStore("StagingArea"); + fService.createStore("SandBox"); + + fService.createDirectory("StagingArea:/", "www"); + fService.createLayeredDirectory("StagingArea:/www", "SandBox:/", "www"); + + for (int i = 0; i < 500; i++) + { + String name = GUID.generate(); + if (i % 100 == 0) + { + name = "A" + name; + } + if(name.startsWith("a")) + { + name = "G"+name; + } + fService.createFile("SandBox:/www", name).close(); + } + System.out.println("Create SandBox:/www"); + + + for (int i = 0; i < 500; i++) + { + String name = GUID.generate(); + if (i % 100 == 0) + { + name = "A" + name; + } + if(name.startsWith("a")) + { + name = "G"+name; + } + fService.createFile("StagingArea:/www", name).close(); + } + System.out.println("Create StagingArea:/www"); + + long start = System.nanoTime(); + AVMNodeDescriptor dir = fService.lookup(-1, "SandBox:/www"); + SortedMap result = fService.getDirectoryListing(dir, "A*"); + assertEquals(10, result.size()); + long end = System.nanoTime(); + System.out.println("Pattern in " + ((end - start) / 1000000000.0f)); + } + finally + { + fService.purgeStore("StagingArea"); + fService.purgeStore("SandBox"); + } + return null; + } + }); + + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMLockingAwareService.java b/source/java/org/alfresco/repo/avm/AVMLockingAwareService.java index 9bc2d7fe99..f952110a36 100644 --- a/source/java/org/alfresco/repo/avm/AVMLockingAwareService.java +++ b/source/java/org/alfresco/repo/avm/AVMLockingAwareService.java @@ -328,6 +328,15 @@ public class AVMLockingAwareService implements AVMService, ApplicationContextAwa { return fService.getDirectoryListing(dir); } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.avm.AVMService#getDirectoryListing(org.alfresco.service.cmr.avm.AVMNodeDescriptor, String) + */ + public SortedMap getDirectoryListing( + AVMNodeDescriptor dir, String childNamePattern) + { + return fService.getDirectoryListing(dir, childNamePattern); + } /* (non-Javadoc) * @see org.alfresco.service.cmr.avm.AVMService#getDirectoryListing(org.alfresco.service.cmr.avm.AVMNodeDescriptor, boolean) diff --git a/source/java/org/alfresco/repo/avm/AVMNodeService.java b/source/java/org/alfresco/repo/avm/AVMNodeService.java index 3012f46e54..ebeac021d4 100644 --- a/source/java/org/alfresco/repo/avm/AVMNodeService.java +++ b/source/java/org/alfresco/repo/avm/AVMNodeService.java @@ -1602,6 +1602,11 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi throw new UnsupportedOperationException(); } + public List getChildrenByName(NodeRef nodeRef, QName assocTypeQName, Collection childNames) + { + throw new UnsupportedOperationException(); + } + /** * Get a child NodeRef by name. * @param nodeRef The parent node. diff --git a/source/java/org/alfresco/repo/avm/AVMRepository.java b/source/java/org/alfresco/repo/avm/AVMRepository.java index c04ec225c1..140950b23e 100644 --- a/source/java/org/alfresco/repo/avm/AVMRepository.java +++ b/source/java/org/alfresco/repo/avm/AVMRepository.java @@ -1202,6 +1202,42 @@ public class AVMRepository } } + /** + * Get a directory listing from a directory node descriptor fo children that match the given pattern + * + * @param dir + * The directory node descriptor. + * @return A SortedMap listing. + */ + public SortedMap getListing(AVMNodeDescriptor dir, String childNamePattern, boolean includeDeleted) + { + fLookupCount.set(1); + try + { + AVMNode node = fAVMNodeDAO.getByID(dir.getId()); + if (node == null) + { + throw new AVMBadArgumentException("Invalid Node."); + } + if (node.getType() != AVMNodeType.LAYERED_DIRECTORY && node.getType() != AVMNodeType.PLAIN_DIRECTORY) + { + throw new AVMWrongTypeException("Not a directory."); + } + if (!can(null, node, PermissionService.READ_CHILDREN, false)) + { + throw new AccessDeniedException("Not allowed to read children: " + dir); + } + DirectoryNode dirNode = (DirectoryNode) node; + SortedMap listing = dirNode.getListing(dir, childNamePattern, includeDeleted); + return listing; + } + finally + { + fLookupCount.set(null); + } + } + + /** * Get the names of deleted nodes in a directory. * diff --git a/source/java/org/alfresco/repo/avm/AVMServiceImpl.java b/source/java/org/alfresco/repo/avm/AVMServiceImpl.java index 04439c56b1..69f3885863 100644 --- a/source/java/org/alfresco/repo/avm/AVMServiceImpl.java +++ b/source/java/org/alfresco/repo/avm/AVMServiceImpl.java @@ -301,6 +301,21 @@ public class AVMServiceImpl implements AVMService return getDirectoryListing(dir, false); } + + public SortedMap getDirectoryListing(AVMNodeDescriptor dir, String childPattern) + { + return getDirectoryListing(dir, childPattern, false); + } + + public SortedMap getDirectoryListing(AVMNodeDescriptor dir, String childNamePattern, boolean includeDeleted) + { + if (dir == null) + { + throw new AVMBadArgumentException("Null descriptor."); + } + return fAVMRepository.getListing(dir, childNamePattern, includeDeleted); + } + /** * Get a directory listing from a node descriptor, with the option of * seeing deleted nodes. diff --git a/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java b/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java index 655de063e6..8a5cb7bd7d 100644 --- a/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java +++ b/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java @@ -1122,6 +1122,9 @@ public class AVMSyncServiceImpl implements AVMSyncService * Takes a layer, deletes it and recreates it pointing at the same underlying * node. Any changes in the layer are lost (except to history if the layer has been * snapshotted.) + * + * NB: fixed to respect permissions and allow reset end preview sandboxes by finding all direct children and flattening + * * @param layerPath */ public void resetLayer(String layerPath) @@ -1131,9 +1134,12 @@ public class AVMSyncServiceImpl implements AVMSyncService { throw new AVMNotFoundException("Not Found: " + layerPath); } - String [] parts = AVMNodeConverter.SplitBase(layerPath); - fAVMService.removeNode(parts[0], parts[1]); - fAVMService.createLayeredDirectory(desc.getIndirection(), parts[0], parts[1]); + Map layerListing = + fAVMService.getDirectoryListingDirect(-1, layerPath, true); + for (String name : layerListing.keySet()) + { + fAVMRepository.flatten(layerPath, name); + } } /** diff --git a/source/java/org/alfresco/repo/avm/ChildEntryDAO.java b/source/java/org/alfresco/repo/avm/ChildEntryDAO.java index e5c1d54f65..acf1093cab 100644 --- a/source/java/org/alfresco/repo/avm/ChildEntryDAO.java +++ b/source/java/org/alfresco/repo/avm/ChildEntryDAO.java @@ -48,10 +48,10 @@ public interface ChildEntryDAO /** * Get all the children of a given parent. * @param parent The parent. - * @param eager - true -> fetch children in the query + * @param childNamePattern - achild name pattern to match - null is supported for match all * @return A List of ChildEntries. */ - public List getByParent(DirectoryNode parent); + public List getByParent(DirectoryNode parent, String childNamePattern); /** * Get the entry for a given child in a given parent. diff --git a/source/java/org/alfresco/repo/avm/DirectoryNode.java b/source/java/org/alfresco/repo/avm/DirectoryNode.java index 9cea6e277b..d2fc266bd7 100644 --- a/source/java/org/alfresco/repo/avm/DirectoryNode.java +++ b/source/java/org/alfresco/repo/avm/DirectoryNode.java @@ -79,6 +79,13 @@ public interface DirectoryNode extends AVMNode */ public Map getListing(Lookup lPath, boolean includeDeleted); + /** + * Get a directory listing. + * @param lPath The lookup context. + * @return A SortedMap of names to DirectoryEntries. + */ + public Map getListing(Lookup lPath, String childNamePattern, boolean includeDeleted); + /** * Get a listing of the nodes directly contained by a directory. * @param lPath The Lookup to this directory. @@ -102,6 +109,17 @@ public interface DirectoryNode extends AVMNode */ public SortedMap getListing(AVMNodeDescriptor dir, boolean includeDeleted); + + /** + * Get a listing from a directory specified by an AVMNodeDescriptor. + * @param dir The directory to list. + * @param childNamePattern - child name pattern to match + * @param includeDeleted = include deleted children + * @return A Map of names to node descriptors + */ + public SortedMap getListing(AVMNodeDescriptor dir, + String childNamePattern, + boolean includeDeleted); /** * Get the names of nodes deleted in this directory. diff --git a/source/java/org/alfresco/repo/avm/LayeredDirectoryNodeImpl.java b/source/java/org/alfresco/repo/avm/LayeredDirectoryNodeImpl.java index f0381ee605..8350e33762 100644 --- a/source/java/org/alfresco/repo/avm/LayeredDirectoryNodeImpl.java +++ b/source/java/org/alfresco/repo/avm/LayeredDirectoryNodeImpl.java @@ -210,7 +210,7 @@ class LayeredDirectoryNodeImpl extends DirectoryNodeImpl implements LayeredDirec AVMDAOs.Instance().fAVMNodeDAO.save(this); if (copyContents) { - for (ChildEntry child : AVMDAOs.Instance().fChildEntryDAO.getByParent(other)) + for (ChildEntry child : AVMDAOs.Instance().fChildEntryDAO.getByParent(other, null)) { ChildKey key = new ChildKey(this, child.getKey().getName()); ChildEntryImpl newChild = new ChildEntryImpl(key, child.getChild()); @@ -430,6 +430,18 @@ class LayeredDirectoryNodeImpl extends DirectoryNodeImpl implements LayeredDirec * @return A Map from names to nodes. This is a sorted Map. */ public Map getListing(Lookup lPath, boolean includeDeleted) + { + return getListing(lPath, null, includeDeleted); + } + + /** + * Get a listing of the virtual contents of this directory. + * + * @param lPath + * The Lookup. + * @return A Map from names to nodes. This is a sorted Map. + */ + public Map getListing(Lookup lPath, String childNamePattern, boolean includeDeleted) { // Get the base listing from the thing we indirect to. Map listing = new HashMap(); @@ -439,7 +451,7 @@ class LayeredDirectoryNodeImpl extends DirectoryNodeImpl implements LayeredDirec if (lookup != null) { DirectoryNode dir = (DirectoryNode) lookup.getCurrentNode(); - Map underListing = dir.getListing(lookup, includeDeleted); + Map underListing = dir.getListing(lookup, childNamePattern, includeDeleted); for (Map.Entry entry : underListing.entrySet()) { if (entry.getValue().getType() == AVMNodeType.LAYERED_DIRECTORY || @@ -454,7 +466,7 @@ class LayeredDirectoryNodeImpl extends DirectoryNodeImpl implements LayeredDirec } } } - for (ChildEntry entry : AVMDAOs.Instance().fChildEntryDAO.getByParent(this)) + for (ChildEntry entry : AVMDAOs.Instance().fChildEntryDAO.getByParent(this, childNamePattern)) { if (entry.getChild().getType() == AVMNodeType.LAYERED_DIRECTORY || entry.getChild().getType() == AVMNodeType.PLAIN_DIRECTORY) @@ -486,7 +498,7 @@ class LayeredDirectoryNodeImpl extends DirectoryNodeImpl implements LayeredDirec public Map getListingDirect(Lookup lPath, boolean includeDeleted) { Map listing = new HashMap(); - for (ChildEntry entry : AVMDAOs.Instance().fChildEntryDAO.getByParent(this)) + for (ChildEntry entry : AVMDAOs.Instance().fChildEntryDAO.getByParent(this, null)) { if (entry.getChild().getType() == AVMNodeType.LAYERED_DIRECTORY || entry.getChild().getType() == AVMNodeType.PLAIN_DIRECTORY) @@ -517,7 +529,7 @@ class LayeredDirectoryNodeImpl extends DirectoryNodeImpl implements LayeredDirec */ public SortedMap getListingDirect(AVMNodeDescriptor dir, boolean includeDeleted) { - List children = AVMDAOs.Instance().fChildEntryDAO.getByParent(this); + List children = AVMDAOs.Instance().fChildEntryDAO.getByParent(this, null); SortedMap listing = new TreeMap(String.CASE_INSENSITIVE_ORDER); for (ChildEntry child : children) { @@ -550,6 +562,22 @@ class LayeredDirectoryNodeImpl extends DirectoryNodeImpl implements LayeredDirec * @return A Map of names to node descriptors. */ public SortedMap getListing(AVMNodeDescriptor dir, boolean includeDeleted) + { + return getListing(dir, null, includeDeleted); + } + + /** + * Get a listing from a directory node descriptor. + * + * @param dir + * The directory node descriptor. + * @param childNamePattern + * Pattern to match for child names - may be null + * @param includeDeleted + * Should DeletedNodes be shown. + * @return A Map of names to node descriptors. + */ + public SortedMap getListing(AVMNodeDescriptor dir, String childNamePattern, boolean includeDeleted) { if (dir.getPath() == null || dir.getIndirection() == null) { @@ -563,7 +591,7 @@ class LayeredDirectoryNodeImpl extends DirectoryNodeImpl implements LayeredDirec if (lookup != null) { DirectoryNode dirNode = (DirectoryNode) lookup.getCurrentNode(); - Map listing = dirNode.getListing(lookup, includeDeleted); + Map listing = dirNode.getListing(lookup, childNamePattern, includeDeleted); for (Map.Entry entry : listing.entrySet()) { if (entry.getValue().getType() == AVMNodeType.LAYERED_DIRECTORY || @@ -581,7 +609,7 @@ class LayeredDirectoryNodeImpl extends DirectoryNodeImpl implements LayeredDirec } } } - List children = AVMDAOs.Instance().fChildEntryDAO.getByParent(this); + List children = AVMDAOs.Instance().fChildEntryDAO.getByParent(this, childNamePattern); for (ChildEntry child : children) { if (child.getChild().getType() == AVMNodeType.LAYERED_DIRECTORY || @@ -612,7 +640,7 @@ class LayeredDirectoryNodeImpl extends DirectoryNodeImpl implements LayeredDirec */ public List getDeletedNames() { - List children = AVMDAOs.Instance().fChildEntryDAO.getByParent(this); + List children = AVMDAOs.Instance().fChildEntryDAO.getByParent(this, null); List listing = new ArrayList(); for (ChildEntry entry : children) { diff --git a/source/java/org/alfresco/repo/avm/MultiTAVMService.java b/source/java/org/alfresco/repo/avm/MultiTAVMService.java index dea84e8a0b..8851f80b6a 100644 --- a/source/java/org/alfresco/repo/avm/MultiTAVMService.java +++ b/source/java/org/alfresco/repo/avm/MultiTAVMService.java @@ -305,6 +305,11 @@ public class MultiTAVMService implements AVMService return getBaseNodes(fService.getDirectoryListing(getTenantNode(dir), includeDeleted)); } + public SortedMap getDirectoryListing(AVMNodeDescriptor dir, String childNamePattern) + { + return getBaseNodes(fService.getDirectoryListing(getTenantNode(dir), childNamePattern)); + } + /* (non-Javadoc) * @see org.alfresco.service.cmr.avm.AVMService#getDirectoryListingArray(int, java.lang.String, boolean) */ diff --git a/source/java/org/alfresco/repo/avm/PlainDirectoryNodeImpl.java b/source/java/org/alfresco/repo/avm/PlainDirectoryNodeImpl.java index 1d0f3796df..de683a3fdd 100644 --- a/source/java/org/alfresco/repo/avm/PlainDirectoryNodeImpl.java +++ b/source/java/org/alfresco/repo/avm/PlainDirectoryNodeImpl.java @@ -30,7 +30,6 @@ import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; -import org.alfresco.repo.domain.DbAccessControlList; import org.alfresco.repo.security.permissions.ACLCopyMode; import org.alfresco.service.cmr.avm.AVMBadArgumentException; import org.alfresco.service.cmr.avm.AVMExistsException; @@ -77,7 +76,7 @@ class PlainDirectoryNodeImpl extends DirectoryNodeImpl implements PlainDirectory { super(store.getAVMRepository().issueID(), store); AVMDAOs.Instance().fAVMNodeDAO.save(this); - for (ChildEntry child : AVMDAOs.Instance().fChildEntryDAO.getByParent(other)) + for (ChildEntry child : AVMDAOs.Instance().fChildEntryDAO.getByParent(other, null)) { ChildKey key = new ChildKey(this, child.getKey().getName()); ChildEntry newChild = new ChildEntryImpl(key, @@ -109,9 +108,22 @@ class PlainDirectoryNodeImpl extends DirectoryNodeImpl implements PlainDirectory */ @SuppressWarnings("unchecked") public Map getListing(Lookup lPath, boolean includeDeleted) + { + return getListing(lPath, null, includeDeleted); + } + + /** + * Get a directory listing. + * @param lPath The lookup path. + * @param childNamePattern A child name pattern. + * @param includeDeleted Include deleted nodes. + * @return The listing. + */ + @SuppressWarnings("unchecked") + public Map getListing(Lookup lPath, String childNamePattern, boolean includeDeleted) { Map result = new HashMap(); - List children = AVMDAOs.Instance().fChildEntryDAO.getByParent(this); + List children = AVMDAOs.Instance().fChildEntryDAO.getByParent(this, childNamePattern); for (ChildEntry child : children) { if (child.getChild().getType() == AVMNodeType.LAYERED_DIRECTORY || @@ -159,13 +171,24 @@ class PlainDirectoryNodeImpl extends DirectoryNodeImpl implements PlainDirectory * @return A Map of names to node descriptors. */ public SortedMap getListing(AVMNodeDescriptor dir, boolean includeDeleted) + { + return getListing(dir, null, includeDeleted); + } + + /** + * Get a listing of from a directory node descriptor. + * @param dir The directory node descriptor. + * @param childNamePattern - pattern to match for child names - may be null + * @return A Map of names to node descriptors. + */ + public SortedMap getListing(AVMNodeDescriptor dir, String childNamePattern, boolean includeDeleted) { if (dir.getPath() == null) { throw new AVMBadArgumentException("Path is null."); } SortedMap result = new TreeMap(String.CASE_INSENSITIVE_ORDER); - List children = AVMDAOs.Instance().fChildEntryDAO.getByParent(this); + List children = AVMDAOs.Instance().fChildEntryDAO.getByParent(this, childNamePattern); for (ChildEntry child : children) { if (child.getChild().getType() == AVMNodeType.LAYERED_DIRECTORY || diff --git a/source/java/org/alfresco/repo/avm/hibernate/ChildEntryDAOHibernate.java b/source/java/org/alfresco/repo/avm/hibernate/ChildEntryDAOHibernate.java index aca775324d..1207719497 100644 --- a/source/java/org/alfresco/repo/avm/hibernate/ChildEntryDAOHibernate.java +++ b/source/java/org/alfresco/repo/avm/hibernate/ChildEntryDAOHibernate.java @@ -27,14 +27,15 @@ import java.util.List; import org.alfresco.repo.avm.AVMNode; import org.alfresco.repo.avm.ChildEntry; -import org.alfresco.repo.avm.ChildEntryImpl; import org.alfresco.repo.avm.ChildEntryDAO; +import org.alfresco.repo.avm.ChildEntryImpl; import org.alfresco.repo.avm.ChildKey; import org.alfresco.repo.avm.DirectoryNode; +import org.alfresco.util.SearchLanguageConversion; import org.hibernate.Criteria; import org.hibernate.FetchMode; import org.hibernate.Query; -import org.hibernate.criterion.Criterion; +import org.hibernate.criterion.MatchMode; import org.hibernate.criterion.Restrictions; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; @@ -87,16 +88,17 @@ class ChildEntryDAOHibernate extends HibernateDaoSupport implements ChildEntryDA * @return A List of ChildEntries. */ @SuppressWarnings("unchecked") - public List getByParent(DirectoryNode parent) + public List getByParent(DirectoryNode parent, String childNamePattern) { Criteria criteria = getSession().createCriteria(ChildEntryImpl.class, "ce"); criteria.add(Restrictions.eq("ce.key.parent", parent)); + if(childNamePattern != null) + { + String pattern = SearchLanguageConversion.convert(SearchLanguageConversion.DEF_LUCENE, SearchLanguageConversion.DEF_SQL_LIKE, childNamePattern); + criteria.add(Restrictions.like("ce.key.name", pattern)); + } criteria.createCriteria("child", "cld").setFetchMode("ce.child", FetchMode.JOIN); criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); - // Query query = - // getSession().createQuery("select ce, cld from ChildEntryImpl ce join child cld where ce.key.parent = - // :parent"); - // query.setEntity("parent", parent); return (List) criteria.list(); } diff --git a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml index 84926b898e..5a1e3b9645 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml @@ -355,12 +355,42 @@ assoc.id + + select + assoc.id, + assoc.typeQNameId, + assoc.qnameNamespaceId, + assoc.qnameLocalName, + assoc.childNodeName, + assoc.childNodeNameCrc, + assoc.isPrimary, + assoc.index, + child.id, + store.protocol, + store.identifier, + child.uuid + from + org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc + join assoc.parent as parent + join assoc.child as child + join child.store as store + where + parent.id = :parentId and + assoc.typeQNameId = :typeQNameId and + assoc.childNodeName in (:childNodeNames) + order by + assoc.index, + assoc.id + + select assoc.id, assoc.typeQNameId, assoc.qnameNamespaceId, assoc.qnameLocalName, + assoc.childNodeName, + assoc.childNodeNameCrc, assoc.isPrimary, assoc.index, child.id, @@ -385,6 +415,8 @@ assoc.typeQNameId, assoc.qnameNamespaceId, assoc.qnameLocalName, + assoc.childNodeName, + assoc.childNodeNameCrc, assoc.isPrimary, assoc.index, child.id, @@ -411,6 +443,8 @@ assoc.typeQNameId, assoc.qnameNamespaceId, assoc.qnameLocalName, + assoc.childNodeName, + assoc.childNodeNameCrc, assoc.isPrimary, assoc.index, child.id, @@ -436,6 +470,8 @@ assoc.typeQNameId, assoc.qnameNamespaceId, assoc.qnameLocalName, + assoc.childNodeName, + assoc.childNodeNameCrc, assoc.isPrimary, assoc.index, child.id, @@ -463,6 +499,8 @@ assoc.typeQNameId, assoc.qnameNamespaceId, assoc.qnameLocalName, + assoc.childNodeName, + assoc.childNodeNameCrc, assoc.isPrimary, assoc.index, child.id, @@ -488,6 +526,8 @@ assoc.typeQNameId, assoc.qnameNamespaceId, assoc.qnameLocalName, + assoc.childNodeName, + assoc.childNodeNameCrc, assoc.isPrimary, assoc.index, child.id, @@ -513,6 +553,8 @@ assoc.typeQNameId, assoc.qnameNamespaceId, assoc.qnameLocalName, + assoc.childNodeName, + assoc.childNodeNameCrc, assoc.isPrimary, assoc.index, child.id, diff --git a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java index 60dcd9592b..9b277cd379 100644 --- a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java +++ b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java @@ -27,6 +27,7 @@ package org.alfresco.repo.node; import java.io.InputStream; import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -2269,6 +2270,15 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest ContentModel.TYPE_CONTENT, props); NodeRef defRef = pathDefRef.getChildRef(); + // create node KLM + props.put(ContentModel.PROP_NAME, "KLM"); + ChildAssociationRef pathKlmRef = nodeService.createNode( + abcRef, + ASSOC_TYPE_QNAME_TEST_CONTAINS, + QName.createQName("KLM"), + ContentModel.TYPE_CONTENT, + props); + NodeRef klmRef = pathDefRef.getChildRef(); // now browse down using the node service NodeRef checkAbcRef = nodeService.getChildByName(parentRef, ASSOC_TYPE_QNAME_TEST_CONTAINS, "abc"); @@ -2280,6 +2290,13 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest // check that we get null where not present NodeRef checkHijRef = nodeService.getChildByName(checkAbcRef, ASSOC_TYPE_QNAME_TEST_CONTAINS, "hij"); assertNull("Third level, named node 'HIJ' should not have been found", checkHijRef); + + // Now search for multiple names + List namesList = Arrays.asList("ABC", "DEF", "HIJ", "KLM"); + List childAssocRefs = nodeService.getChildrenByName(checkAbcRef, ASSOC_TYPE_QNAME_TEST_CONTAINS, namesList); + assertEquals("Expected exactly 2 results", 2, childAssocRefs.size()); + assertTrue("Expected result not included", childAssocRefs.contains(pathDefRef)); + assertTrue("Expected result not included", childAssocRefs.contains(pathKlmRef)); } public void testLocalizedAspect() throws Exception diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index b19e316e92..991606a828 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -1443,8 +1443,10 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl }; // Get all child associations with the specific qualified name nodeDaoService.getChildAssocsByChildTypes(nodeId, childNodeTypeQNames, callback); + // Sort the results + List orderedList = reorderChildAssocs(results); // Done - return results; + return orderedList; } private List reorderChildAssocs(Collection childAssocRefs) @@ -1488,6 +1490,33 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl } } + public List getChildrenByName(NodeRef nodeRef, QName assocTypeQName, Collection childNames) + { + // Get the node + Pair nodePair = getNodePairNotNull(nodeRef); + Long nodeId = nodePair.getFirst(); + + final List results = new ArrayList(100); + + NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback() + { + public boolean handle( + Pair childAssocPair, + Pair parentNodePair, + Pair childNodePair) + { + results.add(childAssocPair.getSecond()); + return false; + } + }; + // Get all child associations with the specific qualified name + nodeDaoService.getChildAssocs(nodeId, assocTypeQName, childNames, callback); + // Sort the results + List orderedList = reorderChildAssocs(results); + // Done + return orderedList; + } + public ChildAssociationRef getPrimaryParent(NodeRef nodeRef) throws InvalidNodeRefException { // Get the node diff --git a/source/java/org/alfresco/repo/node/db/NodeDaoService.java b/source/java/org/alfresco/repo/node/db/NodeDaoService.java index 1b445742bd..4d7af7a2d1 100644 --- a/source/java/org/alfresco/repo/node/db/NodeDaoService.java +++ b/source/java/org/alfresco/repo/node/db/NodeDaoService.java @@ -232,6 +232,31 @@ public interface NodeDaoService ); } + /** + * Interface used to iterate over results from child association queries + * @author Derek Hulley + */ + public interface ChildAssocRefQueryCallbackFilter extends ChildAssocRefQueryCallback + { + /** + * Method to handle raw query results and decide if the result should be filtered out or not. + * If true is returned, the standard {@link #handle(Pair, Pair, Pair) handler} method + * will be called. If false is returned, then the query result will be skipped. + *

+ * This provides a quick way to filter out results without having to pull in full entities. + * + * @return Return true if the standard {@link #handle(Pair, Pair, Pair) handler} + * method should be called, or false to filter the result out. + */ + boolean isDesiredRow( + Pair childAssocPair, + Pair parentNodePair, + Pair childNodePair, + String assocChildNodeName, + Long assocChildNodeNameCrc + ); + } + /** * Get a collection of all child association references for a given parent node. *

@@ -249,11 +274,23 @@ public interface NodeDaoService /** * Get a collection of all child association references for a given parent node. * - * @param parentNodeId the parent node + * @param parentNodeId the parent node * @param resultsCallback the callback that will be called with the results */ @DirtySessionAnnotation(markDirty=false) public void getChildAssocs(Long parentNodeId, QName assocQName, ChildAssocRefQueryCallback resultsCallback); + + /** + * Get a collection of all child associations references where the child name is an exact match. + * This method only works if the association type fundamentally supports unique-name enforcement. + * + * @param parentNodeId the parent node + * @param assocTypeQName the type of the association to check. + * @param childNames the names of the child nodes (cm:name). These will be matched exactly. + * @param resultsCallback the callback that will be called with the results + */ + @DirtySessionAnnotation(markDirty=false) + public void getChildAssocs(Long parentNodeId, QName assocTypeQName, Collection childNames, ChildAssocRefQueryCallback resultsCallback); @DirtySessionAnnotation(markDirty=false) public void getChildAssocsByTypeQNames( @@ -314,8 +351,7 @@ public interface NodeDaoService public void getNodesWithAspect(QName aspectQName, Long minNodeId, int count, NodeRefQueryCallback resultsCallback); /** - * @return Returns an association matching the given parent, type and child name - or null - * if not found + * @return Returns an association matching the given parent, type and child name (cm:name) - or null if not found */ @DirtySessionAnnotation(markDirty=false) public Pair getChildAssoc(Long parentNodeId, QName assocTypeQName, String childName); diff --git a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java index d9b4778d38..6645160d5b 100644 --- a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java @@ -133,6 +133,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements private static final String QUERY_GET_CHILD_NODE_IDS = "node.GetChildNodeIds"; private static final String QUERY_GET_CHILD_ASSOCS_BY_ALL = "node.GetChildAssocsByAll"; private static final String QUERY_GET_CHILD_ASSOC_BY_TYPE_AND_NAME = "node.GetChildAssocByTypeAndName"; + private static final String QUERY_GET_CHILD_ASSOC_REFS_BY_TYPE_AND_NAME_LIST = "node.GetChildAssocRefsByTypeAndNameList"; private static final String QUERY_GET_CHILD_ASSOC_REFS = "node.GetChildAssocRefs"; private static final String QUERY_GET_CHILD_ASSOC_REFS_BY_QNAME = "node.GetChildAssocRefsByQName"; private static final String QUERY_GET_CHILD_ASSOC_REFS_BY_TYPEQNAMES = "node.GetChildAssocRefsByTypeQNames"; @@ -2059,6 +2060,92 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements // Done } + public void getChildAssocs(final Long parentNodeId, QName assocTypeQName, Collection childNames, final ChildAssocRefQueryCallback resultsCallback) + { + /* + * The child names are converted to the stardard form (shortened, lower-case) and used directly in an IN clause. + * In order to guarantee uniqueness, the CRC for each matching, stardard-form name is checked for a match before + * the result is passed to the client's callback handler. + */ + + // Shortcut + if (childNames.size() == 0) + { + return; + } + else if (childNames.size() > 1000) + { + throw new IllegalArgumentException("Unable to process more than 1000 child names in getChildAssocs"); + } + final Pair assocTypeQNamePair = qnameDAO.getQName(assocTypeQName); + if (assocTypeQNamePair == null) + { + return; + } + + // Convert the child names and compile the CRC values + final Set crcValues = new HashSet(childNames.size() * 2); + final Set nameValues = new HashSet(childNames.size() * 2); + for (String childName : childNames) + { + String childNameLower = childName.toLowerCase(); + String childNameShort = getShortName(childNameLower); + Long childNameLowerCrc = new Long(getCrc(childNameLower)); + crcValues.add(childNameLowerCrc); + nameValues.add(childNameShort); + } + + Node parentNode = getNodeNotNull(parentNodeId); + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_REFS_BY_TYPE_AND_NAME_LIST) + .setLong("parentId", parentNodeId) + .setLong("typeQNameId", assocTypeQNamePair.getFirst()) + .setParameterList("childNodeNames", nameValues); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); + return query.scroll(ScrollMode.FORWARD_ONLY); + } + }; + + // Create an extended callback to filter out the crc values + ChildAssocRefQueryCallbackFilter filterCallback = new ChildAssocRefQueryCallbackFilter() + { + public boolean isDesiredRow( + Pair childAssocPair, + Pair parentNodePair, + Pair childNodePair, + String assocChildNodeName, Long assocChildNodeNameCrc) + { + // The CRC value must be in the list + return crcValues.contains(assocChildNodeNameCrc); + } + /** + * Defers to the client's handler + */ + public boolean handle(Pair childAssocPair, Pair parentNodePair, Pair childNodePair) + { + return resultsCallback.handle(childAssocPair, parentNodePair, childNodePair); + } + }; + + ScrollableResults queryResults = null; + try + { + queryResults = (ScrollableResults) getHibernateTemplate().execute(callback); + convertToChildAssocRefs(parentNode, queryResults, filterCallback); + } + finally + { + if (queryResults != null) + { + queryResults.close(); + } + } + } + public void getChildAssocsByTypeQNames( final Long parentNodeId, final List assocTypeQNames, @@ -2360,12 +2447,14 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements String assocQNameNamespace = qnameDAO.getNamespace((Long) row[2]).getSecond(); String assocQNameLocalName = (String) row[3]; QName assocQName = QName.createQName(assocQNameNamespace, assocQNameLocalName); - Boolean assocIsPrimary = (Boolean) row[4]; - Integer assocIndex = (Integer) row[5]; - Long childNodeId = (Long) row[6]; - String childProtocol = (String) row[7]; - String childIdentifier = (String) row[8]; - String childUuid = (String) row[9]; + String assocChildNodeName = (String) row[4]; + Long assocChildNodeNameCrc = (Long) row[5]; + Boolean assocIsPrimary = (Boolean) row[6]; + Integer assocIndex = (Integer) row[7]; + Long childNodeId = (Long) row[8]; + String childProtocol = (String) row[9]; + String childIdentifier = (String) row[10]; + String childUuid = (String) row[11]; NodeRef childNodeRef = new NodeRef(new StoreRef(childProtocol, childIdentifier), childUuid); ChildAssociationRef assocRef = new ChildAssociationRef( assocTypeQName, @@ -2376,6 +2465,22 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements assocIndex.intValue()); Pair assocPair = new Pair(assocId, assocRef); Pair childNodePair = new Pair(childNodeId, childNodeRef); + // Check if we are doing a filtering callback + if (resultsCallback instanceof ChildAssocRefQueryCallbackFilter) + { + ChildAssocRefQueryCallbackFilter filterCallback = (ChildAssocRefQueryCallbackFilter) resultsCallback; + boolean process = filterCallback.isDesiredRow( + assocPair, + parentNodePair, + childNodePair, + assocChildNodeName, + assocChildNodeNameCrc); + if (!process) + { + // The result is filtered out + continue; + } + } // Call back resultsCallback.handle(assocPair, parentNodePair, childNodePair); } diff --git a/source/java/org/alfresco/repo/version/NodeServiceImpl.java b/source/java/org/alfresco/repo/version/NodeServiceImpl.java index 7380353e7a..ad5b2b7222 100644 --- a/source/java/org/alfresco/repo/version/NodeServiceImpl.java +++ b/source/java/org/alfresco/repo/version/NodeServiceImpl.java @@ -326,6 +326,7 @@ public class NodeServiceImpl implements NodeService, VersionModel /** * Translation for version store */ + @SuppressWarnings("unchecked") public Set getAspects(NodeRef nodeRef) throws InvalidNodeRefException { return new HashSet( @@ -523,6 +524,15 @@ public class NodeServiceImpl implements NodeService, VersionModel throw new UnsupportedOperationException(MSG_UNSUPPORTED); } + /** + * @throws UnsupportedOperationException always + */ + public List getChildrenByName(NodeRef nodeRef, QName assocTypeQName, Collection childNames) + { + // This operation is not supported for a version store + throw new UnsupportedOperationException(MSG_UNSUPPORTED); + } + /** * @throws UnsupportedOperationException always */ diff --git a/source/java/org/alfresco/service/cmr/avm/AVMService.java b/source/java/org/alfresco/service/cmr/avm/AVMService.java index b4660c2cf9..4dbc03269c 100644 --- a/source/java/org/alfresco/service/cmr/avm/AVMService.java +++ b/source/java/org/alfresco/service/cmr/avm/AVMService.java @@ -170,6 +170,21 @@ public interface AVMService * @throws AVMWrongTypeException */ public SortedMap getDirectoryListing(AVMNodeDescriptor dir); + + /** + * Get a non-recursive directory listing of a directory node + * identified by a node descriptor. Only return children matching the given pattern. + * '*' match any number of characters - equivalent to SQL '%' + * '?' match a single character - equivalent to SQL '_' + * '\' escape - valid sequences "\\", "\*" and "\?" + * + * @param dir The directory node descriptor. + * @param childPattern + * @return A sorted Map of names to node descriptors. + * @throws AVMNotFoundException + * @throws AVMWrongTypeException + */ + public SortedMap getDirectoryListing(AVMNodeDescriptor dir, String childNamePattern); /** * Non-recursively get the names of nodes that have been deleted in diff --git a/source/java/org/alfresco/service/cmr/repository/NodeService.java b/source/java/org/alfresco/service/cmr/repository/NodeService.java index 211391fb08..a97bcece32 100644 --- a/source/java/org/alfresco/service/cmr/repository/NodeService.java +++ b/source/java/org/alfresco/service/cmr/repository/NodeService.java @@ -25,6 +25,7 @@ package org.alfresco.service.cmr.repository; import java.io.Serializable; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -531,6 +532,21 @@ public interface NodeService QName assocTypeQName, String childName); + /** + * Get the nodes with the given names within the context of the parent node. + * + * {@inheritDoc #getChildByName(NodeRef, QName, String)} + * + * @param childNames a collection of up to 1000 child names to match on + * + * @see {@link #getChildByName(NodeRef, QName, String)} + */ + @Auditable(key = Auditable.Key.ARG_0 ,parameters = {"nodeRef", "assocTypeQName", "childName"}) + public List getChildrenByName( + NodeRef nodeRef, + QName assocTypeQName, + Collection childNames); + /** * Fetches the primary parent-child relationship. *

diff --git a/source/java/org/alfresco/wcm/AbstractWCMServiceImplTest.java b/source/java/org/alfresco/wcm/AbstractWCMServiceImplTest.java new file mode 100644 index 0000000000..f0e9f11099 --- /dev/null +++ b/source/java/org/alfresco/wcm/AbstractWCMServiceImplTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.wcm; + +import junit.framework.TestCase; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.PropertyMap; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * Abstract WCM Service implementation unit test + * + * @author janv + */ +public class AbstractWCMServiceImplTest extends TestCase +{ + // override jbpm.job.executor idleInterval to 10s (was 1.5m) for WCM unit tests + protected static ApplicationContext ctx =new ClassPathXmlApplicationContext( + new String[] {ApplicationContextHelper.CONFIG_LOCATIONS[0], "classpath:wcm/wcm-jbpm-context.xml"} + ); + + protected static final long SUBMIT_DELAY = 20000L; // 20s - to allow async submit direct workflow to complete (as per 10s idleInterval above) + + // + // test data + // + + protected static final String TEST_RUN = ""+System.currentTimeMillis(); + protected static final boolean CLEAN = true; // cleanup during teardown + + // + // services + // + + protected AuthenticationService authenticationService; + protected PersonService personService; + + + @Override + protected void setUp() throws Exception + { + // Get the required services + authenticationService = (AuthenticationService)ctx.getBean("AuthenticationService"); + personService = (PersonService)ctx.getBean("PersonService"); + } + + @Override + protected void tearDown() throws Exception + { + } + + protected void createUser(String userName) + { + if (authenticationService.authenticationExists(userName) == false) + { + authenticationService.createAuthentication(userName, "PWD".toCharArray()); + + 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); + } + } + + protected void deleteUser(String userName) + { + if (authenticationService.authenticationExists(userName) == true) + { + personService.deletePerson(userName); + authenticationService.deleteAuthentication(userName); + } + } +} diff --git a/source/java/org/alfresco/wcm/WCMTestSuite.java b/source/java/org/alfresco/wcm/WCMTestSuite.java index d4d881a510..cf9235914f 100644 --- a/source/java/org/alfresco/wcm/WCMTestSuite.java +++ b/source/java/org/alfresco/wcm/WCMTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2008 Alfresco Software Limited. + * Copyright (C) 2005-2009 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/source/java/org/alfresco/wcm/asset/AssetServiceImplTest.java b/source/java/org/alfresco/wcm/asset/AssetServiceImplTest.java index b090662c24..8cf1e82307 100644 --- a/source/java/org/alfresco/wcm/asset/AssetServiceImplTest.java +++ b/source/java/org/alfresco/wcm/asset/AssetServiceImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2008 Alfresco Software Limited. + * Copyright (C) 2005-2009 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -32,42 +32,27 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import junit.framework.TestCase; - import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.avm.AVMNotFoundException; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.security.AuthenticationService; -import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.namespace.QName; -import org.alfresco.util.ApplicationContextHelper; -import org.alfresco.util.PropertyMap; +import org.alfresco.wcm.AbstractWCMServiceImplTest; import org.alfresco.wcm.sandbox.SandboxInfo; import org.alfresco.wcm.sandbox.SandboxService; import org.alfresco.wcm.util.WCMUtil; import org.alfresco.wcm.webproject.WebProjectInfo; import org.alfresco.wcm.webproject.WebProjectService; -import org.springframework.context.ApplicationContext; /** * Asset Service implementation unit test * * @author janv */ -public class AssetServiceImplTest extends TestCase +public class AssetServiceImplTest extends AbstractWCMServiceImplTest { - private static final ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); - - // - // test data - // - - private static final String TEST_RUN = ""+System.currentTimeMillis(); - private static final boolean CLEAN = true; // cleanup during teardown - // base web project private static final String TEST_WEBPROJ_DNS = "testAsset-"+TEST_RUN; private static final String TEST_WEBPROJ_NAME = "testAsset Web Project Display Name - "+TEST_RUN; @@ -83,7 +68,7 @@ public class AssetServiceImplTest extends TestCase private static final String USER_ONE = TEST_USER+"-One"; private static final String USER_TWO = TEST_USER+"-Two"; private static final String USER_THREE = TEST_USER+"-Three"; - + // // services // @@ -92,20 +77,16 @@ public class AssetServiceImplTest extends TestCase private SandboxService sbService; private AssetService assetService; - private AuthenticationService authenticationService; - private PersonService personService; - @Override protected void setUp() throws Exception { + super.setUp(); + // Get the required services wpService = (WebProjectService)ctx.getBean("WebProjectService"); sbService = (SandboxService)ctx.getBean("SandboxService"); assetService = (AssetService)ctx.getBean("AssetService"); - - authenticationService = (AuthenticationService)ctx.getBean("AuthenticationService"); - personService = (PersonService)ctx.getBean("PersonService"); - + // By default run as Admin AuthenticationUtil.setFullyAuthenticatedUser(USER_ADMIN); @@ -140,32 +121,6 @@ public class AssetServiceImplTest extends TestCase super.tearDown(); } - private void createUser(String userName) - { - if (authenticationService.authenticationExists(userName) == false) - { - authenticationService.createAuthentication(userName, "PWD".toCharArray()); - - 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); - } - } - - private void deleteUser(String userName) - { - if (authenticationService.authenticationExists(userName) == true) - { - personService.deletePerson(userName); - authenticationService.deleteAuthentication(userName); - } - } - private void checkAssetInfo(AssetInfo assetInfo, String expectedName, String expectedPath, String expectedCreator, boolean expectedIsFile, boolean expectedIsFolder, boolean expectedIsDeleted, boolean expectedIsLocked, String expectedLockOwner) { assertNotNull(assetInfo); @@ -720,7 +675,7 @@ public class AssetServiceImplTest extends TestCase checkAssetInfo(myMovedFile1Asset, "myFile1", path+"/myFolder2/myFile1", USER_ONE, true, false, false, false, null); } - public void testProperties() + public void testProperties() throws InterruptedException { // create web project (also creates staging sandbox and admin's author sandbox) WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-properties", TEST_WEBPROJ_NAME+"-properties", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION, TEST_WEBPROJ_DEFAULT_WEBAPP, TEST_WEBPROJ_DONT_USE_AS_TEMPLATE, null); @@ -760,6 +715,8 @@ public class AssetServiceImplTest extends TestCase sbService.submitWebApp(sbStoreId, defaultWebApp, "submit1 label", "submit1 comment"); + Thread.sleep(SUBMIT_DELAY); + assertNull(assetService.getLockOwner(myFile1Asset)); // update (or set, if not already set) specific properties - eg. title and description @@ -873,7 +830,7 @@ public class AssetServiceImplTest extends TestCase } - public void testSimpleLockFile() + public void testSimpleLockFile() throws InterruptedException { // create web project (also creates staging sandbox and admin's author sandbox) WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-simpleLock", TEST_WEBPROJ_NAME+"-simpleLock", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION, TEST_WEBPROJ_DEFAULT_WEBAPP, TEST_WEBPROJ_DONT_USE_AS_TEMPLATE, null); @@ -918,6 +875,8 @@ public class AssetServiceImplTest extends TestCase sbService.submitWebApp(sbStoreId, defaultWebApp, "submit1 label", "submit1 comment"); + Thread.sleep(SUBMIT_DELAY); + assertNull(assetService.getLockOwner(myFile1Asset)); assertTrue(assetService.hasLockAccess(myFile1Asset)); diff --git a/source/java/org/alfresco/wcm/sandbox/SandboxService.java b/source/java/org/alfresco/wcm/sandbox/SandboxService.java index be6910ac67..4a99a796c1 100644 --- a/source/java/org/alfresco/wcm/sandbox/SandboxService.java +++ b/source/java/org/alfresco/wcm/sandbox/SandboxService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2008 Alfresco Software Limited. + * Copyright (C) 2005-2009 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -24,6 +24,7 @@ */ package org.alfresco.wcm.sandbox; +import java.io.Serializable; import java.util.Date; import java.util.List; import java.util.Map; @@ -238,21 +239,6 @@ public interface SandboxService */ public void submitList(String sbStoreId, List relativePaths, String submitLabel, String submitDescription); - /** - * Submit list of changed assets for given sandbox (eg. from user sandbox to staging sandbox) - * - * NOTE: for backwards compatibility - subject to change - hence deprecated for now - * - * @param sbStoreId sandbox store id - * @param assetPaths list of assets, as relative paths (eg. /www/avm_webapps/ROOT/MyFolderToSubmit) - * @param expirationDates map of AVM src paths to expiration dates - * @param submitLabel label for submitted snapshot - * @param submitDescription description for submitted snapshot - * - * @deprecated - */ - public void submitList(String sbStoreId, List relativePaths, Map expirationDates, String submitLabel, String submitDescription); - /** * Submit list of changed assets for given sandbox (eg. from user sandbox to staging sandbox) * @@ -266,13 +252,25 @@ public interface SandboxService /** * Submit list of changed assets for given sandbox (eg. from user sandbox to staging sandbox) * + * NOTE: for backwards compatibility - subject to change - hence deprecated for now + * * @param sbStoreId sandbox store id - * @param assets list of assets - * @param expirationDates map of for those assets set with an expiration date, or can be null (if no expiration dates) + * @param assetPaths list of assets, as relative paths (eg. /www/avm_webapps/ROOT/MyFolderToSubmit) + * @param workflowName selected workflow name - if null, will use default submit direct workflow + * @param workflowParams configured workflow params * @param submitLabel label for submitted snapshot * @param submitDescription description for submitted snapshot + * @param expirationDates optional map of for those assets set with an expiration date, or can be null (if no expiration dates) + * @param launchDate optional launch date + * @param validateLinks if true then will validate links (if link validation is enabled) + * @param autoDeploy if true then will auto-deploy on workflow approval + * + * @deprecated subject to change */ - public void submitListAssets(String sbStoreId, List assets, Map expirationDates, String submitLabel, String submitDescription); + public void submitListAssets(String sbStoreId, List relativePaths, + String workflowName, Map workflowParams, + String submitLabel, String submitDescription, + Map expirationDates, Date launchDate, boolean validateLinks, boolean autoDeploy); /** * Revert all changed assets for given sandbox (eg. in user sandbox) diff --git a/source/java/org/alfresco/wcm/sandbox/SandboxServiceImpl.java b/source/java/org/alfresco/wcm/sandbox/SandboxServiceImpl.java index 856619500b..7ad436a1bc 100644 --- a/source/java/org/alfresco/wcm/sandbox/SandboxServiceImpl.java +++ b/source/java/org/alfresco/wcm/sandbox/SandboxServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2008 Alfresco Software Limited. + * Copyright (C) 2005-2009 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -35,7 +35,7 @@ import java.util.Map; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.mbeans.VirtServerRegistry; import org.alfresco.model.WCMAppModel; -import org.alfresco.repo.avm.AVMDAOs; +import org.alfresco.model.WCMWorkflowModel; import org.alfresco.repo.avm.AVMNodeConverter; import org.alfresco.repo.avm.actions.AVMRevertStoreAction; import org.alfresco.repo.avm.actions.AVMUndoSandboxListAction; @@ -43,20 +43,26 @@ import org.alfresco.repo.domain.PropertyValue; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.TransactionListenerAdapter; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.workflow.WorkflowModel; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.avm.VersionDescriptor; -import org.alfresco.service.cmr.avm.locking.AVMLockingService; import org.alfresco.service.cmr.avmsync.AVMDifference; import org.alfresco.service.cmr.avmsync.AVMSyncService; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.repository.NodeRef; +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.cmr.workflow.WorkflowTaskState; import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.NameMatcher; import org.alfresco.util.Pair; import org.alfresco.util.ParameterCheck; @@ -84,16 +90,19 @@ public class SandboxServiceImpl implements SandboxService /** Logger */ private static Log logger = LogFactory.getLog(SandboxServiceImpl.class); + private static final String WORKFLOW_SUBMITDIRECT = "jbpm$wcmwf:submitdirect"; + private WebProjectService wpService; private SandboxFactory sandboxFactory; private AVMService avmService; - private AVMLockingService avmLockingService; private AVMSyncService avmSyncService; private NameMatcher nameMatcher; private VirtServerRegistry virtServerRegistry; private ActionService actionService; private WorkflowService workflowService; private AssetService assetService; + private TransactionService transactionService; + public void setWebProjectService(WebProjectService wpService) { @@ -110,11 +119,6 @@ public class SandboxServiceImpl implements SandboxService this.avmService = avmService; } - public void setAvmLockingService(AVMLockingService avmLockingService) - { - this.avmLockingService = avmLockingService; - } - public void setAvmSyncService(AVMSyncService avmSyncService) { this.avmSyncService = avmSyncService; @@ -144,6 +148,12 @@ public class SandboxServiceImpl implements SandboxService { this.assetService = assetService; } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + /* (non-Javadoc) * @see org.alfresco.wcm.sandbox.SandboxService#createAuthorSandbox(java.lang.String) @@ -491,16 +501,6 @@ public class SandboxServiceImpl implements SandboxService { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); - submitList(sbStoreId, relativePaths, null, submitLabel, submitComment); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.sandbox.SandboxService#submitList(java.lang.String, java.util.List, java.util.Map, java.lang.String, java.lang.String) - */ - public void submitList(String sbStoreId, List relativePaths, Map expirationDates, String submitLabel, String submitComment) - { - ParameterCheck.mandatoryString("sbStoreId", sbStoreId); - List assets = new ArrayList(relativePaths.size()); for (String relativePath : relativePaths) @@ -513,7 +513,7 @@ public class SandboxServiceImpl implements SandboxService } } - submitListAssets(sbStoreId, assets, expirationDates, submitLabel, submitComment); + submitListAssets(sbStoreId, assets, submitLabel, submitComment); } /* (non-Javadoc) @@ -522,18 +522,7 @@ public class SandboxServiceImpl implements SandboxService public void submitListAssets(String sbStoreId, List assets, String submitLabel, String submitComment) { ParameterCheck.mandatoryString("sbStoreId", sbStoreId); - - submitListAssets(sbStoreId, assets, null, submitLabel, submitComment); - } - - /* (non-Javadoc) - * @see org.alfresco.wcm.sandbox.SandboxService#submitListAssets(java.lang.String, java.util.List, java.util.Map, java.lang.String, java.lang.String) - */ - public void submitListAssets(String sbStoreId, List assets, Map expirationDates, final String submitLabel, final String submitComment) - { - ParameterCheck.mandatoryString("sbStoreId", sbStoreId); - - // direct submit to the staging area (without workflow) + ParameterCheck.mandatoryString("submitLabel", submitLabel); // TODO - consider submit to higher-level sandbox, not just to staging if (! WCMUtil.isUserStore(sbStoreId)) @@ -541,61 +530,322 @@ public class SandboxServiceImpl implements SandboxService throw new AlfrescoRuntimeException("Not an author sandbox: "+sbStoreId); } - // construct diffs for selected assets for submission - String wpStoreId = WCMUtil.getWebProjectStoreId(sbStoreId); - String stagingSandboxId = WCMUtil.buildStagingStoreName(wpStoreId); - - final List diffs = new ArrayList(assets.size()); - + List relativePaths = new ArrayList(assets.size()); for (AssetInfo asset : assets) { - String relativePath = WCMUtil.getStoreRelativePath(asset.getAvmPath()); - - String srcPath = sbStoreId + WCMUtil.AVM_STORE_SEPARATOR + relativePath; - String dstPath = stagingSandboxId + WCMUtil.AVM_STORE_SEPARATOR + relativePath; - - AVMDifference diff = new AVMDifference(-1, srcPath, -1, dstPath, AVMDifference.NEWER); - diffs.add(diff); + relativePaths.add(asset.getPath()); + } + + // via submit direct workflow + submitViaWorkflow(sbStoreId, relativePaths, null, null, submitLabel, submitComment, null, null, false, false); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#submitListAssets(java.lang.String, java.util.List, java.lang.String, java.util.Map, java.lang.String, java.lang.String, java.util.Map, java.util.Date, boolean, boolean) + */ + public void submitListAssets(String sbStoreId, List relativePaths, + String workflowName, Map workflowParams, + String submitLabel, String submitComment, + Map expirationDates, Date launchDate, boolean validateLinks, boolean autoDeploy) + { + // via selected workflow + submitViaWorkflow(sbStoreId, relativePaths, workflowName, workflowParams, submitLabel, submitComment, + expirationDates, launchDate, validateLinks, autoDeploy); + } + + /** + * Submits the selected items via the configured workflow. + *

+ * This method uses 2 separate transactions to perform the submit. + * The first one creates the workflow sandbox. The virtualisation + * server is then informed of the new stores. The second + * transaction then starts the appropriate workflow. This approach + * is needed to allow link validation to be performed on the + * workflow sandbox. + */ + private void submitViaWorkflow(final String sbStoreId, final List relativePaths, String workflowName, Map workflowParams, + final String submitLabel, final String submitComment, + final Map expirationDates, final Date launchDate, final boolean validateLinks, final boolean autoDeploy) + { + final String wpStoreId = WCMUtil.getWebProjectStoreId(sbStoreId); + final String stagingSandboxId = WCMUtil.buildStagingStoreName(wpStoreId); + + final String finalWorkflowName; + final Map finalWorkflowParams; + + if ((workflowName == null) || (workflowName.equals(""))) + { + finalWorkflowName = WORKFLOW_SUBMITDIRECT; + finalWorkflowParams = new HashMap(); + } + else + { + finalWorkflowName = workflowName; + finalWorkflowParams = workflowParams; + } + + RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); + + final List srcPaths = new ArrayList(relativePaths.size()); - if (expirationDates != null) + String derivedWebApp = null; + boolean multiWebAppsFound = false; + + for (String relativePath : relativePaths) + { + // Example srcPath: + // mysite--alice:/www/avm_webapps/ROOT/foo.txt + String srcPath = sbStoreId + WCMUtil.AVM_STORE_SEPARATOR + relativePath; + srcPaths.add(srcPath); + + // derive webapp for now (TODO check usage) + String srcWebApp = WCMUtil.getWebapp(srcPath); + if (srcWebApp != null) + { + if (derivedWebApp == null) + { + derivedWebApp = srcWebApp; + } + else if (! derivedWebApp.equals(srcWebApp)) + { + multiWebAppsFound = true; + } + } + } + + final String webApp = (multiWebAppsFound == false ? derivedWebApp : null); + + RetryingTransactionCallback> sandboxCallback = new RetryingTransactionCallback>() + { + public Pair execute() throws Throwable + { + // call the actual implementation + return createWorkflowSandbox(finalWorkflowName, finalWorkflowParams, stagingSandboxId, srcPaths, expirationDates); + } + }; + + // create the workflow sandbox firstly + final Pair workflowInfo = txnHelper.doInTransaction(sandboxCallback, false, true); + + if (workflowInfo != null) + { + final SandboxInfo wfSandboxInfo = workflowInfo.getFirst(); + String virtUpdatePath = workflowInfo.getSecond(); + + // inform the virtualisation server if the workflow sandbox was created + if (virtUpdatePath != null) + { + WCMUtil.updateVServerWebapp(virtServerRegistry, virtUpdatePath, true); + } + + try + { + RetryingTransactionCallback workflowCallback = new RetryingTransactionCallback() + { + public String execute() throws Throwable + { + // call the actual implementation + startWorkflow(wpStoreId, sbStoreId, wfSandboxInfo, webApp, finalWorkflowName, finalWorkflowParams, submitLabel, submitComment, launchDate, validateLinks, autoDeploy); + return null; + } + }; + + // start the workflow + txnHelper.doInTransaction(workflowCallback, false, true); + } + catch (Throwable err) + { + cleanupWorkflowSandbox(wfSandboxInfo); + throw new AlfrescoRuntimeException("Failed to submit to workflow", err); + } + } + } + + /** + * Creates a workflow sandbox for all the submitted items + * + * @param context Faces context + */ + protected Pair createWorkflowSandbox(String workflowName, Map workflowParams, String stagingSandboxId, final List srcPaths, Map expirationDates) + { + // The virtualization server might need to be notified + // because one or more of the files submitted could alter + // the behavior the virtual webapp in the target of the submit. + // For example, the user might be submitting a new jar or web.xml file. + // + // This must take place after the transaction has been completed; + // therefore, a variable is needed to store the path to the + // updated webapp so it can happen in doPostCommitProcessing. + String virtUpdatePath = null; + SandboxInfo sandboxInfo = null; + + // create container for our avm workflow package + + if (! workflowName.equals(WORKFLOW_SUBMITDIRECT)) + { + // Create workflow sandbox for workflow package + sandboxInfo = sandboxFactory.createWorkflowSandbox(stagingSandboxId); + } + else + { + // default to direct submit workflow + + // NOTE: read only workflow sandbox is lighter to construct than full workflow sandbox + sandboxInfo = sandboxFactory.createReadOnlyWorkflowSandbox(stagingSandboxId); + } + + // Example workflow main store name: + // mysite--workflow-9161f640-b020-11db-8015-130bf9b5b652 + String workflowMainStoreName = sandboxInfo.getMainStoreName(); + + List diffs = new ArrayList(srcPaths.size()); + + // get diff list - also process expiration dates, if any, and set virt svr update path + + for (String srcPath : srcPaths) + { + // We *always* want to update virtualization server + // when a workflow sandbox is given data in the + // context of a submit workflow. Without this, + // it would be impossible to see workflow data + // in context. The raw operation to create a + // workflow sandbox does not notify the virtualization + // server that it exists because it's useful to + // defer this operation until everything is already + // in place; this allows pointlessly fine-grained + // notifications to be suppressed (they're expensive). + // + // Therefore, just derive the name of the webapp + // in the workflow sandbox from the 1st item in + // the submit list (even if it's not in WEB-INF), + // and force the virt server notification after the + // transaction has completed via doPostCommitProcessing. + if (virtUpdatePath == null) + { + // The virtUpdatePath looks just like the srcPath + // except that it belongs to a the main store of + // the workflow sandbox instead of the sandbox + // that originated the submit. + virtUpdatePath = workflowMainStoreName + srcPath.substring(srcPath.indexOf(':'),srcPath.length()); + } + + if ((expirationDates != null) && (! expirationDates.isEmpty())) { // process the expiration date (if any) processExpirationDate(srcPath, expirationDates); } + + diffs.add(new AVMDifference(-1, srcPath, -1, WCMUtil.getCorrespondingPath(srcPath, workflowMainStoreName), AVMDifference.NEWER)); + } - // recursively remove locks from this item - recursivelyRemoveLocks(wpStoreId, -1, avmService.lookup(-1, srcPath, true), srcPath); + // write changes to layer so files are marked as modified + avmSyncService.update(diffs, null, false, false, false, false, null, null); + + return new Pair(sandboxInfo, virtUpdatePath); + } - // check to see if destPath forces a notification of the virtualization server - // (e.g.: it might be a path to a jar file within WEB-INF/lib). - if (VirtServerUtils.requiresUpdateNotification(dstPath)) + /** + * Starts the configured workflow to allow the submitted items to be link + * checked and reviewed. + */ + protected void startWorkflow(String wpStoreId, String sbStoreId, SandboxInfo wfSandboxInfo, String webApp, String workflowName, Map workflowParams, + String submitLabel, String submitComment, Date launchDate, boolean validateLinks, boolean autoDeploy) + { + ParameterCheck.mandatoryString("workflowName", workflowName); + ParameterCheck.mandatory("workflowParams", workflowParams); + + // start the workflow to get access to the start task + WorkflowDefinition wfDef = workflowService.getDefinitionByName(workflowName); + WorkflowPath path = workflowService.startWorkflow(wfDef.id, null); + + if (path != null) + { + // extract the start task + List tasks = workflowService.getTasksForWorkflowPath(path.id); + if (tasks.size() == 1) { - // Bind the post-commit transaction listener with data required for virtualization server notification - UpdateSandboxTransactionListener tl = new UpdateSandboxTransactionListener(dstPath); - AlfrescoTransactionSupport.bindListener(tl); + WorkflowTask startTask = tasks.get(0); + + if (startTask.state == WorkflowTaskState.IN_PROGRESS) + { + final NodeRef workflowPackage = WCMWorkflowUtil.createWorkflowPackage(workflowService, avmService, wfSandboxInfo); + + workflowParams.put(WorkflowModel.ASSOC_PACKAGE, workflowPackage); + + // add submission parameters + workflowParams.put(WorkflowModel.PROP_WORKFLOW_DESCRIPTION, submitComment); + workflowParams.put(WCMWorkflowModel.PROP_LABEL, submitLabel); + workflowParams.put(WCMWorkflowModel.PROP_FROM_PATH, + WCMUtil.buildStoreRootPath(sbStoreId)); + workflowParams.put(WCMWorkflowModel.PROP_LAUNCH_DATE, launchDate); + workflowParams.put(WCMWorkflowModel.PROP_VALIDATE_LINKS, + new Boolean(validateLinks)); + workflowParams.put(WCMWorkflowModel.PROP_AUTO_DEPLOY, + new Boolean(autoDeploy)); + workflowParams.put(WCMWorkflowModel.PROP_WEBAPP, + webApp); + workflowParams.put(WCMWorkflowModel.ASSOC_WEBPROJECT, + wpService.getWebProjectNodeFromStore(wpStoreId)); + + // update start task with submit parameters + workflowService.updateTask(startTask.id, workflowParams, null, null); + + // end the start task to trigger the first 'proper' task in the workflow + workflowService.endTask(startTask.id, null); + } } } - - // write changes to layer so files are marked as modified - - // Submit is done as system as the staging store is read only - // We could add support to runIgnoringStoreACls - - // TODO review flatten - assumes webapps, hence currently flattens at /www/avm_webapps level - // also review flatten for SimpleAVMSubmitAction and AVMSubmitAction - final String sandboxPath = WCMUtil.buildSandboxRootPath(sbStoreId); - final String stagingPath = WCMUtil.buildSandboxRootPath(stagingSandboxId); + } - AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + /** + * Cleans up the workflow sandbox created by the first transaction. This + * action is itself preformed in a separate transaction. + */ + private void cleanupWorkflowSandbox(final SandboxInfo sandboxInfo) + { + RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); + + RetryingTransactionCallback callback = new RetryingTransactionCallback() { - public Object doWork() throws Exception - { - avmSyncService.update(diffs, null, true, true, false, false, submitLabel, submitComment); - AVMDAOs.Instance().fAVMNodeDAO.flush(); - avmSyncService.flatten(sandboxPath, stagingPath); - return null; - } - }, AuthenticationUtil.getSystemUserName()); + public String execute() throws Throwable + { + // call the actual implementation + cleanupWorkflowSandboxImpl(sandboxInfo); + return null; + } + }; + + try + { + // Execute the cleanup handler + txnHelper.doInTransaction(callback); + } + catch (Throwable e) + { + // not much we can do now, just log the error to inform admins + logger.error("Failed to cleanup workflow sandbox after workflow failure", e); + } + } + + /** + * Performs the actual deletion of stores in the workflow sandbox. + */ + private void cleanupWorkflowSandboxImpl(SandboxInfo sandboxInfo) + { + if (sandboxInfo != null) + { + String mainWorkflowStore = sandboxInfo.getMainStoreName(); + Map matches = avmService.queryStorePropertyKey(mainWorkflowStore, + QName.createQName(null, ".sandbox-id%")); + + QName sandboxID = matches.keySet().iterator().next(); + // Get all the stores in the sandbox. + Map> stores = avmService.queryStoresPropertyKeys(sandboxID); + for (String storeName : stores.keySet()) + { + avmService.purgeStore(storeName); + } + } } /* (non-Javadoc) @@ -826,6 +1076,7 @@ public class SandboxServiceImpl implements SandboxService * Recursively remove locks from a path. Walking child folders looking for files * to remove locks from. */ + /* private void recursivelyRemoveLocks(String wpStoreId, int version, AVMNodeDescriptor desc, String absoluteAVMPath) { if (desc.isFile() || desc.isDeletedFile()) @@ -854,6 +1105,7 @@ public class SandboxServiceImpl implements SandboxService } } } + */ /** * Create Sandbox Transaction listener - invoked after commit diff --git a/source/java/org/alfresco/wcm/sandbox/SandboxServiceImplTest.java b/source/java/org/alfresco/wcm/sandbox/SandboxServiceImplTest.java index faa53eea72..2e5b1eb27d 100644 --- a/source/java/org/alfresco/wcm/sandbox/SandboxServiceImplTest.java +++ b/source/java/org/alfresco/wcm/sandbox/SandboxServiceImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2008 Alfresco Software Limited. + * Copyright (C) 2005-2009 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -31,9 +31,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import junit.framework.TestCase; - -import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.permissions.AccessDeniedException; @@ -41,35 +38,24 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransacti import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.security.AuthenticationService; -import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.PropertyMap; +import org.alfresco.wcm.AbstractWCMServiceImplTest; import org.alfresco.wcm.asset.AssetInfo; import org.alfresco.wcm.asset.AssetService; import org.alfresco.wcm.util.WCMUtil; import org.alfresco.wcm.webproject.WebProjectInfo; import org.alfresco.wcm.webproject.WebProjectService; -import org.springframework.context.ApplicationContext; /** * Sandbox Service implementation unit test * * @author janv */ -public class SandboxServiceImplTest extends TestCase -{ - private static final ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); - - // - // test data - // - - private static final String TEST_RUN = ""+System.currentTimeMillis(); - private static final boolean CLEAN = true; // cleanup during teardown - +public class SandboxServiceImplTest extends AbstractWCMServiceImplTest +{ // base web project private static final String TEST_WEBPROJ_DNS = "testSandbox-"+TEST_RUN; private static final String TEST_WEBPROJ_NAME = "testSandbox Web Project Display Name - "+TEST_RUN; @@ -102,9 +88,6 @@ public class SandboxServiceImplTest extends TestCase private SandboxService sbService; private AssetService assetService; - private AuthenticationService authenticationService; - private PersonService personService; - // TODO: temporary - remove from here when r13170 is merged from V3.1->HEAD private TransactionService transactionService; @@ -117,14 +100,13 @@ public class SandboxServiceImplTest extends TestCase @Override protected void setUp() throws Exception { + super.setUp(); + // Get the required services wpService = (WebProjectService)ctx.getBean("WebProjectService"); sbService = (SandboxService)ctx.getBean("SandboxService"); assetService = (AssetService)ctx.getBean("AssetService"); - authenticationService = (AuthenticationService)ctx.getBean("AuthenticationService"); - personService = (PersonService)ctx.getBean("PersonService"); - avmService = (AVMService)ctx.getBean("AVMService"); // TODO: temporary - remove from here when r13170 is merged from V3.1->HEAD @@ -183,32 +165,6 @@ public class SandboxServiceImplTest extends TestCase super.tearDown(); } - private void createUser(String userName) - { - if (authenticationService.authenticationExists(userName) == false) - { - authenticationService.createAuthentication(userName, "PWD".toCharArray()); - - 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); - } - } - - private void deleteUser(String userName) - { - if (authenticationService.authenticationExists(userName) == true) - { - personService.deletePerson(userName); - authenticationService.deleteAuthentication(userName); - } - } - public void testSimple() { int storeCnt = avmService.getStores().size(); @@ -792,7 +748,7 @@ public class SandboxServiceImplTest extends TestCase */ // submit new assets in user sandbox to staging sandbox - public void testSubmitNewItems1() + public void testSubmitNewItems1() throws InterruptedException { WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-submitNewItems1", TEST_WEBPROJ_NAME+" submitNewItems1", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); @@ -833,6 +789,8 @@ public class SandboxServiceImplTest extends TestCase // submit (new assets) ! sbService.submitWebApp(authorSandboxId, webApp, "a submit label", "a submit comment"); + Thread.sleep(SUBMIT_DELAY); + assets = sbService.listChangedWebApp(authorSandboxId, webApp, false); assertEquals(0, assets.size()); @@ -858,7 +816,7 @@ public class SandboxServiceImplTest extends TestCase } // submit changed assets in user sandbox to staging sandbox - public void testSubmitChangedAssets1() throws IOException + public void testSubmitChangedAssets1() throws IOException, InterruptedException { WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-submitChangedAssets1", TEST_WEBPROJ_NAME+" submitChangedAssets1", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); @@ -867,8 +825,15 @@ public class SandboxServiceImplTest extends TestCase final String stagingSandboxId = wpInfo.getStagingStoreName(); // Invite web users + + // TODO - pending merge of ETWOTWO-1088 fix + /* wpService.inviteWebUser(wpStoreId, USER_ONE, WCMUtil.ROLE_CONTENT_CONTRIBUTOR, true); wpService.inviteWebUser(wpStoreId, USER_TWO, WCMUtil.ROLE_CONTENT_PUBLISHER, true); + */ + + wpService.inviteWebUser(wpStoreId, USER_ONE, WCMUtil.ROLE_CONTENT_MANAGER, true); + wpService.inviteWebUser(wpStoreId, USER_TWO, WCMUtil.ROLE_CONTENT_MANAGER, true); // Switch to USER_ONE AuthenticationUtil.setFullyAuthenticatedUser(USER_ONE); @@ -906,6 +871,8 @@ public class SandboxServiceImplTest extends TestCase // submit (new assets) ! sbService.submitWebApp(authorSandboxId, webApp, "a submit label", "a submit comment"); + Thread.sleep(SUBMIT_DELAY); + assets = sbService.listChangedWebApp(authorSandboxId, webApp, false); assertEquals(0, assets.size()); @@ -957,7 +924,9 @@ public class SandboxServiceImplTest extends TestCase assertEquals(MYFILE2, new String(buff, 0, MYFILE2.length())); // submit (modified assets) ! - sbService.submitWebApp(authorSandboxId, webApp, null, null); + sbService.submitWebApp(authorSandboxId, webApp, "my label", null); + + Thread.sleep(SUBMIT_DELAY); assets = sbService.listChangedWebApp(authorSandboxId, webApp, false); assertEquals(0, assets.size()); @@ -978,8 +947,94 @@ public class SandboxServiceImplTest extends TestCase assertEquals(MYFILE2_MODIFIED, new String(buff, 0, MYFILE1_MODIFIED.length())); } + // submit "all" changed assets in user sandbox to staging sandbox (not using default webapp) + public void testSubmitChangedAssets2() throws IOException, InterruptedException + { + WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-submitChangedAssets1", TEST_WEBPROJ_NAME+" submitChangedAssets1", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); + + final String wpStoreId = wpInfo.getStoreId(); + final String stagingSandboxId = wpInfo.getStagingStoreName(); + + SandboxInfo sbInfo = sbService.getAuthorSandbox(wpStoreId); + String authorSandboxId = sbInfo.getSandboxId(); + + String rootPath = sbInfo.getSandboxRootPath(); // currently /www/avm_webapps + + // no changes yet + List assets = sbService.listChangedAll(authorSandboxId, true); + assertEquals(0, assets.size()); + + assetService.createFolder(authorSandboxId, rootPath, "a", null); + assetService.createFolder(authorSandboxId, rootPath+"/a", "b", null); + assetService.createFolder(authorSandboxId, rootPath+"/a/b", "c", null); + + final String MYFILE1 = "This is foo"; + ContentWriter writer = assetService.createFile(authorSandboxId, rootPath+"/a/b/c", "foo", null); + writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + writer.setEncoding("UTF-8"); + writer.putContent(MYFILE1); + + final String MYFILE2 = "This is bar"; + writer = assetService.createFile(authorSandboxId, rootPath+"/a/b/c", "bar", null); + writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + writer.setEncoding("UTF-8"); + writer.putContent(MYFILE2); + + assets = sbService.listChangedAll(authorSandboxId, true); + assertEquals(1, assets.size()); + + // check staging before + assertEquals(1, assetService.listAssets(stagingSandboxId, -1, rootPath, false).size()); // note: currently includes default webapp ('ROOT') + + // submit (new assets) ! + sbService.submitAll(authorSandboxId, "a submit label", "a submit comment"); + + Thread.sleep(SUBMIT_DELAY); + + // check staging after + List listing = assetService.listAssets(stagingSandboxId, -1, rootPath, false); + assertEquals(2, listing.size()); // 'a' and 'ROOT' + + // no changes in sandbox + assets = sbService.listChangedAll(authorSandboxId, true); + assertEquals(0, assets.size()); + + final String MYFILE3 = "This is figs"; + writer = assetService.createFile(authorSandboxId, rootPath, "figs", null); + writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + writer.setEncoding("UTF-8"); + writer.putContent(MYFILE3); + + final String MYFILE1_MODIFIED = "This is foo ... modified"; + writer = assetService.getContentWriter(assetService.getAsset(authorSandboxId, rootPath+"/a/b/c/foo")); + writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + writer.setEncoding("UTF-8"); + writer.putContent(MYFILE1_MODIFIED); + + assetService.deleteAsset(assetService.getAsset(authorSandboxId, rootPath+"/a/b/c/bar")); + + assets = sbService.listChangedAll(authorSandboxId, true); + assertEquals(3, assets.size()); + + // check staging before + listing = assetService.listAssets(stagingSandboxId, -1, rootPath, false); + assertEquals(2, listing.size()); // 'a' and 'ROOT' + + // submit all (modified assets) ! + sbService.submitAll(authorSandboxId, "my label", null); + + Thread.sleep(SUBMIT_DELAY); + + assets = sbService.listChangedAll(authorSandboxId, true); + assertEquals(0, assets.size()); + + // check staging after + listing = assetService.listAssets(stagingSandboxId, -1, rootPath, false); + assertEquals(3, listing.size()); // 'figs', 'a' and 'ROOT' + } + // submit deleted assets in user sandbox to staging sandbox - public void testSubmitDeletedItems1() throws IOException + public void testSubmitDeletedItems1() throws IOException, InterruptedException { WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-submitDeletedItems1", TEST_WEBPROJ_NAME+" submitDeletedItems1", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); @@ -1028,6 +1083,8 @@ public class SandboxServiceImplTest extends TestCase // submit (new assets) ! sbService.submitWebApp(authorSandboxId, webApp, "a submit label", "a submit comment"); + Thread.sleep(SUBMIT_DELAY); + assets = sbService.listChangedWebApp(authorSandboxId, webApp, false); assertEquals(0, assets.size()); @@ -1068,7 +1125,9 @@ public class SandboxServiceImplTest extends TestCase assertNotNull(assetService.getAssetWebApp(stagingSandboxId, webApp, "/myDir1/myFile2")); // submit (deleted assets) ! - sbService.submitWebApp(authorSandboxId, webApp, null, null); + sbService.submitWebApp(authorSandboxId, webApp, "my label", null); + + Thread.sleep(SUBMIT_DELAY); assets = sbService.listChangedWebApp(authorSandboxId, webApp, false); assertEquals(0, assets.size()); @@ -1082,7 +1141,7 @@ public class SandboxServiceImplTest extends TestCase } // revert all (changed) assets in user sandbox - public void testRevertAll() throws IOException + public void testRevertAll() throws IOException, InterruptedException { WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-revertChangedAssets", TEST_WEBPROJ_NAME+" revertChangedAssets", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); @@ -1134,6 +1193,8 @@ public class SandboxServiceImplTest extends TestCase // submit (new assets) ! sbService.submitWebApp(authorSandboxId, webApp, "a submit label", "a submit comment"); + Thread.sleep(SUBMIT_DELAY); + assets = sbService.listChangedWebApp(authorSandboxId, webApp, false); assertEquals(0, assets.size()); @@ -1207,7 +1268,7 @@ public class SandboxServiceImplTest extends TestCase assertEquals(MYFILE2, new String(buff, 0, MYFILE2.length())); } - public void testListSnapshots() throws IOException + public void testListSnapshots() throws IOException, InterruptedException { Date fromDate = new Date(); @@ -1247,6 +1308,8 @@ public class SandboxServiceImplTest extends TestCase // submit (new assets) ! sbService.submitWebApp(authorSandboxId, webApp, "a submit label", "a submit comment"); + Thread.sleep(SUBMIT_DELAY); + assets = sbService.listChangedWebApp(authorSandboxId, webApp, false); assertEquals(0, assets.size()); @@ -1263,6 +1326,8 @@ public class SandboxServiceImplTest extends TestCase // submit (new assets) ! sbService.submitWebApp(authorSandboxId, webApp, "a submit label", "a submit comment"); + Thread.sleep(SUBMIT_DELAY); + // check staging after listing = assetService.listAssets(stagingSandboxId, -1, stagingSandboxPath, false); assertEquals(4, listing.size()); @@ -1271,7 +1336,7 @@ public class SandboxServiceImplTest extends TestCase assertEquals(2, sbVersions.size()); } - public void testRevertSnapshot() throws IOException + public void testRevertSnapshot() throws IOException, InterruptedException { Date fromDate = new Date(); @@ -1315,6 +1380,8 @@ public class SandboxServiceImplTest extends TestCase // submit (new assets) ! sbService.submitWebApp(authorSandboxId, webApp, "a submit label", "a submit comment"); + Thread.sleep(SUBMIT_DELAY); + assets = sbService.listChangedWebApp(authorSandboxId, webApp, false); assertEquals(0, assets.size()); @@ -1342,6 +1409,8 @@ public class SandboxServiceImplTest extends TestCase // submit (new assets) ! sbService.submitWebApp(authorSandboxId, webApp, "a submit label", "a submit comment"); + Thread.sleep(SUBMIT_DELAY); + // check staging after listing = assetService.listAssets(stagingSandboxId, -1, stagingSandboxPath, false); assertEquals(2, listing.size()); @@ -1370,6 +1439,8 @@ public class SandboxServiceImplTest extends TestCase // submit (new assets) ! sbService.submitWebApp(authorSandboxId, webApp, "a submit label", "a submit comment"); + Thread.sleep(SUBMIT_DELAY); + // check staging after listing = assetService.listAssets(stagingSandboxId, -1, stagingSandboxPath, false); assertEquals(3, listing.size()); diff --git a/source/java/org/alfresco/wcm/util/WCMUtil.java b/source/java/org/alfresco/wcm/util/WCMUtil.java index 350662880c..a572d4e8d0 100644 --- a/source/java/org/alfresco/wcm/util/WCMUtil.java +++ b/source/java/org/alfresco/wcm/util/WCMUtil.java @@ -289,7 +289,7 @@ public class WCMUtil * * @return the corresponding path within the supplied store */ - protected static String getCorrespondingPath(final String avmPath, final String otherStore) + public static String getCorrespondingPath(final String avmPath, final String otherStore) { return (otherStore + AVM_STORE_SEPARATOR + WCMUtil.getStoreRelativePath(avmPath)); } @@ -390,7 +390,7 @@ public class WCMUtil * * @return root path for the specified store name */ - protected static String buildStoreRootPath(final String storeName) + public static String buildStoreRootPath(final String storeName) { ParameterCheck.mandatoryString("storeName", storeName); return storeName + AVM_STORE_SEPARATOR + "/" + JNDIConstants.DIR_DEFAULT_WWW; @@ -578,7 +578,7 @@ public class WCMUtil * * @return an the webapp name contained within the path or null. */ - protected static String getWebapp(final String absoluteAVMPath) + public static String getWebapp(final String absoluteAVMPath) { final Matcher m = WEBAPP_RELATIVE_PATH_PATTERN.matcher(absoluteAVMPath); return m.matches() && m.group(2).length() != 0 ? m.group(2) : null; diff --git a/source/java/org/alfresco/wcm/util/WCMWorkflowUtil.java b/source/java/org/alfresco/wcm/util/WCMWorkflowUtil.java index 0a65729dd1..d863feee3e 100644 --- a/source/java/org/alfresco/wcm/util/WCMWorkflowUtil.java +++ b/source/java/org/alfresco/wcm/util/WCMWorkflowUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2008 Alfresco Software Limited. + * Copyright (C) 2005-2009 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -29,15 +29,20 @@ import java.util.LinkedList; import java.util.List; import org.alfresco.model.WCMWorkflowModel; +import org.alfresco.repo.avm.AVMNodeConverter; +import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.workflow.WorkflowModel; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; import org.alfresco.service.cmr.avm.AVMNotFoundException; import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.avm.LayeringDescriptor; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.workflow.WorkflowService; import org.alfresco.service.cmr.workflow.WorkflowTask; import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; import org.alfresco.service.namespace.QName; +import org.alfresco.wcm.sandbox.SandboxInfo; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -52,6 +57,28 @@ public class WCMWorkflowUtil { private static final Log logger = LogFactory.getLog(WCMWorkflowUtil.class); + public static NodeRef createWorkflowPackage(WorkflowService workflowService, AVMService avmService, SandboxInfo sandboxInfo) + { + // create package paths (layered to user sandbox area as target) + final String workflowMainStoreName = sandboxInfo.getMainStoreName(); + final String packagesPath = WCMUtil.buildStoreRootPath(workflowMainStoreName); + + // convert package to workflow package + final AVMNodeDescriptor packageDesc = avmService.lookup(-1, packagesPath); + final NodeRef packageNodeRef = workflowService.createPackage(AVMNodeConverter.ToNodeRef(-1, packageDesc.getPath())); + + avmService.setNodeProperty(packagesPath, WorkflowModel.PROP_IS_SYSTEM_PACKAGE, new PropertyValue(DataTypeDefinition.BOOLEAN, true)); + + // NOTE: WCM-1019: As permissions are now implemented for AVM nodes we no longer need to set permisssions here + // as they will be inherited from the store the workflow store is layered over. + + //final ServiceRegistry services = Repository.getServiceRegistry(FacesContext.getCurrentInstance()); + //final PermissionService permissionService = services.getPermissionService(); + //permissionService.setPermission(packageNodeRef, PermissionService.ALL_AUTHORITIES, PermissionService.ALL_PERMISSIONS, true); + + return packageNodeRef; + } + public static List getAssociatedTasksForSandbox(WorkflowService workflowService, final String storeName) { String fromPath = WCMUtil.buildStoreRootPath(storeName); diff --git a/source/java/org/alfresco/wcm/webproject/WebProjectServiceImplTest.java b/source/java/org/alfresco/wcm/webproject/WebProjectServiceImplTest.java index 576d9cd7e7..fd8a794f9b 100644 --- a/source/java/org/alfresco/wcm/webproject/WebProjectServiceImplTest.java +++ b/source/java/org/alfresco/wcm/webproject/WebProjectServiceImplTest.java @@ -30,8 +30,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import junit.framework.TestCase; - import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.security.authentication.AuthenticationUtil; @@ -47,27 +45,16 @@ import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.util.ApplicationContextHelper; -import org.alfresco.util.PropertyMap; +import org.alfresco.wcm.AbstractWCMServiceImplTest; import org.alfresco.wcm.util.WCMUtil; -import org.springframework.context.ApplicationContext; /** * Web Project Service implementation unit test * * @author janv */ -public class WebProjectServiceImplTest extends TestCase +public class WebProjectServiceImplTest extends AbstractWCMServiceImplTest { - private static final ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); - - // - // test data - // - - private static final String TEST_RUN = ""+System.currentTimeMillis(); - private static final boolean CLEAN = true; // cleanup during teardown - // base web project dns / name private static final String TEST_WEBPROJ_DNS = "testWebProjDNS-"+TEST_RUN; private static final String TEST_WEBPROJ_NAME = "test Web Project Display Name - "+TEST_RUN; @@ -109,8 +96,6 @@ public class WebProjectServiceImplTest extends TestCase // private WebProjectService wpService; - private AuthenticationService authenticationService; - private PersonService personService; private FileFolderService fileFolderService; private AuthorityService authorityService; private PermissionService permissionService; @@ -119,6 +104,8 @@ public class WebProjectServiceImplTest extends TestCase @Override protected void setUp() throws Exception { + super.setUp(); + // Get the required services wpService = (WebProjectService)ctx.getBean("WebProjectService"); authenticationService = (AuthenticationService)ctx.getBean("AuthenticationService"); @@ -186,23 +173,6 @@ public class WebProjectServiceImplTest extends TestCase super.tearDown(); } - private void createUser(String userName) - { - if (authenticationService.authenticationExists(userName) == false) - { - authenticationService.createAuthentication(userName, "PWD".toCharArray()); - - 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); - } - } - private void createSimpleGroup(String shortName, Set userNames) { String groupName = authorityService.getName(AuthorityType.GROUP, shortName); @@ -217,15 +187,6 @@ public class WebProjectServiceImplTest extends TestCase } } - private void deleteUser(String userName) - { - if (authenticationService.authenticationExists(userName) == true) - { - personService.deletePerson(userName); - authenticationService.deleteAuthentication(userName); - } - } - private void deleteGroup(String shortName) { String groupName = authorityService.getName(AuthorityType.GROUP, shortName); diff --git a/source/java/org/alfresco/wcm/webproject/script/ScriptWebProjectsTest.java b/source/java/org/alfresco/wcm/webproject/script/ScriptWebProjectsTest.java index 3361364b69..ef75485dad 100644 --- a/source/java/org/alfresco/wcm/webproject/script/ScriptWebProjectsTest.java +++ b/source/java/org/alfresco/wcm/webproject/script/ScriptWebProjectsTest.java @@ -2,70 +2,27 @@ package org.alfresco.wcm.webproject.script; import java.util.HashMap; -import org.alfresco.model.ContentModel; import org.alfresco.repo.jscript.ClasspathScriptLocation; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.repository.ScriptLocation; import org.alfresco.service.cmr.repository.ScriptService; -import org.alfresco.service.cmr.security.AuthenticationService; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.util.BaseAlfrescoSpringTest; -import org.alfresco.util.PropertyMap; +import org.alfresco.wcm.AbstractWCMServiceImplTest; -public class ScriptWebProjectsTest extends BaseAlfrescoSpringTest { - - - private static final String USER_ONE = "WebProjectTestOne"; - private static final String USER_TWO = "WebProjectTestTwo"; - private static final String USER_THREE = "WebProjectTestThree"; - - private static final String URL_WEB_PROJECTS = "/api/wcm/webprojects"; - private AuthenticationService authenticationService; - private PersonService personService; +public class ScriptWebProjectsTest extends AbstractWCMServiceImplTest +{ private ScriptService scriptService; - private AuthenticationComponent authenticationComponent; - private void createUser(String userName) + protected void setUp() throws Exception { - if (this.authenticationService.authenticationExists(userName) == false) - { - this.authenticationService.createAuthentication(userName, "PWD".toCharArray()); - - 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"); - - this.personService.createPerson(ppOne); - } - } - - protected void onSetUpInTransaction() throws Exception - { - super.onSetUpInTransaction(); - this.scriptService = (ScriptService)this.applicationContext.getBean("ScriptService"); + super.setUp(); - this.authenticationService = (AuthenticationService)this.applicationContext.getBean("AuthenticationService"); - this.authenticationComponent = (AuthenticationComponent)this.applicationContext.getBean("authenticationComponent"); - this.personService = (PersonService)this.applicationContext.getBean("PersonService"); + this.scriptService = (ScriptService)ctx.getBean("ScriptService"); - // Create users - createUser(USER_ONE); - createUser(USER_TWO); - createUser(USER_THREE); - - // Do tests as user one - this.authenticationComponent.setCurrentUser(USER_ONE); - + AuthenticationUtil.setFullyAuthenticatedUser("admin"); } public void testJSAPI() throws Exception { - this.authenticationComponent.setCurrentUser("admin"); - ScriptLocation location = new ClasspathScriptLocation("org/alfresco/wcm/webproject/script/test_WebProjectService.js"); this.scriptService.executeScript(location, new HashMap(0)); } diff --git a/source/test-resources/wcm/jbpm.cfg.xml b/source/test-resources/wcm/jbpm.cfg.xml new file mode 100644 index 0000000000..024d36fd04 --- /dev/null +++ b/source/test-resources/wcm/jbpm.cfg.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/test-resources/wcm/wcm-jbpm-context.xml b/source/test-resources/wcm/wcm-jbpm-context.xml new file mode 100644 index 0000000000..2dc68c962c --- /dev/null +++ b/source/test-resources/wcm/wcm-jbpm-context.xml @@ -0,0 +1,11 @@ + + + + + + + + + + +