diff --git a/config/alfresco/authentication-services-context.xml b/config/alfresco/authentication-services-context.xml
index ad984c2688..c1a83469b7 100644
--- a/config/alfresco/authentication-services-context.xml
+++ b/config/alfresco/authentication-services-context.xml
@@ -251,7 +251,7 @@
-
+
diff --git a/config/alfresco/workflow-context.xml b/config/alfresco/workflow-context.xml
index fbe590b1af..d9bc0de942 100644
--- a/config/alfresco/workflow-context.xml
+++ b/config/alfresco/workflow-context.xml
@@ -12,6 +12,9 @@
+
+ ${server.transaction.allow-writes}
+
diff --git a/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java b/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java
index 75e0687eea..69a9b78c33 100644
--- a/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java
+++ b/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java
@@ -259,10 +259,6 @@ public class CheckOutCheckInServiceImpl implements CheckOutCheckInService
workingCopyProperties.put(ContentModel.PROP_WORKING_COPY_OWNER, userName);
this.nodeService.addAspect(workingCopy, ContentModel.ASPECT_WORKING_COPY, workingCopyProperties);
- // Apply the sys:temporary aspect to tag the working copy as a temporary node
- // so it doesn't get archived when checked in
- this.nodeService.addAspect(workingCopy, ContentModel.ASPECT_TEMPORARY, null);
-
// Lock the origional node
this.lockService.lock(nodeRef, LockType.READ_ONLY_LOCK);
diff --git a/source/java/org/alfresco/repo/domain/PropertyValue.java b/source/java/org/alfresco/repo/domain/PropertyValue.java
index 4954c2989d..55ccce5cd3 100644
--- a/source/java/org/alfresco/repo/domain/PropertyValue.java
+++ b/source/java/org/alfresco/repo/domain/PropertyValue.java
@@ -24,6 +24,10 @@
*/
package org.alfresco.repo.domain;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
@@ -512,6 +516,11 @@ public class PropertyValue implements Cloneable, Serializable
}
else
{
+ // Convert the value to the type required. This ensures that any type conversion issues
+ // are caught early and prevent the scenario where the data in the DB cannot be given
+ // back out because it is unconvertable.
+ ValueType valueType = makeValueType(typeQName);
+ value = valueType.convert(value);
// get the persisted type
ValueType persistedValueType = this.actualType.getPersistedType(value);
// convert to the persistent type
@@ -679,7 +688,7 @@ public class PropertyValue implements Cloneable, Serializable
this.attributeValue = (Attribute) value;
break;
case SERIALIZABLE:
- this.serializableValue = (Serializable) value;
+ this.serializableValue = cloneSerializable(value);
break;
default:
throw new AlfrescoRuntimeException("Unrecognised value type: " + persistedType);
@@ -687,6 +696,37 @@ public class PropertyValue implements Cloneable, Serializable
// we store the type that we persisted as
this.persistedType = persistedType;
}
+
+ /**
+ * Clones a serializable object to disconnect the original instance from the persisted instance.
+ *
+ * @param original the original object
+ * @return the new cloned object
+ */
+ private Serializable cloneSerializable(Serializable original)
+ {
+ try
+ {
+ // Connect the pipes
+ PipedOutputStream pipeOut = new PipedOutputStream();
+ PipedInputStream pipeIn = new PipedInputStream();
+ pipeOut.connect(pipeIn);
+
+ ObjectOutputStream objectOut = new ObjectOutputStream(pipeOut);
+ ObjectInputStream objectIn = new ObjectInputStream(pipeIn);
+
+ // Now write the object
+ objectOut.writeObject(original);
+ // Read the new object in
+ Object newObj = objectIn.readObject();
+ // Done
+ return (Serializable) newObj;
+ }
+ catch (Throwable e)
+ {
+ throw new AlfrescoRuntimeException("Failed to clone serializable object: " + original, e);
+ }
+ }
/**
* @return Returns the persisted value, keying off the persisted value type
diff --git a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java
index 482ad17b4f..9857e106f6 100644
--- a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java
+++ b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java
@@ -671,8 +671,8 @@ public abstract class AbstractNodeServiceImpl implements NodeService
/**
* Extracts the externally-visible property from the {@link PropertyValue propertyValue}.
*
- * @param propertyDef
- * @param propertyValue
+ * @param propertyDef the model property definition - may be null
+ * @param propertyValue the persisted property
* @return Returns the value of the property in the format dictated by the property
* definition, or null if the property value is null
*/
@@ -711,8 +711,8 @@ public abstract class AbstractNodeServiceImpl implements NodeService
/**
* Sets the default property values
*
- * @param classDefinition
- * @param properties
+ * @param classDefinition the model type definition for which to get defaults
+ * @param properties the properties of the node
*/
protected void addDefaultPropertyValues(ClassDefinition classDefinition, Map properties)
{
diff --git a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java
index 78ee8d01be..7be168d986 100644
--- a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java
+++ b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java
@@ -53,6 +53,7 @@ import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.service.cmr.dictionary.ClassDefinition;
+import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryException;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
@@ -68,12 +69,14 @@ import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.Path;
import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.cmr.repository.datatype.TypeConversionException;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.BaseSpringTest;
import org.alfresco.util.GUID;
+import org.alfresco.util.PropertyMap;
import org.hibernate.Session;
import org.springframework.context.ApplicationContext;
@@ -556,11 +559,22 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest
}
Map propertyDefs = classDef.getProperties();
// make up a property value for each property
- for (QName propertyName : propertyDefs.keySet())
+ for (Map.Entry entry : propertyDefs.entrySet())
{
- Serializable value = new Long(System.currentTimeMillis());
+ QName propertyQName = entry.getKey();
+ QName propertyTypeQName = entry.getValue().getDataType().getName();
+ // Get the property type
+ Serializable value = null;
+ if (propertyTypeQName.equals(DataTypeDefinition.CONTENT))
+ {
+ value = new ContentData(null, MimetypeMap.EXTENSION_BINARY, 0L, "UTF-8");
+ }
+ else
+ {
+ value = new Long(System.currentTimeMillis());
+ }
// add it
- properties.put(propertyName, value);
+ properties.put(propertyQName, value);
}
}
@@ -1829,10 +1843,10 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest
ContentModel.TYPE_CONTENT,
props).getChildRef();
- this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_TITLED, null);
+ nodeService.addAspect(nodeRef, ContentModel.ASPECT_TITLED, null);
- this.nodeService.setProperty(nodeRef, ContentModel.PROP_DESCRIPTION, "my description");
- this.nodeService.setProperty(nodeRef, ContentModel.PROP_TITLE, "my title");
+ nodeService.setProperty(nodeRef, ContentModel.PROP_DESCRIPTION, "my description");
+ nodeService.setProperty(nodeRef, ContentModel.PROP_TITLE, "my title");
JavaBehaviour behaviour = new JavaBehaviour(this, "onUpdateProperties");
PolicyComponent policyComponent = (PolicyComponent)this.applicationContext.getBean("policyComponent");
@@ -1844,7 +1858,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest
behaviourExecuted = false;
// Update the title property and check that the behaviour has been fired
- this.nodeService.setProperty(nodeRef, ContentModel.PROP_TITLE, "changed title");
+ nodeService.setProperty(nodeRef, ContentModel.PROP_TITLE, "changed title");
assertTrue("The onUpdateProperties behaviour has not been fired.", behaviourExecuted);
}
@@ -1853,19 +1867,66 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest
Map before,
Map after)
{
- behaviourExecuted = true;
- assertFalse(before.get(ContentModel.PROP_TITLE).toString().equals(after.get(ContentModel.PROP_TITLE).toString()));
-
- System.out.print("Before values: ");
- for (Map.Entry entry : before.entrySet())
- {
- System.out.println(entry.getKey().toString() + " : " + entry.getValue().toString());
- }
- System.out.print("\nAfter values: ");
- for (Map.Entry entry : after.entrySet())
- {
- System.out.println(entry.getKey().toString() + " : " + entry.getValue().toString());
- }
+ behaviourExecuted = true;
+ assertFalse(before.get(ContentModel.PROP_TITLE).toString().equals(after.get(ContentModel.PROP_TITLE).toString()));
+
+ System.out.print("Before values: ");
+ for (Map.Entry entry : before.entrySet())
+ {
+ System.out.println(entry.getKey().toString() + " : " + entry.getValue().toString());
+ }
+ System.out.print("\nAfter values: ");
+ for (Map.Entry entry : after.entrySet())
+ {
+ System.out.println(entry.getKey().toString() + " : " + entry.getValue().toString());
+ }
}
+ /**
+ * Checks that unconvertable property values cannot be persisted.
+ */
+ public void testAR782() throws Exception
+ {
+ Map properties = nodeService.getProperties(rootNodeRef);
+ // Set cm:created correctly
+ properties.put(ContentModel.PROP_CREATED, new Date());
+ nodeService.setProperties(rootNodeRef, properties);
+ try
+ {
+ // Set cm:created using something that can't be converted to a Date
+ properties.put(ContentModel.PROP_CREATED, "blah");
+ nodeService.setProperties(rootNodeRef, properties);
+ fail("Failed to catch type conversion issue early.");
+ }
+ catch (TypeConversionException e)
+ {
+ // Expected
+ }
+ }
+
+ /**
+ * Helper test class for {@link BaseNodeServiceTest#testAR1414()}.
+ */
+ private static class AR1414Blob implements Serializable
+ {
+ private static final long serialVersionUID = 5616094206968290908L;
+ int i = 0;
+ }
+
+ /**
+ * Check that Serializable properties do not remain connected to the L1 session
+ */
+ public void testAR1414() throws Exception
+ {
+ AR1414Blob blob = new AR1414Blob();
+
+ QName propertyQName = QName.createQName(NAMESPACE, "testAR1414Prop");
+ nodeService.setProperty(rootNodeRef, propertyQName, blob);
+ // Modify our original blob
+ blob.i = 100;
+ // Get the property
+ AR1414Blob checkBlob = (AR1414Blob) nodeService.getProperty(rootNodeRef, propertyQName);
+ assertNotNull(checkBlob);
+ assertEquals("Blob was modified while persisted", 0, checkBlob.i);
+ }
}
diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java
index 05e7cab3f7..21c70b49f0 100644
--- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java
+++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java
@@ -774,10 +774,11 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
// check if we need to archive the node
StoreRef archiveStoreRef = null;
- if (nodeAspectQNames.contains(ContentModel.ASPECT_TEMPORARY))
+ if (nodeAspectQNames.contains(ContentModel.ASPECT_TEMPORARY) ||
+ nodeAspectQNames.contains(ContentModel.ASPECT_WORKING_COPY))
{
- // the node has the temporary aspect meaning
- // it can not be archived
+ // The node is either temporary or a working copy.
+ // It can not be archived.
requiresDelete = true;
}
else
diff --git a/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java b/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java
index c1f5424f0e..39e6e7fb41 100644
--- a/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java
+++ b/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java
@@ -63,6 +63,7 @@ public class WorkflowDeployer extends AbstractLifecycleBean
public static final String REDEPLOY = "redeploy";
// Dependencies
+ private boolean allowWrite = true;
private TransactionService transactionService;
private WorkflowService workflowService;
private AuthenticationComponent authenticationComponent;
@@ -72,6 +73,16 @@ public class WorkflowDeployer extends AbstractLifecycleBean
private List resourceBundles = new ArrayList();
+ /**
+ * Set whether we write or not
+ *
+ * @param write true (default) if the import must go ahead, otherwise no import will occur
+ */
+ public void setAllowWrite(boolean write)
+ {
+ this.allowWrite = write;
+ }
+
/**
* Sets the Transaction Service
*
@@ -159,7 +170,7 @@ public class WorkflowDeployer extends AbstractLifecycleBean
{
throw new ImporterException("Workflow Service must be provided");
}
-
+
UserTransaction userTransaction = transactionService.getUserTransaction();
authenticationComponent.setSystemUserAsCurrentUser();
@@ -200,16 +211,24 @@ public class WorkflowDeployer extends AbstractLifecycleBean
ClassPathResource workflowResource = new ClassPathResource(location);
// deploy workflow definition
- if (!redeploy && workflowService.isDefinitionDeployed(engineId, workflowResource.getInputStream(), mimetype))
+ if (!allowWrite)
{
- if (logger.isDebugEnabled())
- logger.debug("Workflow deployer: Definition '" + location + "' already deployed");
+ // we're in read-only node
+ logger.warn("Repository is in read-only mode; not deploying workflow " + location);
}
else
{
- WorkflowDeployment deployment = workflowService.deployDefinition(engineId, workflowResource.getInputStream(), mimetype);
- if (logger.isInfoEnabled())
- logger.info("Workflow deployer: Deployed process definition '" + deployment.definition.title + "' (version " + deployment.definition.version + ") from '" + location + "' with " + deployment.problems.length + " problems");
+ if (!redeploy && workflowService.isDefinitionDeployed(engineId, workflowResource.getInputStream(), mimetype))
+ {
+ if (logger.isDebugEnabled())
+ logger.debug("Workflow deployer: Definition '" + location + "' already deployed");
+ }
+ else
+ {
+ WorkflowDeployment deployment = workflowService.deployDefinition(engineId, workflowResource.getInputStream(), mimetype);
+ if (logger.isInfoEnabled())
+ logger.info("Workflow deployer: Deployed process definition '" + deployment.definition.title + "' (version " + deployment.definition.version + ") from '" + location + "' with " + deployment.problems.length + " problems");
+ }
}
}
}
diff --git a/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java b/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java
index a25441acd0..4c6147bbc8 100644
--- a/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java
+++ b/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java
@@ -114,8 +114,11 @@ public class WorkflowInterpreter extends BaseInterpreter
{
setCurrentUserName(BaseInterpreter.DEFAULT_ADMIN);
- interpretCommand("var bpm:package package 1");
- interpretCommand("var bpm:assignee person admin");
+ if (!transactionService.isReadOnly())
+ {
+ interpretCommand("var bpm:package package 1");
+ interpretCommand("var bpm:assignee person admin");
+ }
setCurrentUserName(null);
}
diff --git a/source/java/org/alfresco/repo/workflow/jbpm/WorkflowTaskInstance.java b/source/java/org/alfresco/repo/workflow/jbpm/WorkflowTaskInstance.java
index a51577711d..963109d08c 100644
--- a/source/java/org/alfresco/repo/workflow/jbpm/WorkflowTaskInstance.java
+++ b/source/java/org/alfresco/repo/workflow/jbpm/WorkflowTaskInstance.java
@@ -28,6 +28,7 @@ import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.service.cmr.workflow.WorkflowException;
import org.alfresco.service.namespace.QName;
@@ -110,6 +111,12 @@ public class WorkflowTaskInstance extends TaskInstance
@Override
public void end(Transition transition)
{
+ // Force assignment of task if transition is taken, but no owner has yet been assigned
+ if (actorId == null)
+ {
+ actorId = AuthenticationUtil.getCurrentUserName();
+ }
+
// Set task properties on completion of task
// NOTE: Set properties first, so they're available during the submission of
// task variables to the process context