diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index c3ae5806a9..b4c9b25500 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -56,18 +56,23 @@ + + + + + + true + + + - + classpath:alfresco/version.properties - - true - @@ -90,10 +95,7 @@ - - true - + parent="common-placeholder-configurer" class="org.alfresco.config.JndiPropertyPlaceholderConfigurer"> classpath:alfresco/alfresco-shared.properties @@ -338,6 +340,9 @@ ${db.pool.statements.max} + + ${db.pool.abandoned.log} + @@ -1162,12 +1167,6 @@ - - - - - - diff --git a/config/alfresco/extension/mimetype-map-extension-context.xml.sample b/config/alfresco/extension/mimetype-map-extension-context.xml.sample index 253534f262..39d87c8488 100644 --- a/config/alfresco/extension/mimetype-map-extension-context.xml.sample +++ b/config/alfresco/extension/mimetype-map-extension-context.xml.sample @@ -3,9 +3,9 @@ - + - + classpath:alfresco/mimetype/mimetype-map.xml diff --git a/config/alfresco/extension/mimetypes-extension-context.xml.sample b/config/alfresco/extension/mimetypes-extension.xml.sample similarity index 100% rename from config/alfresco/extension/mimetypes-extension-context.xml.sample rename to config/alfresco/extension/mimetypes-extension.xml.sample diff --git a/config/alfresco/hibernate-context.xml b/config/alfresco/hibernate-context.xml index daa0a24c73..1ab65634dc 100644 --- a/config/alfresco/hibernate-context.xml +++ b/config/alfresco/hibernate-context.xml @@ -21,10 +21,7 @@ - - - true - + classpath:alfresco/domain/cache-strategies.properties diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml index 39a9fe15a7..5991e1141d 100644 --- a/config/alfresco/public-services-security-context.xml +++ b/config/alfresco/public-services-security-context.xml @@ -712,6 +712,7 @@ org.alfresco.service.cmr.security.AuthorityService.hasAdminAuthority=ACL_ALLOW + org.alfresco.service.cmr.security.AuthorityService.hasGuestAuthority=ACL_ALLOW org.alfresco.service.cmr.security.AuthorityService.isAdminAuthority=ACL_ALLOW org.alfresco.service.cmr.security.AuthorityService.isGuestAuthority=ACL_ALLOW org.alfresco.service.cmr.security.AuthorityService.getAuthorities=ACL_ALLOW diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 9b1f071c36..7f62e3c732 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -269,8 +269,15 @@ db.pool.evict.idle.min=1800000 db.pool.validate.borrow=true db.pool.validate.return=false db.pool.evict.validate=false +# db.pool.abandoned.detect=false db.pool.abandoned.time=300 +# +# db.pool.abandoned.log=true (logAbandoned) adds overhead (http://commons.apache.org/dbcp/configuration.html) +# and also requires db.pool.abandoned.detect=true (removeAbandoned) +# +db.pool.abandoned.log=false + # Audit configuration audit.enabled=false diff --git a/config/alfresco/script-services-context.xml b/config/alfresco/script-services-context.xml index dad6620db4..74a95199d7 100644 --- a/config/alfresco/script-services-context.xml +++ b/config/alfresco/script-services-context.xml @@ -10,6 +10,9 @@ + + + diff --git a/config/alfresco/subsystems/Authentication/common-ldap-context.xml b/config/alfresco/subsystems/Authentication/common-ldap-context.xml index 6cffa575ee..bbd9118ae1 100644 --- a/config/alfresco/subsystems/Authentication/common-ldap-context.xml +++ b/config/alfresco/subsystems/Authentication/common-ldap-context.xml @@ -110,6 +110,11 @@ ${ldap.authentication.java.naming.security.authentication} + + + + follow + @@ -154,6 +159,11 @@ true + + + + follow + diff --git a/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java b/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java index 3e99340bc7..2ee6307686 100644 --- a/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java +++ b/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java @@ -1479,7 +1479,10 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement { // Check for the machine account name - if ( userName.endsWith( "$") && userName.equals( userName.toUpperCase())) + // ALF-4395: Sometimes machine account name comes lowercase + // and new Alfresco user is being created with machine name + // if ( userName.endsWith( "$") && userName.equals( userName.toUpperCase())) + if ( userName.endsWith( "$")) { // Null logon @@ -1510,7 +1513,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Store the full user name in the client information, indicate that this is not a guest logon - client.setUserName( krbDetails.getSourceName()); + // ALF-4599: CIFS access to alfresco creates wrong users with Realm suffix + // client.setUserName( krbDetails.getSourceName()); + client.setUserName( krbDetails.getUserName()); client.setGuest( false); // Indicate that the session is logged on diff --git a/source/java/org/alfresco/filesys/config/ServerConfigurationBean.java b/source/java/org/alfresco/filesys/config/ServerConfigurationBean.java index 9064902c74..01ddc49307 100644 --- a/source/java/org/alfresco/filesys/config/ServerConfigurationBean.java +++ b/source/java/org/alfresco/filesys/config/ServerConfigurationBean.java @@ -1696,6 +1696,11 @@ public class ServerConfigurationBean extends AbstractServerConfigurationBean fsysConfig.addFileStateCache( filesystem.getDeviceName(), filesysContext.getStateCache()); } + // Check if change notifications should be enabled + + if ( filesysContext.getDisableChangeNotifications() == false) + filesysContext.enableChangeHandler( true); + // Start the filesystem filesysContext.startFilesystem(filesys); diff --git a/source/java/org/alfresco/filesys/repo/ContentContext.java b/source/java/org/alfresco/filesys/repo/ContentContext.java index 4cf2c825a2..a9337cf4b5 100644 --- a/source/java/org/alfresco/filesys/repo/ContentContext.java +++ b/source/java/org/alfresco/filesys/repo/ContentContext.java @@ -57,6 +57,10 @@ public class ContentContext extends AlfrescoContext private boolean m_disableNodeMonitor; + // Disable change notifications for CIFS + + private boolean m_disableChangeNotifications; + private AccessControlListBean m_accessControlList; // Enable/disable oplocks @@ -130,6 +134,15 @@ public class ContentContext extends AlfrescoContext m_disableNodeMonitor = disableNodeMonitor; } + /** + * Disable change notifications + * + * @param disableChangeNotify boolean + */ + public void setDisableChangeNotifications( boolean disableChangeNotify) { + m_disableChangeNotifications = disableChangeNotify; + } + public void setAccessControlList(AccessControlListBean accessControlList) { m_accessControlList = accessControlList; @@ -237,6 +250,15 @@ public class ContentContext extends AlfrescoContext return m_oplocksDisabled; } + /** + * Determine if change notifications are disabled + * + * @return boolean + */ + public boolean getDisableChangeNotifications() { + return m_disableChangeNotifications; + } + /** * Gets the access control list. * diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java index 5971b5e909..aacad50f0c 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java @@ -3035,7 +3035,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Copy content data from the old file to the new file - copyContentData(sess, tree, nodeToMoveRef, targetNodeRef); + copyContentData(sess, tree, nodeToMoveRef, targetNodeRef, newName); final NodeRef finalTargetNodeRef = targetNodeRef; postTxn.add(new Runnable() @@ -3724,10 +3724,13 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa * @param tree TreeConnection * @param fromNode NodeRef * @param toNode NodeRef + * @param newName String */ - private void copyContentData( SrvSession sess, TreeConnection tree, NodeRef fromNode, NodeRef toNode) + private void copyContentData( SrvSession sess, TreeConnection tree, NodeRef fromNode, NodeRef toNode, String newName) { ContentData content = (ContentData) nodeService.getProperty(fromNode, ContentModel.PROP_CONTENT); + if ( newName != null) + content = ContentData.setMimetype( content, mimetypeService.guessMimetype( newName)); nodeService.setProperty(toNode, ContentModel.PROP_CONTENT, content); } diff --git a/source/java/org/alfresco/filesys/repo/ContentNetworkFile.java b/source/java/org/alfresco/filesys/repo/ContentNetworkFile.java index a92db7ccd9..8ca61be71c 100644 --- a/source/java/org/alfresco/filesys/repo/ContentNetworkFile.java +++ b/source/java/org/alfresco/filesys/repo/ContentNetworkFile.java @@ -382,6 +382,10 @@ public class ContentNetworkFile extends NodeRefNetworkFile catch (IOException ex) { logger.error( ex); } + + // Indicate that the file is open + + setClosed( false); } } @@ -399,12 +403,14 @@ public class ContentNetworkFile extends NodeRefNetworkFile { // Nothing to do + setClosed( true); return; } else if (!hasContent()) { // File was not read/written so channel was not opened + setClosed( true); return; } @@ -479,6 +485,8 @@ public class ContentNetworkFile extends NodeRefNetworkFile { content = null; preUpdateContentURL = null; + + setClosed( true); } }); } diff --git a/source/java/org/alfresco/filesys/repo/NodeMonitor.java b/source/java/org/alfresco/filesys/repo/NodeMonitor.java index e3329217c9..26fa97f79c 100644 --- a/source/java/org/alfresco/filesys/repo/NodeMonitor.java +++ b/source/java/org/alfresco/filesys/repo/NodeMonitor.java @@ -658,18 +658,18 @@ public class NodeMonitor extends TransactionListenerAdapter // If change notifications are enabled then send an event to registered listeners - if ( m_changeHandler != null) { + if ( m_filesysCtx.hasChangeHandler()) { // Check if there are any active notifications - if ( m_changeHandler.getGlobalNotifyMask() != 0) { + if ( m_filesysCtx.getChangeHandler().getGlobalNotifyMask() != 0) { // Send a file created event to the change notification handler if ( createEvent.getFileType() == FileFolderServiceType.FILE) - m_changeHandler.notifyFileChanged(NotifyChange.ActionAdded, relPath); + m_filesysCtx.getChangeHandler().notifyFileChanged(NotifyChange.ActionAdded, relPath); else - m_changeHandler.notifyDirectoryChanged(NotifyChange.ActionAdded, relPath); + m_filesysCtx.getChangeHandler().notifyDirectoryChanged(NotifyChange.ActionAdded, relPath); // DEBUG @@ -738,18 +738,18 @@ public class NodeMonitor extends TransactionListenerAdapter // If change notifications are enabled then send an event to registered listeners - if ( m_changeHandler != null) { + if ( m_filesysCtx.hasChangeHandler()) { // Check if there are any active notifications - if ( m_changeHandler.getGlobalNotifyMask() != 0) { + if ( m_filesysCtx.getChangeHandler().getGlobalNotifyMask() != 0) { // Send a file deleted event to the change notification handler if ( deleteEvent.getFileType() == FileFolderServiceType.FILE) - m_changeHandler.notifyFileChanged(NotifyChange.ActionRemoved, relPath); + m_filesysCtx.getChangeHandler().notifyFileChanged(NotifyChange.ActionRemoved, relPath); else - m_changeHandler.notifyDirectoryChanged(NotifyChange.ActionRemoved, relPath); + m_filesysCtx.getChangeHandler().notifyDirectoryChanged(NotifyChange.ActionRemoved, relPath); // DEBUG @@ -830,15 +830,15 @@ public class NodeMonitor extends TransactionListenerAdapter // If change notifications are enabled then send an event to registered listeners - if ( m_changeHandler != null) { + if ( m_filesysCtx.hasChangeHandler()) { // Check if there are any active notifications - if ( m_changeHandler.getGlobalNotifyMask() != 0) { + if ( m_filesysCtx.getChangeHandler().getGlobalNotifyMask() != 0) { // Send a file renamed event to the change notification handler - m_changeHandler.notifyRename( fromPath, toPath); + m_filesysCtx.getChangeHandler().notifyRename( fromPath, toPath); // DEBUG @@ -881,11 +881,11 @@ public class NodeMonitor extends TransactionListenerAdapter // Node has been locked or unlocked, send a change notification to indicate the file attributes have changed - if ( m_changeHandler != null) { + if ( m_filesysCtx.hasChangeHandler()) { // Send out a change of attributes notification - m_changeHandler.notifyAttributesChanged( relPath, lockEvent.getFileType() == FileFolderServiceType.FILE ? false : true); + m_filesysCtx.getChangeHandler().notifyAttributesChanged( relPath, lockEvent.getFileType() == FileFolderServiceType.FILE ? false : true); // DEBUG diff --git a/source/java/org/alfresco/repo/action/constraint/FolderContentsParameterConstraint.java b/source/java/org/alfresco/repo/action/constraint/FolderContentsParameterConstraint.java index 4b5ba9b034..41a711d48c 100644 --- a/source/java/org/alfresco/repo/action/constraint/FolderContentsParameterConstraint.java +++ b/source/java/org/alfresco/repo/action/constraint/FolderContentsParameterConstraint.java @@ -103,8 +103,15 @@ public class FolderContentsParameterConstraint extends BaseParameterConstraint QName className = nodeService.getType(nodeRef); if (dictionaryService.isSubClass(className, ContentModel.TYPE_CONTENT) == true) { - result.put(nodeRef.toString(), - (String)nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE)); + String title = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); + if (title != null) + { + result.put(nodeRef.toString(), title); + } + else + { + result.put(nodeRef.toString(), (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME)); + } } else if (dictionaryService.isSubClass(className, ContentModel.TYPE_FOLDER) == true) { diff --git a/source/java/org/alfresco/repo/avm/wf/AVMRemoveAllSrcWebappsHandler.java b/source/java/org/alfresco/repo/avm/wf/AVMRemoveAllSrcWebappsHandler.java index 3c34793856..4f1266ec3f 100644 --- a/source/java/org/alfresco/repo/avm/wf/AVMRemoveAllSrcWebappsHandler.java +++ b/source/java/org/alfresco/repo/avm/wf/AVMRemoveAllSrcWebappsHandler.java @@ -1,5 +1,5 @@ -/*----------------------------------------------------------------------------- -* Copyright 2007-2010 Alfresco Software Limited. +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. * * This file is part of Alfresco * @@ -15,26 +15,19 @@ * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . -* -* -* Author Jon Cox -* File AVMRemoveAllSrcWebappsHandler.java -*----------------------------------------------------------------------------*/ + */ package org.alfresco.repo.avm.wf; -import java.util.Map; import org.alfresco.config.JNDIConstants; import org.alfresco.mbeans.VirtServerRegistry; import org.alfresco.repo.avm.AVMNodeConverter; import org.alfresco.repo.avm.util.RawServices; -import org.alfresco.repo.domain.PropertyValue; import org.alfresco.repo.workflow.jbpm.JBPMNode; import org.alfresco.repo.workflow.jbpm.JBPMSpringActionHandler; -import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; +import org.alfresco.wcm.util.WCMUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jbpm.graph.exe.ExecutionContext; @@ -46,18 +39,11 @@ import org.springframework.context.ApplicationContext; * * @author Jon Cox */ -public class AVMRemoveAllSrcWebappsHandler extends JBPMSpringActionHandler +public class AVMRemoveAllSrcWebappsHandler extends JBPMSpringActionHandler { static final long serialVersionUID = 3004374776252613278L; - - private static Log log = - LogFactory.getLog(AVMRemoveAllSrcWebappsHandler.class); - - /** - * The AVMService instance. - */ - private AVMService fAVMService; - + + private static Log logger = LogFactory.getLog(AVMRemoveAllSrcWebappsHandler.class); /** * Initialize service references. @@ -66,7 +52,6 @@ public class AVMRemoveAllSrcWebappsHandler extends JBPMSpringActionHandler @Override protected void initialiseHandler(BeanFactory factory) { - fAVMService = (AVMService)factory.getBean("AVMService"); } /** @@ -75,35 +60,43 @@ public class AVMRemoveAllSrcWebappsHandler extends JBPMSpringActionHandler */ public void execute(ExecutionContext executionContext) throws Exception { - if (log.isDebugEnabled()) - log.debug("AVMRemoveAllSrcWebappsHandler.execute()"); - - // retrieve submitted package - NodeRef pkg = ((JBPMNode)executionContext.getContextInstance(). - getVariable("bpm_package")).getNodeRef(); - - Pair pkgPath = AVMNodeConverter.ToAVMVersionPath(pkg); - - Integer version = pkgPath.getFirst(); - String www_dir = pkgPath.getSecond(); - String appbase_dir = www_dir + "/" + JNDIConstants.DIR_DEFAULT_APPBASE; - - if (log.isDebugEnabled()) + String workflowName = executionContext.getProcessDefinition().getName(); + + // optimization: direct submits no longer virtualize the workflow sandbox + boolean isSubmitDirectWorkflowSandbox = ((workflowName != null) && (workflowName.equals(WCMUtil.WORKFLOW_SUBMITDIRECT_NAME))); + + if (logger.isDebugEnabled()) { - log.debug("version: " + version ); - log.debug("appbase_dir: " + appbase_dir ); + logger.debug("AVMRemoveAllSrcWebappsHandler.execute: "+workflowName); + } + + if (! isSubmitDirectWorkflowSandbox) + { + // retrieve submitted package + NodeRef pkg = ((JBPMNode)executionContext.getContextInstance(). + getVariable("bpm_package")).getNodeRef(); + + Pair pkgPath = AVMNodeConverter.ToAVMVersionPath(pkg); + + Integer version = pkgPath.getFirst(); + String www_dir = pkgPath.getSecond(); + String appbase_dir = www_dir + "/" + JNDIConstants.DIR_DEFAULT_APPBASE; + + ApplicationContext springContext = RawServices.Instance().getContext(); + VirtServerRegistry vServerRegistry = (VirtServerRegistry) + springContext.getBean("VirtServerRegistry"); + + if (logger.isDebugEnabled()) + { + logger.debug("Sending JMX message to shut down workflow webapps: ["+version+", "+appbase_dir+"]"); + } + + vServerRegistry.removeAllWebapps( version, appbase_dir, true ); + + if (logger.isDebugEnabled()) + { + logger.debug("Sent JMX message to shut down workflow webapps: ["+version+", "+appbase_dir+"]"); + } } - - ApplicationContext springContext = RawServices.Instance().getContext(); - VirtServerRegistry vServerRegistry = (VirtServerRegistry) - springContext.getBean("VirtServerRegistry"); - - if (log.isDebugEnabled()) - log.debug("Sending JMX message to shut down workflow webapps"); - - vServerRegistry.removeAllWebapps( version, appbase_dir, true ); - - if (log.isDebugEnabled()) - log.debug("Sent JMX message to shut down workflow webapps"); } } diff --git a/source/java/org/alfresco/repo/avm/wf/AVMRemoveWFStoreHandler.java b/source/java/org/alfresco/repo/avm/wf/AVMRemoveWFStoreHandler.java index 5b2ecd18e2..bd413f8a88 100644 --- a/source/java/org/alfresco/repo/avm/wf/AVMRemoveWFStoreHandler.java +++ b/source/java/org/alfresco/repo/avm/wf/AVMRemoveWFStoreHandler.java @@ -24,7 +24,7 @@ import org.alfresco.repo.workflow.jbpm.JBPMNode; import org.alfresco.repo.workflow.jbpm.JBPMSpringActionHandler; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.util.Pair; -import org.alfresco.wcm.sandbox.SandboxService; +import org.alfresco.wcm.sandbox.SandboxFactory; import org.alfresco.wcm.util.WCMUtil; import org.jbpm.graph.exe.ExecutionContext; import org.springframework.beans.factory.BeanFactory; @@ -39,10 +39,7 @@ public class AVMRemoveWFStoreHandler extends JBPMSpringActionHandler { private static final long serialVersionUID = 4113360751217684995L; - /** - * The WCM SandboxService instance. - */ - private SandboxService sbService; + private SandboxFactory sandboxFactory; /** * Initialize service references. @@ -51,7 +48,7 @@ public class AVMRemoveWFStoreHandler extends JBPMSpringActionHandler @Override protected void initialiseHandler(BeanFactory factory) { - sbService = (SandboxService)factory.getBean("SandboxService"); + sandboxFactory = (SandboxFactory)factory.getBean("sandboxFactory"); } /** @@ -67,6 +64,11 @@ public class AVMRemoveWFStoreHandler extends JBPMSpringActionHandler NodeRef pkg = ((JBPMNode)executionContext.getContextInstance().getVariable("bpm_package")).getNodeRef(); Pair pkgPath = AVMNodeConverter.ToAVMVersionPath(pkg); + String workflowName = executionContext.getProcessDefinition().getName(); + + // optimization: direct submits no longer virtualize the workflow sandbox + final boolean isSubmitDirectWorkflowSandbox = ((workflowName != null) && (workflowName.equals(WCMUtil.WORKFLOW_SUBMITDIRECT_NAME))); + // Now delete the stores in the WCM workflow sandbox final String avmPath = pkgPath.getSecond(); @@ -74,11 +76,10 @@ public class AVMRemoveWFStoreHandler extends JBPMSpringActionHandler { public Object doWork() throws Exception { - sbService.deleteSandbox(WCMUtil.getSandboxStoreId(avmPath)); + sandboxFactory.deleteSandbox(WCMUtil.getSandboxStoreId(avmPath), isSubmitDirectWorkflowSandbox); return null; } }, AuthenticationUtil.getSystemUserName()); - } } diff --git a/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanerTest.java b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanerTest.java index 1b6c632f67..c381aa6b88 100644 --- a/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanerTest.java +++ b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanerTest.java @@ -32,6 +32,7 @@ import junit.framework.TestCase; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.ContentStore; import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.UnsupportedContentUrlException; import org.alfresco.repo.domain.avm.AVMNodeDAO; import org.alfresco.repo.domain.contentdata.ContentDataDAO; import org.alfresco.repo.lock.JobLockService; @@ -90,7 +91,10 @@ public class ContentStoreCleanerTest extends TestCase // we need a store store = (ContentStore) ctx.getBean("fileContentStore"); // and a listener + List listeners = new ArrayList(2); listener = new DummyCleanerListener(); + listeners.add(listener); + listeners.add(new DummyUnsupportiveCleanerListener()); // initialise record of deleted URLs deletedUrls = new ArrayList(5); @@ -98,7 +102,7 @@ public class ContentStoreCleanerTest extends TestCase eagerCleaner = (EagerContentStoreCleaner) ctx.getBean("eagerContentStoreCleaner"); eagerCleaner.setEagerOrphanCleanup(false); eagerCleaner.setStores(Collections.singletonList(store)); - eagerCleaner.setListeners(Collections.singletonList(listener)); + eagerCleaner.setListeners(listeners); cleaner = new ContentStoreCleaner(); cleaner.setEagerContentStoreCleaner(eagerCleaner); @@ -388,4 +392,14 @@ public class ContentStoreCleanerTest extends TestCase deletedUrls.add(contentUrl); } } + /** + * Cleaner listener that doesn't support the URLs passed in + */ + private class DummyUnsupportiveCleanerListener implements ContentStoreCleanerListener + { + public void beforeDelete(ContentStore store, String contentUrl) throws ContentIOException + { + throw new UnsupportedContentUrlException(store, contentUrl); + } + } } diff --git a/source/java/org/alfresco/repo/content/cleanup/DeletedContentBackupCleanerListener.java b/source/java/org/alfresco/repo/content/cleanup/DeletedContentBackupCleanerListener.java index d82af3a0f3..8a607c7514 100644 --- a/source/java/org/alfresco/repo/content/cleanup/DeletedContentBackupCleanerListener.java +++ b/source/java/org/alfresco/repo/content/cleanup/DeletedContentBackupCleanerListener.java @@ -61,17 +61,32 @@ public class DeletedContentBackupCleanerListener implements ContentStoreCleanerL // Nothing to copy over return; } - // write the content into the target store - ContentWriter writer = store.getWriter(context); - // copy across - writer.putContent(reader); - // done - if (logger.isDebugEnabled()) + if (store.isContentUrlSupported(contentUrl)) { - logger.debug("Moved content before deletion: \n" + - " URL: " + contentUrl + "\n" + - " Source: " + sourceStore + "\n" + - " Target: " + store); + // write the content into the target store + ContentWriter writer = store.getWriter(context); + // copy across + writer.putContent(reader); + // done + if (logger.isDebugEnabled()) + { + logger.debug( + "Moved content before deletion: \n" + + " URL: " + contentUrl + "\n" + + " Source: " + sourceStore + "\n" + + " Target: " + store); + } + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug( + "Content cannot be moved during deletion. A backup will not be made: \n" + + " URL: " + contentUrl + "\n" + + " Source: " + sourceStore + "\n" + + " Target: " + store); + } } } } diff --git a/source/java/org/alfresco/repo/content/cleanup/EagerContentStoreCleaner.java b/source/java/org/alfresco/repo/content/cleanup/EagerContentStoreCleaner.java index 45a93bd6cd..46a845c935 100644 --- a/source/java/org/alfresco/repo/content/cleanup/EagerContentStoreCleaner.java +++ b/source/java/org/alfresco/repo/content/cleanup/EagerContentStoreCleaner.java @@ -215,9 +215,33 @@ public class EagerContentStoreCleaner extends TransactionListenerAdapter { for (ContentStoreCleanerListener listener : listeners) { - listener.beforeDelete(store, contentUrl); + try + { + // Since we are in post-commit, we do best-effort + listener.beforeDelete(store, contentUrl); + } + catch (Throwable e) + { + logger.error( + "Content deletion listener failed: \n" + + " URL: " + contentUrl + "\n" + + " Source: " + store, + e); + } + } + try + { + // Since we are in post-commit, we do best-effort + store.delete(contentUrl); + } + catch (Throwable e) + { + logger.error( + "Content deletion failed: \n" + + " URL: " + contentUrl + "\n" + + " Source: " + store, + e); } - store.delete(contentUrl); } } } diff --git a/source/java/org/alfresco/repo/copy/AbstractCopyBehaviourCallback.java b/source/java/org/alfresco/repo/copy/AbstractCopyBehaviourCallback.java index f9971506da..4698f9eb4b 100644 --- a/source/java/org/alfresco/repo/copy/AbstractCopyBehaviourCallback.java +++ b/source/java/org/alfresco/repo/copy/AbstractCopyBehaviourCallback.java @@ -24,10 +24,13 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import org.alfresco.repo.copy.CopyBehaviourCallback.AssocCopySourceAction; +import org.alfresco.repo.copy.CopyBehaviourCallback.AssocCopyTargetAction; import org.alfresco.repo.transaction.TransactionalResourceHelper; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; /** * Abstract implementation to allow for easier migration if the interface changes. @@ -39,6 +42,22 @@ public abstract class AbstractCopyBehaviourCallback implements CopyBehaviourCall { private static final String KEY_NODEREF_REPOINTING_PREFIX = "recordNodeRefPropertiesForRepointing-"; + /** + * @return Returns + * {@link AssocCopySourceAction#COPY_REMOVE_EXISTING} and + * {@link AssocCopyTargetAction#USE_COPIED_TARGET} + */ + @Override + public Pair getAssociationCopyAction( + QName classQName, + CopyDetails copyDetails, + CopyAssociationDetails assocCopyDetails) + { + return new Pair( + AssocCopySourceAction.COPY_REMOVE_EXISTING, + AssocCopyTargetAction.USE_COPIED_TARGET); + } + /** * @return Returns {@link ChildAssocRecurseAction#RESPECT_RECURSE_FLAG} */ @@ -85,7 +104,7 @@ public abstract class AbstractCopyBehaviourCallback implements CopyBehaviourCall { Serializable parameterValue = properties.get(propertyQName); if (parameterValue != null && - (parameterValue instanceof Collection || parameterValue instanceof NodeRef)) + (parameterValue instanceof Collection || parameterValue instanceof NodeRef)) { String key = KEY_NODEREF_REPOINTING_PREFIX + propertyQName.toString(); // Store it for later diff --git a/source/java/org/alfresco/repo/copy/CompoundCopyBehaviourCallback.java b/source/java/org/alfresco/repo/copy/CompoundCopyBehaviourCallback.java index cee7f38045..d5538ee90e 100644 --- a/source/java/org/alfresco/repo/copy/CompoundCopyBehaviourCallback.java +++ b/source/java/org/alfresco/repo/copy/CompoundCopyBehaviourCallback.java @@ -26,6 +26,7 @@ import java.util.Map; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -83,6 +84,45 @@ public class CompoundCopyBehaviourCallback extends AbstractCopyBehaviourCallback return sb.toString(); } + @Override + public Pair getAssociationCopyAction( + QName classQName, + CopyDetails copyDetails, + CopyAssociationDetails assocCopyDetails) + { + AssocCopySourceAction bestSourceAction = AssocCopySourceAction.COPY; + AssocCopyTargetAction bestTargetAction = AssocCopyTargetAction.USE_ORIGINAL_TARGET; + for (CopyBehaviourCallback callback : callbacks) + { + Pair action = callback.getAssociationCopyAction( + classQName, + copyDetails, + assocCopyDetails); + if (action.getFirst().compareTo(bestSourceAction) > 0) + { + // We've trumped the last best one + bestSourceAction = action.getFirst(); + } + if (action.getSecond().compareTo(bestTargetAction) > 0) + { + // We've trumped the last best one + bestTargetAction = action.getSecond(); + } + } + Pair bestAction = + new Pair(bestSourceAction, bestTargetAction); + // Done + if (logger.isDebugEnabled()) + { + logger.debug( + "Association copy behaviour: " + bestAction + "\n" + + " " + assocCopyDetails + "\n" + + " " + copyDetails + "\n" + + " " + this); + } + return bestAction; + } + /** * Individual callbacks effectively have a veto on the copy i.e. if one of the * callbacks returns false for {@link CopyBehaviourCallback#mustCopy(NodeRef)}, diff --git a/source/java/org/alfresco/repo/copy/CopyBehaviourCallback.java b/source/java/org/alfresco/repo/copy/CopyBehaviourCallback.java index 2671a8b2cf..29132139a3 100644 --- a/source/java/org/alfresco/repo/copy/CopyBehaviourCallback.java +++ b/source/java/org/alfresco/repo/copy/CopyBehaviourCallback.java @@ -21,9 +21,11 @@ package org.alfresco.repo.copy; import java.io.Serializable; import java.util.Map; +import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; /** * A callback to modify copy behaviour associated with a given type or aspect. This @@ -34,6 +36,70 @@ import org.alfresco.service.namespace.QName; */ public interface CopyBehaviourCallback { + /** + * Description of how the copy process should handle multiplicity of peer associations + * at the source end of the association.
+ * The order of this enum denotes the priority when mixing behaviour as well; + * that is to say that a 'ignore' behaviour will occur even if an 'copy' is + * also provided by the registered behaviour callbacks. + * + * @author Derek Hulley + * @since 3.3SP3 + */ + public enum AssocCopySourceAction implements Comparable + { + /** + * Always copy the association. + *
+ * Note that this can cause duplicate associations when copying over + * {@link CopyAssociationDetails#isTargetNodeIsNew() existing target nodes}. + */ + COPY, + /** + * Always copy the association but remove the copy-target's matching associations + * when copying over an existing node. + *
+ * This is akin to the original CopyService behaviour + * (see ALF-958). + */ + COPY_REMOVE_EXISTING, + /** + * Ignore the association + */ + IGNORE, + } + + /** + * Description of how the copy process should handle multiplicity of peer associations + * at the target end of the association.
+ * The order of this enum denotes the priority when mixing behaviour as well; + * that is to say that a 'ignore' behaviour will occur even if an 'copy' is + * also provided by the registered behaviour callbacks. + * + * @author Derek Hulley + * @since 3.3SP3 + */ + public enum AssocCopyTargetAction implements Comparable + { + /** + * The copied association will use, as target, the original association's target + * i.e. the target of the association will remain as it was. + */ + USE_ORIGINAL_TARGET, + /** + * The copied association will use, as target, the node copied from the + * original target; if the original association's target is not copied + * in the process, then nothing is done. + */ + USE_COPIED_TARGET, + /** + * The copied association will use, as target, the node copied from the + * original target; if the original association's target is not copied + * in the original target is used. + */ + USE_COPIED_OTHERWISE_ORIGINAL_TARGET, + } + /** * Description of how the copy process should traverse a child association. * The order of this enum denotes the priority when mixing behaviour as well; @@ -48,21 +114,15 @@ public interface CopyBehaviourCallback /** * Ignore the child association */ - IGNORE - { - }, + IGNORE, /** * Copy the association only, keeping the existing child node */ - COPY_ASSOC - { - }, + COPY_ASSOC, /** * Traverse the child association and copy the child node */ - COPY_CHILD - { - }; + COPY_CHILD, } /** @@ -76,9 +136,7 @@ public interface CopyBehaviourCallback /** * Respect the client's recursion decision */ - RESPECT_RECURSE_FLAG - { - }, + RESPECT_RECURSE_FLAG, /** * Force all further copies of the source hierarchy to recurse into children. * This allows behaviour to force a copy of a subtree that it expects to @@ -88,17 +146,81 @@ public interface CopyBehaviourCallback * so this is mainly useful where the subtree contains the default * behaviour. */ - FORCE_RECURSE + FORCE_RECURSE, + } + + /** + * A simple bean class to convey information to the callback methods dealing with + * copying of associations. + * + * @see CopyBehaviourCallback#getAssociationCopyAction(QName, CopyDetails, CopyAssociationDetails) + * + * @author Derek Hulley + * @since 3.3SP3 + */ + public static final class CopyAssociationDetails + { + private final AssociationRef assocRef; + private final NodeRef copyTarget; + private final boolean copyTargetIsNew; + + public CopyAssociationDetails( + AssociationRef assocRef, + NodeRef copyTarget, + boolean copyTargetIsNew) { - }, + this.assocRef = assocRef; + this.copyTarget = copyTarget; + this.copyTargetIsNew = copyTargetIsNew; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(256); + sb.append("CopyChildAssociationDetails ") + .append("[ assocRef=").append(assocRef) + .append(", copyTarget=").append(copyTarget) + .append(", copyTargetIsNew=").append(copyTargetIsNew) + .append("]"); + return sb.toString(); + } + + /** + * @return Returns the association being examined + */ + public final AssociationRef getAssocRef() + { + return assocRef; + } + + /** + * @return Returns the node that will be the + * new source if the association is copied + */ + public final NodeRef getCopyTarget() + { + return copyTarget; + } + + /** + * + * @return Returns true if the {@link #getCopyTarget() copy target node} + * has been newly created by the copy process or false if it + * is a node that existed prior to the copy + */ + public final boolean getCopyTargetIsNew() + { + return copyTargetIsNew; + } } /** * A simple bean class to convey information to the callback methods dealing with * copying of child associations. * - * @see CopyBehaviourCallback#getChildAssociationCopyAction(QName, CopyDetails, ChildAssociationRef, NodeRef, boolean) - * @see CopyBehaviourCallback#getChildAssociationRecurseAction(QName, CopyDetails, ChildAssociationRef, boolean) + * @see CopyBehaviourCallback#getChildAssociationCopyAction(QName, CopyDetails, CopyChildAssociationDetails) + * @see CopyBehaviourCallback#getChildAssociationRecurseAction(QName, CopyDetails, CopyChildAssociationDetails) * * @author Derek Hulley * @since 3.2 @@ -106,19 +228,19 @@ public interface CopyBehaviourCallback public static final class CopyChildAssociationDetails { private final ChildAssociationRef childAssocRef; - private final NodeRef targetNodeRef; - private final boolean targetNodeIsNew; + private final NodeRef copyTarget; + private final boolean copyTargetIsNew; private final boolean copyChildren; public CopyChildAssociationDetails( ChildAssociationRef childAssocRef, - NodeRef targetNodeRef, - boolean targetNodeIsNew, + NodeRef copyTarget, + boolean copyTargetIsNew, boolean copyChildren) { this.childAssocRef = childAssocRef; - this.targetNodeRef = targetNodeRef; - this.targetNodeIsNew = targetNodeIsNew; + this.copyTarget = copyTarget; + this.copyTargetIsNew = copyTargetIsNew; this.copyChildren = copyChildren; } @@ -128,8 +250,8 @@ public interface CopyBehaviourCallback StringBuilder sb = new StringBuilder(256); sb.append("CopyChildAssociationDetails ") .append("[ childAssocRef=").append(childAssocRef) - .append(", targetNodeRef=").append(targetNodeRef) - .append(", targetNodeIsNew=").append(targetNodeIsNew) + .append(", copyTarget=").append(copyTarget) + .append(", copyTargetIsNew=").append(copyTargetIsNew) .append(", copyChildren=").append(copyChildren) .append("]"); return sb.toString(); @@ -144,23 +266,23 @@ public interface CopyBehaviourCallback } /** - * @return Returns the target node that will be the + * @return Returns the node that will be the * new parent if the association is copied */ - public final NodeRef getTargetNodeRef() + public final NodeRef getCopyTarget() { - return targetNodeRef; + return copyTarget; } /** * - * @return Returns true if the {@link #getTargetNodeRef() target node} + * @return Returns true if the {@link #getCopyTarget() target node} * has been newly created by the copy process or false if it * is a node that existed prior to the copy */ - public final boolean isTargetNodeIsNew() + public final boolean getCopyTargetIsNew() { - return targetNodeIsNew; + return copyTargetIsNew; } /** @@ -187,6 +309,21 @@ public interface CopyBehaviourCallback */ boolean getMustCopy(QName classQName, CopyDetails copyDetails); + /** + * Determine the copy behaviour associated with a given peer association. + * + * @param classQName the name of the class that this is being invoked for + * @param copyDetails the source node's copy details for quick reference + * @param assocCopyDetails all other details relating to the association + * @return Returns the copy actions + * ({@link AssocCopySourceAction source} and {@link AssocCopySourceAction target}) + * to take with the given association + */ + Pair getAssociationCopyAction( + QName classQName, + CopyDetails copyDetails, + CopyAssociationDetails assocCopyDetails); + /** * Determine if a copy should copy the child, the association only or do nothing with * the given association. diff --git a/source/java/org/alfresco/repo/copy/CopyServiceImpl.java b/source/java/org/alfresco/repo/copy/CopyServiceImpl.java index 48fdeaa56d..0ce6b7fd51 100644 --- a/source/java/org/alfresco/repo/copy/CopyServiceImpl.java +++ b/source/java/org/alfresco/repo/copy/CopyServiceImpl.java @@ -31,12 +31,16 @@ import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.action.ActionServiceImpl; +import org.alfresco.repo.copy.CopyBehaviourCallback.AssocCopySourceAction; +import org.alfresco.repo.copy.CopyBehaviourCallback.AssocCopyTargetAction; import org.alfresco.repo.copy.CopyBehaviourCallback.ChildAssocCopyAction; import org.alfresco.repo.copy.CopyBehaviourCallback.ChildAssocRecurseAction; +import org.alfresco.repo.copy.CopyBehaviourCallback.CopyAssociationDetails; import org.alfresco.repo.copy.CopyBehaviourCallback.CopyChildAssociationDetails; import org.alfresco.repo.policy.ClassPolicyDelegate; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.transaction.TransactionalResourceHelper; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; @@ -44,6 +48,7 @@ import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.CopyService; import org.alfresco.service.cmr.repository.CopyServiceException; @@ -52,21 +57,21 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.rule.RuleService; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.security.AuthenticationService; -import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.GUID; +import org.alfresco.util.Pair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.extensions.surf.util.I18NUtil; import org.springframework.extensions.surf.util.ParameterCheck; /** - * Node operations service implmentation. + * Service implementation of copy operations. * * @author Roy Wetherall + * @author Derek Hulley */ public class CopyServiceImpl implements CopyService { @@ -84,21 +89,12 @@ public class CopyServiceImpl implements CopyService /** The dictionary service*/ private DictionaryService dictionaryService; - /** The search service */ private SearchService searchService; - /** Policy component */ private PolicyComponent policyComponent; - /** Rule service */ private RuleService ruleService; - - /** Permission service */ - private PermissionService permissionService; - - /** Authentication service */ - private AuthenticationService authenticationService; /** Policy delegates */ private ClassPolicyDelegate onCopyNodeDelegate; @@ -165,26 +161,6 @@ public class CopyServiceImpl implements CopyService this.ruleService = ruleService; } - /** - * Set the permission service - * - * @param permissionService the permission service - */ - public void setPermissionService(PermissionService permissionService) - { - this.permissionService = permissionService; - } - - /** - * Sets the authentication service - * - * @param authenticationService the authentication service - */ - public void setAuthenticationService(AuthenticationService authenticationService) - { - this.authenticationService = authenticationService; - } - /** * Initialise method */ @@ -231,6 +207,9 @@ public class CopyServiceImpl implements CopyService throw new UnsupportedOperationException("Copying nodes across stores is not currently supported."); } + // Clear out any record of copied associations + TransactionalResourceHelper.getList(KEY_POST_COPY_ASSOCS).clear(); + // Keep track of copied children Map copiesByOriginals = new HashMap(17); Set copies = new HashSet(17); @@ -250,6 +229,9 @@ public class CopyServiceImpl implements CopyService " " + copyDetails); } + // Copy an associations that were left until now + copyPendingAssociations(copiesByOriginals); + // Foreach of the newly created copies call the copy complete policy for (Map.Entry entry : copiesByOriginals.entrySet()) { @@ -348,6 +330,9 @@ public class CopyServiceImpl implements CopyService // invoke the before copy policy invokeBeforeCopy(sourceNodeRef, targetNodeRef); + // Clear out any record of copied associations + TransactionalResourceHelper.getList(KEY_POST_COPY_ASSOCS).clear(); + // Copy copyProperties(copyDetails, targetNodeRef, sourceNodeTypeQName, callbacks); copyAspects(copyDetails, targetNodeRef, Collections.emptySet(), callbacks); @@ -356,6 +341,7 @@ public class CopyServiceImpl implements CopyService // invoke the copy complete policy Map copiedNodeRefs = new HashMap(1); copiedNodeRefs.put(sourceNodeRef, targetNodeRef); + copyPendingAssociations(copiedNodeRefs); // Copy an associations that were left until now invokeCopyComplete(sourceNodeRef, targetNodeRef, false, copiedNodeRefs); } @@ -522,13 +508,13 @@ public class CopyServiceImpl implements CopyService assocQName, sourceNodeTypeQName, targetNodeProperties); - NodeRef targetNodeRef = targetChildAssocRef.getChildRef(); + NodeRef copyTarget = targetChildAssocRef.getChildRef(); // Save the mapping for later - copiesByOriginal.put(sourceNodeRef, targetNodeRef); - copies.add(targetNodeRef); + copiesByOriginal.put(sourceNodeRef, copyTarget); + copies.add(copyTarget); // We now have a node, so fire the BeforeCopyPolicy - invokeBeforeCopy(sourceNodeRef, targetNodeRef); + invokeBeforeCopy(sourceNodeRef, copyTarget); // Work out which aspects still need copying. The source aspects less the default aspects // will give this set. @@ -536,29 +522,29 @@ public class CopyServiceImpl implements CopyService remainingAspectQNames.removeAll(defaultAspectQNames); // Prevent any rules being fired on the new destination node - this.ruleService.disableRules(targetNodeRef); + this.ruleService.disableRules(copyTarget); try { // Apply the remaining aspects and properties for (QName remainingAspectQName : remainingAspectQNames) { - copyProperties(copyDetails, targetNodeRef, remainingAspectQName, callbacks); + copyProperties(copyDetails, copyTarget, remainingAspectQName, callbacks); } // Copy residual properties - copyResidualProperties(copyDetails, targetNodeRef); + copyResidualProperties(copyDetails, copyTarget); // Apply the copy aspect to the new node Map copyProperties = new HashMap(); copyProperties.put(ContentModel.PROP_COPY_REFERENCE, sourceNodeRef); - internalNodeService.addAspect(targetNodeRef, ContentModel.ASPECT_COPIEDFROM, copyProperties); + internalNodeService.addAspect(copyTarget, ContentModel.ASPECT_COPIEDFROM, copyProperties); // Do not copy permissions // We present the recursion option regardless of what the client chooses copyChildren( copyDetails, - targetNodeRef, + copyTarget, true, // We know that the node has been created copyChildren, copiesByOriginal, @@ -567,10 +553,10 @@ public class CopyServiceImpl implements CopyService } finally { - this.ruleService.enableRules(targetNodeRef); + this.ruleService.enableRules(copyTarget); } - return targetNodeRef; + return copyTarget; } private Set getDefaultAspects(QName sourceNodeTypeQName) @@ -715,6 +701,57 @@ public class CopyServiceImpl implements CopyService } } + /** + * Copy any remaining associations that could not be copied or ignored during the copy process. + * See ALF-958: Target associations aren't copied. + */ + private void copyPendingAssociations(Map copiedNodeRefs) + { + // Prepare storage for post-copy association handling + List> postCopyAssocs = + TransactionalResourceHelper.getList(KEY_POST_COPY_ASSOCS); + for (Pair pair : postCopyAssocs) + { + AssociationRef assocRef = pair.getFirst(); + AssocCopyTargetAction action = pair.getSecond(); + // Was the original target copied? + NodeRef newSourceForAssoc = copiedNodeRefs.get(assocRef.getSourceRef()); + if (newSourceForAssoc == null) + { + // Developer #fail + throw new IllegalStateException("Post-copy association has a source that was NOT copied."); + } + NodeRef oldTargetForAssoc = assocRef.getTargetRef(); + NodeRef newTargetForAssoc = copiedNodeRefs.get(oldTargetForAssoc); // May be null + QName assocTypeQName = assocRef.getTypeQName(); + switch (action) + { + case USE_ORIGINAL_TARGET: + internalNodeService.createAssociation(newSourceForAssoc, oldTargetForAssoc, assocTypeQName); + break; + case USE_COPIED_TARGET: + // Do nothing if the target was not copied + if (newTargetForAssoc != null) + { + internalNodeService.createAssociation(newSourceForAssoc, newTargetForAssoc, assocTypeQName); + } + break; + case USE_COPIED_OTHERWISE_ORIGINAL_TARGET: + if (newTargetForAssoc == null) + { + internalNodeService.createAssociation(newSourceForAssoc, oldTargetForAssoc, assocTypeQName); + } + else + { + internalNodeService.createAssociation(newSourceForAssoc, newTargetForAssoc, assocTypeQName); + } + break; + default: + throw new IllegalStateException("Unknown association action: " + action); + } + } + + } /** * Gets the copy details. This calls the appropriate policies that have been registered @@ -951,8 +988,8 @@ public class CopyServiceImpl implements CopyService */ private void copyChildren( CopyDetails copyDetails, - NodeRef targetNodeRef, - boolean targetNodeIsNew, + NodeRef copyTarget, + boolean copyTargetIsNew, boolean copyChildren, Map copiesByOriginals, Set copies, @@ -964,8 +1001,8 @@ public class CopyServiceImpl implements CopyService copyChildren( copyDetails, sourceNodeTypeQName, - targetNodeRef, - targetNodeIsNew, + copyTarget, + copyTargetIsNew, copyChildren, copiesByOriginals, copies, @@ -981,8 +1018,8 @@ public class CopyServiceImpl implements CopyService copyChildren( copyDetails, aspectQName, - targetNodeRef, - targetNodeIsNew, + copyTarget, + copyTargetIsNew, copyChildren, copiesByOriginals, copies, @@ -990,14 +1027,15 @@ public class CopyServiceImpl implements CopyService } } + private static final String KEY_POST_COPY_ASSOCS = "CopyServiceImpl.postCopyAssocs"; /** * @param copyChildren false if the client selected not to recurse */ private void copyChildren( CopyDetails copyDetails, QName classQName, - NodeRef targetNodeRef, - boolean targetNodeIsNew, + NodeRef copyTarget, + boolean copyTargetIsNew, boolean copyChildren, Map copiesByOriginals, Set copies, @@ -1017,17 +1055,84 @@ public class CopyServiceImpl implements CopyService { throw new IllegalStateException("Source node class has no callback: " + classQName); } - for (Map.Entry entry : classDef.getChildAssociations().entrySet()) + + // Prepare storage for post-copy association handling + List> postCopyAssocs = + TransactionalResourceHelper.getList(KEY_POST_COPY_ASSOCS); + + // Handle peer associations. + for (Map.Entry entry : classDef.getAssociations().entrySet()) { QName assocTypeQName = entry.getKey(); - ChildAssociationDefinition assocDef = entry.getValue(); - if (!assocDef.isChild()) + AssociationDefinition assocDef = entry.getValue(); + if (assocDef.isChild()) + { + continue; // Ignore child assocs + } + boolean haveRemovedFromCopyTarget = false; + // Get the associations + List assocRefs = nodeService.getTargetAssocs(sourceNodeRef, assocTypeQName); + for (AssociationRef assocRef : assocRefs) + { + // Get the copy action for the association instance + CopyAssociationDetails assocCopyDetails = new CopyAssociationDetails( + assocRef, + copyTarget, + copyTargetIsNew); + Pair assocCopyAction = callback.getAssociationCopyAction( + classQName, + copyDetails, + assocCopyDetails); + + // Consider the source side first + switch (assocCopyAction.getFirst()) + { + case IGNORE: + continue; // Do nothing + case COPY_REMOVE_EXISTING: + if (!copyTargetIsNew && !haveRemovedFromCopyTarget) + { + // Only do this if we are copying over an existing node and we have NOT + // already cleaned up for this association type + haveRemovedFromCopyTarget = true; + for (AssociationRef assocToRemoveRef : internalNodeService.getTargetAssocs(copyTarget, assocTypeQName)) + { + internalNodeService.removeAssociation(assocToRemoveRef.getSourceRef(), assocToRemoveRef.getTargetRef(), assocTypeQName); + } + } + // Fall through to copy + case COPY: + // Record the type of target behaviour that is expected + switch (assocCopyAction.getSecond()) + { + case USE_ORIGINAL_TARGET: + case USE_COPIED_TARGET: + case USE_COPIED_OTHERWISE_ORIGINAL_TARGET: + // Have to save for later to see if the target node is copied, too + postCopyAssocs.add(new Pair(assocRef, assocCopyAction.getSecond())); + break; + default: + throw new IllegalStateException("Unknown association target copy action: " + assocCopyAction); + } + break; + default: + throw new IllegalStateException("Unknown association source copy action: " + assocCopyAction); + } + } + } + + // Handle child associations. These need special attention due to their recursive nature. + for (Map.Entry childEntry : classDef.getChildAssociations().entrySet()) + { + QName childAssocTypeQName = childEntry.getKey(); + ChildAssociationDefinition childAssocDef = childEntry.getValue(); + if (!childAssocDef.isChild()) { continue; // Ignore non-child assocs } // Get the child associations List childAssocRefs = nodeService.getChildAssocs( - sourceNodeRef, assocTypeQName, RegexQNamePattern.MATCH_ALL); + sourceNodeRef, childAssocTypeQName, RegexQNamePattern.MATCH_ALL); for (ChildAssociationRef childAssocRef : childAssocRefs) { NodeRef childNodeRef = childAssocRef.getChildRef(); @@ -1035,8 +1140,8 @@ public class CopyServiceImpl implements CopyService CopyChildAssociationDetails childAssocCopyDetails = new CopyChildAssociationDetails( childAssocRef, - targetNodeRef, - targetNodeIsNew, + copyTarget, + copyTargetIsNew, copyChildren); // Handle nested copies @@ -1054,7 +1159,7 @@ public class CopyServiceImpl implements CopyService // of the same type. This is ignorable. continue; } - // Check the callbacks + // Get the copy action for the association instance ChildAssocCopyAction childAssocCopyAction = callback.getChildAssociationCopyAction( classQName, copyDetails, @@ -1064,7 +1169,7 @@ public class CopyServiceImpl implements CopyService case IGNORE: break; case COPY_ASSOC: - nodeService.addChild(targetNodeRef, childNodeRef, assocTypeQName, assocQName); + nodeService.addChild(copyTarget, childNodeRef, childAssocTypeQName, assocQName); break; case COPY_CHILD: // Handle potentially cyclic relationships @@ -1073,7 +1178,7 @@ public class CopyServiceImpl implements CopyService // This is either a cyclic relationship or there are multiple different // types of associations between the same parent and child. // Just hook the child up with the association. - nodeService.addChild(targetNodeRef, childNodeRef, assocTypeQName, assocQName); + nodeService.addChild(copyTarget, childNodeRef, childAssocTypeQName, assocQName); } else { @@ -1096,8 +1201,8 @@ public class CopyServiceImpl implements CopyService } // This copy may fail silently copyImpl( - childNodeRef, targetNodeRef, - assocTypeQName, assocQName, + childNodeRef, copyTarget, + childAssocTypeQName, assocQName, copyChildren, false, // Keep child names for deep copies copiesByOriginals, copies); } diff --git a/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java b/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java index 55cae8622c..7c681a28d9 100644 --- a/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java +++ b/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java @@ -41,6 +41,7 @@ import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionCondition; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentReader; @@ -549,6 +550,11 @@ public class CopyServiceImplTest extends BaseSpringTest QName nodeThreeAssocName = QName.createQName("{test}nodeThree"); QName nodeFourAssocName = QName.createQName("{test}nodeFour"); + NodeRef nodeNotCopied = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + nodeOneAssocName, + TEST_TYPE_QNAME).getChildRef(); NodeRef nodeOne = this.nodeService.createNode( this.rootNodeRef, ContentModel.ASSOC_CHILDREN, @@ -571,6 +577,7 @@ public class CopyServiceImplTest extends BaseSpringTest TEST_TYPE_QNAME).getChildRef(); this.nodeService.addChild(nodeFour, nodeThree, TEST_CHILD_ASSOC_TYPE_QNAME, TEST_CHILD_ASSOC_QNAME); this.nodeService.createAssociation(nodeTwo, nodeThree, TEST_ASSOC_TYPE_QNAME); + this.nodeService.createAssociation(nodeTwo, nodeNotCopied, TEST_ASSOC_TYPE_QNAME); // Make node one actionable with a rule to copy nodes into node two Map params = new HashMap(1); @@ -633,13 +640,15 @@ public class CopyServiceImplTest extends BaseSpringTest ChildAssociationRef child = children.get(0); assertEquals(child.getChildRef(), nodeThree); -// // Check the target assoc -// List assocs = this.nodeService.getTargetAssocs(nodeTwoCopy, TEST_ASSOC_TYPE_QNAME); -// assertNotNull(assocs); -// assertEquals(1, assocs.size()); -// AssociationRef assoc = assocs.get(0); -// assertEquals(assoc.getTargetRef(), nodeThreeCopy); -// + // Check the target assoc + List assocs = this.nodeService.getTargetAssocs(nodeTwoCopy, TEST_ASSOC_TYPE_QNAME); + assertNotNull(assocs); + assertEquals(2, assocs.size()); + AssociationRef assoc0 = assocs.get(0); + assertTrue(assoc0.getTargetRef().equals(nodeThreeCopy) || assoc0.getTargetRef().equals(nodeNotCopied)); + AssociationRef assoc1 = assocs.get(1); + assertTrue(assoc1.getTargetRef().equals(nodeThreeCopy) || assoc1.getTargetRef().equals(nodeNotCopied)); + // Check that the rule parameter values have been made relative List rules = this.ruleService.getRules(nodeOneCopy); assertNotNull(rules); diff --git a/source/java/org/alfresco/repo/copy/DefaultCopyBehaviourCallback.java b/source/java/org/alfresco/repo/copy/DefaultCopyBehaviourCallback.java index 68cc1e63dd..743fa56b97 100644 --- a/source/java/org/alfresco/repo/copy/DefaultCopyBehaviourCallback.java +++ b/source/java/org/alfresco/repo/copy/DefaultCopyBehaviourCallback.java @@ -22,6 +22,7 @@ import java.io.Serializable; import java.util.Map; import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; /** @@ -62,6 +63,22 @@ public class DefaultCopyBehaviourCallback extends AbstractCopyBehaviourCallback return true; } + /** + * Default behaviour:
+ * * {@link AssocCopySourceAction#COPY_REMOVE_EXISTING}
+ * * {@link AssocCopyTargetAction#USE_COPIED_OTHERWISE_ORIGINAL_TARGET} + */ + @Override + public Pair getAssociationCopyAction( + QName classQName, + CopyDetails copyDetails, + CopyAssociationDetails assocCopyDetails) + { + return new Pair( + AssocCopySourceAction.COPY_REMOVE_EXISTING, + AssocCopyTargetAction.USE_COPIED_OTHERWISE_ORIGINAL_TARGET); + } + /** * Default behaviour: Cascade if we are copying children AND the association is primary * diff --git a/source/java/org/alfresco/repo/googledocs/GoogleDocsServiceImpl.java b/source/java/org/alfresco/repo/googledocs/GoogleDocsServiceImpl.java index d7f8093609..2d175666d9 100755 --- a/source/java/org/alfresco/repo/googledocs/GoogleDocsServiceImpl.java +++ b/source/java/org/alfresco/repo/googledocs/GoogleDocsServiceImpl.java @@ -500,6 +500,9 @@ public class GoogleDocsServiceImpl extends TransactionListenerAdapter // Set the owner of the document setGoogleResourcePermission(folder, AuthorityType.USER, username, "owner"); + + // Set the owner of the document + setGoogleResourcePermission(folder, AuthorityType.USER, username, "owner"); } } diff --git a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java index 6a9717b9a8..75bb8a5ab6 100644 --- a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java +++ b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java @@ -972,12 +972,10 @@ public class ImapServiceImpl implements ImapService FileInfo mountPointFileInfo = fileFolderService.getFileInfo(mountPoint); NodeRef mountParent = nodeService.getParentAssocs(mountPoint).get(0).getParentRef(); ImapViewMode viewMode = imapConfigMountPoints.get(mountPointName).getMode(); - /* if (!mailboxPattern.equals("*")) { mountPoint = mountParent; } - */ List folders = expandFolder(mountPoint, mountPoint, user, mailboxPattern, listSubscribed, viewMode); if (folders != null) { diff --git a/source/java/org/alfresco/repo/jscript/People.java b/source/java/org/alfresco/repo/jscript/People.java index 81d3778329..97d95d8af6 100644 --- a/source/java/org/alfresco/repo/jscript/People.java +++ b/source/java/org/alfresco/repo/jscript/People.java @@ -504,13 +504,14 @@ public final class People extends BaseScopableProcessorExtension implements Init for (StringTokenizer t = new StringTokenizer(filter, " "); t.hasMoreTokens(); /**/) { String term = LuceneQueryParser.escape(t.nextToken().replace('"', ' ')); - query.append("@").append(NamespaceService.CONTENT_MODEL_PREFIX).append("\\:firstName:\"*"); + query.append("+TYPE:\"").append(ContentModel.TYPE_PERSON).append("\" "); + query.append("+(@").append(NamespaceService.CONTENT_MODEL_PREFIX).append("\\:firstName:\"*"); query.append(term); query.append("*\" @").append(NamespaceService.CONTENT_MODEL_PREFIX).append("\\:lastName:\"*"); query.append(term); query.append("*\" @").append(NamespaceService.CONTENT_MODEL_PREFIX).append("\\:userName:\"*"); query.append(term); - query.append("*\" "); + query.append("*\") "); } // define the search parameters diff --git a/source/java/org/alfresco/repo/jscript/Presence.java b/source/java/org/alfresco/repo/jscript/Presence.java index 7b35da17eb..2cecaffc06 100644 --- a/source/java/org/alfresco/repo/jscript/Presence.java +++ b/source/java/org/alfresco/repo/jscript/Presence.java @@ -56,7 +56,7 @@ public final class Presence extends BaseScopableProcessorExtension String presenceProvider = (String)person.getProperties().get(ContentModel.PROP_PRESENCEPROVIDER); String presenceUsername = (String)person.getProperties().get(ContentModel.PROP_PRESENCEUSERNAME); - return ((presenceProvider != "") && (presenceUsername != "")); + return (!"".equals((presenceProvider)) && (!"".equals(presenceUsername))); } /** diff --git a/source/java/org/alfresco/repo/jscript/ScriptUrls.java b/source/java/org/alfresco/repo/jscript/ScriptUrls.java new file mode 100644 index 0000000000..6933699a43 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/ScriptUrls.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.jscript; + +import java.io.Serializable; + +import org.alfresco.repo.admin.SysAdminParams; + +public class ScriptUrls implements Serializable +{ + private static final long serialVersionUID = 690400883682643830L; + + private SysAdminParams sysAdminParams; + + public ScriptUrls(SysAdminParams sysAdminParams) + { + this.sysAdminParams = sysAdminParams; + } + + public UrlResolver getAlfresco() + { + return new UrlResolver() + { + private static final long serialVersionUID = -3325783930102513154L; + + public String getContext() + { + return sysAdminParams.getAlfrescoContext(); + } + + public String getHost() + { + return sysAdminParams.getAlfrescoHost(); + } + + public Integer getPort() + { + return sysAdminParams.getAlfrescoPort(); + } + + public String getProtocol() + { + return sysAdminParams.getAlfrescoProtocol(); + } + }; + } + + public UrlResolver getShare() + { + return new UrlResolver() + { + private static final long serialVersionUID = -5853699981548697768L; + + public String getContext() + { + return sysAdminParams.getShareContext(); + } + + public String getHost() + { + return sysAdminParams.getShareHost(); + } + + public Integer getPort() + { + return sysAdminParams.getSharePort(); + } + + public String getProtocol() + { + return sysAdminParams.getShareProtocol(); + } + }; + } + + public interface UrlResolver extends Serializable + { + public String getContext(); + + public String getHost(); + + public Integer getPort(); + + public String getProtocol(); + } +} diff --git a/source/java/org/alfresco/repo/model/ml/ContentFilterLanguagesMap.java b/source/java/org/alfresco/repo/model/ml/ContentFilterLanguagesMap.java index 4542ed5f5a..59c1942dc1 100644 --- a/source/java/org/alfresco/repo/model/ml/ContentFilterLanguagesMap.java +++ b/source/java/org/alfresco/repo/model/ml/ContentFilterLanguagesMap.java @@ -31,6 +31,7 @@ import org.springframework.extensions.config.ConfigService; import org.alfresco.error.AlfrescoRuntimeException; import org.springframework.extensions.surf.util.I18NUtil; import org.alfresco.service.cmr.ml.ContentFilterLanguagesService; +import org.alfresco.util.EqualsHelper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -220,7 +221,7 @@ public class ContentFilterLanguagesMap implements ContentFilterLanguagesService this.defaultLanguage = code; } } - if (defaultLanguage == code) + if (EqualsHelper.nullSafeEquals(defaultLanguage, code)) { orderedLangCodes.add(0, code); } diff --git a/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java b/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java index 3b4b9ff551..3280eb1032 100644 --- a/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java +++ b/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java @@ -80,6 +80,9 @@ public class ModuleManagementTool private static final String OPTION_NOBACKUP = "-nobackup"; private static final String OPTION_DIRECTORY = "-directory"; + private static final int ERROR_EXIT_CODE = 1; + private static final int SUCCESS_EXIT_CODE = 0; + /** Default zip detector */ public static final ZipDetector DETECTOR_AMP_AND_WAR = new DefaultRaesZipDetector("amp|war"); @@ -720,7 +723,7 @@ public class ModuleManagementTool if (args.length <= 1) { outputUsage(); - return; + System.exit(ERROR_EXIT_CODE); } ModuleManagementTool manager = new ModuleManagementTool(); @@ -775,12 +778,14 @@ public class ModuleManagementTool // Install the modules from the directory manager.installModules(aepFileLocation, warFileLocation, previewInstall, forceInstall, backup); } + System.exit(SUCCESS_EXIT_CODE); } catch (ModuleManagementToolException e) { // These are user-friendly manager.outputMessage(e.getMessage()); outputUsage(); + System.exit(ERROR_EXIT_CODE); } } else if (OP_LIST.equals(operation) == true && args.length == 2) @@ -788,10 +793,12 @@ public class ModuleManagementTool // List the installed modules String warFileLocation = args[1]; manager.listModules(warFileLocation); + System.exit(SUCCESS_EXIT_CODE); } else { outputUsage(); + System.exit(SUCCESS_EXIT_CODE); } } diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index deb222d0d7..ba9c974ef5 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -1158,7 +1158,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl public boolean preLoadNodes() { - return true; + return false; } public void done() diff --git a/source/java/org/alfresco/repo/processor/ScriptServiceImpl.java b/source/java/org/alfresco/repo/processor/ScriptServiceImpl.java index 29722acc99..835519742b 100644 --- a/source/java/org/alfresco/repo/processor/ScriptServiceImpl.java +++ b/source/java/org/alfresco/repo/processor/ScriptServiceImpl.java @@ -22,6 +22,8 @@ import java.util.HashMap; import java.util.Map; import org.alfresco.model.ContentModel; +import org.alfresco.repo.admin.SysAdminParams; +import org.alfresco.repo.jscript.ScriptUrls; import org.alfresco.scripts.ScriptException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -55,6 +57,8 @@ public class ScriptServiceImpl implements ScriptService /** The node service */ private NodeService nodeService; + private SysAdminParams sysAdminParams; + /** * Sets the name of the default script processor * @@ -75,6 +79,16 @@ public class ScriptServiceImpl implements ScriptService this.nodeService = nodeService; } + /** + * Set the sysAdminParams + * + * @param sysAdminParams the sysAdminParams + */ + public void setSysAdminParams(SysAdminParams sysAdminParams) + { + this.sysAdminParams = sysAdminParams; + } + /** * Register a script processor * @@ -402,6 +416,15 @@ public class ScriptServiceImpl implements ScriptService return extension; } + /** + * @see org.alfresco.service.cmr.repository.ScriptService#buildCoreModel(java.util.Map) + */ + public void buildCoreModel(Map inputMap) + { + ParameterCheck.mandatory("InputMap", inputMap); + inputMap.put("urls", new ScriptUrls(sysAdminParams)); + } + /** * @see org.alfresco.service.cmr.repository.ScriptService#buildDefaultModel(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef) */ @@ -414,6 +437,7 @@ public class ScriptServiceImpl implements ScriptService NodeRef space) { Map model = new HashMap(); + buildCoreModel(model); // add the well known node wrapper objects model.put("companyhome", companyHome); diff --git a/source/java/org/alfresco/repo/search/DocumentNavigator.java b/source/java/org/alfresco/repo/search/DocumentNavigator.java index e9954919c4..5768a3be7b 100644 --- a/source/java/org/alfresco/repo/search/DocumentNavigator.java +++ b/source/java/org/alfresco/repo/search/DocumentNavigator.java @@ -39,6 +39,7 @@ import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QNamePattern; import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.EqualsHelper; import org.alfresco.util.ISO9075; import org.jaxen.DefaultNavigator; import org.jaxen.JaxenException; @@ -182,12 +183,7 @@ public class DocumentNavigator extends DefaultNavigator implements NamedAccessNa public String getAttributeName(Object o) { // Get the local name - String escapedLocalName = ISO9075.encode(((Property) o).qname.getLocalName()); - if(escapedLocalName == ((Property) o).qname.getLocalName()) - { - return escapedLocalName; - } - return escapedLocalName; + return ISO9075.encode(((Property) o).qname.getLocalName()); } public String getAttributeNamespaceUri(Object o) @@ -199,7 +195,7 @@ public class DocumentNavigator extends DefaultNavigator implements NamedAccessNa { QName qName = ((Property) o).qname; String escapedLocalName = ISO9075.encode(qName.getLocalName()); - if(escapedLocalName == qName.getLocalName()) + if (EqualsHelper.nullSafeEquals(escapedLocalName, qName.getLocalName())) { return qName.toString(); } @@ -250,7 +246,7 @@ public class DocumentNavigator extends DefaultNavigator implements NamedAccessNa return ""; } String escapedLocalName = ISO9075.encode(qName.getLocalName()); - if(escapedLocalName == qName.getLocalName()) + if (EqualsHelper.nullSafeEquals(escapedLocalName, qName.getLocalName())) { return qName.toString(); } diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java index 497573b028..94b87557c3 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java @@ -329,6 +329,8 @@ public class IndexInfo implements IndexMonitor private boolean mergerUseCompoundFile = true; private int mergerTargetOverlays = 5; + + private int mergerTargetIndexes = 5; private int mergerTargetOverlaysBlockingFactor = 1; @@ -499,6 +501,7 @@ public class IndexInfo implements IndexMonitor this.mergerMaxMergeDocs = config.getMergerMaxMergeDocs(); this.termIndexInterval = config.getTermIndexInterval(); this.mergerTargetOverlays = config.getMergerTargetOverlayCount(); + this.mergerTargetIndexes = config.getMergerTargetIndexCount(); this.mergerTargetOverlaysBlockingFactor = config.getMergerTargetOverlaysBlockingFactor(); // Work out the relative path of the index try @@ -1542,7 +1545,7 @@ public class IndexInfo implements IndexMonitor public boolean beforeWithReadLock(String id, Set toDelete, Set read) throws IOException { // We want to block until the merger has executed if we have more than a certain number of indexes - return indexEntries.size() <= mergerMergeBlockingFactor * mergerMergeFactor + mergerTargetOverlaysBlockingFactor * mergerTargetOverlays; + return indexEntries.size() <= mergerMergeBlockingFactor * mergerTargetIndexes + mergerTargetOverlaysBlockingFactor * mergerTargetOverlays; } public void transition(String id, Set toDelete, Set read) throws IOException @@ -3318,7 +3321,7 @@ public class IndexInfo implements IndexMonitor if (!mergingIndexes && !applyingDeletions) { - if (indexes > mergerMergeFactor) + if (indexes > mergerTargetIndexes) { // Try merge action = MergeAction.MERGE_INDEX; @@ -3821,7 +3824,7 @@ public class IndexInfo implements IndexMonitor } } - int position = findMergeIndex(1, mergerMaxMergeDocs, mergerMergeFactor, mergeList); + int position = findMergeIndex(1, mergerMaxMergeDocs, mergerTargetIndexes, mergeList); String firstMergeId = mergeList.get(position).getName(); long count = 0; @@ -4052,21 +4055,21 @@ public class IndexInfo implements IndexMonitor return MergeAction.MERGE_INDEX; } - private final int findMergeIndex(long min, long max, int factor, List entries) throws IOException + private final int findMergeIndex(long min, long max, int target, List entries) throws IOException { // TODO: Support max - if (entries.size() <= factor) + if (entries.size() <= target) { return -1; } int total = 0; - for (int i = factor; i < entries.size(); i++) + for (int i = target; i < entries.size(); i++) { total += entries.get(i).getDocumentCount(); } - for (int i = factor - 1; i > 0; i--) + for (int i = target - 1; i > 0; i--) { total += entries.get(i).getDocumentCount(); if (total < entries.get(i - 1).getDocumentCount()) diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java index b7a0b343e8..6f1084179c 100644 --- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java +++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java @@ -787,13 +787,6 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per // Ignore this } - // remove user from any containing authorities - Set containerAuthorities = authorityService.getContainingAuthorities(null, userName, true); - for (String containerAuthority : containerAuthorities) - { - authorityService.removeAuthority(containerAuthority, userName); - } - // remove any user permissions permissionServiceSPI.deletePermissions(userName); diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java index c65321eba2..6a87b44373 100644 --- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java +++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java @@ -1072,11 +1072,13 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase public NodeDescription next() { this.pos++; - String[] authorityNames = new String[17]; + + // Just for fun, make the last group one that includes ALL authorities! + String[] authorityNames = new String[this.pos == RandomGroupCollection.this.size ? RandomGroupCollection.this.size : 17]; for (int i = 0; i < authorityNames.length; i++) { // Choose an authority at random from the list of known authorities - int index = RandomGroupCollection.this.generator.nextInt(RandomGroupCollection.this.authorities + int index = this.pos == RandomGroupCollection.this.size ? i : RandomGroupCollection.this.generator.nextInt(RandomGroupCollection.this.authorities .size()); authorityNames[i] = ChainingUserRegistrySynchronizerTest.this.authorityService .getShortName((String) RandomGroupCollection.this.authorities.get(index)); diff --git a/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java b/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java index fa00499a52..ad36cce9b3 100644 --- a/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java +++ b/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java @@ -893,7 +893,13 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial { SearchControls userSearchCtls = new SearchControls(); userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); - userSearchCtls.setReturningAttributes(new String[] {}); + + // Although we don't actually need any attributes, we ask for the UID for compatibility with Sun Directory Server. See ALF-3868 + userSearchCtls.setReturningAttributes(new String[] + { + this.userIdAttributeName + }); + InitialDirContext ctx = null; try { diff --git a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScript.java b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScript.java index b4a717bc6d..e0b8c5a5c3 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScript.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScript.java @@ -258,6 +258,7 @@ public class AlfrescoJavaScript extends JBPMSpringActionHandler { Map inputMap = createInputMap(context, services, variableAccesses); ScriptService scriptService = services.getScriptService(); + scriptService.buildCoreModel(inputMap); Object result = scriptService.executeScriptString(expression, inputMap); result = convertForJBPM(result, services); return result; diff --git a/source/java/org/alfresco/service/cmr/repository/CopyService.java b/source/java/org/alfresco/service/cmr/repository/CopyService.java index e7a12693b7..c5d710cf41 100644 --- a/source/java/org/alfresco/service/cmr/repository/CopyService.java +++ b/source/java/org/alfresco/service/cmr/repository/CopyService.java @@ -25,7 +25,7 @@ import org.alfresco.service.PublicService; import org.alfresco.service.namespace.QName; /** - * Node operations service interface. + * Copy operations service interface. *

* This interface provides methods to copy nodes within and across workspaces and to * update the state of a node, with that of another node, within and across workspaces. diff --git a/source/java/org/alfresco/service/cmr/repository/ScriptService.java b/source/java/org/alfresco/service/cmr/repository/ScriptService.java index 2638eb8ee5..1788e29699 100644 --- a/source/java/org/alfresco/service/cmr/repository/ScriptService.java +++ b/source/java/org/alfresco/service/cmr/repository/ScriptService.java @@ -184,6 +184,16 @@ public interface ScriptService @Auditable public void resetScriptProcessors(); + + /** + * Add core data-model to provided Map + * + * @param inputMap initial Map of global scope scriptable Node objects + * @return A Map of global scope scriptable Node objects + */ + @Auditable(parameters = {"inputMap"}) + public void buildCoreModel(Map inputMap); + /** * Create the default data-model available to scripts as global scope level objects: *

diff --git a/source/java/org/alfresco/wcm/sandbox/SandboxFactory.java b/source/java/org/alfresco/wcm/sandbox/SandboxFactory.java index 547ff3c888..7d93bc3d96 100644 --- a/source/java/org/alfresco/wcm/sandbox/SandboxFactory.java +++ b/source/java/org/alfresco/wcm/sandbox/SandboxFactory.java @@ -146,6 +146,8 @@ public final class SandboxFactory extends WCMUtil NodeRef webProjectNodeRef, String branchStoreId) { + long start = System.currentTimeMillis(); + // create the 'staging' store for the website String stagingStoreName = WCMUtil.buildStagingStoreName(storeId); @@ -159,9 +161,9 @@ public final class SandboxFactory extends WCMUtil avmService.createStore(stagingStoreName, props); - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("Created staging sandbox: " + stagingStoreName); + logger.trace("Created staging sandbox: " + stagingStoreName); } // we can either branch from an existing staging store or create a new structure @@ -204,9 +206,9 @@ public final class SandboxFactory extends WCMUtil avmService.createStore(previewStoreName, props); - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("Created staging preview sandbox store: " + previewStoreName + + logger.trace("Created staging preview sandbox store: " + previewStoreName + " above " + stagingStoreName); } @@ -238,8 +240,15 @@ public final class SandboxFactory extends WCMUtil dumpStoreProperties(avmService, stagingStoreName); dumpStoreProperties(avmService, previewStoreName); } - - return getSandbox(stagingStoreName); + + SandboxInfo sbInfo = getSandbox(stagingStoreName); + + if (logger.isTraceEnabled()) + { + logger.trace("createStagingSandbox: " + sbInfo.getSandboxId() + " in "+(System.currentTimeMillis()-start)+" ms"); + } + + return sbInfo; } /** @@ -568,6 +577,8 @@ public final class SandboxFactory extends WCMUtil String username, String role) { + long start = System.currentTimeMillis(); + // create the user 'main' store String userStoreName = WCMUtil.buildUserMainStoreName(storeId, username); String previewStoreName = WCMUtil.buildUserPreviewStoreName(storeId, username); @@ -575,9 +586,9 @@ public final class SandboxFactory extends WCMUtil SandboxInfo userSandboxInfo = getSandbox(userStoreName); if (userSandboxInfo != null) { - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("Not creating author sandbox as it already exists: " + userStoreName); + logger.trace("Not creating author sandbox as it already exists: " + userStoreName); } return userSandboxInfo; } @@ -604,9 +615,9 @@ public final class SandboxFactory extends WCMUtil avmService.createStore(userStoreName, props); - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("Created user sandbox: " + userStoreName + " above staging store " + stagingStoreName); + logger.trace("Created user sandbox: " + userStoreName + " above staging store " + stagingStoreName); } // create a layered directory pointing to 'www' in the staging area @@ -623,7 +634,7 @@ public final class SandboxFactory extends WCMUtil addToGroupIfRequired(stagingStoreName, currentUser, PermissionService.WCM_CONTENT_MANAGER); String cms = authorityService.getName(AuthorityType.GROUP, stagingStoreName + "-" + PermissionService.WCM_CONTENT_MANAGER); permissionService.setPermission(dirRef.getStoreRef(), cms, PermissionService.WCM_CONTENT_MANAGER, true); - + permissionService.setPermission(dirRef.getStoreRef(), username, PermissionService.ALL_PERMISSIONS, true); permissionService.setPermission(dirRef.getStoreRef(), PermissionService.ALL_AUTHORITIES, PermissionService.READ, true); // apply the manager role permission for each manager in the web project @@ -654,9 +665,9 @@ public final class SandboxFactory extends WCMUtil // create the user 'preview' store avmService.createStore(previewStoreName, props); - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("Created user preview sandbox store: " + previewStoreName + + logger.trace("Created user preview sandbox store: " + previewStoreName + " above " + userStoreName); } @@ -687,7 +698,14 @@ public final class SandboxFactory extends WCMUtil dumpStoreProperties(avmService, previewStoreName); } - return getSandbox(userStoreName); + SandboxInfo sbInfo = getSandbox(userStoreName); + + if (logger.isTraceEnabled()) + { + logger.trace("createUserSandbox: " + sbInfo.getSandboxId() + " in "+(System.currentTimeMillis()-start)+" ms"); + } + + return sbInfo; } /** @@ -704,6 +722,8 @@ public final class SandboxFactory extends WCMUtil */ public SandboxInfo createWorkflowSandbox(final String storeId) { + long start = System.currentTimeMillis(); + String stagingStoreName = WCMUtil.buildStagingStoreName(storeId); // create the workflow 'main' store @@ -730,9 +750,9 @@ public final class SandboxFactory extends WCMUtil avmService.createStore(mainStoreName, props); - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("Created workflow sandbox store: " + mainStoreName); + logger.trace("Created workflow sandbox store: " + mainStoreName); } // create a layered directory pointing to 'www' in the staging area @@ -765,9 +785,9 @@ public final class SandboxFactory extends WCMUtil avmService.createStore(previewStoreName, props); - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("Created workflow sandbox preview store: " + previewStoreName); + logger.trace("Created workflow sandbox preview store: " + previewStoreName); } // create a layered directory pointing to 'www' in the workflow 'main' store @@ -784,7 +804,14 @@ public final class SandboxFactory extends WCMUtil dumpStoreProperties(avmService, previewStoreName); } - return getSandbox(mainStoreName); + SandboxInfo sbInfo = getSandbox(mainStoreName); + + if (logger.isTraceEnabled()) + { + logger.trace("createWorkflowSandbox: " + sbInfo.getSandboxId() + " in "+(System.currentTimeMillis()-start)+" ms"); + } + + return sbInfo; } /** @@ -808,6 +835,8 @@ public final class SandboxFactory extends WCMUtil */ public SandboxInfo createReadOnlyWorkflowSandbox(final String storeId) { + long start = System.currentTimeMillis(); + String wpStoreId = WCMUtil.getWebProjectStoreId(storeId); String stagingStoreName = WCMUtil.buildStagingStoreName(storeId); @@ -835,11 +864,11 @@ public final class SandboxFactory extends WCMUtil avmService.createStore(mainStoreName, props); - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("Created read-only workflow sandbox store: " + mainStoreName); + logger.trace("Created read-only workflow sandbox store: " + mainStoreName); } - + // create a layered directory pointing to 'www' in the staging area avmService.createLayeredDirectory(WCMUtil.buildStoreRootPath(stagingStoreName), mainStoreName + ":/", @@ -850,7 +879,14 @@ public final class SandboxFactory extends WCMUtil dumpStoreProperties(avmService, mainStoreName); } - return getSandbox(wpStoreId, mainStoreName, false); // no preview store + SandboxInfo sbInfo = getSandbox(wpStoreId, mainStoreName, false); // no preview store + + if (logger.isTraceEnabled()) + { + logger.trace("createReadOnlyWorkflowSandbox: " + sbInfo.getSandboxId() + " in "+(System.currentTimeMillis()-start)+" ms"); + } + + return sbInfo; } @@ -865,6 +901,8 @@ public final class SandboxFactory extends WCMUtil // TODO refactor AVMExpiredContentProcessor ... public String createUserWorkflowSandbox(String stagingStore, String userStore) { + long start = System.currentTimeMillis(); + // create the workflow 'main' store String packageName = "workflow-" + GUID.generate(); String workflowStoreName = userStore + STORE_SEPARATOR + packageName; @@ -891,9 +929,9 @@ public final class SandboxFactory extends WCMUtil avmService.createStore(workflowStoreName, props); - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("Created user workflow sandbox store: " + workflowStoreName); + logger.trace("Created user workflow sandbox store: " + workflowStoreName); } // create a layered directory pointing to 'www' in the users store @@ -927,9 +965,9 @@ public final class SandboxFactory extends WCMUtil avmService.createStore(previewStoreName, props); - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("Created user workflow sandbox preview store: " + previewStoreName); + logger.trace("Created user workflow sandbox preview store: " + previewStoreName); } // create a layered directory pointing to 'www' in the workflow 'main' store @@ -940,6 +978,11 @@ public final class SandboxFactory extends WCMUtil // snapshot the store avmService.createSnapshot(previewStoreName, null, null); + if (logger.isTraceEnabled()) + { + logger.trace("createUserWorkflowSandbox: " + workflowStoreName + " in "+(System.currentTimeMillis()-start)+" ms"); + } + // return the main workflow store name return workflowStoreName; } @@ -951,6 +994,8 @@ public final class SandboxFactory extends WCMUtil public List listAllSandboxes(String wpStoreId, boolean includeWorkflowSandboxes, boolean includeLocalhostDeployed) { + long start = System.currentTimeMillis(); + List stores = avmService.getStores(); List sbInfos = new ArrayList(); @@ -969,22 +1014,29 @@ public final class SandboxFactory extends WCMUtil } } + if (logger.isTraceEnabled()) + { + logger.trace("listAllSandboxes: " + wpStoreId + "[" + sbInfos.size() + "] in "+(System.currentTimeMillis()-start)+" ms"); + } + return sbInfos; } public void deleteSandbox(String sbStoreId) { - deleteSandbox(WCMUtil.getWebProjectStoreId(sbStoreId), sbStoreId); + deleteSandbox(sbStoreId, false); } - public void deleteSandbox(String wpStoreId, String sbStoreId) + public void deleteSandbox(String sbStoreId, boolean isSubmitDirectWorkflowSandbox) { - deleteSandbox(getSandbox(wpStoreId, sbStoreId, true), true); + deleteSandbox(getSandbox(WCMUtil.getWebProjectStoreId(sbStoreId), sbStoreId, true), isSubmitDirectWorkflowSandbox, true); } - public void deleteSandbox(SandboxInfo sbInfo, boolean removeLocks) + public void deleteSandbox(SandboxInfo sbInfo, boolean isSubmitDirectWorkflowSandbox, boolean removeLocks) { if (sbInfo != null) { + long start = System.currentTimeMillis(); + String mainSandboxStore = sbInfo.getMainStoreName(); String wpStoreId = WCMUtil.getWebProjectStoreId(mainSandboxStore); @@ -1008,7 +1060,11 @@ public final class SandboxFactory extends WCMUtil // accessing a preview layer whose main layer has been torn // out from under it. - WCMUtil.removeAllVServerWebapps(virtServerRegistry, path, true); + // optimization: direct submits no longer virtualize the workflow sandbox + if (! isSubmitDirectWorkflowSandbox) + { + WCMUtil.removeAllVServerWebapps(virtServerRegistry, path, true); + } // NOTE: Could use the .sandbox-id. GUID property to delete all sandboxes, // rather than assume a sandbox always had a single preview @@ -1033,9 +1089,9 @@ public final class SandboxFactory extends WCMUtil } } - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("Deleted sandbox: " + mainSandboxStore); + logger.trace("deleteSandbox: " + mainSandboxStore + " in "+(System.currentTimeMillis()-start)+" ms"); } } } diff --git a/source/java/org/alfresco/wcm/sandbox/SandboxServiceImpl.java b/source/java/org/alfresco/wcm/sandbox/SandboxServiceImpl.java index b5743f5b16..16ebe1d8d6 100644 --- a/source/java/org/alfresco/wcm/sandbox/SandboxServiceImpl.java +++ b/source/java/org/alfresco/wcm/sandbox/SandboxServiceImpl.java @@ -82,7 +82,7 @@ public class SandboxServiceImpl implements SandboxService /** Logger */ private static Log logger = LogFactory.getLog(SandboxServiceImpl.class); - private static final String WORKFLOW_SUBMITDIRECT = "jbpm$wcmwf:submitdirect"; + private static final String WORKFLOW_SUBMITDIRECT = "jbpm$"+WCMUtil.WORKFLOW_SUBMITDIRECT_NAME; private WebProjectService wpService; private SandboxFactory sandboxFactory; @@ -188,6 +188,8 @@ public class SandboxServiceImpl implements SandboxService private SandboxInfo createAuthorSandboxImpl(String wpStoreId, String userName) { + long start = System.currentTimeMillis(); + WebProjectInfo wpInfo = wpService.getWebProject(wpStoreId); final NodeRef wpNodeRef = wpInfo.getNodeRef(); @@ -223,9 +225,9 @@ public class SandboxServiceImpl implements SandboxService CreateSandboxTransactionListener tl = new CreateSandboxTransactionListener(sandboxInfoList, wpService.listWebApps(wpNodeRef)); AlfrescoTransactionSupport.bindListener(tl); - if (logger.isInfoEnabled()) + if (logger.isDebugEnabled()) { - logger.info("Created author sandbox: " + sbInfo.getSandboxId() + " (web project id: " + wpStoreId + ")"); + logger.debug("createAuthorSandboxImpl: " + sbInfo.getSandboxId() + " in "+(System.currentTimeMillis()-start)+" ms (web project id: " + wpStoreId + ")"); } return sbInfo; @@ -236,6 +238,8 @@ public class SandboxServiceImpl implements SandboxService */ public List listSandboxes(String wpStoreId) { + long start = System.currentTimeMillis(); + ParameterCheck.mandatoryString("wpStoreId", wpStoreId); List sbInfos = null; @@ -264,6 +268,11 @@ public class SandboxServiceImpl implements SandboxService } } + if (logger.isDebugEnabled()) + { + logger.debug("listSandboxes: " + wpStoreId + " in "+(System.currentTimeMillis()-start)+" ms (web project id: " + wpStoreId + ")"); + } + return sbInfos; } @@ -313,6 +322,8 @@ public class SandboxServiceImpl implements SandboxService */ public SandboxInfo getSandbox(String sbStoreId) { + long start = System.currentTimeMillis(); + ParameterCheck.mandatoryString("sbStoreId", sbStoreId); String wpStoreId = WCMUtil.getWebProjectStoreId(sbStoreId); @@ -337,7 +348,21 @@ public class SandboxServiceImpl implements SandboxService } } - return sandboxFactory.getSandbox(sbStoreId); + SandboxInfo sbInfo = sandboxFactory.getSandbox(sbStoreId); + + if (logger.isTraceEnabled()) + { + if (sbInfo != null) + { + logger.trace("getSandbox: " + sbInfo.getSandboxId() + " in "+(System.currentTimeMillis()-start)+" ms (web project id: " + wpStoreId + ")"); + } + else + { + logger.trace("getSandbox: " + sbStoreId +" (does not exist) in "+(System.currentTimeMillis()-start)+" ms (web project id: " + wpStoreId + ")"); + } + } + + return sbInfo; } /* (non-Javadoc) @@ -377,6 +402,8 @@ public class SandboxServiceImpl implements SandboxService */ public void deleteSandbox(String sbStoreId) { + long start = System.currentTimeMillis(); + ParameterCheck.mandatoryString("sbStoreId", sbStoreId); String wpStoreId = WCMUtil.getWebProjectStoreId(sbStoreId); @@ -411,9 +438,9 @@ public class SandboxServiceImpl implements SandboxService } } - if (logger.isInfoEnabled()) + if (logger.isDebugEnabled()) { - logger.info("Deleted sandbox: " + sbStoreId + " (web project id: " + wpStoreId + ")"); + logger.debug("deleteSandbox: " + sbStoreId + " in "+(System.currentTimeMillis()-start)+" ms (web project id: " + wpStoreId + ")"); } } @@ -506,11 +533,11 @@ public class SandboxServiceImpl implements SandboxService } } - if (logger.isTraceEnabled()) + if (logger.isDebugEnabled()) { - logger.trace("listChanged: "+assets.size()+" assets in "+(System.currentTimeMillis()-start)+" ms (between "+srcVersion+","+srcPath+" and "+dstVersion+","+dstPath); + logger.debug("listChanged: "+assets.size()+" assets in "+(System.currentTimeMillis()-start)+" ms (between "+srcVersion+","+srcPath+" and "+dstVersion+","+dstPath); } - + return assets; } @@ -623,6 +650,8 @@ public class SandboxServiceImpl implements SandboxService final String submitLabel, final String submitComment, final Map expirationDates, final Date launchDate, final boolean autoDeploy) { + long start = System.currentTimeMillis(); + // checks sandbox access (TODO review) getSandbox(sbStoreId); // ignore result @@ -632,10 +661,13 @@ public class SandboxServiceImpl implements SandboxService final String finalWorkflowName; final Map finalWorkflowParams; + boolean isSubmitDirectWorkflowSandbox = false; + if ((workflowName == null) || (workflowName.equals(""))) { finalWorkflowName = WORKFLOW_SUBMITDIRECT; finalWorkflowParams = new HashMap(); + isSubmitDirectWorkflowSandbox = true; } else { @@ -673,7 +705,11 @@ public class SandboxServiceImpl implements SandboxService // inform the virtualisation server if the workflow sandbox was created if (virtUpdatePath != null) { - WCMUtil.updateVServerWebapp(virtServerRegistry, virtUpdatePath, true); + // optimization: direct submits no longer virtualize the workflow sandbox + if (! isSubmitDirectWorkflowSandbox) + { + WCMUtil.updateVServerWebapp(virtServerRegistry, virtUpdatePath, true); + } } try @@ -697,6 +733,11 @@ public class SandboxServiceImpl implements SandboxService throw new AlfrescoRuntimeException("Failed to submit to workflow", err); } } + + if (logger.isDebugEnabled()) + { + logger.debug("submitViaWorkflow: " + sbStoreId + " ["+submitLabel+", "+finalWorkflowName+"] in "+(System.currentTimeMillis()-start)+" ms (web project id: " + wpStoreId + ")"); + } } /** @@ -789,7 +830,7 @@ public class SandboxServiceImpl implements SandboxService return new Pair(sandboxInfo, virtUpdatePath); } - + /** * Starts the configured workflow to allow the submitted items to be link * checked and reviewed. @@ -854,7 +895,7 @@ public class SandboxServiceImpl implements SandboxService public String execute() throws Throwable { // delete AVM stores in the workflow sandbox - sandboxFactory.deleteSandbox(sandboxInfo, true); + sandboxFactory.deleteSandbox(sandboxInfo.getSandboxId()); return null; } }; @@ -934,6 +975,8 @@ public class SandboxServiceImpl implements SandboxService */ public void revertListAssets(String sbStoreId, List assets) { + long start = System.currentTimeMillis(); + ParameterCheck.mandatoryString("sbStoreId", sbStoreId); // checks sandbox access (TODO review) @@ -980,9 +1023,9 @@ public class SandboxServiceImpl implements SandboxService if (parent.isLayeredDirectory()) { - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("reverting " + parentChild[1] + " in " + parentChild[0]); + logger.trace("reverting " + parentChild[1] + " in " + parentChild[0]); } avmService.makeTransparent(parentChild[0], parentChild[1]); @@ -993,9 +1036,9 @@ public class SandboxServiceImpl implements SandboxService // is file or deleted file String relativePath = asset.getPath(); - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("unlocking file " + relativePath + " in web project " + wpStoreId); + logger.trace("unlocking file " + relativePath + " in web project " + wpStoreId); } if (avmLockingService.getLockOwner(wpStoreId, relativePath) != null) @@ -1011,6 +1054,11 @@ public class SandboxServiceImpl implements SandboxService } } } + + if (logger.isDebugEnabled()) + { + logger.debug("revertListAssets: " + sbStoreId + " ["+assets.size()+", "+assetsToRevert.size()+"] in "+(System.currentTimeMillis()-start)+" ms (web project id: " + wpStoreId + ")"); + } } /* (non-Javadoc) @@ -1026,8 +1074,7 @@ public class SandboxServiceImpl implements SandboxService throw new AccessDeniedException("Only content managers may list snapshots '"+sbStoreId+"' (web project id: "+wpStoreId+")"); } - List allVersions = avmService.getStoreVersions(sbStoreId); - return listSnapshots(allVersions, includeSystemGenerated); + return listSnapshots(sbStoreId, null, null, includeSystemGenerated); } /* (non-Javadoc) @@ -1043,18 +1090,30 @@ public class SandboxServiceImpl implements SandboxService throw new AccessDeniedException("Only content managers may list snapshots '"+sbStoreId+"' (web project id: "+wpStoreId+")"); } - List versionsToFilter = avmService.getStoreVersions(sbStoreId, from, to); - return listSnapshots(versionsToFilter, includeSystemGenerated); + return listSnapshotsImpl(sbStoreId, from, to, includeSystemGenerated); } - private List listSnapshots(List versionsToFilter, boolean includeSystemGenerated) + private List listSnapshotsImpl(String sbStoreId, Date from, Date to, boolean includeSystemGenerated) { + long start = System.currentTimeMillis(); + + List versionsToFilter = null; + + if ((from != null) && (to != null)) + { + versionsToFilter = avmService.getStoreVersions(sbStoreId, from, to); + } + else + { + versionsToFilter = avmService.getStoreVersions(sbStoreId); + } + List versions = new ArrayList(versionsToFilter.size()); for (int i = versionsToFilter.size() - 1; i >= 0; i--) // reverse order { VersionDescriptor item = versionsToFilter.get(i); - + // only display snapshots with a valid tag - others are system generated snapshots if ((includeSystemGenerated == true) || ((item.getTag() != null) && (item.getVersionID() != 0))) { @@ -1062,6 +1121,11 @@ public class SandboxServiceImpl implements SandboxService } } + if (logger.isDebugEnabled()) + { + logger.debug("listSnapshotsImpl: " + sbStoreId + " ["+from+", "+to+", "+versions.size()+"] in "+(System.currentTimeMillis()-start)+" ms (web project id: "+WCMUtil.getWebProjectStoreId(sbStoreId)+")"); + } + return versions; } @@ -1070,6 +1134,8 @@ public class SandboxServiceImpl implements SandboxService */ public void revertSnapshot(final String sbStoreId, final int revertVersion) { + long start = System.currentTimeMillis(); + ParameterCheck.mandatoryString("sbStoreId", sbStoreId); String wpStoreId = WCMUtil.getWebProjectStoreId(sbStoreId); @@ -1104,6 +1170,11 @@ public class SandboxServiceImpl implements SandboxService break; } } + + if (logger.isDebugEnabled()) + { + logger.debug("revertSnapshot: " + sbStoreId + " ["+revertVersion+"] in "+(System.currentTimeMillis()-start)+" ms (web project id: "+WCMUtil.getWebProjectStoreId(sbStoreId)+")"); + } } /** @@ -1126,14 +1197,14 @@ public class SandboxServiceImpl implements SandboxService { avmService.addAspect(srcPath, WCMAppModel.ASPECT_EXPIRES); } - + // set the expiration date avmService.setNodeProperty(srcPath, WCMAppModel.PROP_EXPIRATIONDATE, new PropertyValue(DataTypeDefinition.DATETIME, expirationDate)); - - if (logger.isDebugEnabled()) + + if (logger.isTraceEnabled()) { - logger.debug("Set expiration date of " + expirationDate + " for " + srcPath); + logger.trace("Set expiration date of " + expirationDate + " for " + srcPath); } } diff --git a/source/java/org/alfresco/wcm/util/WCMUtil.java b/source/java/org/alfresco/wcm/util/WCMUtil.java index 13708da530..79a6cdfa99 100644 --- a/source/java/org/alfresco/wcm/util/WCMUtil.java +++ b/source/java/org/alfresco/wcm/util/WCMUtil.java @@ -40,11 +40,11 @@ import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.FileNameValidator; -import org.springframework.extensions.surf.util.ParameterCheck; import org.alfresco.util.VirtServerUtils; import org.alfresco.wcm.sandbox.SandboxConstants; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.ParameterCheck; /** * Helper methods and constants related to WCM directories, paths and store name manipulation. @@ -698,7 +698,7 @@ public class WCMUtil extends AVMUtil protected static Map listWebUsers(NodeService nodeService, NodeRef wpNodeRef) { - List userInfoRefs = nodeService.getChildAssocs(wpNodeRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL); + List userInfoRefs = listWebUserRefs(nodeService, wpNodeRef, true); Map webUsers = new HashMap(23); @@ -710,10 +710,15 @@ public class WCMUtil extends AVMUtil webUsers.put(userName, userRole); } - + return webUsers; } + protected static List listWebUserRefs(NodeService nodeService, NodeRef wpNodeRef, boolean preLoad) + { + return nodeService.getChildAssocs(wpNodeRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL, preLoad); + } + /** * Creates all directories for a path if they do not already exist. */ @@ -885,4 +890,6 @@ public class WCMUtil extends AVMUtil private final static Pattern SANDBOX_RELATIVE_PATH_PATTERN = Pattern.compile("([^:]+:/" + JNDIConstants.DIR_DEFAULT_WWW + AVM_PATH_SEPARATOR + JNDIConstants.DIR_DEFAULT_APPBASE + ")(.*)"); + + public static final String WORKFLOW_SUBMITDIRECT_NAME = "wcmwf:submitdirect"; } diff --git a/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java b/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java index 061b6c866e..ea5173ffa6 100644 --- a/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java +++ b/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java @@ -185,6 +185,8 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService public WebProjectInfo createWebProject(WebProjectInfo wpInfo) { + long start = System.currentTimeMillis(); + String wpStoreId = wpInfo.getStoreId(); String name = wpInfo.getName(); String title = wpInfo.getTitle(); @@ -290,9 +292,9 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService CreateWebProjectTransactionListener tl = new CreateWebProjectTransactionListener(wpStoreId); AlfrescoTransactionSupport.bindListener(tl); - if (logger.isInfoEnabled()) + if (logger.isDebugEnabled()) { - logger.info("Created web project: " + wpNodeRef + " (store id: " + wpStoreId + ")"); + logger.debug("Created web project: " + wpNodeRef + " in "+(System.currentTimeMillis()-start)+" ms (store id: " + wpStoreId + ")"); } // Return created web project info @@ -312,6 +314,8 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public void createWebApp(NodeRef wpNodeRef, final String webAppName, final String webAppDescription) { + long start = System.currentTimeMillis(); + WebProjectInfo wpInfo = getWebProject(wpNodeRef); if (isContentManager(wpNodeRef)) @@ -345,9 +349,9 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService } }, AuthenticationUtil.getSystemUserName()); - if (logger.isInfoEnabled()) + if (logger.isDebugEnabled()) { - logger.info("Created web app: "+webAppName+" (store id: "+wpInfo.getStoreId()+")"); + logger.debug("Created web app: "+webAppName+" in "+(System.currentTimeMillis()-start)+" ms (store id: "+wpInfo.getStoreId()+")"); } } else @@ -392,6 +396,8 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public void deleteWebApp(NodeRef wpNodeRef, final String webAppName) { + long start = System.currentTimeMillis(); + ParameterCheck.mandatoryString("webAppName", webAppName); WebProjectInfo wpInfo = getWebProject(wpNodeRef); @@ -422,9 +428,9 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService } }, AuthenticationUtil.getSystemUserName()); - if (logger.isInfoEnabled()) + if (logger.isDebugEnabled()) { - logger.info("Deleted web app: "+webAppName+" (store id: "+wpStoreId+")"); + logger.debug("Deleted web app: "+webAppName+" in "+(System.currentTimeMillis()-start)+" ms (store id: "+wpStoreId+")"); } } else @@ -623,6 +629,8 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public void updateWebProject(WebProjectInfo wpInfo) { + long start = System.currentTimeMillis(); + NodeRef wpNodeRef = getWebProjectNodeFromStore(wpInfo.getStoreId()); if (wpNodeRef == null) { @@ -667,7 +675,7 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService if (logger.isDebugEnabled()) { - logger.debug("Updated web project: " + wpNodeRef + " (store id: " + wpInfo.getStoreId() + ")"); + logger.debug("Updated web project: " + wpNodeRef + " in "+(System.currentTimeMillis()-start)+" ms (store id: " + wpInfo.getStoreId() + ")"); } } @@ -693,6 +701,8 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public void deleteWebProject(final NodeRef wpNodeRef) { + long start = System.currentTimeMillis(); + if (! isContentManager(wpNodeRef)) { // the current user is not a content manager since the web project does not exist (or is not visible) @@ -701,7 +711,7 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService // delete all attached website sandboxes in reverse order to the layering final String wpStoreId = (String)nodeService.getProperty(wpNodeRef, WCMAppModel.PROP_AVMSTORE); - + if (wpStoreId != null) { // Notify virtualization server about removing this website @@ -754,7 +764,7 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService } // delete sandbox (and associated preview sandbox, if it exists) - sandboxFactory.deleteSandbox(sbInfo, false); + sandboxFactory.deleteSandbox(sbInfo, false, false); } // delete all web project locks in one go (ie. all those currently held against staging store) @@ -778,9 +788,9 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService transactionService.getRetryingTransactionHelper().doInTransaction(deleteWebProjectWork); - if (logger.isInfoEnabled()) + if (logger.isDebugEnabled()) { - logger.info("Deleted web project: " + wpNodeRef + " (store id: " + wpStoreId + ")"); + logger.debug("Deleted web project: " + wpNodeRef + " in "+(System.currentTimeMillis()-start)+" ms (store id: " + wpStoreId + ")"); } } catch (Throwable err) @@ -867,7 +877,16 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public int getWebUserCount(NodeRef wpNodeRef) { - return WCMUtil.listWebUsers(nodeService, wpNodeRef).size(); + long start = System.currentTimeMillis(); + + int cnt = WCMUtil.listWebUserRefs(nodeService, wpNodeRef, false).size(); + + if (logger.isTraceEnabled()) + { + logger.trace("Get web user cnt: " + wpNodeRef + "(" + cnt + ") in "+(System.currentTimeMillis()-start)+" ms"); + } + + return cnt; } /* (non-Javadoc) @@ -883,19 +902,28 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public Map listWebUsers(NodeRef wpNodeRef) { + long start = System.currentTimeMillis(); + // special case: allow System - eg. to allow user to create their own sandbox on-demand (createAuthorSandbox) if (isContentManager(wpNodeRef) || (AuthenticationUtil.getRunAsUser().equals(AuthenticationUtil.getSystemUserName()) || (permissionService.hasPermission(wpNodeRef, PermissionService.ADD_CHILDREN) == AccessStatus.ALLOWED))) { - return WCMUtil.listWebUsers(nodeService, wpNodeRef); + Map users = WCMUtil.listWebUsers(nodeService, wpNodeRef); + + if (logger.isTraceEnabled()) + { + logger.trace("List web users: " + wpNodeRef + "(" + users.size() + ") in "+(System.currentTimeMillis()-start)+" ms"); + } + + return users; } else { throw new AccessDeniedException("Only content managers may list users in a web project"); } } - + /* (non-Javadoc) * @see org.alfresco.wcm.webproject.WebProjectService#getWebUserRole(java.lang.String, java.lang.String) */ @@ -1039,6 +1067,8 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService public void inviteWebUsersGroups(NodeRef wpNodeRef, Map userGroupRoles, boolean autoCreateAuthorSandbox) { + long start = System.currentTimeMillis(); + if (! (isContentManager(wpNodeRef) || permissionService.hasPermission(wpNodeRef, PermissionService.ADD_CHILDREN) == AccessStatus.ALLOWED)) { @@ -1191,7 +1221,7 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService } } } - + // TODO - split out into separate 'change role' // update user's roles if (usersToUpdate.size() != 0) @@ -1199,9 +1229,9 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService sandboxFactory.updateSandboxRoles(wpStoreId, usersToUpdate, perms); } - if (logger.isInfoEnabled()) + if (logger.isDebugEnabled()) { - logger.info("Invited "+invitedCount+" web users (store id: "+wpStoreId+")"); + logger.debug("Invited "+invitedCount+" web users in "+(System.currentTimeMillis()-start)+" ms (store id: "+wpStoreId+")"); } } @@ -1226,6 +1256,8 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public void inviteWebUser(NodeRef wpNodeRef, String userAuth, String role, boolean autoCreateAuthorSandbox) { + long start = System.currentTimeMillis(); + if (! (isContentManager(wpNodeRef) || permissionService.hasPermission(wpNodeRef, PermissionService.ADD_CHILDREN) == AccessStatus.ALLOWED)) { @@ -1234,7 +1266,7 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService WebProjectInfo wpInfo = getWebProject(wpNodeRef); final String wpStoreId = wpInfo.getStoreId(); - + // build a list of managers who will have full permissions on ALL staging areas List managers = new ArrayList(4); @@ -1286,9 +1318,9 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService sandboxFactory.updateSandboxRoles(wpStoreId, usersToUpdate, perms); - if (logger.isInfoEnabled()) + if (logger.isDebugEnabled()) { - logger.info("Web user "+userAuth +"'s role has been changed from '" + oldUserRole + "' to '" + role + "' (store id: "+wpStoreId+")"); + logger.debug("Web user "+userAuth +"'s role has been changed from '" + oldUserRole + "' to '" + role + "' in "+(System.currentTimeMillis()-start)+" ms (store id: "+wpStoreId+")"); } } } @@ -1333,9 +1365,9 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService } } - if (logger.isInfoEnabled()) + if (logger.isDebugEnabled()) { - logger.info("Invited web user: "+userAuth+" (store id: "+wpStoreId+")"); + logger.debug("Invited web user: "+userAuth+" in "+(System.currentTimeMillis()-start)+" ms (store id: "+wpStoreId+")"); } } } @@ -1374,6 +1406,8 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public void uninviteWebUser(NodeRef wpNodeRef, String userAuth, boolean autoDeleteAuthorSandbox) { + long start = System.currentTimeMillis(); + if (! isContentManager(wpNodeRef)) { throw new AccessDeniedException("Only content managers may uninvite web user '"+userAuth+"' from web project: "+wpNodeRef); @@ -1385,7 +1419,7 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService WebProjectInfo wpInfo = getWebProject(wpNodeRef); String wpStoreId = wpInfo.getStoreId(); String userMainStore = WCMUtil.buildUserMainStoreName(wpStoreId, userAuth); - + if (autoDeleteAuthorSandbox) { sandboxFactory.deleteSandbox(userMainStore); @@ -1420,12 +1454,12 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService // remove permission for the user (also fixes ETWOONE-338) permissionService.clearPermission(wpNodeRef, userAuth); - - if (logger.isInfoEnabled()) + + if (logger.isDebugEnabled()) { - logger.info("Uninvited web user: "+userAuth+" (store id: "+wpStoreId+")"); + logger.debug("Uninvited web user: "+userAuth+" in "+(System.currentTimeMillis()-start)+" ms (store id: "+wpStoreId+")"); } - + break; // for loop } }