From 6e854e61668fe0827bd91af090706ad1c129c578 Mon Sep 17 00:00:00 2001 From: Jan Vonka Date: Tue, 4 Sep 2007 14:23:55 +0000 Subject: [PATCH] First-cut dynamic Dictionary & Namespaces - updated caches to be cluster-aware & tenant-aware git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@6675 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/cache-context.xml | 133 +++++ config/alfresco/core-services-context.xml | 44 +- config/alfresco/ehcache-default.xml | 30 + .../ehcache-custom.xml.sample.cluster | 68 ++- .../repo/dictionary/DictionaryBootstrap.java | 88 ++- .../repo/dictionary/DictionaryComponent.java | 24 +- .../repo/dictionary/DictionaryDAO.java | 25 +- .../repo/dictionary/DictionaryDAOImpl.java | 527 +++++++++++++++++- .../repo/dictionary/DictionaryDAOTest.java | 65 ++- .../repo/dictionary/DictionaryDeployer.java | 45 ++ .../DictionaryRepositoryBootstrap.java | 426 +++++++++----- .../DictionaryRepositoryBootstrapTest.java | 48 +- .../repo/dictionary/NamespaceDAO.java | 14 + .../repo/dictionary/NamespaceDAOImpl.java | 449 ++++++++++++++- .../repo/dictionary/RepositoryLocation.java | 186 +++++++ .../alfresco/repo/dictionary/TestModel.java | 58 +- 16 files changed, 1992 insertions(+), 238 deletions(-) create mode 100755 source/java/org/alfresco/repo/dictionary/DictionaryDeployer.java create mode 100755 source/java/org/alfresco/repo/dictionary/RepositoryLocation.java diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml index 01e42aebd3..180340b0c2 100644 --- a/config/alfresco/cache-context.xml +++ b/config/alfresco/cache-context.xml @@ -345,4 +345,137 @@ + + + + + + + + + + + + + + + org.alfresco.cache.uriToModelsCache + + + + + + + + + + + + + + + + org.alfresco.uriToModelsTransactionalCache + + + 10 + + + + + + + + + + + + + org.alfresco.cache.compiledModelsCache + + + + + + + + + + + + + + + + org.alfresco.compiledModelsTransactionalCache + + + 10 + + + + + + + + + + + + + org.alfresco.cache.urisCache + + + + + + + + + + + + + + + + org.alfresco.urisTransactionalCache + + + 10 + + + + + + + + + + + + + org.alfresco.cache.prefixesCache + + + + + + + + + + + + + + + + org.alfresco.prefixesTransactionalCache + + + 10 + + + \ No newline at end of file diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 55a6f2e3bc..837a533832 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -600,7 +600,19 @@ - + + + + + + + + + + + + + @@ -624,6 +636,15 @@ + + + + + + + + + @@ -641,6 +662,9 @@ + + + @@ -690,24 +714,6 @@ - - - - - - - - - - - - - - - - - - diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml index 55e92c1bad..cd018c0a48 100644 --- a/config/alfresco/ehcache-default.xml +++ b/config/alfresco/ehcache-default.xml @@ -354,4 +354,34 @@ overflowToDisk="false" /> + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/extension/ehcache-custom.xml.sample.cluster b/config/alfresco/extension/ehcache-custom.xml.sample.cluster index baea128889..4d50bc11d7 100644 --- a/config/alfresco/extension/ehcache-custom.xml.sample.cluster +++ b/config/alfresco/extension/ehcache-custom.xml.sample.cluster @@ -650,6 +650,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + - - diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryBootstrap.java b/source/java/org/alfresco/repo/dictionary/DictionaryBootstrap.java index 70fc83fd9d..7b27e2cee7 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryBootstrap.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryBootstrap.java @@ -29,18 +29,19 @@ import java.util.ArrayList; import java.util.List; import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.cmr.dictionary.DictionaryException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** - * Bootstrap Dictionary DAO with pre-defined models + * Bootstrap Dictionary DAO with pre-defined models & message resources (from classpath) * * @author David Caruana * */ -public class DictionaryBootstrap +public class DictionaryBootstrap implements DictionaryDeployer { // The list of models to bootstrap with private List models = new ArrayList(); @@ -50,6 +51,9 @@ public class DictionaryBootstrap // Dictionary DAO private DictionaryDAO dictionaryDAO = null; + + // Tenant Service + private TenantService tenantService; // Logger private static Log logger = LogFactory.getLog(DictionaryDAO.class); @@ -65,6 +69,16 @@ public class DictionaryBootstrap this.dictionaryDAO = dictionaryDAO; } + /** + * Sets the Tenant Service + * + * @param tenantService + */ + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + /** * Sets the initial list of models to bootstrap with * @@ -86,37 +100,65 @@ public class DictionaryBootstrap } /** - * Bootstrap the Dictionary + * Bootstrap the Dictionary - register and populate + * */ public void bootstrap() { - // register models - for (String bootstrapModel : models) + initDictionary(); + initStaticMessages(); + + register(); + } + + /** + * Register with the Dictionary + */ + public void register() + { + dictionaryDAO.register(this); + } + + /** + * Populate the Dictionary + */ + public void initDictionary() + { + if ((tenantService == null) || (! tenantService.isTenantUser())) { - InputStream modelStream = getClass().getClassLoader().getResourceAsStream(bootstrapModel); - if (modelStream == null) + // register models + for (String bootstrapModel : models) { - throw new DictionaryException("Could not find bootstrap model " + bootstrapModel); - } - try - { - if (logger.isInfoEnabled()) - logger.info("Loading model from " + bootstrapModel); - - M2Model model = M2Model.createModel(modelStream); - dictionaryDAO.putModel(model); - } - catch(DictionaryException e) - { - throw new DictionaryException("Could not import bootstrap model " + bootstrapModel, e); + InputStream modelStream = getClass().getClassLoader().getResourceAsStream(bootstrapModel); + if (modelStream == null) + { + throw new DictionaryException("Could not find bootstrap model " + bootstrapModel); + } + try + { + if (logger.isInfoEnabled()) + logger.info("Loading model from " + bootstrapModel); + + M2Model model = M2Model.createModel(modelStream); + dictionaryDAO.putModel(model); + } + catch(DictionaryException e) + { + throw new DictionaryException("Could not import bootstrap model " + bootstrapModel, e); + } } } - - // register models + } + + /** + * Register the static resource bundles + */ + private void initStaticMessages() + { + // register messages for (String resourceBundle : resourceBundles) { I18NUtil.registerResourceBundle(resourceBundle); } } - } diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryComponent.java b/source/java/org/alfresco/repo/dictionary/DictionaryComponent.java index f61d6cc83d..b5ba37ff3f 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryComponent.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryComponent.java @@ -27,9 +27,9 @@ package org.alfresco.repo.dictionary; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.Map; +import org.alfresco.repo.tenant.TenantDeployer; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; @@ -48,7 +48,7 @@ import org.alfresco.util.ParameterCheck; * * @author David Caruana */ -public class DictionaryComponent implements DictionaryService +public class DictionaryComponent implements DictionaryService, TenantDeployer { private DictionaryDAO dictionaryDAO; @@ -339,6 +339,26 @@ public class DictionaryComponent implements DictionaryService } return props; } + + public void init() + { + dictionaryDAO.init(); + } + + public void destroy() + { + dictionaryDAO.destroy(); + } + + public void onEnableTenant() + { + dictionaryDAO.reset(); // to initialise empty dictionary and re-populate + } + + public void onDisableTenant() + { + dictionaryDAO.destroy(); + } } diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAO.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAO.java index 3492c6d04d..0166c0c580 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAO.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAO.java @@ -93,8 +93,9 @@ public interface DictionaryDAO extends ModelQuery * Adds a model to the dictionary. The model is compiled and validated. * * @param model the model to add + * @return QName name of model */ - public void putModel(M2Model model); + public QName putModel(M2Model model); /** * Removes a model from the dictionary. The types and aspect in the model will no longer be @@ -114,4 +115,26 @@ public interface DictionaryDAO extends ModelQuery */ public Collection getProperties(QName modelName, QName dataType); + /** + * + * Register with the Dictionary + * + * @param dictionaryDeployer + */ + public void register(DictionaryDeployer dictionaryDeployer); + + /** + * Reset the Dictionary - destroy & re-initialise + */ + public void reset(); + + /** + * Initialise the Dictionary + */ + public void init(); + + /** + * Destroy the Dictionary + */ + public void destroy(); } diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java index 24a5cb241e..4ad0dbe721 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java @@ -31,7 +31,15 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; @@ -59,20 +67,51 @@ public class DictionaryDAOImpl implements DictionaryDAO // registration of models, the ability to load models // from a persistent store, the refresh of the cache // and concurrent read/write of the models. + // (in progress) + + /** + * Lock objects + */ + private ReadWriteLock lock = new ReentrantReadWriteLock(); + private Lock readLock = lock.readLock(); + private Lock writeLock = lock.writeLock(); // Namespace Data Access private NamespaceDAO namespaceDAO; + // Tenant Service + private TenantService tenantService; + // Map of Namespace URI usages to Models - private Map> uriToModels = new HashMap>(); - + private SimpleCache>> uriToModelsCache; + // Map of model name to compiled model - private Map compiledModels = new HashMap(); + private SimpleCache> compiledModelsCache; + + // Static list of registered dictionary deployers + private List dictionaryDeployers = new ArrayList(); // Logger private static Log logger = LogFactory.getLog(DictionaryDAO.class); + // inject dependencies + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + public void setUriToModelsCache(SimpleCache>> uriToModelsCache) + { + this.uriToModelsCache = uriToModelsCache; + } + + public void setCompiledModelsCache(SimpleCache> compiledModelsCache) + { + this.compiledModelsCache = compiledModelsCache; + } + /** * Construct * @@ -81,20 +120,111 @@ public class DictionaryDAOImpl implements DictionaryDAO public DictionaryDAOImpl(NamespaceDAO namespaceDAO) { this.namespaceDAO = namespaceDAO; + this.namespaceDAO.registerDictionary(this); + } + /** + * Register with the Dictionary + */ + public void register(DictionaryDeployer dictionaryDeployer) + { + if (! dictionaryDeployers.contains(dictionaryDeployer)) + { + dictionaryDeployers.add(dictionaryDeployer); + } + } + + /** + * Initialise the Dictionary & Namespaces + */ + public void init() + { + String tenantDomain = getTenantDomain(); + + // initialise empty dictionary & namespaces + putCompiledModels(tenantDomain, new HashMap()); + putUriToModels(tenantDomain, new HashMap>()); + + namespaceDAO.init(); + + // populate the dictionary + for (DictionaryDeployer dictionaryDeployer : dictionaryDeployers) + { + dictionaryDeployer.initDictionary(); + } + + logger.info("Dictionary initialised"); + } + + /** + * Destroy the Dictionary & Namespaces + */ + public void destroy() + { + String tenantDomain = getTenantDomain(); + + removeCompiledModels(tenantDomain); + removeUriToModels(tenantDomain); + + namespaceDAO.destroy(); + + logger.info("Dictionary destroyed"); + } + + /** + * Reset the Dictionary & Namespaces + */ + public void reset() + { + reset(getTenantDomain()); + } + + private void reset(String tenantDomain) + { + if (logger.isDebugEnabled()) + { + logger.debug("Resetting dictionary ..."); + } + + String userName; + if (tenantDomain == "") + { + userName = AuthenticationUtil.getSystemUserName(); + } + else + { + userName = tenantService.getDomainUser(TenantService.ADMIN_BASENAME, tenantDomain); + } + + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + destroy(); + init(); + + return null; + } + }, userName); + + if (logger.isDebugEnabled()) + { + logger.debug("... resetting dictionary completed"); + } + } /* (non-Javadoc) * @see org.alfresco.repo.dictionary.impl.DictionaryDAO#putModel(org.alfresco.repo.dictionary.impl.M2Model) */ - public void putModel(M2Model model) + public QName putModel(M2Model model) { // Compile model definition CompiledModel compiledModel = model.compile(this, namespaceDAO); QName modelName = compiledModel.getModelDefinition().getName(); // Remove namespace definitions for previous model, if it exists - CompiledModel previousVersion = compiledModels.get(modelName); + CompiledModel previousVersion = getCompiledModels().get(modelName); if (previousVersion != null) { for (M2Namespace namespace : previousVersion.getM2Model().getNamespaces()) @@ -122,7 +252,7 @@ public class DictionaryDAOImpl implements DictionaryDAO } // Publish new Model Definition - compiledModels.put(modelName, compiledModel); + getCompiledModels().put(modelName, compiledModel); if (logger.isInfoEnabled()) { @@ -132,6 +262,8 @@ public class DictionaryDAOImpl implements DictionaryDAO logger.info("Registered namespace '" + namespace.getUri() + "' (prefix '" + namespace.getPrefix() + "')"); } } + + return modelName; } @@ -140,7 +272,7 @@ public class DictionaryDAOImpl implements DictionaryDAO */ public void removeModel(QName modelName) { - CompiledModel compiledModel = this.compiledModels.get(modelName); + CompiledModel compiledModel = getCompiledModels().get(modelName); if (compiledModel != null) { // Remove the namespaces from the namespace service @@ -153,7 +285,7 @@ public class DictionaryDAOImpl implements DictionaryDAO } // Remove the model from the list - this.compiledModels.remove(modelName); + getCompiledModels().remove(modelName); } } @@ -166,11 +298,11 @@ public class DictionaryDAOImpl implements DictionaryDAO */ private void mapUriToModel(String uri, CompiledModel model) { - List models = uriToModels.get(uri); + List models = getUriToModels().get(uri); if (models == null) { models = new ArrayList(); - uriToModels.put(uri, models); + getUriToModels().put(uri, models); } if (!models.contains(model)) { @@ -187,7 +319,7 @@ public class DictionaryDAOImpl implements DictionaryDAO */ private void unmapUriToModel(String uri, CompiledModel model) { - List models = uriToModels.get(uri); + List models = getUriToModels().get(uri); if (models != null) { models.remove(model); @@ -203,25 +335,93 @@ public class DictionaryDAOImpl implements DictionaryDAO */ private List getModelsForUri(String uri) { - List models = uriToModels.get(uri); - if (models == null) - { - models = Collections.emptyList(); - } - return models; + // note: special case, if running as System - e.g. addAuditAspect + String currentUserName = AuthenticationUtil.getCurrentUserName(); + + if ((tenantService.isTenantUser()) || + (tenantService.isTenantName(uri) && (currentUserName != null) && (currentUserName.equals(AuthenticationUtil.getSystemUserName())))) + { + String tenantDomain = null; + if (currentUserName.equals(AuthenticationUtil.getSystemUserName())) + { + tenantDomain = tenantService.getDomain(uri); + } + else + { + tenantDomain = tenantService.getCurrentUserDomain(); + } + uri = tenantService.getBaseName(uri, true); + + // get non-tenant models (if any) + List models = getUriToModels("").get(uri); + + List filteredModels = new ArrayList(); + if (models != null) + { + filteredModels.addAll(models); + } + + // get tenant models (if any) + List tenantModels = getUriToModels(tenantDomain).get(uri); + if (tenantModels != null) + { + if (models != null) + { + // check to see if tenant model overrides a non-tenant model + for (CompiledModel tenantModel : tenantModels) + { + for (CompiledModel model : models) + { + if (tenantModel.getM2Model().getName().equals(model.getM2Model().getName())) + { + filteredModels.remove(model); + } + } + } + } + filteredModels.addAll(tenantModels); + models = filteredModels; + } + + if (models == null) + { + models = Collections.emptyList(); + } + return models; + } + else + { + List models = getUriToModels().get(uri); + if (models == null) + { + models = Collections.emptyList(); + } + return models; + } } - - + + /** * @param modelName the model name * @return the compiled model of the given name */ private CompiledModel getCompiledModel(QName modelName) { - CompiledModel model = compiledModels.get(modelName); + if (tenantService.isTenantUser()) + { + // get tenant-specific model (if any) + CompiledModel model = getCompiledModels().get(modelName); + if (model != null) + { + return model; + } + // else drop down to check for shared (core/system) models ... + } + + // get non-tenant model (if any) + CompiledModel model = getCompiledModels("").get(modelName); if (model == null) { - // TODO: Load model from persistent store throw new DictionaryException("d_dictionary.model.err.no_model", modelName); } return model; @@ -251,12 +451,39 @@ public class DictionaryDAOImpl implements DictionaryDAO */ public DataTypeDefinition getDataType(Class javaClass) { - for (CompiledModel model : compiledModels.values()) + if (tenantService.isTenantUser() == true) { - DataTypeDefinition dataTypeDef = model.getDataType(javaClass); - if (dataTypeDef != null) - { - return dataTypeDef; + // get tenant models (if any) + for (CompiledModel model : getCompiledModels().values()) + { + DataTypeDefinition dataTypeDef = model.getDataType(javaClass); + if (dataTypeDef != null) + { + return dataTypeDef; + } + } + + // get non-tenant models (if any) + for (CompiledModel model : getCompiledModels("").values()) + { + DataTypeDefinition dataTypeDef = model.getDataType(javaClass); + if (dataTypeDef != null) + { + return dataTypeDef; + } + } + + return null; + } + else + { + for (CompiledModel model : getCompiledModels().values()) + { + DataTypeDefinition dataTypeDef = model.getDataType(javaClass); + if (dataTypeDef != null) + { + return dataTypeDef; + } } } return null; @@ -315,6 +542,11 @@ public class DictionaryDAOImpl implements DictionaryDAO public ClassDefinition getClass(QName className) { List models = getModelsForUri(className.getNamespaceURI()); + + // note: special case, if running as System - e.g. addAuditAspect + // now force, even for System user + className = tenantService.getBaseName(className, true); + for (CompiledModel model : models) { ClassDefinition classDef = model.getClass(className); @@ -386,9 +618,56 @@ public class DictionaryDAOImpl implements DictionaryDAO */ public Collection getModels() { - return compiledModels.keySet(); - } + if (tenantService.isTenantUser()) + { + // return all tenant-specific models and all shared (non-overridden) models + Collection filteredModels = new ArrayList(); + Collection tenantModels = new ArrayList(); + Collection nontenantModels = new ArrayList(); + // get tenant models (if any) + for (QName key : getCompiledModels().keySet()) + { + tenantModels.add(key); + } + + // get non-tenant models (if any) + // note: these will be shared, if not overridden - could be core/system model or additional custom model shared between tenants + for (QName key : getCompiledModels("").keySet()) + { + nontenantModels.add(key); + } + + // check for overrides + filteredModels.addAll(nontenantModels); + + for (QName tenantModel : tenantModels) + { + for (QName nontenantModel : nontenantModels) + { + if (tenantModel.equals(nontenantModel)) + { + // override + filteredModels.remove(nontenantModel); + break; + } + } + } + + filteredModels.addAll(tenantModels); + return filteredModels; + } + else + { + return getCompiledModels().keySet(); + } + } + + // used for clean-up, e.g. when deleting a tenant + protected Collection getNonSharedModels() + { + return getCompiledModels().keySet(); + } /* (non-Javadoc) * @see org.alfresco.repo.dictionary.impl.DictionaryDAO#getModel(org.alfresco.repo.ref.QName) @@ -479,4 +758,196 @@ public class DictionaryDAOImpl implements DictionaryDAO return properties; } + /** + * Get compiledModels from the cache (in the context of the current user's tenant domain) + * + * @param tenantDomain + */ + /* package */ Map getCompiledModels() + { + return getCompiledModels(getTenantDomain()); + } + + /** + * Get compiledModels from the cache (in the context of the given tenant domain) + * + * @param tenantDomain + */ + private Map getCompiledModels(String tenantDomain) + { + Map compiledModels = null; + try + { + readLock.lock(); + compiledModels = compiledModelsCache.get(tenantDomain); + } + finally + { + readLock.unlock(); + } + + + if (compiledModels == null) + { + reset(tenantDomain); // reset caches - may have been invalidated (e.g. in a cluster) + + try + { + readLock.lock(); + compiledModels = compiledModelsCache.get(tenantDomain); + } + finally + { + readLock.unlock(); + } + + if (compiledModels == null) + { + // unexpected + throw new AlfrescoRuntimeException("Failed to re-initialise compiledModelsCache " + tenantDomain); + } + } + + return compiledModels; + } + + /** + * Put compiledModels into the cache (in the context of the given tenant domain) + * + * @param tenantDomain + */ + private void putCompiledModels(String tenantDomain, Map compiledModels) + { + try + { + writeLock.lock(); + compiledModelsCache.put(tenantDomain, compiledModels); + } + finally + { + writeLock.unlock(); + } + } + + /** + * Remove compiledModels from the cache (in the context of the given tenant domain) + * + * @param tenantDomain + */ + private void removeCompiledModels(String tenantDomain) + { + try + { + writeLock.lock(); + if (compiledModelsCache.get(tenantDomain) != null) + { + compiledModelsCache.get(tenantDomain).clear(); + compiledModelsCache.remove(tenantDomain); + } + } + finally + { + writeLock.unlock(); + } + } + + /** + * Get uriToModels from the cache (in the context of the current user's tenant domain) + * + * @param tenantDomain + */ + private Map> getUriToModels() + { + return getUriToModels(getTenantDomain()); + } + + /** + * Get uriToModels from the cache (in the context of the given tenant domain) + * + * @param tenantDomain + */ + private Map> getUriToModels(String tenantDomain) + { + Map> uriToModels = null; + try + { + readLock.lock(); + uriToModels = uriToModelsCache.get(tenantDomain); + } + finally + { + readLock.unlock(); + } + + if (uriToModels == null) + { + reset(tenantDomain); // reset caches - may have been invalidated (e.g. in a cluster) + + try + { + readLock.lock(); + uriToModels = uriToModelsCache.get(tenantDomain); + } + finally + { + readLock.unlock(); + } + + if (uriToModels == null) + { + // unexpected + throw new AlfrescoRuntimeException("Failed to re-initialise uriToModelsCache " + tenantDomain); + } + } + + return uriToModels; + } + + /** + * Put uriToModels into the cache (in the context of the given tenant domain) + * + * @param tenantDomain + */ + private void putUriToModels(String tenantDomain, Map> uriToModels) + { + try + { + writeLock.lock(); + uriToModelsCache.put(tenantDomain, uriToModels); + } + finally + { + writeLock.unlock(); + } + } + + /** + * Remove uriToModels from the cache (in the context of the given tenant domain) + * + * @param tenantDomain + */ + private void removeUriToModels(String tenantDomain) + { + try + { + writeLock.lock(); + if (uriToModelsCache.get(tenantDomain) != null) + { + uriToModelsCache.get(tenantDomain).clear(); + uriToModelsCache.remove(tenantDomain); + } + } + finally + { + writeLock.unlock(); + } + } + + /** + * Local helper - returns tenant domain (or empty string if default non-tenant) + */ + private String getTenantDomain() + { + return tenantService.getCurrentUserDomain(); + } } diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java index 2cb2ddb057..7fa24f1714 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java @@ -24,16 +24,20 @@ */ package org.alfresco.repo.dictionary; -import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Map; import junit.framework.TestCase; +import net.sf.ehcache.Cache; +import net.sf.ehcache.CacheManager; import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.cache.EhCacheAdapter; import org.alfresco.repo.dictionary.constraint.RegexConstraint; import org.alfresco.repo.dictionary.constraint.StringLengthConstraint; +import org.alfresco.repo.tenant.SingleTServiceImpl; +import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.Constraint; @@ -45,7 +49,6 @@ import org.alfresco.service.cmr.dictionary.ModelDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; public class DictionaryDAOTest extends TestCase @@ -65,8 +68,14 @@ public class DictionaryDAOTest extends TestCase I18NUtil.registerResourceBundle(TEST_RESOURCE_MESSAGES); // Instantiate Dictionary Service - NamespaceDAO namespaceDAO = new NamespaceDAOImpl(); + TenantService tenantService = new SingleTServiceImpl(); + NamespaceDAOImpl namespaceDAO = new NamespaceDAOImpl(); + namespaceDAO.setTenantService(tenantService); + initNamespaceCaches(namespaceDAO); + DictionaryDAOImpl dictionaryDAO = new DictionaryDAOImpl(namespaceDAO); + dictionaryDAO.setTenantService(tenantService); + initDictionaryCaches(dictionaryDAO); // Populate with appropriate models DictionaryBootstrap bootstrap = new DictionaryBootstrap(); @@ -78,6 +87,7 @@ public class DictionaryDAOTest extends TestCase bootstrap.setModels(bootstrapModels); bootstrap.setLabels(labels); bootstrap.setDictionaryDAO(dictionaryDAO); + bootstrap.setTenantService(tenantService); bootstrap.bootstrap(); DictionaryComponent component = new DictionaryComponent(); @@ -85,11 +95,55 @@ public class DictionaryDAOTest extends TestCase service = component; } + private void initDictionaryCaches(DictionaryDAOImpl dictionaryDAO) + { + CacheManager cacheManager = new CacheManager(); + + Cache uriToModelsEhCache = new Cache("uriToModelsCache", 50, false, true, 0L, 0L); + cacheManager.addCache(uriToModelsEhCache); + EhCacheAdapter>> uriToModelsCache = new EhCacheAdapter>>(); + uriToModelsCache.setCache(uriToModelsEhCache); + + dictionaryDAO.setUriToModelsCache(uriToModelsCache); + + Cache compileModelsEhCache = new Cache("compiledModelsCache", 50, false, true, 0L, 0L); + cacheManager.addCache(compileModelsEhCache); + EhCacheAdapter> compileModelCache = new EhCacheAdapter>(); + compileModelCache.setCache(compileModelsEhCache); + + dictionaryDAO.setCompiledModelsCache(compileModelCache); + } + + private void initNamespaceCaches(NamespaceDAOImpl namespaceDAO) + { + CacheManager cacheManager = new CacheManager(); + + Cache urisEhCache = new Cache("urisCache", 50, false, true, 0L, 0L); + cacheManager.addCache(urisEhCache); + EhCacheAdapter> urisCache = new EhCacheAdapter>(); + urisCache.setCache(urisEhCache); + + namespaceDAO.setUrisCache(urisCache); + + Cache prefixesEhCache = new Cache("prefixesCache", 50, false, true, 0L, 0L); + cacheManager.addCache(prefixesEhCache); + EhCacheAdapter> prefixesCache = new EhCacheAdapter>(); + prefixesCache.setCache(prefixesEhCache); + + namespaceDAO.setPrefixesCache(prefixesCache); + } + public void testBootstrap() { - NamespaceDAO namespaceDAO = new NamespaceDAOImpl(); + TenantService tenantService = new SingleTServiceImpl(); + NamespaceDAOImpl namespaceDAO = new NamespaceDAOImpl(); + namespaceDAO.setTenantService(tenantService); + initNamespaceCaches(namespaceDAO); + DictionaryDAOImpl dictionaryDAO = new DictionaryDAOImpl(namespaceDAO); + dictionaryDAO.setTenantService(tenantService); + initDictionaryCaches(dictionaryDAO); DictionaryBootstrap bootstrap = new DictionaryBootstrap(); List bootstrapModels = new ArrayList(); @@ -107,7 +161,8 @@ public class DictionaryDAOTest extends TestCase bootstrap.setModels(bootstrapModels); bootstrap.setDictionaryDAO(dictionaryDAO); - bootstrap.bootstrap(); + bootstrap.setTenantService(tenantService); + bootstrap.bootstrap(); } diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDeployer.java b/source/java/org/alfresco/repo/dictionary/DictionaryDeployer.java new file mode 100755 index 0000000000..7502876e8e --- /dev/null +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDeployer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.dictionary; + + +/** + * Dictionary Deployer interface. + *

+ * This interface allows DictionaryDAO to be (re-)initialised. + * Dictionary Deployer components must register with the DictionaryService. + * + */ + +public interface DictionaryDeployer +{ + // callback for (re-)initialising the Dictionary caches + public void initDictionary(); + + // register prior to bootstrap + public void register(); +} + + diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java b/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java index 2e28135644..1a138045e9 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java @@ -30,26 +30,43 @@ import java.util.List; import java.util.Map; import org.alfresco.model.ContentModel; -import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.i18n.MessageDeployer; +import org.alfresco.repo.i18n.MessageService; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.tenant.TenantDeployer; +import org.alfresco.repo.tenant.TenantDeployerService; +import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; 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.ResultSet; import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.transaction.TransactionService; - +import org.alfresco.util.AbstractLifecycleBean; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEvent; /** * Bootstrap the dictionary from specified locations within the repository * - * @author Roy Wetherall + * @author Roy Wetherall, JanV */ -public class DictionaryRepositoryBootstrap -{ - /** Loactions in the respository fro which models should be loaded */ - private List repositoryLocations = new ArrayList(); +public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean implements TenantDeployer, DictionaryDeployer, MessageDeployer +{ + // Logging support + private static Log logger = LogFactory + .getLog("org.alfresco.repo.dictionary.DictionaryRepositoryBootstrap"); + + /** Locations in the repository from which models should be loaded */ + private List repositoryModelsLocations = new ArrayList(); + + /** Locations in the repository from which messages should be loaded */ + private List repositoryMessagesLocations = new ArrayList(); /** Dictionary DAO */ private DictionaryDAO dictionaryDAO = null; @@ -59,12 +76,24 @@ public class DictionaryRepositoryBootstrap /** The content service */ private ContentService contentService; + + /** The node service */ + private NodeService nodeService; + + /** The tenant service */ + private TenantService tenantService; + /** The tenant deployer service */ + private TenantDeployerService tenantDeployerService; + + /** The namespace service */ + private NamespaceService namespaceService; + + /** The message service */ + private MessageService messageService; + /** The transaction service */ private TransactionService transactionService; - - /** The authentication component */ - private AuthenticationComponent authenticationComponent; /** * Sets the Dictionary DAO @@ -95,7 +124,57 @@ public class DictionaryRepositoryBootstrap { this.contentService = contentService; } + + /** + * Set the node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the tenant service + * + * @param tenantService the tenant service + */ + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + /** + * Set the tenant admin service + * + * @param tenantAdminService the tenant admin service + */ + public void setTenantDeployerService(TenantDeployerService tenantDeployerService) + { + this.tenantDeployerService = tenantDeployerService; + } + + /** + * Set the namespace service + * + * @param namespaceService the namespace service + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * Set the message service + * + * @param messageService the message service + */ + public void setMessageService(MessageService messageService) + { + this.messageService = messageService; + } + /** * Set the transaction service * @@ -107,95 +186,175 @@ public class DictionaryRepositoryBootstrap } /** - * Set the authentication service + * Set the repository models locations * - * @param authenticationComponent the authentication component - */ - public void setAuthenticationComponent( - AuthenticationComponent authenticationComponent) + * @param repositoryModelsLocations list of the repository models locations + */ public void setRepositoryModelsLocations( + List repositoryLocations) { - this.authenticationComponent = authenticationComponent; + this.repositoryModelsLocations = repositoryLocations; } /** - * Set the respository locations + * Set the repository messages (resource bundle) locations * - * @param repositoryLocations list of the repository locaitons + * @param repositoryLocations + * list of the repository messages locations */ - public void setRepositoryLocations( + public void setRepositoryMessagesLocations( List repositoryLocations) { - this.repositoryLocations = repositoryLocations; + this.repositoryMessagesLocations = repositoryLocations; } - - @SuppressWarnings("unchecked") - public void bootstrap() + + + /** + * Initialise - after bootstrap of schema and tenant admin service + */ + public void init() { - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { public Object execute() throws Exception { - DictionaryRepositoryBootstrap.this.authenticationComponent.setCurrentUser( - DictionaryRepositoryBootstrap.this.authenticationComponent.getSystemUserName()); - try - { - bootstrapImpl(); - } - finally - { - DictionaryRepositoryBootstrap.this.authenticationComponent.clearCurrentSecurityContext(); - } - return null; + initDictionary(); + initMessages(); + + return (Object)null; } }); } - /** - * Bootstrap the Dictionary - */ - public void bootstrapImpl() + public void destroy() + { + // NOOP - will be destroyed directly via DictionaryComponent + } + + public void initDictionary() { - Map modelMap = new HashMap(); - - // Register the models found in the respository - for (RepositoryLocation repositoryLocation : this.repositoryLocations) - { - ResultSet resultSet = null; - try + if (this.repositoryModelsLocations != null) + { + Map modelMap = new HashMap(); + + // Register the models found in the repository + + for (RepositoryLocation repositoryLocation : this.repositoryModelsLocations) { - resultSet = this.searchService.query(repositoryLocation.getStoreRef(), SearchService.LANGUAGE_LUCENE, repositoryLocation.getQueryStatement()); - - for (NodeRef dictionaryModel : resultSet.getNodeRefs()) + StoreRef storeRef = repositoryLocation.getStoreRef(); + + if (! nodeService.exists(storeRef)) { - M2Model model = createM2Model(dictionaryModel); - if (model != null) + logger.warn("StoreRef '"+ storeRef+"' does not exist"); + continue; // skip this location + } + + if (repositoryLocation.getQueryLanguage().equals( + SearchService.LANGUAGE_XPATH)) + { + NodeRef rootNode = nodeService.getRootNode(storeRef); + + List nodeRefs = searchService.selectNodes(rootNode, + repositoryLocation.getXPathQueryStatement(ContentModel.TYPE_DICTIONARY_MODEL.getPrefixedQName(namespaceService)), + null, + namespaceService, + false); + + for (NodeRef dictionaryModel : nodeRefs) { - for (M2Namespace namespace : model.getNamespaces()) + // TODO - should validate in case of re-deploy - e.g. update or delete + M2Model model = createM2Model(dictionaryModel); + if (model != null) { - modelMap.put(namespace.getUri(), model); - } + for (M2Namespace namespace : model.getNamespaces()) + { + modelMap.put(namespace.getUri(), model); + } + } } } } - finally + + // Load the models ensuring that they are loaded in the correct order + List loadedModels = new ArrayList(); + for (Map.Entry entry : modelMap.entrySet()) { - if (resultSet != null) - { - resultSet.close(); - } + loadModel(modelMap, loadedModels, entry.getValue()); } } - - // Load the models ensuring that they are loaded in the correct order - List loadedModels = new ArrayList(); - for (Map.Entry entry : modelMap.entrySet()) + } + + public void initMessages() + { + if (this.repositoryMessagesLocations != null) { - loadModel(modelMap, loadedModels, entry.getValue()); + // Register the messages found in the repository + for (RepositoryLocation repositoryLocation : this.repositoryMessagesLocations) + { + StoreRef storeRef = repositoryLocation.getStoreRef(); + String path = repositoryLocation.getPath(); + + if (! nodeService.exists(storeRef)) + { + logger.warn("StoreRef '"+ storeRef+"' does not exist"); + continue; // skip this location + } + + if (repositoryLocation.getQueryLanguage().equals( + SearchService.LANGUAGE_XPATH)) + { + NodeRef rootNode = nodeService.getRootNode(storeRef); + + List nodeRefs = searchService.selectNodes(rootNode, + repositoryLocation.getXPathQueryStatement(ContentModel.TYPE_CONTENT.getPrefixedQName(namespaceService)), + null, + namespaceService, + false); + + List resourceBundleBaseNames = new ArrayList(); + + for (NodeRef messageResource : nodeRefs) + { + String name = (String) nodeService.getProperty( + messageResource, ContentModel.PROP_NAME); + + // convert resource file name to a resource bundle basename + // e.g. either 'workflow_fr_FR.properties' or 'workflow.properties' should be converted to 'workflow' + // note: this assumes that the baseName itself does not contain underscore ! + int idx = name.indexOf("_"); + if (idx > 0) + { + name = name.substring(0, idx - 1); + } + else + { + int idx1 = name.indexOf("."); + if (idx1 > 0) + { + name = name.substring(0, idx1); + } + } + + if (!resourceBundleBaseNames.contains(name)) + { + resourceBundleBaseNames.add(name); + } + } + + // Only need to register resource bundle names + for (String resourceBundleBaseName : resourceBundleBaseNames) + { + logger.info("Register bundle: " + resourceBundleBaseName); + + messageService.registerResourceBundle(storeRef.toString() + path + "/cm:" + resourceBundleBaseName); + + } + } + } } } /** - * Loads a model (and it dependants) if it does not exist in the list of loaded models. + * Loads a model (and its dependents) if it does not exist in the list of loaded models. * * @param modelMap a map of the models to be loaded * @param loadedModels the list of models already loaded @@ -240,77 +399,92 @@ public class DictionaryRepositoryBootstrap // TODO should we inactivate the model node and put the error somewhere?? return model; } - - /** - * Repositotry location object, defines a location in the repository from within which dictionary models should be loaded - * for inclusion in the data dictionary. - * - * @author Roy Wetherall - */ - public class RepositoryLocation + + @Override + protected void onBootstrap(ApplicationEvent event) { - /** Store protocol */ - private String storeProtocol; - - /** Store identifier */ - private String storeId; - - /** Path */ - private String path; - - /** - * Set the store protocol - * - * @param storeProtocol the store protocol - */ - public void setStoreProtocol(String storeProtocol) + // run as System on bootstrap + AuthenticationUtil.runAs(new RunAsWork() { - this.storeProtocol = storeProtocol; + public Object doWork() + { + init(); + return null; + } + }, AuthenticationUtil.getSystemUserName()); + + if (tenantService.isEnabled()) + { + tenantDeployerService.deployTenants(this, logger); } - /** - * Set the store identifier - * - * @param storeId the store identifier - */ - public void setStoreId(String storeId) - { - this.storeId = storeId; - } + register(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + unregister(); - /** - * Set the path - * - * @param path the path - */ - public void setPath(String path) + // run as System on shutdown + AuthenticationUtil.runAs(new RunAsWork() { - this.path = path; - } + public Object doWork() + { + destroy(); + return null; + } + }, AuthenticationUtil.getSystemUserName()); - /** - * Get the store reference - * - * @return the store reference - */ - public StoreRef getStoreRef() + if (tenantService.isEnabled()) { - return new StoreRef(this.storeProtocol, this.storeId); + tenantDeployerService.undeployTenants(this, logger); } + } + + public void onEnableTenant() + { + init(); // will be called in context of tenant + } + + public void onDisableTenant() + { + destroy(); // will be called in context of tenant + } + + /** + * Register + */ + public void register() + { + // register with Dictionary Service to allow (re-)init + dictionaryDAO.register(this); + + // register with Message Service to allow (re-)init + messageService.register(this); - /** - * Get the query statement, based on the path - * - * @return the query statement - */ - public String getQueryStatement() + if (tenantService.isEnabled()) { - String result = "+TYPE:\"" + ContentModel.TYPE_DICTIONARY_MODEL.toString() + "\""; - if (this.path != null) - { - result += " +PATH:\"" + this.path + "\""; - } - return result; + // register dictionary repository bootstrap + tenantDeployerService.register(this); + + // register repository message (I18N) service + tenantDeployerService.register(messageService); + } + } + + /** + * Unregister + */ + protected void unregister() + { + if (tenantService.isEnabled()) + { + // register dictionary repository bootstrap + tenantDeployerService.unregister(this); + + // register repository message (I18N) service + tenantDeployerService.unregister(messageService); } } } diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrapTest.java b/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrapTest.java index 70286a317a..21e6979f0c 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrapTest.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrapTest.java @@ -6,14 +6,16 @@ import java.util.List; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.dictionary.DictionaryRepositoryBootstrap.RepositoryLocation; +import org.alfresco.repo.i18n.MessageService; import org.alfresco.repo.policy.BehaviourFilter; -import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.tenant.TenantDeployerService; +import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.cmr.dictionary.DictionaryException; import org.alfresco.service.cmr.dictionary.ModelDefinition; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; 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.BaseAlfrescoSpringTest; @@ -67,9 +69,18 @@ public class DictionaryRepositoryBootstrapTest extends BaseAlfrescoSpringTest /** The transaction service */ private TransactionService transactionService; - - /** The authentication service */ - private AuthenticationComponent authenticationComponent; + + /** The tenant service */ + private TenantService tenantService; + + /** The tenant deployer service */ + private TenantDeployerService tenantDeployerService; + + /** The namespace service */ + private NamespaceService namespaceService; + + /** The message service */ + private MessageService messageService; /** * @see org.springframework.test.AbstractTransactionalSpringContextTests#onSetUpInTransaction() @@ -85,24 +96,35 @@ public class DictionaryRepositoryBootstrapTest extends BaseAlfrescoSpringTest this.searchService = (SearchService)this.applicationContext.getBean("searchService"); this.dictionaryDAO = (DictionaryDAO)this.applicationContext.getBean("dictionaryDAO"); - this.transactionService = (TransactionService)this.applicationContext.getBean("transactionComponent"); - this.authenticationComponent = (AuthenticationComponent)this.applicationContext.getBean("authenticationComponent"); + this.transactionService = (TransactionService)this.applicationContext.getBean("transactionComponent"); + this.tenantService = (TenantService)this.applicationContext.getBean("tenantService"); + this.tenantDeployerService = (TenantDeployerService)this.applicationContext.getBean("tenantAdminService"); + this.namespaceService = (NamespaceService)this.applicationContext.getBean("namespaceService"); + this.messageService = (MessageService)this.applicationContext.getBean("messageService"); this.bootstrap = new DictionaryRepositoryBootstrap(); this.bootstrap.setContentService(this.contentService); this.bootstrap.setSearchService(this.searchService); this.bootstrap.setDictionaryDAO(this.dictionaryDAO); - this.bootstrap.setAuthenticationComponent(this.authenticationComponent); - this.bootstrap.setTransactionService(this.transactionService); - - RepositoryLocation location = this.bootstrap.new RepositoryLocation(); + this.bootstrap.setTransactionService(this.transactionService); + this.bootstrap.setTenantService(this.tenantService); + this.bootstrap.setTenantDeployerService(this.tenantDeployerService); + this.bootstrap.setNodeService(this.nodeService); + this.bootstrap.setNamespaceService(this.namespaceService); + this.bootstrap.setMessageService(this.messageService); + + RepositoryLocation location = new RepositoryLocation(); location.setStoreProtocol(this.storeRef.getProtocol()); location.setStoreId(this.storeRef.getIdentifier()); // NOTE: we are not setting the path for now .. in doing so we are searching the whole dictionary List locations = new ArrayList(); locations.add(location); - this.bootstrap.setRepositoryLocations(locations); + + this.bootstrap.setRepositoryModelsLocations(locations); + + // register with dictionary service + this.bootstrap.register(); } /** @@ -148,7 +170,7 @@ public class DictionaryRepositoryBootstrapTest extends BaseAlfrescoSpringTest } // Now do the bootstrap - this.bootstrap.bootstrap(); + this.bootstrap.init(); // Check that the model is now there ModelDefinition modelDefinition1 = this.dictionaryDAO.getModel( diff --git a/source/java/org/alfresco/repo/dictionary/NamespaceDAO.java b/source/java/org/alfresco/repo/dictionary/NamespaceDAO.java index 46c61fc97d..505e732e5e 100644 --- a/source/java/org/alfresco/repo/dictionary/NamespaceDAO.java +++ b/source/java/org/alfresco/repo/dictionary/NamespaceDAO.java @@ -66,4 +66,18 @@ public interface NamespaceDAO extends NamespacePrefixResolver */ public void removePrefix(String prefix); + /** + * Initialise Namespaces + */ + public void init(); + + /** + * Destroy Namespaces + */ + public void destroy(); + + /** + * Register with the Dictionary + */ + public void registerDictionary(DictionaryDAO dictionaryDAO); } diff --git a/source/java/org/alfresco/repo/dictionary/NamespaceDAOImpl.java b/source/java/org/alfresco/repo/dictionary/NamespaceDAOImpl.java index da618eba50..39ffef74c6 100644 --- a/source/java/org/alfresco/repo/dictionary/NamespaceDAOImpl.java +++ b/source/java/org/alfresco/repo/dictionary/NamespaceDAOImpl.java @@ -29,22 +29,153 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.namespace.NamespaceException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** * Simple in-memory namespace DAO */ public class NamespaceDAOImpl implements NamespaceDAO { + private static final Log logger = LogFactory.getLog(NamespaceDAOImpl.class); + + /** + * Lock objects + */ + private ReadWriteLock lock = new ReentrantReadWriteLock(); + private Lock readLock = lock.readLock(); + private Lock writeLock = lock.writeLock(); + + // internal caches that are clusterable + private SimpleCache> urisCache; + private SimpleCache> prefixesCache; - private List uris = new ArrayList(); - private HashMap prefixes = new HashMap(); + // Dependencies + private TenantService tenantService; + + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + public void setUrisCache(SimpleCache> urisCache) + { + this.urisCache = urisCache; + } + + public void setPrefixesCache(SimpleCache> prefixesCache) + { + this.prefixesCache = prefixesCache; + } + + + private DictionaryDAO dictionaryDAO; + + public void registerDictionary(DictionaryDAO dictionaryDAO) + { + this.dictionaryDAO = dictionaryDAO; + } + /** + * Initialise empty namespaces + */ + public void init() + { + String tenantDomain = getTenantDomain(); + + putUrisCtx(tenantDomain, new ArrayList()); + putPrefixesCtx(tenantDomain, new HashMap()); + + if (logger.isDebugEnabled()) + { + logger.debug("Empty namespaces initialised"); + } + } + + /** + * Destroy the namespaces + */ + public void destroy() + { + String tenantDomain = getTenantDomain(); + + removeUrisCtx(tenantDomain); + removePrefixesCtx(tenantDomain); + + if (logger.isDebugEnabled()) + { + logger.debug("Namespaces destroyed"); + } + } + + /** + * Resets the namespaces (by resetting the dictionary) + */ + private void reset() + { + if (logger.isDebugEnabled()) + { + logger.debug("Resetting namespaces ..."); + } + + if (dictionaryDAO == null) + { + // Unexpected + throw new AlfrescoRuntimeException("Dictionary should be registered in order to perform reset"); + } + else + { + dictionaryDAO.reset(); + } + + if (logger.isDebugEnabled()) + { + logger.debug("... resetting namespaces completed"); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.ref.NamespacePrefixResolver#getURIs() + */ public Collection getURIs() { - return Collections.unmodifiableCollection(uris); + if (! tenantService.isTenantUser()) + { + return Collections.unmodifiableCollection(getUrisCtx()); + } + else + { + // Get tenant-specific URIs + List domainUris = getUrisCtx(); + + // Get non-tenant-specific URIs (and filter out, if overridden) + List urisFiltered = new ArrayList(); + for(String uri : getUrisCtx("")) + { + if (domainUris.contains(uri)) + { + // overridden, hence skip this default prefix + continue; + } + urisFiltered.add(uri); + } + + // default (non-overridden) + tenant-specific + urisFiltered.addAll(domainUris); + + return Collections.unmodifiableCollection(urisFiltered); + } } @@ -52,8 +183,33 @@ public class NamespaceDAOImpl implements NamespaceDAO * @see org.alfresco.repo.ref.NamespacePrefixResolver#getPrefixes() */ public Collection getPrefixes() - { - return Collections.unmodifiableCollection(prefixes.keySet()); + { + if (! tenantService.isTenantUser()) + { + return Collections.unmodifiableCollection(getPrefixesCtx().keySet()); + } + else + { + // Get tenant-specific prefixes + Collection domainPrefixes = getPrefixesCtx().keySet(); + + // Get non-tenant-specific URIs (and filter out, if overridden) + List prefixesFiltered = new ArrayList(); + for(String prefix : getPrefixesCtx("").keySet()) + { + if (domainPrefixes.contains(prefix)) + { + // overridden, hence skip this default prefix + continue; + } + prefixesFiltered.add(prefix); + } + + // default (non-overridden) + tenant-specific + prefixesFiltered.addAll(domainPrefixes); + + return Collections.unmodifiableCollection(prefixesFiltered); + } } @@ -62,11 +218,11 @@ public class NamespaceDAOImpl implements NamespaceDAO */ public void addURI(String uri) { - if (uris.contains(uri)) + if (getUrisCtx().contains(uri)) { throw new NamespaceException("URI " + uri + " has already been defined"); } - uris.add(uri); + getUrisCtx().add(uri); } @@ -75,11 +231,11 @@ public class NamespaceDAOImpl implements NamespaceDAO */ public void addPrefix(String prefix, String uri) { - if (!uris.contains(uri)) + if (!getUrisCtx().contains(uri)) { throw new NamespaceException("Namespace URI " + uri + " does not exist"); } - prefixes.put(prefix, uri); + getPrefixesCtx().put(prefix, uri); } @@ -88,7 +244,7 @@ public class NamespaceDAOImpl implements NamespaceDAO */ public void removeURI(String uri) { - uris.remove(uri); + getUrisCtx().remove(uri); } @@ -97,7 +253,7 @@ public class NamespaceDAOImpl implements NamespaceDAO */ public void removePrefix(String prefix) { - prefixes.remove(prefix); + getPrefixesCtx().remove(prefix); } @@ -106,7 +262,25 @@ public class NamespaceDAOImpl implements NamespaceDAO */ public String getNamespaceURI(String prefix) { - return prefixes.get(prefix); + if (! tenantService.isTenantUser()) + { + return getPrefixesCtx().get(prefix); + } + else + { + // first look for tenant-specific prefix + String uri = getPrefixesCtx().get(prefix); + if (uri != null) + { + // found tenant specific uri + return uri; + } + else + { + // try with default (non-tenant-specific) prefix + return getPrefixesCtx("").get(prefix); + } + } } @@ -115,16 +289,255 @@ public class NamespaceDAOImpl implements NamespaceDAO */ public Collection getPrefixes(String URI) { - Collection uriPrefixes = new ArrayList(); - for (String key : prefixes.keySet()) + if (! tenantService.isTenantUser()) { - String uri = prefixes.get(key); - if ((uri != null) && (uri.equals(URI))) + Collection uriPrefixes = new ArrayList(); + for (String key : getPrefixesCtx().keySet()) { - uriPrefixes.add(key); + String uri = getPrefixesCtx().get(key); + if ((uri != null) && (uri.equals(URI))) + { + uriPrefixes.add(key); + } } + return uriPrefixes; } - return uriPrefixes; + else + { + // check domain prefixes + Collection domainUriPrefixes = new ArrayList(); + for (String key : getPrefixesCtx().keySet()) + { + String uri = getPrefixesCtx().get(key); + if ((uri != null) && (uri.equals(URI))) + { + domainUriPrefixes.add(key); + } + } + + // check non-domain prefixes + Collection uriPrefixes = new ArrayList(); + for (String key : getPrefixesCtx("").keySet()) + { + String uri = getPrefixesCtx("").get(key); + if ((uri != null) && (uri.equals(URI))) + { + if (domainUriPrefixes != null) + { + if (domainUriPrefixes.contains(key)) + { + // overridden, hence skip this default prefix + continue; + } + } + + uriPrefixes.add(key); + } + } + + if (domainUriPrefixes != null) + { + // default (non-overridden) + domain + uriPrefixes.addAll(domainUriPrefixes); + } + + return uriPrefixes; + } + } + + /** + * Get URIs from the cache (in the context of the current user's tenant domain) + * + * @return URIs + */ + private List getUrisCtx() + { + return getUrisCtx(getTenantDomain()); } + /** + * Get URIs from the cache + * + * @param tenantDomain + * @return URIs + */ + private List getUrisCtx(String tenantDomain) + { + List uris = null; + try + { + readLock.lock(); + uris = urisCache.get(tenantDomain); + } + finally + { + readLock.unlock(); + } + + + if (uris == null) + { + reset(); // reset caches - may have been invalidated (e.g. in a cluster) + + try + { + readLock.lock(); + uris = urisCache.get(tenantDomain); + } + finally + { + readLock.unlock(); + } + + if (uris == null) + { + // unexpected + throw new AlfrescoRuntimeException("Failed to re-initialise urisCache " + tenantDomain); + } + } + return uris; + } + + /** + * Put URIs into the cache + * + * @param tenantDomain + * @param uris + */ + private void putUrisCtx(String tenantDomain, List uris) + { + try + { + writeLock.lock(); + urisCache.put(tenantDomain, uris); + } + finally + { + writeLock.unlock(); + } + } + + /** + * Remove URIs from the cache + * + * @param tenantDomain + */ + private void removeUrisCtx(String tenantDomain) + { + try + { + writeLock.lock(); + if (urisCache.get(tenantDomain) != null) + { + urisCache.get(tenantDomain).clear(); + urisCache.remove(tenantDomain); + } + } + finally + { + writeLock.unlock(); + } + } + + /** + * Get prefixes from the cache + * + * @return prefixes + */ + private Map getPrefixesCtx() + { + return getPrefixesCtx(getTenantDomain()); + } + + /** + * Get prefixes from the cache + * + * @param tenantDomain + * @return prefixes + */ + private Map getPrefixesCtx(String tenantDomain) + { + Map prefixes = null; + try + { + readLock.lock(); + prefixes = prefixesCache.get(tenantDomain); + } + finally + { + readLock.unlock(); + } + + if (prefixes == null) + { + reset(); // reset caches - may have been invalidated (e.g. in a cluster) + + try + { + readLock.lock(); + prefixes = prefixesCache.get(tenantDomain); + } + finally + { + readLock.unlock(); + } + + if (prefixes == null) + { + // unexpected + throw new AlfrescoRuntimeException("Failed to re-initialise prefixesCache " + tenantDomain); + } + } + + return prefixes; + } + + /** + * Put prefixes into the cache + * + * @param tenantDomain + * @param prefixes + */ + private void putPrefixesCtx(String tenantDomain, Map prefixes) + { + try + { + writeLock.lock(); + prefixesCache.put(tenantDomain, prefixes); + } + finally + { + writeLock.unlock(); + } + } + + /** + * Remove prefixes from the cache + * + * @param tenantDomain + */ + private void removePrefixesCtx(String tenantDomain) + { + try + { + writeLock.lock(); + if (prefixesCache.get(tenantDomain) != null) + { + prefixesCache.get(tenantDomain).clear(); + prefixesCache.remove(tenantDomain); + } + } + finally + { + writeLock.unlock(); + } + } + + /** + * Local helper - returns tenant domain (or empty string if default non-tenant) + */ + private String getTenantDomain() + { + return tenantService.getCurrentUserDomain(); + } } diff --git a/source/java/org/alfresco/repo/dictionary/RepositoryLocation.java b/source/java/org/alfresco/repo/dictionary/RepositoryLocation.java new file mode 100755 index 0000000000..d3f2fac672 --- /dev/null +++ b/source/java/org/alfresco/repo/dictionary/RepositoryLocation.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.dictionary; + +import org.alfresco.repo.search.SearcherException; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.QName; + +/** + * Repository location object, defines a location in the repository from which dictionary models/resources should be loaded + * for inclusion in the data dictionary. + * + */ +public class RepositoryLocation +{ + /** Store protocol */ + private String storeProtocol = StoreRef.PROTOCOL_WORKSPACE; // default + + /** Store identifier */ + private String storeId = "SpacesStore"; // default + + /** Path */ + private String path = ""; // default + + /** Search Language */ + private String queryLanguage = "xpath"; // default + + + /** + */ + public RepositoryLocation() + { + } + + /** + * Constructor + * + * @param storeRef the store reference (e.g. 'workspace://SpacesStore' ) + * @param path the path (e.g. '/app:company_home/app:dictionary/app:models' ) + * @param queryLanguage the query language (e.g. 'xpath' or 'lucence') + */ + public RepositoryLocation(StoreRef storeRef, String path, String queryLanguage) + { + this.storeProtocol = storeRef.getProtocol(); + this.storeId = storeRef.getIdentifier(); + this.path = path; + + setQueryLanguage(queryLanguage); + } + + /** + * Set the store protocol + * + * @param storeProtocol the store protocol + */ + public void setStoreProtocol(String storeProtocol) + { + this.storeProtocol = storeProtocol; + } + + /** + * Set the store identifier + * + * @param storeId the store identifier + */ + public void setStoreId(String storeId) + { + this.storeId = storeId; + } + + /** + * Set the path + * + * Example path: /app:company_home/app:dictionary/app:models + * + * @param path the path + */ + public void setPath(String path) + { + this.path = path; + } + + /** + * Set the queru language + * + * @param path the search language + */ + public void setQueryLanguage(String queryLanguage) + { + if (queryLanguage.equals(SearchService.LANGUAGE_LUCENE) || queryLanguage.equals(SearchService.LANGUAGE_XPATH)) + { + this.queryLanguage = queryLanguage; + } + else + { + throw new SearcherException("Unknown query language: " + queryLanguage); + } + } + + /** + * Get the store reference + * + * @return the store reference + */ + public StoreRef getStoreRef() + { + return new StoreRef(this.storeProtocol, this.storeId); + } + + /** + * Get the path + * + * @return the path + */ + public String getPath() + { + return this.path; + } + + /** + * Get the query language + * + * @return the query language + */ + public String getQueryLanguage() + { + return this.queryLanguage; + } + + /** + * Get the Lucene query statement for models, based on the path + * + * @return the Lucene query statement + */ + public String getLuceneQueryStatement(QName contentModelType) + { + String result = "+TYPE:\"" + contentModelType.toString() + "\""; + + if (this.path != null) + { + result += " +PATH:\"" + this.path + "\""; + } + + return result; + } + + /** + * Get the XPath query statement for models, based on the path + * + * @return the XPath query statement + */ + public String getXPathQueryStatement(QName prefixResolvedContentModelType) + { + String result = "/*[subtypeOf('" + prefixResolvedContentModelType.toPrefixString() + "')]"; // immediate children only + + if (this.path != null) + { + result = this.path + result; + } + + return result; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/dictionary/TestModel.java b/source/java/org/alfresco/repo/dictionary/TestModel.java index f709ed4d44..689806c100 100644 --- a/source/java/org/alfresco/repo/dictionary/TestModel.java +++ b/source/java/org/alfresco/repo/dictionary/TestModel.java @@ -26,6 +26,15 @@ package org.alfresco.repo.dictionary; import java.util.ArrayList; import java.util.List; +import java.util.Map; + +import net.sf.ehcache.Cache; +import net.sf.ehcache.CacheManager; + +import org.alfresco.repo.cache.EhCacheAdapter; +import org.alfresco.repo.tenant.SingleTServiceImpl; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.namespace.QName; /** @@ -67,8 +76,17 @@ public class TestModel } // construct dictionary dao - NamespaceDAO namespaceDAO = new NamespaceDAOImpl(); + TenantService tenantService = new SingleTServiceImpl(); + + NamespaceDAOImpl namespaceDAO = new NamespaceDAOImpl(); + namespaceDAO.setTenantService(tenantService); + + initNamespaceCaches(namespaceDAO); + DictionaryDAOImpl dictionaryDAO = new DictionaryDAOImpl(namespaceDAO); + dictionaryDAO.setTenantService(tenantService); + + initDictionaryCaches(dictionaryDAO); // bootstrap dao try @@ -90,4 +108,42 @@ public class TestModel } } } + + private static void initDictionaryCaches(DictionaryDAOImpl dictionaryDAO) + { + CacheManager cacheManager = new CacheManager(); + + Cache uriToModelsEhCache = new Cache("uriToModelsCache", 50, false, true, 0L, 0L); + cacheManager.addCache(uriToModelsEhCache); + EhCacheAdapter>> uriToModelsCache = new EhCacheAdapter>>(); + uriToModelsCache.setCache(uriToModelsEhCache); + + dictionaryDAO.setUriToModelsCache(uriToModelsCache); + + Cache compileModelsEhCache = new Cache("compiledModelsCache", 50, false, true, 0L, 0L); + cacheManager.addCache(compileModelsEhCache); + EhCacheAdapter> compileModelCache = new EhCacheAdapter>(); + compileModelCache.setCache(compileModelsEhCache); + + dictionaryDAO.setCompiledModelsCache(compileModelCache); + } + + private static void initNamespaceCaches(NamespaceDAOImpl namespaceDAO) + { + CacheManager cacheManager = new CacheManager(); + + Cache urisEhCache = new Cache("urisCache", 50, false, true, 0L, 0L); + cacheManager.addCache(urisEhCache); + EhCacheAdapter> urisCache = new EhCacheAdapter>(); + urisCache.setCache(urisEhCache); + + namespaceDAO.setUrisCache(urisCache); + + Cache prefixesEhCache = new Cache("prefixesCache", 50, false, true, 0L, 0L); + cacheManager.addCache(prefixesEhCache); + EhCacheAdapter> prefixesCache = new EhCacheAdapter>(); + prefixesCache.setCache(prefixesEhCache); + + namespaceDAO.setPrefixesCache(prefixesCache); + } } \ No newline at end of file