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
This commit is contained in:
Jan Vonka
2007-09-04 14:23:55 +00:00
parent d51344044d
commit 6e854e6166
16 changed files with 1992 additions and 238 deletions

View File

@@ -345,4 +345,137 @@
</property>
</bean>
<!-- ===================================== -->
<!-- Dictionary / Namespace Caches -->
<!-- ===================================== -->
<!-- The cross-transaction shared cache for In-Memory UriToModels -->
<bean name="uriToModelsSharedCache" class="org.alfresco.repo.cache.EhCacheAdapter">
<property name="cache">
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean" >
<property name="cacheManager">
<ref bean="internalEHCacheManager" />
</property>
<property name="cacheName">
<value>org.alfresco.cache.uriToModelsCache</value>
</property>
</bean>
</property>
</bean>
<!-- The transactional cache for In-Memory UriToModels -->
<bean name="uriToModelsCache" class="org.alfresco.repo.cache.TransactionalCache">
<property name="sharedCache">
<ref bean="uriToModelsSharedCache" />
</property>
<property name="cacheManager" >
<ref bean="transactionalEHCacheManager" />
</property>
<property name="name">
<value>org.alfresco.uriToModelsTransactionalCache</value>
</property>
<property name="maxCacheSize">
<value>10</value>
</property>
</bean>
<!-- The cross-transaction shared cache for In-Memory CompiledModels -->
<bean name="compiledModelsSharedCache" class="org.alfresco.repo.cache.EhCacheAdapter">
<property name="cache">
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean" >
<property name="cacheManager">
<ref bean="internalEHCacheManager" />
</property>
<property name="cacheName">
<value>org.alfresco.cache.compiledModelsCache</value>
</property>
</bean>
</property>
</bean>
<!-- The transactional cache for In-Memory CompiledModels -->
<bean name="compiledModelsCache" class="org.alfresco.repo.cache.TransactionalCache">
<property name="sharedCache">
<ref bean="compiledModelsSharedCache" />
</property>
<property name="cacheManager" >
<ref bean="transactionalEHCacheManager" />
</property>
<property name="name">
<value>org.alfresco.compiledModelsTransactionalCache</value>
</property>
<property name="maxCacheSize">
<value>10</value>
</property>
</bean>
<!-- The cross-transaction shared cache for In-Memory Namespace Uris -->
<bean name="urisSharedCache" class="org.alfresco.repo.cache.EhCacheAdapter">
<property name="cache">
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean" >
<property name="cacheManager">
<ref bean="internalEHCacheManager" />
</property>
<property name="cacheName">
<value>org.alfresco.cache.urisCache</value>
</property>
</bean>
</property>
</bean>
<!-- The transactional cache for In-Memory Namespace Uris -->
<bean name="urisCache" class="org.alfresco.repo.cache.TransactionalCache">
<property name="sharedCache">
<ref bean="urisSharedCache" />
</property>
<property name="cacheManager" >
<ref bean="transactionalEHCacheManager" />
</property>
<property name="name">
<value>org.alfresco.urisTransactionalCache</value>
</property>
<property name="maxCacheSize">
<value>10</value>
</property>
</bean>
<!-- The cross-transaction shared cache for In-Memory Namespace Prefixes -->
<bean name="prefixesSharedCache" class="org.alfresco.repo.cache.EhCacheAdapter">
<property name="cache">
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean" >
<property name="cacheManager">
<ref bean="internalEHCacheManager" />
</property>
<property name="cacheName">
<value>org.alfresco.cache.prefixesCache</value>
</property>
</bean>
</property>
</bean>
<!-- The transactional cache for In-Memory Namespace Prefixes -->
<bean name="prefixesCache" class="org.alfresco.repo.cache.TransactionalCache">
<property name="sharedCache">
<ref bean="prefixesSharedCache" />
</property>
<property name="cacheManager" >
<ref bean="transactionalEHCacheManager" />
</property>
<property name="name">
<value>org.alfresco.prefixesTransactionalCache</value>
</property>
<property name="maxCacheSize">
<value>10</value>
</property>
</bean>
</beans>

View File

@@ -600,7 +600,19 @@
<!-- Data Dictionary -->
<!-- -->
<bean id="namespaceDAO" class="org.alfresco.repo.dictionary.NamespaceDAOImpl" />
<bean id="namespaceDAO" class="org.alfresco.repo.dictionary.NamespaceDAOImpl">
<property name="tenantService">
<ref bean="tenantService"/>
</property>
<property name="urisCache">
<ref bean="urisCache"/>
</property>
<property name="prefixesCache">
<ref bean="prefixesCache"/>
</property>
</bean>
<bean id="dictionaryModelType" class="org.alfresco.repo.dictionary.DictionaryModelType" init-method="init">
<property name="dictionaryDAO">
@@ -624,6 +636,15 @@
<constructor-arg index="0">
<ref bean="namespaceDAO" />
</constructor-arg>
<property name="tenantService">
<ref bean="tenantService"/>
</property>
<property name="uriToModelsCache">
<ref bean="uriToModelsCache"/>
</property>
<property name="compiledModelsCache">
<ref bean="compiledModelsCache"/>
</property>
</bean>
<bean id="dictionaryService" class="org.alfresco.repo.dictionary.DictionaryComponent" depends-on="dictionaryBootstrap">
@@ -641,6 +662,9 @@
<bean id="dictionaryModelBootstrap" class="org.alfresco.repo.dictionary.DictionaryBootstrap" init-method="bootstrap" abstract="true">
<property name="dictionaryDAO"><ref local="dictionaryDAO"/></property>
<property name="tenantService">
<ref bean="tenantService"/>
</property>
</bean>
<bean id="dictionaryBootstrap" parent="dictionaryModelBootstrap" depends-on="resourceBundles">
@@ -691,24 +715,6 @@
</property>
</bean>
<bean id="dictionaryRepositoryBootstrap" class="org.alfresco.repo.dictionary.DictionaryRepositoryBootstrap" init-method="bootstrap">
<property name="dictionaryDAO">
<ref local="dictionaryDAO"/>
</property>
<property name="contentService">
<ref bean="contentService"/>
</property>
<property name="searchService">
<ref bean="searchService"/>
</property>
<property name="transactionService">
<ref bean="transactionService"/>
</property>
<property name="authenticationComponent">
<ref bean="authenticationComponent"/>
</property>
</bean>
<!-- -->
<!-- Copy Service -->
<!-- -->

View File

@@ -354,4 +354,34 @@
overflowToDisk="false"
/>
<!-- Dictionary / Namespace (tenant-based) -->
<cache
name="org.alfresco.cache.uriToModelsCache"
maxElementsInMemory="100"
eternal="true"
overflowToDisk="false"
/>
<cache
name="org.alfresco.cache.compiledModelsCache"
maxElementsInMemory="100"
eternal="true"
overflowToDisk="false"
/>
<cache
name="org.alfresco.cache.urisCache"
maxElementsInMemory="100"
eternal="true"
overflowToDisk="false"
/>
<cache
name="org.alfresco.cache.prefixesCache"
maxElementsInMemory="100"
eternal="true"
overflowToDisk="false"
/>
</ehcache>

View File

@@ -650,6 +650,70 @@
</cache>
<!-- Dictionary / Namespace (tenant-based) -->
<cache
name="org.alfresco.cache.uriToModelsCache"
maxElementsInMemory="100"
eternal="true"
overflowToDisk="false">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicatePuts = false,
replicateUpdates = true,
replicateRemovals = true,
replicateUpdatesViaCopy = false,
replicateAsynchronously = false"/>
</cache>
<cache
name="org.alfresco.cache.compiledModelsCache"
maxElementsInMemory="100"
eternal="true"
overflowToDisk="false">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicatePuts = false,
replicateUpdates = true,
replicateRemovals = true,
replicateUpdatesViaCopy = false,
replicateAsynchronously = false"/>
</cache>
<cache
name="org.alfresco.cache.urisCache"
maxElementsInMemory="100"
eternal="true"
overflowToDisk="false">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicatePuts = false,
replicateUpdates = true,
replicateRemovals = true,
replicateUpdatesViaCopy = false,
replicateAsynchronously = false"/>
</cache>
<cache
name="org.alfresco.cache.prefixesCache"
maxElementsInMemory="100"
eternal="true"
overflowToDisk="false">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicatePuts = false,
replicateUpdates = true,
replicateRemovals = true,
replicateUpdatesViaCopy = false,
replicateAsynchronously = false"/>
</cache>
</ehcache>

View File

@@ -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<String> models = new ArrayList<String>();
@@ -51,6 +52,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)
{
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);
initDictionary();
initStaticMessages();
M2Model model = M2Model.createModel(modelStream);
dictionaryDAO.putModel(model);
}
catch(DictionaryException e)
register();
}
/**
* Register with the Dictionary
*/
public void register()
{
dictionaryDAO.register(this);
}
/**
* Populate the Dictionary
*/
public void initDictionary()
{
if ((tenantService == null) || (! tenantService.isTenantUser()))
{
// register models
for (String bootstrapModel : models)
{
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);
}
}
}

View File

@@ -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;
@@ -340,5 +340,25 @@ 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();
}
}

View File

@@ -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<PropertyDefinition> 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();
}

View File

@@ -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<String, List<CompiledModel>> uriToModels = new HashMap<String, List<CompiledModel>>();
private SimpleCache<String, Map<String, List<CompiledModel>>> uriToModelsCache;
// Map of model name to compiled model
private Map<QName,CompiledModel> compiledModels = new HashMap<QName,CompiledModel>();
private SimpleCache<String, Map<QName,CompiledModel>> compiledModelsCache;
// Static list of registered dictionary deployers
private List<DictionaryDeployer> dictionaryDeployers = new ArrayList<DictionaryDeployer>();
// Logger
private static Log logger = LogFactory.getLog(DictionaryDAO.class);
// inject dependencies
public void setTenantService(TenantService tenantService)
{
this.tenantService = tenantService;
}
public void setUriToModelsCache(SimpleCache<String, Map<String, List<CompiledModel>>> uriToModelsCache)
{
this.uriToModelsCache = uriToModelsCache;
}
public void setCompiledModelsCache(SimpleCache<String, Map<QName,CompiledModel>> 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<QName,CompiledModel>());
putUriToModels(tenantDomain, new HashMap<String, List<CompiledModel>>());
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<Object>()
{
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<CompiledModel> models = uriToModels.get(uri);
List<CompiledModel> models = getUriToModels().get(uri);
if (models == null)
{
models = new ArrayList<CompiledModel>();
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<CompiledModel> models = uriToModels.get(uri);
List<CompiledModel> models = getUriToModels().get(uri);
if (models != null)
{
models.remove(model);
@@ -203,12 +335,69 @@ public class DictionaryDAOImpl implements DictionaryDAO
*/
private List<CompiledModel> getModelsForUri(String uri)
{
List<CompiledModel> 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<CompiledModel> models = getUriToModels("").get(uri);
List<CompiledModel> filteredModels = new ArrayList<CompiledModel>();
if (models != null)
{
filteredModels.addAll(models);
}
// get tenant models (if any)
List<CompiledModel> 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<CompiledModel> models = getUriToModels().get(uri);
if (models == null)
{
models = Collections.emptyList();
}
return models;
}
}
@@ -218,10 +407,21 @@ public class DictionaryDAOImpl implements DictionaryDAO
*/
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)
// get tenant models (if any)
for (CompiledModel model : getCompiledModels().values())
{
return dataTypeDef;
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<CompiledModel> 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<QName> getModels()
{
return compiledModels.keySet();
if (tenantService.isTenantUser())
{
// return all tenant-specific models and all shared (non-overridden) models
Collection<QName> filteredModels = new ArrayList<QName>();
Collection<QName> tenantModels = new ArrayList<QName>();
Collection<QName> nontenantModels = new ArrayList<QName>();
// 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<QName> 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<QName,CompiledModel> getCompiledModels()
{
return getCompiledModels(getTenantDomain());
}
/**
* Get compiledModels from the cache (in the context of the given tenant domain)
*
* @param tenantDomain
*/
private Map<QName,CompiledModel> getCompiledModels(String tenantDomain)
{
Map<QName,CompiledModel> 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<QName, CompiledModel> 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<String, List<CompiledModel>> getUriToModels()
{
return getUriToModels(getTenantDomain());
}
/**
* Get uriToModels from the cache (in the context of the given tenant domain)
*
* @param tenantDomain
*/
private Map<String, List<CompiledModel>> getUriToModels(String tenantDomain)
{
Map<String, List<CompiledModel>> 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<String, List<CompiledModel>> 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();
}
}

View File

@@ -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<String, Map<String, List<CompiledModel>>> uriToModelsCache = new EhCacheAdapter<String, Map<String, List<CompiledModel>>>();
uriToModelsCache.setCache(uriToModelsEhCache);
dictionaryDAO.setUriToModelsCache(uriToModelsCache);
Cache compileModelsEhCache = new Cache("compiledModelsCache", 50, false, true, 0L, 0L);
cacheManager.addCache(compileModelsEhCache);
EhCacheAdapter<String, Map<QName,CompiledModel>> compileModelCache = new EhCacheAdapter<String, Map<QName,CompiledModel>>();
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<String, List<String>> urisCache = new EhCacheAdapter<String, List<String>>();
urisCache.setCache(urisEhCache);
namespaceDAO.setUrisCache(urisCache);
Cache prefixesEhCache = new Cache("prefixesCache", 50, false, true, 0L, 0L);
cacheManager.addCache(prefixesEhCache);
EhCacheAdapter<String, Map<String, String>> prefixesCache = new EhCacheAdapter<String, Map<String, String>>();
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<String> bootstrapModels = new ArrayList<String>();
@@ -107,6 +161,7 @@ public class DictionaryDAOTest extends TestCase
bootstrap.setModels(bootstrapModels);
bootstrap.setDictionaryDAO(dictionaryDAO);
bootstrap.setTenantService(tenantService);
bootstrap.bootstrap();
}

View File

@@ -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.
* <p>
* 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();
}

View File

@@ -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
public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean implements TenantDeployer, DictionaryDeployer, MessageDeployer
{
/** Loactions in the respository fro which models should be loaded */
private List<RepositoryLocation> repositoryLocations = new ArrayList<RepositoryLocation>();
// 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<RepositoryLocation> repositoryModelsLocations = new ArrayList<RepositoryLocation>();
/** Locations in the repository from which messages should be loaded */
private List<RepositoryLocation> repositoryMessagesLocations = new ArrayList<RepositoryLocation>();
/** Dictionary DAO */
private DictionaryDAO dictionaryDAO = null;
@@ -60,12 +77,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
*
@@ -96,6 +125,56 @@ 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<RepositoryLocation> 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<RepositoryLocation> 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<Object>()
{
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()
{
Map<String, M2Model> modelMap = new HashMap<String, M2Model>();
// NOOP - will be destroyed directly via DictionaryComponent
}
// Register the models found in the respository
for (RepositoryLocation repositoryLocation : this.repositoryLocations)
public void initDictionary()
{
if (this.repositoryModelsLocations != null)
{
ResultSet resultSet = null;
try
{
resultSet = this.searchService.query(repositoryLocation.getStoreRef(), SearchService.LANGUAGE_LUCENE, repositoryLocation.getQueryStatement());
Map<String, M2Model> modelMap = new HashMap<String, M2Model>();
for (NodeRef dictionaryModel : resultSet.getNodeRefs())
// Register the models found in the repository
for (RepositoryLocation repositoryLocation : this.repositoryModelsLocations)
{
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<NodeRef> 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<String> loadedModels = new ArrayList<String>();
for (Map.Entry<String, M2Model> 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<String> loadedModels = new ArrayList<String>();
for (Map.Entry<String, M2Model> 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<NodeRef> nodeRefs = searchService.selectNodes(rootNode,
repositoryLocation.getXPathQueryStatement(ContentModel.TYPE_CONTENT.getPrefixedQName(namespaceService)),
null,
namespaceService,
false);
List<String> resourceBundleBaseNames = new ArrayList<String>();
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
@@ -241,76 +400,91 @@ public class DictionaryRepositoryBootstrap
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<Object>()
{
this.storeProtocol = storeProtocol;
}
/**
* Set the store identifier
*
* @param storeId the store identifier
*/
public void setStoreId(String storeId)
{
this.storeId = storeId;
}
/**
* Set the path
*
* @param path the path
*/
public void setPath(String path)
{
this.path = path;
}
/**
* Get the store reference
*
* @return the store reference
*/
public StoreRef getStoreRef()
{
return new StoreRef(this.storeProtocol, this.storeId);
}
/**
* Get the query statement, based on the path
*
* @return the query statement
*/
public String getQueryStatement()
{
String result = "+TYPE:\"" + ContentModel.TYPE_DICTIONARY_MODEL.toString() + "\"";
if (this.path != null)
public Object doWork()
{
result += " +PATH:\"" + this.path + "\"";
init();
return null;
}
return result;
}, AuthenticationUtil.getSystemUserName());
if (tenantService.isEnabled())
{
tenantDeployerService.deployTenants(this, logger);
}
register();
}
@Override
protected void onShutdown(ApplicationEvent event)
{
unregister();
// run as System on shutdown
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
public Object doWork()
{
destroy();
return null;
}
}, AuthenticationUtil.getSystemUserName());
if (tenantService.isEnabled())
{
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);
if (tenantService.isEnabled())
{
// 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);
}
}
}

View File

@@ -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;
@@ -68,8 +70,17 @@ 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()
@@ -86,23 +97,34 @@ 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.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);
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 = this.bootstrap.new RepositoryLocation();
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<RepositoryLocation> locations = new ArrayList<RepositoryLocation>();
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(

View File

@@ -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);
}

View File

@@ -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);
private List<String> uris = new ArrayList<String>();
private HashMap<String, String> prefixes = new HashMap<String, String>();
/**
* Lock objects
*/
private ReadWriteLock lock = new ReentrantReadWriteLock();
private Lock readLock = lock.readLock();
private Lock writeLock = lock.writeLock();
// internal caches that are clusterable
private SimpleCache<String, List<String>> urisCache;
private SimpleCache<String, Map<String, String>> prefixesCache;
// Dependencies
private TenantService tenantService;
public void setTenantService(TenantService tenantService)
{
this.tenantService = tenantService;
}
public void setUrisCache(SimpleCache<String, List<String>> urisCache)
{
this.urisCache = urisCache;
}
public void setPrefixesCache(SimpleCache<String, Map<String, String>> 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<String>());
putPrefixesCtx(tenantDomain, new HashMap<String, String>());
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<String> getURIs()
{
return Collections.unmodifiableCollection(uris);
if (! tenantService.isTenantUser())
{
return Collections.unmodifiableCollection(getUrisCtx());
}
else
{
// Get tenant-specific URIs
List<String> domainUris = getUrisCtx();
// Get non-tenant-specific URIs (and filter out, if overridden)
List<String> urisFiltered = new ArrayList<String>();
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);
}
}
@@ -53,7 +184,32 @@ public class NamespaceDAOImpl implements NamespaceDAO
*/
public Collection<String> getPrefixes()
{
return Collections.unmodifiableCollection(prefixes.keySet());
if (! tenantService.isTenantUser())
{
return Collections.unmodifiableCollection(getPrefixesCtx().keySet());
}
else
{
// Get tenant-specific prefixes
Collection<String> domainPrefixes = getPrefixesCtx().keySet();
// Get non-tenant-specific URIs (and filter out, if overridden)
List<String> prefixesFiltered = new ArrayList<String>();
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<String> getPrefixes(String URI)
{
Collection<String> uriPrefixes = new ArrayList<String>();
for (String key : prefixes.keySet())
if (! tenantService.isTenantUser())
{
String uri = prefixes.get(key);
if ((uri != null) && (uri.equals(URI)))
Collection<String> uriPrefixes = new ArrayList<String>();
for (String key : getPrefixesCtx().keySet())
{
uriPrefixes.add(key);
String uri = getPrefixesCtx().get(key);
if ((uri != null) && (uri.equals(URI)))
{
uriPrefixes.add(key);
}
}
return uriPrefixes;
}
else
{
// check domain prefixes
Collection<String> domainUriPrefixes = new ArrayList<String>();
for (String key : getPrefixesCtx().keySet())
{
String uri = getPrefixesCtx().get(key);
if ((uri != null) && (uri.equals(URI)))
{
domainUriPrefixes.add(key);
}
}
// check non-domain prefixes
Collection<String> uriPrefixes = new ArrayList<String>();
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;
}
return uriPrefixes;
}
/**
* Get URIs from the cache (in the context of the current user's tenant domain)
*
* @return URIs
*/
private List<String> getUrisCtx()
{
return getUrisCtx(getTenantDomain());
}
/**
* Get URIs from the cache
*
* @param tenantDomain
* @return URIs
*/
private List<String> getUrisCtx(String tenantDomain)
{
List<String> 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<String> 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<String, String> getPrefixesCtx()
{
return getPrefixesCtx(getTenantDomain());
}
/**
* Get prefixes from the cache
*
* @param tenantDomain
* @return prefixes
*/
private Map<String, String> getPrefixesCtx(String tenantDomain)
{
Map<String, String> 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<String, String> 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();
}
}

View File

@@ -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;
}
}

View File

@@ -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<String, Map<String, List<CompiledModel>>> uriToModelsCache = new EhCacheAdapter<String, Map<String, List<CompiledModel>>>();
uriToModelsCache.setCache(uriToModelsEhCache);
dictionaryDAO.setUriToModelsCache(uriToModelsCache);
Cache compileModelsEhCache = new Cache("compiledModelsCache", 50, false, true, 0L, 0L);
cacheManager.addCache(compileModelsEhCache);
EhCacheAdapter<String, Map<QName,CompiledModel>> compileModelCache = new EhCacheAdapter<String, Map<QName,CompiledModel>>();
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<String, List<String>> urisCache = new EhCacheAdapter<String, List<String>>();
urisCache.setCache(urisEhCache);
namespaceDAO.setUrisCache(urisCache);
Cache prefixesEhCache = new Cache("prefixesCache", 50, false, true, 0L, 0L);
cacheManager.addCache(prefixesEhCache);
EhCacheAdapter<String, Map<String, String>> prefixesCache = new EhCacheAdapter<String, Map<String, String>>();
prefixesCache.setCache(prefixesEhCache);
namespaceDAO.setPrefixesCache(prefixesCache);
}
}