diff --git a/.externalToolBuilders/JibX.launch b/.externalToolBuilders/JibX.launch index 186d90d901..209a3cdd97 100644 --- a/.externalToolBuilders/JibX.launch +++ b/.externalToolBuilders/JibX.launch @@ -1,34 +1,34 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/activiti-context.xml b/config/alfresco/activiti-context.xml index c91dc97b24..691440e49a 100644 --- a/config/alfresco/activiti-context.xml +++ b/config/alfresco/activiti-context.xml @@ -60,6 +60,9 @@ + + ${system.workflow.deployWorkflowsInTenant} + @@ -90,7 +93,7 @@ - + @@ -123,13 +126,12 @@ - + - - + @@ -228,12 +230,6 @@ factory-method="getManagementService" /> - - - - - - + diff --git a/config/alfresco/application-context.xml b/config/alfresco/application-context.xml index 4506f69de6..3c046a0300 100644 --- a/config/alfresco/application-context.xml +++ b/config/alfresco/application-context.xml @@ -16,6 +16,9 @@ --> + + + diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index a58806787c..43008c4aef 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -835,4 +835,10 @@ + + + + + diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml index 155df3f97a..92ea231226 100644 --- a/config/alfresco/content-services-context.xml +++ b/config/alfresco/content-services-context.xml @@ -367,7 +367,7 @@ - + cm:expiryDate sys:clientVisibilityMask + cm:lastThumbnailModification + cm:likesRatingSchemeTotal + cm:likesRatingSchemeCount + cm:fiveStarRatingSchemeCount + cm:fiveStarRatingSchemeTotal + + fm:commentCount diff --git a/config/alfresco/hazelcast/hazelcast-ec2.xml b/config/alfresco/hazelcast/hazelcast-ec2.xml new file mode 100644 index 0000000000..b4a3f8b7bd --- /dev/null +++ b/config/alfresco/hazelcast/hazelcast-ec2.xml @@ -0,0 +1,183 @@ + + + + ${alfresco.cluster.name} + ${alfresco.hazelcast.password} + + + 5701 + + + 224.2.2.3 + 54327 + + + 127.0.0.1 + + + ${alfresco.hazelcast.ec2.accesskey} + ${alfresco.hazelcast.ec2.secretkey} + + ${alfresco.hazelcast.ec2.region} + + ${alfresco.hazelcast.ec2.securitygroup} + ${alfresco.hazelcast.ec2.tagkey} + ${alfresco.hazelcast.ec2.tagvalue} + + + + 10.10.1.* + + + + PBEWithMD5AndDES + + thesalt + + thepass + + 19 + + + + RSA/NONE/PKCS1PADDING + + thekeypass + + local + + JKS + + thestorepass + + keystore + + + + 16 + 64 + 60 + + + + 0 + + default + + + + 1 + + 0 + + 0 + + NONE + + 0 + + 25 + + hz.ADD_NEW_ENTRY + + + + + 1 + ${alfresco.hazelcast.conccurrentuser.timeToLive} + 0 + LRU + 0 + 25 + hz.ADD_NEW_ENTRY + 15 + + + + + + + + log4j + false + false + false + + + \ No newline at end of file diff --git a/config/alfresco/hazelcast/hazelcast-tcp.xml b/config/alfresco/hazelcast/hazelcast-tcp.xml new file mode 100644 index 0000000000..8fa5a13238 --- /dev/null +++ b/config/alfresco/hazelcast/hazelcast-tcp.xml @@ -0,0 +1,180 @@ + + + + ${alfresco.cluster.name} + ${alfresco.hazelcast.password} + + + 5701 + + + 224.2.2.3 + 54327 + + + ${alfresco.hazelcast.tcp.config} + 127.0.0.1 + + + my-access-key + my-secret-key + + us-west-1 + + hazelcast-sg + type + hz-nodes + + + + 10.10.1.* + + + + PBEWithMD5AndDES + + thesalt + + thepass + + 19 + + + + RSA/NONE/PKCS1PADDING + + thekeypass + + local + + JKS + + thestorepass + + keystore + + + + 16 + 64 + 60 + + + + 0 + + default + + + + 1 + + 0 + + 0 + + NONE + + 0 + + 25 + + hz.ADD_NEW_ENTRY + + + + + 1 + ${alfresco.hazelcast.conccurrentuser.timeToLive} + 0 + LRU + 0 + 25 + hz.ADD_NEW_ENTRY + 15 + + + + + + + + log4j + + \ No newline at end of file diff --git a/config/alfresco/hazelcast/hazelcast-udp.xml b/config/alfresco/hazelcast/hazelcast-udp.xml new file mode 100644 index 0000000000..4b6765eda9 --- /dev/null +++ b/config/alfresco/hazelcast/hazelcast-udp.xml @@ -0,0 +1,179 @@ + + + + ${alfresco.cluster.name} + ${alfresco.hazelcast.password} + + + 5701 + + + 224.2.2.3 + 54327 + + + 127.0.0.1 + + + my-access-key + my-secret-key + + us-west-1 + + hazelcast-sg + type + hz-nodes + + + + 10.10.1.* + + + + PBEWithMD5AndDES + + thesalt + + thepass + + 19 + + + + RSA/NONE/PKCS1PADDING + + thekeypass + + local + + JKS + + thestorepass + + keystore + + + + 16 + 64 + 60 + + + + 0 + + default + + + + 1 + + 0 + + 0 + + NONE + + 0 + + 25 + + hz.ADD_NEW_ENTRY + + + + + 1 + ${alfresco.hazelcast.conccurrentuser.timeToLive} + 0 + LRU + 0 + 25 + hz.ADD_NEW_ENTRY + 15 + + + + + + + + log4j + + \ No newline at end of file diff --git a/config/alfresco/mt/mt-base-context.xml b/config/alfresco/mt/mt-base-context.xml index 562a9fb310..2983ce4ccc 100644 --- a/config/alfresco/mt/mt-base-context.xml +++ b/config/alfresco/mt/mt-base-context.xml @@ -33,10 +33,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -213,12 +66,136 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/tx-cache-context.xml b/config/alfresco/tx-cache-context.xml index 7c0699742b..fc9fc48918 100644 --- a/config/alfresco/tx-cache-context.xml +++ b/config/alfresco/tx-cache-context.xml @@ -575,7 +575,7 @@ - org.alfresco.tenantsTransactionalCache + org.alfresco.cache.tenantEntityTransactionalCache diff --git a/config/alfresco/webdav-context.xml b/config/alfresco/webdav-context.xml index 5d938f577e..a45954e5ae 100644 --- a/config/alfresco/webdav-context.xml +++ b/config/alfresco/webdav-context.xml @@ -25,7 +25,38 @@ - + + + + + + + + + + + ${alfresco.cluster.name} + ${alfresco.hazelcast.password} + ${alfresco.hazelcast.specify.interface} + ${alfresco.hazelcast.bind.interface} + + ${alfresco.hazelcast.tcp.config} + + ${alfresco.hazelcast.ec2.accesskey} + ${alfresco.hazelcast.ec2.secretkey} + ${alfresco.hazelcast.ec2.region} + ${alfresco.hazelcast.ec2.securitygroup} + ${alfresco.hazelcast.ec2.tagkey} + ${alfresco.hazelcast.ec2.tagvalue} + + ${alfresco.conccurrentusers.timeToLive} + + + + + + + \ No newline at end of file diff --git a/config/alfresco/workflow-context.xml b/config/alfresco/workflow-context.xml index ea9d1f7018..a1f9c6068b 100644 --- a/config/alfresco/workflow-context.xml +++ b/config/alfresco/workflow-context.xml @@ -37,6 +37,9 @@ ${system.workflow.maxPooledTasks} + + ${system.workflow.deployWorkflowsInTenant} + diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java index f7728011f7..3a32bfb062 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java @@ -19,6 +19,8 @@ package org.alfresco.opencmis; import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.InputStream; import java.io.Serializable; import java.math.BigInteger; @@ -69,6 +71,7 @@ import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.Pair; +import org.alfresco.util.TempFileProvider; import org.apache.chemistry.opencmis.commons.PropertyIds; import org.apache.chemistry.opencmis.commons.data.Acl; import org.apache.chemistry.opencmis.commons.data.AllowableActions; @@ -156,78 +159,6 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr objectInfoMap = new HashMap(); } - @Override - public void beforeCall() - { - AuthenticationUtil.pushAuthentication(); - if (authentication != null) - { - // Use the previously-obtained authentication - AuthenticationUtil.setFullAuthentication(authentication); - } - else - { - if (context == null) - { - // Service not opened, yet - return; - } - // Sticky sessions? - if (connector.openHttpSession()) - { - // create a session -> set a cookie - // if the CMIS client supports cookies that might help in clustered environments - ((HttpServletRequest) getContext().get(CallContext.HTTP_SERVLET_REQUEST)).getSession(); - } - - // Authenticate - if (authentication != null) - { - // We have already authenticated; just reuse the authentication - AuthenticationUtil.setFullAuthentication(authentication); - } - else - { - // First check if we already are authenticated - if (AuthenticationUtil.getFullyAuthenticatedUser() == null) - { - // We have to go to the repo and authenticate - String user = getContext().getUsername(); - String password = getContext().getPassword(); - Authorization auth = new Authorization(user, password); - if (auth.isTicket()) - { - connector.getAuthenticationService().validate(auth.getTicket()); - } - else - { - connector.getAuthenticationService().authenticate(auth.getUserName(), auth.getPasswordCharArray()); - } - } - this.authentication = AuthenticationUtil.getFullAuthentication(); - } - -// // TODO: How is the proxy user working. -// // Until we know what it is meant to do, it's not available -// String currentUser = connector.getAuthenticationService().getCurrentUserName(); -// String user = getContext().getUsername(); -// String password = getContext().getPassword(); -// if (currentUser != null && currentUser.equals(connector.getProxyUser())) -// { -// if (user != null && user.length() > 0) -// { -// AuthenticationUtil.setFullyAuthenticatedUser(user); -// } -// } - } - } - - @Override - public void afterCall() - { - AuthenticationUtil.popAuthentication(); - } - @Override public void open(CallContext context) { @@ -571,10 +502,10 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr { try { - if(connector.filter(child.getNodeRef())) - { - continue; - } + if(connector.filter(child.getNodeRef())) + { + continue; + } // create a child CMIS object CMISNodeInfo ni = createNodeInfo(child.getNodeRef()); @@ -711,19 +642,19 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr if(isFolder && type.getAlfrescoClass().equals(ContentModel.TYPE_SYSTEM_FOLDER)) { - continue; + continue; } - if(connector.isHidden(child.getChildRef())) - { - continue; - } + if(connector.isHidden(child.getChildRef())) + { + continue; + } - if(connector.filter(child.getChildRef())) - { - continue; - } - + if(connector.filter(child.getChildRef())) + { + continue; + } + // create a child CMIS object ObjectInFolderDataImpl object = new ObjectInFolderDataImpl(); CMISNodeInfo ni = createNodeInfo(child.getChildRef()); @@ -1000,11 +931,11 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr { for (NodeRef nodeRef : nodeRefs) { - // TODO - perhaps filter by path in the query instead? - if(connector.filter(nodeRef)) - { - continue; - } + // TODO - perhaps filter by path in the query instead? + if(connector.filter(nodeRef)) + { + continue; + } if (skipCounter > 0) { @@ -1197,7 +1128,10 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr throw new CmisConstraintException("This document type is not versionable!"); } - final Charset encoding = getEncoding(contentStream); + // copy stream to temp file + // OpenCMIS does this for us .... + final File tempFile = copyToTempFile(contentStream); + final Charset encoding = (tempFile == null ? null : getEncoding(tempFile, contentStream.getMimeType())); FileInfo fileInfo = connector.getFileFolderService().create( parentInfo.getNodeRef(), name, type.getAlfrescoClass()); @@ -1214,7 +1148,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr String mimeType = stripEncoding(contentStream.getMimeType()); writer.setMimetype(mimeType); writer.setEncoding(encoding.name()); - writer.putContent(contentStream.getStream()); + writer.putContent(tempFile); } connector.extractMetadata(nodeRef); @@ -1224,6 +1158,8 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr connector.applyVersioningState(nodeRef, versioningState); + removeTempFile(tempFile); + String objectId = connector.createObjectId(nodeRef); connector.getActivityPoster().postFileFolderAdded(fileInfo); @@ -1406,12 +1342,21 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr throw new CmisInvalidArgumentException("No content!"); } - final Charset encoding = getEncoding(contentStream); + // copy stream to temp file + final File tempFile = copyToTempFile(contentStream); + final Charset encoding = getEncoding(tempFile, contentStream.getMimeType()); - ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); - writer.setMimetype(contentStream.getMimeType()); - writer.setEncoding(encoding.name()); - writer.putContent(contentStream.getStream()); + try + { + ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); + writer.setMimetype(contentStream.getMimeType()); + writer.setEncoding(encoding.name()); + writer.putContent(tempFile); + } + finally + { + removeTempFile(tempFile); + } objectId.setValue(connector.createObjectId(nodeRef)); @@ -1583,9 +1528,9 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr if(info.hasPWC()) { - // is a checked out document. If a delete, don't allow unless checkout is canceled. If a cancel - // checkout, not allowed. - throw new CmisConstraintException( + // is a checked out document. If a delete, don't allow unless checkout is canceled. If a cancel + // checkout, not allowed. + throw new CmisConstraintException( "Could not delete/cancel checkout on the original checked out document"); } @@ -1936,7 +1881,9 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr final NodeRef nodeRef = info.getNodeRef(); final TypeDefinitionWrapper type = info.getType(); - final Charset encoding = getEncoding(contentStream); + // copy stream to temp file + final File tempFile = copyToTempFile(contentStream); + final Charset encoding = (tempFile == null ? null : getEncoding(tempFile, contentStream.getMimeType())); // check in // update PWC @@ -1952,7 +1899,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); writer.setMimetype(contentStream.getMimeType()); writer.setEncoding(encoding.name()); - writer.putContent(contentStream.getStream()); + writer.putContent(tempFile); } // check aspect @@ -1979,6 +1926,8 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr connector.getActivityPoster().postFileFolderUpdated(info.isFolder(), newNodeRef); objectId.setValue(connector.createObjectId(newNodeRef)); + + removeTempFile(tempFile); } @Override @@ -2454,15 +2403,15 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr protected String getGuid(String nodeId) { - int idx = nodeId.lastIndexOf("/"); - if(idx != -1) - { - return nodeId.substring(idx+1); - } - else - { - return nodeId; - } + int idx = nodeId.lastIndexOf("/"); + if(idx != -1) + { + return nodeId.substring(idx+1); + } + else + { + return nodeId; + } } /** @@ -2693,25 +2642,137 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr } } - private Charset getEncoding(ContentStream stream) + private Charset getEncoding(File tempFile, String mimeType) { Charset encoding = null; - if(stream != null) + try { - try - { - String mimeType = stream.getMimeType(); - InputStream tfis = new BufferedInputStream(stream.getStream()); - ContentCharsetFinder charsetFinder = connector.getMimetypeService().getContentCharsetFinder(); - encoding = charsetFinder.getCharset(tfis, mimeType); - tfis.close(); - } catch (Exception e) - { - throw new CmisStorageException("Unable to read content: " + e.getMessage(), e); - } + InputStream tfis = new BufferedInputStream(new FileInputStream(tempFile)); + ContentCharsetFinder charsetFinder = connector.getMimetypeService().getContentCharsetFinder(); + encoding = charsetFinder.getCharset(tfis, mimeType); + tfis.close(); + } catch (Exception e) + { + throw new CmisStorageException("Unable to read content: " + e.getMessage(), e); } return encoding; } + + private File copyToTempFile(ContentStream contentStream) + { + if (contentStream == null) + { + return null; + } + + File result = null; + try + { + result = TempFileProvider.createTempFile(contentStream.getStream(), "cmis", "content"); + } + catch (Exception e) + { + throw new CmisStorageException("Unable to store content: " + e.getMessage(), e); + } + + if ((contentStream.getLength() > -1) && (result == null || contentStream.getLength() != result.length())) + { + removeTempFile(result); + throw new CmisStorageException("Expected " + contentStream.getLength() + " bytes but retrieved " + + (result == null ? -1 :result.length()) + " bytes!"); + } + + return result; + } + + private void removeTempFile(File tempFile) + { + if (tempFile == null) + { + return; + } + + try + { + tempFile.delete(); + } + catch (Exception e) + { + // ignore - file will be removed by TempFileProvider + } + } + + @Override + public void beforeCall() + { + AuthenticationUtil.pushAuthentication(); + if (authentication != null) + { + // Use the previously-obtained authentication + AuthenticationUtil.setFullAuthentication(authentication); + } + else + { + if (context == null) + { + // Service not opened, yet + return; + } + // Sticky sessions? + if (connector.openHttpSession()) + { + // create a session -> set a cookie + // if the CMIS client supports cookies that might help in clustered environments + ((HttpServletRequest) getContext().get(CallContext.HTTP_SERVLET_REQUEST)).getSession(); + } + + // Authenticate + if (authentication != null) + { + // We have already authenticated; just reuse the authentication + AuthenticationUtil.setFullAuthentication(authentication); + } + else + { + // First check if we already are authenticated + if (AuthenticationUtil.getFullyAuthenticatedUser() == null) + { + // We have to go to the repo and authenticate + String user = context.getUsername(); + String password = context.getPassword(); + Authorization auth = new Authorization(user, password); + if (auth.isTicket()) + { + connector.getAuthenticationService().validate(auth.getTicket()); + } + else + { + connector.getAuthenticationService().authenticate(auth.getUserName(), auth.getPasswordCharArray()); + } + } + this.authentication = AuthenticationUtil.getFullAuthentication(); + } + +// // TODO: How is the proxy user working. +// // Until we know what it is meant to do, it's not available +// String currentUser = connector.getAuthenticationService().getCurrentUserName(); +// String user = getContext().getUsername(); +// String password = getContext().getPassword(); +// if (currentUser != null && currentUser.equals(connector.getProxyUser())) +// { +// if (user != null && user.length() > 0) +// { +// AuthenticationUtil.setFullyAuthenticatedUser(user); +// } +// } + } + } + + @Override + public void afterCall() + { + AuthenticationUtil.popAuthentication(); + } } diff --git a/source/java/org/alfresco/opencmis/CMISConnector.java b/source/java/org/alfresco/opencmis/CMISConnector.java index 1ac4dd6604..57636c0234 100644 --- a/source/java/org/alfresco/opencmis/CMISConnector.java +++ b/source/java/org/alfresco/opencmis/CMISConnector.java @@ -71,7 +71,6 @@ import org.alfresco.repo.security.permissions.impl.AccessPermissionImpl; import org.alfresco.repo.security.permissions.impl.ModelDAO; import org.alfresco.repo.tenant.TenantAdminService; import org.alfresco.repo.tenant.TenantDeployer; -import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.thumbnail.ThumbnailDefinition; import org.alfresco.repo.thumbnail.ThumbnailHelper; import org.alfresco.repo.thumbnail.ThumbnailRegistry; @@ -260,7 +259,6 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen private RenditionService renditionService; private FileFolderService fileFolderService; private TenantAdminService tenantAdminService; - private TenantService tenantService; private TransactionService transactionService; private AuthenticationService authenticationService; private PermissionService permissionService; @@ -307,15 +305,15 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen private ObjectFilter objectFilter; + // -------------------------------------------------------------- + // Configuration + // -------------------------------------------------------------- + public void setObjectFilter(ObjectFilter objectFilter) { this.objectFilter = objectFilter; } - // -------------------------------------------------------------- - // Configuration - // -------------------------------------------------------------- - /** * Sets the root store. * @@ -342,11 +340,6 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen return activityPoster; } - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - public void setHiddenAspect(HiddenAspect hiddenAspect) { this.hiddenAspect = hiddenAspect; diff --git a/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java b/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java index ee609d3328..38d690a50b 100644 --- a/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java +++ b/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java @@ -303,8 +303,20 @@ public class CMISNodeInfoImpl implements CMISNodeInfo { objecVariant = CMISObjectVariant.CURRENT_VERSION; } + + // Is it un-versioned, or currently versioned? + Version currentVersion = connector.getVersionService().getCurrentVersion(nodeRef); + if (currentVersion != null) + { + versionLabel = currentVersion.getVersionLabel(); + versionHistory = connector.getVersionService().getVersionHistory(nodeRef); + } + else + { + versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; + } + objectId = getGuid(currentNodeId) + CMISConnector.ID_SEPERATOR + CMISConnector.UNVERSIONED_VERSION_LABEL; - versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; currentObjectId = objectId; hasPWC = (connector.getLockService().getLockType(nodeRef) == LockType.READ_ONLY_LOCK); } else diff --git a/source/java/org/alfresco/repo/action/ActionTestSuite.java b/source/java/org/alfresco/repo/action/ActionTestSuite.java index e67e08da96..f2fdc2984f 100644 --- a/source/java/org/alfresco/repo/action/ActionTestSuite.java +++ b/source/java/org/alfresco/repo/action/ActionTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -18,9 +18,6 @@ */ package org.alfresco.repo.action; -import junit.framework.Test; -import junit.framework.TestSuite; - import org.alfresco.repo.action.evaluator.CompareMimeTypeEvaluatorTest; import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluatorTest; import org.alfresco.repo.action.evaluator.HasAspectEvaluatorTest; @@ -28,50 +25,48 @@ import org.alfresco.repo.action.evaluator.IsSubTypeEvaluatorTest; import org.alfresco.repo.action.executer.AddFeaturesActionExecuterTest; import org.alfresco.repo.action.executer.ContentMetadataEmbedderTest; import org.alfresco.repo.action.executer.ContentMetadataExtracterTest; +import org.alfresco.repo.action.executer.MailActionExecuterTest; import org.alfresco.repo.action.executer.RemoveFeaturesActionExecuterTest; import org.alfresco.repo.action.executer.SetPropertyValueActionExecuterTest; import org.alfresco.repo.action.executer.SpecialiseTypeActionExecuterTest; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; /** - * Version test suite + * Action test suite * * @author Roy Wetherall + * @author Alex Miller */ -public class ActionTestSuite extends TestSuite +@RunWith(Suite.class) +@SuiteClasses({ + ParameterDefinitionImplTest.class, + ActionDefinitionImplTest.class, + ActionConditionDefinitionImplTest.class, + ActionImplTest.class, + ActionConditionImplTest.class, + CompositeActionImplTest.class, + ActionServiceImplTest.class, + CompositeActionConditionImplTest.class, + + // Test evaluators + IsSubTypeEvaluatorTest.class, + ComparePropertyValueEvaluatorTest.class, + CompareMimeTypeEvaluatorTest.class, + HasAspectEvaluatorTest.class, + + // Test executors + SetPropertyValueActionExecuterTest.class, + AddFeaturesActionExecuterTest.class, + ContentMetadataExtracterTest.class, + ContentMetadataEmbedderTest.class, + SpecialiseTypeActionExecuterTest.class, + RemoveFeaturesActionExecuterTest.class, + ActionTrackingServiceImplTest.class, // intermittent - pending ALF-9773 & ALF-9774 + MailActionExecuterTest.class +}) +public class ActionTestSuite { - /** - * Creates the test suite - * - * @return the test suite - */ - public static Test suite() - { - TestSuite suite = new TestSuite(); - suite.addTestSuite(ParameterDefinitionImplTest.class); - suite.addTestSuite(ActionDefinitionImplTest.class); - suite.addTestSuite(ActionConditionDefinitionImplTest.class); - suite.addTestSuite(ActionImplTest.class); - suite.addTestSuite(ActionConditionImplTest.class); - suite.addTestSuite(CompositeActionImplTest.class); - suite.addTestSuite(ActionServiceImplTest.class); - suite.addTestSuite(CompositeActionConditionImplTest.class); - - // Test evaluators - suite.addTestSuite(IsSubTypeEvaluatorTest.class); - suite.addTestSuite(ComparePropertyValueEvaluatorTest.class); - suite.addTestSuite(CompareMimeTypeEvaluatorTest.class); - suite.addTestSuite(HasAspectEvaluatorTest.class); - - // Test executors - suite.addTestSuite(SetPropertyValueActionExecuterTest.class); - suite.addTestSuite(AddFeaturesActionExecuterTest.class); - suite.addTestSuite(ContentMetadataExtracterTest.class); - suite.addTestSuite(ContentMetadataEmbedderTest.class); - suite.addTestSuite(SpecialiseTypeActionExecuterTest.class); - suite.addTestSuite(RemoveFeaturesActionExecuterTest.class); - suite.addTestSuite(ActionTrackingServiceImplTest.class); - - return suite; - } } diff --git a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java index 76fc9d5f84..202eea48da 100644 --- a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java @@ -111,7 +111,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase /** * The java mail sender */ - private JavaMailSender javaMailSender; + private JavaMailSender mailService; /** * The Template service @@ -196,7 +196,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase */ public void setMailService(JavaMailSender javaMailSender) { - this.javaMailSender = javaMailSender; + this.mailService = javaMailSender; } /** @@ -243,7 +243,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { this.authorityService = authorityService; } - + /** * @param nodeService the NodeService to set. */ @@ -251,7 +251,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { this.nodeService = nodeService; } - + /** * @param tenantService the TenantService to set. */ @@ -259,7 +259,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { this.tenantService = tenantService; } - + /** * @param headerEncoding The mail header encoding to set. */ @@ -267,7 +267,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { this.headerEncoding = headerEncoding; } - + /** * @param fromAddress The default mail address. */ @@ -307,6 +307,12 @@ public class MailActionExecuter extends ActionExecuterAbstractBase this.sendTestMessage = sendTestMessage; } + /** + * This stores an email address which, if it is set, overrides ALL email recipients sent from + * this class. It is intended for dev/test usage only !! + */ + private String testModeRecipient; + /** * Send a test message * @@ -334,11 +340,8 @@ public class MailActionExecuter extends ActionExecuterAbstractBase Action ruleAction = serviceRegistry.getActionService().createAction(NAME, params); - // TODO review & test converged code (and remove comment below) !! - prepareAndSendEmail(ruleAction, null); - - /* - MimeMessageHelper message = prepareEmail(ruleAction, null); + MimeMessageHelper message = prepareEmail(ruleAction, null, + new Pair(testMessageTo, getLocaleForUser(testMessageTo)), getFrom(ruleAction)); try { mailService.send(message.getMimeMessage()); @@ -362,17 +365,10 @@ public class MailActionExecuter extends ActionExecuterAbstractBase Object[] args = {testMessageTo, txt.toString()}; throw new AlfrescoRuntimeException("email.outbound.err.send.failed", args, me); } - */ return true; } - /** - * This stores an email address which, if it is set, overrides ALL email recipients sent from - * this class. It is intended for dev/test usage only !! - */ - private String testModeRecipient; - public void setTestModeRecipient(String testModeRecipient) { this.testModeRecipient = testModeRecipient; @@ -503,8 +499,13 @@ public class MailActionExecuter extends ActionExecuterAbstractBase } } - private void prepareAndSendEmail(final Action ruleAction, final NodeRef actionedUponNodeRef, final Pair recipient, final Pair sender) + public MimeMessageHelper prepareEmail(final Action ruleAction , final NodeRef actionedUponNodeRef, final Pair recipient, final Pair sender) { + // Create the mime mail message. + // Hack: using an array here to get around the fact that inner classes aren't closures. + // The MimeMessagePreparator.prepare() signature does not allow us to return a value and yet + // we can't set a result on a bare, non-final object reference due to Java language restrictions. + final MimeMessageHelper[] messageRef = new MimeMessageHelper[1]; MimeMessagePreparator mailPreparer = new MimeMessagePreparator() { @SuppressWarnings("unchecked") @@ -515,7 +516,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase logger.debug(ruleAction.getParameterValues()); } - MimeMessageHelper message = new MimeMessageHelper(mimeMessage); + messageRef[0] = new MimeMessageHelper(mimeMessage); // set header encoding if one has been supplied if (headerEncoding != null && headerEncoding.length() != 0) @@ -527,7 +528,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase String to = (String)ruleAction.getParameterValue(PARAM_TO); if (to != null && to.length() != 0) { - message.setTo(to); + messageRef[0].setTo(to); } else { @@ -595,7 +596,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase if(recipients.size() > 0) { - message.setTo(recipients.toArray(new String[recipients.size()])); + messageRef[0].setTo(recipients.toArray(new String[recipients.size()])); } else { @@ -620,8 +621,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase // from is enabled if (! authService.isCurrentUserTheSystemUser()) { - String currentUserName = authService.getCurrentUserName(); - fromPerson = getPerson(currentUserName); + fromPerson = personService.getPerson(authService.getCurrentUserName()); } if(isFromEnabled()) @@ -641,17 +641,17 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { try { - message.setFrom(from, fromPersonalName); + messageRef[0].setFrom(from, fromPersonalName); } catch (UnsupportedEncodingException error) { // Uses the JVM's default encoding, can never be unsupported. Just in case, revert to simple email - message.setFrom(from); + messageRef[0].setFrom(from); } } else { - message.setFrom(from); + messageRef[0].setFrom(from); } } else @@ -669,14 +669,15 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { logger.debug("looked up email address for :" + fromPerson + " email from " + fromActualUser); } - message.setFrom(fromActualUser); + messageRef[0].setFrom(fromActualUser); } else { // from system or user does not have email address - message.setFrom(fromDefaultAddress); + messageRef[0].setFrom(fromDefaultAddress); } } + } else { @@ -685,17 +686,20 @@ public class MailActionExecuter extends ActionExecuterAbstractBase logger.debug("from not enabled - sending from default address:" + fromDefaultAddress); } // from is not enabled. - message.setFrom(fromDefaultAddress); + messageRef[0].setFrom(fromDefaultAddress); } + + + // set subject line - message.setSubject((String)ruleAction.getParameterValue(PARAM_SUBJECT)); + messageRef[0].setSubject((String)ruleAction.getParameterValue(PARAM_SUBJECT)); if ((testModeRecipient != null) && (testModeRecipient.length() > 0) && (! testModeRecipient.equals("${dev.email.recipient.address}"))) { // If we have an override for the email recipient, we'll send the email to that address instead. // We'll prefix the subject with the original recipient, but leave the email message unchanged in every other way. - message.setTo(testModeRecipient); + messageRef[0].setTo(testModeRecipient); String emailRecipient = (String)ruleAction.getParameterValue(PARAM_TO); if (emailRecipient == null) @@ -709,9 +713,10 @@ public class MailActionExecuter extends ActionExecuterAbstractBase String recipientPrefixedSubject = "(" + emailRecipient + ") " + (String)ruleAction.getParameterValue(PARAM_SUBJECT); - message.setSubject(recipientPrefixedSubject); + messageRef[0].setSubject(recipientPrefixedSubject); } + // See if an email template has been specified String text = null; @@ -767,18 +772,18 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { // If we have an override for the email recipient, we'll send the email to that address instead. // We'll prefix the subject with the original recipient, but leave the email message unchanged in every other way. - message.setTo(testModeRecipient); + messageRef[0].setTo(testModeRecipient); String emailRecipient = recipient.getFirst(); String recipientPrefixedSubject = "(" + emailRecipient + ") " + localizedSubject; - message.setSubject(recipientPrefixedSubject); + messageRef[0].setSubject(recipientPrefixedSubject); } else { - message.setTo(recipient.getFirst()); - message.setSubject(localizedSubject); + messageRef[0].setTo(recipient.getFirst()); + messageRef[0].setSubject(localizedSubject); } } @@ -810,39 +815,50 @@ public class MailActionExecuter extends ActionExecuterAbstractBase isHTML = true; } } - + if (text != null) { - message.setText(text, isHTML); + messageRef[0].setText(text, isHTML); } } }; + MimeMessage mimeMessage = mailService.createMimeMessage(); + try + { + mailPreparer.prepare(mimeMessage); + } catch (Exception e) + { + // We're forced to catch java.lang.Exception here. Urgh. + if (logger.isInfoEnabled()) + { + logger.warn("Unable to prepare mail message. Skipping.", e); + } + } + + return messageRef[0]; + } + + private void prepareAndSendEmail(final Action ruleAction, final NodeRef actionedUponNodeRef, final Pair recipient, final Pair sender) + { + MimeMessageHelper preparedMessage = prepareEmail(ruleAction, actionedUponNodeRef, recipient, sender); try { // Send the message unless we are in "testMode" - if(!testMode) + if (!testMode) { - javaMailSender.send(mailPreparer); + mailService.send(preparedMessage.getMimeMessage()); + onSend(); } else { - try { - MimeMessage mimeMessage = javaMailSender.createMimeMessage(); - mailPreparer.prepare(mimeMessage); - lastTestMessage = mimeMessage; - } catch(Exception e) { - // We're forced to catch java.lang.Exception here. Urgh. - if (logger.isInfoEnabled()) - { - logger.warn("Unable to prepare mail message. Skipping.", e); - } - } + lastTestMessage = preparedMessage.getMimeMessage(); } } catch (MailException e) { + onFail(); String to = (String)ruleAction.getParameterValue(PARAM_TO); if (to == null) { @@ -860,7 +876,8 @@ public class MailActionExecuter extends ActionExecuterAbstractBase Boolean ignoreError = (Boolean)ruleAction.getParameterValue(PARAM_IGNORE_SEND_FAILURE); if (ignoreError == null || ignoreError.booleanValue() == false) { - throw new AlfrescoRuntimeException("Failed to send email to:" + to, e); + Object[] args = {to, e.toString()}; + throw new AlfrescoRuntimeException("email.outbound.err.send.failed", args, e); } } } @@ -1063,9 +1080,9 @@ public class MailActionExecuter extends ActionExecuterAbstractBase ); } } - return recipients; - } - + return recipients; + } + @SuppressWarnings("deprecation") public boolean personExists(final String user) { @@ -1081,6 +1098,10 @@ public class MailActionExecuter extends ActionExecuterAbstractBase } }, domain); } + else + { + exists = personService.personExists(user); + } return exists; } @@ -1099,6 +1120,10 @@ public class MailActionExecuter extends ActionExecuterAbstractBase } }, domain); } + else + { + person = personService.getPerson(user); + } return person; } @@ -1113,26 +1138,35 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { public Locale doWork() throws Exception { - Locale locale = null; - String localeString = (String)preferenceService.getPreference(user, "locale"); - if (localeString != null) - { - locale = StringUtils.parseLocaleString(localeString); - } - return locale; + return getLocaleForUserImpl(user); } }, domain); } + else + { + return getLocaleForUserImpl(user); + } + return locale; + } + + private Locale getLocaleForUserImpl(String user) + { + Locale locale = null; + String localeString = (String)preferenceService.getPreference(user, "locale"); + if (localeString != null) + { + locale = StringUtils.parseLocaleString(localeString); + } return locale; } private String getDomain(String user) { String[] parts = user.split("@"); - return parts.length == 1 ? "" : parts[1]; + return parts.length == 1 ? "" : parts[1].toLowerCase(I18NUtil.getLocale()); } - - /** + + /** * Return true if address has valid format * @param address * @return diff --git a/source/java/org/alfresco/repo/action/executer/MailActionExecuterTest.java b/source/java/org/alfresco/repo/action/executer/MailActionExecuterTest.java index 49ac060822..0c02183e5a 100644 --- a/source/java/org/alfresco/repo/action/executer/MailActionExecuterTest.java +++ b/source/java/org/alfresco/repo/action/executer/MailActionExecuterTest.java @@ -50,11 +50,11 @@ public class MailActionExecuterTest { public static ApplicationContextInit APP_CONTEXT_INIT = new ApplicationContextInit(); // Rules to create 2 test users. - public static AlfrescoPerson AUSTRALIAN_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "EnglishUser"); - public static AlfrescoPerson BRITISH_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "EnglishUser"); - public static AlfrescoPerson FRENCH_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "FrenchUser"); - public static AlfrescoPerson UNKNOWN_USER1 = new AlfrescoPerson(APP_CONTEXT_INIT, "UnknownUser1"); - public static AlfrescoPerson UNKNOWN_USER2 = new AlfrescoPerson(APP_CONTEXT_INIT, "UnknowUser2"); + public static AlfrescoPerson AUSTRALIAN_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "AustralianUser@test.com"); + public static AlfrescoPerson BRITISH_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "EnglishUser@test.com"); + public static AlfrescoPerson FRENCH_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "FrenchUser@test.com"); + public static AlfrescoPerson UNKNOWN_USER1 = new AlfrescoPerson(APP_CONTEXT_INIT, "UnknownUser1@test.com"); + public static AlfrescoPerson UNKNOWN_USER2 = new AlfrescoPerson(APP_CONTEXT_INIT, "UnknowUser2@test.com"); // Tie them together in a static Rule Chain @ClassRule public static RuleChain ruleChain = RuleChain.outerRule(APP_CONTEXT_INIT) @@ -94,7 +94,7 @@ public class MailActionExecuterTest { preferences.clear(); preferences.put("locale", "en_AU"); - PREFERENCE_SERVICE.setPreferences(BRITISH_USER.getUsername(), preferences); + PREFERENCE_SERVICE.setPreferences(AUSTRALIAN_USER.getUsername(), preferences); } @@ -161,7 +161,7 @@ public class MailActionExecuterTest { MimeMessage message = ACTION_EXECUTER.retrieveLastTestMessage(); Assert.assertNotNull(message); - Assert.assertEquals("G'Day Jan 1, 1970", (String)message.getContent()); + Assert.assertEquals("G'Day 01/01/1970", (String)message.getContent()); } } diff --git a/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java b/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java index 9dd0bd77f5..519cfb1076 100644 --- a/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java +++ b/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java @@ -320,6 +320,9 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean // is local to the method because we only want to cache per request - there is not point in keeping // an instance cache because the data will become stale if a user changes their avatar. Map userIdToAvatarNodeRefCache = new HashMap(); + + String currentTenantDomain = tenantService.getCurrentUserDomain(); + if (logger.isDebugEnabled()) @@ -329,12 +332,9 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean + maxFeedItems); } - String currentTenantDomain = tenantService.getCurrentUserDomain(); - for (ActivityFeedEntity activityFeed : activityFeeds) { - if (actvityFilter != null && !actvityFilter.contains(activityFeed.getActivityType())) - { + if (actvityFilter != null && !actvityFilter.contains(activityFeed.getActivityType())) { if (logger.isTraceEnabled()) { logger.trace("Filtering " + activityFeed.toString() + " \n by the activity filter."); @@ -342,8 +342,7 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean continue; } - if (userFilter != null && !userFilter.contains(activityFeed.getPostUserId())) - { + if (userFilter != null && !userFilter.contains(activityFeed.getPostUserId())) { if (logger.isTraceEnabled()) { logger.trace("Filtering " + activityFeed.toString() + " \n by the user filter."); @@ -356,10 +355,6 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean // note: pending requirements for THOR-224, for now assume all activities are within context of site and filter by current tenant if (! currentTenantDomain.equals(tenantService.getDomain(activityFeed.getSiteNetwork()))) { - if (logger.isTraceEnabled()) - { - logger.trace("Filtering " + activityFeed.toString() + " \n by the site/tenant filter."); - } continue; } } @@ -392,7 +387,7 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean { if (logger.isDebugEnabled()) { - logger.debug("getUserFeedEntries: person no longer exists: "+postUserId); + logger.warn("getUserFeedEntries: person no longer exists: "+postUserId); } } diff --git a/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java b/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java index fe6a0a497c..e2d40f2705 100644 --- a/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java +++ b/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java @@ -38,6 +38,8 @@ import org.alfresco.repo.lock.JobLockService.JobLockRefreshCallback; import org.alfresco.repo.lock.LockAcquisitionException; import org.alfresco.repo.search.SearcherException; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.admin.RepoAdminService; @@ -330,6 +332,7 @@ public class FeedNotifierImpl implements FeedNotifier, ApplicationContextAware try { final String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); + final String tenantDomain = TenantUtil.getCurrentDomain(); // process the feeds using the batch processor {@link BatchProcessor} BatchProcessor.BatchProcessWorker worker = new BatchProcessor.BatchProcessWorker() @@ -355,14 +358,22 @@ public class FeedNotifierImpl implements FeedNotifier, ApplicationContextAware final RetryingTransactionHelper txHelper = transactionService.getRetryingTransactionHelper(); txHelper.setMaxRetries(0); - txHelper.doInTransaction(new RetryingTransactionCallback() + TenantUtil.runAsTenant(new TenantRunAsWork() { - public Void execute() throws Throwable + @Override + public Void doWork() throws Exception { - processInternal(person); - return null; + txHelper.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + processInternal(person); + return null; + } + }, false, true); + return null; } - }, false, true); + }, tenantDomain); } private void processInternal(final PersonInfo person) throws Exception diff --git a/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java b/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java index 2ed215ad0d..2013e940ed 100644 --- a/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java +++ b/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java @@ -40,7 +40,6 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; diff --git a/source/java/org/alfresco/repo/cluster/HazelcastConfigFactoryBean.java b/source/java/org/alfresco/repo/cluster/HazelcastConfigFactoryBean.java new file mode 100644 index 0000000000..405535583c --- /dev/null +++ b/source/java/org/alfresco/repo/cluster/HazelcastConfigFactoryBean.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2005-2012 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.cluster; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.util.Properties; +import java.util.regex.Pattern; + +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.Resource; + +import com.hazelcast.config.Config; +import com.hazelcast.config.InMemoryXmlConfig; + +/** + * FactoryBean used to create Hazelcast {@link Config} objects. A configuration file is supplied + * in the form of a Spring {@link Resource} and a set of {@link Properties} can also be provided. The + * XML file is processed so that property placeholders of the form ${property.name} are substitued for + * the corresponding property value before the XML is parsed into the Hazelcast configuration object. + * + * @author Matt Ward + */ +public class HazelcastConfigFactoryBean implements InitializingBean, FactoryBean +{ + private static final String PLACEHOLDER_END = "}"; + private static final String PLACEHOLDER_START = "${"; + private Resource configFile; + private Config config; + private Properties properties; + + + /** + * Set the Hazelcast XML configuration file to use. This will be merged with the supplied + * Properties and parsed to produce a final {@link Config} object. + * @param configFile the configFile to set + */ + public void setConfigFile(Resource configFile) + { + this.configFile = configFile; + } + + /** + * Used to supply the set of Properties that the configuration file can reference. + * + * @param properties the properties to set + */ + public void setProperties(Properties properties) + { + this.properties = properties; + } + + /** + * Spring {@link InitializingBean} lifecycle method. Substitutes property placeholders for their + * corresponding values and creates a {@link Config Hazelcast configuration} with the post-processed + * XML file - ready for the {@link #getObject()} factory method to be used to retrieve it. + */ + @Override + public void afterPropertiesSet() throws Exception + { + if (configFile == null) + { + throw new IllegalArgumentException("No configuration file specified."); + } + if (properties == null) + { + properties = new Properties(); + } + + // These configXML strings will be large and are therefore intended + // to be thrown away. We only want to keep the final Config object. + String rawConfigXML = getConfigFileContents(); + String configXML = substituteProperties(rawConfigXML); + config = new InMemoryXmlConfig(configXML); + } + + /** + * For the method parameter text, replaces all occurrences of placeholders having + * the form ${property.name} with the value of the property having the key "property.name". The + * properties are supplied using {@link #setProperties(Properties)}. + * + * @param text The String to apply property substitutions to. + * @return String after substitutions have been applied. + */ + private String substituteProperties(String text) + { + for (String propName : properties.stringPropertyNames()) + { + String propValue = properties.getProperty(propName); + String quotedPropName = Pattern.quote(PLACEHOLDER_START + propName + PLACEHOLDER_END); + text = text.replaceAll(quotedPropName, propValue); + } + + return text; + } + + /** + * Opens the configFile {@link Resource} and reads the contents into a String. + * + * @return the contents of the configFile resource. + */ + private String getConfigFileContents() + { + StringWriter writer = new StringWriter(); + InputStream inputStream = null; + try + { + inputStream = configFile.getInputStream(); + IOUtils.copy(inputStream, writer, "UTF-8"); + return writer.toString(); + } + catch (IOException e) + { + throw new RuntimeException("Couldn't read configuration: " + configFile, e); + } + finally + { + try + { + if (inputStream != null) + { + inputStream.close(); + } + } + catch (IOException e) + { + throw new RuntimeException("Couldn't close stream", e); + } + } + } + + /** + * FactoryBean's factory method. Returns the config with the property key/value + * substitutions in place. + */ + @Override + public Config getObject() throws Exception + { + return config; + } + + @Override + public Class getObjectType() + { + return Config.class; + } + + @Override + public boolean isSingleton() + { + return true; + } +} diff --git a/source/java/org/alfresco/repo/cluster/HazelcastConfigFactoryBeanTest.java b/source/java/org/alfresco/repo/cluster/HazelcastConfigFactoryBeanTest.java new file mode 100644 index 0000000000..5a897852cf --- /dev/null +++ b/source/java/org/alfresco/repo/cluster/HazelcastConfigFactoryBeanTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2005-2012 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.cluster; + +import static org.junit.Assert.assertEquals; + +import java.util.Properties; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import com.hazelcast.config.Config; + +/** + * Tests for the HazelcastConfigFactoryBean class. + * + * @author Matt Ward + */ +public class HazelcastConfigFactoryBeanTest +{ + private HazelcastConfigFactoryBean configFactory; + private Resource resource; + private Properties properties; + + @Before + public void setUp() throws Exception + { + configFactory = new HazelcastConfigFactoryBean(); + resource = new ClassPathResource("cluster-test/placeholder-test.xml"); + configFactory.setConfigFile(resource); + + properties = new Properties(); + properties.setProperty("alfresco.hazelcast.password", "let-me-in"); + properties.setProperty("alfresco.cluster.name", "cluster-name"); + configFactory.setProperties(properties); + + // Trigger the spring post-bean creation lifecycle method + configFactory.afterPropertiesSet(); + } + + + @Test + public void testConfigHasNewPropertyValues() throws Exception + { + // Invoke the factory method. + Config config = configFactory.getObject(); + + assertEquals("let-me-in", config.getGroupConfig().getPassword()); + assertEquals("cluster-name", config.getGroupConfig().getName()); + } +} diff --git a/source/java/org/alfresco/repo/cluster/HazelcastInstanceFactory.java b/source/java/org/alfresco/repo/cluster/HazelcastInstanceFactory.java new file mode 100644 index 0000000000..92652f389c --- /dev/null +++ b/source/java/org/alfresco/repo/cluster/HazelcastInstanceFactory.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2005-2013 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.cluster; + +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.alfresco.util.PropertyCheck; + +import com.hazelcast.config.Config; +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; + +/** + * Provides a way of lazily creating HazelcastInstances for a given configuration. + * The HazelcastInstance will not be created until {@link #getInstance()} is called. + *

+ * An intermediary class such as this is required in order to avoid starting + * Hazelcast instances when clustering is not configured/required. Otherwise + * simply by defining a HazelcastInstance bean clustering would spring into life. + *

+ * Please note this class provides non-static access deliberately, and should be + * injected into any clients that require its services. + * + * @author Matt Ward + */ +public class HazelcastInstanceFactory +{ + private Config config; + private HazelcastInstance hazelcastInstance; + /** Guards {@link #config} and {@link #hazelcastInstance} */ + private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); + + public HazelcastInstance getInstance() + { + rwLock.readLock().lock(); + try + { + if (hazelcastInstance != null) + { + return hazelcastInstance; + } + } + finally + { + rwLock.readLock().unlock(); + } + + // hazelcastInstance is null, so create it. + rwLock.writeLock().lock(); + try + { + // Double check condition hasn't changed in between locks. + if (hazelcastInstance == null) + { + hazelcastInstance = Hazelcast.newHazelcastInstance(config); + } + return hazelcastInstance; + } + finally + { + rwLock.writeLock().unlock(); + } + } + + /** + * Checks whether hazelcast has been given a valid cluster name. If so, + * then clustering is considered enabled. This condition should be checked + * before calling {@link #getInstance()}. + * + * @return true if clustering is enabled, false otherwise. + */ + public boolean isClusteringEnabled() + { + rwLock.readLock().lock(); + try + { + String clusterName = config.getGroupConfig().getName(); + return (PropertyCheck.isValidPropertyString(clusterName)); + } + finally + { + rwLock.readLock().unlock(); + } + } + + /** + * Retrieve the name of the cluster for the configuration used by this factory. + * + * @return String - the cluster name. + */ + public String getClusterName() + { + rwLock.readLock().lock(); + try + { + String clusterName = config.getGroupConfig().getName(); + return clusterName; + } + finally + { + rwLock.readLock().unlock(); + } + } + + /** + * Sets the Hazelcast configuration that will be used by this factory when + * creating the HazelcastInstance. + * + * @param config Hazelcast configuration + */ + public void setConfig(Config config) + { + rwLock.writeLock().lock(); + try + { + this.config = config; + } + finally + { + rwLock.writeLock().unlock(); + } + } +} diff --git a/source/java/org/alfresco/repo/importer/ImporterComponent.java b/source/java/org/alfresco/repo/importer/ImporterComponent.java index a3cba3d057..88a7edb45f 100644 --- a/source/java/org/alfresco/repo/importer/ImporterComponent.java +++ b/source/java/org/alfresco/repo/importer/ImporterComponent.java @@ -775,6 +775,12 @@ public class ImporterComponent implements ImporterService String contentUrl = contentData.getContentUrl(); if (contentUrl != null && contentUrl.length() > 0) { + Map propsBefore = null; + if (contentUsageImpl != null && contentUsageImpl.getEnabled()) + { + propsBefore = nodeService.getProperties(nodeRef); + } + if (contentCache != null) { // import content from source @@ -783,27 +789,20 @@ public class ImporterComponent implements ImporterService } else { - // import the content from the url + // import the content from the import source file InputStream contentStream = streamHandler.importStream(contentUrl); ContentWriter writer = contentService.getWriter(nodeRef, propertyName, true); writer.setEncoding(contentData.getEncoding()); writer.setMimetype(contentData.getMimetype()); - - Map propsBefore = null; - if (contentUsageImpl != null && contentUsageImpl.getEnabled()) - { - propsBefore = nodeService.getProperties(nodeRef); - } - writer.putContent(contentStream); - - if (contentUsageImpl != null && contentUsageImpl.getEnabled()) - { - // Since behaviours for content nodes have all been disabled, - // it is necessary to update the user's usage stats. - Map propsAfter = nodeService.getProperties(nodeRef); - contentUsageImpl.onUpdateProperties(nodeRef, propsBefore, propsAfter); - } + } + + if (contentUsageImpl != null && contentUsageImpl.getEnabled()) + { + // Since behaviours for content nodes have all been disabled, + // it is necessary to update the user's usage stats. + Map propsAfter = nodeService.getProperties(nodeRef); + contentUsageImpl.onUpdateProperties(nodeRef, propsBefore, propsAfter); } reportContentCreated(nodeRef, contentUrl); diff --git a/source/java/org/alfresco/repo/mail/AlfrescoJavaMailSender.java b/source/java/org/alfresco/repo/mail/AlfrescoJavaMailSender.java index 074f47bf56..9857e4c222 100644 --- a/source/java/org/alfresco/repo/mail/AlfrescoJavaMailSender.java +++ b/source/java/org/alfresco/repo/mail/AlfrescoJavaMailSender.java @@ -25,7 +25,6 @@ import javax.mail.NoSuchProviderException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.URLName; -import javax.mail.event.ConnectionListener; import org.apache.commons.pool.KeyedPoolableObjectFactory; import org.apache.commons.pool.impl.GenericKeyedObjectPool; diff --git a/source/java/org/alfresco/repo/model/Repository.java b/source/java/org/alfresco/repo/model/Repository.java index eeb0b6adee..aefce2ce89 100644 --- a/source/java/org/alfresco/repo/model/Repository.java +++ b/source/java/org/alfresco/repo/model/Repository.java @@ -181,6 +181,7 @@ public class Repository implements ApplicationContextAware @Override protected void onShutdown(ApplicationEvent event) { + //NOOP } } @@ -204,7 +205,7 @@ public class Repository implements ApplicationContextAware } /** - * Gets the Company Home + * Gets the Company Home. Note this is tenant-aware if the correct Cache is supplied. * * @return company home node ref */ diff --git a/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java index ff18920a17..920c4ee302 100644 --- a/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java +++ b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java @@ -203,9 +203,8 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest nodeService.deleteNode(nodeWithFreeMarkerContent); nodeService.deleteNode(testTargetFolder); } - - //TODO Fix this failing test. - public void off_testRenderFreeMarkerTemplate() throws Exception + + public void testRenderFreeMarkerTemplate() throws Exception { this.setComplete(); this.endTransaction(); diff --git a/source/java/org/alfresco/repo/security/person/GetPeopleCannedQuery.java b/source/java/org/alfresco/repo/security/person/GetPeopleCannedQuery.java index 00d2598dd5..f53c9f338d 100644 --- a/source/java/org/alfresco/repo/security/person/GetPeopleCannedQuery.java +++ b/source/java/org/alfresco/repo/security/person/GetPeopleCannedQuery.java @@ -63,6 +63,8 @@ public class GetPeopleCannedQuery extends AbstractCannedQuery public static final int MAX_FILTER_SORT_PROPS = 3; + private static final int MAX_EXPECTED_ADMINS = 5; // TODO refine non-admin paging + private NodeDAO nodeDAO; private QNameDAO qnameDAO; private CannedQueryDAO cannedQueryDAO; @@ -190,26 +192,35 @@ public class GetPeopleCannedQuery extends AbstractCannedQuery filterSortPropCnt = setFilterSortParams(sortFilterProps, sortAsc, params); // filtered and/or sorted - note: permissions not applicable for getPeople - final List result = new ArrayList(100); + List result = new ArrayList(100); final PersonQueryCallback c = new DefaultPersonQueryCallback(result, paramBean.getIncludeAdministrators()); PersonResultHandler resultHandler = new PersonResultHandler(c); int offset = parameters.getPageDetails().getSkipResults(); int totalResultCountMax = parameters.getTotalResultCountMax(); - int limit = totalResultCountMax > 0 ? totalResultCountMax : parameters.getPageDetails().getPageSize(); - if (limit != Integer.MAX_VALUE) + + int origOffset = offset; + int origLimit = totalResultCountMax > 0 ? totalResultCountMax : parameters.getPageDetails().getPageSize(); + + long newLimit = (long)origLimit; + + // to enable hasMore flag + newLimit++; + + boolean excludeAdmins = (! paramBean.getIncludeAdministrators()); + if (excludeAdmins) { - // to enable hasMore flag - limit++; - - if ((! paramBean.getIncludeAdministrators()) && (limit != Integer.MAX_VALUE)) - { - // TODO - only works in case where there is only 1 default admin - limit++; - } + // TODO refine - non-admin paging + offset = 0; + newLimit = offset + (long)newLimit + MAX_EXPECTED_ADMINS; } - cannedQueryDAO.executeQuery(QUERY_NAMESPACE, QUERY_SELECT_GET_PEOPLE, params, offset, limit, resultHandler); + if (newLimit > Integer.MAX_VALUE) + { + newLimit = Integer.MAX_VALUE; + } + + cannedQueryDAO.executeQuery(QUERY_NAMESPACE, QUERY_SELECT_GET_PEOPLE, params, offset, (int)newLimit, resultHandler); resultHandler.done(); if (start != null) @@ -217,6 +228,17 @@ public class GetPeopleCannedQuery extends AbstractCannedQuery logger.debug("Base query: "+result.size()+" in "+(System.currentTimeMillis()-start)+" msecs"); } + if (excludeAdmins) + { + // TODO refine - non-admin paging + long max = origOffset + (long)origLimit; + if (max > result.size()) + { + max = result.size(); + } + result = result.subList(origOffset, (int)max); + } + return result; } @@ -296,7 +318,9 @@ public class GetPeopleCannedQuery extends AbstractCannedQuery @Override protected Pair getTotalResultCount(List results) { - return super.getTotalResultCount(results); + int offset = super.getParameters().getPageDetails().getSkipResults(); + Integer size = offset + results.size(); + return new Pair(size, size); } protected interface PersonQueryCallback @@ -318,9 +342,7 @@ public class GetPeopleCannedQuery extends AbstractCannedQuery @Override public boolean handle(NodeRef personRef) { - // TODO refine - // - return username as part of query - // - can break paging if more than one admin + // TODO refine - return username as part of query if (includeAdministrators == false) { String userName = (String) nodeService.getProperty(personRef, ContentModel.PROP_USERNAME); diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java index ea129f18de..db502a7dea 100644 --- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java +++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java @@ -430,14 +430,6 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per return getPerson(userName, true); } - /** - * {@inheritDoc} - */ - public NodeRef getPersonOrNull(String userName) - { - return getPersonImpl(userName, false, false); - } - /** * {@inheritDoc} */ @@ -453,9 +445,8 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per throw new NoSuchPersonException(personRef.toString()); } - // belts-and-braces String username = (String)props.get(ContentModel.PROP_USERNAME); - if (getPersonOrNull(username) == null) + if (username == null) { throw new NoSuchPersonException(personRef.toString()); } @@ -466,6 +457,14 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per (String)props.get(ContentModel.PROP_LASTNAME)); } + /** + * {@inheritDoc} + */ + public NodeRef getPersonOrNull(String userName) + { + return getPersonImpl(userName, false, false); + } + /** * {@inheritDoc} */ @@ -474,6 +473,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per return getPersonImpl(userName, autoCreateHomeFolderAndMissingPersonIfAllowed, true); } + private NodeRef getPersonImpl( final String userName, final boolean autoCreateHomeFolderAndMissingPersonIfAllowed, @@ -513,7 +513,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per */ public boolean personExists(String caseSensitiveUserName) { - NodeRef person = getPersonOrNull(caseSensitiveUserName); + NodeRef person = getPersonOrNullImpl(caseSensitiveUserName); if (person != null) { // re: THOR-293 diff --git a/source/java/org/alfresco/repo/security/person/PersonTest.java b/source/java/org/alfresco/repo/security/person/PersonTest.java index eec7733fc9..23a5c81e35 100644 --- a/source/java/org/alfresco/repo/security/person/PersonTest.java +++ b/source/java/org/alfresco/repo/security/person/PersonTest.java @@ -636,9 +636,10 @@ public class PersonTest extends TestCase NodeRef p6 = personService.createPerson(createDefaultProperties("bb", "Bb", "Bb", "bb@bb", "alfresco", rootNodeRef)); NodeRef p7 = personService.createPerson(createDefaultProperties("dd", "Dd", "Dd", "dd@dd", "alfresco", rootNodeRef)); + int expectedTotalCount = 7; + assertEquals(expectedTotalCount, getPeopleCount()); - - assertEquals(7, getPeopleCount()); + Pair expectedResultCount = new Pair(expectedTotalCount,expectedTotalCount); List> sort = new ArrayList>(1); sort.add(new Pair(ContentModel.PROP_USERNAME, true)); @@ -651,13 +652,16 @@ public class PersonTest extends TestCase assertEquals(p3, results.get(0).getNodeRef()); assertEquals(p1, results.get(1).getNodeRef()); - // page 2 + // page 2 (with total count) pr = new PagingRequest(2, 2, null); + pr.setRequestTotalCountMax(Integer.MAX_VALUE); + ppr = personService.getPeople(null, true, sort, pr); results = ppr.getPage(); assertEquals(2, results.size()); assertEquals(p6, results.get(0).getNodeRef()); assertEquals(p4, results.get(1).getNodeRef()); + assertEquals(expectedResultCount, ppr.getTotalResultCount()); // page 3 pr = new PagingRequest(4, 2, null); @@ -667,12 +671,76 @@ public class PersonTest extends TestCase assertEquals(p7, results.get(0).getNodeRef()); assertEquals(p2, results.get(1).getNodeRef()); - // page 4 + // page 4 (with total count) pr = new PagingRequest(6, 2, null); + pr.setRequestTotalCountMax(Integer.MAX_VALUE); + ppr = personService.getPeople(null, true, sort, pr); results = ppr.getPage(); assertEquals(1, results.size()); assertEquals(p5, results.get(0).getNodeRef()); + assertEquals(expectedResultCount, ppr.getTotalResultCount()); + } + + public void testPeopleSortingPaging_NoAdmin() + { + personService.setCreateMissingPeople(false); + + assertEquals(2, getPeopleCount()); + + NodeRef p1 = personService.getPerson(AuthenticationUtil.getAdminUserName()); // admin - by default + NodeRef p2 = personService.getPerson(AuthenticationUtil.getGuestUserName()); // guest - by default + + NodeRef p3 = personService.createPerson(createDefaultProperties("aa", "Aa", "Aa", "aa@aa", "alfresco", rootNodeRef)); + NodeRef p4 = personService.createPerson(createDefaultProperties("cc", "Cc", "Cc", "cc@cc", "alfresco", rootNodeRef)); + NodeRef p5 = personService.createPerson(createDefaultProperties("hh", "Hh", "Hh", "hh@hh", "alfresco", rootNodeRef)); + NodeRef p6 = personService.createPerson(createDefaultProperties("bb", "Bb", "Bb", "bb@bb", "alfresco", rootNodeRef)); + NodeRef p7 = personService.createPerson(createDefaultProperties("dd", "Dd", "Dd", "dd@dd", "alfresco", rootNodeRef)); + + int expectedTotalCount = 7; + assertEquals(expectedTotalCount, getPeopleCount()); + + int expectedTotalCountWithAdmin = expectedTotalCount - 1; + Pair expectedResultCount = new Pair(expectedTotalCountWithAdmin,expectedTotalCountWithAdmin); + + List> sort = new ArrayList>(1); + sort.add(new Pair(ContentModel.PROP_USERNAME, true)); + + // page 1 + PagingRequest pr = new PagingRequest(0, 2, null); + PagingResults ppr = personService.getPeople(null, null, null, null, false, sort, pr); + List results = ppr.getPage(); + assertEquals(2, results.size()); + assertEquals(p3, results.get(0).getNodeRef()); + assertEquals(p6, results.get(1).getNodeRef()); + + // page 2 (with total count) + pr = new PagingRequest(2, 2, null); + pr.setRequestTotalCountMax(Integer.MAX_VALUE); + + ppr = personService.getPeople(null, null, null, null, false, sort, pr); + results = ppr.getPage(); + assertEquals(2, results.size()); + assertEquals(p4, results.get(0).getNodeRef()); + assertEquals(p7, results.get(1).getNodeRef()); + assertEquals(expectedResultCount, ppr.getTotalResultCount()); + + // page 3 + pr = new PagingRequest(4, 2, null); + ppr = personService.getPeople(null, null, null, null, false, sort, pr); + results = ppr.getPage(); + assertEquals(2, results.size()); + assertEquals(p2, results.get(0).getNodeRef()); + assertEquals(p5, results.get(1).getNodeRef()); + + // page 4 (with total count) + pr = new PagingRequest(6, 2, null); + pr.setRequestTotalCountMax(Integer.MAX_VALUE); + + ppr = personService.getPeople(null, null, null, null, false, sort, pr); + results = ppr.getPage(); + assertEquals(0, results.size()); + assertEquals(expectedResultCount, ppr.getTotalResultCount()); } // note: this test can be removed as and when we remove the deprecated "getPeople" impl diff --git a/source/java/org/alfresco/repo/security/person/TestPersonManager.java b/source/java/org/alfresco/repo/security/person/TestPersonManager.java index c9699a3a86..c4256c478c 100644 --- a/source/java/org/alfresco/repo/security/person/TestPersonManager.java +++ b/source/java/org/alfresco/repo/security/person/TestPersonManager.java @@ -78,6 +78,15 @@ public class TestPersonManager } private NodeRef makePersonNode(String userName) + { + PropertyMap personProps = makePersonProperties(userName); + + NodeRef person = personService.createPerson(personProps); + people.put(userName, person); + return person; + } + + public static PropertyMap makePersonProperties(String userName) { PropertyMap personProps = new PropertyMap(); personProps.put(ContentModel.PROP_USERNAME, userName); @@ -86,10 +95,7 @@ public class TestPersonManager personProps.put(ContentModel.PROP_EMAIL, userName+EMAIL_SUFFIX); personProps.put(ContentModel.PROP_JOBTITLE, userName+JOB_SUFFIX); personProps.put(ContentModel.PROP_JOBTITLE, userName+ORGANISATION_SUFFIX); - - NodeRef person = personService.createPerson(personProps); - people.put(userName, person); - return person; + return personProps; } public NodeRef get(String userName) diff --git a/source/java/org/alfresco/repo/site/SiteServiceImpl.java b/source/java/org/alfresco/repo/site/SiteServiceImpl.java index 179f4ac776..aa87b9f98b 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImpl.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImpl.java @@ -118,7 +118,7 @@ import org.springframework.extensions.surf.util.ParameterCheck; public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServiceInternal, SiteModel, NodeServicePolicies.OnRestoreNodePolicy { /** Logger */ - private static Log logger = LogFactory.getLog(SiteServiceImpl.class); + protected static Log logger = LogFactory.getLog(SiteServiceImpl.class); /** The DM store where site's are kept */ public static final StoreRef SITE_STORE = new StoreRef("workspace://SpacesStore"); @@ -938,8 +938,11 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic */ public List listSites(final String userName, final int size) { - // MT share - for activity service system callback - if (tenantService.isEnabled() && (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && tenantService.isTenantUser(userName)) + // MT share - for activity service remote system callback (deprecated) + if (tenantService.isEnabled() && + TenantUtil.isCurrentDomainDefault() && + (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && + tenantService.isTenantUser(userName)) { final String tenantDomain = tenantService.getUserDomain(userName); @@ -1177,8 +1180,11 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic */ public SiteInfo getSite(final String shortName) { - // MT share - for activity service system callback - if (tenantService.isEnabled() && (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && tenantService.isTenantName(shortName)) + // MT share - for activity service remote system callback (deprecated) + if (tenantService.isEnabled() && + TenantUtil.isCurrentDomainDefault() && + (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && + tenantService.isTenantName(shortName)) { final String tenantDomain = tenantService.getDomain(shortName); final String sName = tenantService.getBaseName(shortName, true); @@ -1715,8 +1721,11 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic public Map listMembers(String shortName, final String nameFilter, final String roleFilter, final int size, final boolean collapseGroups) { - // MT share - for activity service system callback - if (tenantService.isEnabled() && (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && tenantService.isTenantName(shortName)) + // MT share - for activity service remote system callback (deprecated) + if (tenantService.isEnabled() && + TenantUtil.isCurrentDomainDefault() && + (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && + tenantService.isTenantName(shortName)) { final String tenantDomain = tenantService.getDomain(shortName); final String sName = tenantService.getBaseName(shortName, true); diff --git a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java index 30c342e7ce..c43b952a68 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java @@ -579,10 +579,13 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo tenantDeployer.onEnableTenant(); } - // bootstrap workflows - for (WorkflowDeployer workflowDeployer : workflowDeployers) + // bootstrap workflows, if needed + if(workflowService.isMultiTenantWorkflowDeploymentEnabled()) { - workflowDeployer.init(); + for (WorkflowDeployer workflowDeployer : workflowDeployers) + { + workflowDeployer.init(); + } } // bootstrap modules (if any) @@ -773,14 +776,17 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo { public Object doWork() { - List workflowDefs = workflowService.getDefinitions(); - if (workflowDefs != null) - { - for (WorkflowDefinition workflowDef : workflowDefs) - { - workflowService.undeployDefinition(workflowDef.getId()); - } - } + // Only undeploy tenant-workflows when MT-workflow deployment is enabled + if(workflowService.isMultiTenantWorkflowDeploymentEnabled()) { + List workflowDefs = workflowService.getDefinitions(); + if (workflowDefs != null) + { + for (WorkflowDefinition workflowDef : workflowDefs) + { + workflowService.undeployDefinition(workflowDef.getId()); + } + } + } List messageResourceBundles = repoAdminService.getMessageBundles(); if (messageResourceBundles != null) diff --git a/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java b/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java index 246dc96243..03664211ec 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java +++ b/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java @@ -222,7 +222,7 @@ public class MultiTDemoTest extends TestCase private void createTenant(final String tenantDomain) { // create tenants (if not already created) - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -236,7 +236,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, AuthenticationUtil.getSystemUserName()); + }, TenantService.DEFAULT_DOMAIN); } private void deleteTenant(final String tenantDomain) @@ -246,7 +246,7 @@ public class MultiTDemoTest extends TestCase public Object execute() throws Throwable { // delete tenant (if it exists) - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -259,7 +259,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, AuthenticationUtil.getSystemUserName()); + }, TenantService.DEFAULT_DOMAIN); return null; } }); @@ -270,7 +270,7 @@ public class MultiTDemoTest extends TestCase for (final String tenantDomain : tenants) { String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -284,7 +284,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } } @@ -374,8 +374,10 @@ public class MultiTDemoTest extends TestCase private void deleteTestAuthoritiesForTenant(final String[] uniqueGroupNames, final String userName) { + String tenantDomain = tenantService.getUserDomain(userName); + // Check deletion for tenant1 - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -391,12 +393,15 @@ public class MultiTDemoTest extends TestCase } return null; } - }, userName); + }, userName, tenantDomain); } + private void createTestAuthoritiesForTenant(final String[] uniqueGroupNames, final String userName) { + String tenantDomain = tenantService.getUserDomain(userName); + // Create groups for tenant - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -413,13 +418,15 @@ public class MultiTDemoTest extends TestCase return null; } - }, userName); + }, userName, tenantDomain); } - + private void checkTestAuthoritiesPresence(final String[] uniqueGroupNames, final String userName, final boolean shouldPresent) { + String tenantDomain = tenantService.getUserDomain(userName); + // Check that created permissions are not visible to tenant 2 - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -445,7 +452,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, userName); + }, userName, tenantDomain); } private void createGroup(String shortName, String parentShortName) @@ -924,7 +931,7 @@ public class MultiTDemoTest extends TestCase createTenant(tenantDomain1); String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain1); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -932,7 +939,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain1); createTenant(tenantDomain2); } @@ -979,7 +986,7 @@ public class MultiTDemoTest extends TestCase { String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1009,7 +1016,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } } catch (Throwable t) @@ -1101,20 +1108,20 @@ public class MultiTDemoTest extends TestCase assertTrue(tenants.size() > 0); - final int rootGrpsOrigCnt = TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + final int rootGrpsOrigCnt = TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Integer doWork() throws Exception { return authorityService.getAllRootAuthorities(AuthorityType.GROUP).size(); } - }, tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenants.get(0))); + }, tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenants.get(0)), tenants.get(0)); // create groups and add users for (final String tenantDomain : tenants) { final String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1135,7 +1142,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } // check groups/users @@ -1143,7 +1150,7 @@ public class MultiTDemoTest extends TestCase { final String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1172,7 +1179,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } } @@ -1181,7 +1188,7 @@ public class MultiTDemoTest extends TestCase logger.info("Create demo categories"); // super admin - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1190,13 +1197,12 @@ public class MultiTDemoTest extends TestCase return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getAdminUserName(), TenantService.DEFAULT_DOMAIN); for (final String tenantDomain : tenants) { String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1206,7 +1212,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } } @@ -1304,7 +1310,7 @@ public class MultiTDemoTest extends TestCase { final String tenantUserName = tenantService.getDomainUser(baseUserName, tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1324,7 +1330,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantUserName); + }, tenantUserName, tenantDomain); } } } @@ -1347,7 +1353,7 @@ public class MultiTDemoTest extends TestCase { final String tenantUserName = tenantService.getDomainUser(baseUserName, tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1364,7 +1370,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantUserName); + }, tenantUserName, tenantDomain); } } } @@ -1375,7 +1381,7 @@ public class MultiTDemoTest extends TestCase logger.info("Get tenant stores"); // system - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1387,23 +1393,23 @@ public class MultiTDemoTest extends TestCase assertTrue("System: "+nodeService.getStores().size()+", "+(tenants.size()+1), (nodeService.getStores().size() >= (DEFAULT_STORE_COUNT * (tenants.size()+1)))); return null; } - }, AuthenticationUtil.getSystemUserName()); + }, TenantService.DEFAULT_DOMAIN); // super admin - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { assertTrue("Super admin: "+nodeService.getStores().size(), (nodeService.getStores().size() >= DEFAULT_STORE_COUNT)); return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getAdminUserName(), TenantService.DEFAULT_DOMAIN); for (final String tenantDomain : tenants) { String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1412,7 +1418,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } } @@ -1423,8 +1429,7 @@ public class MultiTDemoTest extends TestCase for (final String tenantDomain : tenants) { String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1445,7 +1450,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } } @@ -1456,8 +1461,7 @@ public class MultiTDemoTest extends TestCase for (final String tenantDomain : tenants) { final String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1496,7 +1500,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } } @@ -1546,7 +1550,7 @@ public class MultiTDemoTest extends TestCase { final String tenantUserName = tenantService.getDomainUser(TEST_USER1, tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1554,7 +1558,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantUserName); + }, tenantUserName, tenantDomain); } } @@ -1600,8 +1604,7 @@ public class MultiTDemoTest extends TestCase for (final String tenantDomain : tenants) { final String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1640,7 +1643,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } } @@ -1653,7 +1656,7 @@ public class MultiTDemoTest extends TestCase { final String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1667,7 +1670,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } } @@ -1681,7 +1684,7 @@ public class MultiTDemoTest extends TestCase { final String tenantUserName = tenantService.getDomainUser(TEST_USER1, tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1696,13 +1699,11 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantUserName); + }, tenantUserName, tenantDomain); } } - - // TODO pending CLOUD-1351 fix - public void xtest20_ALF_12732() + public void test20_ALF_12732() { final String tenantDomain1 = TEST_RUN+".one.alf12732"; @@ -1714,7 +1715,7 @@ public class MultiTDemoTest extends TestCase { AuthenticationUtil.setFullyAuthenticatedUser(tenantAdminName); // note: since SiteServiceImpl.setupSitePermissions currently uses getCurrentUserName (rather than runAs) - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1734,7 +1735,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain1); } finally { @@ -1751,7 +1752,8 @@ public class MultiTDemoTest extends TestCase createTenant(tenantDomain2); String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain1); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1761,10 +1763,11 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain1); tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain2); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1774,7 +1777,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain2); } diff --git a/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java index 118be354d0..66266dcfeb 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java @@ -482,8 +482,8 @@ public class MultiTServiceImpl implements TenantService ParameterCheck.mandatory("RootPath", rootPath); ParameterCheck.mandatory("RootNodeRef", rootNodeRef); - String username = AuthenticationUtil.getFullyAuthenticatedUser(); - StoreRef storeRef = getName(username, rootNodeRef.getStoreRef()); + //String username = AuthenticationUtil.getFullyAuthenticatedUser(); + StoreRef storeRef = rootNodeRef.getStoreRef(); AuthenticationUtil.RunAsWork action = new GetRootNode(nodeService, searchService, namespaceService, rootPath, rootNodeRef, storeRef); return getBaseName(AuthenticationUtil.runAs(action, AuthenticationUtil.getSystemUserName())); diff --git a/source/java/org/alfresco/repo/tenant/TenantRoutingDataSource.java b/source/java/org/alfresco/repo/tenant/TenantRoutingDataSource.java index d8bd8b3656..d84fd631e2 100644 --- a/source/java/org/alfresco/repo/tenant/TenantRoutingDataSource.java +++ b/source/java/org/alfresco/repo/tenant/TenantRoutingDataSource.java @@ -124,11 +124,11 @@ public class TenantRoutingDataSource extends AbstractRoutingDataSource super.afterPropertiesSet(); // to update resolved data sources } - + @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { - return null; + throw new SQLFeatureNotSupportedException(); } } diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java index a2017aa35f..3673b65a5f 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java @@ -244,6 +244,58 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi return result; } + public List getThumbnailDefinitions(String sourceUrl, String mimetype, long sourceSize) + { + List thumbnailDefinitionsLimitsForMimetype = this.mimetypeMap.get(mimetype); + + if (thumbnailDefinitionsLimitsForMimetype == null) + { + boolean foundAtLeastOneTransformer = false; + thumbnailDefinitionsLimitsForMimetype = new ArrayList(7); + + for (ThumbnailDefinition thumbnailDefinition : this.thumbnailDefinitions.values()) + { + if (isThumbnailDefinitionAvailable(sourceUrl, mimetype, sourceSize, thumbnailDefinition)) + { + long maxSourceSizeBytes = getMaxSourceSizeBytes(mimetype, thumbnailDefinition); + if (maxSourceSizeBytes != 0) + { + thumbnailDefinitionsLimitsForMimetype.add(new ThumbnailDefinitionLimits(thumbnailDefinition, maxSourceSizeBytes)); + foundAtLeastOneTransformer = true; + } + } + } + + // If we have found no transformers for the given MIME type then we do + // not cache the empty list. We prevent this because we want to allow for + // transformers only coming online *during* system operation - as opposed + // to coming online during startup. + // + // An example of such a transient transformer would be those that use OpenOffice.org. + // It is possible that the system might start without OOo-based transformers + // being available. Therefore we must not cache an empty list for the relevant + // MIME types - otherwise this class would hide the fact that OOo (soffice) has + // been launched and that new transformers are available. + if (foundAtLeastOneTransformer) + { + this.mimetypeMap.put(mimetype, thumbnailDefinitionsLimitsForMimetype); + } + } + + // Only return ThumbnailDefinition for this specific source - may be limited on size. + List result = new ArrayList(thumbnailDefinitionsLimitsForMimetype.size()); + for (ThumbnailDefinitionLimits thumbnailDefinitionLimits: thumbnailDefinitionsLimitsForMimetype) + { + long maxSourceSizeBytes = thumbnailDefinitionLimits.getMaxSourceSizeBytes(); + if (sourceSize <= 0 || maxSourceSizeBytes < 0 || maxSourceSizeBytes >= sourceSize) + { + result.add(thumbnailDefinitionLimits.getThumbnailDefinition()); + } + } + + return result; + } + /** * * @param mimetype @@ -290,6 +342,34 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi } } + /** + * Checks to see if at this moment in time, the specified {@link ThumbnailDefinition} + * is able to thumbnail the source mimetype. Typically used with Thumbnail Definitions + * retrieved by name, and/or when dealing with transient {@link ContentTransformer}s. + * @param sourceUrl The URL of the source (optional) + * @param sourceMimeType The source mimetype + * @param sourceSize the size (in bytes) of the source. Use -1 if unknown. + * @param thumbnailDefinition The {@link ThumbnailDefinition} to check for + */ + public boolean isThumbnailDefinitionAvailable(String sourceUrl, String sourceMimeType, long sourceSize, ThumbnailDefinition thumbnailDefinition) + { + // Log the following getTransform() as trace so we can see the wood for the trees + boolean orig = TransformerDebug.setDebugOutput(false); + try + { + return this.contentService.getTransformer( + sourceUrl, + sourceMimeType, + sourceSize, + thumbnailDefinition.getMimetype(), thumbnailDefinition.getTransformationOptions() + ) != null; + } + finally + { + TransformerDebug.setDebugOutput(orig); + } + } + /** * Returns the maximum source size of any content that may transformed between the supplied * sourceMimetype and thumbnailDefinition's targetMimetype using its transformation options. diff --git a/source/java/org/alfresco/repo/webdav/LockStoreFactoryImpl.java b/source/java/org/alfresco/repo/webdav/LockStoreFactoryImpl.java new file mode 100644 index 0000000000..351e406b7c --- /dev/null +++ b/source/java/org/alfresco/repo/webdav/LockStoreFactoryImpl.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2005-2013 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.webdav; + +import java.util.concurrent.ConcurrentMap; + +import org.alfresco.repo.cluster.HazelcastInstanceFactory; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.util.PropertyCheck; + +import com.hazelcast.core.HazelcastInstance; + + +/** + * Default implementation of the {@link LockStoreFactory} interface. Creates {@link LockStore}s + * backed by a Hazelcast distributed Map if the clusterName property is set, + * otherwise it creates a non-clustered {@link SimpleLockStore}. + * + * @see LockStoreFactory + * @see LockStoreImpl + * @author Matt Ward + */ +public class LockStoreFactoryImpl implements LockStoreFactory +{ + private static final String HAZELCAST_MAP_NAME = "webdav-locks"; + private HazelcastInstanceFactory hazelcastInstanceFactory; + private String clusterName; + + /** + * This method should be used sparingly and the created {@link LockStore}s should be + * retained (this factory does not cache instances of them). + */ + @Override + public synchronized LockStore createLockStore() + { + if (!PropertyCheck.isValidPropertyString(clusterName)) + { + return new SimpleLockStore(); + } + else + { + HazelcastInstance instance = hazelcastInstanceFactory.getInstance(); + ConcurrentMap map = instance.getMap(HAZELCAST_MAP_NAME); + return new LockStoreImpl(map); + } + } + + /** + * @param hazelcastInstanceFactory the factory that will create a HazelcastInstance if required. + */ + public synchronized void setHazelcastInstanceFactory(HazelcastInstanceFactory hazelcastInstanceFactory) + { + this.hazelcastInstanceFactory = hazelcastInstanceFactory; + } + + /** + * @param clusterName the clusterName to set + */ + public synchronized void setClusterName(String clusterName) + { + this.clusterName = clusterName; + } +} diff --git a/source/java/org/alfresco/repo/webdav/WebDavServiceImpl.java b/source/java/org/alfresco/repo/webdav/WebDavServiceImpl.java index eb8ff584df..42fbd54af0 100644 --- a/source/java/org/alfresco/repo/webdav/WebDavServiceImpl.java +++ b/source/java/org/alfresco/repo/webdav/WebDavServiceImpl.java @@ -31,8 +31,6 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.webdav.WebDavService; import org.alfresco.service.namespace.QName; import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.extensions.surf.util.URLEncoder; /** @@ -46,8 +44,7 @@ import org.springframework.extensions.surf.util.URLEncoder; */ public class WebDavServiceImpl implements WebDavService { - public static final String WEBDAV_PREFIX = "webdav"; - private static Log logger = LogFactory.getLog(WebDavServiceImpl.class); + public static final String WEBDAV_PREFIX = "webdav"; private boolean enabled = false; private NodeService nodeService; private DictionaryService dictionaryService; diff --git a/source/java/org/alfresco/repo/workflow/AbstractMultitenantWorkflowTest.java b/source/java/org/alfresco/repo/workflow/AbstractMultitenantWorkflowTest.java index 999963b18a..a1d78440d3 100644 --- a/source/java/org/alfresco/repo/workflow/AbstractMultitenantWorkflowTest.java +++ b/source/java/org/alfresco/repo/workflow/AbstractMultitenantWorkflowTest.java @@ -29,6 +29,7 @@ import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.repo.tenant.TenantContextHolder; import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; @@ -81,6 +82,7 @@ public abstract class AbstractMultitenantWorkflowTest extends BaseSpringTest { // Run as User1 so tenant domain 1 AuthenticationUtil.setFullyAuthenticatedUser(user1); + TenantContextHolder.setTenantDomain(tenant1); List allDefs = workflowService.getAllDefinitions(); int allDefsSize = allDefs.size(); @@ -104,6 +106,7 @@ public abstract class AbstractMultitenantWorkflowTest extends BaseSpringTest // Switch to tenant2. AuthenticationUtil.setFullyAuthenticatedUser(user2); + TenantContextHolder.setTenantDomain(tenant2); // Check definition not visible on tenant2. try @@ -125,13 +128,14 @@ public abstract class AbstractMultitenantWorkflowTest extends BaseSpringTest // Switch back to tenant1. AuthenticationUtil.setFullyAuthenticatedUser(user1); + TenantContextHolder.setTenantDomain(tenant1); // Check the definition hasn't changed WorkflowDefinition definitionByName = workflowService.getDefinitionByName(definitionKey); assertEquals(definition.getId(), definitionByName.getId()); } - public void xtestQueryTasks() throws Exception + public void testQueryTasks() throws Exception { WorkflowTaskQuery query = new WorkflowTaskQuery(); @@ -139,6 +143,7 @@ public abstract class AbstractMultitenantWorkflowTest extends BaseSpringTest // Run as User1 so tenant domain 1 AuthenticationUtil.setFullyAuthenticatedUser(user1); + TenantContextHolder.setTenantDomain(tenant1); // Check no tasks to start with List tasks = workflowService.queryTasks(query); @@ -168,6 +173,7 @@ public abstract class AbstractMultitenantWorkflowTest extends BaseSpringTest //Switch to tenant2 AuthenticationUtil.setFullyAuthenticatedUser(user2); + TenantContextHolder.setTenantDomain(tenant2); // Tenant2 should not find the task tasks = workflowService.queryTasks(query); @@ -224,7 +230,6 @@ public abstract class AbstractMultitenantWorkflowTest extends BaseSpringTest { if (! tenantAdminService.existsTenant(tenantDomain)) { - //tenantAdminService.createTenant(tenantDomain, DEFAULT_ADMIN_PW.toCharArray(), ROOT_DIR + "/" + tenantDomain); tenantAdminService.createTenant(tenantDomain, (DEFAULT_ADMIN_PW+" "+tenantDomain).toCharArray(), null); // use default root dir } return tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); diff --git a/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java b/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java index cd9b9a61eb..92ee9a05c0 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java @@ -27,8 +27,8 @@ import java.util.Map; import org.alfresco.repo.i18n.MessageService; import org.alfresco.repo.jscript.ScriptNode; -import org.alfresco.repo.tenant.MultiTServiceImpl; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.repository.NodeRef; @@ -370,10 +370,18 @@ public class WorkflowObjectFactory { processKey = getLocalEngineId(defName); } - tenantService.checkDomain(processKey); + if(tenantService.isTenantName(processKey)) + { + tenantService.checkDomain(processKey); + } } } + public boolean isDefaultDomain() + { + return TenantService.DEFAULT_DOMAIN.equals(tenantService.getCurrentUserDomain()); + } + public List filterByDomain(Collection values, final Function processKeyGetter) { final String currentDomain = tenantService.getCurrentUserDomain(); @@ -382,8 +390,8 @@ public class WorkflowObjectFactory public Boolean apply(T value) { String key = processKeyGetter.apply(value); - String domain = MultiTServiceImpl.getMultiTenantDomainName(key); - return (currentDomain.equals(domain)); + String domain = TenantUtil.getTenantDomain(key); + return currentDomain.equals(domain) || domain.equals(TenantService.DEFAULT_DOMAIN); } }); } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java index 6448ca8b91..7e273f7404 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java @@ -89,6 +89,7 @@ public class WorkflowServiceImpl implements WorkflowService private WorkflowAdminService workflowAdminService; private int maxAuthoritiesForPooledTasks = 100; private int maxPooledTasks = -1; + private boolean deployWorkflowsInTenant = false; /** * Sets the Authority Service @@ -591,6 +592,16 @@ public class WorkflowServiceImpl implements WorkflowService } return result; } + + public void setMultiTenantWorkflowDeploymentEnabled(boolean deployWorkflowsInTenant) + { + this.deployWorkflowsInTenant = deployWorkflowsInTenant; + } + + public boolean isMultiTenantWorkflowDeploymentEnabled() + { + return deployWorkflowsInTenant; + } private Map> batchByEngineId(List workflowIds) { diff --git a/source/java/org/alfresco/repo/workflow/WorkflowTestSuite.java b/source/java/org/alfresco/repo/workflow/WorkflowTestSuite.java index 89cae5a23b..844525efc9 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowTestSuite.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowTestSuite.java @@ -69,6 +69,7 @@ public class WorkflowTestSuite extends TestSuite // These tests use a different Spring config. suite.addTestSuite( ActivitiMultitenantWorkflowTest.class ); + suite.addTestSuite( JbpmMultitenantWorkflowTest.class ); // Note the following workflow tests are not included in this sutie: diff --git a/source/java/org/alfresco/repo/workflow/activiti/AbstractActivitiComponentTest.java b/source/java/org/alfresco/repo/workflow/activiti/AbstractActivitiComponentTest.java index 4418d51542..35bf83af86 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/AbstractActivitiComponentTest.java +++ b/source/java/org/alfresco/repo/workflow/activiti/AbstractActivitiComponentTest.java @@ -44,9 +44,9 @@ import org.activiti.engine.runtime.ProcessInstance; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.i18n.MessageService; +import org.alfresco.repo.model.Repository; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authority.AuthorityDAO; -import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.workflow.WorkflowModel; import org.alfresco.repo.workflow.activiti.variable.ScriptNodeVariableType; import org.alfresco.service.ServiceRegistry; @@ -123,9 +123,6 @@ public class AbstractActivitiComponentTest @Resource protected MessageService messageService; - @Resource - protected TenantService tenantService; - @Resource(name="NamespaceService") protected NamespaceService namespaceService; @@ -203,7 +200,6 @@ public class AbstractActivitiComponentTest @Before public void setUp() throws Exception { - mockTenantService(); mockNamespaceService(); mockDictionaryService(); mockNodeService(); @@ -213,8 +209,9 @@ public class AbstractActivitiComponentTest mockAuthorityDAO(); mockServiceRegistry(); - workflowEngine.setCompanyHomeStore("workspace://SpacesStore"); - workflowEngine.setCompanyHomePath("spaces.company_home.childname"); + Repository repoHelper = mock(Repository.class); + when(repoHelper.getCompanyHome()).thenReturn(companyHomeNode); + workflowEngine.setRepositoryHelper(repoHelper); // Also add custom type // TODO: Should come from configuration @@ -379,29 +376,6 @@ public class AbstractActivitiComponentTest namespaceService.registerNamespace("test", "http://test"); } - private void mockTenantService() - { - when(tenantService.getBaseName(anyString())).thenAnswer(new Answer() - { - public String answer(InvocationOnMock invocation) throws Throwable - { - Object arg= invocation.getArguments()[0]; - return (String) arg; - } - }); - - when(tenantService.getName(anyString())).thenAnswer(new Answer() - { - public String answer(InvocationOnMock invocation) throws Throwable - { - Object arg= invocation.getArguments()[0]; - return (String) arg; - } - }); - - when(tenantService.getCurrentUserDomain()).thenReturn(""); - } - @After public void tearDown() { diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiConstants.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiConstants.java index a651d7dd52..c66e31a915 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/ActivitiConstants.java +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiConstants.java @@ -35,7 +35,6 @@ public interface ActivitiConstants public static final String DEFAULT_TRANSITION_NAME = "Next"; public static final String DEFAULT_TRANSITION_DESCRIPTION = "Default Transition"; - public static final String START_TASK_PROPERTY_PREFIX = "_start_"; public static final String USER_TASK_NODE_TYPE = "userTask"; public static final String PROP_TASK_FORM_KEY = "taskFormKey"; @@ -46,4 +45,7 @@ public interface ActivitiConstants public static final String SERVICE_REGISTRY_BEAN_KEY = "services"; public static final String PROCESS_INSTANCE_IMAGE_FORMAT = "png"; + + public static final String VAR_TENANT_DOMAIN = "_tenant_domain"; + } diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiEngineInitializer.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiEngineInitializer.java index 5c0a115c2d..3d549ec8c9 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/ActivitiEngineInitializer.java +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiEngineInitializer.java @@ -20,41 +20,44 @@ package org.alfresco.repo.workflow.activiti; import org.activiti.engine.ProcessEngine; import org.activiti.engine.impl.ProcessEngineImpl; -import org.alfresco.repo.domain.schema.SchemaAvailableEvent; import org.alfresco.service.cmr.workflow.WorkflowAdminService; import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; /** - * Class that waits for an {@link SchemaAvailableEvent} to start the activiti - * job executor. + * Bean that starts up the Activiti job executor as part of the + * bootstrap process. * * @author Frederik Heremans * @since 4.0 */ -public class ActivitiEngineInitializer implements ApplicationListener +public class ActivitiEngineInitializer extends AbstractLifecycleBean { private ProcessEngine processEngine; private WorkflowAdminService workflowAdminService; - public void setProcessEngine(ProcessEngine processEngine) - { - this.processEngine = processEngine; - } - public void setWorkflowAdminService(WorkflowAdminService workflowAdminService) { this.workflowAdminService = workflowAdminService; } @Override - public void onApplicationEvent(ApplicationEvent event) - { - if (event instanceof SchemaAvailableEvent && processEngine instanceof ProcessEngineImpl && - workflowAdminService.isEngineEnabled(ActivitiConstants.ENGINE_ID)) - { - // Start the job-executor - ((ProcessEngineImpl)processEngine).getProcessEngineConfiguration().getJobExecutor().start(); - } + protected void onBootstrap(ApplicationEvent event) { + + this.processEngine = getApplicationContext().getBean(ProcessEngine.class); + + if (workflowAdminService.isEngineEnabled(ActivitiConstants.ENGINE_ID)) + { + ((ProcessEngineImpl)processEngine).getProcessEngineConfiguration().getJobExecutor().start(); + } } + + @Override + protected void onShutdown(ApplicationEvent event) { + if(workflowAdminService.isEngineEnabled(ActivitiConstants.ENGINE_ID) && + ((ProcessEngineImpl)processEngine).getProcessEngineConfiguration().getJobExecutor().isActive()) + { + ((ProcessEngineImpl)processEngine).getProcessEngineConfiguration().getJobExecutor().shutdown(); + } + } } diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiMultitenantWorkflowTest.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiMultitenantWorkflowTest.java index c568644097..919c071abf 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/ActivitiMultitenantWorkflowTest.java +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiMultitenantWorkflowTest.java @@ -29,6 +29,7 @@ import org.alfresco.service.cmr.workflow.WorkflowDefinition; import org.alfresco.service.cmr.workflow.WorkflowDeployment; import org.alfresco.service.namespace.QName; + /** * @author Nick Smith * @since 4.0 diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiTypeConverter.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiTypeConverter.java index 08645b77cc..dcf7a97c19 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/ActivitiTypeConverter.java +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiTypeConverter.java @@ -19,6 +19,9 @@ package org.alfresco.repo.workflow.activiti; +import static org.alfresco.repo.workflow.activiti.ActivitiConstants.DEFAULT_TRANSITION_DESCRIPTION; +import static org.alfresco.repo.workflow.activiti.ActivitiConstants.DEFAULT_TRANSITION_NAME; + import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; @@ -30,12 +33,9 @@ import java.util.List; import java.util.Map; import java.util.Set; -import static org.alfresco.repo.workflow.activiti.ActivitiConstants.DEFAULT_TRANSITION_NAME; -import static org.alfresco.repo.workflow.activiti.ActivitiConstants.DEFAULT_TRANSITION_DESCRIPTION; import org.activiti.engine.FormService; import org.activiti.engine.HistoryService; import org.activiti.engine.ProcessEngine; -import org.activiti.engine.RepositoryService; import org.activiti.engine.RuntimeService; import org.activiti.engine.form.StartFormData; import org.activiti.engine.form.TaskFormData; @@ -56,6 +56,7 @@ import org.activiti.engine.repository.ProcessDefinition; import org.activiti.engine.runtime.Execution; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; +import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.repo.workflow.WorkflowObjectFactory; import org.alfresco.repo.workflow.activiti.properties.ActivitiPropertyConverter; import org.alfresco.service.cmr.workflow.WorkflowDefinition; @@ -81,8 +82,6 @@ public class ActivitiTypeConverter private static final String TRANSITION_SUFFIX= ".transition"; private static final String DEFAULT_TRANSITION_KEY= "bpm_businessprocessmodel.transition"; - - private final RepositoryService repoService; private final RuntimeService runtimeService; private final FormService formService; private final HistoryService historyService; @@ -93,15 +92,14 @@ public class ActivitiTypeConverter public ActivitiTypeConverter(ProcessEngine processEngine, WorkflowObjectFactory factory, - ActivitiPropertyConverter propertyConverter) + ActivitiPropertyConverter propertyConverter, boolean deployWorkflowsInTenant) { - this.repoService = processEngine.getRepositoryService(); this.runtimeService = processEngine.getRuntimeService(); this.formService = processEngine.getFormService(); this.historyService = processEngine.getHistoryService(); this.factory = factory; this.propertyConverter =propertyConverter; - this.activitiUtil = new ActivitiUtil(processEngine); + this.activitiUtil = new ActivitiUtil(processEngine, deployWorkflowsInTenant); } public List filterByDomainAndConvert(List values, Function processKeyGetter) @@ -120,10 +118,7 @@ public class ActivitiTypeConverter if(deployment == null) return null; - List processDefs = repoService.createProcessDefinitionQuery() - .deploymentId(deployment.getId()) - .list(); - ProcessDefinition processDef = processDefs.get(0); + ProcessDefinition processDef = activitiUtil.getProcessDefinitionForDeployment(deployment.getId()); WorkflowDefinition wfDef = convert(processDef); return factory.createDeployment(wfDef); } @@ -144,7 +139,7 @@ public class ActivitiTypeConverter String defaultTitle = definition.getName(); String startTaskName = null; - StartFormData startFormData = formService.getStartFormData(definition.getId()); + StartFormData startFormData = getStartFormData(defId, defName); if(startFormData != null) { startTaskName = startFormData.getFormKey(); @@ -158,6 +153,11 @@ public class ActivitiTypeConverter defName, version, defaultTitle, null, startTask); } + + private StartFormData getStartFormData(final String definitionId, String processKey) + { + return formService.getStartFormData(definitionId); + } public WorkflowTaskDefinition getTaskDefinition(PvmActivity activity, String taskFormKey, String processKey, boolean isStart) { @@ -495,6 +495,12 @@ public class ActivitiTypeConverter private WorkflowTask getVirtualStartTask(ProcessInstance processInstance, boolean inProgress) { String processInstanceId = processInstance.getId(); + + if (!activitiUtil.isMultiTenantWorkflowDeploymentEnabled() && !isCorrectTenantRuntime(processInstanceId)) + { + return null; + } + String id = ActivitiConstants.START_TASK_PREFIX + processInstanceId; WorkflowTaskState state = null; @@ -510,10 +516,12 @@ public class ActivitiTypeConverter WorkflowPath path = convert((Execution)processInstance); // Convert start-event to start-task Node - ReadOnlyProcessDefinition procDef = activitiUtil.getDeployedProcessDefinition(processInstance.getProcessDefinitionId()); + String definitionId = processInstance.getProcessDefinitionId(); + ReadOnlyProcessDefinition procDef = activitiUtil.getDeployedProcessDefinition(definitionId); WorkflowNode startNode = convert(procDef.getInitial(), true); - StartFormData startFormData = formService.getStartFormData(processInstance.getProcessDefinitionId()); + String key = ((ProcessDefinition)procDef).getKey(); + StartFormData startFormData = getStartFormData(definitionId, key); String taskDefId = null; if(startFormData != null) { @@ -524,7 +532,7 @@ public class ActivitiTypeConverter // Add properties based on HistoricProcessInstance HistoricProcessInstance historicProcessInstance = historyService .createHistoricProcessInstanceQuery() - .processInstanceId(processInstance.getId()) + .processInstanceId(processInstanceId) .singleResult(); Map properties = propertyConverter.getStartTaskProperties(historicProcessInstance, taskDefId, !inProgress); @@ -536,6 +544,38 @@ public class ActivitiTypeConverter return factory.createTask(id, taskDef, taskDef.getId(), defaultTitle, defaultDescription, state, path, properties); } + + public boolean isCorrectTenantRuntime(String processInstanceId, boolean isRuntime) + { + // Runtime domain only applicable in case tenant-aware deployment is turned off + if(!activitiUtil.isMultiTenantWorkflowDeploymentEnabled()) { + if (isRuntime) + { + return isCorrectTenantRuntime(processInstanceId); + } + else + { + return isCorrectTenantHistoric(processInstanceId); + } + } + return true; + } + + public boolean isCorrectTenantRuntime(String processInstanceId) + { + return runtimeService.createProcessInstanceQuery() + .processInstanceId(processInstanceId) + .variableValueEquals(ActivitiConstants.VAR_TENANT_DOMAIN, TenantUtil.getCurrentDomain()) + .count()>0; + } + + public boolean isCorrectTenantHistoric(String processInstanceId) + { + return historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId) + .variableValueEquals(ActivitiConstants.VAR_TENANT_DOMAIN, TenantUtil.getCurrentDomain()) + .count()>0; + } private WorkflowTask getVirtualStartTask(HistoricProcessInstance historicProcessInstance) { @@ -543,8 +583,13 @@ public class ActivitiTypeConverter { return null; } - String processInstanceId = historicProcessInstance.getId(); + + if (!activitiUtil.isMultiTenantWorkflowDeploymentEnabled() && false == isCorrectTenantHistoric(processInstanceId)) + { + return null; + } + String id = ActivitiConstants.START_TASK_PREFIX + processInstanceId; // Since the process instance is complete the Start Task must be complete! diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiUtil.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiUtil.java index a4b31809c2..7b87a4b144 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/ActivitiUtil.java +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiUtil.java @@ -31,6 +31,7 @@ import org.activiti.engine.TaskService; import org.activiti.engine.form.StartFormData; import org.activiti.engine.history.HistoricProcessInstance; import org.activiti.engine.history.HistoricTaskInstance; +import org.activiti.engine.history.HistoricTaskInstanceQuery; import org.activiti.engine.impl.RepositoryServiceImpl; import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity; import org.activiti.engine.impl.pvm.ReadOnlyProcessDefinition; @@ -38,6 +39,8 @@ import org.activiti.engine.repository.ProcessDefinition; import org.activiti.engine.runtime.Execution; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; +import org.activiti.engine.task.TaskQuery; +import org.alfresco.repo.tenant.TenantUtil; /** * @author Nick Smith @@ -51,8 +54,9 @@ public class ActivitiUtil private final TaskService taskService; private final FormService formService; private final ManagementService managementService; + private boolean deployWorkflowsInTenant; - public ActivitiUtil(ProcessEngine engine) + public ActivitiUtil(ProcessEngine engine, boolean deployWorkflowsInTenant) { this.repoService = engine.getRepositoryService(); this.runtimeService = engine.getRuntimeService(); @@ -60,16 +64,31 @@ public class ActivitiUtil this.historyService = engine.getHistoryService(); this.formService = engine.getFormService(); this.managementService = engine.getManagementService(); + this.deployWorkflowsInTenant = deployWorkflowsInTenant; } public ProcessDefinition getProcessDefinition(String definitionId) { - ProcessDefinition procDef = repoService.createProcessDefinitionQuery() + return repoService.createProcessDefinitionQuery() .processDefinitionId(definitionId) .singleResult(); - return procDef; } + public ProcessDefinition getProcessDefinitionByKey(String processKey) + { + return repoService.createProcessDefinitionQuery() + .processDefinitionKey(processKey) + .latestVersion() + .singleResult(); + } + + public ProcessDefinition getProcessDefinitionForDeployment(String deploymentId) + { + return repoService.createProcessDefinitionQuery() + .deploymentId(deploymentId) + .singleResult(); + } + public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() @@ -79,7 +98,11 @@ public class ActivitiUtil public Task getTaskInstance(String taskId) { - return taskService.createTaskQuery().taskId(taskId).singleResult(); + TaskQuery taskQuery = taskService.createTaskQuery().taskId(taskId); + if(!deployWorkflowsInTenant) { + taskQuery.processVariableValueEquals(ActivitiConstants.VAR_TENANT_DOMAIN, TenantUtil.getCurrentDomain()); + } + return taskQuery.singleResult(); } public HistoricProcessInstance getHistoricProcessInstance(String id) @@ -183,6 +206,16 @@ public class ActivitiUtil */ public HistoricTaskInstance getHistoricTaskInstance(String localId) { - return historyService.createHistoricTaskInstanceQuery().taskId(localId).singleResult(); + HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery() + .taskId(localId); + if(!deployWorkflowsInTenant) { + taskQuery.processVariableValueEquals(ActivitiConstants.VAR_TENANT_DOMAIN, TenantUtil.getCurrentDomain()); + } + return taskQuery.singleResult(); } + + public boolean isMultiTenantWorkflowDeploymentEnabled() + { + return deployWorkflowsInTenant; + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java index f088443b2e..219861b1e5 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java @@ -59,6 +59,7 @@ import org.activiti.engine.impl.pvm.ReadOnlyProcessDefinition; import org.activiti.engine.impl.pvm.process.ActivityImpl; import org.activiti.engine.repository.Deployment; import org.activiti.engine.repository.ProcessDefinition; +import org.activiti.engine.repository.ProcessDefinitionQuery; import org.activiti.engine.runtime.Execution; import org.activiti.engine.runtime.Job; import org.activiti.engine.runtime.ProcessInstance; @@ -66,8 +67,10 @@ import org.activiti.engine.task.Task; import org.activiti.engine.task.TaskQuery; import org.alfresco.model.ContentModel; import org.alfresco.repo.i18n.MessageService; +import org.alfresco.repo.model.Repository; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.repo.workflow.BPMEngine; import org.alfresco.repo.workflow.WorkflowAuthorityManager; import org.alfresco.repo.workflow.WorkflowConstants; @@ -81,9 +84,7 @@ import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; -import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.workflow.LazyActivitiWorkflowTask; import org.alfresco.service.cmr.workflow.WorkflowDefinition; @@ -130,54 +131,35 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine private static final String ERR_GET_ALL_DEFS_BY_NAME = "activiti.engine.get.all.workflow.definitions.by.name.error"; private static final String ERR_GET_DEF_IMAGE = "activiti.engine.get.workflow.definition.image.error"; private static final String ERR_GET_DEF_UNEXISTING_IMAGE = "activiti.engine.get.workflow.definition.unexisting.image.error"; -// private static final String ERR_GET_TASK_DEFS = "activiti.engine.get.task.definitions.error"; -// private static final String ERR_GET_PROCESS_DEF = "activiti.engine.get.process.definition.error"; private static final String ERR_START_WORKFLOW = "activiti.engine.start.workflow.error"; private static final String ERR_GET_WORKFLOW_INSTS = "activiti.engine.get.workflows.error"; private static final String ERR_GET_ACTIVE_WORKFLOW_INSTS = "activiti.engine.get.active.workflows.error"; private static final String ERR_GET_COMPLETED_WORKFLOW_INSTS = "activiti.engine.get.completed.workflows.error"; -// private static final String ERR_GET_WORKFLOW_INST_BY_ID = "activiti.engine.get.workflow.instance.by.id.error"; -// private static final String ERR_GET_PROCESS_INSTANCE = "activiti.engine.get.process.instance.error"; private static final String ERR_GET_WORKFLOW_PATHS = "activiti.engine.get.workflow.paths.error"; -// private static final String ERR_GET_PATH_PROPERTIES = "activiti.engine.get.path.properties.error"; private static final String ERR_CANCEL_WORKFLOW = "activiti.engine.cancel.workflow.error"; private static final String ERR_CANCEL_UNEXISTING_WORKFLOW = "activiti.engine.cancel.unexisting.workflow.error"; private static final String ERR_DELETE_WORKFLOW = "activiti.engine.delete.workflow.error"; private static final String ERR_DELETE_UNEXISTING_WORKFLOW = "activiti.engine.delete.unexisting.workflow.error"; -// private static final String ERR_SIGNAL_TRANSITION = "activiti.engine.signal.transition.error"; protected static final String ERR_FIRE_EVENT_NOT_SUPPORTED = "activiti.engine.event.unsupported"; -// private static final String ERR_FIRE_EVENT = "activiti.engine.fire.event.error"; private static final String ERR_GET_TASKS_FOR_PATH = "activiti.engine.get.tasks.for.path.error"; -// private static final String ERR_GET_TASKS_FOR_UNEXISTING_PATH = "activiti.engine.get.tasks.for.unexisting.path.error"; private static final String ERR_GET_TIMERS = "activiti.engine.get.timers.error"; protected static final String ERR_FIND_COMPLETED_TASK_INSTS = "activiti.engine.find.completed.task.instances.error"; -// private static final String ERR_COMPILE_PROCESS_DEF_zip = "activiti.engine.compile.process.definition.zip.error"; -// private static final String ERR_COMPILE_PROCESS_DEF_XML = "activiti.engine.compile.process.definition.xml.error"; -// private static final String ERR_COMPILE_PROCESS_DEF_UNSUPPORTED = "activiti.engine.compile.process.definition.unsupported.error"; -// private static final String ERR_GET_activiti_ID = "activiti.engine.get.activiti.id.error"; private static final String ERR_GET_WORKFLOW_TOKEN_INVALID = "activiti.engine.get.workflow.token.invalid"; private static final String ERR_GET_WORKFLOW_TOKEN_NULL = "activiti.engine.get.workflow.token.is.null"; - private static final String ERR_GET_COMPANY_HOME_INVALID = "activiti.engine.get.company.home.invalid"; - private static final String ERR_GET_COMPANY_HOME_MULTIPLE = "activiti.engine.get.company.home.multiple"; // Task Component Messages private static final String ERR_GET_ASSIGNED_TASKS = "activiti.engine.get.assigned.tasks.error"; private static final String ERR_GET_POOLED_TASKS = "activiti.engine.get.pooled.tasks.error"; -// private static final String ERR_QUERY_TASKS = "activiti.engine.query.tasks.error"; -// private static final String ERR_GET_TASK_INST = "activiti.engine.get.task.instance.error"; private static final String ERR_UPDATE_TASK = "activiti.engine.update.task.error"; private static final String ERR_UPDATE_TASK_UNEXISTING = "activiti.engine.update.task.unexisting.error"; private static final String ERR_UPDATE_START_TASK = "activiti.engine.update.starttask.illegal.error"; -// private static final String ERR_END_TASK = "activiti.engine.end.task.error"; private static final String ERR_END_UNEXISTING_TASK = "activiti.engine.end.task.unexisting.error"; private static final String ERR_GET_TASK_BY_ID = "activiti.engine.get.task.by.id.error"; private static final String ERR_END_TASK_INVALID_TRANSITION = "activiti.engine.end.task.invalid.transition"; + + public static final QName QNAME_INITIATOR = QName.createQName(NamespaceService.DEFAULT_URI, WorkflowConstants.PROP_INITIATOR); - private final static String WORKFLOW_TOKEN_SEPERATOR = "\\$"; - - - private RepositoryService repoService; private RuntimeService runtimeService; private TaskService taskService; @@ -188,7 +170,6 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine private DictionaryService dictionaryService; private NodeService nodeService; - private SearchService unprotectedSearchService; private PersonService personService; private ActivitiTypeConverter typeConverter; private ActivitiPropertyConverter propertyConverter; @@ -199,10 +180,7 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine private MessageService messageService; private TenantService tenantService; private NamespaceService namespaceService; - - private String companyHomePath; - private StoreRef companyHomeStore; - + private Repository repositoryHelper; public ActivitiWorkflowEngine() { @@ -330,11 +308,6 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine { String resourceName = GUID.generate() + BpmnDeployer.BPMN_RESOURCE_SUFFIXES[0]; - if(tenantService.isEnabled()) - { - name = tenantService.getName(name); - } - Deployment deployment = repoService.createDeployment() .addInputStream(resourceName, workflowDefinition) .name(name) @@ -344,7 +317,7 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine // not exposed return typeConverter.convert(deployment); } - catch(ActivitiException ae) + catch(Exception ae) { String msg = messageService.getMessage(ERR_DEPLOY_WORKFLOW); throw new WorkflowException(msg, ae); @@ -433,8 +406,12 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine { try { - List definitions = repoService.createProcessDefinitionQuery().list(); - return getValidWorkflowDefinitions(definitions); + ProcessDefinitionQuery query = repoService.createProcessDefinitionQuery(); + if(activitiUtil.isMultiTenantWorkflowDeploymentEnabled() && !TenantUtil.isCurrentDomainDefault()) + { + query.processDefinitionKeyLike("@" + TenantUtil.getCurrentDomain() + "%"); + } + return getValidWorkflowDefinitions(query.list()); } catch (ActivitiException ae) { @@ -454,7 +431,7 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine List definitions = repoService.createProcessDefinitionQuery() .processDefinitionKey(key) .list(); - return typeConverter.convert(definitions); + return getValidWorkflowDefinitions(definitions); } catch (ActivitiException ae) { @@ -490,6 +467,12 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine ProcessDefinition procDef = repoService.createProcessDefinitionQuery() .processDefinitionId(localId ) .singleResult(); + + if(activitiUtil.isMultiTenantWorkflowDeploymentEnabled() && procDef != null) + { + factory.checkDomain(procDef.getKey()); + } + return typeConverter.convert(procDef); } catch (ActivitiException ae) @@ -506,11 +489,12 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine { try { - String key =factory.getDomainProcessKey(workflowName); - ProcessDefinition definition = repoService.createProcessDefinitionQuery() - .processDefinitionKey(key) - .latestVersion() - .singleResult(); + String key = factory.getLocalEngineId(workflowName); + if(activitiUtil.isMultiTenantWorkflowDeploymentEnabled()) + { + key = factory.getDomainProcessKey(workflowName); + } + ProcessDefinition definition = activitiUtil.getProcessDefinitionByKey(key); return typeConverter.convert(definition); } catch (ActivitiException ae) @@ -568,10 +552,12 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine { try { - List definitions = repoService.createProcessDefinitionQuery() - .latestVersion() - .list(); - return getValidWorkflowDefinitions(definitions); + ProcessDefinitionQuery query = repoService.createProcessDefinitionQuery().latestVersion(); + if(activitiUtil.isMultiTenantWorkflowDeploymentEnabled() && !TenantUtil.isCurrentDomainDefault()) + { + query.processDefinitionKeyLike("@" + TenantUtil.getCurrentDomain() + "%"); + } + return getValidWorkflowDefinitions(query.list()); } catch (ActivitiException ae) { @@ -657,7 +643,7 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine try { // Extract the Activiti ID from the path - String executionId = getExecutionIdFromPath(pathId); + String executionId = createLocalId(pathId); if (executionId == null) { throw new WorkflowException(messageService.getMessage(ERR_GET_WORKFLOW_TOKEN_INVALID, pathId)); @@ -670,11 +656,15 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine throw new WorkflowException(messageService.getMessage(ERR_GET_WORKFLOW_TOKEN_NULL, pathId)); } + String processInstanceId = execution.getProcessInstanceId(); + ArrayList resultList = new ArrayList(); + if(!activitiUtil.isMultiTenantWorkflowDeploymentEnabled() && false == typeConverter.isCorrectTenantRuntime(processInstanceId)) + { + return resultList; //Wrong tenant + } // Check if workflow's start task has been completed. If not, return // the virtual task // Otherwise, just return the runtime Activiti tasks - String processInstanceId = execution.getProcessInstanceId(); - ArrayList resultList = new ArrayList(); if (typeConverter.isStartTaskActive(processInstanceId)) { resultList.add(typeConverter.getVirtualStartTask(processInstanceId, true)); @@ -696,19 +686,6 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine } } - protected String getExecutionIdFromPath(String workflowPath) - { - if(workflowPath != null) - { - String[] parts = workflowPath.split(WORKFLOW_TOKEN_SEPERATOR); - if(parts.length != 2) - { - throw new WorkflowException(messageService.getMessage(ERR_GET_WORKFLOW_TOKEN_INVALID, workflowPath)); - } - return parts[1]; - } - return null; - } /** * {@inheritDoc} @@ -867,24 +844,12 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine */ public boolean isDefinitionDeployed(InputStream workflowDefinition, String mimetype) { - String key = null; try { - key = getProcessKey(workflowDefinition); + String key = getProcessKey(workflowDefinition); + return null != activitiUtil.getProcessDefinitionByKey(key); } - catch (Exception e) - { - throw new WorkflowException(e.getMessage(), e); - } - - try - { - long count = repoService.createProcessDefinitionQuery() - .processDefinitionKey(key ) - .count(); - return count>0; - } - catch (ActivitiException ae) + catch (Exception ae) { String msg = messageService.getMessage(ERR_IS_WORKFLOW_DEPLOYED); throw new WorkflowException(msg, ae); @@ -910,8 +875,16 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine { throw new IllegalAccessError("The process definition does not have an id!"); } - String key = idAttrib.getNodeValue(); - return factory.getDomainProcessKey(key); + + if(activitiUtil.isMultiTenantWorkflowDeploymentEnabled()) + { + // Workflow-definition is deployed tenant-aware, key should be altered + return factory.getDomainProcessKey(idAttrib.getNodeValue()); + } + else + { + return idAttrib.getNodeValue(); + } } finally { @@ -968,6 +941,12 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine } } + if(!activitiUtil.isMultiTenantWorkflowDeploymentEnabled()) + { + // Specify which tenant domain the workflow was started in using a variable + variables.put(ActivitiConstants.VAR_TENANT_DOMAIN, TenantUtil.getCurrentDomain()); + } + // Start the process-instance ProcessInstance instance = runtimeService.startProcessInstanceById(processDefId, variables); if(instance.isEnded()) @@ -1137,7 +1116,7 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine } }); } - + /** * Gets the Company Home * @@ -1145,30 +1124,7 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine */ private NodeRef getCompanyHome() { - if (tenantService.isEnabled()) - { - try - { - return tenantService.getRootNode(nodeService, unprotectedSearchService, namespaceService, - companyHomePath, nodeService.getRootNode(companyHomeStore)); - } - catch (RuntimeException re) - { - String msg = messageService.getMessage(ERR_GET_COMPANY_HOME_INVALID, companyHomePath); - throw new IllegalStateException(msg, re); - } - } - else - { - List refs = unprotectedSearchService.selectNodes(nodeService.getRootNode(companyHomeStore), - companyHomePath, null, namespaceService, false); - if (refs.size() != 1) - { - String msg = messageService.getMessage(ERR_GET_COMPANY_HOME_MULTIPLE, companyHomePath, refs.size()); - throw new IllegalStateException(msg); - } - return refs.get(0); - } + return repositoryHelper.getCompanyHome(); } /** @@ -1219,35 +1175,11 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine } /** - * Sets the Company Home Path - * - * @param companyHomePath + * @param repositoryHelper the repositoryHelper to set */ - public void setCompanyHomePath(String companyHomePath) + public void setRepositoryHelper(Repository repositoryHelper) { - this.companyHomePath = companyHomePath; - } - - /** - * Sets the Company Home Store - * - * @param companyHomeStore - */ - public void setCompanyHomeStore(String companyHomeStore) - { - this.companyHomeStore = new StoreRef(companyHomeStore); - } - - /** - * Set the unprotected search service - so we can find the node ref for - * company home when folk do not have read access to company home TODO: - * review use with DC - * - * @param unprotectedSearchService - */ - public void setUnprotectedSearchService(SearchService unprotectedSearchService) - { - this.unprotectedSearchService = unprotectedSearchService; + this.repositoryHelper = repositoryHelper; } /** @@ -1395,9 +1327,14 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine { if(state == WorkflowTaskState.IN_PROGRESS) { - List tasks = taskService.createTaskQuery() - .taskAssignee(authority) - .list(); + TaskQuery taskQuery = taskService.createTaskQuery() + .taskAssignee(authority); + + if(!activitiUtil.isMultiTenantWorkflowDeploymentEnabled()) + { + taskQuery.processVariableValueEquals(ActivitiConstants.VAR_TENANT_DOMAIN, TenantUtil.getCurrentDomain()); + } + List tasks = taskQuery.list(); List resultingTasks = new ArrayList(); for(Task task : tasks) { @@ -1416,10 +1353,15 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine } else { - List historicTasks = historyService.createHistoricTaskInstanceQuery() - .taskAssignee(authority) - .finished() - .list(); + HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery() + .taskAssignee(authority) + .finished(); + + if(activitiUtil.isMultiTenantWorkflowDeploymentEnabled()) + { + taskQuery.processVariableValueEquals(ActivitiConstants.VAR_TENANT_DOMAIN, TenantUtil.getCurrentDomain()); + } + List historicTasks =taskQuery.list(); List resultingTasks = new ArrayList(); for(HistoricTaskInstance historicTask : historicTasks) { @@ -1614,6 +1556,13 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine { // Add task name TaskQuery taskQuery = taskService.createTaskQuery(); + + if (!activitiUtil.isMultiTenantWorkflowDeploymentEnabled()) + { + // Filter by tenant domain. + taskQuery.processVariableValueEquals(ActivitiConstants.VAR_TENANT_DOMAIN, TenantUtil.getCurrentDomain()); + } + if (query.getTaskName() != null) { // Task 'key' is stored as variable on task @@ -1688,10 +1637,14 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine for(Entry customProperty : processCustomProps.entrySet()) { String name =factory.mapQNameToName(customProperty.getKey()); - - // Perform minimal property conversions - Object converted = propertyConverter.convertPropertyToValue(customProperty.getValue()); - taskQuery.processVariableValueEquals(name, converted); + + // Exclude the special "VAR_TENANT_DOMAIN" variable, this cannot be queried by users + if(name != ActivitiConstants.VAR_TENANT_DOMAIN) + { + // Perform minimal property conversions + Object converted = propertyConverter.convertPropertyToValue(customProperty.getValue()); + taskQuery.processVariableValueEquals(name, converted); + } } } @@ -1819,6 +1772,12 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine private List queryHistoricTasks(WorkflowTaskQuery query) { HistoricTaskInstanceQuery historicQuery = historyService.createHistoricTaskInstanceQuery().finished(); + + if (!activitiUtil.isMultiTenantWorkflowDeploymentEnabled()) + { + // Filter by tenant domain + historicQuery.processVariableValueEquals(ActivitiConstants.VAR_TENANT_DOMAIN, TenantUtil.getCurrentDomain()); + } if (query.getTaskId() != null) { @@ -1915,9 +1874,13 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine { String name =factory.mapQNameToName(customProperty.getKey()); - // Perform minimal property conversions - Object converted = propertyConverter.convertPropertyToValue(customProperty.getValue()); - taskQuery.processVariableValueEquals(name, converted); + // Exclude the special "VAR_TENANT_DOMAIN" variable, this cannot be queried by users + if(name != ActivitiConstants.VAR_TENANT_DOMAIN) + { + // Perform minimal property conversions + Object converted = propertyConverter.convertPropertyToValue(customProperty.getValue()); + taskQuery.processVariableValueEquals(name, converted); + } } } @@ -2189,7 +2152,6 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine private List getWorkflowsInternal(WorkflowInstanceQuery workflowInstanceQuery, boolean isActive) { String processDefId = workflowInstanceQuery.getWorkflowDefinitionId() == null ? null : createLocalId(workflowInstanceQuery.getWorkflowDefinitionId()); - LinkedList results = new LinkedList(); HistoricProcessInstanceQuery query; if (isActive) @@ -2312,10 +2274,8 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine } } - List completedInstances = query.list(); - List completedResults = typeConverter.convert(completedInstances); - results.addAll(completedResults); - return results; + List completedInstances = query.list(); + return typeConverter.convert(completedInstances); } /** diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowManagerFactory.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowManagerFactory.java index 019fc5a648..1f4972a7b7 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowManagerFactory.java +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowManagerFactory.java @@ -21,6 +21,7 @@ package org.alfresco.repo.workflow.activiti; import org.activiti.engine.ProcessEngine; import org.alfresco.repo.i18n.MessageService; +import org.alfresco.repo.model.Repository; import org.alfresco.repo.security.authority.AuthorityDAO; import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.workflow.BPMEngineRegistry; @@ -34,7 +35,6 @@ import org.alfresco.repo.workflow.activiti.properties.ActivitiPropertyConverter; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.workflow.WorkflowException; import org.alfresco.service.namespace.NamespaceService; @@ -51,19 +51,18 @@ public class ActivitiWorkflowManagerFactory implements FactoryBean tasks = workflowService.queryTasks(taskQuery); assertNotNull(tasks); - // Activiti doesn't return start-task when no process/task id is set in query, so only 2 tasks will be returned assertEquals(3, tasks.size()); taskQuery = createWorkflowTaskQuery(WorkflowTaskState.COMPLETED); diff --git a/source/java/org/alfresco/repo/workflow/activiti/AlfrescoBpmnParseListener.java b/source/java/org/alfresco/repo/workflow/activiti/AlfrescoBpmnParseListener.java index 5a26faa1b9..08315a2439 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/AlfrescoBpmnParseListener.java +++ b/source/java/org/alfresco/repo/workflow/activiti/AlfrescoBpmnParseListener.java @@ -50,6 +50,7 @@ public class AlfrescoBpmnParseListener implements BpmnParseListener private TaskListener createTaskListener; private ExecutionListener processCreateListener; private TenantService tenantService; + private boolean multiTenancyEnabled = true; @Override public void parseUserTask(Element userTaskElement, ScopeImpl scope, ActivityImpl activity) @@ -140,7 +141,7 @@ public class AlfrescoBpmnParseListener implements BpmnParseListener @Override public void parseCallActivity(Element callActivityElement, ScopeImpl scope, ActivityImpl activity) { - if (tenantService.isEnabled()) + if (multiTenancyEnabled && tenantService.isEnabled()) { ActivityBehavior activityBehavior = activity.getActivityBehavior(); if(activityBehavior instanceof CallActivityBehavior) @@ -196,11 +197,12 @@ public class AlfrescoBpmnParseListener implements BpmnParseListener for (ProcessDefinitionEntity processDefinition : arg1) { processDefinition.addExecutionListener(ExecutionListener.EVENTNAME_START, processCreateListener); - if (tenantService.isEnabled()) - { - String key = tenantService.getName(processDefinition.getKey()); - processDefinition.setKey(key); - } + + if (multiTenancyEnabled && tenantService.isEnabled()) + { + String key = tenantService.getName(processDefinition.getKey()); + processDefinition.setKey(key); + } } } @@ -234,6 +236,14 @@ public class AlfrescoBpmnParseListener implements BpmnParseListener { this.tenantService = tenantService; } + + /** + * @param deployInTenant whether or not workflows should be deployed as a tenant-only workflow + * when it's deployed IF the tenantService is enabled and a tenant-context is currently active. + */ + public void setDeployWorkflowsInTenant(boolean deployInTenant) { + this.multiTenancyEnabled = deployInTenant; + } @Override public void parseInclusiveGateway(Element inclusiveGwElement, diff --git a/source/java/org/alfresco/repo/workflow/activiti/AlfrescoProcessEngineConfiguration.java b/source/java/org/alfresco/repo/workflow/activiti/AlfrescoProcessEngineConfiguration.java index 57bc8c9da1..be78473756 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/AlfrescoProcessEngineConfiguration.java +++ b/source/java/org/alfresco/repo/workflow/activiti/AlfrescoProcessEngineConfiguration.java @@ -28,6 +28,7 @@ import org.activiti.engine.impl.jobexecutor.TimerExecuteNestedActivityJobHandler import org.activiti.engine.impl.variable.SerializableType; import org.activiti.engine.impl.variable.VariableType; import org.activiti.spring.SpringProcessEngineConfiguration; +import org.alfresco.service.cmr.repository.NodeService; /** * @author Nick Smith @@ -37,6 +38,7 @@ import org.activiti.spring.SpringProcessEngineConfiguration; public class AlfrescoProcessEngineConfiguration extends SpringProcessEngineConfiguration { private List customTypes; + private NodeService unprotectedNodeService; @Override protected void initVariableTypes() @@ -60,7 +62,7 @@ public class AlfrescoProcessEngineConfiguration extends SpringProcessEngineConfi // Wrap timer-job handler to handle authentication JobHandler timerJobHandler = jobHandlers.get(TimerExecuteNestedActivityJobHandler.TYPE); - JobHandler wrappingTimerJobHandler = new AuthenticatedTimerJobHandler(timerJobHandler); + JobHandler wrappingTimerJobHandler = new AuthenticatedTimerJobHandler(timerJobHandler, unprotectedNodeService); jobHandlers.put(TimerExecuteNestedActivityJobHandler.TYPE, wrappingTimerJobHandler); // Wrap async-job handler to handle authentication @@ -79,4 +81,9 @@ public class AlfrescoProcessEngineConfiguration extends SpringProcessEngineConfi { this.customTypes = customTypes; } + + public void setUnprotectedNodeService(NodeService unprotectedNodeService) + { + this.unprotectedNodeService = unprotectedNodeService; + } } diff --git a/source/java/org/alfresco/repo/workflow/activiti/AuthenticatedTimerJobHandler.java b/source/java/org/alfresco/repo/workflow/activiti/AuthenticatedTimerJobHandler.java index 319d2b4013..0633cef8ac 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/AuthenticatedTimerJobHandler.java +++ b/source/java/org/alfresco/repo/workflow/activiti/AuthenticatedTimerJobHandler.java @@ -26,8 +26,14 @@ import org.activiti.engine.impl.persistence.entity.ExecutionEntity; import org.activiti.engine.impl.persistence.entity.JobEntity; import org.activiti.engine.impl.pvm.PvmActivity; import org.activiti.engine.task.Task; +import org.alfresco.model.ContentModel; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; +import org.alfresco.repo.workflow.WorkflowConstants; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; /** * An {@link JobHandler} which executes activiti timer-jobs @@ -43,13 +49,25 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; public class AuthenticatedTimerJobHandler implements JobHandler { private JobHandler wrappedHandler; + private NodeService unprotectedNodeService; - public AuthenticatedTimerJobHandler(JobHandler jobHandler) + /** + * @param jobHandler the {@link JobHandler} to wrap. + * @param nodeService the UNPROTECTED {@link NodeService} to use for fetching initiator username + * when only tenant is known. We can't use initiator ScriptNode for this, because this uses the + * protected {@link NodeService} which requires an authenticated user in that tenant (see {@link #getInitiator(ActivitiScriptNode)}). + */ + public AuthenticatedTimerJobHandler(JobHandler jobHandler, NodeService nodeService) { if (jobHandler == null) { throw new IllegalArgumentException("JobHandler to delegate to is required"); } + if(nodeService == null) + { + throw new IllegalArgumentException("NodeService is required"); + } + this.unprotectedNodeService = nodeService; this.wrappedHandler = jobHandler; } @@ -58,41 +76,104 @@ public class AuthenticatedTimerJobHandler implements JobHandler final CommandContext commandContext) { String userName = null; - - PvmActivity targetActivity = execution.getActivity(); - if (targetActivity != null) + String tenantToRunIn = (String) execution.getVariable(ActivitiConstants.VAR_TENANT_DOMAIN); + if(tenantToRunIn != null && tenantToRunIn.trim().length() == 0) { - // Only try getting active task, if execution timer is waiting on is a userTask - String activityType = (String) targetActivity.getProperty(ActivitiConstants.NODE_TYPE); - if (ActivitiConstants.USER_TASK_NODE_TYPE.equals(activityType)) + tenantToRunIn = null; + } + + final ActivitiScriptNode initiatorNode = (ActivitiScriptNode) execution.getVariable(WorkflowConstants.PROP_INITIATOR); + + // Extracting the properties from the initiatornode should be done in correct tennant or as administrator, since we don't + // know who started the workflow yet (We can't access node-properties when no valid authentication context is set up). + if(tenantToRunIn != null) + { + userName = TenantUtil.runAsTenant(new TenantRunAsWork() { - Task task = new TaskQueryImpl(commandContext) + @Override + public String doWork() throws Exception + { + return getInitiator(initiatorNode); + } + }, tenantToRunIn); + } + else + { + // No tenant on worklfow, run as admin in default tenant + userName = AuthenticationUtil.runAs(new RunAsWork() + { + @SuppressWarnings("synthetic-access") + public String doWork() throws Exception + { + return getInitiator(initiatorNode); + } + }, AuthenticationUtil.getSystemUserName()); + } + + // Fall back to task assignee, if no initiator is found + if(userName == null) + { + PvmActivity targetActivity = execution.getActivity(); + if (targetActivity != null) + { + // Only try getting active task, if execution timer is waiting on is a userTask + String activityType = (String) targetActivity.getProperty(ActivitiConstants.NODE_TYPE); + if (ActivitiConstants.USER_TASK_NODE_TYPE.equals(activityType)) + { + Task task = new TaskQueryImpl(commandContext) .executionId(execution.getId()) .executeSingleResult(commandContext); - - if (task != null && task.getAssignee() != null) - { - userName = task.getAssignee(); + + if (task != null && task.getAssignee() != null) + { + userName = task.getAssignee(); + } } } } - // When no task assignee is set, use system user to run job + // When no task assignee is set, nor the initiator, use system user to run job if (userName == null) { userName = AuthenticationUtil.getSystemUserName(); + tenantToRunIn = null; } - // Execute timer - AuthenticationUtil.runAs(new RunAsWork() + if(tenantToRunIn != null) { - @SuppressWarnings("synthetic-access") - public Void doWork() throws Exception + TenantUtil.runAsUserTenant(new TenantRunAsWork() { - wrappedHandler.execute(job, configuration, execution, commandContext); - return null; - } - }, userName); + @Override + public Void doWork() throws Exception + { + wrappedHandler.execute(job, configuration, execution, commandContext); + return null; + } + }, userName, tenantToRunIn); + } + else + { + // Execute the timer without tenant + AuthenticationUtil.runAs(new RunAsWork() + { + @SuppressWarnings("synthetic-access") + public Void doWork() throws Exception + { + wrappedHandler.execute(job, configuration, execution, commandContext); + return null; + } + }, userName); + } + } + + protected String getInitiator(ActivitiScriptNode initiatorNode) + { + NodeRef ref = initiatorNode.getNodeRef(); + if(unprotectedNodeService.exists(ref)) + { + return (String) unprotectedNodeService.getProperty(ref, ContentModel.PROP_USERNAME); + } + return null; } @Override diff --git a/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPropertyConverter.java b/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPropertyConverter.java index d618d41b6f..47d0c39df5 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPropertyConverter.java +++ b/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPropertyConverter.java @@ -156,6 +156,8 @@ public class ActivitiPropertyConverter public Map getPathProperties(String executionId) { Map variables = activitiUtil.getExecutionVariables(executionId); + variables.remove(ActivitiConstants.VAR_TENANT_DOMAIN); + Map properties = new HashMap(variables.size()); for (Entry entry: variables.entrySet()) { diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java index e524ae18c8..f015ba5f30 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java @@ -324,7 +324,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine /* * @see org.alfresco.repo.workflow.WorkflowComponent#deployDefinition(java.io.InputStream, java.lang.String, java.lang.String) */ - public WorkflowDeployment deployDefinition(final InputStream workflowDefinition, final String mimetype, final String name) + public WorkflowDeployment deployDefinition(final InputStream workflowDefinition, final String mimetype, String name) { try { diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java index 33fb4741a0..6a65c8a5b6 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java @@ -633,4 +633,11 @@ public interface WorkflowService */ @Auditable(parameters = {"packageItem", "active"}) public List getPackageContents(String taskId); + + /** + * @return true, if all workflows (in workflowDeployers) have a copy deployed per tenant and workflows + * can be deployed in tenant. False when workflows are shared system-wide, regardless of the tenant context. + */ + @Auditable + public boolean isMultiTenantWorkflowDeploymentEnabled(); } diff --git a/source/java/org/alfresco/util/ApplicationContextHelper.java b/source/java/org/alfresco/util/ApplicationContextHelper.java index 426da08a55..74b59218ef 100644 --- a/source/java/org/alfresco/util/ApplicationContextHelper.java +++ b/source/java/org/alfresco/util/ApplicationContextHelper.java @@ -46,7 +46,6 @@ public class ApplicationContextHelper extends BaseApplicationContextHelper return BaseApplicationContextHelper.getApplicationContext(CONFIG_LOCATIONS); } - public static void main(String ... args) { ClassPathXmlApplicationContext ctx = (ClassPathXmlApplicationContext) getApplicationContext(); diff --git a/source/test-resources/activiti/test-activiti-component-context.xml b/source/test-resources/activiti/test-activiti-component-context.xml index b0c4163eee..12b585fbff 100644 --- a/source/test-resources/activiti/test-activiti-component-context.xml +++ b/source/test-resources/activiti/test-activiti-component-context.xml @@ -15,6 +15,11 @@ + + + + + @@ -23,9 +28,7 @@ - - - + @@ -60,6 +63,14 @@ + + + + + + + + diff --git a/source/test-resources/cluster-test/placeholder-test.xml b/source/test-resources/cluster-test/placeholder-test.xml new file mode 100644 index 0000000000..9bbcaab1a7 --- /dev/null +++ b/source/test-resources/cluster-test/placeholder-test.xml @@ -0,0 +1,166 @@ + + + + + ${alfresco.cluster.name} + ${alfresco.hazelcast.password} + + + 5701 + + + 224.2.2.3 + 54327 + + + 127.0.0.1 + + + my-access-key + my-secret-key + + us-west-1 + + hazelcast-sg + type + hz-nodes + + + + 10.10.1.* + + + + PBEWithMD5AndDES + + thesalt + + thepass + + 19 + + + + RSA/NONE/PKCS1PADDING + + thekeypass + + local + + JKS + + thestorepass + + keystore + + + + 16 + 64 + 60 + + + + 0 + + default + + + + 1 + + 0 + + 0 + + NONE + + 0 + + 25 + + hz.ADD_NEW_ENTRY + + + + + + \ No newline at end of file