diff --git a/source/java/org/alfresco/repo/jscript/ScriptNode.java b/source/java/org/alfresco/repo/jscript/ScriptNode.java
index 9564138213..fbbbe9969e 100644
--- a/source/java/org/alfresco/repo/jscript/ScriptNode.java
+++ b/source/java/org/alfresco/repo/jscript/ScriptNode.java
@@ -1726,10 +1726,23 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider
{
Serializable value = (Serializable) this.properties.get(key);
+ QName qname = createQName(key);
+
+ // MNT-15798
+ if (ContentModel.PROP_CONTENT.equals(qname) && isScriptContent(value))
+ {
+ ScriptContentData contentData = (ScriptContentData) value;
+ // Do not persist the contentData if it was not touched
+ if (!contentData.isDirty())
+ {
+ continue;
+ }
+ }
+
// perform the conversion from script wrapper object to repo serializable values
value = getValueConverter().convertValueForRepo(value);
- props.put(createQName(key), value);
+ props.put(qname, value);
}
this.nodeService.setProperties(this.nodeRef, props);
}
@@ -3768,6 +3781,7 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider
{
this.contentData = contentData;
this.property = property;
+ this.isDirty = ContentData.hasContent(contentData);
}
/* (non-Javadoc)
@@ -3814,6 +3828,15 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider
return null;
}
+ /**
+ * @return true
if the contentData has a binary (content URL) associated and the updates on contentData and related properties should be saved.
+ * false
if the contentData has a temporary value and no actual binary to be persisted.
+ */
+ public boolean isDirty()
+ {
+ return this.isDirty;
+ }
+
/**
* Set the content stream
*
@@ -3827,7 +3850,7 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider
writer.putContent(content);
// update cached variables after putContent()
- this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property);
+ updateContentData(true);
}
/**
@@ -3844,7 +3867,7 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider
writer.putContent(content.getInputStream());
// update cached variables after putContent()
- this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property);
+ updateContentData(true);
}
/**
@@ -3885,7 +3908,7 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider
writer.putContent(is);
// update cached variables after putContent()
- this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property);
+ updateContentData(true);
}
/**
@@ -3900,7 +3923,7 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider
writer.putContent(inputStream);
// update cached variables after putContent()
- this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property);
+ updateContentData(true);
}
/**
@@ -3923,7 +3946,7 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider
writer.setEncoding(null);
// update cached variables after putContent()
- this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property);
+ updateContentData(true);
}
/**
@@ -3976,18 +3999,14 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider
{
this.contentData = ContentData.setEncoding(this.contentData, encoding);
services.getNodeService().setProperty(nodeRef, this.property, this.contentData);
-
- // update cached variables after putContent()
- this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property);
+ updateContentData(false);
}
public void setMimetype(String mimetype)
{
this.contentData = ContentData.setMimetype(this.contentData, mimetype);
services.getNodeService().setProperty(nodeRef, this.property, this.contentData);
-
- // update cached variables after putContent()
- this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property);
+ updateContentData(false);
}
/**
@@ -4047,8 +4066,18 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider
return encoding;
}
+ /**
+ * Update cached contentData and the isDirty flag
+ */
+ private void updateContentData(boolean touchContent)
+ {
+ this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property);
+ this.isDirty = touchContent ? true : this.isDirty;
+ }
+
private ContentData contentData;
private QName property;
+ private boolean isDirty;
}
/**
diff --git a/source/test-java/org/alfresco/repo/jscript/ScriptNodeTest.java b/source/test-java/org/alfresco/repo/jscript/ScriptNodeTest.java
index e6d8e0c7f2..01ec997687 100644
--- a/source/test-java/org/alfresco/repo/jscript/ScriptNodeTest.java
+++ b/source/test-java/org/alfresco/repo/jscript/ScriptNodeTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2014 Alfresco Software Limited.
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -35,6 +35,7 @@ import org.alfresco.repo.dictionary.DictionaryDAO;
import org.alfresco.repo.dictionary.DictionaryRepositoryBootstrap;
import org.alfresco.repo.dictionary.RepositoryLocation;
import org.alfresco.repo.i18n.MessageService;
+import org.alfresco.repo.jscript.ScriptNode.ScriptContentData;
import org.alfresco.repo.model.Repository;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
@@ -47,6 +48,7 @@ import org.alfresco.repo.version.VersionableAspect;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
+import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
@@ -80,6 +82,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestName;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.ScriptableObject;
/**
@@ -545,4 +549,93 @@ public class ScriptNodeTest
revertBootstrap();
}
+
+ /**
+ * MNT-15798 - Content Data should be created only when it has a binary, not as a side effect of getters on ScriptNode.
+ */
+ @Test
+ public void testContentDataCreation()
+ {
+ Repository repositoryHelper = (Repository) APP_CONTEXT_INIT.getApplicationContext().getBean("repositoryHelper");
+ NodeRef companyHome = repositoryHelper.getCompanyHome();
+
+ NodeRef newNode1 = testNodes.createNode(companyHome, "theTestContent1", ContentModel.TYPE_CONTENT, AuthenticationUtil.getFullyAuthenticatedUser());
+
+ // test on content data
+ ScriptNode sn = new ScriptNode(newNode1, SERVICE_REGISTRY);
+ sn.setScope(getScope());
+
+ ContentData contentData = (ContentData) NODE_SERVICE.getProperty(newNode1, ContentModel.PROP_CONTENT);
+ assertNull(contentData);
+
+ sn.setMimetype(MimetypeMap.MIMETYPE_PDF);
+ sn.save();
+ contentData = (ContentData) NODE_SERVICE.getProperty(newNode1, ContentModel.PROP_CONTENT);
+ assertNull(contentData);
+
+ sn.setContent("Marks to prove it.");
+ sn.save();
+ contentData = (ContentData) NODE_SERVICE.getProperty(newNode1, ContentModel.PROP_CONTENT);
+ assertNotNull(contentData);
+ assertEquals(true, ContentData.hasContent(contentData));
+
+ // test on ScriptContentData
+ NodeRef newNode2 = testNodes.createNode(companyHome, "theTestContent2.txt", ContentModel.TYPE_CONTENT, AuthenticationUtil.getFullyAuthenticatedUser());
+ ScriptNode sn2 = new ScriptNode(newNode2, SERVICE_REGISTRY);
+ sn2.setScope(getScope());
+
+ ScriptContentData scd = sn2.new ScriptContentData(null, ContentModel.PROP_CONTENT);
+ //set the "mocked" script content data on the script node
+ sn2.getProperties().put(ContentModel.PROP_CONTENT.toString(), scd);
+
+ assertEquals(false, scd.isDirty());
+
+ scd.guessMimetype("theTestContent2.pdf");
+ assertEquals(false, scd.isDirty());
+
+ scd.setMimetype("text/plain");
+ assertEquals(false, scd.isDirty());
+
+ scd.setEncoding("UTF-8");
+ assertEquals(false, scd.isDirty());
+
+ sn2.save();
+ contentData = (ContentData) NODE_SERVICE.getProperty(newNode2, ContentModel.PROP_CONTENT);
+ assertNull(contentData);
+
+ scd.setContent("Marks to prove it.");
+ assertEquals(true, scd.isDirty());
+
+ scd.setEncoding("ISO-8859-1");
+ assertEquals(true, scd.isDirty());
+
+ sn2.save();
+ contentData = (ContentData) NODE_SERVICE.getProperty(newNode2, ContentModel.PROP_CONTENT);
+ assertNotNull(contentData);
+
+ NODE_SERVICE.removeProperty(newNode1, ContentModel.PROP_CONTENT);
+ NODE_SERVICE.removeProperty(newNode2, ContentModel.PROP_CONTENT);
+ }
+
+ private ScriptableObject getScope()
+ {
+ // Create a scope for the value conversion. This scope will be an empty scope exposing basic Object and Function, sufficient for value-conversion.
+ // In case no context is active for the current thread, we can safely enter end exit one to get hold of a scope
+ ScriptableObject scope;
+ Context ctx = Context.getCurrentContext();
+ boolean closeContext = false;
+ if (ctx == null)
+ {
+ ctx = Context.enter();
+ closeContext = true;
+ }
+ scope = ctx.initStandardObjects();
+ scope.setParentScope(null);
+
+ if (closeContext)
+ {
+ Context.exit();
+ }
+ return scope;
+ }
}