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