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() { @@ -272,11 +277,12 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase assertEmailEquals("U1", "changeofemail@alfresco.com"); assertExists("Z1", "U2"); assertExists("Z1", "U6"); + assertExists("Z1", "U7"); assertExists("Z1", "G1", "U1", "U6"); assertExists("Z1", "G2", "U1"); assertExists("Z1", "G3", "U2", "G4", "G5"); assertExists("Z1", "G4"); - assertExists("Z1", "G5", "U6"); + assertExists("Z1", "G5", "U6", "U7", "G4"); assertGroupDisplayNameEquals("G5", "Amazing Group"); assertExists("Z2", "U3"); assertExists("Z2", "U4"); @@ -326,7 +332,8 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase newPerson("U1", "somenewemail@alfresco.com"), newPerson("U3"), newPerson("U6") }, new NodeDescription[] { - newGroup("G2", "U1", "U3", "U4", "U6"), newGroup("G6", "U3", "U4", "G7"), newGroupWithDisplayName("G7", "Late Arrival", "U4", "U5") + newGroup("G2", "U1", "U3", "U4", "U6"), newGroup("G6", "U3", "U4", "G7"), + newGroupWithDisplayName("G7", "Late Arrival", "U4", "U5") })); this.synchronizer.synchronize(true, true, true); this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() @@ -438,7 +445,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase group.setLastModified(new Date()); return group; } - + /** * Constructs a description of a test person with default email (userName@alfresco.com) * @@ -602,6 +609,64 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase this.groups = groups; } + /** + * Modifies the state to match the arguments. Compares new with old and records new modification dates only for + * changes. + * + * @param persons + * the persons + * @param groups + * the groups + */ + public void updateState(Collection persons, Collection groups) + { + List newPersons = new ArrayList(this.persons); + mergeNodeDescriptions(newPersons, persons, ContentModel.PROP_USERNAME); + this.persons = newPersons; + + List newGroups = new ArrayList(this.groups); + mergeNodeDescriptions(newGroups, groups, ContentModel.PROP_AUTHORITY_NAME); + this.groups = newGroups; + } + + /** + * Merges together an old and new list of node descriptions. Retains the old node with its old modification date + * if it is the same in the new list, otherwises uses the node from the new list. + * + * @param oldNodes + * the old node list + * @param newNodes + * the new node list + * @param idProp + * the name of the ID property + */ + private void mergeNodeDescriptions(List oldNodes, Collection newNodes, + QName idProp) + { + Map nodeMap = new LinkedHashMap(newNodes.size() * 2); + for (NodeDescription node : newNodes) + { + nodeMap.put((String) node.getProperties().get(idProp), node); + } + for (int i = 0; i < oldNodes.size(); i++) + { + NodeDescription oldNode = oldNodes.get(i); + String id = (String) oldNode.getProperties().get(idProp); + NodeDescription newNode = nodeMap.remove(id); + if (newNode == null) + { + oldNodes.remove(i); + i--; + } + else if (!oldNode.getProperties().equals(newNode.getProperties()) + || !oldNode.getChildAssociations().equals(newNode.getChildAssociations())) + { + oldNodes.set(i, newNode); + } + } + oldNodes.addAll(nodeMap.values()); + } + /** * Instantiates a new mock user registry. * @@ -649,7 +714,35 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase */ public Collection getGroups(Date modifiedSince) { - return this.groups; + return filterNodeDescriptions(this.groups, modifiedSince); + } + + /** + * Filters the given list of node descriptions, retaining only those with a modification date greater than the + * given date. + * + * @param nodes + * the list of nodes + * @param modifiedSince + * the modified date + * @return the filter list of nodes + */ + private Collection filterNodeDescriptions(Collection nodes, Date modifiedSince) + { + if (modifiedSince == null) + { + return nodes; + } + List filteredNodes = new LinkedList(); + for (NodeDescription node : nodes) + { + Date modified = node.getLastModified(); + if (modifiedSince.compareTo(modified) < 0) + { + filteredNodes.add(node); + } + } + return filteredNodes; } /* @@ -658,7 +751,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase */ public Collection getPersons(Date modifiedSince) { - return this.persons; + return filterNodeDescriptions(this.persons, modifiedSince); } } @@ -688,6 +781,34 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase } } + /** + * Removes the application context for the given zone ID (simulating a change in the authentication chain). + * + * @param zoneId + * the zone id + */ + public void removeZone(String zoneId) + { + this.contexts.remove(zoneId); + } + + /** + * Updates the state of the given zone ID, oopying in new modification dates only where changes have been made. + * + * @param zoneId + * the zone id + * @param persons + * the new list of persons + * @param groups + * the new list of groups + */ + public void updateZone(String zoneId, NodeDescription[] persons, NodeDescription[] groups) + { + ApplicationContext context = this.contexts.get(zoneId); + MockUserRegistry registry = (MockUserRegistry) context.getBean("userRegistry"); + registry.updateState(Arrays.asList(persons), Arrays.asList(groups)); + } + /* * (non-Javadoc) * @see @@ -850,7 +971,8 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase } NodeDescription group = newGroup("G" + GUID.generate(), authorityNames); // Make this group a candidate for adding to other groups - RandomGroupCollection.this.authorities.add((String) group.getProperties().get(ContentModel.PROP_AUTHORITY_NAME)); + RandomGroupCollection.this.authorities.add((String) group.getProperties().get( + ContentModel.PROP_AUTHORITY_NAME)); return group; } diff --git a/source/java/org/alfresco/repo/version/Node2ServiceImpl.java b/source/java/org/alfresco/repo/version/Node2ServiceImpl.java index 2779e01405..df219816f4 100644 --- a/source/java/org/alfresco/repo/version/Node2ServiceImpl.java +++ b/source/java/org/alfresco/repo/version/Node2ServiceImpl.java @@ -36,7 +36,6 @@ import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QNamePattern; - /** * The version2 store node service implementation */ @@ -46,8 +45,8 @@ public class Node2ServiceImpl extends NodeServiceImpl implements NodeService, Ve * The name of the spoofed root association */ private static final QName rootAssocName = QName.createQName(Version2Model.NAMESPACE_URI, "versionedState"); - - + + /** * Type translation for version store */ @@ -58,12 +57,12 @@ public class Node2ServiceImpl extends NodeServiceImpl implements NodeService, Ve return super.getType(nodeRef); } - // frozen node type -> replaced by actual node type of the version node - return (QName)this.dbNodeService.getType(VersionUtil.convertNodeRef(nodeRef)); + // frozen node type -> replaced by actual node type of the version node + return (QName)this.dbNodeService.getType(VersionUtil.convertNodeRef(nodeRef)); } - + /** - * Translation for version store + * Aspects translation for version store */ public Set getAspects(NodeRef nodeRef) throws InvalidNodeRefException { @@ -76,9 +75,9 @@ public class Node2ServiceImpl extends NodeServiceImpl implements NodeService, Ve aspects.remove(Version2Model.ASPECT_VERSION); return aspects; } - + /** - * Property translation for version store + * Properties translation for version store */ public Map getProperties(NodeRef nodeRef) throws InvalidNodeRefException { @@ -87,12 +86,12 @@ public class Node2ServiceImpl extends NodeServiceImpl implements NodeService, Ve return super.getProperties(nodeRef); } - Map props = dbNodeService.getProperties(VersionUtil.convertNodeRef(nodeRef)); - VersionUtil.convertFrozenToOriginalProps(props); - - return props; + Map props = dbNodeService.getProperties(VersionUtil.convertNodeRef(nodeRef)); + VersionUtil.convertFrozenToOriginalProps(props); + + return props; } - + /** * Property translation for version store */ @@ -107,9 +106,9 @@ public class Node2ServiceImpl extends NodeServiceImpl implements NodeService, Ve Map properties = getProperties(VersionUtil.convertNodeRef(nodeRef)); return properties.get(qname); } - + /** - * The node will apprear to be attached to the root of the version store + * The node will appear to be attached to the root of the version store * * @see NodeService#getParentAssocs(NodeRef, QNamePattern, QNamePattern) */ @@ -131,9 +130,9 @@ public class Node2ServiceImpl extends NodeServiceImpl implements NodeService, Ve } return result; } - + /** - * Performs conversion from version store properties to real associations + * Child Assocs translation for version store */ public List getChildAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern, QNamePattern qnamePattern) throws InvalidNodeRefException { @@ -141,35 +140,44 @@ public class Node2ServiceImpl extends NodeServiceImpl implements NodeService, Ve { return super.getChildAssocs(nodeRef, typeQNamePattern, qnamePattern); } - - // Get the child assocs from the version store + + // Get the child assoc references from the version store List childAssocRefs = this.dbNodeService.getChildAssocs( VersionUtil.convertNodeRef(nodeRef), typeQNamePattern, qnamePattern); + List result = new ArrayList(childAssocRefs.size()); + for (ChildAssociationRef childAssocRef : childAssocRefs) { - // Get the child reference - NodeRef childRef = childAssocRef.getChildRef(); - NodeRef referencedNode = (NodeRef)this.dbNodeService.getProperty(childRef, ContentModel.PROP_REFERENCE); - - // Build a child assoc ref to add to the returned list - ChildAssociationRef newChildAssocRef = new ChildAssociationRef( - childAssocRef.getTypeQName(), - childAssocRef.getParentRef(), - childAssocRef.getQName(), - referencedNode, - childAssocRef.isPrimary(), - childAssocRef.getNthSibling()); - result.add(newChildAssocRef); + if (! childAssocRef.getTypeQName().equals(Version2Model.CHILD_QNAME_VERSIONED_ASSOCS)) + { + // Get the child reference + NodeRef childRef = childAssocRef.getChildRef(); + NodeRef referencedNode = (NodeRef)this.dbNodeService.getProperty(childRef, ContentModel.PROP_REFERENCE); + + if (this.dbNodeService.exists(referencedNode)) + { + // Build a child assoc ref to add to the returned list + ChildAssociationRef newChildAssocRef = new ChildAssociationRef( + childAssocRef.getTypeQName(), + childAssocRef.getParentRef(), + childAssocRef.getQName(), + referencedNode, + childAssocRef.isPrimary(), + childAssocRef.getNthSibling()); + + result.add(newChildAssocRef); + } + } } - + // sort the results so that the order appears to be exactly as it was originally Collections.sort(result); - + return result; } - + /** * Simulates the node begin attached to the root node of the version store. */ @@ -186,9 +194,11 @@ public class Node2ServiceImpl extends NodeServiceImpl implements NodeService, Ve rootAssocName, nodeRef); } - + /** - * @return an empty list - ALWAYS. + * Assocs translation for version store + * + * @since 3.3 (Ent) */ @Override public List getTargetAssocs(NodeRef sourceRef, QNamePattern qnamePattern) @@ -197,7 +207,35 @@ public class Node2ServiceImpl extends NodeServiceImpl implements NodeService, Ve { return super.getTargetAssocs(sourceRef, qnamePattern); } - // TODO: Persist these so they are retrievable - return Collections.emptyList(); + + // Get the assoc references from the version store + List childAssocRefs = this.dbNodeService.getChildAssocs( + VersionUtil.convertNodeRef(sourceRef), + Version2Model.CHILD_QNAME_VERSIONED_ASSOCS, qnamePattern); + + List result = new ArrayList(childAssocRefs.size()); + + for (ChildAssociationRef childAssocRef : childAssocRefs) + { + // Get the assoc reference + NodeRef childRef = childAssocRef.getChildRef(); + NodeRef referencedNode = (NodeRef)this.dbNodeService.getProperty(childRef, ContentModel.PROP_REFERENCE); + + if (this.dbNodeService.exists(referencedNode)) + { + Long assocDbId = (Long)this.dbNodeService.getProperty(childRef, Version2Model.PROP_QNAME_ASSOC_DBID); + + // Build an assoc ref to add to the returned list + AssociationRef newAssocRef = new AssociationRef( + assocDbId, + sourceRef, + childAssocRef.getQName(), + referencedNode); + + result.add(newAssocRef); + } + } + + return result; } } diff --git a/source/java/org/alfresco/repo/version/NodeServiceImplTest.java b/source/java/org/alfresco/repo/version/NodeServiceImplTest.java index f7fb458efb..0e919f8983 100644 --- a/source/java/org/alfresco/repo/version/NodeServiceImplTest.java +++ b/source/java/org/alfresco/repo/version/NodeServiceImplTest.java @@ -46,53 +46,53 @@ public class NodeServiceImplTest extends BaseVersionStoreTest { private static Log logger = LogFactory.getLog(NodeServiceImplTest.class); - /** - * Light weight version store node service - */ - protected NodeService lightWeightVersionStoreNodeService = null; - - /** - * Error message - */ - private final static String MSG_ERR = + /** + * version store node service + */ + protected NodeService versionStoreNodeService = null; + + /** + * Error message + */ + private final static String MSG_ERR = "This operation is not supported by a version store implementation of the node service."; - - /** - * Dummy data used in failure tests - */ - private NodeRef dummyNodeRef = null; - private QName dummyQName = null; - - /** + + /** + * Dummy data used in failure tests + */ + private NodeRef dummyNodeRef = null; + private QName dummyQName = null; + + /** * Called during the transaction setup */ protected void onSetUpInTransaction() throws Exception { - super.onSetUpInTransaction(); - + super.onSetUpInTransaction(); + // Get the node service by name - this.lightWeightVersionStoreNodeService = (NodeService)this.applicationContext.getBean("versionNodeService"); + this.versionStoreNodeService = (NodeService)this.applicationContext.getBean("versionNodeService"); // Create some dummy data used during the tests this.dummyNodeRef = new NodeRef( - this.versionService.getVersionStoreReference(), - "dummy"); - this.dummyQName = QName.createQName("{dummy}dummy"); + this.versionService.getVersionStoreReference(), + "dummy"); + this.dummyQName = QName.createQName("{dummy}dummy"); } - + /** * Test getType */ - public void testGetType() + public void testGetType() { // Create a new versionable node NodeRef versionableNode = createNewVersionableNode(); // Create a new version Version version = createVersion(versionableNode, this.versionProperties); - + // Get the type from the versioned state - QName versionedType = this.lightWeightVersionStoreNodeService.getType(version.getFrozenStateNodeRef()); + QName versionedType = this.versionStoreNodeService.getType(version.getFrozenStateNodeRef()); assertNotNull(versionedType); assertEquals(this.dbNodeService.getType(versionableNode), versionedType); } @@ -112,7 +112,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest Version version = createVersion(versionableNode, this.versionProperties); // Get the properties of the versioned state - Map versionedProperties = this.lightWeightVersionStoreNodeService.getProperties(version.getFrozenStateNodeRef()); + Map versionedProperties = this.versionStoreNodeService.getProperties(version.getFrozenStateNodeRef()); if (logger.isDebugEnabled()) { @@ -147,7 +147,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest Version version = createVersion(versionableNode, this.versionProperties); // Check the property values can be retrieved - Serializable value1 = this.lightWeightVersionStoreNodeService.getProperty( + Serializable value1 = this.versionStoreNodeService.getProperty( version.getFrozenStateNodeRef(), PROP_1); assertEquals(VALUE_1, value1); @@ -156,7 +156,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest // TODO // Check the multi values property specifically - Collection multiValue = (Collection)this.lightWeightVersionStoreNodeService.getProperty(version.getFrozenStateNodeRef(), MULTI_PROP); + Collection multiValue = (Collection)this.versionStoreNodeService.getProperty(version.getFrozenStateNodeRef(), MULTI_PROP); assertNotNull(multiValue); assertEquals(2, multiValue.size()); String[] array = multiValue.toArray(new String[multiValue.size()]); @@ -203,7 +203,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest } // Get the children of the versioned node - Collection versionedChildren = this.lightWeightVersionStoreNodeService.getChildAssocs(version.getFrozenStateNodeRef()); + Collection versionedChildren = this.versionStoreNodeService.getChildAssocs(version.getFrozenStateNodeRef()); assertNotNull(versionedChildren); assertEquals(originalChildren.size(), versionedChildren.size()); @@ -226,42 +226,25 @@ public class NodeServiceImplTest extends BaseVersionStoreTest /** * Test getAssociationTargets - * - * @deprecated */ public void testGetAssociationTargets() { - // Switch VersionStore depending on configured impl - if (versionService.getVersionStoreReference().getIdentifier().equals(Version2Model.STORE_ID)) - { - // V2 version store (eg. workspace://version2Store) - // See ETHREEOH-3632: method now doesn't blow up. - List assocs = this.lightWeightVersionStoreNodeService.getTargetAssocs( - dummyNodeRef, - RegexQNamePattern.MATCH_ALL); - assertEquals("Currently the assocs will be empty", 0, assocs.size()); - } - else if (versionService.getVersionStoreReference().getIdentifier().equals(VersionModel.STORE_ID)) - { - // Deprecated V1 version store (eg. workspace://lightWeightVersionStore) - - // Create a new versionable node - NodeRef versionableNode = createNewVersionableNode(); - - // Store the current details of the target associations - List origAssocs = this.dbNodeService.getTargetAssocs( - versionableNode, - RegexQNamePattern.MATCH_ALL); - - // Create a new version - Version version = createVersion(versionableNode, this.versionProperties); - - List assocs = this.lightWeightVersionStoreNodeService.getTargetAssocs( - version.getFrozenStateNodeRef(), - RegexQNamePattern.MATCH_ALL); - assertNotNull(assocs); - assertEquals(origAssocs.size(), assocs.size()); - } + // Create a new versionable node + NodeRef versionableNode = createNewVersionableNode(); + + // Store the current details of the target associations + List origAssocs = this.dbNodeService.getTargetAssocs( + versionableNode, + RegexQNamePattern.MATCH_ALL); + + // Create a new version + Version version = createVersion(versionableNode, this.versionProperties); + + List assocs = this.versionStoreNodeService.getTargetAssocs( + version.getFrozenStateNodeRef(), + RegexQNamePattern.MATCH_ALL); + assertNotNull(assocs); + assertEquals(origAssocs.size(), assocs.size()); } /** @@ -275,12 +258,12 @@ public class NodeServiceImplTest extends BaseVersionStoreTest // Create a new version Version version = createVersion(versionableNode, this.versionProperties); - boolean test1 = this.lightWeightVersionStoreNodeService.hasAspect( + boolean test1 = this.versionStoreNodeService.hasAspect( version.getFrozenStateNodeRef(), ApplicationModel.ASPECT_UIFACETS); assertFalse(test1); - boolean test2 = this.lightWeightVersionStoreNodeService.hasAspect( + boolean test2 = this.versionStoreNodeService.hasAspect( version.getFrozenStateNodeRef(), ContentModel.ASPECT_VERSIONABLE); assertTrue(test2); @@ -298,7 +281,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest // Create a new version Version version = createVersion(versionableNode, this.versionProperties); - Set aspects = this.lightWeightVersionStoreNodeService.getAspects(version.getFrozenStateNodeRef()); + Set aspects = this.versionStoreNodeService.getAspects(version.getFrozenStateNodeRef()); assertEquals(origAspects.size(), aspects.size()); for (QName origAspect : origAspects) @@ -319,7 +302,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest Version version = createVersion(versionableNode, this.versionProperties); NodeRef nodeRef = version.getFrozenStateNodeRef(); - List results = this.lightWeightVersionStoreNodeService.getParentAssocs(nodeRef); + List results = this.versionStoreNodeService.getParentAssocs(nodeRef); assertNotNull(results); assertEquals(1, results.size()); ChildAssociationRef childAssoc = results.get(0); @@ -340,7 +323,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest Version version = createVersion(versionableNode, this.versionProperties); NodeRef nodeRef = version.getFrozenStateNodeRef(); - ChildAssociationRef childAssoc = this.lightWeightVersionStoreNodeService.getPrimaryParent(nodeRef); + ChildAssociationRef childAssoc = this.versionStoreNodeService.getPrimaryParent(nodeRef); assertNotNull(childAssoc); assertEquals(nodeRef, childAssoc.getChildRef()); NodeRef versionStoreRoot = this.dbNodeService.getRootNode(this.versionService.getVersionStoreReference()); @@ -359,7 +342,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest { try { - this.lightWeightVersionStoreNodeService.createNode( + this.versionStoreNodeService.createNode( dummyNodeRef, null, dummyQName, @@ -382,7 +365,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest { try { - this.lightWeightVersionStoreNodeService.addAspect( + this.versionStoreNodeService.addAspect( dummyNodeRef, TEST_ASPECT_QNAME, null); @@ -404,7 +387,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest { try { - this.lightWeightVersionStoreNodeService.removeAspect( + this.versionStoreNodeService.removeAspect( dummyNodeRef, TEST_ASPECT_QNAME); fail("This operation is not supported."); @@ -425,7 +408,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest { try { - this.lightWeightVersionStoreNodeService.deleteNode(this.dummyNodeRef); + this.versionStoreNodeService.deleteNode(this.dummyNodeRef); fail("This operation is not supported."); } catch (UnsupportedOperationException exception) @@ -444,7 +427,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest { try { - this.lightWeightVersionStoreNodeService.addChild( + this.versionStoreNodeService.addChild( this.dummyNodeRef, this.dummyNodeRef, this.dummyQName, @@ -467,7 +450,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest { try { - this.lightWeightVersionStoreNodeService.removeChild( + this.versionStoreNodeService.removeChild( this.dummyNodeRef, this.dummyNodeRef); fail("This operation is not supported."); @@ -488,7 +471,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest { try { - this.lightWeightVersionStoreNodeService.setProperties( + this.versionStoreNodeService.setProperties( this.dummyNodeRef, new HashMap()); fail("This operation is not supported."); @@ -509,7 +492,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest { try { - this.lightWeightVersionStoreNodeService.setProperty( + this.versionStoreNodeService.setProperty( this.dummyNodeRef, this.dummyQName, "dummy"); @@ -531,7 +514,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest { try { - this.lightWeightVersionStoreNodeService.createAssociation( + this.versionStoreNodeService.createAssociation( this.dummyNodeRef, this.dummyNodeRef, this.dummyQName); @@ -553,7 +536,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest { try { - this.lightWeightVersionStoreNodeService.removeAssociation( + this.versionStoreNodeService.removeAssociation( this.dummyNodeRef, this.dummyNodeRef, this.dummyQName); @@ -575,7 +558,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest { try { - this.lightWeightVersionStoreNodeService.getSourceAssocs( + this.versionStoreNodeService.getSourceAssocs( this.dummyNodeRef, this.dummyQName); fail("This operation is not supported."); @@ -594,7 +577,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest */ public void testGetPath() { - Path path = this.lightWeightVersionStoreNodeService.getPath(this.dummyNodeRef); + Path path = this.versionStoreNodeService.getPath(this.dummyNodeRef); } /** @@ -602,6 +585,6 @@ public class NodeServiceImplTest extends BaseVersionStoreTest */ public void testGetPaths() { - List paths = this.lightWeightVersionStoreNodeService.getPaths(this.dummyNodeRef, false); + List paths = this.versionStoreNodeService.getPaths(this.dummyNodeRef, false); } } diff --git a/source/java/org/alfresco/repo/version/Version2Model.java b/source/java/org/alfresco/repo/version/Version2Model.java index 92327c4db5..6eef8101e4 100644 --- a/source/java/org/alfresco/repo/version/Version2Model.java +++ b/source/java/org/alfresco/repo/version/Version2Model.java @@ -29,7 +29,7 @@ public interface Version2Model extends VersionBaseModel * Namespace */ public static final String NAMESPACE_URI = "http://www.alfresco.org/model/versionstore/2.0"; - + /** * The store id */ @@ -37,18 +37,18 @@ public interface Version2Model extends VersionBaseModel /** The version store root aspect */ public static final QName ASPECT_VERSION_STORE_ROOT = QName.createQName(NAMESPACE_URI, ASPECT_LOCALNAME_VERSION_STORE_ROOT); - + /** * Version history type */ public static final QName TYPE_QNAME_VERSION_HISTORY = QName.createQName(NAMESPACE_URI, TYPE_VERSION_HISTORY); - + /** * Version history properties and associations */ public static final QName PROP_QNAME_VERSIONED_NODE_ID = QName.createQName(NAMESPACE_URI, PROP_VERSIONED_NODE_ID); public static final QName ASSOC_ROOT_VERSION = QName.createQName(NAMESPACE_URI, ASSOC_LOCALNAME_ROOT_VERSION); - + /** * Version aspect + aspect properties */ @@ -65,7 +65,7 @@ public interface Version2Model extends VersionBaseModel public static final QName PROP_QNAME_VERSION_NUMBER = QName.createQName(NAMESPACE_URI, PROP_VERSION_NUMBER); public static final QName PROP_QNAME_VERSION_DESCRIPTION = QName.createQName(NAMESPACE_URI, PROP_VERSION_DESCRIPTION); - + // frozen sys:referenceable properties (x4) public static final String PROP_FROZEN_NODE_REF = "frozenNodeRef"; @@ -75,7 +75,7 @@ public interface Version2Model extends VersionBaseModel public static final QName PROP_QNAME_FROZEN_NODE_DBID = QName.createQName(NAMESPACE_URI, PROP_FROZEN_NODE_DBID); // frozen cm:auditable properties (x5) - + public static final String PROP_FROZEN_CREATOR = "frozenCreator"; public static final QName PROP_QNAME_FROZEN_CREATOR = QName.createQName(NAMESPACE_URI, PROP_FROZEN_CREATOR); @@ -91,10 +91,9 @@ public interface Version2Model extends VersionBaseModel public static final String PROP_FROZEN_ACCESSED = "frozenAccessed"; public static final QName PROP_QNAME_FROZEN_ACCESSED = QName.createQName(NAMESPACE_URI, PROP_FROZEN_ACCESSED); - public static final QName ASSOC_SUCCESSOR = QName.createQName(NAMESPACE_URI, "successor"); - + public static final String PROP_METADATA_PREFIX = "metadata-"; public static final String PROP_VERSION_TYPE = "versionType"; @@ -104,7 +103,15 @@ public interface Version2Model extends VersionBaseModel */ public static final QName CHILD_QNAME_VERSION_HISTORIES = QName.createQName(NAMESPACE_URI, CHILD_VERSION_HISTORIES); public static final QName CHILD_QNAME_VERSIONS = QName.createQName(NAMESPACE_URI, CHILD_VERSIONS); + public static final QName CHILD_QNAME_VERSIONED_ASSOCS = QName.createQName(NAMESPACE_URI, CHILD_VERSIONED_ASSOCS); + /** + * Versioned assoc type & properties + */ + public static final QName TYPE_QNAME_VERSIONED_ASSOC = QName.createQName(NAMESPACE_URI, TYPE_VERSIONED_ASSOC); + + public static final String PROP_ASSOC_DBID = "assocDbId"; + public static final QName PROP_QNAME_ASSOC_DBID = QName.createQName(NAMESPACE_URI, PROP_ASSOC_DBID); // Used by ML service diff --git a/source/java/org/alfresco/repo/version/Version2ServiceImpl.java b/source/java/org/alfresco/repo/version/Version2ServiceImpl.java index dbd3c85a04..4c1a532db0 100644 --- a/source/java/org/alfresco/repo/version/Version2ServiceImpl.java +++ b/source/java/org/alfresco/repo/version/Version2ServiceImpl.java @@ -38,6 +38,7 @@ import org.alfresco.repo.version.common.VersionImpl; import org.alfresco.repo.version.common.VersionUtil; import org.alfresco.repo.version.common.VersionHistoryImpl.VersionComparatorAsc; import org.alfresco.service.cmr.repository.AspectMissingException; +import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; @@ -452,15 +453,15 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe PermissionService.ALL_PERMISSIONS, true); } - // add aspect with the standard version properties to the 'version' node - nodeService.addAspect(versionNodeRef, Version2Model.ASPECT_VERSION, standardVersionProperties); - + // add aspect with the standard version properties to the 'version' node + nodeService.addAspect(versionNodeRef, Version2Model.ASPECT_VERSION, standardVersionProperties); + // store the meta data storeVersionMetaData(versionNodeRef, versionProperties); freezeChildAssociations(versionNodeRef, nodeDetails.getChildAssociations()); + freezeAssociations(versionNodeRef, nodeDetails.getAssociations()); freezeAspects(nodeDetails, versionNodeRef, nodeDetails.getAspects()); - } finally { @@ -557,19 +558,27 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe } } + /** + * Freeze child associations + * + * @param versionNodeRef the version node reference + * @param childAssociations the child associations + */ private void freezeChildAssociations(NodeRef versionNodeRef, List childAssociations) { for (ChildAssociationRef childAssocRef : childAssociations) { HashMap properties = new HashMap(); - - QName sourceTypeRef = nodeService.getType(childAssocRef.getChildRef()); + + NodeRef childRef = childAssocRef.getChildRef(); + + QName sourceTypeRef = nodeService.getType(childRef); // Set the reference property to point to the child node - properties.put(ContentModel.PROP_REFERENCE, childAssocRef.getChildRef()); - + properties.put(ContentModel.PROP_REFERENCE, childRef); + // Create child version reference - this.dbNodeService.createNode( + dbNodeService.createNode( versionNodeRef, childAssocRef.getTypeQName(), childAssocRef.getQName(), @@ -577,6 +586,38 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe properties); } } + + /** + * Freeze associations + * + * @param versionNodeRef the version node reference + * @param associations the list of associations + * + * @since 3.3 (Ent) + */ + private void freezeAssociations(NodeRef versionNodeRef, List associations) + { + for (AssociationRef targetAssocRef : associations) + { + HashMap properties = new HashMap(); + + QName sourceTypeRef = nodeService.getType(targetAssocRef.getSourceRef()); + + NodeRef targetRef = targetAssocRef.getTargetRef(); + + // Set the reference property to point to the target node + properties.put(ContentModel.PROP_REFERENCE, targetRef); + properties.put(Version2Model.PROP_QNAME_ASSOC_DBID, targetAssocRef.getId()); + + // Create peer version reference + dbNodeService.createNode( + versionNodeRef, + Version2Model.CHILD_QNAME_VERSIONED_ASSOCS, + targetAssocRef.getTypeQName(), + sourceTypeRef, + properties); + } + } /** * Builds a version history object from the version history reference. @@ -891,7 +932,7 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe { if (this.nodeService.exists(versionedChild.getChildRef()) == true) { - // The node was a primary child of the parent, but that is no longer the case. Dispite this + // The node was a primary child of the parent, but that is no longer the case. Despite this // the node still exits so this means it has been moved. // The best thing to do in this situation will be to re-add the node as a child, but it will not // be a primary child. @@ -901,7 +942,7 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe { if (versionedChild.isPrimary() == true) { - // Only try to resotre missing children if we are doing a deep revert + // Only try to restore missing children if we are doing a deep revert // Look and see if we have a version history for the child node if (deep == true && getVersionHistoryNodeRef(versionedChild.getChildRef()) != null) { @@ -915,7 +956,7 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe // else the deleted child did not have a version history so we can't restore the child // and so we can't revert the association } - + // else // Since this was never a primary assoc and the child has been deleted we won't recreate // the missing node as it was never owned by the node and we wouldn't know where to put it. @@ -930,6 +971,22 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe { this.nodeService.removeChild(nodeRef, ref.getChildRef()); } + + // Add/remove the target associations + for (AssociationRef assocRef : this.nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL)) + { + this.nodeService.removeAssociation(assocRef.getSourceRef(), assocRef.getTargetRef(), assocRef.getTypeQName()); + } + for (AssociationRef versionedAssoc : this.nodeService.getTargetAssocs(versionNodeRef, RegexQNamePattern.MATCH_ALL)) + { + if (this.nodeService.exists(versionedAssoc.getTargetRef()) == true) + { + this.nodeService.createAssociation(nodeRef, versionedAssoc.getTargetRef(), versionedAssoc.getTypeQName()); + } + + // else + // Since the target of the assoc no longer exists we can't recreate the assoc + } } finally { diff --git a/source/java/org/alfresco/repo/version/VersionBaseModel.java b/source/java/org/alfresco/repo/version/VersionBaseModel.java index c85b4d5554..66054147c0 100644 --- a/source/java/org/alfresco/repo/version/VersionBaseModel.java +++ b/source/java/org/alfresco/repo/version/VersionBaseModel.java @@ -24,6 +24,8 @@ import org.alfresco.service.cmr.version.VersionService; /** * Base Version Model interface containing the common local names (and other constants) * used by the lightWeightVersionStore and version2Store implementations + * + * @author Roy Wetherall, janv */ public interface VersionBaseModel { @@ -53,6 +55,7 @@ public interface VersionBaseModel * Version history type */ public static final String TYPE_VERSION_HISTORY = "versionHistory"; + public static final String TYPE_VERSIONED_ASSOC = "versionedAssoc"; /** * Version history properties and associations @@ -66,6 +69,7 @@ public interface VersionBaseModel */ public static final String CHILD_VERSION_HISTORIES = "versionHistory"; public static final String CHILD_VERSIONS = "version"; + public static final String CHILD_VERSIONED_ASSOCS = "versionedAssocs"; // Used by ML service diff --git a/source/java/org/alfresco/repo/version/VersionMigrator.java b/source/java/org/alfresco/repo/version/VersionMigrator.java index f951e25c58..22ff80e81e 100644 --- a/source/java/org/alfresco/repo/version/VersionMigrator.java +++ b/source/java/org/alfresco/repo/version/VersionMigrator.java @@ -27,7 +27,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.springframework.extensions.surf.util.I18NUtil; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.hibernate.SessionSizeResourceManager; import org.alfresco.repo.node.MLPropertyInterceptor; @@ -39,6 +38,7 @@ import org.alfresco.repo.version.common.VersionUtil; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -46,9 +46,11 @@ import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.version.Version; import org.alfresco.service.cmr.version.VersionHistory; import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.service.transaction.TransactionService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; /** * Version2 Migrator @@ -189,6 +191,8 @@ public class VersionMigrator QName sourceType = versionNodeService.getType(frozenStateNodeRef); Set nodeAspects = versionNodeService.getAspects(frozenStateNodeRef); Map nodeProperties = versionNodeService.getProperties(frozenStateNodeRef); + List nodeChildAssocs = versionNodeService.getChildAssocs(frozenStateNodeRef); + List nodeAssocs = versionNodeService.getTargetAssocs(frozenStateNodeRef, RegexQNamePattern.MATCH_ALL); long nodeDbId = (Long)nodeProperties.get(ContentModel.PROP_NODE_DBID); @@ -198,13 +202,14 @@ public class VersionMigrator int versionNumber = 0; // get oldVersion auditable properties (of the version node itself, rather than the live versioned node) - Date versionCreated = (Date)dbNodeService.getProperty(VersionUtil.convertNodeRef(frozenStateNodeRef), ContentModel.PROP_CREATED); - String versionCreator = (String)dbNodeService.getProperty(VersionUtil.convertNodeRef(frozenStateNodeRef), ContentModel.PROP_CREATOR); - Date versionModified = (Date)dbNodeService.getProperty(VersionUtil.convertNodeRef(frozenStateNodeRef), ContentModel.PROP_MODIFIED); - String versionModifier = (String)dbNodeService.getProperty(VersionUtil.convertNodeRef(frozenStateNodeRef), ContentModel.PROP_MODIFIER); - Date versionAccessed = (Date)dbNodeService.getProperty(VersionUtil.convertNodeRef(frozenStateNodeRef), ContentModel.PROP_ACCESSED); + NodeRef versionNode = VersionUtil.convertNodeRef(frozenStateNodeRef); + Date versionCreated = (Date)dbNodeService.getProperty(versionNode, ContentModel.PROP_CREATED); + String versionCreator = (String)dbNodeService.getProperty(versionNode, ContentModel.PROP_CREATOR); + Date versionModified = (Date)dbNodeService.getProperty(versionNode, ContentModel.PROP_MODIFIED); + String versionModifier = (String)dbNodeService.getProperty(versionNode, ContentModel.PROP_MODIFIER); + Date versionAccessed = (Date)dbNodeService.getProperty(versionNode, ContentModel.PROP_ACCESSED); - Map versionMetaDataProperties = version1Service.getVersionMetaData(VersionUtil.convertNodeRef(frozenStateNodeRef)); + Map versionMetaDataProperties = version1Service.getVersionMetaData(versionNode); // Create the node details PolicyScope nodeDetails = new PolicyScope(sourceType); @@ -222,7 +227,7 @@ public class VersionMigrator nodeDetails.addAspect(aspect); // copy the aspect properties - ClassDefinition classDefinition = dictionaryService.getClass(aspect); + ClassDefinition classDefinition = dictionaryService.getClass(aspect); if (classDefinition != null) { Map propertyDefinitions = classDefinition.getProperties(); @@ -234,6 +239,18 @@ public class VersionMigrator } } + // add child assocs (since 3.3 Ent - applies only to direct upgrade from 2.x to 3.3 Ent or higher) + for (ChildAssociationRef childAssoc : nodeChildAssocs) + { + nodeDetails.addChildAssociation(sourceType, childAssoc); + } + + // add target assocs (since 3.3 Ent - applies only to direct upgrade from 2.x to 3.3 Ent or higher) + for (AssociationRef assoc : nodeAssocs) + { + nodeDetails.addAssociation(sourceType, assoc); + } + NodeRef newVersionRef = version2Service.createNewVersion( sourceType, newVersionHistoryRef, diff --git a/source/java/org/alfresco/repo/version/VersionMigratorTest.java b/source/java/org/alfresco/repo/version/VersionMigratorTest.java index dd2ef87792..78804ad8c3 100644 --- a/source/java/org/alfresco/repo/version/VersionMigratorTest.java +++ b/source/java/org/alfresco/repo/version/VersionMigratorTest.java @@ -21,6 +21,7 @@ package org.alfresco.repo.version; import java.io.Serializable; import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -31,6 +32,8 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -38,6 +41,8 @@ import org.alfresco.service.cmr.version.Version; import org.alfresco.service.cmr.version.VersionHistory; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.EqualsHelper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -511,5 +516,110 @@ public class VersionMigratorTest extends BaseVersionStoreTest } logger.info("testMigrateOneCheckoutVersion: Migrated from oldVHNodeRef = " + oldVHNodeRef + " to newVHNodeRef = " + newVHNodeRef); - } + } + + /** + * Test migration of a single versioned node with versioned child assocs & peer assocs + * + * @since 3.3 Ent - applies only to direct upgrade from 2.x to 3.3 Ent or higher + */ + public void testMigrateVersionWithAssocs() throws Exception + { + if (version2Service.useDeprecatedV1 == true) + { + logger.info("testMigrateVersionWithAssocs: skip"); + return; + } + + NodeRef versionableNode = createNewVersionableNode(); + NodeRef targetNode = createNewNode(); + + nodeService.createAssociation(versionableNode, targetNode, TEST_ASSOC); + + // Get the next version label and snapshot the date-time + String nextVersionLabel1 = peekNextVersionLabel(versionableNode, versionProperties); + + // Snap-shot the node created date-time + long beforeVersionTime1 = ((Date)nodeService.getProperty(versionableNode, ContentModel.PROP_CREATED)).getTime(); + + Version version1 = createVersion(versionableNode); + logger.info(version1); + + VersionHistory vh1 = version1Service.getVersionHistory(versionableNode); + assertEquals(1, vh1.getAllVersions().size()); + + List oldChildAssocs = nodeService.getChildAssocs(version1.getFrozenStateNodeRef()); + List oldAssocs = nodeService.getTargetAssocs(version1.getFrozenStateNodeRef(), RegexQNamePattern.MATCH_ALL); + + logger.info("testMigrateVersionWithAssocs: versionedNodeRef = " + versionableNode); + + NodeRef oldVHNodeRef = version1Service.getVersionHistoryNodeRef(versionableNode); + + // Migrate and delete old version history ! + NodeRef versionedNodeRef = versionMigrator.v1GetVersionedNodeRef(oldVHNodeRef); + NodeRef newVHNodeRef = versionMigrator.migrateVersionHistory(oldVHNodeRef, versionedNodeRef); + versionMigrator.v1DeleteVersionHistory(oldVHNodeRef); + + VersionHistory vh2 = version2Service.getVersionHistory(versionableNode); + assertEquals(1, vh2.getAllVersions().size()); + + // check new version - switch to new version service to do the check + super.setVersionService(version2Service); + + Version[] newVersions = vh2.getAllVersions().toArray(new Version[]{}); + + Version newVersion1 = newVersions[0]; + + checkVersion(beforeVersionTime1, nextVersionLabel1, newVersion1, versionableNode); + + List newChildAssocs = nodeService.getChildAssocs(newVersion1.getFrozenStateNodeRef()); + assertEquals(oldChildAssocs.size(), newChildAssocs.size()); + for (ChildAssociationRef oldChildAssoc : oldChildAssocs) + { + boolean found = false; + for (ChildAssociationRef newChildAssoc : newChildAssocs) + { + if (newChildAssoc.getParentRef().getId().equals(oldChildAssoc.getParentRef().getId()) && + newChildAssoc.getChildRef().equals(oldChildAssoc.getChildRef()) && + newChildAssoc.getTypeQName().equals(oldChildAssoc.getTypeQName()) && + newChildAssoc.getQName().equals(oldChildAssoc.getQName()) && + (newChildAssoc.isPrimary() == oldChildAssoc.isPrimary()) && + (newChildAssoc.getNthSibling() == oldChildAssoc.getNthSibling())) + { + found = true; + break; + } + } + + if (! found) + { + fail(oldChildAssoc.toString()+ " not found"); + } + } + + List newAssocs = nodeService.getTargetAssocs(newVersion1.getFrozenStateNodeRef(), RegexQNamePattern.MATCH_ALL); + assertEquals(oldAssocs.size(), newAssocs.size()); + for (AssociationRef oldAssoc : oldAssocs) + { + boolean found = false; + for (AssociationRef newAssoc : newAssocs) + { + if (newAssoc.getSourceRef().getId().equals(oldAssoc.getSourceRef().getId()) && + newAssoc.getTargetRef().equals(oldAssoc.getTargetRef()) && + newAssoc.getTypeQName().equals(oldAssoc.getTypeQName()) && + EqualsHelper.nullSafeEquals(newAssoc.getId(), oldAssoc.getId())) + { + found = true; + break; + } + } + + if (! found) + { + fail(oldAssoc.toString()+ " not found"); + } + } + + logger.info("testMigrateVersionWithAssocs: Migrated from oldVHNodeRef = " + oldVHNodeRef + " to newVHNodeRef = " + newVHNodeRef); + } } diff --git a/source/java/org/alfresco/repo/version/VersionModel.java b/source/java/org/alfresco/repo/version/VersionModel.java index 797a2a8a0d..6e1141f6ee 100644 --- a/source/java/org/alfresco/repo/version/VersionModel.java +++ b/source/java/org/alfresco/repo/version/VersionModel.java @@ -23,7 +23,7 @@ import org.alfresco.service.namespace.QName; /** * Version1 Model Constants used by lightWeightVersionStore implementation * - * @author Roy Wetherall + * @author Roy Wetherall, janv * * NOTE: deprecated since 3.1 (migrate and useVersion2 Model) */ @@ -168,7 +168,6 @@ public interface VersionModel extends VersionBaseModel /** * Versioned assoc type */ - public static final String TYPE_VERSIONED_ASSOC = "versionedAssoc"; public static final QName TYPE_QNAME_VERSIONED_ASSOC = QName.createQName(NAMESPACE_URI, TYPE_VERSIONED_ASSOC); /** @@ -176,7 +175,6 @@ public interface VersionModel extends VersionBaseModel */ public static final String CHILD_VERSIONED_ATTRIBUTES = "versionedAttributes"; public static final String CHILD_VERSIONED_CHILD_ASSOCS = "versionedChildAssocs"; - public static final String CHILD_VERSIONED_ASSOCS = "versionedAssocs"; public static final String CHILD_VERSION_META_DATA = "versionMetaData"; public static final QName CHILD_QNAME_VERSION_HISTORIES = QName.createQName(NAMESPACE_URI, CHILD_VERSION_HISTORIES); diff --git a/source/java/org/alfresco/repo/version/VersionTestSuite.java b/source/java/org/alfresco/repo/version/VersionTestSuite.java index 584e0571ad..b579c93cfc 100644 --- a/source/java/org/alfresco/repo/version/VersionTestSuite.java +++ b/source/java/org/alfresco/repo/version/VersionTestSuite.java @@ -24,14 +24,25 @@ import junit.framework.TestSuite; import org.alfresco.repo.version.common.VersionHistoryImplTest; import org.alfresco.repo.version.common.VersionImplTest; import org.alfresco.repo.version.common.versionlabel.SerialVersionLabelPolicyTest; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; /** * Version test suite * - * @author Roy Wetherall + * @author Roy Wetherall, janv */ public class VersionTestSuite extends TestSuite { + public static ApplicationContext getContext() + { + ApplicationContextHelper.setUseLazyLoading(false); + ApplicationContextHelper.setNoAutoStart(true); + return ApplicationContextHelper.getApplicationContext( + new String[] { "classpath:alfresco/minimal-context.xml" } + ); + } + /** * Creates the test suite * @@ -39,6 +50,9 @@ public class VersionTestSuite extends TestSuite */ public static Test suite() { + // Setup the context + getContext(); + TestSuite suite = new TestSuite(); suite.addTestSuite(VersionImplTest.class); suite.addTestSuite(VersionHistoryImplTest.class); diff --git a/source/java/org/alfresco/repo/version/VersionableAspect.java b/source/java/org/alfresco/repo/version/VersionableAspect.java index b3702883e2..1a3851b22c 100644 --- a/source/java/org/alfresco/repo/version/VersionableAspect.java +++ b/source/java/org/alfresco/repo/version/VersionableAspect.java @@ -75,6 +75,9 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate /** The Version service */ private VersionService versionService; + /** Behaviours */ + JavaBehaviour onUpdatePropertiesBehaviour; + /** * Optional list of excluded props * - only applies if cm:autoVersionOnUpdateProps=true (and cm:autoVersion=true) @@ -147,10 +150,11 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate ContentModel.ASPECT_VERSIONABLE, new JavaBehaviour(this, "onContentUpdate", Behaviour.NotificationFrequency.TRANSACTION_COMMIT)); + onUpdatePropertiesBehaviour = new JavaBehaviour(this, "onUpdateProperties", Behaviour.NotificationFrequency.TRANSACTION_COMMIT); this.policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"), ContentModel.ASPECT_VERSIONABLE, - new JavaBehaviour(this, "onUpdateProperties", Behaviour.NotificationFrequency.TRANSACTION_COMMIT)); + onUpdatePropertiesBehaviour); this.policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), @@ -322,75 +326,89 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TEMPORARY) == false) && (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE) == false)) { - Map versionedNodeRefs = (Map)AlfrescoTransactionSupport.getResource(KEY_VERSIONED_NODEREFS); - if (versionedNodeRefs == null || versionedNodeRefs.containsKey(nodeRef) == false) + onUpdatePropertiesBehaviour.disable(); + try + { + Map versionedNodeRefs = (Map)AlfrescoTransactionSupport.getResource(KEY_VERSIONED_NODEREFS); + if (versionedNodeRefs == null || versionedNodeRefs.containsKey(nodeRef) == false) + { + // Determine whether the node is auto versionable (for property only updates) or not + boolean autoVersion = false; + Boolean value = (Boolean)this.nodeService.getProperty(nodeRef, ContentModel.PROP_AUTO_VERSION); + if (value != null) + { + // If the value is not null then + autoVersion = value.booleanValue(); + } + + boolean autoVersionProps = false; + value = (Boolean)this.nodeService.getProperty(nodeRef, ContentModel.PROP_AUTO_VERSION_PROPS); + if (value != null) + { + // If the value is not null then + autoVersionProps = value.booleanValue(); + } + + if ((autoVersion == true) && (autoVersionProps == true)) + { + // Check for explicitly excluded props - if one or more excluded props changes then do not auto-version on this event (even if other props changed) + if (excludedOnUpdateProps.size() > 0) + { + Map propNames = new HashMap(after.size()); + for (QName afterProp : after.keySet()) + { + if (excludedOnUpdateProps.contains(afterProp.getPrefixString())) + { + propNames.put(afterProp.getPrefixString(), afterProp); + } + } + for (QName beforeProp : before.keySet()) + { + if (excludedOnUpdateProps.contains(beforeProp.getPrefixString())) + { + propNames.put(beforeProp.getPrefixString(), beforeProp); + } + } + + if (propNames.size() > 0) + { + for (QName prop : propNames.values()) + { + Serializable beforeValue = before.get(prop); + Serializable afterValue = after.get(prop); + + if (EqualsHelper.nullSafeEquals(beforeValue, afterValue) != true) + { + // excluded - do not version + return; + } + } + } + + // drop through and auto-version + } + + // Create the auto-version + Map versionProperties = new HashMap(1); + versionProperties.put(Version.PROP_DESCRIPTION, I18NUtil.getMessage(MSG_AUTO_VERSION_PROPS)); + + createVersionImpl(nodeRef, versionProperties); + } + } + } + finally { - // Determine whether the node is auto versionable (for property only updates) or not - boolean autoVersion = false; - Boolean value = (Boolean)this.nodeService.getProperty(nodeRef, ContentModel.PROP_AUTO_VERSION); - if (value != null) - { - // If the value is not null then - autoVersion = value.booleanValue(); - } - - boolean autoVersionProps = false; - value = (Boolean)this.nodeService.getProperty(nodeRef, ContentModel.PROP_AUTO_VERSION_PROPS); - if (value != null) - { - // If the value is not null then - autoVersionProps = value.booleanValue(); - } - - if ((autoVersion == true) && (autoVersionProps == true)) - { - // Check for explicitly excluded props - if one or more excluded props changes then do not auto-version on this event (even if other props changed) - if (excludedOnUpdateProps.size() > 0) - { - Map propNames = new HashMap(after.size()); - for (QName afterProp : after.keySet()) - { - if (excludedOnUpdateProps.contains(afterProp.getPrefixString())) - { - propNames.put(afterProp.getPrefixString(), afterProp); - } - } - for (QName beforeProp : before.keySet()) - { - if (excludedOnUpdateProps.contains(beforeProp.getPrefixString())) - { - propNames.put(beforeProp.getPrefixString(), beforeProp); - } - } - - if (propNames.size() > 0) - { - for (QName prop : propNames.values()) - { - Serializable beforeValue = before.get(prop); - Serializable afterValue = after.get(prop); - - if (EqualsHelper.nullSafeEquals(beforeValue, afterValue) != true) - { - // excluded - do not version - return; - } - } - } - - // drop through and auto-version - } - - // Create the auto-version - Map versionProperties = new HashMap(1); - versionProperties.put(Version.PROP_DESCRIPTION, I18NUtil.getMessage(MSG_AUTO_VERSION_PROPS)); - - createVersionImpl(nodeRef, versionProperties); - } + onUpdatePropertiesBehaviour.enable(); } } } + /** + * On create version implementation method + * + * @param nodeRef + * @param versionProperties + */ private void createVersionImpl(NodeRef nodeRef, Map versionProperties) { recordCreateVersion(nodeRef, null); diff --git a/source/java/org/alfresco/repo/version/version2_model.xml b/source/java/org/alfresco/repo/version/version2_model.xml index b5aebf5fb4..cb4a27a6a8 100644 --- a/source/java/org/alfresco/repo/version/version2_model.xml +++ b/source/java/org/alfresco/repo/version/version2_model.xml @@ -2,8 +2,8 @@ Alfresco Version2 Store Model Alfresco - 2008-07-18 - 2.0 + 2010-04-29 + 2.1 @@ -14,7 +14,7 @@ - + @@ -26,7 +26,7 @@ d:text - + @@ -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 + + + + +