diff --git a/config/alfresco/action-services-context.xml b/config/alfresco/action-services-context.xml
index 249ebbebb0..6ae0233d68 100644
--- a/config/alfresco/action-services-context.xml
+++ b/config/alfresco/action-services-context.xml
@@ -420,12 +420,6 @@
-
-
-
-
-
-
@@ -493,6 +487,12 @@
+
+
+ false
+
+
+
diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml
index 9c7f937189..c5c80a225d 100644
--- a/config/alfresco/cache-context.xml
+++ b/config/alfresco/cache-context.xml
@@ -323,13 +323,13 @@
-
+
-
+
@@ -339,7 +339,7 @@
-
+
diff --git a/config/alfresco/cmis-api-context.xml b/config/alfresco/cmis-api-context.xml
index 568a337583..a432110c34 100644
--- a/config/alfresco/cmis-api-context.xml
+++ b/config/alfresco/cmis-api-context.xml
@@ -117,11 +117,6 @@
-
-
diff --git a/config/alfresco/dao/dao-context.xml b/config/alfresco/dao/dao-context.xml
index 0a4dbec18e..881888fdc7 100644
--- a/config/alfresco/dao/dao-context.xml
+++ b/config/alfresco/dao/dao-context.xml
@@ -287,8 +287,6 @@
-
-
diff --git a/config/alfresco/messages/patch-service_ru.properties b/config/alfresco/messages/patch-service_ru.properties
index cd07c4913d..a13321dbbb 100755
--- a/config/alfresco/messages/patch-service_ru.properties
+++ b/config/alfresco/messages/patch-service_ru.properties
@@ -488,4 +488,5 @@ patch.increaseColumnSizeActiviti.description=ALF-14983 : Upgrade scripts to incr
patch.removeColumnActiviti.description=ALF-16038 : DB2: Upgrade script to remove ALFUSER.ACT_HI_ACTINST.OWNER_
patch.upgradeToActiviti5-10.description=Upgraded Activiti tables to 5.10 version
-patch.addActivtiIndexHistoricActivity.description=Additional index for activiti on historic activity (PROC_INST_ID_ and ACTIVITY_ID_)
\ No newline at end of file
+patch.addActivtiIndexHistoricActivity.description=Additional index for activiti on historic activity (PROC_INST_ID_ and ACTIVITY_ID_)
+patch.renameConstraintActiviti.description=ALF-15828 : DB2: Upgrade script to rename ACT_HI_PROCINST.PROC_INST_ID_ index
\ No newline at end of file
diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml
index b63443dc13..8c34c8b220 100644
--- a/config/alfresco/patch/patch-services-context.xml
+++ b/config/alfresco/patch/patch-services-context.xml
@@ -2484,6 +2484,12 @@
0
5010
5011
+
+
+
+
+
+
diff --git a/config/alfresco/rule-services-context.xml b/config/alfresco/rule-services-context.xml
index 8e3bb6cecd..02bfa6faa4 100644
--- a/config/alfresco/rule-services-context.xml
+++ b/config/alfresco/rule-services-context.xml
@@ -74,7 +74,6 @@
-
@@ -115,6 +114,9 @@
+
+ ${policy.content.update.ignoreEmpty}
+
@@ -153,16 +155,14 @@
-
-
- false
-
-
-
-
+
true
+
+
+ ${policy.content.update.ignoreEmpty}
+
diff --git a/config/alfresco/subsystems/imap/default/imap-server-context.xml b/config/alfresco/subsystems/imap/default/imap-server-context.xml
index c1fdf8b9ad..af0a1bb00d 100644
--- a/config/alfresco/subsystems/imap/default/imap-server-context.xml
+++ b/config/alfresco/subsystems/imap/default/imap-server-context.xml
@@ -115,6 +115,9 @@
+
+
+
diff --git a/config/alfresco/tx-cache-context.xml b/config/alfresco/tx-cache-context.xml
index 13db975ea7..0f7081154b 100644
--- a/config/alfresco/tx-cache-context.xml
+++ b/config/alfresco/tx-cache-context.xml
@@ -428,6 +428,7 @@
+
@@ -507,51 +508,6 @@
-
-
-
-
-
-
-
- org.alfresco.compiledModelsTransactionalCache
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- org.alfresco.prefixesTransactionalCache
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- org.alfresco.webScriptsRegistryTransactionalCache
-
-
-
-
-
-
-
diff --git a/source/java/org/alfresco/cmis/changelog/CMISChangeLogDataExtractor.java b/source/java/org/alfresco/cmis/changelog/CMISChangeLogDataExtractor.java
deleted file mode 100644
index fbd3a17acc..0000000000
--- a/source/java/org/alfresco/cmis/changelog/CMISChangeLogDataExtractor.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
- *
- * This file is part of Alfresco
- *
- * Alfresco is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Alfresco is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with Alfresco. If not, see .
- */
-package org.alfresco.cmis.changelog;
-
-import java.io.Serializable;
-import java.util.HashMap;
-
-import org.alfresco.cmis.CMISDictionaryModel;
-import org.alfresco.cmis.CMISInvalidArgumentException;
-import org.alfresco.cmis.CMISServices;
-import org.alfresco.cmis.CMISTypeDefinition;
-import org.alfresco.cmis.CMISTypeId;
-import org.alfresco.repo.audit.extractor.AbstractDataExtractor;
-import org.alfresco.service.cmr.model.FileInfo;
-import org.alfresco.service.cmr.repository.ChildAssociationRef;
-import org.alfresco.service.cmr.repository.NodeRef;
-
-/**
- * An extractor that allows to filter data using the following rule:
- * Audit records should only be created for items in the CMIS domain model.
- *
- * @author Stas Sokolovsky
- */
-public class CMISChangeLogDataExtractor extends AbstractDataExtractor
-{
- private CMISServices cmisService;
-
- public static final String KEY_NODE_REF = "nodeRef";
- public static final String KEY_OBJECT_ID = "objectId";
-
- /**
- * Extracts relevant node refs and Ids from auditing data
- *
- * @see org.alfresco.repo.audit.extractor.DataExtractor.extractData(java.io.Serializable)
- */
- public Serializable extractData(Serializable value) throws Throwable
- {
- NodeRef nodeRef = getNodeRef(value);
- HashMap result = new HashMap(5);
- result.put(KEY_NODE_REF, nodeRef);
- // Support version nodes by recording the object ID
- result.put(KEY_OBJECT_ID, cmisService.getProperty(nodeRef, CMISDictionaryModel.PROP_OBJECT_ID));
- return result;
- }
-
- /**
- * @return Returns true if items in the CMIS domain model
- * @see org.alfresco.repo.audit.extractor.DataExtractor.isSupported(java.io.Serializable)
- */
- public boolean isSupported(Serializable data)
- {
- if (data != null)
- {
- NodeRef nodeRef = getNodeRef(data);
- if (nodeRef != null)
- {
- try
- {
- CMISTypeDefinition typeDef = cmisService.getTypeDefinition(nodeRef);
- if (typeDef != null)
- {
- CMISTypeId typeId = typeDef.getBaseType().getTypeId();
- return typeId.equals(CMISDictionaryModel.DOCUMENT_TYPE_ID) || typeId.equals(CMISDictionaryModel.FOLDER_TYPE_ID);
- }
- }
- catch (CMISInvalidArgumentException e)
- {
- // Ignore and return false
- }
- }
- }
- return false;
- }
-
- /**
- * Gets the NodeRef from auditing data
- *
- * @param data audit data
- * @return Node Reference
- */
- private NodeRef getNodeRef(Serializable data)
- {
- NodeRef nodeRef = null;
- if (data instanceof ChildAssociationRef)
- {
- nodeRef = ((ChildAssociationRef) data).getChildRef();
- }
- if (data instanceof FileInfo)
- {
- nodeRef = ((FileInfo) data).getNodeRef();
- }
- else if (data instanceof NodeRef)
- {
- nodeRef = (NodeRef) data;
- }
- return nodeRef;
- }
-
- /**
- * Set the CMIS service
- *
- * @param cmisService CMIS service
- */
- public void setCmisService(CMISServices cmisService)
- {
- this.cmisService = cmisService;
- }
-}
diff --git a/source/java/org/alfresco/cmis/changelog/CMISChangeLogServiceImpl.java b/source/java/org/alfresco/cmis/changelog/CMISChangeLogServiceImpl.java
index d6dfd0def8..5524ab90d8 100644
--- a/source/java/org/alfresco/cmis/changelog/CMISChangeLogServiceImpl.java
+++ b/source/java/org/alfresco/cmis/changelog/CMISChangeLogServiceImpl.java
@@ -34,6 +34,7 @@ import org.alfresco.cmis.CMISChangeLogService;
import org.alfresco.cmis.CMISChangeType;
import org.alfresco.cmis.CMISInvalidArgumentException;
import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.opencmis.CMISChangeLogDataExtractor;
import org.alfresco.service.cmr.audit.AuditQueryParameters;
import org.alfresco.service.cmr.audit.AuditService;
import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback;
diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java
index d724d437f7..372b0c48b4 100644
--- a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java
+++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java
@@ -248,12 +248,17 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr
protected CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef)
{
- CMISNodeInfoImpl result = connector.createNodeInfo(nodeRef);
+ return createNodeInfo(nodeRef, null);
+ }
+
+ protected CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef, VersionHistory versionHistory)
+ {
+ CMISNodeInfoImpl result = connector.createNodeInfo(nodeRef, versionHistory);
nodeInfoMap.put(result.getObjectId(), result);
return result;
}
-
+
protected CMISNodeInfo createNodeInfo(AssociationRef assocRef)
{
CMISNodeInfoImpl result = connector.createNodeInfo(assocRef);
@@ -2004,7 +2009,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr
// convert the version history
for (Version version : versionHistory.getAllVersions())
{
- CMISNodeInfo versionInfo = createNodeInfo(version.getFrozenStateNodeRef());
+ CMISNodeInfo versionInfo = createNodeInfo(version.getFrozenStateNodeRef(), versionHistory);
result.add(
connector.createCMISObject(
diff --git a/source/java/org/alfresco/opencmis/CMISChangeLogDataExtractor.java b/source/java/org/alfresco/opencmis/CMISChangeLogDataExtractor.java
index 5579c56277..e97919a38d 100644
--- a/source/java/org/alfresco/opencmis/CMISChangeLogDataExtractor.java
+++ b/source/java/org/alfresco/opencmis/CMISChangeLogDataExtractor.java
@@ -26,6 +26,7 @@ import org.alfresco.opencmis.dictionary.CMISPropertyAccessor;
import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper;
import org.alfresco.repo.audit.extractor.AbstractDataExtractor;
import org.alfresco.service.cmr.model.FileInfo;
+import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
@@ -100,10 +101,15 @@ public class CMISChangeLogDataExtractor extends AbstractDataExtractor
private NodeRef getNodeRef(Serializable data)
{
NodeRef nodeRef = null;
- if (data instanceof FileInfo)
+ if (data instanceof ChildAssociationRef)
+ {
+ nodeRef = ((ChildAssociationRef) data).getChildRef();
+ }
+ else if (data instanceof FileInfo)
{
nodeRef = ((FileInfo) data).getNodeRef();
- } else if (data instanceof NodeRef)
+ }
+ else if (data instanceof NodeRef)
{
nodeRef = (NodeRef) data;
}
diff --git a/source/java/org/alfresco/opencmis/CMISConnector.java b/source/java/org/alfresco/opencmis/CMISConnector.java
index cfcb8bbfe6..237831909a 100644
--- a/source/java/org/alfresco/opencmis/CMISConnector.java
+++ b/source/java/org/alfresco/opencmis/CMISConnector.java
@@ -104,6 +104,7 @@ import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteService;
+import org.alfresco.service.cmr.version.VersionHistory;
import org.alfresco.service.cmr.version.VersionService;
import org.alfresco.service.cmr.version.VersionType;
import org.alfresco.service.descriptor.Descriptor;
@@ -844,7 +845,15 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen
*/
public CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef)
{
- return new CMISNodeInfoImpl(this, nodeRef);
+ return createNodeInfo(nodeRef, null);
+ }
+
+ /**
+ * Creates an object info object.
+ */
+ public CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef, VersionHistory versionHistory)
+ {
+ return new CMISNodeInfoImpl(this, nodeRef, versionHistory);
}
/**
diff --git a/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java b/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java
index 529421db4c..19e158a13b 100644
--- a/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java
+++ b/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java
@@ -94,6 +94,15 @@ public class CMISNodeInfoImpl implements CMISNodeInfo
analyseObjectId();
}
+ public CMISNodeInfoImpl(CMISConnector connector, NodeRef nodeRef, VersionHistory versionHistory)
+ {
+ this.connector = connector;
+ this.nodeRef = nodeRef;
+ this.versionHistory = versionHistory;
+
+ analyseNodeRef();
+ }
+
public CMISNodeInfoImpl(CMISConnector connector, NodeRef nodeRef)
{
this.connector = connector;
@@ -115,10 +124,10 @@ public class CMISNodeInfoImpl implements CMISNodeInfo
return objecVariant != CMISObjectVariant.VERSION;
}
- protected void analyseVersionNode(NodeRef nodeRef)
+ protected void analyseVersionNode()
{
// check version
- versionHistory = connector.getVersionService().getVersionHistory(nodeRef);
+ versionHistory = getVersionHistory();
if (versionHistory == null)
{
objecVariant = CMISObjectVariant.CURRENT_VERSION;
@@ -301,7 +310,7 @@ public class CMISNodeInfoImpl implements CMISNodeInfo
{
// the version label refers to a specific non-head version, find the nodeRef
// of the version node from the version history
- versionHistory = connector.getVersionService().getVersionHistory(nodeRef);
+ versionHistory = getVersionHistory();
if (versionHistory == null)
{
// unexpected null versionHistory, assume not versioned
@@ -420,7 +429,7 @@ public class CMISNodeInfoImpl implements CMISNodeInfo
// check version
if(connector.getVersionService().isAVersion(nodeRef))
{
- analyseVersionNode(nodeRef);
+ analyseVersionNode();
}
else
{
diff --git a/source/java/org/alfresco/repo/action/executer/ImageTransformActionExecuter.java b/source/java/org/alfresco/repo/action/executer/ImageTransformActionExecuter.java
index c54ed8c11f..eda401308a 100644
--- a/source/java/org/alfresco/repo/action/executer/ImageTransformActionExecuter.java
+++ b/source/java/org/alfresco/repo/action/executer/ImageTransformActionExecuter.java
@@ -18,49 +18,36 @@
*/
package org.alfresco.repo.action.executer;
-import java.util.ArrayList;
import java.util.List;
+import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.ParameterDefinitionImpl;
-import org.alfresco.repo.content.transform.ContentTransformer;
-import org.alfresco.repo.content.transform.TransformerDebug;
import org.alfresco.repo.content.transform.magick.ImageTransformationOptions;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
-import org.alfresco.service.cmr.repository.ContentReader;
-import org.alfresco.service.cmr.repository.ContentWriter;
-import org.alfresco.service.cmr.repository.NoTransformerException;
import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.TransformationOptions;
/**
- * Transfor action executer
+ * Transform action executer. Modified original code to use any transformer rather than just
+ * ImageMagick. The original, allowed additional command line options to be supplied (added for
+ * backward compatibility in 15/04/2008). Now removed. Did not find any internal usage (22/11/2012) but it
+ * is possible that there may be some in Javascript calling ScriptNode or custom code using
+ * the context passed to the ImageRenderingEngine. That said, it will almost always be ImagMagick
+ * that does the transform so it will still be used. These command line options were to overcome issues with
+ * ImageMagick. Other transformers will just ignore them.
*
* @author Roy Wetherall
*/
public class ImageTransformActionExecuter extends TransformActionExecuter
{
- private TransformerDebug transformerDebug;
-
/**
* Action constants
*/
public static final String NAME = "transform-image";
public static final String PARAM_CONVERT_COMMAND = "convert-command";
- private ContentTransformer imageMagickContentTransformer;
-
- /**
- * Set the image magick content transformer
- *
- * @param imageMagickContentTransformer
- * the conten transformer
- */
- public void setImageMagickContentTransformer(ContentTransformer imageMagickContentTransformer)
- {
- this.imageMagickContentTransformer = imageMagickContentTransformer;
- }
-
/**
* Add parameter definitions
*/
@@ -71,48 +58,17 @@ public class ImageTransformActionExecuter extends TransformActionExecuter
paramList.add(new ParameterDefinitionImpl(PARAM_CONVERT_COMMAND, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_CONVERT_COMMAND)));
}
- public void setTransformerDebug(TransformerDebug transformerDebug)
+ @Override
+ protected TransformationOptions newTransformationOptions(Action ruleAction, NodeRef sourceNodeRef)
{
- this.transformerDebug = transformerDebug;
- }
-
- /**
- * @see org.alfresco.repo.action.executer.TransformActionExecuter#doTransform(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.ContentReader, org.alfresco.service.cmr.repository.ContentWriter)
- */
- protected void doTransform(Action ruleAction,
- NodeRef sourceNodeRef, ContentReader contentReader,
- NodeRef destinationNodeRef, ContentWriter contentWriter)
- {
- // Try and transform the content
+ ImageTransformationOptions options = new ImageTransformationOptions();
+ options.setSourceNodeRef(sourceNodeRef);
+ options.setSourceContentProperty(ContentModel.PROP_NAME);
+ options.setTargetContentProperty(ContentModel.PROP_NAME);
+
String convertCommand = (String) ruleAction.getParameterValue(PARAM_CONVERT_COMMAND);
- // create some options for the transform
- ImageTransformationOptions imageOptions = new ImageTransformationOptions();
- imageOptions.setCommandOptions(convertCommand);
- imageOptions.setSourceNodeRef(sourceNodeRef);
+ options.setCommandOptions(convertCommand);
- // check if the transformer is going to work, i.e. is available
- String sourceMimetype = contentReader.getMimetype();
- String targetMimetype = contentWriter.getMimetype();
- long sourceSize = contentReader.getSize();
- try
- {
- transformerDebug.pushAvailable(contentReader.getContentUrl(), sourceMimetype, targetMimetype, imageOptions);
- List transformers = new ArrayList(1);
- if (imageMagickContentTransformer.isTransformable(sourceMimetype, contentReader.getSize(), targetMimetype, imageOptions))
- {
- transformers.add(imageMagickContentTransformer);
- }
- transformerDebug.availableTransformers(transformers, sourceSize, "ImageTransformActionExecuter.doTransform(...)");
- if (transformers.size() == 0)
- {
- throw new NoTransformerException(sourceMimetype, targetMimetype);
- }
-
- imageMagickContentTransformer.transform(contentReader, contentWriter, imageOptions);
- }
- finally
- {
- transformerDebug.popAvailable();
- }
+ return options;
}
}
diff --git a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java
index dcea0fcd1c..8ad0c89753 100644
--- a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java
+++ b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2012 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -39,8 +39,8 @@ import org.alfresco.repo.template.I18NMessageMethod;
import org.alfresco.repo.template.TemplateNode;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
-import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ParameterDefinition;
@@ -301,6 +301,13 @@ public class MailActionExecuter extends ActionExecuterAbstractBase
final Action ruleAction,
final NodeRef actionedUponNodeRef)
{
+ MimeMessageHelper message = null;
+ if (!testMode && validNodeRefIfPresent(actionedUponNodeRef))
+ {
+ message = prepareEmail(ruleAction, actionedUponNodeRef);
+ }
+ final MimeMessageHelper finalMessage = message;
+
if (sendAfterCommit(ruleAction))
{
AlfrescoTransactionSupport.bindListener(new TransactionListenerAdapter()
@@ -314,9 +321,9 @@ public class MailActionExecuter extends ActionExecuterAbstractBase
@Override
public Void execute() throws Throwable
{
- if (validNodeRefIfPresent(actionedUponNodeRef))
+ if (finalMessage != null)
{
- prepareAndSendEmail(ruleAction, actionedUponNodeRef);
+ sendEmail(ruleAction, actionedUponNodeRef, finalMessage);
}
return null;
}
@@ -328,7 +335,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase
{
if (validNodeRefIfPresent(actionedUponNodeRef))
{
- prepareAndSendEmail(ruleAction, actionedUponNodeRef);
+ sendEmail(ruleAction, actionedUponNodeRef, finalMessage);
}
}
}
@@ -356,9 +363,14 @@ public class MailActionExecuter extends ActionExecuterAbstractBase
return sendAfterCommit == null ? false : sendAfterCommit.booleanValue();
}
- private void prepareAndSendEmail(final Action ruleAction, final NodeRef actionedUponNodeRef)
+ public MimeMessageHelper prepareEmail(final Action ruleAction, final NodeRef actionedUponNodeRef)
{
- // Create the mime mail message
+ // 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")
@@ -369,7 +381,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)
@@ -381,7 +393,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
{
@@ -449,7 +461,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
{
@@ -487,7 +499,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase
{
logger.debug("from specified as a parameter, from:" + from);
}
- message.setFrom(from);
+ messageRef[0].setFrom(from);
}
else
{
@@ -504,12 +516,12 @@ 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);
}
}
@@ -521,14 +533,14 @@ 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));
// See if an email template has been specified
String text = null;
@@ -587,24 +599,41 @@ public class MailActionExecuter extends ActionExecuterAbstractBase
if (text != null)
{
- message.setText(text, isHTML);
+ messageRef[0].setText(text, isHTML);
}
}
};
+ MimeMessage mimeMessage = javaMailSender.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 sendEmail(final Action ruleAction, final NodeRef actionedUponNodeRef, MimeMessageHelper preparedMessage)
+ {
try
{
// Send the message unless we are in "testMode"
- if(!testMode)
+ if(!testMode && preparedMessage != null)
{
- javaMailSender.send(mailPreparer);
+ javaMailSender.send(preparedMessage.getMimeMessage());
}
- else
+ else if (validNodeRefIfPresent(actionedUponNodeRef))
{
try {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
- mailPreparer.prepare(mimeMessage);
+ prepareEmail(ruleAction, actionedUponNodeRef);
lastTestMessage = mimeMessage;
} catch(Exception e) {
System.err.println(e);
diff --git a/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java b/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java
index 669580f5a8..588b71fb14 100644
--- a/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java
+++ b/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java
@@ -81,6 +81,11 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase
private CopyService copyService;
private MimetypeService mimetypeService;
+ /**
+ * Properties (needed to avoid changing method signatures)
+ */
+ protected TransformationOptions options;
+
/**
* Set the mime type service
*/
@@ -172,8 +177,7 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase
throw new RuleServiceException(CONTENT_READER_NOT_FOUND_MESSAGE);
}
- TransformationOptions options = new TransformationOptions();
- options.setSourceNodeRef(actionedUponNodeRef);
+ options = newTransformationOptions(ruleAction, actionedUponNodeRef);
if (null == contentService.getTransformer(contentReader.getContentUrl(), contentReader.getMimetype(), contentReader.getSize(), mimeType, options))
{
throw new RuleServiceException(String.format(TRANSFORMER_NOT_EXISTS_MESSAGE_PATTERN, contentReader.getMimetype(), mimeType));
@@ -243,7 +247,6 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase
}
}
- boolean newCopy = false;
if (copyNodeRef == null)
{
// Copy the content node
@@ -253,11 +256,7 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase
destinationAssocTypeQName,
destinationAssocQName,
false);
- newCopy = true;
- }
-
- if (newCopy == true)
- {
+
// Adjust the name of the copy
nodeService.setProperty(copyNodeRef, ContentModel.PROP_NAME, newName);
String originalTitle = (String)nodeService.getProperty(actionedUponNodeRef, ContentModel.PROP_TITLE);
@@ -282,6 +281,7 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase
// TODO: Check failure patterns for actions.
try
{
+ options.setTargetNodeRef(copyNodeRef);
doTransform(ruleAction, actionedUponNodeRef, contentReader, copyNodeRef, contentWriter);
}
catch(NoTransformerException e)
@@ -296,6 +296,11 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase
throw new RuleServiceException(TRANSFORMING_ERROR_MESSAGE + e.getMessage());
}
}
+ }
+
+ protected TransformationOptions newTransformationOptions(Action ruleAction, NodeRef sourceNodeRef)
+ {
+ return new TransformationOptions(sourceNodeRef, ContentModel.PROP_NAME, null, ContentModel.PROP_NAME);
}
/**
@@ -306,10 +311,6 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase
NodeRef destinationNodeRef, ContentWriter contentWriter)
{
- // Transformation options
- TransformationOptions options = new TransformationOptions(
- sourceNodeRef, ContentModel.PROP_NAME, destinationNodeRef, ContentModel.PROP_NAME);
-
// transform - will throw NoTransformerException if there are no transformers
this.contentService.transform(contentReader, contentWriter, options);
}
diff --git a/source/java/org/alfresco/repo/activities/feed/ErrorProneActionExecutor.java b/source/java/org/alfresco/repo/activities/feed/ErrorProneActionExecutor.java
new file mode 100644
index 0000000000..1290a6c7c6
--- /dev/null
+++ b/source/java/org/alfresco/repo/activities/feed/ErrorProneActionExecutor.java
@@ -0,0 +1,97 @@
+package org.alfresco.repo.activities.feed;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.repo.action.ParameterDefinitionImpl;
+import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
+import org.alfresco.repo.action.executer.TestModeable;
+import org.alfresco.service.cmr.action.Action;
+import org.alfresco.service.cmr.action.ParameterDefinition;
+import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.InitializingBean;
+
+public class ErrorProneActionExecutor extends ActionExecuterAbstractBase
+ implements InitializingBean, TestModeable
+{
+ private static Log logger = LogFactory.getLog(ErrorProneActionExecutor.class);
+
+ public static final String PARAM_FAILING_PERSON_NODEREF = "failingPersonNodeRef";
+ public static final String PARAM_PERSON_NODEREF = "personNodeRef";
+ public static final String PARAM_USERNAME = "userName";
+
+ public static final String NAME = "errorProneActionExecutor";
+
+ // count of number of successful notifications
+ private AtomicInteger numSuccessful = new AtomicInteger();
+
+ // count of number of failed notifications
+ private AtomicInteger numFailed = new AtomicInteger();
+
+ public int getNumSuccess()
+ {
+ return numSuccessful.get();
+ }
+
+ public int getNumFailed()
+ {
+ return numFailed.get();
+ }
+
+ /**
+ * Send an email message
+ *
+ * @throws AlfrescoRuntimeExeption
+ */
+ @Override
+ protected void executeImpl(
+ final Action ruleAction,
+ final NodeRef actionedUponNodeRef)
+ {
+ NodeRef failingPersonNodeRef = (NodeRef)ruleAction.getParameterValue(PARAM_FAILING_PERSON_NODEREF);
+ NodeRef personNodeRef = (NodeRef)ruleAction.getParameterValue(PARAM_PERSON_NODEREF);
+ String userName = (String)ruleAction.getParameterValue(PARAM_USERNAME);
+
+ System.out.println("userName = " + userName);
+
+ if(personNodeRef.equals(failingPersonNodeRef))
+ {
+ numFailed.incrementAndGet();
+ throw new AlfrescoRuntimeException("");
+ }
+
+ numSuccessful.incrementAndGet();
+ }
+
+ /**
+ * Add the parameter definitions
+ */
+ @Override
+ protected void addParameterDefinitions(List paramList)
+ {
+ paramList.add(new ParameterDefinitionImpl(PARAM_FAILING_PERSON_NODEREF, DataTypeDefinition.NODE_REF, true, "Failing Person NodeRef"));
+ paramList.add(new ParameterDefinitionImpl(PARAM_PERSON_NODEREF, DataTypeDefinition.NODE_REF, true, "Person NodeRef"));
+ paramList.add(new ParameterDefinitionImpl(PARAM_USERNAME, DataTypeDefinition.TEXT, true, "Username"));
+ }
+
+ @Override
+ public boolean isTestMode()
+ {
+ return true;
+ }
+
+ @Override
+ public void setTestMode(boolean testMode)
+ {
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception
+ {
+
+ }
+}
diff --git a/source/java/org/alfresco/repo/activities/feed/ErrorProneUserNotifier.java b/source/java/org/alfresco/repo/activities/feed/ErrorProneUserNotifier.java
new file mode 100644
index 0000000000..7feb812d46
--- /dev/null
+++ b/source/java/org/alfresco/repo/activities/feed/ErrorProneUserNotifier.java
@@ -0,0 +1,94 @@
+package org.alfresco.repo.activities.feed;
+
+import java.io.Serializable;
+import java.util.Map;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.action.executer.MailActionExecuter;
+import org.alfresco.service.cmr.action.Action;
+import org.alfresco.service.cmr.action.ActionService;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+public class ErrorProneUserNotifier extends AbstractUserNotifier
+{
+// private AtomicInteger numSuccessful = new AtomicInteger();
+// private AtomicInteger numFailed = new AtomicInteger();
+
+ private NodeRef failingPersonNodeRef;
+ private ActionService actionService;
+
+ public ErrorProneUserNotifier(NodeRef failingPersonNodeRef)
+ {
+ this.failingPersonNodeRef = failingPersonNodeRef;
+ }
+
+ public void setActionService(ActionService actionService)
+ {
+ this.actionService = actionService;
+ }
+
+ @Override
+ protected boolean skipUser(NodeRef personNodeRef)
+ {
+ return false;
+ }
+
+ @Override
+ protected Long getFeedId(NodeRef personNodeRef)
+ {
+ Map personProps = nodeService.getProperties(personNodeRef);
+
+ // where did we get up to ?
+ Long emailFeedDBID = (Long)personProps.get(ContentModel.PROP_EMAIL_FEED_ID);
+ if (emailFeedDBID != null)
+ {
+ // increment min feed id
+ emailFeedDBID++;
+ }
+ else
+ {
+ emailFeedDBID = -1L;
+ }
+
+ return emailFeedDBID;
+ }
+
+// public int getNumSuccess()
+// {
+// return numSuccessful.get();
+// }
+//
+// public int getNumFailed()
+// {
+// return numFailed.get();
+// }
+
+ @Override
+ protected void notifyUser(NodeRef personNodeRef, String subjectText,
+ Map model, NodeRef templateNodeRef)
+ {
+// super.notifyUser(personNodeRef, subjectText, model, templateNodeRef);
+
+ String userName = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME);
+
+ Action action = actionService.createAction(ErrorProneActionExecutor.NAME);
+
+ action.setParameterValue(ErrorProneActionExecutor.PARAM_FAILING_PERSON_NODEREF, failingPersonNodeRef);
+ action.setParameterValue(ErrorProneActionExecutor.PARAM_PERSON_NODEREF, personNodeRef);
+ action.setParameterValue(ErrorProneActionExecutor.PARAM_USERNAME, userName);
+
+ actionService.executeAction(action, null);
+// String userName = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME);
+//
+// System.out.println("userName = " + userName);
+//
+// if(personNodeRef.equals(failingPersonNodeRef))
+// {
+// numFailed.incrementAndGet();
+// throw new AlfrescoRuntimeException("");
+// }
+//
+// numSuccessful.incrementAndGet();
+ }
+}
diff --git a/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java b/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java
index 68a3d26d14..4055120a9a 100644
--- a/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java
+++ b/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java
@@ -17,6 +17,7 @@ import org.alfresco.repo.lock.JobLockService;
import org.alfresco.repo.lock.LockAcquisitionException;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.admin.RepoAdminService;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
@@ -88,7 +89,7 @@ public class FeedNotifierImpl implements FeedNotifier, ApplicationContextAware
this.batchSize = batchSize;
}
- public void setUserNotifier(UserNotifier userNotifier)
+ public void setUserNotifier(UserNotifier userNotifier)
{
this.userNotifier = userNotifier;
}
@@ -165,6 +166,11 @@ public class FeedNotifierImpl implements FeedNotifier, ApplicationContextAware
}
String lockToken = getLock(LOCK_TTL);
+ if (lockToken == null)
+ {
+ logger.info("Can't get lock. Assume multiple feed notifiers...");
+ return;
+ }
try
{
@@ -281,6 +287,21 @@ public class FeedNotifierImpl implements FeedNotifier, ApplicationContextAware
public void process(final PersonInfo person) throws Throwable
{
+ final RetryingTransactionHelper txHelper = transactionService.getRetryingTransactionHelper();
+ txHelper.setMaxRetries(0);
+
+ txHelper.doInTransaction(new RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ processInternal(person);
+ return null;
+ }
+ }, false, true);
+ }
+
+ private void processInternal(final PersonInfo person) throws Throwable
+ {
final NodeRef personNodeRef = person.getNodeRef();
try
@@ -306,7 +327,7 @@ public class FeedNotifierImpl implements FeedNotifier, ApplicationContextAware
// skip this person - eg. no longer exists ?
logger.warn("Skip feed notification for user ("+personNodeRef+"): " + inre.getMessage());
}
- }
+ }
};
// grab people for the batch processor in chunks of size batchSize
diff --git a/source/java/org/alfresco/repo/activities/feed/FeedNotifierTest.java b/source/java/org/alfresco/repo/activities/feed/FeedNotifierTest.java
new file mode 100644
index 0000000000..d5ccb0ec60
--- /dev/null
+++ b/source/java/org/alfresco/repo/activities/feed/FeedNotifierTest.java
@@ -0,0 +1,243 @@
+package org.alfresco.repo.activities.feed;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.activities.post.lookup.PostLookup;
+import org.alfresco.repo.domain.activities.ActivityPostDAO;
+import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.transaction.RetryingTransactionHelper;
+import org.alfresco.service.cmr.action.ActionService;
+import org.alfresco.service.cmr.activities.ActivityService;
+import org.alfresco.service.cmr.admin.RepoAdminService;
+import org.alfresco.service.cmr.model.FileFolderService;
+import org.alfresco.service.cmr.model.FileInfo;
+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.security.PersonService;
+import org.alfresco.service.cmr.site.SiteService;
+import org.alfresco.service.cmr.subscriptions.SubscriptionService;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.util.ApplicationContextHelper;
+import org.alfresco.util.GUID;
+import org.alfresco.util.PropertyMap;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.quartz.Scheduler;
+import org.springframework.context.ApplicationContext;
+import org.springframework.scheduling.quartz.JobDetailBean;
+
+/**
+ * Feed notifier tests.
+ *
+ * @author steveglover
+ *
+ */
+public class FeedNotifierTest
+{
+ private static ApplicationContext ctx = null;
+
+ private PersonService personService;
+ private NodeService nodeService;
+ private NamespaceService namespaceService;
+ private SiteService siteService;
+ private ActivityService activityService;
+ private RepoAdminService repoAdminService;
+ private FeedNotifierImpl feedNotifier;
+ private TransactionService transactionService;
+ private PostLookup postLookup;
+ private FeedGenerator feedGenerator;
+ private FileFolderService fileFolderService;
+ private SubscriptionService subscriptionService;
+ private ErrorProneActionExecutor errorProneActionExecutor;
+ private ActivityPostDAO postDAO;
+ private ActionService actionService;
+
+ private ErrorProneUserNotifier userNotifier;
+
+ private NodeRef failingPersonNodeRef;
+ private NodeRef personNodeRef;
+ private String userName1 = "user1." + GUID.generate();
+ private String userName2 = "user2." + GUID.generate();
+
+ @BeforeClass
+ public static void init()
+ {
+ ApplicationContextHelper.setUseLazyLoading(false);
+ ApplicationContextHelper.setNoAutoStart(true);
+
+ ctx = ApplicationContextHelper.getApplicationContext();
+ }
+
+ @Before
+ public void before() throws Exception
+ {
+ ChildApplicationContextFactory activitiesFeed = (ChildApplicationContextFactory)ctx.getBean("ActivitiesFeed");
+ ApplicationContext activitiesFeedCtx = activitiesFeed.getApplicationContext();
+ this.feedNotifier = (FeedNotifierImpl)activitiesFeedCtx.getBean("feedNotifier");
+ this.activityService = (ActivityService)activitiesFeedCtx.getBean("activityService");
+ this.postLookup = (PostLookup)activitiesFeedCtx.getBean("postLookup");
+ this.feedGenerator = (FeedGenerator)activitiesFeedCtx.getBean("feedGenerator");
+
+ Scheduler scheduler = (Scheduler)ctx.getBean("schedulerFactory");
+
+ JobDetailBean feedGeneratorJobDetail = (JobDetailBean)activitiesFeedCtx.getBean("feedGeneratorJobDetail");
+ JobDetailBean postLookupJobDetail = (JobDetailBean)activitiesFeedCtx.getBean("postLookupJobDetail");
+ JobDetailBean feedCleanerJobDetail = (JobDetailBean)activitiesFeedCtx.getBean("feedCleanerJobDetail");
+ JobDetailBean postCleanerJobDetail = (JobDetailBean)activitiesFeedCtx.getBean("postCleanerJobDetail");
+ JobDetailBean feedNotifierJobDetail = (JobDetailBean)activitiesFeedCtx.getBean("feedNotifierJobDetail");
+
+ // Pause activities jobs so that we aren't competing with their scheduled versions
+ scheduler.pauseJob(feedGeneratorJobDetail.getName(), feedGeneratorJobDetail.getGroup());
+ scheduler.pauseJob(postLookupJobDetail.getName(), postLookupJobDetail.getGroup());
+ scheduler.pauseJob(feedCleanerJobDetail.getName(), feedCleanerJobDetail.getGroup());
+ scheduler.pauseJob(postCleanerJobDetail.getName(), postCleanerJobDetail.getGroup());
+ scheduler.pauseJob(feedNotifierJobDetail.getName(), feedNotifierJobDetail.getGroup());
+
+ this.personService = (PersonService)ctx.getBean("personService");
+ this.nodeService = (NodeService)ctx.getBean("nodeService");
+ this.namespaceService = (NamespaceService)ctx.getBean("namespaceService");
+ this.siteService = (SiteService)ctx.getBean("siteService");
+ this.repoAdminService = (RepoAdminService)ctx.getBean("repoAdminService");
+ this.transactionService = (TransactionService)ctx.getBean("transactionService");
+ this.postDAO = (ActivityPostDAO)ctx.getBean("postDAO");
+ this.fileFolderService = (FileFolderService)ctx.getBean("fileFolderService");
+ this.subscriptionService = (SubscriptionService)ctx.getBean("SubscriptionService");
+ this.errorProneActionExecutor = (ErrorProneActionExecutor)ctx.getBean("errorProneActionExecutor");
+ this.actionService = (ActionService)ctx.getBean("ActionService");
+
+ // create some users
+ transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ @SuppressWarnings("synthetic-access")
+ public Void execute() throws Throwable
+ {
+ AuthenticationUtil.pushAuthentication();
+ AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
+
+ // create person properties
+ PropertyMap personProps = new PropertyMap();
+ personProps.put(ContentModel.PROP_USERNAME, userName1);
+ personProps.put(ContentModel.PROP_FIRSTNAME, userName1);
+ personProps.put(ContentModel.PROP_LASTNAME, userName1);
+ personProps.put(ContentModel.PROP_EMAIL, userName1+"@email.com");
+ personNodeRef = personService.createPerson(personProps);
+
+ personProps = new PropertyMap();
+ personProps.put(ContentModel.PROP_USERNAME, userName2);
+ personProps.put(ContentModel.PROP_FIRSTNAME, userName2);
+ personProps.put(ContentModel.PROP_LASTNAME, userName2);
+ personProps.put(ContentModel.PROP_EMAIL, userName2+"@email.com");
+ failingPersonNodeRef = personService.createPerson(personProps);
+
+ AuthenticationUtil.popAuthentication();
+
+ return null;
+ }
+ }, false, true);
+
+ // and some activities for those users
+ transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ @SuppressWarnings("synthetic-access")
+ public Void execute() throws Throwable
+ {
+ AuthenticationUtil.pushAuthentication();
+ AuthenticationUtil.setFullyAuthenticatedUser(userName1);
+
+ NodeRef rootNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
+ NodeRef workingRootNodeRef = nodeService.createNode(
+ rootNodeRef,
+ ContentModel.ASSOC_CHILDREN,
+ QName.createQName(NamespaceService.ALFRESCO_URI, "working root"),
+ ContentModel.TYPE_FOLDER).getChildRef();
+ FileInfo file1 = fileFolderService.create(workingRootNodeRef, GUID.generate(), ContentModel.TYPE_CONTENT);
+
+ // ensure at least 3 activities
+ JSONObject activityData = new JSONObject();
+ activityData.put("title", GUID.generate());
+ activityData.put("nodeRef", file1.getNodeRef());
+ activityService.postActivity("org.alfresco.documentlibrary.file-added", null, "documentlibrary", activityData.toString(), userName1);
+
+ AuthenticationUtil.popAuthentication();
+
+ return null;
+ }
+ }, false, true);
+
+ // one of the users follows the other user (so that we can test that notification failures for one of the users does not
+ // affect other users)
+ transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ @SuppressWarnings("synthetic-access")
+ public Void execute() throws Throwable
+ {
+ AuthenticationUtil.pushAuthentication();
+ AuthenticationUtil.setFullyAuthenticatedUser(userName2);
+
+ subscriptionService.follow(userName2, userName1);
+
+ AuthenticationUtil.popAuthentication();
+
+ return null;
+ }
+ }, false, true);
+
+ generateActivities();
+
+ // use our own user notifier for testing purposes
+ this.userNotifier = new ErrorProneUserNotifier(failingPersonNodeRef);
+ userNotifier.setNodeService(nodeService);
+ userNotifier.setNamespaceService(namespaceService);
+ userNotifier.setSiteService(siteService);
+ userNotifier.setActivityService(activityService);
+ userNotifier.setRepoAdminService(repoAdminService);
+ userNotifier.setActionService(actionService);
+ feedNotifier.setUserNotifier(userNotifier);
+ }
+
+ private void generateActivities() throws Exception
+ {
+ // generate the activities
+ postLookup.execute();
+
+ Long maxSequence = postDAO.getMaxActivitySeq();
+ while(maxSequence != null)
+ {
+ feedGenerator.execute();
+
+ maxSequence = postDAO.getMaxActivitySeq();
+ }
+ }
+
+ /**
+ * ALF-16155 test
+ */
+ @Test
+ public void testFailedNotifications()
+ {
+ AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
+
+ // execute the notifier, counting the number of successful and unsuccessful notifications
+ assertEquals(0, errorProneActionExecutor.getNumSuccess());
+ assertEquals(0, errorProneActionExecutor.getNumFailed());
+ feedNotifier.execute(1);
+ assertEquals(1, errorProneActionExecutor.getNumFailed()); // should be a single notification failure
+ assertTrue(errorProneActionExecutor.getNumSuccess() > 0); // others should have gone through ok
+ int numSuccessfulNotifications = errorProneActionExecutor.getNumSuccess();
+
+ // execute the notifier again, checking that there are no more notifications
+ feedNotifier.execute(1);
+ // should still be failing
+ assertEquals(2, errorProneActionExecutor.getNumFailed());
+ // there should not be any more because they have been processed already
+ assertEquals(numSuccessfulNotifications, errorProneActionExecutor.getNumSuccess());
+ }
+}
diff --git a/source/java/org/alfresco/repo/cache/TransactionalCache.java b/source/java/org/alfresco/repo/cache/TransactionalCache.java
index 534ca57d57..a3761e55f8 100644
--- a/source/java/org/alfresco/repo/cache/TransactionalCache.java
+++ b/source/java/org/alfresco/repo/cache/TransactionalCache.java
@@ -19,6 +19,7 @@
package org.alfresco.repo.cache;
import java.io.Serializable;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -266,10 +267,52 @@ public class TransactionalCache
*
* @param noSharedCacheRead true to avoid reading from the shared cache for the transaction
*/
+ @SuppressWarnings("unchecked")
public void setDisableSharedCacheReadForTransaction(boolean noSharedCacheRead)
{
TransactionData txnData = getTransactionData();
- txnData.noSharedCacheRead = noSharedCacheRead;
+
+ // If we are switching on noSharedCacheRead mode, convert all existing reads and updates to avoid 'consistent
+ // read' behaviour giving us a potentially out of date node already accessed
+ if (noSharedCacheRead && !txnData.noSharedCacheRead)
+ {
+ txnData.noSharedCacheRead = noSharedCacheRead;
+ String currentCacheRegion = TenantUtil.getCurrentDomain();
+ for (Map.Entry> entry : new ArrayList>>(
+ txnData.updatedItemsCache.entrySet()))
+ {
+ Serializable cacheKey = entry.getKey();
+ K key = null;
+ if (cacheKey instanceof CacheRegionKey)
+ {
+ CacheRegionKey cacheRegionKey = (CacheRegionKey) cacheKey;
+ if (currentCacheRegion.equals(cacheRegionKey.getCacheRegion()))
+ {
+ key = (K) cacheRegionKey.getCacheKey();
+ }
+ }
+ else
+ {
+ key = (K) cacheKey;
+ }
+
+ if (key != null)
+ {
+ CacheBucket bucket = entry.getValue();
+ // Simply 'forget' reads
+ if (bucket instanceof ReadCacheBucket)
+ {
+ txnData.updatedItemsCache.remove(cacheKey);
+ }
+ // Convert updates to removes
+ else if (bucket instanceof UpdateCacheBucket)
+ {
+ remove(key);
+ }
+ // Leave new entries alone - they can't have come from the shared cache
+ }
+ }
+ }
}
/**
diff --git a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java
index 98bdc6838b..2f8577d8cc 100644
--- a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java
+++ b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java
@@ -4235,6 +4235,54 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
}
}
+ @Override
+ public Set getCachedAncestors(List nodeIds)
+ {
+ // First, make sure 'level 1' nodes and their parents are in the cache
+ cacheNodesById(nodeIds);
+ for (Long nodeId : nodeIds)
+ {
+ // Filter out deleted nodes
+ if (exists(nodeId))
+ {
+ getParentAssocsCached(nodeId);
+ }
+ }
+ // Now recurse on all ancestors in the cache
+ Set ancestors = new TreeSet();
+ for (Long nodeId : nodeIds)
+ {
+ findCachedAncestors(nodeId, ancestors);
+ }
+ return ancestors;
+ }
+
+ /**
+ * Uses the node and parent assocs cache content to recursively find the set of currently cached ancestor node IDs
+ */
+ private void findCachedAncestors(Long nodeId, Set ancestors)
+ {
+ if (!ancestors.add(nodeId))
+ {
+ return; // Already visited
+ }
+ Node node = nodesCache.getValue(nodeId);
+ if (node == null)
+ {
+ return; // Not in cache yet - will load in due course
+ }
+ Pair cacheKey = new Pair(nodeId, node.getTransaction().getChangeTxnId());
+ ParentAssocsInfo value = parentAssocsCache.get(cacheKey);
+ if (value == null)
+ {
+ return; // Not in cache yet - will load in due course
+ }
+ for (ChildAssocEntity childAssoc : value.getParentAssocs().values())
+ {
+ findCachedAncestors(childAssoc.getParentNode().getId(), ancestors);
+ }
+ }
+
@Override
public void cacheNodesById(List nodeIds)
{
diff --git a/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java b/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java
index 1ea9317d2b..cc612183eb 100644
--- a/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java
+++ b/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java
@@ -26,6 +26,7 @@ import java.util.Map;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.cache.SimpleCache;
+import org.alfresco.repo.cache.TransactionalCache;
import org.alfresco.repo.cache.lookup.EntityLookupCache;
import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAO;
import org.alfresco.repo.domain.CrcHelper;
@@ -94,6 +95,12 @@ public abstract class AbstractAclCrudDAOImpl implements AclCrudDAO
*/
private EntityLookupCache aclEntityCache;
+ /**
+ * Backing transactional cache to allow read-through requests to be honoured
+ */
+ private TransactionalCache aclEntityTransactionalCache;
+
+
/**
* Cache for the Authority entity:
* KEY: ID (Authority)
@@ -115,12 +122,13 @@ public abstract class AbstractAclCrudDAOImpl implements AclCrudDAO
*
* @param aclEntityCache the cache of IDs to AclEntities
*/
- public void setAclEntityCache(SimpleCache aclEntityCache)
+ public void setAclEntityCache(TransactionalCache aclEntityCache)
{
this.aclEntityCache = new EntityLookupCache(
aclEntityCache,
CACHE_REGION_ACL,
aclEntityDaoCallback);
+ this.aclEntityTransactionalCache = aclEntityCache;
}
/**
@@ -200,6 +208,12 @@ public abstract class AbstractAclCrudDAOImpl implements AclCrudDAO
return entityPair.getSecond();
}
+ @Override
+ public void setCheckAclConsistency()
+ {
+ aclEntityTransactionalCache.setDisableSharedCacheReadForTransaction(true);
+ }
+
public AclUpdateEntity getAclForUpdate(long id)
{
AclEntity acl = getAclImpl(id);
diff --git a/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java b/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java
index b68a6ab7bc..bc61c9bb8b 100644
--- a/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java
+++ b/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java
@@ -45,6 +45,12 @@ import org.alfresco.util.Pair;
*/
public interface AclCrudDAO
{
+ /**
+ * Transaction-scope setting to make the DAO guarantee the validity of all caches: some cache data will be reloaded;
+ * some cache data will be considered safe.
+ */
+ public void setCheckAclConsistency();
+
//
// Access Control List (ACL)
//
diff --git a/source/java/org/alfresco/repo/domain/permissions/AclDAO.java b/source/java/org/alfresco/repo/domain/permissions/AclDAO.java
index b455c7eafc..ce8b5453ad 100644
--- a/source/java/org/alfresco/repo/domain/permissions/AclDAO.java
+++ b/source/java/org/alfresco/repo/domain/permissions/AclDAO.java
@@ -34,6 +34,12 @@ import org.alfresco.repo.security.permissions.impl.AclChange;
*/
public interface AclDAO
{
+ /**
+ * Transaction-scope setting to make the DAO guarantee the validity of all caches: some cache data will be reloaded;
+ * some cache data will be considered safe.
+ */
+ public void setCheckAclConsistency();
+
/**
* Get an ACL (including entries)
*/
diff --git a/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java b/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java
index f0127c2cdb..40b9acae39 100644
--- a/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java
+++ b/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2012 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -21,6 +21,8 @@ package org.alfresco.repo.domain.permissions;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -70,11 +72,8 @@ public class AclDAOImpl implements AclDAO
private AclCrudDAO aclCrudDAO;
private NodeDAO nodeDAO;
private TenantService tenantService;
- private SimpleCache aclCache;
- private SimpleCache> readersCache;
+ private SimpleCache aclCache;
- private SimpleCache> readersDeniedCache;
-
private enum WriteMode
{
/**
@@ -132,27 +131,11 @@ public class AclDAOImpl implements AclDAO
*
* @param aclCache
*/
- public void setAclCache(SimpleCache aclCache)
+ public void setAclCache(SimpleCache aclCache)
{
this.aclCache = aclCache;
}
- /**
- * @param readersCache the readersCache to set
- */
- public void setReadersCache(SimpleCache> readersCache)
- {
- this.readersCache = readersCache;
- }
-
- /**
- * @param readersDeniedCache the readersDeniedCache to set
- */
- public void setReadersDeniedCache(SimpleCache> readersDeniedCache)
- {
- this.readersDeniedCache = readersDeniedCache;
- }
-
/**
* {@inheritDoc}
*/
@@ -348,8 +331,8 @@ public class AclDAOImpl implements AclDAO
}
getWritable(created, toInherit, excluded, toAdd, toInherit, false, changes, WriteMode.CREATE_AND_INHERIT);
-
- return createdAcl;
+ // Fetch an up-to-date version
+ return getAcl(created);
}
private void getWritable(
@@ -442,9 +425,6 @@ public class AclDAOImpl implements AclDAO
AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(id);
if (!acl.isLatest())
{
- aclCache.remove(id);
- readersCache.remove(id);
- readersDeniedCache.remove(id);
return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType());
}
@@ -493,9 +473,6 @@ public class AclDAOImpl implements AclDAO
}
acl.setAclChangeSetId(getCurrentChangeSetId());
aclCrudDAO.updateAcl(acl);
- aclCache.remove(id);
- readersCache.remove(id);
- readersDeniedCache.remove(id);
return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType());
}
else if ((acl.getAclChangeSetId() == getCurrentChangeSetId()) && (!requiresVersion) && (!acl.getRequiresVersion()))
@@ -533,9 +510,6 @@ public class AclDAOImpl implements AclDAO
acl.setInheritsFrom(inheritsFrom);
}
aclCrudDAO.updateAcl(acl);
- aclCache.remove(id);
- readersCache.remove(id);
- readersDeniedCache.remove(id);
return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType());
}
else
@@ -623,9 +597,6 @@ public class AclDAOImpl implements AclDAO
acl.setLatest(Boolean.FALSE);
acl.setRequiresVersion(Boolean.FALSE);
aclCrudDAO.updateAcl(acl);
- aclCache.remove(id);
- readersCache.remove(id);
- readersDeniedCache.remove(id);
return new AclChangeImpl(id, created, acl.getAclType(), newAcl.getAclType());
}
}
@@ -751,13 +722,13 @@ public class AclDAOImpl implements AclDAO
@Override
public List deleteAccessControlEntries(final String authority)
{
- List acls = new ArrayList();
+ List aclChanges = new LinkedList();
// get authority
Authority authEntity = aclCrudDAO.getAuthority(authority);
if (authEntity == null)
{
- return acls;
+ return aclChanges;
}
List aces = new ArrayList();
@@ -767,6 +738,7 @@ public class AclDAOImpl implements AclDAO
boolean leaveAuthority = false;
if (members.size() > 0)
{
+ Set acls = new HashSet(members.size() * 2);
List membersToDelete = new ArrayList(members.size());
// fix up members and extract acls and aces
@@ -817,12 +789,9 @@ public class AclDAOImpl implements AclDAO
if (!hasAnotherTenantNodes)
{
- aclCache.remove(aclId);
- readersCache.remove(aclId);
- readersDeniedCache.remove(aclId);
-
- Acl list = aclCrudDAO.getAcl(aclId);
- acls.add(new AclChangeImpl(aclId, aclId, list.getAclType(), list.getAclType()));
+ AclUpdateEntity list = aclCrudDAO.getAclForUpdate(aclId);
+ aclChanges.add(new AclChangeImpl(aclId, aclId, list.getAclType(), list.getAclType()));
+ acls.add(list);
membersToDelete.add(aclMemberId);
aces.add((Long)aceId);
}
@@ -830,6 +799,13 @@ public class AclDAOImpl implements AclDAO
// delete list of acl members
aclCrudDAO.deleteAclMembers(membersToDelete);
+
+ // Remember to 'touch' all affected ACLs
+ for (AclUpdateEntity acl : acls)
+ {
+ acl.setAclChangeSetId(getCurrentChangeSetId());
+ aclCrudDAO.updateAcl(acl);
+ }
}
if (!leaveAuthority)
@@ -859,7 +835,7 @@ public class AclDAOImpl implements AclDAO
}
}
- return acls;
+ return aclChanges;
}
/**
@@ -874,10 +850,6 @@ public class AclDAOImpl implements AclDAO
// delete acl members & acl
aclCrudDAO.deleteAclMembersByAcl(aclId);
aclCrudDAO.deleteAcl(aclId);
-
- aclCache.remove(aclId);
- readersCache.remove(aclId);
- readersDeniedCache.remove(aclId);
}
if (dbAcl.getAclType() == ACLType.SHARED)
{
@@ -893,10 +865,6 @@ public class AclDAOImpl implements AclDAO
// delete acl members & acl
aclCrudDAO.deleteAclMembersByAcl(aclId);
aclCrudDAO.deleteAcl(aclId);
-
- aclCache.remove(aclId);
- readersCache.remove(aclId);
- readersDeniedCache.remove(aclId);
}
}
else
@@ -1008,10 +976,6 @@ public class AclDAOImpl implements AclDAO
aclCrudDAO.deleteAcl(acl.getId());
}
- // remove the deleted acl from the cache
- aclCache.remove(id);
- readersCache.remove(id);
- readersDeniedCache.remove(id);
acls.add(new AclChangeImpl(id, null, acl.getAclType(), null));
return acls;
}
@@ -1075,37 +1039,31 @@ public class AclDAOImpl implements AclDAO
return aclCrudDAO.getAcl(id);
}
+ @Override
+ public void setCheckAclConsistency()
+ {
+ aclCrudDAO.setCheckAclConsistency();
+ }
+
/**
* {@inheritDoc}
*/
@Override
public AccessControlList getAccessControlList(Long id)
{
- AccessControlList acl = aclCache.get(id);
- if (acl == null)
- {
- acl = getAccessControlListImpl(id);
- aclCache.put(id, acl);
- }
- else
- {
- // System.out.println("Used cache for "+id);
- }
- return acl;
- }
-
- /**
- * @return the access control list
- */
- private AccessControlList getAccessControlListImpl(final Long id)
- {
- SimpleAccessControlList acl = new SimpleAccessControlList();
+ // Used the cached properties as our cache key
AccessControlListProperties properties = getAccessControlListProperties(id);
if (properties == null)
{
return null;
}
+ AccessControlList aclCached = aclCache.get((Serializable)properties);
+ if (aclCached != null)
+ {
+ return aclCached;
+ }
+ SimpleAccessControlList acl = new SimpleAccessControlList();
acl.setProperties(properties);
List