diff --git a/config/alfresco/node-services-context.xml b/config/alfresco/node-services-context.xml index 90ce0c9dcc..714bbdeecb 100644 --- a/config/alfresco/node-services-context.xml +++ b/config/alfresco/node-services-context.xml @@ -84,7 +84,7 @@ - archive://SpacesStore + ${spaces.archive.store} @@ -228,11 +228,19 @@ - + ${system.cascadeDeleteInTransaction} + + + + + + ${spaces.archive.store} + + diff --git a/source/java/org/alfresco/repo/admin/RepoAdminServiceImplTest.java b/source/java/org/alfresco/repo/admin/RepoAdminServiceImplTest.java index 1758285dcf..f35f3ddea2 100644 --- a/source/java/org/alfresco/repo/admin/RepoAdminServiceImplTest.java +++ b/source/java/org/alfresco/repo/admin/RepoAdminServiceImplTest.java @@ -27,18 +27,31 @@ package org.alfresco.repo.admin; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.PrintWriter; +import java.io.Serializable; import java.io.StringWriter; import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import junit.framework.TestCase; import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.cmr.admin.RepoAdminService; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +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.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; @@ -61,6 +74,11 @@ public class RepoAdminServiceImplTest extends TestCase private DictionaryService dictionaryService; private TransactionService transactionService; + private NodeService nodeService; + private ContentService contentService; + private SearchService searchService; + private NamespaceService namespaceService; + final String modelPrefix = "model-"; final static String MKR = "{MKR}"; @@ -107,6 +125,11 @@ public class RepoAdminServiceImplTest extends TestCase dictionaryService = (DictionaryService) ctx.getBean("DictionaryService"); transactionService = (TransactionService) ctx.getBean("TransactionService"); + nodeService = (NodeService) ctx.getBean("NodeService"); + contentService = (ContentService) ctx.getBean("ContentService"); + searchService = (SearchService) ctx.getBean("SearchService"); + namespaceService = (NamespaceService) ctx.getBean("NamespaceService"); + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); } @@ -125,9 +148,140 @@ public class RepoAdminServiceImplTest extends TestCase // Test custom model management // - public void testSimpleDynamicModel() throws Exception + public void testSimpleDynamicModelViaNodeService() throws Exception { - final int X = 0; + final String X = "A"; + final String modelFileName = modelPrefix+X+".xml"; + final QName typeName = QName.createQName("{http://www.alfresco.org/test/testmodel"+X+"/1.0}base"); + final QName modelName = QName.createQName("{http://www.alfresco.org/test/testmodel"+X+"/1.0}testModel"+X); + + try + { + if (isModelDeployed(modelFileName)) + { + // undeploy model + repoAdminService.undeployModel(modelFileName); + } + + StoreRef storeRef = StoreRef.STORE_REF_WORKSPACE_SPACESSTORE; + NodeRef rootNodeRef = nodeService.getRootNode(storeRef); + + assertNull(dictionaryService.getClass(typeName)); + + final int defaultModelCnt = dictionaryService.getAllModels().size(); + + // deploy custom model + String model = MODEL_MKR_XML.replace(MKR, X+""); + InputStream modelStream = new ByteArrayInputStream(model.getBytes("UTF-8")); + + List nodeRefs = searchService.selectNodes(rootNodeRef, "/app:company_home/app:dictionary/app:models", null, namespaceService, false); + assertEquals(1, nodeRefs.size()); + NodeRef modelsNodeRef = nodeRefs.get(0); + + // create model node + + Map contentProps = new HashMap(); + contentProps.put(ContentModel.PROP_NAME, modelFileName); + + NodeRef model1 = nodeService.createNode( + modelsNodeRef, + ContentModel.ASSOC_CONTAINS, + modelName, + ContentModel.TYPE_DICTIONARY_MODEL, + contentProps).getChildRef(); + + // add titled aspect (for Web Client display) + Map titledProps = new HashMap(); + titledProps.put(ContentModel.PROP_TITLE, modelFileName); + titledProps.put(ContentModel.PROP_DESCRIPTION, modelFileName); + nodeService.addAspect(model1, ContentModel.ASPECT_TITLED, titledProps); + + ContentWriter writer = contentService.getWriter(model1, ContentModel.PROP_CONTENT, true); + + writer.setMimetype(MimetypeMap.MIMETYPE_XML); + writer.setEncoding("UTF-8"); + + writer.putContent(modelStream); // also invokes policies for DictionaryModelType - e.g. onContentUpdate + modelStream.close(); + + // activate the model + nodeService.setProperty(model1, ContentModel.PROP_MODEL_ACTIVE, new Boolean(true)); + + assertEquals(defaultModelCnt+1, dictionaryService.getAllModels().size()); + + ClassDefinition myType = dictionaryService.getClass(typeName); + assertNotNull(myType); + assertEquals(modelName, myType.getModel().getName()); + + // create node with custom type + NodeRef node1 = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("http://www.alfresco.org/model/system/1.0", "node1"), + typeName, + null).getChildRef(); + + // try to delete the model + try + { + nodeService.deleteNode(model1); + fail(); + } + catch (AlfrescoRuntimeException are) + { + // expected + assertTrue(are.getMessage().contains("Failed to validate model delete")); + } + + nodeService.deleteNode(node1); + assertFalse(nodeService.exists(node1)); + + NodeRef archiveRootNode = nodeService.getStoreArchiveNode(storeRef); + NodeRef archiveNode1 = new NodeRef(archiveRootNode.getStoreRef(), node1.getId()); + assertTrue(nodeService.exists(archiveNode1)); + + // try to delete the model + try + { + nodeService.deleteNode(model1); + fail(); + } + catch (AlfrescoRuntimeException are) + { + // expected + assertTrue(are.getMessage().contains("Failed to validate model delete")); + } + + nodeService.deleteNode(archiveNode1); + assertFalse(nodeService.exists(archiveNode1)); + + // delete model + nodeService.deleteNode(model1); + + assertEquals(defaultModelCnt, dictionaryService.getAllModels().size()); + assertNull(dictionaryService.getClass(typeName)); + + NodeRef archiveModel1 = new NodeRef(archiveRootNode.getStoreRef(), model1.getId()); + + // restore model + nodeService.restoreNode(archiveModel1, null, null, null); + + assertEquals(defaultModelCnt+1, dictionaryService.getAllModels().size()); + assertNotNull(dictionaryService.getClass(typeName)); + + // delete for good + nodeService.deleteNode(model1); + nodeService.deleteNode(archiveModel1); + } + finally + { + // NOOP + } + } + + public void testSimpleDynamicModelViaRepoAdminService() throws Exception + { + final String X = "B"; final String modelFileName = modelPrefix+X+".xml"; final QName typeName = QName.createQName("{http://www.alfresco.org/test/testmodel"+X+"/1.0}base"); final QName modelName = QName.createQName("{http://www.alfresco.org/test/testmodel"+X+"/1.0}testModel"+X); @@ -180,6 +334,7 @@ public class RepoAdminServiceImplTest extends TestCase { // expected assertTrue(are.getMessage().contains("Model deactivation failed")); + assertTrue(are.getCause().getMessage().contains("is already deactivated")); } // re-activate model @@ -202,9 +357,57 @@ public class RepoAdminServiceImplTest extends TestCase { // expected assertTrue(are.getMessage().contains("Model activation failed")); + assertTrue(are.getCause().getMessage().contains("is already activated")); } - // undeploy model + StoreRef storeRef = StoreRef.STORE_REF_WORKSPACE_SPACESSTORE; + NodeRef rootNodeRef = nodeService.getRootNode(storeRef); + + // create node with custom type + NodeRef node1 = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("http://www.alfresco.org/model/system/1.0", "node1"), + typeName, + null).getChildRef(); + + // try to undeploy model + try + { + repoAdminService.undeployModel(modelFileName); + fail(); + } + catch (AlfrescoRuntimeException are) + { + // expected + assertTrue(are.getMessage().contains("Model undeployment failed")); + assertTrue(are.getCause().getMessage().contains("Failed to validate model delete")); + } + + nodeService.deleteNode(node1); + assertFalse(nodeService.exists(node1)); + + NodeRef archiveRootNode = nodeService.getStoreArchiveNode(storeRef); + NodeRef archiveNode1 = new NodeRef(archiveRootNode.getStoreRef(), node1.getId()); + assertTrue(nodeService.exists(archiveNode1)); + + // try to undeploy model + try + { + repoAdminService.undeployModel(modelFileName); + fail(); + } + catch (AlfrescoRuntimeException are) + { + // expected + assertTrue(are.getMessage().contains("Model undeployment failed")); + assertTrue(are.getCause().getMessage().contains("Failed to validate model delete")); + } + + nodeService.deleteNode(archiveNode1); + assertFalse(nodeService.exists(archiveNode1)); + + // undeploy repoAdminService.undeployModel(modelFileName); assertFalse(isModelDeployed(modelFileName)); @@ -221,6 +424,7 @@ public class RepoAdminServiceImplTest extends TestCase { // expected assertTrue(are.getMessage().contains("Model undeployment failed")); + assertTrue(are.getCause().getMessage().contains("Could not find custom model")); } } finally diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryBootstrap.java b/source/java/org/alfresco/repo/dictionary/DictionaryBootstrap.java index f9578a4208..c42fb4e6e2 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryBootstrap.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryBootstrap.java @@ -123,6 +123,11 @@ public class DictionaryBootstrap implements DictionaryListener { long startTime = System.currentTimeMillis(); + if (logger.isTraceEnabled()) + { + logger.trace("onDictionaryInit: ["+Thread.currentThread()+"]"); + } + Collection modelsBefore = dictionaryDAO.getModels(); // note: on first bootstrap will init empty dictionary int modelsBeforeCnt = (modelsBefore != null ? modelsBefore.size() : 0); @@ -158,7 +163,7 @@ public class DictionaryBootstrap implements DictionaryListener if (logger.isDebugEnabled()) { - logger.debug("Model count: before="+modelsBeforeCnt+", load="+models.size()+", after="+modelsAfterCnt+" in "+(System.currentTimeMillis()-startTime)+" msecs"); + logger.debug("Model count: before="+modelsBeforeCnt+", load="+models.size()+", after="+modelsAfterCnt+" in "+(System.currentTimeMillis()-startTime)+" msecs ["+Thread.currentThread()+"]"); } } } diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java index 7c8f297fd4..cf6eda34ae 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java @@ -162,8 +162,8 @@ public class DictionaryDAOImpl implements DictionaryDAO } destroy(); - init(); - + init(); + if (logger.isDebugEnabled()) { logger.debug("... resetting dictionary completed"); @@ -175,17 +175,19 @@ public class DictionaryDAOImpl implements DictionaryDAO { long startTime = System.currentTimeMillis(); + if (logger.isDebugEnabled()) + { + logger.debug("Init Dictionary: ["+Thread.currentThread()+"] "+(tenantDomain.equals(TenantService.DEFAULT_DOMAIN) ? "" : " (Tenant: "+tenantDomain+")")); + } + try { return AuthenticationUtil.runAs(new RunAsWork() { public DictionaryRegistry doWork() - { + { try { - // create threadlocal, if needed - createDataDictionaryLocal(tenantDomain); - DictionaryRegistry dictionaryRegistry = initDictionaryRegistry(tenantDomain); if (dictionaryRegistry == null) @@ -221,24 +223,32 @@ public class DictionaryDAOImpl implements DictionaryDAO readLock.unlock(); } } - } + } }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); } finally { if (logger.isInfoEnabled()) { - logger.info("Init Dictionary: model count = "+(getModels() != null ? getModels().size() : 0) +" in "+(System.currentTimeMillis()-startTime)+" msecs "+(tenantDomain.equals(TenantService.DEFAULT_DOMAIN) ? "" : " (Tenant: "+tenantDomain+")")); + logger.info("Init Dictionary: model count = "+(getModels() != null ? getModels().size() : 0) +" in "+(System.currentTimeMillis()-startTime)+" msecs ["+Thread.currentThread()+"] "+(tenantDomain.equals(TenantService.DEFAULT_DOMAIN) ? "" : " (Tenant: "+tenantDomain+")")); } } } private DictionaryRegistry initDictionaryRegistry(String tenantDomain) { - getDictionaryRegistry(tenantDomain).setCompiledModels(new HashMap()); - getDictionaryRegistry(tenantDomain).setUriToModels(new HashMap>()); + // create threadlocal, if needed + DictionaryRegistry dictionaryRegistry = createDataDictionaryLocal(tenantDomain); - // initialise empty dictionary & namespaces + dictionaryRegistry.setCompiledModels(new HashMap()); + dictionaryRegistry.setUriToModels(new HashMap>()); + + if (logger.isTraceEnabled()) + { + logger.trace("Empty dictionary initialised: "+dictionaryRegistry+" - "+defaultDictionaryRegistryThreadLocal+" ["+Thread.currentThread()+"]"); + } + + // initialise empty namespaces namespaceDAO.init(); // populate the dictionary based on registered sources @@ -253,7 +263,7 @@ public class DictionaryDAOImpl implements DictionaryDAO dictionaryListener.afterDictionaryInit(); } - return getDictionaryRegistryLocal(tenantDomain); + return dictionaryRegistry; } /* (non-Javadoc) @@ -1018,7 +1028,7 @@ public class DictionaryDAOImpl implements DictionaryDAO } // create threadlocal - private void createDataDictionaryLocal(String tenantDomain) + private DictionaryRegistry createDataDictionaryLocal(String tenantDomain) { // create threadlocal, if needed DictionaryRegistry dictionaryRegistry = getDictionaryRegistryLocal(tenantDomain); @@ -1035,6 +1045,8 @@ public class DictionaryDAOImpl implements DictionaryDAO dictionaryRegistryThreadLocal.set(dictionaryRegistry); } } + + return dictionaryRegistry; } // get threadlocal diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java b/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java index c425e8cae4..45d6df003c 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java @@ -92,9 +92,6 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda /** Key to the removed "workingcopy" aspect */ private static final String KEY_WORKING_COPY = "dictionaryModelType.workingCopy"; - /** Key to the removed "archived" aspect */ - private static final String KEY_ARCHIVED = "dictionaryModelType.archived"; - /** The dictionary DAO */ private DictionaryDAO dictionaryDAO; @@ -235,37 +232,37 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda // Register interest in the onContentUpdate policy for the dictionary model type policyComponent.bindClassBehaviour( ContentServicePolicies.ON_CONTENT_UPDATE, - ContentModel.TYPE_DICTIONARY_MODEL, + 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"), - ContentModel.TYPE_DICTIONARY_MODEL, + 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"), - ContentModel.TYPE_DICTIONARY_MODEL, + 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"), - ContentModel.TYPE_DICTIONARY_MODEL, + ContentModel.TYPE_DICTIONARY_MODEL, new JavaBehaviour(this, "onDeleteNode")); // Register interest in the onRemoveAspect policy policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onRemoveAspect"), - this, + this, new JavaBehaviour(this, "onRemoveAspect")); // Register interest in the onCreateNode policy policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), - this, + this, new JavaBehaviour(this, "onCreateNode")); // Create the transaction listener @@ -349,12 +346,6 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda { AlfrescoTransactionSupport.bindResource(KEY_WORKING_COPY, nodeRef); } - - // restore removes the "archived" aspect prior to restoring (via delete/move) the node - hence need to track here - if (aspect.equals(ContentModel.ASPECT_ARCHIVED)) - { - AlfrescoTransactionSupport.bindResource(KEY_ARCHIVED, nodeRef); - } } @SuppressWarnings("unchecked") @@ -367,17 +358,10 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda workingCopy = true; } - boolean archived = nodeService.hasAspect(nodeRef, ContentModel.ASPECT_ARCHIVED); - NodeRef aNodeRef = (NodeRef)AlfrescoTransactionSupport.getResource(KEY_ARCHIVED); - if ((aNodeRef != null) && (aNodeRef.equals(nodeRef))) - { - archived = true; - } - boolean isVersionNode = nodeRef.getStoreRef().getIdentifier().equals(Version2Model.STORE_ID); - // Ignore if the node is a working copy, archived or version node - if (! (workingCopy || archived || isVersionNode)) + // Ignore if the node is a working copy or version node + if (! (workingCopy || isVersionNode)) { QName modelName = (QName)this.nodeService.getProperty(nodeRef, ContentModel.PROP_MODEL_NAME); @@ -407,7 +391,7 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda { if (logger.isTraceEnabled()) { - logger.trace("beforeDeleteNode: nodeRef="+nodeRef+ " ignored ("+(workingCopy ? " workingCopy " : "")+(archived ? " archived " : "")+(isVersionNode ? " isVersionNode " : "")+") ["+AlfrescoTransactionSupport.getTransactionId()+"]"); + logger.trace("beforeDeleteNode: nodeRef="+nodeRef+ " ignored ("+(workingCopy ? " workingCopy " : "")+(isVersionNode ? " isVersionNode " : "")+") ["+AlfrescoTransactionSupport.getTransactionId()+"]"); } } } @@ -606,8 +590,8 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda isActive = value.booleanValue(); } - // Ignore if the node is a working copy or archived - if (! (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY) || nodeService.hasAspect(nodeRef, ContentModel.ASPECT_ARCHIVED))) + // Ignore if the node is a working copy + if (! (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY))) { if (isActive == true) { diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java b/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java index ce172e3ae2..73ba849f82 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java @@ -33,7 +33,6 @@ import org.alfresco.repo.i18n.MessageService; import org.alfresco.repo.tenant.TenantAdminService; import org.alfresco.repo.tenant.TenantDeployer; import org.alfresco.repo.tenant.TenantService; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentReader; @@ -211,6 +210,11 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme { long startTime = System.currentTimeMillis(); + if (logger.isTraceEnabled()) + { + logger.trace("onDictionaryInit: ["+Thread.currentThread()+"]"); + } + Collection modelsBefore = dictionaryDAO.getModels(); int modelsBeforeCnt = (modelsBefore != null ? modelsBefore.size() : 0); @@ -220,6 +224,11 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme { Map> modelMap = new HashMap>(); + if (logger.isTraceEnabled()) + { + logger.trace("onDictionaryInit: locations="+this.repositoryModelsLocations); + } + // Register the models found in the repository for (RepositoryLocation repositoryLocation : this.repositoryModelsLocations) @@ -289,14 +298,14 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme if (modelsAfterCnt != (modelsBeforeCnt + loadedModels.size())) { String tenantDomain = tenantAdminService.getCurrentUserDomain(); - logger.warn("Model count: before="+modelsBeforeCnt+", load="+loadedModels.size()+", after="+modelsAfterCnt+" ["+AlfrescoTransactionSupport.getTransactionId()+"] in "+(System.currentTimeMillis()-startTime)+" msecs "+(tenantDomain.equals(TenantService.DEFAULT_DOMAIN) ? "" : " (Tenant: "+tenantDomain+")")); + logger.warn("Model count: before="+modelsBeforeCnt+", load="+loadedModels.size()+", after="+modelsAfterCnt+" in "+(System.currentTimeMillis()-startTime)+" msecs ["+Thread.currentThread()+"] "+(tenantDomain.equals(TenantService.DEFAULT_DOMAIN) ? "" : " (Tenant: "+tenantDomain+")")); } else { if (logger.isDebugEnabled()) { String tenantDomain = tenantAdminService.getCurrentUserDomain(); - logger.debug("Model count: before="+modelsBeforeCnt+", load="+loadedModels.size()+", after="+modelsAfterCnt+" ["+AlfrescoTransactionSupport.getTransactionId()+"] in "+(System.currentTimeMillis()-startTime)+" msecs "+(tenantDomain.equals(TenantService.DEFAULT_DOMAIN) ? "" : " (Tenant: "+tenantDomain+")")); + logger.debug("Model count: before="+modelsBeforeCnt+", load="+loadedModels.size()+", after="+modelsAfterCnt+" in "+(System.currentTimeMillis()-startTime)+" msecs ["+Thread.currentThread()+"] "+(tenantDomain.equals(TenantService.DEFAULT_DOMAIN) ? "" : " (Tenant: "+tenantDomain+")")); } } } diff --git a/source/java/org/alfresco/repo/dictionary/NamespaceDAO.java b/source/java/org/alfresco/repo/dictionary/NamespaceDAO.java index a4ffa75c66..3e6b07c902 100644 --- a/source/java/org/alfresco/repo/dictionary/NamespaceDAO.java +++ b/source/java/org/alfresco/repo/dictionary/NamespaceDAO.java @@ -64,6 +64,8 @@ public interface NamespaceDAO extends NamespacePrefixResolver */ public void init(); + public void afterDictionaryInit(); + /** * Destroy Namespaces */ diff --git a/source/java/org/alfresco/repo/dictionary/NamespaceDAOImpl.java b/source/java/org/alfresco/repo/dictionary/NamespaceDAOImpl.java index c9a4356ba9..27dbfade67 100644 --- a/source/java/org/alfresco/repo/dictionary/NamespaceDAOImpl.java +++ b/source/java/org/alfresco/repo/dictionary/NamespaceDAOImpl.java @@ -38,7 +38,7 @@ import org.apache.commons.logging.LogFactory; /** * Simple in-memory namespace DAO */ -public class NamespaceDAOImpl implements NamespaceDAO +public class NamespaceDAOImpl implements NamespaceDAO, DictionaryListener { private static final Log logger = LogFactory.getLog(NamespaceDAOImpl.class); @@ -74,15 +74,78 @@ public class NamespaceDAOImpl implements NamespaceDAO public void registerDictionary(DictionaryDAO dictionaryDAO) { this.dictionaryDAO = dictionaryDAO; + this.dictionaryDAO.register(this); } + public void afterDictionaryDestroy() + { + // TODO Auto-generated method stub + } + + public void onDictionaryInit() + { + // TODO Auto-generated method stub + } + + /** + * Complete the initialisation + */ + public void afterDictionaryInit() + { + String tenantDomain = getTenantDomain(); + + try + { + NamespaceRegistry namespaceRegistry = getNamespaceRegistryLocal(tenantDomain); + + if (namespaceRegistry == null) + { + // unexpected + throw new AlfrescoRuntimeException("Failed to init namespaceRegistry " + tenantDomain); + } + + try + { + writeLock.lock(); + namespaceRegistryCache.put(tenantDomain, namespaceRegistry); + } + finally + { + writeLock.unlock(); + } + } + finally + { + try + { + readLock.lock(); + if (namespaceRegistryCache.get(tenantDomain) != null) + { + removeNamespaceLocal(tenantDomain); + } + } + finally + { + readLock.unlock(); + } + } + } + + /** * Initialise empty namespaces */ public void init() - { - initNamespace(getTenantDomain()); + { + String tenantDomain = getTenantDomain(); + NamespaceRegistry namespaceRegistry = initNamespaceRegistry(tenantDomain); + + if (namespaceRegistry == null) + { + // unexpected + throw new AlfrescoRuntimeException("Failed to init namespaceRegistry " + tenantDomain); + } } /** @@ -128,60 +191,20 @@ public class NamespaceDAOImpl implements NamespaceDAO return namespaceRegistry; } - private NamespaceRegistry initNamespace(String tenantDomain) - { - try - { - createNamespaceLocal(tenantDomain); - - NamespaceRegistry namespaceRegistry = initNamespaceRegistry(tenantDomain); - - if (namespaceRegistry == null) - { - // unexpected - throw new AlfrescoRuntimeException("Failed to init namespaceRegistry " + tenantDomain); - } - - try - { - writeLock.lock(); - namespaceRegistryCache.put(tenantDomain, namespaceRegistry); - } - finally - { - writeLock.unlock(); - } - - return namespaceRegistry; - } - finally - { - try - { - readLock.lock(); - if (namespaceRegistryCache.get(tenantDomain) != null) - { - removeNamespaceLocal(tenantDomain); - } - } - finally - { - readLock.unlock(); - } - } - } - private NamespaceRegistry initNamespaceRegistry(String tenantDomain) { - getNamespaceRegistry(tenantDomain).setUrisCache(new ArrayList()); - getNamespaceRegistry(tenantDomain).setPrefixesCache(new HashMap()); + // create threadlocal, if needed + NamespaceRegistry namespaceRegistry = createNamespaceLocal(tenantDomain); + + namespaceRegistry.setUrisCache(new ArrayList()); + namespaceRegistry.setPrefixesCache(new HashMap()); if (logger.isTraceEnabled()) { - logger.trace("Empty namespaces initialised"); + logger.trace("Empty namespaces initialised: "+namespaceRegistry+" - "+namespaceRegistryThreadLocal+" ["+Thread.currentThread()+"]"); } - return getNamespaceRegistryLocal(tenantDomain); + return namespaceRegistry; } @@ -242,7 +265,7 @@ public class NamespaceDAOImpl implements NamespaceDAO // overridden, hence skip this default prefix continue; } - prefixesFiltered.add(prefix); + prefixesFiltered.add(prefix); } // default (non-overridden) + tenant-specific @@ -427,7 +450,7 @@ public class NamespaceDAOImpl implements NamespaceDAO } // create threadlocal - private void createNamespaceLocal(String tenantDomain) + private NamespaceRegistry createNamespaceLocal(String tenantDomain) { // create threadlocal, if needed NamespaceRegistry namespaceRegistry = getNamespaceRegistryLocal(tenantDomain); @@ -435,17 +458,17 @@ public class NamespaceDAOImpl implements NamespaceDAO { namespaceRegistry = new NamespaceRegistry(tenantDomain); - if (! tenantDomain.equals(TenantService.DEFAULT_DOMAIN)) + if (tenantDomain.equals(TenantService.DEFAULT_DOMAIN)) + { + defaultNamespaceRegistryThreadLocal.set(namespaceRegistry); + } + else { namespaceRegistryThreadLocal.set(namespaceRegistry); } - - if (defaultNamespaceRegistryThreadLocal.get() == null) - { - namespaceRegistry = new NamespaceRegistry(TenantService.DEFAULT_DOMAIN); - defaultNamespaceRegistryThreadLocal.set(namespaceRegistry); - } } + + return namespaceRegistry; } // get threadlocal @@ -466,7 +489,7 @@ public class NamespaceDAOImpl implements NamespaceDAO if ((namespaceRegistry != null) && (tenantDomain.equals(namespaceRegistry.getTenantDomain()))) { return namespaceRegistry; // return threadlocal, if set - } + } return null; } diff --git a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java index 4fad0bc583..0eb0d66420 100644 --- a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java @@ -19,6 +19,7 @@ package org.alfresco.repo.node; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -52,6 +53,7 @@ import org.alfresco.repo.policy.AssociationPolicyDelegate; import org.alfresco.repo.policy.ClassPolicyDelegate; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.search.Indexer; +import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; @@ -95,6 +97,8 @@ public abstract class AbstractNodeServiceImpl implements NodeService private PolicyComponent policyComponent; protected DictionaryService dictionaryService; protected TransactionService transactionService; + protected TenantService tenantService; + protected List storesToIgnorePolicies = new ArrayList(0); /* * Policy delegates @@ -144,6 +148,16 @@ public abstract class AbstractNodeServiceImpl implements NodeService { this.transactionService = transactionService; } + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + public void setStoresToIgnorePolicies(List storesToIgnorePolicies) + { + this.storesToIgnorePolicies = storesToIgnorePolicies; + } /** * Checks equality by type and uuid @@ -203,13 +217,28 @@ public abstract class AbstractNodeServiceImpl implements NodeService onCreateAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.OnCreateAssociationPolicy.class); onDeleteAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.OnDeleteAssociationPolicy.class); } - + + private boolean ignorePolicy(StoreRef storeRef) + { + return (storesToIgnorePolicies.contains(tenantService.getBaseName(storeRef).toString())); + } + + private boolean ignorePolicy(NodeRef nodeRef) + { + return (storesToIgnorePolicies.contains(tenantService.getBaseName(nodeRef.getStoreRef()).toString())); + } + /** * @see NodeServicePolicies.BeforeCreateStorePolicy#beforeCreateStore(QName, * StoreRef) */ protected void invokeBeforeCreateStore(QName nodeTypeQName, StoreRef storeRef) { + if (ignorePolicy(storeRef)) + { + return; + } + NodeServicePolicies.BeforeCreateStorePolicy policy = this.beforeCreateStoreDelegate.get(nodeTypeQName); policy.beforeCreateStore(nodeTypeQName, storeRef); } @@ -219,6 +248,11 @@ public abstract class AbstractNodeServiceImpl implements NodeService */ protected void invokeOnCreateStore(NodeRef rootNodeRef) { + if (ignorePolicy(rootNodeRef)) + { + return; + } + // get qnames to invoke against Set qnames = getTypeAndAspectQNames(rootNodeRef); // execute policy for node type and aspects @@ -232,6 +266,11 @@ public abstract class AbstractNodeServiceImpl implements NodeService */ protected void invokeBeforeCreateNode(NodeRef parentNodeRef, QName assocTypeQName, QName assocQName, QName childNodeTypeQName) { + if (ignorePolicy(parentNodeRef)) + { + return; + } + // execute policy for node type NodeServicePolicies.BeforeCreateNodePolicy policy = beforeCreateNodeDelegate.get(childNodeTypeQName); policy.beforeCreateNode(parentNodeRef, assocTypeQName, assocQName, childNodeTypeQName); @@ -243,6 +282,12 @@ public abstract class AbstractNodeServiceImpl implements NodeService protected void invokeOnCreateNode(ChildAssociationRef childAssocRef) { NodeRef childNodeRef = childAssocRef.getChildRef(); + + if (ignorePolicy(childNodeRef)) + { + return; + } + // get qnames to invoke against Set qnames = getTypeAndAspectQNames(childNodeRef); // execute policy for node type and aspects @@ -256,6 +301,12 @@ public abstract class AbstractNodeServiceImpl implements NodeService protected void invokeOnMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef) { NodeRef childNodeRef = newChildAssocRef.getChildRef(); + + if (ignorePolicy(childNodeRef)) + { + return; + } + // get qnames to invoke against Set qnames = getTypeAndAspectQNames(childNodeRef); // execute policy for node type and aspects @@ -268,6 +319,11 @@ public abstract class AbstractNodeServiceImpl implements NodeService */ protected void invokeBeforeUpdateNode(NodeRef nodeRef) { + if (ignorePolicy(nodeRef)) + { + return; + } + // get qnames to invoke against Set qnames = getTypeAndAspectQNames(nodeRef); // execute policy for node type and aspects @@ -280,6 +336,11 @@ public abstract class AbstractNodeServiceImpl implements NodeService */ protected void invokeOnUpdateNode(NodeRef nodeRef) { + if (ignorePolicy(nodeRef)) + { + return; + } + // get qnames to invoke against Set qnames = getTypeAndAspectQNames(nodeRef); // execute policy for node type and aspects @@ -295,6 +356,10 @@ public abstract class AbstractNodeServiceImpl implements NodeService Map before, Map after) { + if (ignorePolicy(nodeRef)) + { + return; + } // Some logging so we can see which properties have been modified if (logger.isDebugEnabled() == true) @@ -344,6 +409,11 @@ public abstract class AbstractNodeServiceImpl implements NodeService */ protected void invokeBeforeDeleteNode(NodeRef nodeRef) { + if (ignorePolicy(nodeRef)) + { + return; + } + // get qnames to invoke against Set qnames = getTypeAndAspectQNames(nodeRef); // execute policy for node type and aspects @@ -356,14 +426,30 @@ public abstract class AbstractNodeServiceImpl implements NodeService */ protected void invokeOnDeleteNode(ChildAssociationRef childAssocRef, QName childNodeTypeQName, Set childAspectQnames, boolean isArchivedNode) { - // get qnames to invoke against - Set qnames = new HashSet(childAspectQnames.size() + 1); - qnames.addAll(childAspectQnames); - qnames.add(childNodeTypeQName); + NodeRef childNodeRef = childAssocRef.getChildRef(); - // execute policy for node type and aspects - NodeServicePolicies.OnDeleteNodePolicy policy = onDeleteNodeDelegate.get(childAssocRef.getChildRef(), qnames); - policy.onDeleteNode(childAssocRef, isArchivedNode); + Set qnames = null; + + if (ignorePolicy(childNodeRef)) + { + // special case + qnames = new HashSet(1); + qnames.add(ContentModel.ASPECT_VERSIONABLE); + } + else + { + // get qnames to invoke against + qnames = new HashSet(childAspectQnames.size() + 1); + qnames.addAll(childAspectQnames); + qnames.add(childNodeTypeQName); + } + + if (qnames != null) + { + // execute policy for node type and aspects + NodeServicePolicies.OnDeleteNodePolicy policy = onDeleteNodeDelegate.get(childAssocRef.getChildRef(), qnames); + policy.onDeleteNode(childAssocRef, isArchivedNode); + } } /** @@ -372,6 +458,11 @@ public abstract class AbstractNodeServiceImpl implements NodeService */ protected void invokeBeforeAddAspect(NodeRef nodeRef, QName aspectTypeQName) { + if (ignorePolicy(nodeRef)) + { + return; + } + NodeServicePolicies.BeforeAddAspectPolicy policy = beforeAddAspectDelegate.get(nodeRef, aspectTypeQName); policy.beforeAddAspect(nodeRef, aspectTypeQName); } @@ -381,6 +472,11 @@ public abstract class AbstractNodeServiceImpl implements NodeService */ protected void invokeOnAddAspect(NodeRef nodeRef, QName aspectTypeQName) { + if (ignorePolicy(nodeRef)) + { + return; + } + NodeServicePolicies.OnAddAspectPolicy policy = onAddAspectDelegate.get(nodeRef, aspectTypeQName); policy.onAddAspect(nodeRef, aspectTypeQName); } @@ -391,6 +487,11 @@ public abstract class AbstractNodeServiceImpl implements NodeService */ protected void invokeBeforeRemoveAspect(NodeRef nodeRef, QName aspectTypeQName) { + if (ignorePolicy(nodeRef)) + { + return; + } + NodeServicePolicies.BeforeRemoveAspectPolicy policy = beforeRemoveAspectDelegate.get(nodeRef, aspectTypeQName); policy.beforeRemoveAspect(nodeRef, aspectTypeQName); } @@ -401,6 +502,11 @@ public abstract class AbstractNodeServiceImpl implements NodeService */ protected void invokeOnRemoveAspect(NodeRef nodeRef, QName aspectTypeQName) { + if (ignorePolicy(nodeRef)) + { + return; + } + NodeServicePolicies.OnRemoveAspectPolicy policy = onRemoveAspectDelegate.get(nodeRef, aspectTypeQName); policy.onRemoveAspect(nodeRef, aspectTypeQName); } @@ -411,6 +517,11 @@ public abstract class AbstractNodeServiceImpl implements NodeService */ protected void invokeBeforeCreateNodeAssociation(NodeRef parentNodeRef, QName assocTypeQName, QName assocQName) { + if (ignorePolicy(parentNodeRef)) + { + return; + } + // get qnames to invoke against Set qnames = getTypeAndAspectQNames(parentNodeRef); // execute policy for node type @@ -425,6 +536,12 @@ public abstract class AbstractNodeServiceImpl implements NodeService { // Get the parent reference and the assoc type qName NodeRef parentNodeRef = childAssocRef.getParentRef(); + + if (ignorePolicy(parentNodeRef)) + { + return; + } + QName assocTypeQName = childAssocRef.getTypeQName(); // get qnames to invoke against Set qnames = getTypeAndAspectQNames(parentNodeRef); @@ -439,6 +556,11 @@ public abstract class AbstractNodeServiceImpl implements NodeService */ protected void invokeBeforeCreateChildAssociation(NodeRef parentNodeRef, NodeRef childNodeRef, QName assocTypeQName, QName assocQName, boolean isNewNode) { + if (ignorePolicy(parentNodeRef)) + { + return; + } + // get qnames to invoke against Set qnames = getTypeAndAspectQNames(parentNodeRef); // execute policy for node type @@ -453,6 +575,12 @@ public abstract class AbstractNodeServiceImpl implements NodeService { // Get the parent reference and the assoc type qName NodeRef parentNodeRef = childAssocRef.getParentRef(); + + if (ignorePolicy(parentNodeRef)) + { + return; + } + QName assocTypeQName = childAssocRef.getTypeQName(); // get qnames to invoke against Set qnames = getTypeAndAspectQNames(parentNodeRef); @@ -467,6 +595,12 @@ public abstract class AbstractNodeServiceImpl implements NodeService protected void invokeBeforeDeleteChildAssociation(ChildAssociationRef childAssocRef) { NodeRef parentNodeRef = childAssocRef.getParentRef(); + + if (ignorePolicy(parentNodeRef)) + { + return; + } + QName assocTypeQName = childAssocRef.getTypeQName(); // get qnames to invoke against Set qnames = getTypeAndAspectQNames(parentNodeRef); @@ -481,6 +615,12 @@ public abstract class AbstractNodeServiceImpl implements NodeService protected void invokeOnDeleteChildAssociation(ChildAssociationRef childAssocRef) { NodeRef parentNodeRef = childAssocRef.getParentRef(); + + if (ignorePolicy(parentNodeRef)) + { + return; + } + QName assocTypeQName = childAssocRef.getTypeQName(); // get qnames to invoke against Set qnames = getTypeAndAspectQNames(parentNodeRef); @@ -495,6 +635,12 @@ public abstract class AbstractNodeServiceImpl implements NodeService protected void invokeOnCreateAssociation(AssociationRef nodeAssocRef) { NodeRef sourceNodeRef = nodeAssocRef.getSourceRef(); + + if (ignorePolicy(sourceNodeRef)) + { + return; + } + QName assocTypeQName = nodeAssocRef.getTypeQName(); // get qnames to invoke against Set qnames = getTypeAndAspectQNames(sourceNodeRef); @@ -509,6 +655,12 @@ public abstract class AbstractNodeServiceImpl implements NodeService protected void invokeOnDeleteAssociation(AssociationRef nodeAssocRef) { NodeRef sourceNodeRef = nodeAssocRef.getSourceRef(); + + if (ignorePolicy(sourceNodeRef)) + { + return; + } + QName assocTypeQName = nodeAssocRef.getTypeQName(); // get qnames to invoke against Set qnames = getTypeAndAspectQNames(sourceNodeRef); diff --git a/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java b/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java index 0e58f22b79..5bd1938930 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java +++ b/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java @@ -149,6 +149,8 @@ public class MultiTDemoTest extends TestCase repoAdminService = (RepoAdminService) ctx.getBean("RepoAdminService"); dictionaryService = (DictionaryService) ctx.getBean("DictionaryService"); usageService = (UsageService) ctx.getBean("usageService"); + + createTenants(); } @Override @@ -157,6 +159,51 @@ public class MultiTDemoTest extends TestCase super.tearDown(); } + private void createTenants() + { + for (final String tenantDomain : tenants) + { + createTenant(tenantDomain); + } + } + + public void testCreateTenants() throws Throwable + { + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); // authenticate as super-admin + + logger.info("Create tenants"); + + Set personRefs = personService.getAllPeople(); + //assertEquals(2, personRefs.size()); // super-tenant: admin, guest (note: checking for 2 assumes that this test is run in a fresh bootstrap env) + for (NodeRef personRef : personRefs) + { + String userName = (String)nodeService.getProperty(personRef, ContentModel.PROP_USERNAME); + for (final String tenantDomain : tenants) + { + assertFalse("Unexpected (tenant) user: "+userName, userName.endsWith(tenantDomain)); + } + } + } + + private void createTenant(final String tenantDomain) + { + // create tenants (if not already created) + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + if (! tenantAdminService.existsTenant(tenantDomain)) + { + //tenantAdminService.createTenant(tenantDomain, DEFAULT_ADMIN_PW.toCharArray(), ROOT_DIR + "/" + tenantDomain); + tenantAdminService.createTenant(tenantDomain, (DEFAULT_ADMIN_PW+" "+tenantDomain).toCharArray(), null); // use default root dir + + logger.info("Created tenant " + tenantDomain); + } + + return null; + } + }, AuthenticationUtil.getSystemUserName()); + } public void test_ETHREEOH_2015() { @@ -192,58 +239,6 @@ public class MultiTDemoTest extends TestCase usageService.deleteDeltas(personNodeRef); } - public void testCreateTenants() throws Throwable - { - AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); // authenticate as super-admin - - logger.info("Create tenants"); - - Set personRefs = personService.getAllPeople(); - //assertEquals(2, personRefs.size()); // super-tenant: admin, guest (note: checking for 2 assumes that this test is run in a fresh bootstrap env) - for (NodeRef personRef : personRefs) - { - String userName = (String)nodeService.getProperty(personRef, ContentModel.PROP_USERNAME); - for (final String tenantDomain : tenants) - { - assertFalse("Unexpected (tenant) user: "+userName, userName.endsWith(tenantDomain)); - } - } - - try - { - for (final String tenantDomain : tenants) - { - createTenant(tenantDomain); - } - } - catch (Throwable t) - { - StringWriter stackTrace = new StringWriter(); - t.printStackTrace(new PrintWriter(stackTrace)); - System.err.println(stackTrace.toString()); - throw t; - } - } - - private void createTenant(final String tenantDomain) - { - // create tenants (if not already created) - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() throws Exception - { - if (! tenantAdminService.existsTenant(tenantDomain)) - { - //tenantAdminService.createTenant(tenantDomain, DEFAULT_ADMIN_PW.toCharArray(), ROOT_DIR + "/" + tenantDomain); - tenantAdminService.createTenant(tenantDomain, (DEFAULT_ADMIN_PW+" "+tenantDomain).toCharArray(), null); // use default root dir - - logger.info("Created tenant " + tenantDomain); - } - - return null; - } - }, AuthenticationUtil.getSystemUserName()); - } public void testCreateUsers() throws Throwable {