/* * 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 java.util.ArrayList; import java.util.Collection; import java.util.Collections; 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; import org.alfresco.service.cmr.dictionary.ConstraintDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryException; 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.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Default implementation of the Dictionary. * * @author David Caruana * */ public class DictionaryDAOImpl implements DictionaryDAO { // TODO: Allow for the dynamic creation of models. Supporting // this requires the ability to persistently store the // 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 SimpleCache>> uriToModelsCache; // Map of model name to compiled model 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 * * @param namespaceDAO namespace data access */ 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 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 = getCompiledModels().get(modelName); if (previousVersion != null) { for (M2Namespace namespace : previousVersion.getM2Model().getNamespaces()) { namespaceDAO.removePrefix(namespace.getPrefix()); namespaceDAO.removeURI(namespace.getUri()); unmapUriToModel(namespace.getUri(), previousVersion); } for (M2Namespace importNamespace : previousVersion.getM2Model().getImports()) { unmapUriToModel(importNamespace.getUri(), previousVersion); } } // Create namespace definitions for new model for (M2Namespace namespace : model.getNamespaces()) { namespaceDAO.addURI(namespace.getUri()); namespaceDAO.addPrefix(namespace.getPrefix(), namespace.getUri()); mapUriToModel(namespace.getUri(), compiledModel); } for (M2Namespace importNamespace : model.getImports()) { mapUriToModel(importNamespace.getUri(), compiledModel); } // Publish new Model Definition getCompiledModels().put(modelName, compiledModel); if (logger.isInfoEnabled()) { logger.info("Registered model " + modelName.toPrefixString(namespaceDAO)); for (M2Namespace namespace : model.getNamespaces()) { logger.info("Registered namespace '" + namespace.getUri() + "' (prefix '" + namespace.getPrefix() + "')"); } } return modelName; } /** * @see org.alfresco.repo.dictionary.DictionaryDAO#removeModel(org.alfresco.service.namespace.QName) */ public void removeModel(QName modelName) { CompiledModel compiledModel = getCompiledModels().get(modelName); if (compiledModel != null) { // Remove the namespaces from the namespace service M2Model model = compiledModel.getM2Model(); for (M2Namespace namespace : model.getNamespaces()) { namespaceDAO.removePrefix(namespace.getPrefix()); namespaceDAO.removeURI(namespace.getUri()); unmapUriToModel(namespace.getUri(), compiledModel); } // Remove the model from the list getCompiledModels().remove(modelName); } } /** * Map Namespace URI to Model * * @param uri namespace uri * @param model model */ private void mapUriToModel(String uri, CompiledModel model) { List models = getUriToModels().get(uri); if (models == null) { models = new ArrayList(); getUriToModels().put(uri, models); } if (!models.contains(model)) { models.add(model); } } /** * Unmap Namespace URI from Model * * @param uri namespace uri * @param model model */ private void unmapUriToModel(String uri, CompiledModel model) { List models = getUriToModels().get(uri); if (models != null) { models.remove(model); } } /** * Get Models mapped to Namespace Uri * * @param uri namespace uri * @return mapped models */ private List getModelsForUri(String uri) { // 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) { 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) { throw new DictionaryException("d_dictionary.model.err.no_model", modelName); } return model; } /* (non-Javadoc) * @see org.alfresco.repo.dictionary.impl.ModelQuery#getPropertyType(org.alfresco.repo.ref.QName) */ public DataTypeDefinition getDataType(QName typeName) { List models = getModelsForUri(typeName.getNamespaceURI()); for (CompiledModel model : models) { DataTypeDefinition dataType = model.getDataType(typeName); if (dataType != null) { return dataType; } } return null; } /* (non-Javadoc) * @see org.alfresco.repo.dictionary.ModelQuery#getDataType(java.lang.Class) */ public DataTypeDefinition getDataType(Class javaClass) { if (tenantService.isTenantUser() == true) { // 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; } /* (non-Javadoc) * @see org.alfresco.repo.dictionary.impl.DictionaryDAO#getPropertyTypes(org.alfresco.repo.ref.QName) */ public Collection getDataTypes(QName modelName) { CompiledModel model = getCompiledModel(modelName); return model.getDataTypes(); } /* (non-Javadoc) * @see org.alfresco.repo.dictionary.impl.ModelQuery#getType(org.alfresco.repo.ref.QName) */ public TypeDefinition getType(QName typeName) { List models = getModelsForUri(typeName.getNamespaceURI()); for (CompiledModel model : models) { TypeDefinition type = model.getType(typeName); if (type != null) { return type; } } return null; } /* (non-Javadoc) * @see org.alfresco.repo.dictionary.impl.ModelQuery#getAspect(org.alfresco.repo.ref.QName) */ public AspectDefinition getAspect(QName aspectName) { List models = getModelsForUri(aspectName.getNamespaceURI()); for (CompiledModel model : models) { AspectDefinition aspect = model.getAspect(aspectName); if (aspect != null) { return aspect; } } return null; } /* (non-Javadoc) * @see org.alfresco.repo.dictionary.impl.ModelQuery#getClass(org.alfresco.repo.ref.QName) */ 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); if (classDef != null) { return classDef; } } return null; } /* (non-Javadoc) * @see org.alfresco.repo.dictionary.impl.ModelQuery#getProperty(org.alfresco.repo.ref.QName) */ public PropertyDefinition getProperty(QName propertyName) { List models = getModelsForUri(propertyName.getNamespaceURI()); for (CompiledModel model : models) { PropertyDefinition propDef = model.getProperty(propertyName); if (propDef != null) { return propDef; } } return null; } /* (non-Javadoc) * @see org.alfresco.repo.dictionary.ModelQuery#getConstraint(org.alfresco.service.namespace.QName) */ public ConstraintDefinition getConstraint(QName constraintQName) { List models = getModelsForUri(constraintQName.getNamespaceURI()); for (CompiledModel model : models) { ConstraintDefinition constraintDef = model.getConstraint(constraintQName); if (constraintDef != null) { return constraintDef; } } return null; } /* (non-Javadoc) * @see org.alfresco.repo.dictionary.impl.ModelQuery#getAssociation(org.alfresco.repo.ref.QName) */ public AssociationDefinition getAssociation(QName assocName) { List models = getModelsForUri(assocName.getNamespaceURI()); for (CompiledModel model : models) { AssociationDefinition assocDef = model.getAssociation(assocName); if (assocDef != null) { return assocDef; } } return null; } /* (non-Javadoc) * @see org.alfresco.repo.dictionary.impl.DictionaryDAO#getModels() */ public Collection getModels() { 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) */ public ModelDefinition getModel(QName name) { CompiledModel model = getCompiledModel(name); if (model != null) { return model.getModelDefinition(); } return null; } /* (non-Javadoc) * @see org.alfresco.repo.dictionary.impl.DictionaryDAO#getTypes(org.alfresco.repo.ref.QName) */ public Collection getTypes(QName modelName) { CompiledModel model = getCompiledModel(modelName); return model.getTypes(); } /* (non-Javadoc) * @see org.alfresco.repo.dictionary.impl.DictionaryDAO#getAspects(org.alfresco.repo.ref.QName) */ public Collection getAspects(QName modelName) { CompiledModel model = getCompiledModel(modelName); return model.getAspects(); } /* (non-Javadoc) * @see org.alfresco.repo.dictionary.impl.DictionaryDAO#getAnonymousType(org.alfresco.repo.ref.QName, java.util.Collection) */ public TypeDefinition getAnonymousType(QName type, Collection aspects) { TypeDefinition typeDef = getType(type); if (typeDef == null) { throw new DictionaryException("d_dictionary.model.err.type_not_found", type); } Collection aspectDefs = new ArrayList(); if (aspects != null) { for (QName aspect : aspects) { AspectDefinition aspectDef = getAspect(aspect); if (aspectDef == null) { throw new DictionaryException("d_dictionary.model.err.aspect_not_found", aspect); } aspectDefs.add(aspectDef); } } return new M2AnonymousTypeDefinition(typeDef, aspectDefs); } /* * (non-Javadoc) * @see org.alfresco.repo.dictionary.DictionaryDAO#getProperties(org.alfresco.service.namespace.QName) */ public Collection getProperties(QName modelName) { CompiledModel model = getCompiledModel(modelName); return model.getProperties(); } /* * (non-Javadoc) * @see org.alfresco.repo.dictionary.DictionaryDAO#getProperties(org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName) */ public Collection getProperties(QName modelName, QName dataType) { HashSet properties = new HashSet(); Collection props = getProperties(modelName); for(PropertyDefinition prop : props) { if((dataType == null) || prop.getDataType().getName().equals(dataType)) { properties.add(prop); } } 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(); } }