diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml
index 89cea501d3..81f21dbff7 100644
--- a/config/alfresco/core-services-context.xml
+++ b/config/alfresco/core-services-context.xml
@@ -996,21 +996,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/config/alfresco/messages/action-config.properties b/config/alfresco/messages/action-config.properties
index 1e3c4dee17..c833551b29 100644
--- a/config/alfresco/messages/action-config.properties
+++ b/config/alfresco/messages/action-config.properties
@@ -60,11 +60,11 @@ has-tag.tag.display-label=Tag
# Actions
-add-features.title=Add an aspect
+add-features.title=Add aspect
add-features.description=This will add an aspect to the matched item.
add-features.aspect-name.display-label=Aspect
-remove-features.title=Remove an aspect
+remove-features.title=Remove aspect
remove-features.description=This will remove an aspect from the matched item.
remove-features.aspect-name.display-label=Aspect
diff --git a/config/alfresco/messages/invitation-service.properties b/config/alfresco/messages/invitation-service.properties
index 8d22496449..d6c1c0acfc 100644
--- a/config/alfresco/messages/invitation-service.properties
+++ b/config/alfresco/messages/invitation-service.properties
@@ -13,8 +13,8 @@ invitation.invite.already_finished "Invitation, {0} has already been accepted, c
invitation.invite.authentication_chain "Authentication chain does not allow creation of new accounts"
# InviteSender messages
-invitation.invitesender.email.subject "Invitation to join '{0}' site"
-invitation.invitesender.email.role.SiteManager "Site Manager"
-invitation.invitesender.email.role.SiteCollaborator "Site Collaborator"
-invitation.invitesender.email.role.SiteContributor "Site Contributor"
-invitation.invitesender.email.role.SiteConsumer "Site Consumer"
\ No newline at end of file
+invitation.invitesender.email.subject=Invitation to join {0} site
+invitation.invitesender.email.role.SiteManager=Site Manager
+invitation.invitesender.email.role.SiteCollaborator=Site Collaborator
+invitation.invitesender.email.role.SiteContributor=Site Contributor
+invitation.invitesender.email.role.SiteConsumer=Site Consumer
\ No newline at end of file
diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties
index 16465c2553..6a64416ae0 100644
--- a/config/alfresco/messages/patch-service.properties
+++ b/config/alfresco/messages/patch-service.properties
@@ -328,3 +328,9 @@ patch.convertContentUrls.store.noSupport=\tNo content URLs will be marked for de
patch.convertContentUrls.store.progress=\t\tProcessed {0} content URLs from store.
patch.convertContentUrls.store.scheduled=\tScheduled {0} content URLs for deletion from store: {1}
patch.convertContentUrls.store.done=This job is complete. Deactivate the scheduled job 'contentUrlConverterTrigger'.
+
+patch.fixAuthoritiesCrcValues.description=Fixes authority CRC32 values to match UTF-8 encoding.
+patch.fixAuthoritiesCrcValues.result=Fixed CRC32 values for UTF-8 encoding for {0} authorities. See file {1} for details.
+patch.fixAuthoritiesCrcValues.fixed=Updated CRC32 values for authority ID {0}, authority ''{1}'': {2} -> {3}.
+patch.fixAuthoritiesCrcValues.unableToChange=Failed to update the CRC32 value for authority ID {0}: \n Authority: {1} \n authority CRC old: {2} \n authority CRC new: {3} \n Error: {4}
+
diff --git a/config/alfresco/mimetype/mimetype-map.xml b/config/alfresco/mimetype/mimetype-map.xml
index 3bc9e2a841..80abcb715c 100644
--- a/config/alfresco/mimetype/mimetype-map.xml
+++ b/config/alfresco/mimetype/mimetype-map.xml
@@ -261,6 +261,11 @@
pps
pot
+
+ ppt
+ pps
+ pot
+
ras
@@ -341,6 +346,9 @@
xls
+
+ xls
+
xpm
diff --git a/config/alfresco/model/calendarModel.xml b/config/alfresco/model/calendarModel.xml
index 0c96e19a2e..efbdaba6de 100644
--- a/config/alfresco/model/calendarModel.xml
+++ b/config/alfresco/model/calendarModel.xml
@@ -20,6 +20,16 @@
+
+ Separate Recurring Event
+ cm:content
+
+
+ d:datetime
+
+
+
+
Calendar Event
cm:content
@@ -52,7 +62,29 @@
d:text
+
+ d:text
+
+
+ d:datetime
+
+
+ d:boolean
+
+
+
+
+ false
+ true
+
+
+ ia:ignoreEvent
+ false
+ true
+
+
+
@@ -101,4 +133,17 @@
+
+
+ Doc folder
+
+
+ DocFolder
+ d:text
+ true
+
+
+
+
+
\ No newline at end of file
diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml
index 5229bd73bc..8a7e918900 100644
--- a/config/alfresco/patch/patch-services-context.xml
+++ b/config/alfresco/patch/patch-services-context.xml
@@ -2170,5 +2170,23 @@
classpath:alfresco/dbscripts/upgrade/3.3/${db.script.dialect}/fix-Repo-seqs_1.sql
-
+
+
+ patch.fixAuthoritiesCrcValues
+ patch.fixAuthoritiesCrcValues.description
+ 0
+ 4100
+ 4101
+ false
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/alfresco/policy-context.xml b/config/alfresco/policy-context.xml
index 7997d3b7bb..945186f628 100644
--- a/config/alfresco/policy-context.xml
+++ b/config/alfresco/policy-context.xml
@@ -65,6 +65,7 @@
+
diff --git a/config/alfresco/subsystems/imap/default/imap-server-context.xml b/config/alfresco/subsystems/imap/default/imap-server-context.xml
index 9ffd005107..925af0169d 100644
--- a/config/alfresco/subsystems/imap/default/imap-server-context.xml
+++ b/config/alfresco/subsystems/imap/default/imap-server-context.xml
@@ -110,6 +110,9 @@
+
+
+
diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties
index 371b49e883..4062b96186 100644
--- a/config/alfresco/version.properties
+++ b/config/alfresco/version.properties
@@ -19,4 +19,4 @@ version.build=@build-number@
# Schema number
-version.schema=4100
+version.schema=4101
diff --git a/source/java/org/alfresco/repo/admin/patch/impl/CalendarModelUriPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/CalendarModelUriPatch.java
index 13f1ebc10a..0e3b6f9bcf 100644
--- a/source/java/org/alfresco/repo/admin/patch/impl/CalendarModelUriPatch.java
+++ b/source/java/org/alfresco/repo/admin/patch/impl/CalendarModelUriPatch.java
@@ -69,6 +69,8 @@ public class CalendarModelUriPatch extends AbstractPatch
@Override
protected String applyInternal() throws Exception
{
+ // Make sure the old name spaces exists before we update it ...
+ qnameDAO.getOrCreateNamespace(URI_BEFORE);
// modify namespace for all calendar entries
qnameDAO.updateNamespace(URI_BEFORE, URI_AFTER);
diff --git a/source/java/org/alfresco/repo/admin/patch/impl/FixAuthoritiesCrcValuesPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/FixAuthoritiesCrcValuesPatch.java
new file mode 100644
index 0000000000..dcddfe4f32
--- /dev/null
+++ b/source/java/org/alfresco/repo/admin/patch/impl/FixAuthoritiesCrcValuesPatch.java
@@ -0,0 +1,331 @@
+/*
+ * 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.repo.admin.patch.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.sql.Savepoint;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.zip.CRC32;
+
+import org.alfresco.repo.admin.patch.AbstractPatch;
+import org.alfresco.repo.admin.patch.PatchExecuter;
+import org.alfresco.repo.batch.BatchProcessor;
+import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker;
+import org.alfresco.repo.domain.DbAuthority;
+import org.alfresco.repo.domain.control.ControlDAO;
+import org.alfresco.repo.domain.hibernate.DbAuthorityImpl;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.service.cmr.admin.PatchException;
+import org.alfresco.service.cmr.rule.RuleService;
+import org.alfresco.util.TempFileProvider;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hibernate.SQLQuery;
+import org.hibernate.ScrollMode;
+import org.hibernate.ScrollableResults;
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.type.LongType;
+import org.hibernate.type.StringType;
+import org.springframework.extensions.surf.util.I18NUtil;
+import org.springframework.orm.hibernate3.HibernateCallback;
+import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
+
+/**
+ * Fixes ALF-478.
+ * Checks all CRC values for alf_authorities.
+ *
+ * @author Andrew Hind
+ * @since V3.3
+ */
+public class FixAuthoritiesCrcValuesPatch extends AbstractPatch
+{
+ private static final String MSG_SUCCESS = "patch.fixAuthoritiesCrcValues.result";
+ private static final String MSG_REWRITTEN = "patch.fixAuthoritiesCrcValues.fixed";
+ private static final String MSG_UNABLE_TO_CHANGE = "patch.fixAuthoritiesCrcValues.unableToChange";
+
+ private SessionFactory sessionFactory;
+ private ControlDAO controlDAO;
+ private RuleService ruleService;
+
+ public FixAuthoritiesCrcValuesPatch()
+ {
+ }
+
+ public void setSessionFactory(SessionFactory sessionFactory)
+ {
+ this.sessionFactory = sessionFactory;
+ }
+
+ /**
+ * @param controlDAO used to create Savepoints
+ */
+ public void setControlDAO(ControlDAO controlDAO)
+ {
+ this.controlDAO = controlDAO;
+ }
+
+ /**
+ * @param ruleService the rule service
+ */
+ public void setRuleService(RuleService ruleService)
+ {
+ this.ruleService = ruleService;
+ }
+
+ @Override
+ protected void checkProperties()
+ {
+ super.checkProperties();
+ checkPropertyNotNull(sessionFactory, "sessionFactory");
+ checkPropertyNotNull(applicationEventPublisher, "applicationEventPublisher");
+ }
+
+ @Override
+ protected String applyInternal() throws Exception
+ {
+ // initialise the helper
+ HibernateHelper helper = new HibernateHelper();
+ helper.setSessionFactory(sessionFactory);
+
+ try
+ {
+ String msg = helper.fixCrcValues();
+ // done
+ return msg;
+ }
+ finally
+ {
+ helper.closeWriter();
+ }
+ }
+
+ private class HibernateHelper extends HibernateDaoSupport
+ {
+ private File logFile;
+ private FileChannel channel;
+
+ private HibernateHelper() throws IOException
+ {
+ // put the log file into a long life temp directory
+ File tempDir = TempFileProvider.getLongLifeTempDir("patches");
+ logFile = new File(tempDir, "FixAuthorityCrcValuesPatch.log");
+
+ // open the file for appending
+ RandomAccessFile outputFile = new RandomAccessFile(logFile, "rw");
+ channel = outputFile.getChannel();
+ // move to the end of the file
+ channel.position(channel.size());
+ // add a newline and it's ready
+ writeLine("").writeLine("");
+ writeLine("FixAuthorityCrcValuesPatch executing on " + new Date());
+ }
+
+ private HibernateHelper write(Object obj) throws IOException
+ {
+ channel.write(ByteBuffer.wrap(obj.toString().getBytes("UTF-8")));
+ return this;
+ }
+ private HibernateHelper writeLine(Object obj) throws IOException
+ {
+ write(obj);
+ write("\n");
+ return this;
+ }
+ private void closeWriter()
+ {
+ try { channel.close(); } catch (Throwable e) {}
+ }
+
+ public String fixCrcValues() throws Exception
+ {
+ // get the association types to check
+ BatchProcessor batchProcessor = new BatchProcessor(
+ "FixAuthorityCrcValuesPatch",
+ transactionService.getRetryingTransactionHelper(),
+ findMismatchedCrcs(),
+ 2, 20,
+ applicationEventPublisher,
+ logger, 1000);
+
+ // Precautionary flush and clear so that we have an empty session
+ getSession().flush();
+ getSession().clear();
+
+ int updated = batchProcessor.process(new BatchProcessWorker()
+ {
+ public String getIdentifier(Long entry)
+ {
+ return entry.toString();
+ }
+
+ public void beforeProcess() throws Throwable
+ {
+ // Switch rules off
+ ruleService.disableRules();
+ // Authenticate as system
+ String systemUsername = AuthenticationUtil.getSystemUserName();
+ AuthenticationUtil.setFullyAuthenticatedUser(systemUsername);
+ }
+
+ public void process(Long authorityId) throws Throwable
+ {
+ DbAuthority authority = (DbAuthority) getHibernateTemplate().get(DbAuthorityImpl.class, authorityId);
+ if (authority == null)
+ {
+ // Missing now ...
+ return;
+ }
+ // Get the old CRCs
+ long oldCrc = authority.getCrc();
+ String authorityName = authority.getAuthority();
+
+ // Update the CRCs
+ long updatedCrc = getCrc(authorityName);
+ authority.setCrc(updatedCrc);
+
+ // Persist
+ Savepoint savepoint = controlDAO.createSavepoint("FixAuthorityCrcValuesPatch");
+ try
+ {
+ getSession().flush();
+ controlDAO.releaseSavepoint(savepoint);
+ }
+ catch (Throwable e)
+ {
+ controlDAO.rollbackToSavepoint(savepoint);
+
+ String msg = I18NUtil.getMessage(MSG_UNABLE_TO_CHANGE, authority.getId(), authority.getAuthority(), oldCrc,
+ updatedCrc, e.getMessage());
+ // We just log this and add details to the message file
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(msg, e);
+ }
+ else
+ {
+ logger.warn(msg);
+ }
+ writeLine(msg);
+ }
+ getSession().clear();
+ // Record
+ writeLine(I18NUtil.getMessage(MSG_REWRITTEN,authority.getId(), authority.getAuthority(), oldCrc,
+ updatedCrc));
+ }
+
+ public void afterProcess() throws Throwable
+ {
+ ruleService.enableRules();
+ }
+ }, true);
+
+
+ String msg = I18NUtil.getMessage(MSG_SUCCESS, updated, logFile);
+ return msg;
+ }
+
+ private List findMismatchedCrcs() throws Exception
+ {
+ final List authorityIds = new ArrayList(1000);
+ HibernateCallback callback = new HibernateCallback()
+ {
+ public Object doInHibernate(Session session)
+ {
+ SQLQuery query = session
+ .createSQLQuery(
+ " SELECT " +
+ " au.id AS authority_id," +
+ " au.authority AS authority," +
+ " au.crc as crc" +
+ " FROM" +
+ " alf_authority au");
+ query.addScalar("authority_id", new LongType());
+ query.addScalar("authority", new StringType());
+ query.addScalar("crc", new LongType());
+ return query.scroll(ScrollMode.FORWARD_ONLY);
+ }
+ };
+ ScrollableResults rs = null;
+ try
+ {
+ rs = (ScrollableResults) getHibernateTemplate().execute(callback);
+ while (rs.next())
+ {
+ // Compute child name crc
+ Long authorityId = (Long) rs.get(0);
+ String authority = (String) rs.get(1);
+ Long crc = (Long) rs.get(2);
+ long calculatedCrc = 0;
+ if (authority != null)
+ {
+ calculatedCrc = getCrc(authority);
+ }
+
+ // Check
+ if (crc != null && crc.equals(calculatedCrc))
+ {
+ // It is a match, so ignore
+ continue;
+ }
+ authorityIds.add(authorityId);
+ }
+ }
+ catch (Throwable e)
+ {
+ logger.error("Failed to query for authority CRCs", e);
+ writeLine("Failed to query for authority CRCs: " + e.getMessage());
+ throw new PatchException("Failed to query for authority CRCs", e);
+ }
+ finally
+ {
+ if (rs != null)
+ {
+ try { rs.close(); } catch (Throwable e) { writeLine("Failed to close resultset: " + e.getMessage()); }
+ }
+ }
+ return authorityIds;
+ }
+
+ /**
+ * @param str the name that will be kept as is
+ * @return the CRC32 calculated on the exact case sensitive version of the string
+ */
+ private long getCrc(String str)
+ {
+ CRC32 crc = new CRC32();
+ try
+ {
+ crc.update(str.getBytes("UTF-8")); // https://issues.alfresco.com/jira/browse/ALFCOM-1335
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new RuntimeException("UTF-8 encoding is not supported");
+ }
+ return crc.getValue();
+ }
+ }
+}
diff --git a/source/java/org/alfresco/repo/avm/AVMServiceImpl.java b/source/java/org/alfresco/repo/avm/AVMServiceImpl.java
index 566cd1046f..0482972055 100644
--- a/source/java/org/alfresco/repo/avm/AVMServiceImpl.java
+++ b/source/java/org/alfresco/repo/avm/AVMServiceImpl.java
@@ -29,12 +29,15 @@ import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
+import org.alfresco.config.JNDIConstants;
+import org.alfresco.repo.avm.util.AVMUtil;
import org.alfresco.repo.domain.DbAccessControlList;
import org.alfresco.repo.domain.PropertyValue;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.permissions.ACLCopyMode;
import org.alfresco.service.cmr.avm.AVMBadArgumentException;
+import org.alfresco.service.cmr.avm.AVMCycleException;
import org.alfresco.service.cmr.avm.AVMException;
import org.alfresco.service.cmr.avm.AVMExistsException;
import org.alfresco.service.cmr.avm.AVMNodeDescriptor;
@@ -51,6 +54,7 @@ import org.alfresco.service.namespace.QName;
import org.alfresco.util.FileNameValidator;
import org.alfresco.util.Pair;
import org.alfresco.util.TempFileProvider;
+import org.alfresco.wcm.util.WCMUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -62,8 +66,7 @@ public class AVMServiceImpl implements AVMService
{
public static final String SYSTEM = "system";
- @SuppressWarnings("unused")
- private static Log fgLogger = LogFactory.getLog(AVMServiceImpl.class);
+ private static Log logger = LogFactory.getLog(AVMServiceImpl.class);
/**
* The AVMRepository for each service thread.
@@ -475,8 +478,48 @@ public class AVMServiceImpl implements AVMService
throw new AVMBadArgumentException("Illegal argument.");
}
fAVMRepository.createLayeredDirectory(srcPath, parent, name);
+
+ // check for cycle (note: optimised to skip when creating WCM sandbox layer)
+ String[] pathParts = AVMUtil.splitPath(parent);
+ if ((WCMUtil.getWebProject(this, pathParts[0]) == null) ||
+ (! (pathParts[1].equals("/") && name.equals(JNDIConstants.DIR_DEFAULT_WWW))))
+ {
+ long start = System.currentTimeMillis();
+
+ if (checkForLDCycle(srcPath, AVMUtil.extendAVMPath(parent, name)))
+ {
+ throw new AVMCycleException("Cycle in lookup.");
+ }
+
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("createLayeredDirectory: cycle check: "+parent+"/"+name+" -> "+srcPath+" (in "+(System.currentTimeMillis()-start)+" msecs)");
+ }
+ }
}
-
+
+ private boolean checkForLDCycle(String srcPath, String dstDirPath)
+ {
+ boolean found = false;
+ SortedMap listing = getDirectoryListing(-1, dstDirPath, false);
+ for (AVMNodeDescriptor node : listing.values())
+ {
+ if (node.isDirectory())
+ {
+ if (node.isLayeredDirectory() && node.isPrimary() && node.getIndirection().equals(srcPath))
+ {
+ return true;
+ }
+ if (checkForLDCycle(srcPath, node.getPath()))
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+ return found;
+ }
+
/**
* Create an AVMStore with the given name (it must not exist).
* @param name The name to give the AVMStore.
diff --git a/source/java/org/alfresco/repo/avm/AVMServiceTest.java b/source/java/org/alfresco/repo/avm/AVMServiceTest.java
index a6f333e830..343f8e6377 100644
--- a/source/java/org/alfresco/repo/avm/AVMServiceTest.java
+++ b/source/java/org/alfresco/repo/avm/AVMServiceTest.java
@@ -2276,35 +2276,131 @@ public class AVMServiceTest extends AVMServiceTestBase
}
/**
- * Test cyclic lookup behavior.
+ * Test cyclic behaviour (when creating layered directories)
*/
- public void testCyclicLookup() throws Exception
+ public void testCircularLayering() throws Exception
{
try
{
- fService.createDirectory("main:/", "a");
- fService.createFile("main:/a", "foo").close();
- for (int i = 0; i < 1000; i++)
- {
- fService.lookup(-1, "main:/a/bar");
- }
- fService.lookup(-1, "main:/a/foo");
- fService.createLayeredDirectory("main:/c", "main:/", "b");
+ fService.createLayeredDirectory("main:/c", "main:/", "b"); // note: unbacked
+
fService.createLayeredDirectory("main:/b", "main:/", "c");
- try
- {
- fService.lookup(-1, "main:/b/bar");
- fail();
- }
- catch (AVMCycleException ce)
- {
- // Do nothing; this means success.
- }
+ fail();
}
- catch (Exception e)
+ catch (AVMCycleException e)
{
- e.printStackTrace(System.err);
- throw e;
+ // expected
+ }
+
+ try
+ {
+ fService.createDirectory("main:/", "a");
+ fService.createDirectory("main:/a", "b");
+
+ fService.createLayeredDirectory("main:/a", "main:/a/b", "c");
+ fail();
+ }
+ catch (AVMCycleException e)
+ {
+ // expected
+ }
+
+ try
+ {
+ fService.createStore("a");
+ fService.createStore("b");
+
+ fService.createDirectory("a:/", "a");
+
+ fService.createDirectory("b:/", "a");
+ fService.createLayeredDirectory("a:/a/b", "b:/a", "b"); // note: unbacked
+
+ fService.createLayeredDirectory("b:/a/b", "a:/a", "b");
+ fail();
+ }
+ catch (AVMCycleException e)
+ {
+ // expected
+ }
+ finally
+ {
+ fService.purgeStore("a");
+ fService.purgeStore("b");
+ }
+
+ try
+ {
+ fService.createStore("test1");
+ fService.createStore("test2");
+
+ fService.createDirectory("test1:/", "test1folder");
+ fService.createDirectory("test2:/", "test2folder");
+
+ fService.createLayeredDirectory("test2:/test2folder", "test1:/test1folder", "test2");
+
+ fService.createLayeredDirectory("test1:/test1folder", "test2:/test2folder", "test1");
+ fail();
+ }
+ catch (AVMCycleException e)
+ {
+ // expected
+ }
+ finally
+ {
+ fService.purgeStore("test1");
+ fService.purgeStore("test2");
+ }
+
+ try
+ {
+ fService.createStore("a");
+ fService.createStore("b");
+ fService.createStore("c");
+
+ fService.createDirectory("a:/", "a");
+
+ fService.createDirectory("b:/", "a");
+ fService.createLayeredDirectory("a:/a/b", "b:/a", "b"); // note: unbacked
+
+ fService.createDirectory("c:/", "a");
+ fService.createLayeredDirectory("b:/a/b", "c:/a", "b");
+
+ fService.createLayeredDirectory("c:/a/b", "a:/a", "b");
+ fail();
+ }
+ catch (AVMCycleException e)
+ {
+ // expected
+ }
+ finally
+ {
+ fService.purgeStore("a");
+ fService.purgeStore("b");
+ fService.purgeStore("c");
+ }
+
+ try
+ {
+ fService.createStore("a");
+ fService.createStore("b");
+
+ fService.createDirectory("a:/", "a");
+ fService.createDirectory("a:/a", "b");
+
+ fService.createDirectory("b:/", "a");
+ fService.createLayeredDirectory("a:/a", "b:/a", "b");
+
+ fService.createLayeredDirectory("b:/a", "a:/a/b", "c");
+ fail();
+ }
+ catch (AVMCycleException e)
+ {
+ // expected
+ }
+ finally
+ {
+ fService.purgeStore("a");
+ fService.purgeStore("b");
}
}
diff --git a/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java b/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java
index d241102671..9a37642150 100644
--- a/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java
+++ b/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java
@@ -600,6 +600,9 @@ public class CheckOutCheckInServiceImpl implements CheckOutCheckInService
{
// Delete the working copy
this.nodeService.deleteNode(workingCopyNodeRef);
+
+ // Remove the lock aspect (copied from working copy)
+ this.nodeService.removeAspect(nodeRef, ContentModel.ASPECT_LOCKABLE);
}
else
{
@@ -608,7 +611,7 @@ public class CheckOutCheckInServiceImpl implements CheckOutCheckInService
}
// Invoke policy
- invokeOnCheckIn(nodeRef);
+ invokeOnCheckIn(nodeRef);
}
else
{
diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java b/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java
index c7178fe5d4..04fc6fcb71 100644
--- a/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java
+++ b/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java
@@ -29,6 +29,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.ContentServicePolicies;
+import org.alfresco.repo.lock.JobLockService;
import org.alfresco.repo.node.NodeServicePolicies;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
@@ -92,6 +93,9 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
/** Key to the removed "workingcopy" aspect */
private static final String KEY_WORKING_COPY = "dictionaryModelType.workingCopy";
+ /** The name of the lock used to ensure that DictionaryModelType updates do not run on more than one thread/node at the same time. */
+ private static final QName LOCK_QNAME = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "DictionaryModelType");
+
/** The dictionary DAO */
private DictionaryDAO dictionaryDAO;
@@ -124,6 +128,8 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
private TransactionService transactionService;
+ private JobLockService jobLockService;
+
/** Transaction listener */
private DictionaryModelTypeTransactionListener transactionListener;
@@ -218,6 +224,11 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
this.transactionService = transactionService;
}
+ public void setJobLockService(JobLockService jobLockService)
+ {
+ this.jobLockService = jobLockService;
+ }
+
public void setStoreUrls(List storeUrls)
{
this.storeUrls = storeUrls;
@@ -231,38 +242,38 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
{
// Register interest in the onContentUpdate policy for the dictionary model type
policyComponent.bindClassBehaviour(
- ContentServicePolicies.OnContentUpdatePolicy.QNAME,
+ ContentServicePolicies.OnContentUpdatePolicy.QNAME,
ContentModel.TYPE_DICTIONARY_MODEL,
new JavaBehaviour(this, "onContentUpdate"));
// Register interest in the onUpdateProperties policy for the dictionary model type
policyComponent.bindClassBehaviour(
- QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"),
+ QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"),
ContentModel.TYPE_DICTIONARY_MODEL,
new JavaBehaviour(this, "onUpdateProperties"));
// Register interest in the beforeDeleteNode policy for the dictionary model type
policyComponent.bindClassBehaviour(
- QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"),
+ QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"),
ContentModel.TYPE_DICTIONARY_MODEL,
new JavaBehaviour(this, "beforeDeleteNode"));
// Register interest in the onDeleteNode policy for the dictionary model type
policyComponent.bindClassBehaviour(
- QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteNode"),
+ QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteNode"),
ContentModel.TYPE_DICTIONARY_MODEL,
new JavaBehaviour(this, "onDeleteNode"));
// Register interest in the onRemoveAspect policy
policyComponent.bindClassBehaviour(
- QName.createQName(NamespaceService.ALFRESCO_URI, "onRemoveAspect"),
- this,
+ QName.createQName(NamespaceService.ALFRESCO_URI, "onRemoveAspect"),
+ ContentModel.TYPE_DICTIONARY_MODEL,
new JavaBehaviour(this, "onRemoveAspect"));
// Register interest in the onCreateNode policy
policyComponent.bindClassBehaviour(
QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"),
- this,
+ ContentModel.TYPE_DICTIONARY_MODEL,
new JavaBehaviour(this, "onCreateNode"));
// Create the transaction listener
@@ -555,7 +566,17 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
@SuppressWarnings("unchecked")
@Override
public void beforeCommit(boolean readOnly)
- {
+ {
+ if (jobLockService != null)
+ {
+ jobLockService.getTransactionalLock(LOCK_QNAME, (1000*60), 3000, 10);
+
+ if (logger.isTraceEnabled())
+ {
+ logger.trace(Thread.currentThread().getName()+" got transactional lock ");
+ }
+ }
+
Set pendingModels = (Set)AlfrescoTransactionSupport.getResource(KEY_PENDING_MODELS);
if (pendingModels != null)
diff --git a/source/java/org/alfresco/repo/domain/hibernate/AclDaoComponentImpl.java b/source/java/org/alfresco/repo/domain/hibernate/AclDaoComponentImpl.java
index 86df648e22..4cf7039340 100644
--- a/source/java/org/alfresco/repo/domain/hibernate/AclDaoComponentImpl.java
+++ b/source/java/org/alfresco/repo/domain/hibernate/AclDaoComponentImpl.java
@@ -19,6 +19,7 @@
package org.alfresco.repo.domain.hibernate;
import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
@@ -1564,15 +1565,22 @@ public class AclDaoComponentImpl extends HibernateDaoSupport implements AclDaoCo
private long getCrc(String str)
{
- CRC32 crc = new CRC32();
- crc.update(str.getBytes());
- return crc.getValue();
+ try
+ {
+ CRC32 crc = new CRC32();
+ crc.update(str.getBytes("UTF-8"));
+ return crc.getValue();
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new RuntimeException("UTF-8 encoding is not supported");
+ }
}
public List enableInheritance(Long id, Long parent)
{
List changes = new ArrayList();
-
+
DbAccessControlList acl = (DbAccessControlList) getHibernateTemplate().get(DbAccessControlListImpl.class, id);
switch (acl.getAclType())
diff --git a/source/java/org/alfresco/repo/domain/hibernate/DMAccessControlListDAO.java b/source/java/org/alfresco/repo/domain/hibernate/DMAccessControlListDAO.java
index fe1a03961b..b28610e9f3 100644
--- a/source/java/org/alfresco/repo/domain/hibernate/DMAccessControlListDAO.java
+++ b/source/java/org/alfresco/repo/domain/hibernate/DMAccessControlListDAO.java
@@ -39,6 +39,8 @@ import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.namespace.QNamePattern;
+import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.util.Pair;
/**
@@ -255,7 +257,7 @@ public class DMAccessControlListDAO implements AccessControlListDAO
}
}
- List children = nodeService.getChildAssocs(nodeRef);
+ List children = nodeService.getChildAssocs(nodeRef, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false);
if (children.size() > 0)
{
hibernateSessionHelper.reset();
@@ -353,7 +355,7 @@ public class DMAccessControlListDAO implements AccessControlListDAO
setAccessControlList(nodeRef, aclDaoComponent.getDbAccessControlList(mergeFrom));
}
- List children = nodeService.getChildAssocs(nodeRef);
+ List children = nodeService.getChildAssocs(nodeRef, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false);
for (ChildAssociationRef child : children)
{
diff --git a/source/java/org/alfresco/repo/forms/processor/node/TypeFormProcessor.java b/source/java/org/alfresco/repo/forms/processor/node/TypeFormProcessor.java
index 35b6f7d88c..b63be6c027 100644
--- a/source/java/org/alfresco/repo/forms/processor/node/TypeFormProcessor.java
+++ b/source/java/org/alfresco/repo/forms/processor/node/TypeFormProcessor.java
@@ -31,6 +31,7 @@ import org.alfresco.repo.forms.FormException;
import org.alfresco.repo.forms.FormNotFoundException;
import org.alfresco.repo.forms.Item;
import org.alfresco.repo.forms.FormData.FieldData;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.dictionary.AspectDefinition;
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
@@ -53,6 +54,8 @@ public class TypeFormProcessor extends ContentModelFormProcessor()
+ {
+ public Object doWork() throws Exception
+ {
+ persistNode(nodeRef, data);
+ return null;
+ }
+
+ },
+ AuthenticationUtil.getAdminUserName());
+ }
+ else
+ {
+ // persist the form data
+ persistNode(nodeRef, data);
+ }
// return the newly created node
return nodeRef;
diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java b/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java
index 99a0c212d8..d7521f24cf 100644
--- a/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java
+++ b/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java
@@ -75,6 +75,7 @@ import com.icegreen.greenmail.store.SimpleStoredMessage;
*/
public class AlfrescoImapFolder extends AbstractImapFolder
{
+ private final static long YEAR_2005 = 1101765600000L;
private static Log logger = LogFactory.getLog(AlfrescoImapFolder.class);
@@ -718,7 +719,8 @@ public class AlfrescoImapFolder extends AbstractImapFolder
@Override
protected long getUidValidityInternal()
{
- return ((Date) serviceRegistry.getNodeService().getProperty(folderInfo.getNodeRef(), ContentModel.PROP_MODIFIED)).getTime();
+ long modifDate = ((Date) serviceRegistry.getNodeService().getProperty(folderInfo.getNodeRef(), ContentModel.PROP_MODIFIED)).getTime();
+ return (modifDate - YEAR_2005)/1000;
}
/**
@@ -830,7 +832,8 @@ public class AlfrescoImapFolder extends AbstractImapFolder
{
if (serviceRegistry.getNodeService().getType(fileInfo.getNodeRef()).equals(ContentModel.TYPE_FOLDER))
{
- return ((Date) serviceRegistry.getNodeService().getProperty(fileInfo.getNodeRef(), ContentModel.PROP_MODIFIED)).getTime();
+ long modifDate = ((Date) serviceRegistry.getNodeService().getProperty(fileInfo.getNodeRef(), ContentModel.PROP_MODIFIED)).getTime();
+ return (modifDate - YEAR_2005)/1000;
}
return (Long) serviceRegistry.getNodeService().getProperty(fileInfo.getNodeRef(), ContentModel.PROP_NODE_DBID);
diff --git a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java
index de28496b62..aabf5ce84b 100644
--- a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java
+++ b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java
@@ -80,6 +80,7 @@ public class ImapServiceImpl implements ImapService
private SysAdminParams sysAdminParams;
private FileFolderService fileFolderService;
private NodeService nodeService;
+ private PermissionService permissionService;
private ServiceRegistry serviceRegistry;
private Map imapConfigMountPoints;
@@ -179,6 +180,16 @@ public class ImapServiceImpl implements ImapService
{
this.nodeService = nodeService;
}
+
+ public PermissionService getPermissionService()
+ {
+ return permissionService;
+ }
+
+ public void setPermissionService(PermissionService permissionService)
+ {
+ this.permissionService = permissionService;
+ }
public ServiceRegistry getServiceRegistry()
{
@@ -249,6 +260,7 @@ public class ImapServiceImpl implements ImapService
PropertyCheck.mandatory(this, "fileFolderService", fileFolderService);
PropertyCheck.mandatory(this, "nodeService", nodeService);
+ PropertyCheck.mandatory(this, "permissionService", permissionService);
PropertyCheck.mandatory(this, "serviceRegistry", serviceRegistry);
PropertyCheck.mandatory(this, "defaultFromAddress", defaultFromAddress);
PropertyCheck.mandatory(this, "repositoryTemplatePath", repositoryTemplatePath);
@@ -352,7 +364,7 @@ public class ImapServiceImpl implements ImapService
mailboxName = Utf7.decode(mailboxName, Utf7.UTF7_MODIFIED);
if (logger.isDebugEnabled())
{
- logger.debug("Creating folder: " + mailboxName);
+ logger.debug("Create mailbox: " + mailboxName);
}
NodeRef root = getMailboxRootRef(mailboxName, user.getLogin());
NodeRef parentNodeRef = root; // it is used for hierarhy deep search.
@@ -366,7 +378,7 @@ public class ImapServiceImpl implements ImapService
if (folders.size() == 0)
{
// folder doesn't exist
- AccessStatus status = serviceRegistry.getPermissionService().hasPermission(parentNodeRef, PermissionService.CREATE_CHILDREN);
+ AccessStatus status = permissionService.hasPermission(parentNodeRef, PermissionService.CREATE_CHILDREN);
if (status == AccessStatus.DENIED)
{
throw new AlfrescoRuntimeException(ERROR_PERMISSION_DENIED);
@@ -483,7 +495,7 @@ public class ImapServiceImpl implements ImapService
if (folders.size() == 0)
{
// check creation permission
- AccessStatus status = serviceRegistry.getPermissionService().hasPermission(parentNodeRef, PermissionService.CREATE_CHILDREN);
+ AccessStatus status = permissionService.hasPermission(parentNodeRef, PermissionService.CREATE_CHILDREN);
if (status == AccessStatus.DENIED)
{
throw new AlfrescoRuntimeException(ERROR_PERMISSION_DENIED);
@@ -1216,27 +1228,41 @@ public class ImapServiceImpl implements ImapService
}
/**
+ * Get the node ref of the user's imap home. Will create it on demand if it
+ * does not already exist.
+ *
* @param userName user name
* @return user IMAP home reference and create it if it doesn't exist.
*/
private NodeRef getUserImapHomeRef(final String userName)
{
- NodeRef userHome = fileFolderService.searchSimple(imapHomeNodeRef, userName);
- if (userHome == null)
+
+ NodeRef userHome = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork()
{
- // create user home
- userHome = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork()
+ public NodeRef doWork() throws Exception
{
- public NodeRef doWork() throws Exception
+ // Look for user imap home
+ NodeRef userHome = fileFolderService.searchSimple(imapHomeNodeRef, userName);
+ if (userHome == null)
{
+ // user imap home does not exist
NodeRef result = fileFolderService.create(imapHomeNodeRef, userName, ContentModel.TYPE_FOLDER).getNodeRef();
nodeService.setProperty(result, ContentModel.PROP_DESCRIPTION, userName);
- // create inbox
+
+ // create user inbox
fileFolderService.create(result, AlfrescoImapConst.INBOX_NAME, ContentModel.TYPE_FOLDER);
+
+ // Set permissions on user's imap home
+ permissionService.setInheritParentPermissions(result, false);
+ permissionService.setPermission(result, PermissionService.OWNER_AUTHORITY, PermissionService.ALL_PERMISSIONS, true);
+
return result;
}
- }, AuthenticationUtil.getSystemUserName());
- }
+
+ return userHome;
+ }
+ }, AuthenticationUtil.getSystemUserName());
+
return userHome;
}
diff --git a/source/java/org/alfresco/repo/invitation/InvitationServiceImplTest.java b/source/java/org/alfresco/repo/invitation/InvitationServiceImplTest.java
index f32d1f7588..ad2fb05292 100644
--- a/source/java/org/alfresco/repo/invitation/InvitationServiceImplTest.java
+++ b/source/java/org/alfresco/repo/invitation/InvitationServiceImplTest.java
@@ -246,6 +246,7 @@ public class InvitationServiceImplTest extends BaseAlfrescoSpringTest
assertNull("Not been sent yet", msg.getReceivedDate());
// TODO - check some more details of the email
+ assertEquals("Invitation to join InviteSiteTitle site", msg.getSubject());
}
/**
diff --git a/source/java/org/alfresco/repo/jscript/ScriptNode.java b/source/java/org/alfresco/repo/jscript/ScriptNode.java
index 91c36e5d9a..fdf9d56772 100644
--- a/source/java/org/alfresco/repo/jscript/ScriptNode.java
+++ b/source/java/org/alfresco/repo/jscript/ScriptNode.java
@@ -24,6 +24,7 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Serializable;
+import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -3219,11 +3220,47 @@ public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResol
this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property);
}
+ /**
+ * Guess the mimetype for the given filename - uses the extension to match on system mimetype map
+ */
public void guessMimetype(String filename)
{
setMimetype(services.getMimetypeService().guessMimetype(filename));
}
+ /**
+ * Guess the character encoding of a file. For non-text files UTF-8 default is applied, otherwise
+ * the appropriate encoding (such as UTF-16 or similar) will be appiled if detected.
+ */
+ public void guessEncoding()
+ {
+ String encoding = "UTF-8";
+ InputStream in = null;
+ try
+ {
+ in = getInputStream();
+ if (in != null)
+ {
+ Charset charset = services.getMimetypeService().getContentCharsetFinder().getCharset(in, getMimetype());
+ encoding = charset.name();
+ }
+ }
+ finally
+ {
+ try
+ {
+ if (in != null)
+ {
+ in.close();
+ }
+ }
+ catch (IOException ioErr)
+ {
+ }
+ }
+ setEncoding(encoding);
+ }
+
private ContentData contentData;
private QName property;
}
diff --git a/source/java/org/alfresco/repo/jscript/ScriptUtils.java b/source/java/org/alfresco/repo/jscript/ScriptUtils.java
index 0c1b80c05e..d328dcaf5c 100644
--- a/source/java/org/alfresco/repo/jscript/ScriptUtils.java
+++ b/source/java/org/alfresco/repo/jscript/ScriptUtils.java
@@ -21,12 +21,12 @@ package org.alfresco.repo.jscript;
import java.util.Date;
import org.alfresco.service.ServiceRegistry;
-import org.alfresco.service.cmr.repository.NodeRef;
-import org.springframework.extensions.surf.util.ISO8601DateFormat;
-import org.alfresco.service.cmr.module.ModuleService;
import org.alfresco.service.cmr.module.ModuleDetails;
+import org.alfresco.service.cmr.module.ModuleService;
+import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
+import org.springframework.extensions.surf.util.I18NUtil;
import org.springframework.extensions.surf.util.ISO8601DateFormat;
/**
@@ -176,4 +176,16 @@ public final class ScriptUtils extends BaseScopableProcessorExtension
}
return qname;
}
+
+ /**
+ * Get a localized message string, parameterized using standard MessageFormatter.
+ *
+ * @param messageKey message key
+ * @param params format parameters
+ * @return the localized string, null if not found
+ */
+ public String toLocalizedString(String messageId, Object... params)
+ {
+ return I18NUtil.getMessage(messageId, params);
+ }
}
diff --git a/source/java/org/alfresco/repo/lock/LockServiceImpl.java b/source/java/org/alfresco/repo/lock/LockServiceImpl.java
index 084e1e2f59..fa9e34dfee 100644
--- a/source/java/org/alfresco/repo/lock/LockServiceImpl.java
+++ b/source/java/org/alfresco/repo/lock/LockServiceImpl.java
@@ -40,6 +40,7 @@ import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.policy.PolicyScope;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.tenant.TenantService;
+import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.version.VersionServicePolicies;
import org.alfresco.service.cmr.lock.LockService;
import org.alfresco.service.cmr.lock.LockStatus;
@@ -86,12 +87,10 @@ public class LockServiceImpl implements LockService,
* The policy component
*/
private PolicyComponent policyComponent;
-
- /**
- * List of node ref's to ignore when checking for locks
- */
- private Set ignoreNodeRefs = new HashSet();
-
+
+ /** Key to the nodes ref's to ignore when checking for locks */
+ private static final String KEY_IGNORE_NODES = "lockService.ignoreNodes";
+
/**
* The authentication service
*/
@@ -209,6 +208,39 @@ public class LockServiceImpl implements LockService,
new JavaBehaviour(this, "onCreateVersion"));
}
+ @SuppressWarnings("unchecked")
+ private void addToIgnoreSet(NodeRef nodeRef)
+ {
+ Set ignoreNodeRefs = (Set)AlfrescoTransactionSupport.getResource(KEY_IGNORE_NODES);
+ if (ignoreNodeRefs == null)
+ {
+ ignoreNodeRefs = new HashSet();
+ AlfrescoTransactionSupport.bindResource(KEY_IGNORE_NODES, ignoreNodeRefs);
+ }
+ ignoreNodeRefs.add(nodeRef);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void removeFromIgnoreSet(NodeRef nodeRef)
+ {
+ Set ignoreNodeRefs = (Set)AlfrescoTransactionSupport.getResource(KEY_IGNORE_NODES);
+ if (ignoreNodeRefs != null)
+ {
+ ignoreNodeRefs.remove(nodeRef);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private boolean ignore(NodeRef nodeRef)
+ {
+ Set ignoreNodeRefs = (Set)AlfrescoTransactionSupport.getResource(KEY_IGNORE_NODES);
+ if (ignoreNodeRefs != null)
+ {
+ return ignoreNodeRefs.contains(nodeRef);
+ }
+ return false;
+ }
+
/**
* @see org.alfresco.service.cmr.lock.LockService#lock(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, org.alfresco.service.cmr.lock.LockType)
*/
@@ -243,21 +275,21 @@ public class LockServiceImpl implements LockService,
// Error since we are trying to lock a locked node
throw new UnableToAquireLockException(nodeRef);
}
- else if (LockStatus.NO_LOCK.equals(currentLockStatus) == true ||
+ else if (LockStatus.NO_LOCK.equals(currentLockStatus) == true ||
LockStatus.LOCK_EXPIRED.equals(currentLockStatus) == true ||
LockStatus.LOCK_OWNER.equals(currentLockStatus) == true)
{
- this.ignoreNodeRefs.add(nodeRef);
+ addToIgnoreSet(nodeRef);
try
{
// Set the current user as the lock owner
this.nodeService.setProperty(nodeRef, ContentModel.PROP_LOCK_OWNER, userName);
- this.nodeService.setProperty(nodeRef, ContentModel.PROP_LOCK_TYPE, lockType.toString());
+ this.nodeService.setProperty(nodeRef, ContentModel.PROP_LOCK_TYPE, lockType.toString());
setExpiryDate(nodeRef, timeToExpire);
}
finally
{
- this.ignoreNodeRefs.remove(nodeRef);
+ removeFromIgnoreSet(nodeRef);
}
}
}
@@ -325,16 +357,15 @@ public class LockServiceImpl implements LockService,
// Check for lock aspect
checkForLockApsect(nodeRef);
- this.ignoreNodeRefs.add(nodeRef);
+ addToIgnoreSet(nodeRef);
try
{
- // Clear the lock owner
- this.nodeService.setProperty(nodeRef, ContentModel.PROP_LOCK_OWNER, null);
- this.nodeService.setProperty(nodeRef, ContentModel.PROP_LOCK_TYPE, null);
+ // Clear the lock
+ this.nodeService.removeAspect(nodeRef, ContentModel.ASPECT_LOCKABLE);
}
finally
{
- this.ignoreNodeRefs.remove(nodeRef);
+ removeFromIgnoreSet(nodeRef);
}
}
@@ -474,7 +505,7 @@ public class LockServiceImpl implements LockService,
{
String effectiveUserName = AuthenticationUtil.getRunAsUser();
// Check to see if should just ignore this node - note: special MT System due to AuditableAspect
- if (!(this.ignoreNodeRefs.contains(nodeRef) || tenantService.getBaseNameUser(effectiveUserName).equals(AuthenticationUtil.getSystemUserName())))
+ if (! (ignore(nodeRef) || tenantService.getBaseNameUser(effectiveUserName).equals(AuthenticationUtil.getSystemUserName())))
{
try
{
diff --git a/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthorityTest.java b/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthorityTest.java
index 7392ade2eb..2343df0b78 100644
--- a/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthorityTest.java
+++ b/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthorityTest.java
@@ -46,7 +46,7 @@ import org.alfresco.util.ApplicationContextHelper;
import org.springframework.context.ApplicationContext;
/**
- * Test the lock owner dynaic authority
+ * Test the lock owner dynamic authority
*
* @author andyh
*
@@ -262,13 +262,14 @@ public class LockOwnerDynamicAuthorityTest extends TestCase
permissionService.setPermission(rootNodeRef, "frog", PermissionService.CHECK_OUT, true);
permissionService.setPermission(rootNodeRef, "frog", PermissionService.WRITE, true);
permissionService.setPermission(rootNodeRef, "frog", PermissionService.READ, true);
-
+
authenticationService.authenticate("andy", "andy".toCharArray());
+
NodeRef testNode = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_PERSON,
ContentModel.TYPE_CMOBJECT, null).getChildRef();
permissionService.setPermission(rootNodeRef, "andy", PermissionService.ALL_PERMISSIONS, false);
-
-
+
+
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode,
PermissionService.LOCK));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode,
@@ -381,13 +382,17 @@ public class LockOwnerDynamicAuthorityTest extends TestCase
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode,
PermissionService.LOCK));
+
@SuppressWarnings("unused")
Map properties = nodeService.getProperties(testNode);
+
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode,
+ PermissionService.LOCK));
+ assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode,
PermissionService.UNLOCK));
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT));
- assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN));
- assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT));
+ assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN));
+ assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT));
authenticationService.authenticate("lemur", "lemur".toCharArray());
@@ -408,21 +413,19 @@ public class LockOwnerDynamicAuthorityTest extends TestCase
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT));
-
+
authenticationService.authenticate("frog", "frog".toCharArray());
workingCopy = checkOutCheckInService.checkout(testNode);
ownableService.setOwner(workingCopy, "lemur");
checkOutCheckInService.checkin(workingCopy, null);
-
}
/**
*
*/
- public void testCeckInCheckOut()
+ public void testLockUnlock()
{
-
permissionService.setPermission(rootNodeRef, "andy", PermissionService.ALL_PERMISSIONS, true);
permissionService.setPermission(rootNodeRef, "lemur", PermissionService.CHECK_OUT, true);
permissionService.setPermission(rootNodeRef, "lemur", PermissionService.WRITE, true);
@@ -430,50 +433,70 @@ public class LockOwnerDynamicAuthorityTest extends TestCase
permissionService.setPermission(rootNodeRef, "frog", PermissionService.CHECK_OUT, true);
permissionService.setPermission(rootNodeRef, "frog", PermissionService.WRITE, true);
permissionService.setPermission(rootNodeRef, "frog", PermissionService.READ, true);
+
authenticationService.authenticate("andy", "andy".toCharArray());
+
NodeRef testNode = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_PERSON,
ContentModel.TYPE_CMOBJECT, null).getChildRef();
+
+ assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.LOCK));
+ assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.UNLOCK));
+ assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT));
+ assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN));
+ assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT));
+
lockService.lock(testNode, LockType.READ_ONLY_LOCK);
-
-
- assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode,
- PermissionService.LOCK));
- assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode,
- PermissionService.UNLOCK));
+
+ assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.LOCK));
+ assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.UNLOCK));
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT));
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN));
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT));
authenticationService.authenticate("lemur", "lemur".toCharArray());
- assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode,
- PermissionService.LOCK));
- assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode,
- PermissionService.UNLOCK));
+ assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.LOCK));
+ assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.UNLOCK));
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT));
authenticationService.authenticate("andy", "andy".toCharArray());
- lockService.unlock(testNode);
- authenticationService.authenticate("lemur", "lemur".toCharArray());
- lockService.lock(testNode, LockType.READ_ONLY_LOCK);
- assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode,
- PermissionService.LOCK));
- assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode,
- PermissionService.UNLOCK));
+ assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.LOCK));
+ assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.UNLOCK));
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT));
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN));
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT));
+ lockService.unlock(testNode);
+
+ assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.LOCK));
+ assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.UNLOCK));
+ assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT));
+ assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN));
+ assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT));
+
+ authenticationService.authenticate("lemur", "lemur".toCharArray());
+
+ assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.LOCK));
+ assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.UNLOCK));
+ assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT));
+ assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN));
+ assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT));
+
+ lockService.lock(testNode, LockType.READ_ONLY_LOCK);
+
+ assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.LOCK));
+ assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.UNLOCK));
+ assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT));
+ assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN));
+ assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT));
authenticationService.authenticate("frog", "frog".toCharArray());
- assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode,
- PermissionService.LOCK));
- assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode,
- PermissionService.UNLOCK));
+ assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.LOCK));
+ assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.UNLOCK));
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testNode, PermissionService.CHECK_OUT));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CHECK_IN));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testNode, PermissionService.CANCEL_CHECK_OUT));
diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java
index a8af8b841e..3ee4087890 100644
--- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java
+++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java
@@ -18,12 +18,14 @@
*/
package org.alfresco.repo.security.sync;
+import java.io.Serializable;
import java.sql.BatchUpdateException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -551,6 +553,9 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
private final Map groupsToCreate = new TreeMap();
private final Map> groupAssocsToCreate = new TreeMap>();
private final Map> groupAssocsToDelete = new TreeMap>();
+ private final Set authoritiesMaintained = new TreeSet();
+ private Set deletionCandidates;
+
private long latestTime;
public Analyzer(final long latestTime)
@@ -562,25 +567,10 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
{
return this.latestTime;
}
-
- public Set getAllZoneAuthorities()
+
+ public Set getDeletionCandidates()
{
- return this.allZoneAuthorities;
- }
-
- public Map getGroupsToCreate()
- {
- return this.groupsToCreate;
- }
-
- public Map> getGroupAssocsToCreate()
- {
- return this.groupAssocsToCreate;
- }
-
- public Map> getGroupAssocsToDelete()
- {
- return this.groupAssocsToDelete;
+ return this.deletionCandidates;
}
public String getIdentifier(NodeDescription entry)
@@ -759,156 +749,218 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
parents.add(groupName);
}
}
- }
-
- Analyzer groupAnalyzer = new Analyzer(lastModifiedMillis);
- int groupProcessedCount = groupProcessor.process(groupAnalyzer, splitTxns);
- final Map> groupAssocsToCreate = groupAnalyzer.getGroupAssocsToCreate();
- final Map> groupAssocsToDelete = groupAnalyzer.getGroupAssocsToDelete();
- Set deletionCandidates = null;
-
- // If we got back some groups, we have to cross reference them with the set of known authorities
- if (allowDeletions || !groupAssocsToCreate.isEmpty())
- {
- // Get current set of known authorities
- Set allZoneAuthorities = this.retryingTransactionHelper.doInTransaction(
- new RetryingTransactionCallback>()
- {
- public Set execute() throws Throwable
- {
- return ChainingUserRegistrySynchronizer.this.authorityService.getAllAuthoritiesInZone(
- zoneId, null);
- }
- }, true, splitTxns);
- // Add in those that will be created or moved
- allZoneAuthorities.addAll(groupAnalyzer.getAllZoneAuthorities());
-
- // Prune our set of authorities according to deletions
- if (allowDeletions)
+
+ public void processGroups(UserRegistry userRegistry, boolean allowDeletions, boolean splitTxns)
{
- deletionCandidates = new TreeSet(allZoneAuthorities);
- userRegistry.processDeletions(deletionCandidates);
- allZoneAuthorities.removeAll(deletionCandidates);
- groupAssocsToCreate.keySet().removeAll(deletionCandidates);
- groupAssocsToDelete.keySet().removeAll(deletionCandidates);
+ // If we got back some groups, we have to cross reference them with the set of known authorities
+ if (allowDeletions || !groupAssocsToCreate.isEmpty())
+ {
+ // Add in current set of known authorities
+ allZoneAuthorities.addAll(ChainingUserRegistrySynchronizer.this.retryingTransactionHelper.doInTransaction(
+ new RetryingTransactionCallback>()
+ {
+ public Set execute() throws Throwable
+ {
+ return ChainingUserRegistrySynchronizer.this.authorityService.getAllAuthoritiesInZone(
+ zoneId, null);
+ }
+ }, true, splitTxns));
+
+
+ // Prune our set of authorities according to deletions
+ if (allowDeletions)
+ {
+ deletionCandidates = new TreeSet(allZoneAuthorities);
+ userRegistry.processDeletions(deletionCandidates);
+ allZoneAuthorities.removeAll(deletionCandidates);
+ groupAssocsToCreate.keySet().removeAll(deletionCandidates);
+ groupAssocsToDelete.keySet().removeAll(deletionCandidates);
+ }
+
+ if (!groupAssocsToCreate.isEmpty())
+ {
+ // Sort the group associations in depth-first order (root groups first) and filter out non-existent parents
+ Map> sortedGroupAssociations = new LinkedHashMap>(
+ groupAssocsToCreate.size() * 2);
+ List authorityPath = new ArrayList(5);
+ for (String authority : groupAssocsToCreate.keySet())
+ {
+ if (allZoneAuthorities.contains(authority))
+ {
+ authorityPath.add(authority);
+ visitGroupAssociations(authorityPath, allZoneAuthorities, groupAssocsToCreate,
+ sortedGroupAssociations);
+ authorityPath.clear();
+ }
+ }
+
+ // Add the groups and their parent associations in depth-first order
+ BatchProcessor>> groupCreator = new BatchProcessor>>(
+ zone + " Group Creation and Association",
+ ChainingUserRegistrySynchronizer.this.retryingTransactionHelper,
+ sortedGroupAssociations.entrySet(),
+ ChainingUserRegistrySynchronizer.this.workerThreads, 20,
+ ChainingUserRegistrySynchronizer.this.applicationEventPublisher,
+ ChainingUserRegistrySynchronizer.logger,
+ ChainingUserRegistrySynchronizer.this.loggingInterval);
+ groupCreator.process(new BatchProcessWorker>>()
+ {
+ public String getIdentifier(Map.Entry> entry)
+ {
+ return entry.getKey() + " " + entry.getValue();
+ }
+
+ public void beforeProcess() throws Throwable
+ {
+ // Disable rules
+ ruleService.disableRules();
+ // Authentication
+ AuthenticationUtil.setRunAsUser(AuthenticationUtil.getSystemUserName());
+ }
+
+ public void afterProcess() throws Throwable
+ {
+ // Enable rules
+ ruleService.enableRules();
+ // Clear authentication
+ AuthenticationUtil.clearCurrentSecurityContext();
+ }
+
+ public void process(Map.Entry> entry) throws Throwable
+ {
+ String child = entry.getKey();
+
+ String groupDisplayName = groupsToCreate.get(child);
+ if (groupDisplayName != null)
+ {
+ String groupShortName = ChainingUserRegistrySynchronizer.this.authorityService
+ .getShortName(child);
+ if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled())
+ {
+ ChainingUserRegistrySynchronizer.logger
+ .debug("Creating group '" + groupShortName + "'");
+ }
+ // create the group
+ ChainingUserRegistrySynchronizer.this.authorityService.createAuthority(AuthorityType
+ .getAuthorityType(child), groupShortName, groupDisplayName, zoneSet);
+ }
+ maintainAssociations(child);
+ }
+ }, splitTxns);
+ }
+ }
}
- if (!groupAssocsToCreate.isEmpty())
+ public void finalizeAssociations(UserRegistry userRegistry, boolean splitTxns)
{
- // Sort the group associations in depth-first order (root groups first)
- Map> sortedGroupAssociations = new LinkedHashMap>(
- groupAssocsToCreate.size() * 2);
- List authorityPath = new ArrayList(5);
- for (String authority : groupAssocsToCreate.keySet())
+ // Remove all the associations we have already dealt with
+ groupAssocsToCreate.keySet().removeAll(authoritiesMaintained);
+
+ // Filter out associations to authorities that simply can't exist
+ groupAssocsToCreate.keySet().retainAll(allZoneAuthorities);
+
+ if (!groupAssocsToCreate.isEmpty())
{
- if (allZoneAuthorities.contains(authority))
+ BatchProcessor>> groupCreator = new BatchProcessor>>(
+ zone + " Authority Association",
+ ChainingUserRegistrySynchronizer.this.retryingTransactionHelper,
+ groupAssocsToCreate.entrySet(),
+ ChainingUserRegistrySynchronizer.this.workerThreads, 20,
+ ChainingUserRegistrySynchronizer.this.applicationEventPublisher,
+ ChainingUserRegistrySynchronizer.logger,
+ ChainingUserRegistrySynchronizer.this.loggingInterval);
+ groupCreator.process(new BatchProcessWorker>>()
{
- authorityPath.add(authority);
- visitGroupAssociations(authorityPath, allZoneAuthorities, groupAssocsToCreate,
- sortedGroupAssociations);
- authorityPath.clear();
+ public String getIdentifier(Map.Entry> entry)
+ {
+ return entry.getKey() + " " + entry.getValue();
+ }
+
+ public void beforeProcess() throws Throwable
+ {
+ // Disable rules
+ ruleService.disableRules();
+ // Authentication
+ AuthenticationUtil.setRunAsUser(AuthenticationUtil.getSystemUserName());
+ }
+
+ public void afterProcess() throws Throwable
+ {
+ // Enable rules
+ ruleService.enableRules();
+ // Clear authentication
+ AuthenticationUtil.clearCurrentSecurityContext();
+ }
+
+ public void process(Map.Entry> entry) throws Throwable
+ {
+ maintainAssociations(entry.getKey());
+ }
+ }, splitTxns);
+ }
+ }
+
+ private void maintainAssociations(String authorityName) throws BatchUpdateException
+ {
+ Set parents = this.groupAssocsToCreate.get(authorityName);
+ if (parents != null && !parents.isEmpty())
+ {
+ if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled())
+ {
+ for (String groupName : parents)
+ {
+ ChainingUserRegistrySynchronizer.logger.debug("Adding '"
+ + ChainingUserRegistrySynchronizer.this.authorityService
+ .getShortName(authorityName) + "' to group '"
+ + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName)
+ + "'");
+ }
+ }
+ try
+ {
+ ChainingUserRegistrySynchronizer.this.authorityService.addAuthority(parents, authorityName);
+ }
+ catch (UnknownAuthorityException e)
+ {
+ // Let's force a transaction retry if a parent doesn't exist. It may be because we are
+ // waiting for another worker thread to create it
+ BatchUpdateException e1 = new BatchUpdateException();
+ e1.initCause(e);
+ throw e1;
}
}
-
- // Add the groups and their parent associations in depth-first order
- final Map groupsToCreate = groupAnalyzer.getGroupsToCreate();
- BatchProcessor>> groupCreator = new BatchProcessor>>(
- zone + " Group Creation and Association",
- this.retryingTransactionHelper,
- sortedGroupAssociations.entrySet(),
- this.workerThreads, 20,
- this.applicationEventPublisher,
- ChainingUserRegistrySynchronizer.logger,
- this.loggingInterval);
- groupCreator.process(new BatchProcessWorker>>()
+ Set parentsToDelete = this.groupAssocsToDelete.get(authorityName);
+ if (parentsToDelete != null && !parentsToDelete.isEmpty())
{
- public String getIdentifier(Map.Entry> entry)
+ for (String parent : parentsToDelete)
{
- return entry.getKey() + " " + entry.getValue();
- }
-
- public void beforeProcess() throws Throwable
- {
- // Disable rules
- ruleService.disableRules();
- // Authentication
- AuthenticationUtil.setRunAsUser(AuthenticationUtil.getSystemUserName());
- }
-
- public void afterProcess() throws Throwable
- {
- // Enable rules
- ruleService.enableRules();
- // Clear authentication
- AuthenticationUtil.clearCurrentSecurityContext();
- }
-
- public void process(Map.Entry> entry) throws Throwable
- {
- Set parents = entry.getValue();
- String child = entry.getKey();
-
- String groupDisplayName = groupsToCreate.get(child);
- if (groupDisplayName != null)
+ if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled())
{
- String groupShortName = ChainingUserRegistrySynchronizer.this.authorityService
- .getShortName(child);
- if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled())
- {
- ChainingUserRegistrySynchronizer.logger
- .debug("Creating group '" + groupShortName + "'");
- }
- // create the group
- ChainingUserRegistrySynchronizer.this.authorityService.createAuthority(AuthorityType
- .getAuthorityType(child), groupShortName, groupDisplayName, zoneSet);
- }
- if (!parents.isEmpty())
- {
- if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled())
- {
- for (String groupName : parents)
- {
- ChainingUserRegistrySynchronizer.logger.debug("Adding '"
+ ChainingUserRegistrySynchronizer.logger
+ .debug("Removing '"
+ ChainingUserRegistrySynchronizer.this.authorityService
- .getShortName(child)
- + "' to group '"
- + ChainingUserRegistrySynchronizer.this.authorityService
- .getShortName(groupName) + "'");
- }
- }
- try
- {
- ChainingUserRegistrySynchronizer.this.authorityService.addAuthority(parents, child);
- }
- catch (UnknownAuthorityException e)
- {
- // Let's force a transaction retry if a parent doesn't exist. It may be because we are
- // waiting for another worker thread to create it
- throw new BatchUpdateException().initCause(e);
- }
- }
- Set parentsToDelete = groupAssocsToDelete.get(child);
- if (parentsToDelete != null && !parentsToDelete.isEmpty())
- {
- for (String parent : parentsToDelete)
- {
- if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled())
- {
- ChainingUserRegistrySynchronizer.logger.debug("Removing '"
- + ChainingUserRegistrySynchronizer.this.authorityService
- .getShortName(child)
+ .getShortName(authorityName)
+ "' from group '"
+ ChainingUserRegistrySynchronizer.this.authorityService
.getShortName(parent) + "'");
- }
- ChainingUserRegistrySynchronizer.this.authorityService.removeAuthority(parent, child);
- }
}
+ ChainingUserRegistrySynchronizer.this.authorityService.removeAuthority(parent, authorityName);
}
- }, splitTxns);
+ }
+
+ // Remember that this authority's associations have been maintained
+ synchronized (this)
+ {
+ this.authoritiesMaintained.add(authorityName);
+ }
}
}
+ final Analyzer groupAnalyzer = new Analyzer(lastModifiedMillis);
+ int groupProcessedCount = groupProcessor.process(groupAnalyzer, splitTxns);
+
+ groupAnalyzer.processGroups(userRegistry, allowDeletions, splitTxns);
+
// Process persons and their parent associations
lastModifiedMillis = forceUpdate ? -1 : getMostRecentUpdateTime(
@@ -971,7 +1023,8 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
public void process(NodeDescription person) throws Throwable
{
- PropertyMap personProperties = person.getProperties();
+ // Make a mutable copy of the person properties, since they get written back to by person service
+ HashMap personProperties = new HashMap(person.getProperties());
String personName = (String) personProperties.get(ContentModel.PROP_USERNAME);
Set zones = ChainingUserRegistrySynchronizer.this.authorityService
.getAuthorityZones(personName);
@@ -1037,41 +1090,9 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
ChainingUserRegistrySynchronizer.this.personService.createPerson(personProperties, zoneSet);
}
}
+
// Maintain associations
- Set parents = groupAssocsToCreate.get(personName);
- if (parents != null && !parents.isEmpty())
- {
- if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled())
- {
- for (String groupName : parents)
- {
- ChainingUserRegistrySynchronizer.logger.debug("Adding '"
- + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(personName)
- + "' to group '"
- + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName)
- + "'");
- }
- }
- ChainingUserRegistrySynchronizer.this.authorityService.addAuthority(parents, personName);
- }
- Set parentsToDelete = groupAssocsToDelete.get(personName);
- if (parentsToDelete != null && !parentsToDelete.isEmpty())
- {
- for (String parent : parentsToDelete)
- {
- if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled())
- {
- ChainingUserRegistrySynchronizer.logger
- .debug("Removing '"
- + ChainingUserRegistrySynchronizer.this.authorityService
- .getShortName(personName)
- + "' from group '"
- + ChainingUserRegistrySynchronizer.this.authorityService
- .getShortName(parent) + "'");
- }
- ChainingUserRegistrySynchronizer.this.authorityService.removeAuthority(parent, personName);
- }
- }
+ groupAnalyzer.maintainAssociations(personName);
synchronized (this)
{
@@ -1087,6 +1108,9 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
PersonWorker persons = new PersonWorker(lastModifiedMillis);
int personProcessedCount = personProcessor.process(persons, splitTxns);
+
+ // Process those associations to persons who themselves have not been updated
+ groupAnalyzer.finalizeAssociations(userRegistry, splitTxns);
// Only now that the whole tree has been processed is it safe to persist the last modified dates
long latestTime = groupAnalyzer.getLatestTime();
@@ -1108,7 +1132,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl
BatchProcessor authorityDeletionProcessor = new BatchProcessor(
zone + " Authority Deletion",
this.retryingTransactionHelper,
- deletionCandidates,
+ groupAnalyzer.getDeletionCandidates(),
this.workerThreads, 10,
this.applicationEventPublisher,
ChainingUserRegistrySynchronizer.logger,
diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java
index 82c48a7d85..d7c8fff111 100644
--- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java
+++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java
@@ -27,6 +27,7 @@ import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
@@ -44,6 +45,7 @@ import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.security.PersonService;
+import org.alfresco.service.namespace.QName;
import org.alfresco.util.GUID;
import org.alfresco.util.PropertyMap;
import org.springframework.context.ApplicationContext;
@@ -155,7 +157,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
newGroup("G1")
}), new MockUserRegistry("Z1", new NodeDescription[]
{
- newPerson("U1"), newPerson("U2")
+ newPerson("U1"), newPerson("U2"), newPerson("U7")
}, new NodeDescription[]
{
newGroup("G2", "U1", "G3"), newGroup("G3", "U2", "G4", "G5"), newGroup("G4"), newGroup("G5")
@@ -215,6 +217,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
assertNotExists("U4");
assertNotExists("U5");
assertNotExists("U6");
+ assertNotExists("U7");
assertNotExists("G1");
assertNotExists("G2");
assertNotExists("G3");
@@ -247,19 +250,21 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
public void testDifferentialUpdate() throws Exception
{
setUpTestUsersAndGroups();
- this.applicationContextManager.setUserRegistries(new MockUserRegistry("Z1", new NodeDescription[]
+ this.applicationContextManager.removeZone("Z0");
+ this.applicationContextManager.updateZone("Z1", new NodeDescription[]
{
- newPerson("U1", "changeofemail@alfresco.com"), newPerson("U6")
+ newPerson("U1", "changeofemail@alfresco.com"), newPerson("U6"), newPerson("U7")
}, new NodeDescription[]
{
- newGroup("G1", "U1", "U6"), newGroup("G2", "U1"), newGroupWithDisplayName("G5", "Amazing Group", "U6")
- }), new MockUserRegistry("Z2", new NodeDescription[]
+ newGroup("G1", "U1", "U6", "UDangling"), newGroup("G2", "U1", "GDangling"), newGroupWithDisplayName("G5", "Amazing Group", "U6", "U7", "G4")
+ });
+ this.applicationContextManager.updateZone("Z2", new NodeDescription[]
{
newPerson("U1", "shouldbeignored@alfresco.com"), newPerson("U5", "u5email@alfresco.com"), newPerson("U6")
}, new NodeDescription[]
{
newGroup("G2", "U1", "U3", "U4", "U6"), newGroup("G7")
- }));
+ });
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback
-
+
@@ -38,7 +38,20 @@
sys:base
-
+
+
+
+
+
+ sys:reference
+
+
+ d:long
+
+
+ d:noderef
+
+
@@ -109,9 +122,9 @@
Created
d:datetime
true
-
- true
- false
+
+ true
+ false
both
@@ -124,9 +137,9 @@
Modified
d:datetime
true
-
- true
- false
+
+ true
+ false
both
@@ -139,15 +152,26 @@
Accessed
d:datetime
true
-
- true
- false
+
+ true
+ false
both
+
+
+
+
+
+ ver2:versionedAssoc
+
+
+
+
+