diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index df3ecd83be..de3cf06e96 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -652,6 +652,7 @@ webdav:lockScope + @@ -788,7 +789,6 @@ - diff --git a/config/alfresco/deployment-service-context.xml b/config/alfresco/deployment-service-context.xml index 561ce9db8e..b3b2783f22 100644 --- a/config/alfresco/deployment-service-context.xml +++ b/config/alfresco/deployment-service-context.xml @@ -58,10 +58,22 @@ + + + + + + + + + + + + diff --git a/config/alfresco/messages/templates-messages.properties b/config/alfresco/messages/templates-messages.properties index 66d9c2ad30..91a58e7e2e 100644 --- a/config/alfresco/messages/templates-messages.properties +++ b/config/alfresco/messages/templates-messages.properties @@ -12,6 +12,7 @@ templates.show_audit.application=Application templates.show_audit.service=Service templates.show_audit.method=Method templates.show_audit.timestamp=Timestamp +templates.show_audit.values=Audit Entry Values templates.show_audit.failed=Failed templates.show_audit.message=Message templates.show_audit.arg_1=Arg 1 diff --git a/config/alfresco/node-services-context.xml b/config/alfresco/node-services-context.xml index cfd254c95a..83df8e0955 100644 --- a/config/alfresco/node-services-context.xml +++ b/config/alfresco/node-services-context.xml @@ -209,6 +209,15 @@ + + + + ${spaces.archive.store} + ${version.store.version2Store} + + + + @@ -222,14 +231,11 @@ - - ${spaces.archive.store} - ${version.store.version2Store} - + - + diff --git a/config/alfresco/ownable-services-context.xml b/config/alfresco/ownable-services-context.xml index 11e3c2ca4f..57e1ffe30c 100644 --- a/config/alfresco/ownable-services-context.xml +++ b/config/alfresco/ownable-services-context.xml @@ -15,5 +15,11 @@ + + + + + + \ No newline at end of file diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 76e335528d..9757a72da1 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -436,6 +436,7 @@ spaces.templates.email.workflowemailnotification.childname=cm:workflownotificati spaces.nodetemplates.childname=app:node_templates # ADM VersionStore Configuration +version.store.enableAutoVersioning=true version.store.deprecated.lightWeightVersionStore=workspace://lightWeightVersionStore version.store.version2Store=workspace://version2Store diff --git a/config/alfresco/subsystems/thirdparty/default/swf-transform.properties b/config/alfresco/subsystems/thirdparty/default/swf-transform.properties index ec7d2c21ff..83fec62c75 100644 --- a/config/alfresco/subsystems/thirdparty/default/swf-transform.properties +++ b/config/alfresco/subsystems/thirdparty/default/swf-transform.properties @@ -3,5 +3,6 @@ swf.exe=./bin/pdf2swf # This option on pdf2swf improves the transformation of graphics-heavy pdfs. See ALF-3580. # poly2bitmap improves the chances of successful transformation. On its own it reduces -# the resolution of embedded images. subpixels sets the dpi for embedded images. -swf.encoder.params=-s poly2bitmap,subpixels=72 +# the resolution of embedded images. subpixels sets the dpi for embedded images. +# zoom introduced and -s added before each option (ALF-9417). +swf.encoder.params=-s zoom=72 -s ppmsubpixels=1 -s poly2bitmap=1 -s bitmapfonts=1 diff --git a/config/alfresco/templates/content/examples/show_audit.ftl b/config/alfresco/templates/content/examples/show_audit.ftl index a72b90a80d..3c353f04e5 100644 --- a/config/alfresco/templates/content/examples/show_audit.ftl +++ b/config/alfresco/templates/content/examples/show_audit.ftl @@ -6,81 +6,27 @@ ${message("templates.show_audit.user_name")} ${message("templates.show_audit.application")} - ${message("templates.show_audit.service")} ${message("templates.show_audit.method")} ${message("templates.show_audit.timestamp")} - ${message("templates.show_audit.failed")} - ${message("templates.show_audit.message")} - ${message("templates.show_audit.arg_1")} - ${message("templates.show_audit.arg_2")} - ${message("templates.show_audit.arg_3")} - ${message("templates.show_audit.arg_4")} - ${message("templates.show_audit.arg_5")} - ${message("templates.show_audit.return")} - ${message("templates.show_audit.thowable")} - ${message("templates.show_audit.tx")} + ${message("templates.show_audit.values")} <#list document.auditTrail as t> ${t.userIdentifier} ${t.auditApplication} - <#if t.auditService?exists> - ${t.auditService} - <#else> -   - <#if t.auditMethod?exists> ${t.auditMethod} <#else>   - ${t.date} - <#if t.fail?exists> - ${t.fail?string("FAILED", "OK")} + ${t.date?datetime} + <#if t.values?exists> + + <@hashMap map=t.values /> + <#else>   - <#if t.message?exists> - ${t.message} - <#else> -   - - <#if t.methodArgumentsAsStrings[0]?exists> - ${t.methodArgumentsAsStrings[0]} - <#else> -   - - <#if t.methodArgumentsAsStrings[1]?exists> - ${t.methodArgumentsAsStrings[1]} - <#else> -   - - <#if t.methodArgumentsAsStrings[2]?exists> - ${t.methodArgumentsAsStrings[2]} - <#else> -   - - <#if t.methodArgumentsAsStrings[3]?exists> - ${t.methodArgumentsAsStrings[3]} - <#else> -   - - <#if t.methodArgumentsAsStrings[4]?exists> - ${t.methodArgumentsAsStrings[4]} - <#else> -   - - <#if t.returnObjectAsString?exists> - ${t.returnObjectAsString} - <#else> -   - - <#if t.throwableAsString?exists> - ${t.throwableAsString} - <#else> -   - - ${t.txId} @@ -91,83 +37,74 @@ ${message("templates.show_audit.user_name")} ${message("templates.show_audit.application")} - ${message("templates.show_audit.service")} ${message("templates.show_audit.method")} ${message("templates.show_audit.timestamp")} - ${message("templates.show_audit.failed")} - ${message("templates.show_audit.message")} - ${message("templates.show_audit.arg_1")} - ${message("templates.show_audit.arg_2")} - ${message("templates.show_audit.arg_3")} - ${message("templates.show_audit.arg_4")} - ${message("templates.show_audit.arg_5")} - ${message("templates.show_audit.return")} - ${message("templates.show_audit.thowable")} - ${message("templates.show_audit.tx")} + ${message("templates.show_audit.values")} <#list space.auditTrail as t> ${t.userIdentifier} ${t.auditApplication} - <#if t.auditService?exists> - ${t.auditService} - <#else> -   - <#if t.auditMethod?exists> ${t.auditMethod} <#else>   - ${t.date} - <#if t.fail?exists> - ${t.fail?string("FAILED", "OK")} + ${t.date?datetime} + <#if t.values?exists> + + <@hashMap map=t.values /> + <#else>   - <#if t.message?exists> - ${t.message} - <#else> -   - - <#if t.methodArgumentsAsStrings[0]?exists> - ${t.methodArgumentsAsStrings[0]} - <#else> -   - - <#if t.methodArgumentsAsStrings[1]?exists> - ${t.methodArgumentsAsStrings[1]} - <#else> -   - - <#if t.methodArgumentsAsStrings[2]?exists> - ${t.methodArgumentsAsStrings[2]} - <#else> -   - - <#if t.methodArgumentsAsStrings[3]?exists> - ${t.methodArgumentsAsStrings[3]} - <#else> -   - - <#if t.methodArgumentsAsStrings[4]?exists> - ${t.methodArgumentsAsStrings[4]} - <#else> -   - - <#if t.returnObjectAsString?exists> - ${t.returnObjectAsString} - <#else> -   - - <#if t.throwableAsString?exists> - ${t.throwableAsString} - <#else> -   - - ${t.txId} - \ No newline at end of file + + +<#-- renders an audit entry values --> +<#macro hashMap map simpleMode=false> +
    + <#assign index = 0 /> + <#list map?keys as key> + <#if simpleMode> +
  • <@parseValue value=key />=<@parseValue value=map?values[index] />
  • + <#else> + <#assign value = map[key] /> + <#if value?is_sequence> +
  • <@parseValue value=key />= +
      + <#list value as element> +
    • <@parseValue value=element />
    • + +
    +
  • + <#elseif value?is_hash> +
  • <@parseValue value=key />= + <@hashMap map=value simpleMode=true /> +
  • + <#else> +
  • <@parseValue value=key />=<@parseValue value=value />
  • + + + <#assign index = index + 1 /> + +
+ + +<#-- renders an audit entry value --> +<#macro parseValue value="null"> + <#if value?is_number> + ${value?c} + <#elseif value?is_boolean> + ${value?string} + <#elseif value?is_date> + ${value?datetime} + <#elseif value?is_string && value != "null"> + ${shortQName(value?string)} + <#elseif value?is_hash && value?values[0]?exists> + ${value?values[0]} + + \ No newline at end of file diff --git a/source/java/org/alfresco/repo/avm/wf/AVMDeployHandler.java b/source/java/org/alfresco/repo/avm/wf/AVMDeployHandler.java index 80bc979e96..7f7f6a50f3 100644 --- a/source/java/org/alfresco/repo/avm/wf/AVMDeployHandler.java +++ b/source/java/org/alfresco/repo/avm/wf/AVMDeployHandler.java @@ -24,17 +24,15 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Properties; import org.alfresco.config.JNDIConstants; -import org.alfresco.model.ContentModel; import org.alfresco.model.WCMAppModel; import org.alfresco.repo.avm.AVMNodeConverter; import org.alfresco.repo.avm.actions.AVMDeployWebsiteAction; import org.alfresco.repo.domain.PropertyValue; -import org.alfresco.repo.importer.ImporterBootstrap; import org.alfresco.repo.workflow.jbpm.JBPMNode; import org.alfresco.repo.workflow.jbpm.JBPMSpringActionHandler; +import org.alfresco.service.cmr.avm.deploy.DeploymentService; import org.alfresco.wcm.sandbox.SandboxConstants; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; @@ -42,13 +40,9 @@ import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.GUID; -import org.alfresco.util.ISO9075; import org.alfresco.util.Pair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -63,21 +57,17 @@ import org.springframework.beans.factory.BeanFactory; */ public class AVMDeployHandler extends JBPMSpringActionHandler { + private DeploymentService deploymentService; private AVMService avmService; private ActionService actionService; - private SearchService searchService; private NodeService unprotectedNodeService; private PermissionService unprotectedPermissionService; - private ImporterBootstrap importerBootstrap; + private static final String BEAN_DEPLOYMENT_SERVICE = "deploymentService"; private static final String BEAN_AVM_SERVICE = "AVMService"; private static final String BEAN_ACTION_SERVICE = "actionService"; private static final String BEAN_NODE_SERVICE = "nodeService"; - private static final String BEAN_SEARCH_SERVICE = "searchService"; private static final String BEAN_PERMISSION_SERVICE = "permissionService"; - private static final String BEAN_IMPORTER_BOOTSTRAP = "spacesBootstrap"; - private static final String PROP_ROOT_FOLDER = "spaces.company_home.childname"; - private static final String PROP_WCM_FOLDER = "spaces.wcm.childname"; private static final long serialVersionUID = 5590265401983087178L; private static final Log logger = LogFactory.getLog(AVMDeployHandler.class); @@ -89,10 +79,9 @@ public class AVMDeployHandler extends JBPMSpringActionHandler @Override protected void initialiseHandler(BeanFactory factory) { + this.deploymentService = (DeploymentService)factory.getBean(BEAN_DEPLOYMENT_SERVICE); this.avmService = (AVMService)factory.getBean(BEAN_AVM_SERVICE); this.actionService = (ActionService)factory.getBean(BEAN_ACTION_SERVICE); - this.searchService = (SearchService)factory.getBean(BEAN_SEARCH_SERVICE); - this.importerBootstrap = (ImporterBootstrap)factory.getBean(BEAN_IMPORTER_BOOTSTRAP); this.unprotectedNodeService = (NodeService)factory.getBean(BEAN_NODE_SERVICE); this.unprotectedPermissionService = (PermissionService)factory.getBean(BEAN_PERMISSION_SERVICE); } @@ -122,7 +111,7 @@ public class AVMDeployHandler extends JBPMSpringActionHandler NodeRef webProjectRef = webProjNode.getNodeRef(); // get the list of live servers for the project that have the auto deploy flag turned on - List servers = findDeployToServers(webProjectRef); + List servers = deploymentService.findLiveDeploymentServers(webProjectRef); // if there are servers do the deploy if (servers.size() > 0) @@ -206,59 +195,4 @@ public class AVMDeployHandler extends JBPMSpringActionHandler } } } - - private List findDeployToServers(NodeRef webProjectRef) - { - // get folder names - Properties configuration = this.importerBootstrap.getConfiguration(); - String rootFolder = configuration.getProperty(PROP_ROOT_FOLDER); - String wcmFolder = configuration.getProperty(PROP_WCM_FOLDER); - - // get web project name - String webProjectName = (String)this.unprotectedNodeService.getProperty( - webProjectRef, ContentModel.PROP_NAME); - String safeProjectName = ISO9075.encode(webProjectName); - - // build the query - StringBuilder query = new StringBuilder("PATH:\"/"); - query.append(rootFolder); - query.append("/"); - query.append(wcmFolder); - query.append("/cm:"); - query.append(safeProjectName); - query.append("/*\" AND @"); - query.append(NamespaceService.WCMAPP_MODEL_PREFIX); - query.append("\\:"); - query.append(WCMAppModel.PROP_DEPLOYSERVERTYPE.getLocalName()); - query.append(":\""); - query.append(WCMAppModel.CONSTRAINT_LIVESERVER); - query.append("\" AND @"); - query.append(NamespaceService.WCMAPP_MODEL_PREFIX); - query.append("\\:"); - query.append(WCMAppModel.PROP_DEPLOYONAPPROVAL.getLocalName()); - query.append(":\"true\""); - - // execute the query - ResultSet results = null; - List servers = new ArrayList(); - try - { - results = searchService.query(webProjectRef.getStoreRef(), - SearchService.LANGUAGE_LUCENE, query.toString()); - - for (NodeRef server : results.getNodeRefs()) - { - servers.add(server); - } - } - finally - { - if (results != null) - { - results.close(); - } - } - - return servers; - } } diff --git a/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java b/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java index af81eccb38..98110b8e25 100644 --- a/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java +++ b/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java @@ -38,7 +38,6 @@ import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.policy.ClassPolicyDelegate; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.version.VersionableAspect; import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.coci.CheckOutCheckInServiceException; import org.alfresco.service.cmr.lock.LockService; @@ -95,9 +94,9 @@ public class CheckOutCheckInServiceImpl implements CheckOutCheckInService * Extension character, used to recalculate the working copy names */ private static final String EXTENSION_CHARACTER = "."; - + private static Log logger = LogFactory.getLog(CheckOutCheckInServiceImpl.class); - + private NodeService nodeService; private VersionService versionService; private LockService lockService; @@ -108,12 +107,7 @@ public class CheckOutCheckInServiceImpl implements CheckOutCheckInService private AuthenticationService authenticationService; private RuleService ruleService; - /** - * The versionable aspect behaviour implementation - */ - @SuppressWarnings("unused") - private VersionableAspect versionableAspect; - + /** Component to determine which behaviours are active and which not */ private BehaviourFilter behaviourFilter; /** @@ -180,14 +174,6 @@ public class CheckOutCheckInServiceImpl implements CheckOutCheckInService this.fileFolderService = fileFolderService; } - /** - * Sets the versionable aspect behaviour implementation - */ - public void setVersionableAspect(VersionableAspect versionableAspect) - { - this.versionableAspect = versionableAspect; - } - /** * @param policyComponent policy component */ diff --git a/source/java/org/alfresco/repo/copy/AbstractCopyBehaviourCallback.java b/source/java/org/alfresco/repo/copy/AbstractCopyBehaviourCallback.java index 4698f9eb4b..372a3ac3c3 100644 --- a/source/java/org/alfresco/repo/copy/AbstractCopyBehaviourCallback.java +++ b/source/java/org/alfresco/repo/copy/AbstractCopyBehaviourCallback.java @@ -151,7 +151,7 @@ public abstract class AbstractCopyBehaviourCallback implements CopyBehaviourCall Serializable newListValue = oldListValue; if (oldListValue instanceof NodeRef) { - newListValue = repointNodeRef(copyMap, (NodeRef) value); + newListValue = repointNodeRef(copyMap, (NodeRef) oldListValue); } // Put the value in the new list even though the new list might be discarded newList.add(newListValue); @@ -159,7 +159,7 @@ public abstract class AbstractCopyBehaviourCallback implements CopyBehaviourCall if (!newListValue.equals(oldListValue)) { // The value changed, so the new list will have to be set onto the target node - newValue = newListValue; + newValue = (Serializable) newList; } } } diff --git a/source/java/org/alfresco/repo/deploy/DeploymentServiceImpl.java b/source/java/org/alfresco/repo/deploy/DeploymentServiceImpl.java index 3abbfe64d4..7c5daf15a0 100644 --- a/source/java/org/alfresco/repo/deploy/DeploymentServiceImpl.java +++ b/source/java/org/alfresco/repo/deploy/DeploymentServiceImpl.java @@ -47,6 +47,7 @@ import org.alfresco.deployment.DeploymentToken; import org.alfresco.deployment.DeploymentTransportOutputFilter; import org.alfresco.deployment.FileDescriptor; import org.alfresco.deployment.FileType; +import org.alfresco.model.WCMAppModel; import org.alfresco.repo.action.ActionServiceRemote; import org.alfresco.repo.avm.AVMNodeConverter; import org.alfresco.repo.avm.AVMNodeService; @@ -79,7 +80,13 @@ import org.alfresco.service.cmr.remote.AVMRemoteTransport; import org.alfresco.service.cmr.remote.AVMSyncServiceTransport; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; +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.namespace.NamespacePrefixResolver; +import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.NameMatcher; @@ -97,6 +104,10 @@ public class DeploymentServiceImpl implements DeploymentService { private static Log fgLogger = LogFactory.getLog(DeploymentServiceImpl.class); + private NodeService nodeService; + private NamespacePrefixResolver namespacePrefixResolver; + private SearchService searchService; + /** * The local AVMService Instance. */ @@ -194,6 +205,33 @@ public class DeploymentServiceImpl implements DeploymentService this.trxService = trxService; } + /** + * Setter. + * @param nodeService The instance to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Setter. + * @param namespacePrefixResolver The instance to set. + */ + public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver) + { + this.namespacePrefixResolver = namespacePrefixResolver; + } + + /** + * Setter. + * @param searchService The instance to set. + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + /* * Deploy differences to an ASR * (non-Javadoc) @@ -1634,6 +1672,83 @@ public class DeploymentServiceImpl implements DeploymentService } } + public List findLiveDeploymentServers(NodeRef webProjectRef) + { + return findDeploymentServers(webProjectRef, true, false); + } + + public List findTestDeploymentServers(NodeRef webProjectRef, boolean availableOnly) + { + return findDeploymentServers(webProjectRef, false, availableOnly); + } + + private List findDeploymentServers(NodeRef webProjectRef, boolean live, boolean availableOnly) + { + + Path projectPath = nodeService.getPath(webProjectRef); + String stringPath = projectPath.toPrefixString(namespacePrefixResolver); + String serverType; + + if (live) + { + serverType = WCMAppModel.CONSTRAINT_LIVESERVER; + } + else + { + serverType = WCMAppModel.CONSTRAINT_TESTSERVER; + } + + + StringBuilder query = new StringBuilder("PATH:\""); + + query.append(stringPath); + query.append("/*\" "); + query.append(" AND @"); + query.append(NamespaceService.WCMAPP_MODEL_PREFIX); + query.append("\\:"); + query.append(WCMAppModel.PROP_DEPLOYSERVERTYPE.getLocalName()); + query.append(":\""); + query.append(serverType); + query.append("\""); + + // if required filter the test servers + if (live == false && availableOnly) + { + query.append(" AND ISNULL:\""); + query.append(WCMAppModel.PROP_DEPLOYSERVERALLOCATEDTO.toString()); + query.append("\""); + } + + if (fgLogger.isDebugEnabled()) + fgLogger.debug("Finding deployment servers using query: " + query.toString()); + + // execute the query + ResultSet results = null; + List servers = new ArrayList(); + try + { + results = searchService.query(webProjectRef.getStoreRef(), + SearchService.LANGUAGE_LUCENE, query.toString()); + + if (fgLogger.isDebugEnabled()) + fgLogger.debug("Found " + results.length() + " deployment servers"); + + for (NodeRef server : results.getNodeRefs()) + { + servers.add(server); + } + } + finally + { + if (results != null) + { + results.close(); + } + } + + return servers; + } + public void setNumberOfSendingThreads(int numberOfSendingThreads) { this.numberOfSendingThreads = numberOfSendingThreads; } diff --git a/source/java/org/alfresco/repo/exporter/ExporterComponentTest.java b/source/java/org/alfresco/repo/exporter/ExporterComponentTest.java index 439f633f3a..98ea6bfe3a 100644 --- a/source/java/org/alfresco/repo/exporter/ExporterComponentTest.java +++ b/source/java/org/alfresco/repo/exporter/ExporterComponentTest.java @@ -18,30 +18,47 @@ */package org.alfresco.repo.exporter; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; import java.util.Locale; +import java.util.Map; -import org.springframework.extensions.surf.util.I18NUtil; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.importer.ACPImportPackageHandler; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.CategoryService; import org.alfresco.service.cmr.security.AccessPermission; import org.alfresco.service.cmr.view.Exporter; import org.alfresco.service.cmr.view.ExporterContext; import org.alfresco.service.cmr.view.ExporterCrawlerParameters; import org.alfresco.service.cmr.view.ExporterService; +import org.alfresco.service.cmr.view.ImportPackageHandler; import org.alfresco.service.cmr.view.ImporterService; import org.alfresco.service.cmr.view.Location; import org.alfresco.service.namespace.QName; import org.alfresco.util.BaseSpringTest; import org.alfresco.util.TempFileProvider; import org.alfresco.util.debug.NodeStoreInspector; +import org.springframework.extensions.surf.util.I18NUtil; public class ExporterComponentTest extends BaseSpringTest @@ -50,6 +67,8 @@ public class ExporterComponentTest extends BaseSpringTest private NodeService nodeService; private ExporterService exporterService; private ImporterService importerService; + private FileFolderService fileFolderService; + private CategoryService categoryService; private StoreRef storeRef; private AuthenticationComponent authenticationComponent; @@ -60,18 +79,11 @@ public class ExporterComponentTest extends BaseSpringTest nodeService = (NodeService)applicationContext.getBean(ServiceRegistry.NODE_SERVICE.getLocalName()); exporterService = (ExporterService)applicationContext.getBean("exporterComponent"); importerService = (ImporterService)applicationContext.getBean("importerComponent"); - - // Create the store -// this.storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); -// this.storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "test"); -// this.storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); - - + fileFolderService = (FileFolderService) applicationContext.getBean("fileFolderService"); + categoryService = (CategoryService) applicationContext.getBean("categoryService"); this.authenticationComponent = (AuthenticationComponent)this.applicationContext.getBean("authenticationComponent"); - this.authenticationComponent.setSystemUserAsCurrentUser(); - this.storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); } @@ -116,6 +128,167 @@ public class ExporterComponentTest extends BaseSpringTest output.close(); } + /** + * Round-trip of export then import will result in the imported content having the same categories + * assigned to it as for the exported content -- provided the source and destination stores are the same. + */ + @SuppressWarnings("unchecked") + public void testRoundTripKeepsCategoriesWhenWithinSameStore() throws Exception + { + // Use a store ref that has the bootstrapped categories + StoreRef storeRef = StoreRef.STORE_REF_WORKSPACE_SPACESSTORE; + NodeRef rootNode = nodeService.getRootNode(storeRef); + + ChildAssociationRef contentChildAssocRef = createContentWithCategories(storeRef, rootNode); + + // Export/import + File acpFile = exportContent(contentChildAssocRef); + FileInfo importFolderFileInfo = importContent(acpFile, rootNode); + + // Check categories + NodeRef importedFileNode = fileFolderService.searchSimple(importFolderFileInfo.getNodeRef(), "test.txt"); + assertNotNull("Couldn't find imported file: test.txt", importedFileNode); + assertTrue(nodeService.hasAspect(importedFileNode, ContentModel.ASPECT_GEN_CLASSIFIABLE)); + List importedFileCategories = (List) + nodeService.getProperty(importedFileNode, ContentModel.PROP_CATEGORIES); + assertCategoriesEqual(importedFileCategories, + "Regions", + "Software Document Classification"); + } + + /** + * If the source and destination stores are not the same, then a round-trip of export then import + * will result in the imported content not having the categories assigned to it that were present + * on the exported content. + */ + @SuppressWarnings("unchecked") + public void testRoundTripLosesCategoriesImportingToDifferentStore() throws Exception + { + // Use a store ref that has the bootstrapped categories + StoreRef storeRef = StoreRef.STORE_REF_WORKSPACE_SPACESSTORE; + NodeRef rootNode = nodeService.getRootNode(storeRef); + + ChildAssociationRef contentChildAssocRef = createContentWithCategories(storeRef, rootNode); + + // Export + File acpFile = exportContent(contentChildAssocRef); + // Import - destination store is different from export store. + NodeRef destRootNode = nodeService.getRootNode(this.storeRef); + FileInfo importFolderFileInfo = importContent(acpFile, destRootNode); + + // Check categories + NodeRef importedFileNode = fileFolderService.searchSimple(importFolderFileInfo.getNodeRef(), "test.txt"); + assertNotNull("Couldn't find imported file: test.txt", importedFileNode); + assertTrue(nodeService.hasAspect(importedFileNode, ContentModel.ASPECT_GEN_CLASSIFIABLE)); + List importedFileCategories = (List) + nodeService.getProperty(importedFileNode, ContentModel.PROP_CATEGORIES); + assertEquals("No categories should have been imported for the content", 0, importedFileCategories.size()); + } + + /** + * @param contentChildAssocRef + * @return + * @throws FileNotFoundException + * @throws IOException + */ + private File exportContent(ChildAssociationRef contentChildAssocRef) + throws FileNotFoundException, IOException + { + TestProgress testProgress = new TestProgress(); + Location location = new Location(contentChildAssocRef.getParentRef()); + ExporterCrawlerParameters parameters = new ExporterCrawlerParameters(); + parameters.setExportFrom(location); + File acpFile = TempFileProvider.createTempFile("category-export-test", ACPExportPackageHandler.ACP_EXTENSION); + System.out.println("Exporting to file: " + acpFile.getAbsolutePath()); + File dataFile = new File("test-data-file"); + File contentDir = new File("test-content-dir"); + OutputStream fos = new FileOutputStream(acpFile); + ACPExportPackageHandler acpHandler = new ACPExportPackageHandler(fos, dataFile, contentDir, null); + acpHandler.setNodeService(nodeService); + acpHandler.setExportAsFolders(true); + exporterService.exportView(acpHandler, parameters, testProgress); + fos.close(); + return acpFile; + } + + /** + * @param storeRef + * @param rootNode + * @return + */ + private ChildAssociationRef createContentWithCategories(StoreRef storeRef, NodeRef rootNode) + { + Collection assocRefs = categoryService. + getRootCategories(storeRef, ContentModel.ASPECT_GEN_CLASSIFIABLE); + assertTrue("Pre-condition failure: not enough categories", assocRefs.size() >= 2); + Iterator it = assocRefs.iterator(); + NodeRef softwareDocCategoryNode = it.next().getChildRef(); + it.next(); // skip one + NodeRef regionsCategoryNode = it.next().getChildRef(); + + + // Create a content node to categorise + FileInfo exportFileInfo = fileFolderService.create(rootNode, "Export Folder", ContentModel.TYPE_FOLDER); + Map properties = Collections.singletonMap(ContentModel.PROP_NAME, (Serializable)"test.txt"); + ChildAssociationRef contentChildAssocRef = nodeService.createNode( + exportFileInfo.getNodeRef(), + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_CONTENT, + properties); + + NodeRef contentNodeRef = contentChildAssocRef.getChildRef(); + + // Attach categories + ArrayList categories = new ArrayList(2); + categories.add(softwareDocCategoryNode); + categories.add(regionsCategoryNode); + if(!nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_GEN_CLASSIFIABLE)) + { + HashMap props = new HashMap(); + props.put(ContentModel.PROP_CATEGORIES, categories); + nodeService.addAspect(contentNodeRef, ContentModel.ASPECT_GEN_CLASSIFIABLE, props); + } + else + { + nodeService.setProperty(contentNodeRef, ContentModel.PROP_CATEGORIES, categories); + } + return contentChildAssocRef; + } + + /** + * @param acpFile + * @param destRootNode + * @return + */ + private FileInfo importContent(File acpFile, NodeRef destRootNode) + { + FileInfo importFolderFileInfo = fileFolderService.create(destRootNode, "Import Folder", ContentModel.TYPE_FOLDER); + ImportPackageHandler importHandler = new ACPImportPackageHandler(acpFile, ACPImportPackageHandler.DEFAULT_ENCODING); + importerService.importView(importHandler, new Location(importFolderFileInfo.getNodeRef()), null, null); + return importFolderFileInfo; + } + + private void assertCategoriesEqual(List categories, String... expectedCategoryNames) + { + assertEquals("Number of categories is not as expected.", expectedCategoryNames.length, categories.size()); + + List categoryNames = new ArrayList(10); + for (NodeRef nodeRef : categories) + { + String name = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + categoryNames.add(name); + } + // Sort, to give deterministic test results + Collections.sort(categoryNames); + + for (int i = 0; i < expectedCategoryNames.length; i++) + { + assertEquals(expectedCategoryNames[i], categoryNames.get(i)); + } + } + + private void dumpNodeStore(Locale locale) { diff --git a/source/java/org/alfresco/repo/exporter/ViewXMLExporter.java b/source/java/org/alfresco/repo/exporter/ViewXMLExporter.java index 9c8e3e7222..830b2340cc 100644 --- a/source/java/org/alfresco/repo/exporter/ViewXMLExporter.java +++ b/source/java/org/alfresco/repo/exporter/ViewXMLExporter.java @@ -22,6 +22,7 @@ import java.io.InputStream; import java.util.List; import java.util.Locale; +import org.alfresco.model.ContentModel; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; @@ -560,7 +561,18 @@ import org.xml.sax.helpers.AttributesImpl; NodeRef valueNodeRef = (NodeRef)value; if (nodeRef.getStoreRef().equals(valueNodeRef.getStoreRef())) { - Path nodeRefPath = createPath(context.getExportParent(), nodeRef, valueNodeRef); + Path nodeRefPath = null; + if (property.equals(ContentModel.PROP_CATEGORIES)) + { + // Special case for categories - use the full path so that categories + // can be successfully assigned to imported content (provided the same store + // was used for both import and export and the categories still exist). + nodeRefPath = nodeService.getPath(valueNodeRef); + } + else + { + nodeRefPath = createPath(context.getExportParent(), nodeRef, valueNodeRef); + } value = (nodeRefPath == null) ? null : nodeRefPath.toPrefixString(namespaceService); } } diff --git a/source/java/org/alfresco/repo/forms/FormServiceImplTest.java b/source/java/org/alfresco/repo/forms/FormServiceImplTest.java index d321ea6ac5..f26e3d1ce4 100644 --- a/source/java/org/alfresco/repo/forms/FormServiceImplTest.java +++ b/source/java/org/alfresco/repo/forms/FormServiceImplTest.java @@ -1703,6 +1703,44 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest assertNotNull(form); } + public void testNonContentNode() throws Exception + { + // create a node (not cm:content) + Map nodeProps = new HashMap(1); + String nodeName = "testNode" + GUID.generate(); + nodeProps.put(ContentModel.PROP_NAME, nodeName); + NodeRef node = this.nodeService.createNode( + this.folder, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, nodeName), + ContentModel.TYPE_CMOBJECT, + nodeProps).getChildRef(); + + List fields = new ArrayList(8); + fields.add("cm:name"); + fields.add("cm:creator"); + fields.add("cm:created"); + + Form form = this.formService.getForm(new Item(NODE_FORM_ITEM_KIND, node.toString()), fields); + + // check a form got returned + assertNotNull("Expecting form to be present", form); + + // check fields were returned + List fieldNames = form.getFieldDefinitionNames(); + assertEquals(3, fieldNames.size()); + + // rename the node via the forms service + FormData data = new FormData(); + String newName = "new-" + nodeName; + data.addFieldData("prop_cm_name", newName); + this.formService.saveForm(new Item(NODE_FORM_ITEM_KIND, node.toString()), data); + + Map updatedProps = this.nodeService.getProperties(node); + String updatedName = (String)updatedProps.get(ContentModel.PROP_NAME); + assertEquals(newName, updatedName); + } + public void testJavascriptAPI() throws Exception { Map model = new HashMap(); diff --git a/source/java/org/alfresco/repo/forms/processor/node/ContentModelFormProcessor.java b/source/java/org/alfresco/repo/forms/processor/node/ContentModelFormProcessor.java index acc5f5ae77..4fb746c6cd 100644 --- a/source/java/org/alfresco/repo/forms/processor/node/ContentModelFormProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/node/ContentModelFormProcessor.java @@ -55,6 +55,7 @@ import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.model.FileExistsException; import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -330,7 +331,7 @@ public abstract class ContentModelFormProcessor extends // requirements if (fullQName.equals(ContentModel.PROP_NAME)) { - processNamePropertyPersist(nodeRef, fieldData); + processNamePropertyPersist(nodeRef, fieldData, propsToPersist); } else if (propDef.getDataType().getName().equals(DataTypeDefinition.CONTENT)) { @@ -592,22 +593,35 @@ public abstract class ContentModelFormProcessor extends * * @param nodeRef The NodeRef to update the name for * @param fieldData The data representing the new name value + * @param propsToPersist Map of properties to be persisted */ - protected void processNamePropertyPersist(NodeRef nodeRef, FieldData fieldData) + protected void processNamePropertyPersist(NodeRef nodeRef, FieldData fieldData, + Map propsToPersist) { - try + // determine whether the file folder service can handle the current node + FileInfo fileInfo = this.fileFolderService.getFileInfo(nodeRef); + if (fileInfo != null) { - // if the name property changes the rename method of the file folder - // service should be called rather than updating the property directly - this.fileFolderService.rename(nodeRef, (String) fieldData.getValue()); + try + { + // if the name property changes the rename method of the file folder + // service should be called rather than updating the property directly + this.fileFolderService.rename(nodeRef, (String) fieldData.getValue()); + } + catch (FileExistsException fee) + { + throw new FormException("Failed to persist field '" + fieldData.getName() + "'", fee); + } + catch (FileNotFoundException fnne) + { + throw new FormException("Failed to persist field '" + fieldData.getName() + "'", fnne); + } } - catch (FileExistsException fee) + else { - throw new FormException("Failed to persist field '" + fieldData.getName() + "'", fee); - } - catch (FileNotFoundException fnne) - { - throw new FormException("Failed to persist field '" + fieldData.getName() + "'", fnne); + // as the file folder service can not be used just set the name property, + // the node service will deal with the details of renaming. + propsToPersist.put(ContentModel.PROP_NAME, (Serializable)fieldData.getValue()); } } diff --git a/source/java/org/alfresco/repo/forms/processor/node/PropertyFieldProcessor.java b/source/java/org/alfresco/repo/forms/processor/node/PropertyFieldProcessor.java index 286c030d7b..80098f7ba1 100644 --- a/source/java/org/alfresco/repo/forms/processor/node/PropertyFieldProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/node/PropertyFieldProcessor.java @@ -41,6 +41,7 @@ import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.Period; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; @@ -148,6 +149,11 @@ public class PropertyFieldProcessor extends QNameFieldProcessor results = new ArrayList(10); + ArrayList results = new ArrayList(10); // get the primary path Path path = nodeService.getPath(nodeRef); // iterate and turn the results into file info objects @@ -1289,6 +1289,14 @@ public class FileFolderServiceImpl implements FileFolderService } // we found the root and expect to be building the path up FileInfo pathInfo = toFileInfo(childNodeRef, true); + + // we can't append a path element to the results if there is already a (non-folder) file at the tail + // since this would result in a path anomoly - file's cannot contain other files. + if (!results.isEmpty() && !results.get(results.size()-1).isFolder()) + { + throw new InvalidTypeException( + "File is not the last element in path: files cannot contain other files."); + } results.add(pathInfo); } // check that we found the root diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java index d0857bc0f3..c60be12de3 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java @@ -41,6 +41,7 @@ import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.M2Model; import org.alfresco.repo.dictionary.M2Type; +import org.alfresco.repo.model.filefolder.FileFolderServiceImpl.InvalidTypeException; import org.alfresco.repo.node.integrity.IntegrityChecker; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; @@ -828,6 +829,68 @@ public class FileFolderServiceImplTest extends TestCase } } + + public void testGetNamePathDoesNotReturnPathContainingNonLeafFileNode() throws Exception + { + FileInfo parentFolderInfo = getByName(NAME_L0_FOLDER_A, true); + assertNotNull(parentFolderInfo); + NodeRef parentFolderRef = parentFolderInfo.getNodeRef(); + + // create hierarchy: folder > file + FileInfo dirInfo = fileFolderService.create(parentFolderRef, "newDir", ContentModel.TYPE_FOLDER); + FileInfo fileInfo = fileFolderService.create(dirInfo.getNodeRef(), "newFile", ContentModel.TYPE_CONTENT); + // generate a path where the file is the last element: ok + List path = fileFolderService.getNamePath(parentFolderRef, fileInfo.getNodeRef()); + assertEquals(2, path.size()); + + + // create hierarchy: folder > file > file + FileInfo fileInfo2 = fileFolderService.create(fileInfo.getNodeRef(), "newFile2", ContentModel.TYPE_CONTENT); + // generate a path where a file is not the last element in the path: not ok + try + { + fileFolderService.getNamePath(parentFolderRef, fileInfo2.getNodeRef()); + fail("Shouldn't create path for non-leaf file."); + } + catch(InvalidTypeException e) + { + // Good + } + } + + + public void testGetNamePathDoesNotCrossIntoNonFileFolderHierarchy() throws Exception + { + FileInfo parentFolderInfo = getByName(NAME_L0_FOLDER_A, true); + assertNotNull(parentFolderInfo); + NodeRef parentFolderRef = parentFolderInfo.getNodeRef(); + + // create hierarchy: folder > file + FileInfo dirInfo = fileFolderService.create(parentFolderRef, "newDir", ContentModel.TYPE_FOLDER); + FileInfo fileInfo = fileFolderService.create(dirInfo.getNodeRef(), "newFile", ContentModel.TYPE_CONTENT); + // generate a path where the file is the last element: ok + List path = fileFolderService.getNamePath(parentFolderRef, fileInfo.getNodeRef()); + assertEquals(2, path.size()); + + NodeRef cmContainer = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.ALFRESCO_URI, "container"), + ContentModel.TYPE_CONTAINER).getChildRef(); + + NodeRef cmChild = nodeService.moveNode( + fileInfo.getNodeRef(), + cmContainer, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.ALFRESCO_URI, "contains")).getChildRef(); + + // This is ok, since the root - whilst not a folder - directly contains a file + List path2 = fileFolderService.getNamePath(cmContainer, cmChild); + assertEquals(1, path2.size()); + assertEquals("newFile", path2.get(0).getName()); + } + + public void testSearchSimple() throws Exception { FileInfo folderInfo = getByName(NAME_L0_FOLDER_A, true); diff --git a/source/java/org/alfresco/repo/model/filefolder/MLTranslationInterceptor.java b/source/java/org/alfresco/repo/model/filefolder/MLTranslationInterceptor.java index c10f28ec2d..f34e3389c5 100644 --- a/source/java/org/alfresco/repo/model/filefolder/MLTranslationInterceptor.java +++ b/source/java/org/alfresco/repo/model/filefolder/MLTranslationInterceptor.java @@ -78,6 +78,7 @@ public class MLTranslationInterceptor implements MethodInterceptor METHOD_NAMES_SINGLE.add("searchSimple"); METHOD_NAMES_SINGLE.add("rename"); METHOD_NAMES_SINGLE.add("move"); + METHOD_NAMES_SINGLE.add("moveFrom"); METHOD_NAMES_SINGLE.add("copy"); METHOD_NAMES_SINGLE.add("create"); METHOD_NAMES_SINGLE.add("makeFolders"); diff --git a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java index 8e2277e4ea..d742db2e14 100644 --- a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java @@ -19,7 +19,6 @@ package org.alfresco.repo.node; import java.io.Serializable; -import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -98,7 +97,7 @@ public abstract class AbstractNodeServiceImpl implements NodeService protected DictionaryService dictionaryService; protected TransactionService transactionService; protected TenantService tenantService; - protected List storesToIgnorePolicies = new ArrayList(0); + protected Set storesToIgnorePolicies = Collections.emptySet(); /* * Policy delegates @@ -154,7 +153,7 @@ public abstract class AbstractNodeServiceImpl implements NodeService this.tenantService = tenantService; } - public void setStoresToIgnorePolicies(List storesToIgnorePolicies) + public void setStoresToIgnorePolicies(Set storesToIgnorePolicies) { this.storesToIgnorePolicies = storesToIgnorePolicies; } diff --git a/source/java/org/alfresco/repo/ownable/impl/OwnableServiceImpl.java b/source/java/org/alfresco/repo/ownable/impl/OwnableServiceImpl.java index 41a5d05a61..3610f2a896 100644 --- a/source/java/org/alfresco/repo/ownable/impl/OwnableServiceImpl.java +++ b/source/java/org/alfresco/repo/ownable/impl/OwnableServiceImpl.java @@ -19,8 +19,10 @@ package org.alfresco.repo.ownable.impl; import java.io.Serializable; +import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.repo.cache.SimpleCache; @@ -33,13 +35,13 @@ import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.policy.Behaviour.NotificationFrequency; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.cmr.security.OwnableService; -import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.EqualsHelper; import org.alfresco.util.PropertyCheck; @@ -61,6 +63,8 @@ public class OwnableServiceImpl implements private AuthenticationService authenticationService; private SimpleCache nodeOwnerCache; private PolicyComponent policyComponent; + private TenantService tenantService; + private Set storesToIgnorePolicies = Collections.emptySet(); public OwnableServiceImpl() { @@ -84,6 +88,16 @@ public class OwnableServiceImpl implements this.policyComponent = policyComponent; } + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + public void setStoresToIgnorePolicies(Set storesToIgnorePolicies) + { + this.storesToIgnorePolicies = storesToIgnorePolicies; + } + /** * @param ownerCache * a transactionally-safe cache of node owners @@ -164,7 +178,7 @@ public class OwnableServiceImpl implements { userName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_CREATOR)); } - nodeOwnerCache.put(nodeRef, userName); + cacheOwner(nodeRef, userName); } return userName; @@ -182,7 +196,7 @@ public class OwnableServiceImpl implements { nodeService.setProperty(nodeRef, ContentModel.PROP_OWNER, userName); } - nodeOwnerCache.put(nodeRef, userName); + cacheOwner(nodeRef, userName); } public void takeOwnership(NodeRef nodeRef) @@ -241,6 +255,16 @@ public class OwnableServiceImpl implements return AuditableOwnableAspectCopyBehaviourCallback.INSTANCE; } + private void cacheOwner(NodeRef nodeRef, String userName) + { + // do not cache owners of nodes that are from stores that ignores policies + // to prevent mess in nodeOwnerCache + if (!storesToIgnorePolicies.contains(tenantService.getBaseName(nodeRef.getStoreRef()).toString())) + { + nodeOwnerCache.put(nodeRef, userName); + } + } + /** * Extends the default copy behaviour to prevent copying of some ownable and * auditable properties, but lets the aspects themselves go through. diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java index 188ea2c239..a6ed2198ba 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java @@ -386,7 +386,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener mlText.addValue(Locale.GERMAN, "banane"); mlText.addValue(new Locale("el"), "μπανάνα"); mlText.addValue(Locale.ITALIAN, "banana"); - mlText.addValue(new Locale("ja"), "バナナ"); + mlText.addValue(new Locale("ja"), "?ナナ"); mlText.addValue(new Locale("ko"), "바나나"); mlText.addValue(new Locale("pt"), "banana"); mlText.addValue(new Locale("ru"), "банан"); @@ -4191,6 +4191,9 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/one//thirteen/fourteen\"", null); assertEquals(1, results.length()); results.close(); + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/one//thirteen/fourteen/.\"", null); + assertEquals(1, results.length()); + results.close(); results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/one//thirteen/fourteen//.\"", null); assertEquals(1, results.length()); results.close(); @@ -6088,7 +6091,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener sp = new SearchParameters(); sp.addStore(rootNodeRef.getStoreRef()); sp.setLanguage("lucene"); - sp.setQuery("@" + LuceneQueryParser.escape(mlQName.toString()) + ":バナナ"); + sp.setQuery("@" + LuceneQueryParser.escape(mlQName.toString()) + ":?ナナ"); sp.addLocale(new Locale("ja")); results = searcher.query(sp); assertEquals(1, results.length()); 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 be4c6b18f9..62ab001d79 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 @@ -1386,17 +1386,12 @@ public class IndexInfo implements IndexMonitor s_logger.debug("Main index reader references = " + ((ReferenceCounting) mainIndexReader).getReferenceCount()); } - // Prevent close calls from really closing the main reader - return new FilterIndexReader(mainIndexReader) - { - - @Override - protected void doClose() throws IOException - { - in.decRef(); - } - - }; + // ALF-10040: Wrap with a one-off CachingIndexReader (with cache disabled) so that LeafScorer behaves and passes through SingleFieldSelectors to the main index readers + IndexReader reader = ReferenceCountingReadOnlyIndexReaderFactory.createReader(MAIN_READER + GUID.generate(), mainIndexReader, false, config); + ReferenceCounting refCounting = (ReferenceCounting) reader; + reader.incRef(); + refCounting.setInvalidForReuse(); + return reader; } catch (RuntimeException e) { diff --git a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java b/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java index af3937bb54..48c1d4ef90 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java @@ -585,6 +585,14 @@ public class ACLEntryAfterInvocationProvider implements AfterInvocationProvider, .getSearchParameters())); } } + if (returnedObject.length() > 0) + { + //force prefetch before starting record time + boolean builkFetch = returnedObject.getBulkFetch(); + returnedObject.setBulkFetch(false); + returnedObject.getNodeRef(returnedObject.length() - 1); + returnedObject.setBulkFetch(builkFetch); + } // record the start time long startTimeMillis = System.currentTimeMillis(); @@ -715,6 +723,14 @@ public class ACLEntryAfterInvocationProvider implements AfterInvocationProvider, } } + if (returnedObject.length() > 0) + { + // force prefetch before starting record time + boolean builkFetch = returnedObject.getBulkFetch(); + returnedObject.setBulkFetch(false); + returnedObject.getNodeRef(returnedObject.length() - 1); + returnedObject.setBulkFetch(builkFetch); + } // record the start time long startTimeMillis = System.currentTimeMillis(); diff --git a/source/java/org/alfresco/repo/site/SiteServiceImpl.java b/source/java/org/alfresco/repo/site/SiteServiceImpl.java index d2dc6083c6..89965ab0a1 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImpl.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImpl.java @@ -463,7 +463,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic properties.put(ContentModel.PROP_TITLE, title); properties.put(ContentModel.PROP_DESCRIPTION, description); - final NodeRef siteNodeRef = AuthenticationUtil.runAsSystem(new RunAsWork() { + final NodeRef siteNodeRef = AuthenticationUtil.runAs(new RunAsWork() { @Override public NodeRef doWork() throws Exception { return nodeService.createNode( @@ -474,7 +474,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic properties ).getChildRef(); } - }); + }, AuthenticationUtil.getSystemUserName()); // Make the new site a tag scope this.taggingService.addTagScope(siteNodeRef); diff --git a/source/java/org/alfresco/repo/template/TemplateNode.java b/source/java/org/alfresco/repo/template/TemplateNode.java index 77cd1ea346..4c4717e1a4 100644 --- a/source/java/org/alfresco/repo/template/TemplateNode.java +++ b/source/java/org/alfresco/repo/template/TemplateNode.java @@ -22,15 +22,22 @@ import java.io.Serializable; import java.io.StringReader; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.admin.SysAdminParams; import org.alfresco.repo.admin.SysAdminParamsImpl; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.audit.AuditQueryParameters; +import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.lock.LockStatus; import org.alfresco.service.cmr.repository.AssociationRef; @@ -528,6 +535,77 @@ public class TemplateNode extends BasePermissionsNode implements NamespacePrefix return new NodeSearchResultsMap(this, this.services); } + // ------------------------------------------------------------------------------ + // Audit API + + /** + * @return a list of AuditInfo objects describing the Audit Trail for this node instance + */ + public List getAuditTrail() + { + final List result = new ArrayList(); + + // create the callback for auditQuery method + final AuditQueryCallback callback = new AuditQueryCallback() + { + public boolean valuesRequired() + { + return true; + } + + public boolean handleAuditEntryError(Long entryId, String errorMsg, Throwable error) + { + throw new AlfrescoRuntimeException("Failed to retrieve audit data.", error); + } + + public boolean handleAuditEntry(Long entryId, String applicationName, String user, long time, + Map values) + { + TemplateAuditInfo auditInfo = new TemplateAuditInfo(applicationName, user, time, values); + result.add(auditInfo); + return true; + } + }; + + // resolve the path of the node + final String nodePath = services.getNodeService().getPath(this.nodeRef).toPrefixString(services.getNamespaceService()); + + // run as admin user to allow everyone to see audit information + // (new 3.4 API doesn't allow this by default) + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + String applicationName = "alfresco-access"; + AuditQueryParameters pathParams = new AuditQueryParameters(); + pathParams.setApplicationName(applicationName); + pathParams.addSearchKey("/alfresco-access/transaction/path", nodePath); + services.getAuditService().auditQuery(callback, pathParams, -1); + + AuditQueryParameters copyFromPathParams = new AuditQueryParameters(); + copyFromPathParams.setApplicationName(applicationName); + copyFromPathParams.addSearchKey("/alfresco-access/transaction/copy/from/path", nodePath); + services.getAuditService().auditQuery(callback, copyFromPathParams, -1); + + AuditQueryParameters moveFromPathParams = new AuditQueryParameters(); + moveFromPathParams.setApplicationName(applicationName); + moveFromPathParams.addSearchKey("/alfresco-access/transaction/move/from/path", nodePath); + services.getAuditService().auditQuery(callback, moveFromPathParams, -1); + return null; + } + }, AuthenticationUtil.getAdminUserName()); + + // sort audit entries by time of generation + Collections.sort(result, new Comparator() + { + public int compare(TemplateAuditInfo o1, TemplateAuditInfo o2) + { + return o1.getDate().compareTo(o2.getDate()); + } + }); + return result; + } + // ------------------------------------------------------------------------------ // Misc helpers @@ -623,4 +701,45 @@ public class TemplateNode extends BasePermissionsNode implements NamespacePrefix } } } + + public class TemplateAuditInfo + { + private String applicationName; + private String userName; + private long time; + private Map values; + + public TemplateAuditInfo(String applicationName, String userName, long time, Map values) + { + this.applicationName = applicationName; + this.userName = userName; + this.time = time; + this.values = values; + } + + public String getAuditApplication() + { + return this.applicationName; + } + + public String getUserIdentifier() + { + return this.userName; + } + + public Date getDate() + { + return new Date(time); + } + + public String getAuditMethod() + { + return this.values.get("/alfresco-access/transaction/action").toString(); + } + + public Map getValues() + { + return this.values; + } + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/version/VersionableAspect.java b/source/java/org/alfresco/repo/version/VersionableAspect.java index 515dc0aa45..ac5266317d 100644 --- a/source/java/org/alfresco/repo/version/VersionableAspect.java +++ b/source/java/org/alfresco/repo/version/VersionableAspect.java @@ -100,6 +100,9 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate private Set excludedOnUpdatePropQNames = Collections.emptySet(); + /** flag indicating whether auto-versioning should be enabled or not */ + private boolean enableAutoVersioning = true; + /** * Set the policy component * @@ -167,12 +170,28 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate { this.excludedOnUpdateProps = Collections.unmodifiableList(excludedOnUpdateProps); } - + + /** + * Set whether the aspect-associated behaviour should be enabled or disabled. This is only used + * during {@link #init() initialization}. + * + * @param enableAutoVersioning true to enable the aspect behaviour otherwise false + */ + public void setEnableAutoVersioning(boolean enableAutoVersioning) + { + this.enableAutoVersioning = enableAutoVersioning; + } + /** * Initialise the versionable aspect policies */ public void init() { + if (!enableAutoVersioning) + { + return; + } + this.policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), ContentModel.ASPECT_VERSIONABLE, diff --git a/source/java/org/alfresco/service/cmr/avm/deploy/DeploymentService.java b/source/java/org/alfresco/service/cmr/avm/deploy/DeploymentService.java index 3fa8258fe5..7078884e43 100644 --- a/source/java/org/alfresco/service/cmr/avm/deploy/DeploymentService.java +++ b/source/java/org/alfresco/service/cmr/avm/deploy/DeploymentService.java @@ -21,8 +21,8 @@ package org.alfresco.service.cmr.avm.deploy; import java.util.Set; import java.util.List; -import org.alfresco.service.PublicService; import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.util.NameMatcher; import org.alfresco.service.Auditable; import org.alfresco.service.NotAuditable; @@ -112,4 +112,17 @@ public interface DeploymentService @NotAuditable public Set getAdapterNames(); + /* + * @param webProjectRef Web project reference. + * */ + @NotAuditable + public List findLiveDeploymentServers(NodeRef webProjectRef); + + + /* + * @param webProjectRef Web project reference. + * @param availableOnly find available servers only. + * */ + @NotAuditable + public List findTestDeploymentServers(NodeRef webProjectRef, boolean availableOnly); }