sessionContext);
/**
* Evaluate the scenarios contained within the context against the current operation
diff --git a/source/java/org/alfresco/filesys/repo/rules/RuleEvaluatorImpl.java b/source/java/org/alfresco/filesys/repo/rules/RuleEvaluatorImpl.java
index 6da6688b23..a2e9a6c16c 100644
--- a/source/java/org/alfresco/filesys/repo/rules/RuleEvaluatorImpl.java
+++ b/source/java/org/alfresco/filesys/repo/rules/RuleEvaluatorImpl.java
@@ -33,17 +33,23 @@ import org.apache.commons.logging.LogFactory;
* The Rule Evaluator evaluates the operation and returns
* details of the commands to implement those operations.
*
- * It is configured with a list of scenarios.
+ * It is configured with a list of scenarios which act as factories for scenario instances.
*/
public class RuleEvaluatorImpl implements RuleEvaluator
{
private static Log logger = LogFactory.getLog(RuleEvaluatorImpl.class);
/**
- * The evaluator context
+ * The evaluator context, one for each folder
*/
private class EvaluatorContextImpl implements EvaluatorContext
{
+ MapsessionState;
+
+ EvaluatorContextImpl (MapsessionState)
+ {
+ this.sessionState = sessionState;
+ }
/**
* Current instances of scenarios
*/
@@ -53,6 +59,12 @@ public class RuleEvaluatorImpl implements RuleEvaluator
public List getScenarioInstances()
{
return currentScenarioInstances;
+ }
+
+ @Override
+ public Map getSessionState()
+ {
+ return sessionState;
}
}
@@ -87,7 +99,7 @@ public class RuleEvaluatorImpl implements RuleEvaluator
{
for(Scenario scenario : scenarios)
{
- ScenarioInstance instance = scenario.createInstance(context.getScenarioInstances(), operation);
+ ScenarioInstance instance = scenario.createInstance(context, operation);
if(instance != null)
{
context.getScenarioInstances().add(instance);
@@ -161,9 +173,11 @@ public class RuleEvaluatorImpl implements RuleEvaluator
}
@Override
- public EvaluatorContext createContext()
+ public EvaluatorContext createContext(MapsessionState)
{
- return new EvaluatorContextImpl();
+ EvaluatorContextImpl impl = new EvaluatorContextImpl(sessionState);
+
+ return impl;
}
}
diff --git a/source/java/org/alfresco/filesys/repo/rules/Scenario.java b/source/java/org/alfresco/filesys/repo/rules/Scenario.java
index beb4f83f6c..2fc43aba70 100644
--- a/source/java/org/alfresco/filesys/repo/rules/Scenario.java
+++ b/source/java/org/alfresco/filesys/repo/rules/Scenario.java
@@ -35,7 +35,7 @@ public interface Scenario
* @param operation the operation to be performed
* @return the scenario instance or null if a new instance is not required.
*/
- ScenarioInstance createInstance(final List currentInstances, Operation operation);
+ ScenarioInstance createInstance(EvaluatorContext ctx, Operation operation);
}
diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffle.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffle.java
index ad004c279d..21fd198a52 100644
--- a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffle.java
+++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffle.java
@@ -51,7 +51,7 @@ public class ScenarioCreateDeleteRenameShuffle implements Scenario
private Ranking ranking = Ranking.HIGH;
@Override
- public ScenarioInstance createInstance(final List currentInstances, Operation operation)
+ public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation)
{
/**
* This scenario is triggered by a create of a file matching
diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffleInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffleInstance.java
index 179f70336f..559a90e6f1 100644
--- a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffleInstance.java
+++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateDeleteRenameShuffleInstance.java
@@ -46,11 +46,11 @@ import org.apache.commons.logging.LogFactory;
*
*
* If this filter is active then this is what happens.
- * a) New file created. New file created (X).
- * b) Existing file deleted (Y to Z). File moved to temporary location.
+ * a) New file created. New file created.
+ * b) Existing file deleted. File moved to temporary location instead.
* c) Rename - Scenario fires
* - File moved back from temporary location
- * - Content updated/
+ * - Content updated.
* - temporary file deleted
*/
public class ScenarioCreateDeleteRenameShuffleInstance implements ScenarioInstance
diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffle.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffle.java
index 73e843de6d..d1ad567c77 100644
--- a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffle.java
+++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffle.java
@@ -52,7 +52,7 @@ public class ScenarioCreateShuffle implements Scenario
private Ranking ranking = Ranking.HIGH;
@Override
- public ScenarioInstance createInstance(final List currentInstances, Operation operation)
+ public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation)
{
/**
* This scenario is triggered by a create of a file matching
diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffle.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffle.java
index b7521d225b..b921665ce2 100644
--- a/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffle.java
+++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioDoubleRenameShuffle.java
@@ -51,7 +51,7 @@ public class ScenarioDoubleRenameShuffle implements Scenario
private Ranking ranking = Ranking.HIGH;
@Override
- public ScenarioInstance createInstance(final List currentInstances, Operation operation)
+ public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation)
{
/**
* This scenario is triggered by a rename of a file matching
diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioLockedDeleteShuffle.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioLockedDeleteShuffle.java
index d9568aa8e7..0838b713ba 100644
--- a/source/java/org/alfresco/filesys/repo/rules/ScenarioLockedDeleteShuffle.java
+++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioLockedDeleteShuffle.java
@@ -47,7 +47,7 @@ public class ScenarioLockedDeleteShuffle implements Scenario
private Ranking ranking = Ranking.HIGH;
@Override
- public ScenarioInstance createInstance(final List currentInstances, Operation operation)
+ public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation)
{
/**
* This scenario is triggered by a create of a file matching
diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java
index 1e43724699..ed92fcb4fb 100644
--- a/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java
+++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java
@@ -54,7 +54,7 @@ public class ScenarioOpenFile implements Scenario
private long timeout = 300000;
@Override
- public ScenarioInstance createInstance(final List currentInstances, Operation operation)
+ public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation)
{
/**
* This scenario is triggered by an open or create of a new file
@@ -71,7 +71,7 @@ public class ScenarioOpenFile implements Scenario
if(c.getName().matches(pattern))
{
- if(checkScenarioActive(c.getName(),currentInstances))
+ if(checkScenarioActive(c.getName(),ctx.getScenarioInstances()))
{
logger.debug("scenario already active for name" + c.getName());
return null;
@@ -102,7 +102,7 @@ public class ScenarioOpenFile implements Scenario
if(o.getName().matches(pattern))
{
- if(checkScenarioActive(o.getName(),currentInstances))
+ if(checkScenarioActive(o.getName(),ctx.getScenarioInstances()))
{
logger.debug("scenario already active for name" + o.getName());
return null;
diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameShuffle.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameShuffle.java
index 7ffa139071..1828795c38 100644
--- a/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameShuffle.java
+++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameShuffle.java
@@ -50,7 +50,7 @@ public class ScenarioRenameShuffle implements Scenario
private long timeout = 30000;
@Override
- public ScenarioInstance createInstance(final List currentInstances, Operation operation)
+ public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation)
{
/**
* This scenario is triggered by a rename of a file matching
diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioSimpleNonBuffered.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioSimpleNonBuffered.java
index 697b930fad..7e0817648d 100644
--- a/source/java/org/alfresco/filesys/repo/rules/ScenarioSimpleNonBuffered.java
+++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioSimpleNonBuffered.java
@@ -34,7 +34,7 @@ public class ScenarioSimpleNonBuffered implements Scenario
private Ranking ranking = Ranking.LOW;
@Override
- public ScenarioInstance createInstance(final List currentInstances, Operation operation)
+ public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation)
{
/**
* The bog standard scenario is always interested.
diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioTempDeleteShuffle.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioTempDeleteShuffle.java
new file mode 100644
index 0000000000..c86bb560dc
--- /dev/null
+++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioTempDeleteShuffle.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2005-2011 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.filesys.repo.rules;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking;
+import org.alfresco.filesys.repo.rules.operations.CreateFileOperation;
+import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation;
+import org.alfresco.jlan.server.filesys.FileName;
+import org.alfresco.util.MaxSizeMap;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * A temp delete shuffle.
+ *
+ * Files are created in a temporary directory
+ * and then a delete and move.
+ */
+public class ScenarioTempDeleteShuffle implements Scenario
+{
+ private static Log logger = LogFactory.getLog(ScenarioTempDeleteShuffle.class);
+
+ protected final static String SCENARIO_KEY = "org.alfresco.filesys.repo.rules.ScenarioTempDeleteShuffle";
+
+ /**
+ * The regex pattern of a create that will identify a temporary directory.
+ */
+ private Pattern tempDirPattern;
+ private String strTempDirPattern;
+
+ /**
+ * The regex pattern of a create that will trigger a new instance of
+ * the scenario.
+ */
+ private Pattern pattern;
+ private String strPattern;
+
+
+ private long timeout = 30000;
+
+ private Ranking ranking = Ranking.HIGH;
+
+ @Override
+ public ScenarioInstance createInstance(final EvaluatorContext ctx, Operation operation)
+ {
+ /**
+ * This scenario is triggered by a delete of a file matching
+ * the pattern
+ */
+ if(operation instanceof CreateFileOperation)
+ {
+ CreateFileOperation c = (CreateFileOperation)operation;
+
+ // check whether file is below .TemporaryItems
+ String path = c.getPath();
+
+ // if path contains .TemporaryItems
+ Matcher d = tempDirPattern.matcher(path);
+ if(d.matches())
+ {
+ logger.debug("pattern matches temp dir folder so this is a new create in a temp dir");
+ Matcher m = pattern.matcher(c.getName());
+ if(m.matches())
+ {
+ // and how to lock - since we are already have one lock on the scenarios/folder here
+ // this is a potential deadlock and synchronization bottleneck
+ Map createdTempFiles = (Map)ctx.getSessionState().get(SCENARIO_KEY);
+
+ if(createdTempFiles == null)
+ {
+ synchronized(ctx.getSessionState())
+ {
+ logger.debug("created new temp file map and added it to the session state");
+ createdTempFiles = (Map)ctx.getSessionState().get(SCENARIO_KEY);
+ if(createdTempFiles == null)
+ {
+ createdTempFiles = Collections.synchronizedMap(new MaxSizeMap(5, false));
+ ctx.getSessionState().put(SCENARIO_KEY, createdTempFiles);
+ }
+ }
+ }
+ createdTempFiles.put(c.getName(), c.getName());
+
+ // TODO - Return a different scenario instance here ???
+ // So it can time out and have anti-patterns etc?
+ }
+ }
+ }
+
+ if(operation instanceof DeleteFileOperation)
+ {
+ DeleteFileOperation c = (DeleteFileOperation)operation;
+
+ Matcher m = pattern.matcher(c.getName());
+ if(m.matches())
+ {
+ Map createdTempFiles = (Map)ctx.getSessionState().get(SCENARIO_KEY);
+
+ if(createdTempFiles != null)
+ {
+ if(createdTempFiles.containsKey(c.getName()))
+ {
+ if(logger.isDebugEnabled())
+ {
+ logger.debug("New Scenario Temp Delete Shuffle Instance:" + c.getName());
+ }
+
+ ScenarioTempDeleteShuffleInstance instance = new ScenarioTempDeleteShuffleInstance() ;
+ instance.setTimeout(timeout);
+ instance.setRanking(ranking);
+ return instance;
+ }
+ }
+ }
+ }
+
+ // No not interested.
+ return null;
+
+ }
+
+ public void setPattern(String pattern)
+ {
+ this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
+ this.strPattern = pattern;
+ }
+
+ public String getPattern()
+ {
+ return this.strPattern;
+ }
+
+ public void setTempDirPattern(String tempDirPattern)
+ {
+ this.tempDirPattern = Pattern.compile(tempDirPattern, Pattern.CASE_INSENSITIVE);
+ this.strTempDirPattern = tempDirPattern;
+ }
+
+ public String getTempDirPattern()
+ {
+ return this.strTempDirPattern;
+ }
+
+ public void setTimeout(long timeout)
+ {
+ this.timeout = timeout;
+ }
+
+ public long getTimeout()
+ {
+ return timeout;
+ }
+
+ public void setRanking(Ranking ranking)
+ {
+ this.ranking = ranking;
+ }
+
+ public Ranking getRanking()
+ {
+ return ranking;
+ }
+}
diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioTempDeleteShuffleInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioTempDeleteShuffleInstance.java
new file mode 100644
index 0000000000..fab004d210
--- /dev/null
+++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioTempDeleteShuffleInstance.java
@@ -0,0 +1,241 @@
+/*
+ * 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.filesys.repo.rules;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.alfresco.filesys.repo.rules.commands.CompoundCommand;
+import org.alfresco.filesys.repo.rules.commands.CopyContentCommand;
+import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand;
+import org.alfresco.filesys.repo.rules.commands.RenameFileCommand;
+import org.alfresco.filesys.repo.rules.operations.CreateFileOperation;
+import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation;
+import org.alfresco.filesys.repo.rules.operations.MoveFileOperation;
+import org.alfresco.filesys.repo.rules.operations.RenameFileOperation;
+import org.alfresco.jlan.server.filesys.FileName;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * This is an instance of a "temp delete shuffle" triggered by a delete of a file matching
+ * a newly created file in a temporary directory.
+ *
+ * First implemented for TextEdit from MacOS Lion
+ *
+ *
+ * Sequence of operations.
+ * a) Temporary Directory Created
+ * b) Temporary file created in temporary directory.
+ * c) Target file deleted
+ * d) Temp file moved in place of target file.
+ * e) Temporary directory deleted.
+ *
+ * If this filter is active then this is what happens.
+ * a) Temp file created - in another folder.
+ * b) Existing file deleted. Scenario kicks in to rename rather than delete.
+ * c) New file moved into place (X to Y). Scenario kicks in
+ * 1) renames file from step c
+ * 2) copies content from temp file to target file
+ * 3) deletes temp file.
+ * d) Clean up scenario.
+ */
+public class ScenarioTempDeleteShuffleInstance implements ScenarioInstance
+{
+ private static Log logger = LogFactory.getLog(ScenarioTempDeleteShuffleInstance.class);
+
+ enum InternalState
+ {
+ NONE,
+ DELETE_SUBSTITUTED, // Scenario has intervened and renamed rather than delete
+ MOVED
+ }
+
+ InternalState internalState = InternalState.NONE;
+
+ private Date startTime = new Date();
+
+ private String lockName;
+
+ private Ranking ranking;
+
+ /**
+ * Timeout in ms. Default 30 seconds.
+ */
+ private long timeout = 60000;
+
+ private boolean isComplete;
+
+ /**
+ * Keep track of deletes that we substitute with a rename
+ * could be more than one if scenarios overlap
+ *
+ * From, TempFileName
+ */
+ private Map deletes = new HashMap();
+
+ /**
+ * Evaluate the next operation
+ * @param operation
+ */
+ public Command evaluate(Operation operation)
+ {
+
+ /**
+ * Anti-pattern : timeout
+ */
+ Date now = new Date();
+ if(now.getTime() > startTime.getTime() + getTimeout())
+ {
+ if(logger.isDebugEnabled())
+ {
+ logger.debug("Instance timed out lockName:" + lockName);
+ isComplete = true;
+ return null;
+ }
+ }
+
+ switch (internalState)
+ {
+
+ case NONE:
+
+ /**
+ * Looking for target file being deleted
+ *
+ * Need to intervene and replace delete with a rename to temp file.
+ */
+ if(operation instanceof DeleteFileOperation)
+ {
+ DeleteFileOperation d = (DeleteFileOperation)operation;
+
+
+ if(logger.isDebugEnabled())
+ {
+ logger.debug("entering DELETE_SUBSTITUTED state: " + lockName);
+ }
+
+ String tempName = ".shuffle" + d.getName();
+
+ deletes.put(d.getName(), tempName);
+
+ String[] paths = FileName.splitPath(d.getPath());
+ String currentFolder = paths[0];
+
+ RenameFileCommand r1 = new RenameFileCommand(d.getName(), tempName, d.getRootNodeRef(), d.getPath(), currentFolder + "\\" + tempName);
+
+ internalState = InternalState.DELETE_SUBSTITUTED;
+
+ return r1;
+
+ }
+ else
+ {
+ // anything else bomb out
+ if(logger.isDebugEnabled())
+ {
+ logger.debug("State error, expected a DELETE");
+ }
+ isComplete = true;
+ }
+ break;
+
+ case DELETE_SUBSTITUTED:
+
+ /**
+ * Looking for a move operation of the deleted file
+ */
+ if(operation instanceof MoveFileOperation)
+ {
+ MoveFileOperation m = (MoveFileOperation)operation;
+
+ String targetFile = m.getTo();
+
+ if(deletes.containsKey(targetFile))
+ {
+ String tempName = deletes.get(targetFile);
+
+ String[] paths = FileName.splitPath(m.getToPath());
+ String currentFolder = paths[0];
+
+ /**
+ * This is where the scenario fires.
+ * a) Rename the temp file back to the targetFile
+ * b) Copy content from moved file
+ * c) Delete rather than move file
+ */
+ logger.debug("scenario fires");
+ ArrayList commands = new ArrayList();
+
+ RenameFileCommand r1 = new RenameFileCommand(tempName, targetFile, m.getRootNodeRef(), currentFolder + "\\" + tempName, m.getToPath());
+
+ CopyContentCommand copyContent = new CopyContentCommand(m.getFrom(), targetFile, m.getRootNodeRef(), m.getFromPath(), m.getToPath());
+
+ DeleteFileCommand d1 = new DeleteFileCommand(m.getFrom(), m.getRootNodeRef(), m.getFromPath());
+
+ commands.add(r1);
+ commands.add(copyContent);
+ commands.add(d1);
+
+ logger.debug("Scenario complete");
+ isComplete = true;
+
+ return new CompoundCommand(commands);
+ }
+ }
+
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean isComplete()
+ {
+ return isComplete;
+ }
+
+ @Override
+ public Ranking getRanking()
+ {
+ return ranking;
+ }
+
+ public void setRanking(Ranking ranking)
+ {
+ this.ranking = ranking;
+ }
+
+ public String toString()
+ {
+ return "ScenarioTempDeleteShuffleInstance:" + lockName;
+ }
+
+ public void setTimeout(long timeout)
+ {
+ this.timeout = timeout;
+ }
+
+ public long getTimeout()
+ {
+ return timeout;
+ }
+}
diff --git a/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizer.java b/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizer.java
index c5cff08223..12875d301e 100644
--- a/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizer.java
+++ b/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizer.java
@@ -22,11 +22,12 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeSet;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.batch.BatchProcessWorkProvider;
@@ -100,6 +101,7 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean;
public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
{
private static final Log logger = LogFactory.getLog(HomeFolderProviderSynchronizer.class);
+ private static final Log batchLogger = LogFactory.getLog(HomeFolderProviderSynchronizer.class+".batch");
private static final String GUEST_HOME_FOLDER_PROVIDER = "guestHomeFolderProvider";
private static final String BOOTSTRAP_HOME_FOLDER_PROVIDER = "bootstrapHomeFolderProvider";
@@ -225,6 +227,8 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
*
* Alternative approaches are possible, but the above has the advantage that
* nodes are not moved if they are already in their preferred location.
+ *
+ * Also needed to change the case of parent folders.
*/
// Using authorities rather than Person objects as they are much lighter
@@ -279,9 +283,9 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
for (RunAsWorker worker: workers)
{
String name = worker.getName();
- if (logger.isDebugEnabled())
+ if (logger.isInfoEnabled())
{
- logger.debug(" -- "+
+ logger.info(" -- "+
(TenantService.DEFAULT_DOMAIN.equals(tenantDomain)? "" : tenantDomain+" ")+
name+" --");
}
@@ -296,11 +300,11 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
new WorkProvider(authorities),
threadCount, peoplePerTransaction,
null,
- logger, 100);
+ batchLogger, 100);
processor.process(worker, true);
if (processor.getTotalErrors() > 0)
{
- logger.debug(" -- Give up after error --");
+ logger.info(" -- Give up after error --");
break;
}
}
@@ -319,7 +323,11 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
{
public Set execute() throws Exception
{
- return authorityService.getAllAuthorities(AuthorityType.USER);
+ // Returns a sorted set (using natural ordering) rather than a hashCode
+ // so that it is more obvious what the order is for processing users.
+ Set result = new TreeSet();
+ result.addAll(authorityService.getAllAuthorities(AuthorityType.USER));
+ return result;
}
};
return txnHelper.doInTransaction(restoreCallback, false, true);
@@ -414,17 +422,24 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
private String createTmpFolderName(NodeRef root)
{
// Try a few times but then give up.
- for (int i = 1; i <= 100; i++)
+ String temporary = "Temporary-";
+ int from = 1;
+ int to = 100;
+ for (int i = from; i <= to; i++)
{
- String tmpFolderName = "Temporary"+i;
+ String tmpFolderName = temporary+i;
if (fileFolderService.searchSimple(root, tmpFolderName) == null)
{
fileFolderService.create(root, tmpFolderName, ContentModel.TYPE_FOLDER);
return tmpFolderName;
}
}
- throw new PersonException("Unable to create a temporty " +
- "folder into which home folders could be moved.");
+ String msg = "Unable to create a temporary " +
+ "folder into which home folders will be moved. " +
+ "Tried creating " + temporary + from + " .. " + temporary + to +
+ ". Remove these folders and try again.";
+ logger.error(" # "+msg);
+ throw new PersonException(msg);
}
}.doWork();
}
@@ -480,18 +495,18 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
@Override
protected void handleInPreferredLocation()
{
- if (logger.isDebugEnabled())
+ if (logger.isInfoEnabled())
{
- logger.debug(" "+toPath(actualPath)+" is already in preferred location.");
+ logger.info(" # "+toPath(actualPath)+" is already in preferred location.");
}
}
@Override
protected void handleSharedHomeProvider()
{
- if (logger.isDebugEnabled())
+ if (logger.isInfoEnabled())
{
- logger.debug(" "+userName+" "+providerName+" creates shared home folders - These are not moved.");
+ logger.info(" # "+userName+" "+providerName+" creates shared home folders - These are not moved.");
}
}
@@ -499,36 +514,36 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
@Override
protected void handleOriginalSharedHomeProvider()
{
- if (logger.isDebugEnabled())
+ if (logger.isInfoEnabled())
{
- logger.debug(" "+userName+" Original "+originalProviderName+" creates shared home folders - These are not moved.");
+ logger.info(" # "+userName+" Original "+originalProviderName+" creates shared home folders - These are not moved.");
}
}
@Override
protected void handleNotAHomeFolderProvider2()
{
- if (logger.isDebugEnabled())
+ if (logger.isInfoEnabled())
{
- logger.debug(" "+userName+" "+providerName+" for is not a HomeFolderProvider2.");
+ logger.info(" # "+userName+" "+providerName+" for is not a HomeFolderProvider2.");
}
}
@Override
protected void handleSpecialHomeFolderProvider()
{
- if (logger.isDebugEnabled())
+ if (logger.isInfoEnabled())
{
- logger.debug(" "+userName+" Original "+originalProviderName+" is an internal type - These are not moved.");
+ logger.info(" # "+userName+" Original "+originalProviderName+" is an internal type - These are not moved.");
}
}
@Override
protected void handleHomeFolderNotSet()
{
- if (logger.isDebugEnabled())
+ if (logger.isInfoEnabled())
{
- logger.debug(" "+userName+" Home folder is not set - ignored");
+ logger.info(" # "+userName+" Home folder is not set - ignored");
}
}
}.doWork();
@@ -538,6 +553,11 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
* @return a String for debug a folder list.
*/
private String toPath(List folders)
+ {
+ return toPath(folders, (folders == null) ? 0 : folders.size()-1);
+ }
+
+ private String toPath(List folders, int depth)
{
StringBuilder sb = new StringBuilder("");
if (folders != null)
@@ -549,8 +569,16 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
sb.append('/');
}
sb.append(folder);
+ if (depth-- <= 0)
+ {
+ break;
+ }
}
}
+ else
+ {
+ sb.append('.');
+ }
return sb.toString();
}
@@ -634,20 +662,23 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
// parent folders should have been created.
NodeRef newParent = createNewParentIfRequired(root, preferredPath);
+ // If the preferred home folder already exists, append "-N"
+ homeFolderManager.modifyHomeFolderNameIfItExists(root, preferredPath);
String homeFolderName = preferredPath.get(preferredPath.size() - 1);
- // Throw our own FileExistsException before we get one that
- // marks the transaction for rollback, as there is no point
- // trying again.
- if (nodeService.getChildByName(newParent, ContentModel.ASSOC_CONTAINS,
- homeFolderName) != null)
- {
- throw new FileExistsException(newParent, homeFolderName);
- }
-
// Get the old parent before we move anything.
NodeRef oldParent = nodeService.getPrimaryParent(homeFolder) .getParentRef();
+ // Log action
+ if (logger.isInfoEnabled())
+ {
+ logger.info(" mv "+toPath(actualPath)+
+ " "+ toPath(preferredPath)+
+ ((providerName != null && !providerName.equals(originalProviderName))
+ ? " # AND reset provider to "+providerName
+ : "") + ".");
+ }
+
// Perform the move
homeFolder = fileFolderService.move(homeFolder, newParent,
homeFolderName).getNodeRef();
@@ -662,16 +693,6 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
ContentModel.PROP_HOME_FOLDER_PROVIDER, providerName);
}
- // Log action
- if (logger.isDebugEnabled())
- {
- logger.debug(" mv "+toPath(actualPath)+
- " "+ toPath(preferredPath)+
- ((providerName != null && !providerName.equals(originalProviderName))
- ? " AND reset provider to "+providerName
- : "") + ".");
- }
-
// Tidy up
removeEmptyParentFolders(oldParent, oldRoot);
}
@@ -679,7 +700,7 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
{
String message = "mv "+toPath(actualPath)+" "+toPath(preferredPath)+
" failed as the target already existed.";
- logger.error(" "+message);
+ logger.error(" # "+message);
throw new PersonException(message);
}
catch (FileNotFoundException e)
@@ -691,7 +712,7 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
throw new PersonException(message);
}
}
-
+
private NodeRef createNewParentIfRequired(NodeRef root, List homeFolderPath)
{
NodeRef parent = root;
@@ -701,8 +722,13 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
String pathElement = homeFolderPath.get(i);
NodeRef nodeRef = nodeService.getChildByName(parent,
ContentModel.ASSOC_CONTAINS, pathElement);
+ String path = toPath(homeFolderPath, i);
if (nodeRef == null)
{
+ if (logger.isInfoEnabled())
+ {
+ logger.info(" mkdir "+path);
+ }
parent = fileFolderService.create(parent, pathElement,
ContentModel.TYPE_FOLDER).getNodeRef();
}
@@ -714,7 +740,13 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
// there is no point trying again.
if (!fileFolderService.getFileInfo(nodeRef).isFolder())
{
- throw new FileExistsException(parent, null);
+ if (logger.isErrorEnabled())
+ {
+ logger.error(" # cannot create folder " + path +
+ " as content with the same name exists. " +
+ "Move the content and try again.");
+ }
+ throw new FileExistsException(parent, path);
}
parent = nodeRef;
@@ -757,9 +789,9 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
{
return;
}
- if (logger.isDebugEnabled())
+ if (logger.isInfoEnabled())
{
- logger.debug(" rm "+toPath(root, nodeRef));
+ logger.info(" rm "+toPath(root, nodeRef));
}
nodeService.deleteNode(nodeRef);
}
@@ -983,84 +1015,226 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean
}
}
- // Gathers and checks parent folder paths.
+ // Records the parents of the preferred folder paths (the leaf folder are not recorded)
+ // and checks actual paths against these.
private class ParentFolderStructure
{
- // Sets of parent folders within each root node
- private Map>> folders = new HashMap>>();
+ // Parent folders within each root node
+ private Map folders = new HashMap();
public void recordParentFolder(NodeRef root, List path)
{
- Set> rootsFolders = getFolders(root);
+ RootFolder rootsFolders = getFolders(root);
synchronized(rootsFolders)
{
- // If parent is the root, all home folders clash
- int parentSize = path.size() - 1;
- if (parentSize == 0)
- {
- // We could optimise the code a little by clearing
- // all other entries and putting a contains(null)
- // check just inside the synchronized(rootsFolders)
- // but it might be useful to have a complete lit of
- // folders.
- rootsFolders.add(null);
-
- if (logger.isDebugEnabled())
- {
- logger.debug(" Recorded root as parent");
- }
- }
- else
- {
- while (parentSize-- > 0)
- {
- List parentPath = new ArrayList();
- for (int j = 0; j <= parentSize; j++)
- {
- parentPath.add(path.get(j));
- }
-
- if (logger.isDebugEnabled()
- && !rootsFolders.contains(parentPath))
- {
- logger.debug(" Recorded parent: "
- + toPath(parentPath));
- }
-
- rootsFolders.add(parentPath);
- }
- }
+ rootsFolders.add(path);
}
}
/**
- * @return {@code true} if the {@code path} is a parent folder
- * or the parent folders includes the root itself. In
- * the latter case all existing folders might clash
- * so must be moved out of the way.
+ * Checks to see if there is a clash between the preferred paths and the
+ * existing folder structure. If there is a clash, the existing home folder
+ * (the leaf folder) is moved to a temporary structure. This allows any
+ * parent folders to be tidied up (if empty), so that the new preferred
+ * structure can be recreated.
+ *
+ * 1. There is no clash if the path is null or empty.
+ *
+ * 2. There is a clash if there is a parent structure included the root
+ * folder itself.
+ *
+ * 3. There is a clash if the existing path exists in the parent structure.
+ * This comparison ignores case as Alfresco does not allow duplicates
+ * regardless of case.
+ *
+ * 4. There is a clash if any of the folders in the existing path don't
+ * match the case of the parent folders.
+ *
+ * 5. There is a clash there are different case versions of the parent
+ * folders themselves or other existing folders.
+ *
+ * When 4 takes place, we will end up with the first one we try to recreate
+ * being used for all.
*/
public boolean clash(NodeRef root, List path)
{
- Set> rootsFolders = getFolders(root);
+ if (path == null || path.isEmpty())
+ {
+ return false;
+ }
+
+ RootFolder rootsFolders = getFolders(root);
synchronized(rootsFolders)
{
- return rootsFolders.contains(path) ||
- rootsFolders.contains(null);
+ return rootsFolders.clash(path);
}
}
- private Set> getFolders(NodeRef root)
+ private RootFolder getFolders(NodeRef root)
{
synchronized(folders)
{
- Set> rootsFolders = folders.get(root);
+ RootFolder rootsFolders = folders.get(root);
if (rootsFolders == null)
{
- rootsFolders = new HashSet>();
+ rootsFolders = new RootFolder();
folders.put(root, rootsFolders);
}
return rootsFolders;
}
}
+
+ // Records the parents of the preferred folder paths (the leaf folder are not recorded)
+ // and checks actual paths against these BUT only for a single root.
+ private class RootFolder extends Folder
+ {
+ private boolean includesRoot;
+
+ public RootFolder()
+ {
+ super(null);
+ }
+
+ // Adds a path (but not the leaf folder) if it does not already exist.
+ public void add(List path)
+ {
+ if (!includesRoot)
+ {
+ int parentSize = path.size() - 1;
+ if (parentSize == 0)
+ {
+ includesRoot = true;
+ children = null; // can discard children as all home folders now clash.
+ if (logger.isInfoEnabled())
+ {
+ logger.info(" # Recorded root as parent - no need to record other parents as all home folders will clash");
+ }
+ }
+ else
+ {
+ add(path, 0);
+ }
+ }
+ }
+
+ /**
+ * See description of {@link ParentFolderStructure#clash(NodeRef, List)}.
+ *
+ * Performs check 2 and then calls {@link Folder#clash(List, int)} to
+ * perform 3, 4 and 5.
+ */
+ public boolean clash(List path)
+ {
+ // Checks 2.
+ return includesRoot ? false : clash(path, 0);
+ }
+ }
+
+ private class Folder
+ {
+ // Case specific name of first folder added.
+ String name;
+
+ // Indicates if there is another preferred name that used different case.
+ boolean duplicateWithDifferentCase;
+
+ List children;
+
+ public Folder(String name)
+ {
+ this.name = name;
+ }
+
+ /**
+ * Adds a path (but not the leaf folder) if it does not already exist.
+ * @param path the full path to add
+ * @param depth the current depth into the path starting with 0.
+ */
+ protected void add(List path, int depth)
+ {
+ int parentSize = path.size() - 1;
+ String name = path.get(depth);
+ Folder child = getChild(name);
+ if (child == null)
+ {
+ child = new Folder(name);
+ if (children == null)
+ {
+ children = new LinkedList();
+ }
+ children.add(child);
+ if (logger.isInfoEnabled())
+ {
+ logger.info(" " + toPath(path, depth));
+ }
+ }
+ else if (!child.name.equals(name))
+ {
+ child.duplicateWithDifferentCase = true;
+ }
+
+ // Don't add the leaf folder
+ if (++depth < parentSize)
+ {
+ add(path, depth);
+ }
+ }
+
+ /**
+ * See description of {@link ParentFolderStructure#clash(NodeRef, List)}.
+ *
+ * Performs checks 3, 4 and 5 for a single level and then recursively checks
+ * lower levels.
+ */
+ protected boolean clash(List path, int depth)
+ {
+ String name = path.get(depth);
+ Folder child = getChild(name); // Uses equalsIgnoreCase
+ if (child == null)
+ {
+ // Negation of check 3.
+ return false;
+ }
+ else if (child.duplicateWithDifferentCase) // if there folders using different case!
+ {
+ // Check 5.
+ return true;
+ }
+ else if (!child.name.equals(name)) // if the case does not match
+ {
+ // Check 4.
+ child.duplicateWithDifferentCase = true;
+ return true;
+ }
+
+ // If a match (including case) has been made to the end of the path
+ if (++depth == path.size())
+ {
+ // Check 3.
+ return true;
+ }
+
+ // Check lower levels.
+ return clash(path, depth);
+ }
+
+ /**
+ * Returns the child folder with the specified name (ignores case).
+ */
+ private Folder getChild(String name)
+ {
+ if (children != null)
+ {
+ for (Folder child: children)
+ {
+ if (name.equalsIgnoreCase(child.name))
+ {
+ return child;
+ }
+ }
+ }
+ return null;
+ }
+ }
}
}
diff --git a/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizerTest.java b/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizerTest.java
index 498295271d..46539e60c0 100644
--- a/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizerTest.java
+++ b/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizerTest.java
@@ -28,8 +28,8 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
-import java.util.Properties;
import java.util.Set;
+import java.util.TreeSet;
import javax.transaction.Status;
import javax.transaction.UserTransaction;
@@ -51,7 +51,6 @@ import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.Path;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.security.AuthorityService;
-import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
@@ -83,6 +82,7 @@ public class HomeFolderProviderSynchronizerTest
private static AuthorityService authorityService;
private static TenantAdminService tenantAdminService;
private static TenantService tenantService;
+ private static UserNameMatcherImpl userNameMatcher;
private static PortableHomeFolderManager homeFolderManager;
private static RegexHomeFolderProvider largeHomeFolderProvider;
private static String largeHomeFolderProviderName;
@@ -109,6 +109,7 @@ public class HomeFolderProviderSynchronizerTest
authorityService = (AuthorityService) applicationContext.getBean("authorityService");
tenantAdminService = (TenantAdminService) applicationContext.getBean("tenantAdminService");
tenantService = (TenantService) applicationContext.getBean("tenantService");
+ userNameMatcher = (UserNameMatcherImpl) applicationContext.getBean("userNameMatcher");
homeFolderManager = (PortableHomeFolderManager) applicationContext.getBean("homeFolderManager");
largeHomeFolderProvider = (RegexHomeFolderProvider) applicationContext.getBean("largeHomeFolderProvider");
largeHomeFolderProviderName = largeHomeFolderProvider.getName();
@@ -207,6 +208,7 @@ public class HomeFolderProviderSynchronizerTest
trans.commit();
trans = null;
AuthenticationUtil.clearCurrentSecurityContext();
+ userNameMatcher.setUserNamesAreCaseSensitive(false); // Put back the default
}
private Set deleteNonAdminGuestUsers()
@@ -704,6 +706,8 @@ public class HomeFolderProviderSynchronizerTest
@Test
public void testPathAlreadyInUseByContent() throws Exception
{
+ System.out.println("testPathAlreadyInUseByContent: EXPECT TO SEE AN EXCEPTION IN THE LOG ======================== ");
+
createUser("", "fred");
createContent("", "fr");
@@ -731,7 +735,7 @@ public class HomeFolderProviderSynchronizerTest
assertHomeFolderLocation("peter", "pe/peter");
assertHomeFolderLocation("pe", "pe/pe");
- assertFalse("The Temporary1 folder should have been removed", exists("Temporary1"));
+ assertFalse("The Temporary-1 folder should have been removed", exists("Temporary-1"));
}
@Test
@@ -739,24 +743,26 @@ public class HomeFolderProviderSynchronizerTest
{
createUser("", "fr");
createUser("", "fred");
- createFolder("Temporary1");
- createFolder("Temporary2");
- createFolder("Temporary3");
+ createFolder("Temporary-1");
+ createFolder("Temporary-2");
+ createFolder("Temporary-3");
// Don't delete the temporary folder
homeFolderProviderSynchronizer.setKeepEmptyParents("true");
moveUserHomeFolders();
- assertTrue("The existing Temporary1 folder should still exist", exists("Temporary1"));
- assertTrue("The existing Temporary2 folder should still exist", exists("Temporary2"));
- assertTrue("The existing Temporary3 folder should still exist", exists("Temporary3"));
- assertTrue("The existing Temporary4 folder should still exist", exists("Temporary4"));
+ assertTrue("The existing Temporary-1 folder should still exist", exists("Temporary-1"));
+ assertTrue("The existing Temporary-2 folder should still exist", exists("Temporary-2"));
+ assertTrue("The existing Temporary-3 folder should still exist", exists("Temporary-3"));
+ assertTrue("The existing Temporary-4 folder should still exist", exists("Temporary-4"));
}
@Test
public void testException() throws Exception
{
+ System.out.println("testException: EXPECT TO SEE AN EXCEPTION IN THE LOG ======================== ");
+
// Force the need for a temporary folder
createUser("", "fr");
createUser("", "fred");
@@ -764,7 +770,7 @@ public class HomeFolderProviderSynchronizerTest
// Use up all possible temporary folder names
for (int i=1; i<=100; i++)
{
- createFolder("Temporary"+i);
+ createFolder("Temporary-"+i);
}
moveUserHomeFolders();
@@ -939,4 +945,38 @@ public class HomeFolderProviderSynchronizerTest
assertHomeFolderLocation(tenant2, "fred", "fr/"+tenantService.getDomainUser("fred", tenant2));
}
}
+
+ // ALF-11535
+ @Test
+ public void testChangeParentFolderCase() throws Exception
+ {
+ // By default, user names are case sensitive
+ createUser("fr", "FRED");
+ moveUserHomeFolders();
+ assertHomeFolderLocation("FRED", "FR/FRED");
+ assertHomeFolderLocation("fred", "FR/FRED"); // Same user
+ }
+
+ // ALF-11535
+ @Test
+ public void testCaseSensitiveUsers() throws Exception
+ {
+ userNameMatcher.setUserNamesAreCaseSensitive(true);
+
+ // Users are processed in a sorted order (natural ordering).
+ // The preferred parent folder structure of the first user
+ // is used where there is a clash between users.
+
+ // The following users are in their natural order.
+ createUser("Ab", "Abby");
+ createUser("TE", "TESS");
+ createUser("TE", "Tess");
+ createUser("Ab", "aBBY");
+
+ moveUserHomeFolders();
+ assertHomeFolderLocation("Abby", "Ab/Abby");
+ assertHomeFolderLocation("TESS", "TE/TESS");
+ assertHomeFolderLocation("Tess", "TE/Tess-1");
+ assertHomeFolderLocation("aBBY", "Ab/aBBY-1");
+ }
}
diff --git a/source/java/org/alfresco/repo/security/person/PersonTest.java b/source/java/org/alfresco/repo/security/person/PersonTest.java
index 4958b51210..df5d12c8f9 100644
--- a/source/java/org/alfresco/repo/security/person/PersonTest.java
+++ b/source/java/org/alfresco/repo/security/person/PersonTest.java
@@ -72,6 +72,8 @@ public class PersonTest extends TestCase
private TransactionService transactionService;
private PersonService personService;
+ private UserNameMatcherImpl userNameMatcher;
+
private BehaviourFilter policyBehaviourFilter;
private NodeService nodeService;
private NodeRef rootNodeRef;
@@ -93,6 +95,7 @@ public class PersonTest extends TestCase
transactionService = (TransactionService) ctx.getBean("transactionService");
personService = (PersonService) ctx.getBean("personService");
+ userNameMatcher = (UserNameMatcherImpl) ctx.getBean("userNameMatcher");
nodeService = (NodeService) ctx.getBean("nodeService");
permissionService = (PermissionService) ctx.getBean("permissionService");
authorityService = (AuthorityService) ctx.getBean("authorityService");
@@ -124,6 +127,7 @@ public class PersonTest extends TestCase
@Override
protected void tearDown() throws Exception
{
+ userNameMatcher.setUserNamesAreCaseSensitive(false); // Put back the default
if ((testTX.getStatus() == Status.STATUS_ACTIVE) || (testTX.getStatus() == Status.STATUS_MARKED_ROLLBACK))
{
@@ -1125,9 +1129,7 @@ public class PersonTest extends TestCase
final String TEST_PERSON_UPPER = TEST_PERSON_MIXED.toUpperCase();
final String TEST_PERSON_LOWER = TEST_PERSON_MIXED.toLowerCase();
- UserNameMatcherImpl usernameMatcher = new UserNameMatcherImpl();
- usernameMatcher.setUserNamesAreCaseSensitive(true);
- ((PersonServiceImpl)personService).setUserNameMatcher(usernameMatcher); // case-sensitive
+ userNameMatcher.setUserNamesAreCaseSensitive(true);
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
@@ -1217,9 +1219,6 @@ public class PersonTest extends TestCase
}
// ignore - expected
}
-
- usernameMatcher.setUserNamesAreCaseSensitive(false);
- ((PersonServiceImpl)personService).setUserNameMatcher(usernameMatcher); // case-insensitive
}
public void testUpdateUserNameCase()
@@ -1227,6 +1226,7 @@ public class PersonTest extends TestCase
final String TEST_PERSON_UPPER = "TEST_PERSON_THREE";
final String TEST_PERSON_LOWER = TEST_PERSON_UPPER.toLowerCase();
+ userNameMatcher.setUserNamesAreCaseSensitive(true);
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
diff --git a/source/java/org/alfresco/repo/security/person/PortableHomeFolderManager.java b/source/java/org/alfresco/repo/security/person/PortableHomeFolderManager.java
index 54d0e61e49..91cec9aa15 100644
--- a/source/java/org/alfresco/repo/security/person/PortableHomeFolderManager.java
+++ b/source/java/org/alfresco/repo/security/person/PortableHomeFolderManager.java
@@ -353,43 +353,51 @@ public class PortableHomeFolderManager implements HomeFolderManager
}
else
{
+ // If the preferred home folder already exists, append "-N"
+ NodeRef root = getRootPathNodeRef(provider);
List homeFolderPath = provider.getHomeFolderPath(person);
-
- FileInfo fileInfo;
+ modifyHomeFolderNameIfItExists(root, homeFolderPath);
- // Test if it already exists
- NodeRef existing = getExisting(provider, fileFolderService, homeFolderPath);
- if (existing != null)
- {
- fileInfo = fileFolderService.getFileInfo(existing);
- }
- else
- {
- fileInfo = createTree(provider, getRootPathNodeRef(provider), homeFolderPath,
- provider.getTemplateNodeRef(), fileFolderService);
- }
+ // Create folder
+ FileInfo fileInfo = createTree(provider, getRootPathNodeRef(provider), homeFolderPath,
+ provider.getTemplateNodeRef(), fileFolderService);
NodeRef homeFolderNodeRef = fileInfo.getNodeRef();
return new HomeSpaceNodeRef(homeFolderNodeRef, HomeSpaceNodeRef.Status.CREATED);
}
return homeSpaceNodeRef;
}
- private NodeRef getExisting(HomeFolderProvider2 provider, FileFolderService fileFolderService,
- List homeFolderPath)
+ /**
+ * Modifies (if required) the leaf folder name in the {@code homeFolderPath} by
+ * appending {@code "-N"} (where N is an integer starting with 1), so that a
+ * new folder will be created.
+ * @param root folder.
+ * @param homeFolderPath the full path. Only the final element is used.
+ */
+ public void modifyHomeFolderNameIfItExists(NodeRef root, List homeFolderPath)
{
- NodeRef existing;
+ int n = 0;
+ int last = homeFolderPath.size()-1;
+ String name = homeFolderPath.get(last);
+ String homeFolderName = name;
try
{
- FileInfo existingFileInfo = fileFolderService.resolveNamePath(getRootPathNodeRef(provider), homeFolderPath);
- existing = existingFileInfo.getNodeRef();
+ do
+ {
+ if (n > 0)
+ {
+ homeFolderName = name+'-'+n;
+ homeFolderPath.set(last, homeFolderName);
+ }
+ n++;
+ } while (fileFolderService.resolveNamePath(root, homeFolderPath, false) != null);
}
- catch (FileNotFoundException fnfe)
+ catch (FileNotFoundException e)
{
- existing = null;// home folder noderef doesn't exist yet
+ // Should not be thrown as call to resolveNamePath passes in false
}
- return existing;
}
-
+
/**
* creates a tree of folder nodes based on the path elements provided.
*/
diff --git a/source/java/org/alfresco/repo/site/SiteServiceImpl.java b/source/java/org/alfresco/repo/site/SiteServiceImpl.java
index e276bb12c5..f60725b401 100644
--- a/source/java/org/alfresco/repo/site/SiteServiceImpl.java
+++ b/source/java/org/alfresco/repo/site/SiteServiceImpl.java
@@ -80,6 +80,7 @@ import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.security.NoSuchPersonException;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService;
+import org.alfresco.service.cmr.security.PublicServiceAccessService;
import org.alfresco.service.cmr.security.AuthorityService.AuthorityFilter;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteService;
@@ -161,6 +162,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic
private BehaviourFilter behaviourFilter;
private SitesPermissionCleaner sitesPermissionsCleaner;
private PolicyComponent policyComponent;
+ private PublicServiceAccessService publicServiceAccessService;
private NamedObjectRegistry> cannedQueryRegistry;
@@ -327,6 +329,11 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic
this.sitesPermissionsCleaner = sitesPermissionsCleaner;
}
+ public void setPublicServiceAccessService(PublicServiceAccessService publicServiceAccessService)
+ {
+ this.publicServiceAccessService = publicServiceAccessService;
+ }
+
/**
* Set the registry of {@link CannedQueryFactory canned queries}
*/
@@ -385,13 +392,9 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic
*/
public boolean hasCreateSitePermissions()
{
- final NodeRef siteRoot = getSiteRoot();
- if (siteRoot == null)
- {
- throw new SiteServiceException("No root sites folder exists");
- }
- boolean result = permissionService.hasPermission(siteRoot, PermissionService.CONTRIBUTOR).equals(AccessStatus.ALLOWED);
- return result;
+ // NOTE: see ALF-13580 - since 3.4.6 PermissionService.CONTRIBUTOR is no longer used as the default on the Sites folder
+ // instead the ability to call createSite() and the Spring configured ACL is the mechanism used to protect access.
+ return (publicServiceAccessService.hasAccess("SiteService", "createSite", "", "", "", "", true) == AccessStatus.ALLOWED);
}
/**
diff --git a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java
index b65826a292..b7019b3027 100644
--- a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java
+++ b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java
@@ -432,11 +432,11 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo
/**
* Create tenant by restoring from a complete repository export. This is equivalent to a bootstrap import using restore-context.xml.
*/
- public void importTenant(String tenantDomain, final File directorySource, String rootContentStoreDir)
- {
- tenantDomain = getTenantDomain(tenantDomain);
-
- initTenant(tenantDomain, rootContentStoreDir);
+ public void importTenant(final String tenantDomainIn, final File directorySource, String contentRoot)
+ {
+ final String tenantDomain = getTenantDomain(tenantDomainIn);
+
+ initTenant(tenantDomain, contentRoot);
try
{
diff --git a/source/test-resources/tenant/mt-admin-context.xml b/source/test-resources/tenant/mt-admin-context.xml
index 0a138f0c35..b066fb04f2 100644
--- a/source/test-resources/tenant/mt-admin-context.xml
+++ b/source/test-resources/tenant/mt-admin-context.xml
@@ -14,25 +14,26 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
${alfresco_user_store.adminusername}
-
-
-
+
+
+
alfresco.messages.tenant-interpreter-help
-
+
+