parentAssocs = getParentAssocsInternal(nodeId);
+ for (ChildAssoc parentAssoc : parentAssocs)
+ {
+ propagateTimestamps(parentAssoc);
+ }
+ }
+
+ /**
+ * Sets the timestamps for nodes set during the transaction.
+ *
+ * The implementation attempts to propagate the timestamps in the same transaction, but during periods of high
+ * concurrent modification to children of a particular parent node, the contention-resolution at the database
+ * can lead to delays in the processes. When this occurs, the process is pushed to after the transaction for an
+ * arbitrary period of time, after which the server will again attempt to do the work in the transaction.
+ *
+ * @author Derek Hulley
+ */
+ private class TimestampPropagator extends TransactionListenerAdapter implements RetryingTransactionCallback
+ {
+ private final Set nodeIds;
+
+ private TimestampPropagator()
+ {
+ this.nodeIds = new HashSet(23);
+ }
+
+ public void addNode(Long nodeId)
+ {
+ nodeIds.add(nodeId);
+ }
+
+ @Override
+ public void afterCommit()
+ {
+ if (nodeIds.size() == 0)
+ {
+ return;
+ }
+ // Execute using the explicit transaction attributes
+ try
+ {
+ auditableTransactionHelper.doInTransaction(this, false, true);
+ }
+ catch (Throwable e)
+ {
+ logger.info("Failed to update auditable properties for nodes: " + nodeIds);
+ }
+ }
+
+ public static final String QUERY_UPDATE_AUDITABLE_MODIFIED = "node.UpdateAuditableModified";
+ public Integer execute() throws Throwable
+ {
+ long now = System.currentTimeMillis();
+ return executeImpl(now, true);
+ }
+
+ private Integer executeImpl(long now, boolean isPostTransaction) throws Throwable
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Updating timestamps for nodes: " + nodeIds);
+ }
+ Session session = getSession();
+ final Date modifiedDate = new Date(now);
+ final String modifier = getCurrentUser();
+ int count = 0;
+ for (final Long nodeId : nodeIds)
+ {
+ Node node = getNodeOrNull(nodeId);
+ if (node == null)
+ {
+ continue;
+ }
+ AuditableProperties auditableProperties = node.getAuditableProperties();
+ if (auditableProperties == null)
+ {
+ // Don't bother setting anything if there are no values
+ continue;
+ }
+ // Only set the value if our modified date is later
+ Date currentModifiedDate = (Date) auditableProperties.getAuditableProperty(ContentModel.PROP_MODIFIED);
+ if (currentModifiedDate != null && currentModifiedDate.compareTo(modifiedDate) >= 0)
+ {
+ // The value on the node is greater
+ continue;
+ }
+ // Lock it
+ session.lock(node, LockMode.UPGRADE_NOWAIT); // Might fail immediately, but that is better than waiting
+ auditableProperties.setAuditValues(modifier, modifiedDate, false);
+ count++;
+ if (count % 1000 == 0)
+ {
+ DirtySessionMethodInterceptor.flushSession(session);
+ SessionSizeResourceManager.clear(session);
+ }
+ }
+ return new Integer(count);
+ }
+ }
+
+ private static final String RESOURCE_KEY_TIMESTAMP_PROPAGATOR = "hibernate.timestamp.propagator";
+ /**
+ * Ensures that the timestamps are propogated to the parent node of the association, but only
+ * if the association requires it.
+ */
+ private void propagateTimestamps(ChildAssoc parentAssoc)
+ {
+ // Shortcut
+ if (!enableTimestampPropagation)
+ {
+ return;
+ }
+ QName assocTypeQName = parentAssoc.getTypeQName(qnameDAO);
+ AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName);
+ if (assocDef == null)
+ {
+ // Not found, so just ignore
+ return;
+ }
+ else if (!assocDef.isChild())
+ {
+ // Unexpected, but not our immediate concern
+ return;
+ }
+ ChildAssociationDefinition childAssocDef = (ChildAssociationDefinition) assocDef;
+ // Do we send timestamps up?
+ if (!childAssocDef.getPropagateTimestamps())
+ {
+ return;
+ }
+ // We have to update the parent
+ TimestampPropagator propagator =
+ (TimestampPropagator) AlfrescoTransactionSupport.getResource(RESOURCE_KEY_TIMESTAMP_PROPAGATOR);
+ if (propagator == null)
+ {
+ propagator = new TimestampPropagator();
+ AlfrescoTransactionSupport.bindListener(propagator);
+ }
+ propagator.addNode(parentAssoc.getParent().getId());
+ }
public Pair newNode(StoreRef storeRef, String uuid, QName nodeTypeQName) throws InvalidTypeException
{
@@ -1365,6 +1559,10 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
public void deleteNode(Long nodeId)
{
Node node = getNodeNotNull(nodeId);
+
+ // Propagate timestamps
+ propagateTimestamps(node);
+
Set deletedChildAssocIds = new HashSet(10);
deleteNodeInternal(node, false, deletedChildAssocIds);
@@ -4159,7 +4357,10 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
{
if (collapsedValue != null && !(collapsedValue instanceof Collection))
{
- collapsedValue = (Serializable) Collections.singletonList(collapsedValue);
+ // Can't use Collections.singletonList: ETHREEOH-1172
+ ArrayList collection = new ArrayList(1);
+ collection.add(collapsedValue);
+ collapsedValue = collection;
}
}
// Store the value
@@ -4251,7 +4452,10 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
// Make sure that multi-valued properties are returned as a collection
if (propertyDef != null && propertyDef.isMultiValued() && result != null && !(result instanceof Collection))
{
- result = (Serializable) Collections.singletonList(result);
+ // Can't use Collections.singletonList: ETHREEOH-1172
+ ArrayList collection = new ArrayList(1);
+ collection.add(result);
+ result = collection;
}
// Done
return result;
diff --git a/source/java/org/alfresco/repo/node/index/IndexTransactionTracker.java b/source/java/org/alfresco/repo/node/index/IndexTransactionTracker.java
index 7f3d129b95..863c0f9eb1 100644
--- a/source/java/org/alfresco/repo/node/index/IndexTransactionTracker.java
+++ b/source/java/org/alfresco/repo/node/index/IndexTransactionTracker.java
@@ -36,6 +36,7 @@ import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.domain.Transaction;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.util.ISO8601DateFormat;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -195,24 +196,12 @@ public class IndexTransactionTracker extends AbstractReindexComponent
}
};
- public void reindexFromTxn(long txnId)
+ public void resetFromTxn(long txnId)
{
- try
- {
- logger.info("reindexFromTxn: "+txnId);
+ logger.info("resetFromTxn: "+txnId);
- this.fromTxnId = txnId;
- this.started = false;
-
- reindex();
-
- this.started = false;
- }
- finally
- {
- this.fromTxnId = 0L;
-
- }
+ this.fromTxnId = txnId;
+ this.started = false; // this will cause index tracker to break out (so that it can be re-started)
}
@Override
@@ -225,7 +214,7 @@ public class IndexTransactionTracker extends AbstractReindexComponent
RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper();
- if ((!started) || (this.fromTxnId != 0L))
+ if (!started)
{
// Disable in-transaction indexing
if (disableInTransactionIndexing && nodeIndexer != null)
@@ -240,6 +229,8 @@ public class IndexTransactionTracker extends AbstractReindexComponent
if (this.fromTxnId != 0L)
{
+ logger.info("reindexImpl: start fromTxnId: "+fromTxnId+" "+this);
+
Long fromTxnCommitTime = getTxnCommitTime(this.fromTxnId);
if (fromTxnCommitTime == null)
@@ -254,12 +245,10 @@ public class IndexTransactionTracker extends AbstractReindexComponent
fromTimeInclusive = retryingTransactionHelper.doInTransaction(getStartingCommitTimeWork, true, true);
}
+ fromTxnId = 0L;
started = true;
- if (logger.isDebugEnabled())
- {
- logger.debug("reindexImpl: fromTimeInclusive: "+fromTimeInclusive+" "+this);
- }
+ logger.info("reindexImpl: start fromTimeInclusive: "+ISO8601DateFormat.format(new Date(fromTimeInclusive))+" "+this);
}
while (true)
@@ -274,9 +263,9 @@ public class IndexTransactionTracker extends AbstractReindexComponent
// Wait for the asynchronous reindexing to complete
waitForAsynchronousReindexing();
- if (logger.isDebugEnabled())
+ if (logger.isTraceEnabled())
{
- logger.debug("reindexImpl: completed: " + this);
+ logger.trace("reindexImpl: completed: "+this);
}
statusMsg = NO_REINDEX;
@@ -383,9 +372,9 @@ public class IndexTransactionTracker extends AbstractReindexComponent
previousTxnIds.add(txn.getId());
}
- if (isShuttingDown())
+ if (isShuttingDown() || (! started))
{
- // break out if the VM is shutting down
+ // break out if the VM is shutting down or tracker has been reset (ie. !started)
return false;
}
else
@@ -644,8 +633,9 @@ found:
}
}
- if (isShuttingDown())
+ if (isShuttingDown() || (! started))
{
+ // break out if the VM is shutting down or tracker has been reset (ie. !started)
break;
}
// Flush the reindex buffer, if it is full or if we are on the last transaction and there are no more
diff --git a/source/java/org/alfresco/service/cmr/avm/AVMService.java b/source/java/org/alfresco/service/cmr/avm/AVMService.java
index 4dbc03269c..dcee00138c 100644
--- a/source/java/org/alfresco/service/cmr/avm/AVMService.java
+++ b/source/java/org/alfresco/service/cmr/avm/AVMService.java
@@ -1172,6 +1172,20 @@ public interface AVMService
*/
public void link(String parentPath, String name, AVMNodeDescriptor toLink);
+ /**
+ * Low-level internal function: replace a node
+ * in a parent directly. Caution: this is not something
+ * one ordinary applications should do, but it is used
+ * internally by the AVMSyncService.update() method.
+ * This function may disappear from the public interface.
+ *
+ * @param parentPath The path to the parent directory.
+ * @param name The name to give the node.
+ * @param toLink A descriptor for the node to insert.
+ * @throws AVMNotFoundException
+ */
+ public void updateLink(String parentPath, String name, AVMNodeDescriptor toLink);
+
/**
* Low-level internal function: Force a copy on write
* write event on the given node. This function is not usually
diff --git a/source/java/org/alfresco/service/cmr/model/FileFolderService.java b/source/java/org/alfresco/service/cmr/model/FileFolderService.java
index ce2e575f1d..c7936af746 100644
--- a/source/java/org/alfresco/service/cmr/model/FileFolderService.java
+++ b/source/java/org/alfresco/service/cmr/model/FileFolderService.java
@@ -228,8 +228,8 @@ public interface FileFolderService
/**
* Resolve a file or folder name path from a given root node down to the final node.
*
- * @param rootNodeRef the start of the path given, i.e. the '/' in '/A/B/C' for example
- * @param pathElements a list of names in the path
+ * @param rootNodeRef the start point node - a cm:folder type or subtype, e.g. the Company Home's nodeRef
+ * @param pathElements a list of names in the path. Do not include the referenced rootNodeRef's path element.
* @return Returns the info of the file or folder
* @throws FileNotFoundException if no file or folder exists along the path
*/
@@ -266,6 +266,14 @@ public interface FileFolderService
public ContentWriter getWriter(NodeRef nodeRef);
+ /**
+ * Check the validity of a node reference
+ *
+ * @return returns true if the NodeRef is valid
+ */
+ @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef"})
+ public boolean exists(NodeRef nodeRef);
+
/**
* Checks the type for whether it is a recognised file or folder type or is invalid for the FileFolderService.
*
diff --git a/source/java/org/alfresco/wcm/asset/AssetServiceImplTest.java b/source/java/org/alfresco/wcm/asset/AssetServiceImplTest.java
index cd19dbc472..04ae7d1f3b 100644
--- a/source/java/org/alfresco/wcm/asset/AssetServiceImplTest.java
+++ b/source/java/org/alfresco/wcm/asset/AssetServiceImplTest.java
@@ -34,6 +34,7 @@ import java.util.Map;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.service.cmr.avm.AVMNotFoundException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
@@ -58,6 +59,11 @@ public class AssetServiceImplTest extends AbstractWCMServiceImplTest
private SandboxService sbService;
private AssetService assetService;
+ // test data
+ private static final String PREFIX = "created-by-admin-";
+ private static final String FILE = "This is file1 - admin";
+
+
@Override
protected void setUp() throws Exception
{
@@ -377,6 +383,295 @@ public class AssetServiceImplTest extends AbstractWCMServiceImplTest
}
}
+ /**
+ * Test CRUD - create, retrieve (get, list), update and delete for each role
+ */
+ public void testCRUDforRoles() throws IOException, InterruptedException
+ {
+ // create web project (also creates staging sandbox and admin's author sandbox)
+ WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-crudroles", TEST_WEBPROJ_NAME+"-crudroles", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION, TEST_WEBPROJ_DEFAULT_WEBAPP, TEST_WEBPROJ_DONT_USE_AS_TEMPLATE, null);
+
+ String wpStoreId = wpInfo.getStoreId();
+ String defaultWebApp = wpInfo.getDefaultWebApp();
+
+ // get admin sandbox
+ SandboxInfo sbInfo = sbService.getAuthorSandbox(wpStoreId);
+ String sbStoreId = sbInfo.getSandboxId();
+
+ // create some existing folders and files
+ String[] users = new String[]{USER_ONE, USER_TWO, USER_THREE, USER_FOUR};
+ for (String user : users)
+ {
+ assetService.createFolderWebApp(sbStoreId, defaultWebApp, "/", PREFIX+user);
+
+ // create file (and add content)
+ ContentWriter writer = assetService.createFileWebApp(sbStoreId, defaultWebApp, "/"+PREFIX+user, "fileA");
+ writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
+ writer.setEncoding("UTF-8");
+ writer.putContent(FILE);
+ }
+
+ sbService.submitWebApp(sbStoreId, defaultWebApp, "some existing folders and files", null);
+
+ Thread.sleep(SUBMIT_DELAY);
+
+ runCRUDforRoles(USER_ONE, WCMUtil.ROLE_CONTENT_MANAGER, wpStoreId, defaultWebApp, true, true, true);
+
+ // TODO - pending ETHREEOH-1314 (see below) if updating folder properties
+ runCRUDforRoles(USER_TWO, WCMUtil.ROLE_CONTENT_PUBLISHER, wpStoreId, defaultWebApp, true, true, false);
+ runCRUDforRoles(USER_THREE, WCMUtil.ROLE_CONTENT_REVIEWER, wpStoreId, defaultWebApp, false, true, false);
+
+ runCRUDforRoles(USER_FOUR, WCMUtil.ROLE_CONTENT_CONTRIBUTOR, wpStoreId, defaultWebApp, true, false, false);
+ }
+
+ private void runCRUDforRoles(String user, String role, String wpStoreId, String defaultWebApp, boolean canCreate, boolean canUpdateExisting, boolean canDeleteExisting) throws IOException, InterruptedException
+ {
+ // switch to user - content manager
+ AuthenticationUtil.setFullyAuthenticatedUser(USER_ADMIN);
+
+ // invite web user and auto-create their (author) sandbox
+ wpService.inviteWebUser(wpStoreId, user, role, true);
+
+ // switch to user
+ AuthenticationUtil.setFullyAuthenticatedUser(user);
+
+ // get user's author sandbox
+ SandboxInfo sbInfo = sbService.getAuthorSandbox(wpStoreId);
+ String sbStoreId = sbInfo.getSandboxId();
+ String path = sbInfo.getSandboxRootPath() + "/" + defaultWebApp; // for checks only
+
+ if (canCreate)
+ {
+ // create folder
+ assetService.createFolderWebApp(sbStoreId, defaultWebApp, "/", user);
+
+ // create file (and add content)
+ final String MYFILE1 = "This is myFile1 - "+user;
+ ContentWriter writer = assetService.createFileWebApp(sbStoreId, defaultWebApp, "/"+user, "fileA");
+ writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
+ writer.setEncoding("UTF-8");
+ writer.putContent(MYFILE1);
+
+ // list assets
+ assertEquals(1, assetService.listAssetsWebApp(sbStoreId, defaultWebApp, "/"+user, false).size());
+
+ // get assets
+ AssetInfo myFolder1Asset = assetService.getAssetWebApp(sbStoreId, defaultWebApp, "/"+user);
+ checkAssetInfo(myFolder1Asset, user, path+"/"+user, user, false, true, false, false, null);
+
+ AssetInfo myFile1Asset = assetService.getAssetWebApp(sbStoreId, defaultWebApp, "/"+user+"/fileA");
+ checkAssetInfo(myFile1Asset, "fileA", path+"/"+user+"/fileA", user, true, false, false, true, user);
+
+ // get content
+
+ ContentReader reader = assetService.getContentReader(myFile1Asset);
+ InputStream in = reader.getContentInputStream();
+ byte[] buff = new byte[1024];
+ in.read(buff);
+ in.close();
+ assertEquals(MYFILE1, new String(buff, 0, MYFILE1.length())); // assumes 1byte=1char
+
+ // update content
+
+ final String MYFILE1_MODIFIED = "This is myFile1 ... modified";
+ writer = assetService.getContentWriter(myFile1Asset);
+ writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
+ writer.setEncoding("UTF-8");
+ writer.putContent(MYFILE1_MODIFIED);
+
+ // get updated content
+
+ reader = assetService.getContentReader(myFile1Asset);
+ in = reader.getContentInputStream();
+ buff = new byte[1024];
+ in.read(buff);
+ in.close();
+ assertEquals(MYFILE1_MODIFIED, new String(buff, 0, MYFILE1_MODIFIED.length())); // assumes 1byte=1char
+
+ // update folder properties - eg. title and description
+
+ Map newProps = new HashMap(2);
+ newProps.put(ContentModel.PROP_TITLE, "folder title");
+ newProps.put(ContentModel.PROP_DESCRIPTION, "folder description");
+
+ assetService.updateAssetProperties(myFolder1Asset, newProps);
+ Map props = assetService.getAssetProperties(myFolder1Asset);
+ assertEquals("folder title", props.get(ContentModel.PROP_TITLE));
+ assertEquals("folder description", props.get(ContentModel.PROP_DESCRIPTION));
+
+ // Delete created file and folder
+ assetService.deleteAsset(myFile1Asset);
+ assetService.deleteAsset(myFolder1Asset);
+ }
+ else
+ {
+ try
+ {
+ // try to create folder (-ve test)
+ assetService.createFolderWebApp(sbStoreId, defaultWebApp, "/", user);
+ fail("User "+user+" with role "+role+" should not be able to create folder");
+ }
+ catch (AccessDeniedException ade)
+ {
+ // expected
+ }
+
+ try
+ {
+ // try to create file (-ve test)
+ assetService.createFileWebApp(sbStoreId, defaultWebApp, "/", "file-"+user);
+ fail("User "+user+" with role "+role+" should not be able to create file");
+ }
+ catch (AccessDeniedException ade)
+ {
+ // expected
+ }
+ }
+
+ // list existing assets
+ assertEquals(1, assetService.listAssetsWebApp(sbStoreId, defaultWebApp, "/"+PREFIX+user, false).size());
+
+ // get existing assets
+ AssetInfo existingFolder1Asset = assetService.getAssetWebApp(sbStoreId, defaultWebApp, "/"+PREFIX+user);
+ checkAssetInfo(existingFolder1Asset, PREFIX+user, path+"/"+PREFIX+user, USER_ADMIN, false, true, false, false, null);
+
+ AssetInfo existingFile1Asset = assetService.getAssetWebApp(sbStoreId, defaultWebApp, "/"+PREFIX+user+"/fileA");
+ checkAssetInfo(existingFile1Asset, "fileA", path+"/"+PREFIX+user+"/fileA", USER_ADMIN, true, false, false, false, null);
+
+ // get existing content
+
+ ContentReader reader = assetService.getContentReader(existingFile1Asset);
+ InputStream in = reader.getContentInputStream();
+ byte[] buff = new byte[1024];
+ in.read(buff);
+ in.close();
+ assertEquals(FILE, new String(buff, 0, FILE.length())); // assumes 1byte=1char
+
+ if (canUpdateExisting)
+ {
+ // update content
+
+ final String MYFILE1_MODIFIED = "This is myFile1 ... modified";
+ ContentWriter writer = assetService.getContentWriter(existingFile1Asset);
+ writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
+ writer.setEncoding("UTF-8");
+ writer.putContent(MYFILE1_MODIFIED);
+
+ // get updated content
+
+ reader = assetService.getContentReader(existingFile1Asset);
+ in = reader.getContentInputStream();
+ buff = new byte[1024];
+ in.read(buff);
+ in.close();
+ assertEquals(MYFILE1_MODIFIED, new String(buff, 0, MYFILE1_MODIFIED.length())); // assumes 1byte=1char
+
+ // update file properties - eg. title and description
+
+ Map newProps = new HashMap(2);
+ newProps.put(ContentModel.PROP_TITLE, "file title");
+ newProps.put(ContentModel.PROP_DESCRIPTION, "file description");
+
+ assetService.updateAssetProperties(existingFile1Asset, newProps);
+ Map props = assetService.getAssetProperties(existingFile1Asset);
+ assertEquals("file title", props.get(ContentModel.PROP_TITLE));
+ assertEquals("file description", props.get(ContentModel.PROP_DESCRIPTION));
+
+ /* TODO - pending ETHREEOH-1314 - fails for content contributor / content publisher during submit if updating folder properties
+
+ // update folder properties - eg. title and description
+
+ newProps = new HashMap(2);
+ newProps.put(ContentModel.PROP_TITLE, "folder title");
+ newProps.put(ContentModel.PROP_DESCRIPTION, "folder description");
+
+ assetService.updateAssetProperties(existingFolder1Asset, newProps);
+ props = assetService.getAssetProperties(existingFolder1Asset);
+ assertEquals("folder title", props.get(ContentModel.PROP_TITLE));
+ assertEquals("folder description", props.get(ContentModel.PROP_DESCRIPTION));
+ */
+ }
+ else
+ {
+ try
+ {
+ // try to update file (-ve test)
+ assetService.getContentWriter(existingFile1Asset);
+ fail("User "+user+" with role "+role+" should not be able to update existing file");
+ }
+ catch (AccessDeniedException ade)
+ {
+ // expected
+ }
+
+ try
+ {
+ // try to update file properties (-ve test)
+ Map newProps = new HashMap(2);
+ newProps.put(ContentModel.PROP_TITLE, "file title");
+ newProps.put(ContentModel.PROP_DESCRIPTION, "file description");
+
+ assetService.updateAssetProperties(existingFile1Asset, newProps);
+ fail("User "+user+" with role "+role+" should not be able to update existing file properties");
+ }
+ catch (AccessDeniedException ade)
+ {
+ // expected
+ }
+
+ try
+ {
+ // try to update folder properties (-ve test)
+ Map newProps = new HashMap(2);
+ newProps.put(ContentModel.PROP_TITLE, "folder title");
+ newProps.put(ContentModel.PROP_DESCRIPTION, "folder description");
+
+ assetService.updateAssetProperties(existingFolder1Asset, newProps);
+ fail("User "+user+" with role "+role+" should not be able to update existing folder properties");
+ }
+ catch (AccessDeniedException ade)
+ {
+ // expected
+ }
+ }
+
+ if (canDeleteExisting)
+ {
+ // Delete existing file and folder
+ assetService.deleteAsset(existingFile1Asset);
+ assetService.deleteAsset(existingFolder1Asset);
+ }
+ else
+ {
+ try
+ {
+ // try to delete file (-ve test)
+ assetService.deleteAsset(existingFile1Asset);
+ fail("User "+user+" with role "+role+" should not be able to delete existing file");
+ }
+ catch (AVMNotFoundException nfe)
+ {
+ // expected
+ }
+
+ try
+ {
+ // try to delete folder (-ve test)
+ assetService.deleteAsset(existingFolder1Asset);
+ fail("User "+user+" with role "+role+" should not be able to delete existing folder");
+ }
+ catch (AVMNotFoundException ade)
+ {
+ // expected
+ }
+ }
+
+ // submit the changes
+ sbService.submitWebApp(sbStoreId, defaultWebApp, "some updates by "+user, null);
+
+ Thread.sleep(SUBMIT_DELAY);
+ }
+
public void testRenameFile()
{
// create web project (also creates staging sandbox and admin's author sandbox)
diff --git a/source/java/org/alfresco/wcm/sandbox/SandboxServiceImplTest.java b/source/java/org/alfresco/wcm/sandbox/SandboxServiceImplTest.java
index 4a8c92dce5..8f30874d2d 100644
--- a/source/java/org/alfresco/wcm/sandbox/SandboxServiceImplTest.java
+++ b/source/java/org/alfresco/wcm/sandbox/SandboxServiceImplTest.java
@@ -1067,10 +1067,7 @@ public class SandboxServiceImplTest extends AbstractWCMServiceImplTest
// Invite web users
wpService.inviteWebUser(wpStoreId, USER_ONE, WCMUtil.ROLE_CONTENT_CONTRIBUTOR, true);
-
- // TODO - pending merge of ETWOTWO-1109 fix
- //wpService.inviteWebUser(wpStoreId, USER_TWO, WCMUtil.ROLE_CONTENT_PUBLISHER, true);
- wpService.inviteWebUser(wpStoreId, USER_TWO, WCMUtil.ROLE_CONTENT_MANAGER, true);
+ wpService.inviteWebUser(wpStoreId, USER_TWO, WCMUtil.ROLE_CONTENT_PUBLISHER, true);
// Switch to USER_ONE
AuthenticationUtil.setFullyAuthenticatedUser(USER_ONE);