Merged HEAD-BUG-FIX (5.0/Cloud) to HEAD (5.0/Cloud)

77150: Merged PLATFORM1 (5.0/Cloud) to HEAD-BUG-FIX (5.0/Cloud)
      73977: ACE-1802 "MT / Cloud Restrict namespace URI of dynamic models."
      ACE-955 "Custom Content Models in Cloud"


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@78008 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Mark Rogers
2014-07-23 16:02:04 +00:00
parent f1310c6c8d
commit a576035f0f
32 changed files with 1576 additions and 583 deletions

View File

@@ -21,14 +21,11 @@ package org.alfresco.repo.dictionary;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.ContentServicePolicies;
import org.alfresco.repo.lock.JobLockService;
@@ -38,34 +35,17 @@ import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.tenant.Tenant;
import org.alfresco.repo.tenant.TenantAdminService;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.repo.version.Version2Model;
import org.alfresco.repo.workflow.BPMEngineRegistry;
import org.alfresco.service.cmr.dictionary.AspectDefinition;
import org.alfresco.service.cmr.dictionary.ClassDefinition;
import org.alfresco.service.cmr.dictionary.ConstraintDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryException;
import org.alfresco.service.cmr.dictionary.ModelDefinition;
import org.alfresco.service.cmr.dictionary.NamespaceDefinition;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
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.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition;
import org.alfresco.service.namespace.NamespaceException;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
@@ -116,30 +96,18 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
/** The policy component */
private PolicyComponent policyComponent;
/** The workflow service */
private WorkflowService workflowService;
/** The search service */
private SearchService searchService;
/** The namespace service */
private NamespaceService namespaceService;
/** The tenant service */
private TenantService tenantService;
/** The tenant deployer service */
private TenantAdminService tenantAdminService;
private TransactionService transactionService;
private JobLockService jobLockService;
/** Transaction listener */
private DictionaryModelTypeTransactionListener transactionListener;
private List<String> storeUrls; // stores against which model deletes should be validated
private ModelValidator modelValidator;
/** Validation marker */
private boolean doValidation = true;
@@ -150,8 +118,13 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
{
this.dictionaryDAO = dictionaryDAO;
}
/**
public void setModelValidator(ModelValidator modelValidator)
{
this.modelValidator = modelValidator;
}
/**
* Set the namespace DOA
*/
public void setNamespaceDAO(NamespaceDAO namespaceDAO)
@@ -182,31 +155,7 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
{
this.policyComponent = policyComponent;
}
/**
* Set the workflow service
*/
public void setWorkflowService(WorkflowService workflowService)
{
this.workflowService = workflowService;
}
/**
* Set the search service
*/
public void setSearchService(SearchService searchService)
{
this.searchService = searchService;
}
/**
* Set the namespace service
*/
public void setNamespaceService(NamespaceService namespaceService)
{
this.namespaceService = namespaceService;
}
/**
* Set the tenant service
*/
@@ -214,15 +163,7 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
{
this.tenantService = tenantService;
}
/**
* Set the tenant admin service
*/
public void setTenantAdminService(TenantAdminService tenantAdminService)
{
this.tenantAdminService = tenantAdminService;
}
/**
* Set the transaction service
*/
@@ -235,12 +176,7 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
{
this.jobLockService = jobLockService;
}
public void setStoreUrls(List<String> storeUrls)
{
this.storeUrls = storeUrls;
}
public void setDoValidation(boolean doValidation)
{
this.doValidation = doValidation;
@@ -403,7 +339,7 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
if (modelName != null)
{
// Validate model delete against usages - content and/or workflows
validateModelDelete(modelName);
modelValidator.validateModelDelete(modelName);
Set<NodeRef> pendingModelDeletes = (Set<NodeRef>)AlfrescoTransactionSupport.getResource(KEY_PENDING_DELETE_MODELS);
if (pendingModelDeletes == null)
@@ -484,7 +420,7 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
}
}
}
/**
* Dictionary model type transaction listener class.
*/
@@ -693,10 +629,11 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
// Validate model against dictionary - could be new, unchanged or updated
if (doValidation == true)
{
validateModel(modelDefinition.getName(), m2Model, compiledModel);
modelValidator.validateModel(compiledModel);
}
// invalidate - to force lazy re-init
// TODO
//dictionaryDAO.destroy();
if (logger.isTraceEnabled())
@@ -711,7 +648,7 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
if (modelName != null)
{
// Validate model delete against usages - content and/or workflows
validateModelDelete(modelName);
modelValidator.validateModelDelete(modelName);
// invalidate - to force lazy re-init
//dictionaryDAO.destroy();
@@ -752,314 +689,4 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
}
}
}
/**
* validate against repository contents / workflows (e.g. when deleting an existing model)
*
* @param modelName
*/
private void validateModelDelete(final QName modelName)
{
// TODO add model locking during delete (would need to be tenant-aware & cluster-aware) to avoid potential
// for concurrent addition of new content/workflow as model is being deleted
final Collection<NamespaceDefinition> namespaceDefs;
final Collection<TypeDefinition> typeDefs;
final Collection<AspectDefinition> aspectDefs;
try
{
namespaceDefs = dictionaryDAO.getNamespaces(modelName);
typeDefs = dictionaryDAO.getTypes(modelName);
aspectDefs = dictionaryDAO.getAspects(modelName);
}
catch (DictionaryException e)
{
if (logger.isDebugEnabled())
{
logger.debug("Dictionary model '" + modelName + "' does not exist ... skip delete validation : " + e);
}
return;
}
// TODO - in case of MT we do not currently allow deletion of an overridden model (with usages) ... but could allow if (re-)inherited model is equivalent to an incremental update only ?
validateModelDelete(namespaceDefs, typeDefs, aspectDefs, false);
if (tenantService.isEnabled() && tenantService.isTenantUser() == false)
{
// shared model - need to check all tenants (whether enabled or disabled) unless they have overridden
List<Tenant> tenants = tenantAdminService.getAllTenants();
for (Tenant tenant : tenants)
{
// validate model delete within context of tenant domain
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
public Object doWork()
{
if (dictionaryDAO.isModelInherited(modelName))
{
validateModelDelete(namespaceDefs, typeDefs, aspectDefs, true);
}
return null;
}
}, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenant.getTenantDomain()));
}
}
}
private void validateModelDelete(Collection<NamespaceDefinition> namespaceDefs, Collection<TypeDefinition> typeDefs, Collection<AspectDefinition> aspectDefs, boolean sharedModel)
{
String tenantDomain = TenantService.DEFAULT_DOMAIN;
if (sharedModel)
{
tenantDomain = " for tenant [" + tenantService.getCurrentUserDomain() + "]";
}
List<WorkflowDefinition> workflowDefs = workflowService.getDefinitions();
if (workflowDefs.size() > 0)
{
if (namespaceDefs.size() > 0)
{
// check workflow namespace usage
for (WorkflowDefinition workflowDef : workflowDefs)
{
String workflowDefName = workflowDef.getName();
String workflowNamespaceURI = null;
try
{
workflowNamespaceURI = QName.createQName(BPMEngineRegistry.getLocalId(workflowDefName), namespaceService).getNamespaceURI();
}
catch (NamespaceException ne)
{
logger.warn("Skipped workflow when validating model delete - unknown namespace: "+ne);
continue;
}
for (NamespaceDefinition namespaceDef : namespaceDefs)
{
if (workflowNamespaceURI.equals(namespaceDef.getUri()))
{
throw new AlfrescoRuntimeException("Failed to validate model delete" + tenantDomain + " - found workflow process definition " + workflowDefName + " using model namespace '" + namespaceDef.getUri() + "'");
}
}
}
}
}
// check for type usages
for (TypeDefinition type : typeDefs)
{
validateClass(tenantDomain, type);
}
// check for aspect usages
for (AspectDefinition aspect : aspectDefs)
{
validateClass(tenantDomain, aspect);
}
}
private void validateClass(String tenantDomain, ClassDefinition classDef)
{
QName className = classDef.getName();
String classType = "TYPE";
if (classDef instanceof AspectDefinition)
{
classType = "ASPECT";
}
for (String storeUrl : this.storeUrls)
{
StoreRef store = new StoreRef(storeUrl);
// search for TYPE or ASPECT - TODO - alternative would be to extract QName and search by namespace ...
ResultSet rs = searchService.query(store, SearchService.LANGUAGE_LUCENE, classType+":\""+className+"\"");
try
{
if (rs.length() > 0)
{
throw new AlfrescoRuntimeException("Failed to validate model delete" + tenantDomain + " - found " + rs.length() + " nodes in store " + store + " with " + classType + " '" + className + "'" );
}
}
finally
{
rs.close();
}
}
// check against workflow task usage
for (WorkflowDefinition workflowDef : workflowService.getDefinitions())
{
for (WorkflowTaskDefinition workflowTaskDef : workflowService.getTaskDefinitions(workflowDef.getId()))
{
TypeDefinition workflowTypeDef = workflowTaskDef.metadata;
if (workflowTypeDef.getName().equals(className))
{
throw new AlfrescoRuntimeException("Failed to validate model delete" + tenantDomain + " - found task definition in workflow " + workflowDef.getName() + " with " + classType + " '" + className + "'");
}
}
}
}
/**
* validate against dictionary
*
* if new model
* then nothing to validate
*
* else if an existing model
* then could be updated (or unchanged) so validate to currently only allow incremental updates
* - addition of new types, aspects (except default aspects), properties, associations
* - no deletion of types, aspects or properties or associations
* - no addition, update or deletion of default/mandatory aspects
*
* @paramn modelName
* @param newOrUpdatedModel
*/
private void validateModel(QName modelName, M2Model model, CompiledModel compiledModel)
{
List<M2ModelDiff> modelDiffs = dictionaryDAO.diffModel(model);
for (M2ModelDiff modelDiff : modelDiffs)
{
if (modelDiff.getDiffType().equals(M2ModelDiff.DIFF_DELETED))
{
// TODO - check tenants if model is shared / inherited
if (modelDiff.getElementType().equals(M2ModelDiff.TYPE_PROPERTY))
{
validatePropertyDelete(modelName, modelDiff.getElementName(), false);
continue;
}
else if (modelDiff.getElementType().equals(M2ModelDiff.TYPE_CONSTRAINT))
{
validateConstraintDelete(compiledModel, modelDiff.getElementName(), false);
continue;
}
else
{
throw new AlfrescoRuntimeException("Failed to validate model update - found deleted " + modelDiff.getElementType() + " '" + modelDiff.getElementName() + "'");
}
}
if (modelDiff.getDiffType().equals(M2ModelDiff.DIFF_UPDATED))
{
throw new AlfrescoRuntimeException("Failed to validate model update - found non-incrementally updated " + modelDiff.getElementType() + " '" + modelDiff.getElementName() + "'");
}
}
// TODO validate that any deleted constraints are not being referenced - else currently will become anon - or push down into model compilation (check backwards compatibility ...)
}
private void validatePropertyDelete(QName modelName, QName propertyName, boolean sharedModel)
{
String tenantDomain = TenantService.DEFAULT_DOMAIN;
if (sharedModel)
{
tenantDomain = " for tenant [" + tenantService.getCurrentUserDomain() + "]";
}
boolean found = false;
// check for property usages
for (PropertyDefinition prop : dictionaryDAO.getProperties(modelName, null))
{
// TODO ... match property
if (prop.getName().equals(propertyName))
{
// found
found = true;
validateIndexedProperty(tenantDomain, prop);
break;
}
}
if (! found)
{
throw new AlfrescoRuntimeException("Failed to validate property delete" + tenantDomain + " - property definition '" + propertyName + "' not defined in model '" + modelName + "'");
}
}
private void validateIndexedProperty(String tenantDomain, PropertyDefinition propDef)
{
QName propName = propDef.getName();
if (! propDef.isIndexed())
{
// TODO ... implement DB-level referential integrity
throw new AlfrescoRuntimeException("Failed to validate property delete" + tenantDomain + " - cannot delete unindexed property definition '" + propName);
}
for (String storeUrl : this.storeUrls)
{
StoreRef store = new StoreRef(storeUrl);
// search for indexed PROPERTY
String escapePropName = propName.toPrefixString().replace(":", "\\:");
ResultSet rs = searchService.query(store, SearchService.LANGUAGE_LUCENE, "@"+escapePropName+":*");
try
{
if (rs.length() > 0)
{
throw new AlfrescoRuntimeException("Failed to validate property delete" + tenantDomain + " - found " + rs.length() + " nodes in store " + store + " with PROPERTY '" + propName + "'" );
}
}
finally
{
rs.close();
}
}
}
// validate delete of a referencable constraint def
private void validateConstraintDelete(CompiledModel compiledModel, QName constraintName, boolean sharedModel)
{
String tenantDomain = TenantService.DEFAULT_DOMAIN;
if (sharedModel)
{
tenantDomain = " for tenant [" + tenantService.getCurrentUserDomain() + "]";
}
Set<QName> referencedBy = new HashSet<QName>(0);
// check for references to constraint definition
// note: could be anon prop constraint (if no referenceable constraint)
Collection<QName> allModels = dictionaryDAO.getModels();
for (QName model : allModels)
{
Collection<PropertyDefinition> propDefs = null;
if (compiledModel.getModelDefinition().getName().equals(model))
{
// TODO deal with multiple pending model updates
propDefs = compiledModel.getProperties();
}
else
{
propDefs = dictionaryDAO.getProperties(model);
}
for (PropertyDefinition propDef : propDefs)
{
for (ConstraintDefinition conDef : propDef.getConstraints())
{
if (constraintName.equals(conDef.getRef()))
{
referencedBy.add(conDef.getName());
}
}
}
}
if (referencedBy.size() == 1)
{
throw new AlfrescoRuntimeException("Failed to validate constraint delete" + tenantDomain + " - constraint definition '" + constraintName + "' is being referenced by '" + referencedBy.toArray()[0] + "' property constraint");
}
else if (referencedBy.size() > 1)
{
throw new AlfrescoRuntimeException("Failed to validate constraint delete" + tenantDomain + " - constraint definition '" + constraintName + "' is being referenced by " + referencedBy.size() + " property constraints");
}
}
}

View File

@@ -64,9 +64,10 @@ import org.alfresco.repo.dictionary.DynamicModelPolicies.OnLoadDynamicModel;
/**
* Bootstrap the dictionary from specified locations within the repository
*
* @author Roy Wetherall, JanV
* @author Roy Wetherall, JanV, sglover
*/
public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean implements TenantDeployer, DictionaryListener, MessageDeployer
public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean
implements TenantDeployer, DictionaryListener, /*TenantDictionaryListener, */MessageDeployer
{
// Logging support
private static Log logger = LogFactory.getLog(DictionaryRepositoryBootstrap.class);
@@ -131,7 +132,17 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme
{
this.nodeService = nodeService;
}
public PolicyComponent getPolicyComponent()
{
return policyComponent;
}
public void setPolicyComponent(PolicyComponent policyComponent)
{
this.policyComponent = policyComponent;
}
/**
* Set the tenant admin service
*
@@ -231,29 +242,7 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme
{
// NOOP - will be destroyed directly via DictionaryComponent
}
/**
* Initialise the dictionary, ensuring that a transaction is available
*/
@Override
public void onDictionaryInit()
{
if(onLoadDynamicModelDelegate == null)
{
onLoadDynamicModelDelegate = policyComponent.registerClassPolicy(DynamicModelPolicies.OnLoadDynamicModel.class);
}
RetryingTransactionCallback<Void> initCallback = new RetryingTransactionCallback<Void>()
{
@Override
public Void execute() throws Throwable
{
onDictionaryInitInTxn();
return null;
}
};
transactionService.getRetryingTransactionHelper().doInTransaction(initCallback, true, false);
}
/**
* Perform the actual repository access, checking for the existence of a valid transaction
*/
@@ -272,7 +261,7 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme
logger.trace("onDictionaryInit: ["+Thread.currentThread()+"]"+(tenantDomain.equals(TenantService.DEFAULT_DOMAIN) ? "" : " (Tenant: "+tenantDomain+")"));
}
Collection<QName> modelsBefore = dictionaryDAO.getModels(); // note: re-entrant
Collection<QName> modelsBefore = dictionaryDAO.getModels(true); // note: re-entrant
int modelsBeforeCnt = (modelsBefore != null ? modelsBefore.size() : 0);
List<String> loadedModels = new ArrayList<String>();
@@ -312,7 +301,8 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme
try
{
// Ignore if the node is a working copy or archived, or if its inactive
if (! (nodeService.hasAspect(dictionaryModel, ContentModel.ASPECT_WORKING_COPY) || nodeService.hasAspect(dictionaryModel, ContentModel.ASPECT_ARCHIVED)))
if (! (nodeService.hasAspect(dictionaryModel, ContentModel.ASPECT_WORKING_COPY) ||
nodeService.hasAspect(dictionaryModel, ContentModel.ASPECT_ARCHIVED)))
{
Boolean isActive = (Boolean)nodeService.getProperty(dictionaryModel, ContentModel.PROP_MODEL_ACTIVE);
@@ -363,7 +353,7 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme
}
}
Collection<QName> modelsAfter = dictionaryDAO.getModels();
Collection<QName> modelsAfter = dictionaryDAO.getModels(true);
int modelsAfterCnt = (modelsAfter != null ? modelsAfter.size() : 0);
if (logger.isDebugEnabled())
@@ -383,23 +373,7 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme
DynamicModelPolicies.OnLoadDynamicModel policy = onLoadDynamicModelDelegate.get(ContentModel.TYPE_CONTENT);
policy.onLoadDynamicModel(entry.model, entry.nodeRef);
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.dictionary.DictionaryListener#afterInit()
*/
public void afterDictionaryInit()
{
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.dictionary.DictionaryListener#onDictionaryDestroy()
*/
public void afterDictionaryDestroy()
{
}
public void initMessages()
{
if (this.repositoryMessagesLocations != null)
@@ -496,14 +470,20 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme
return modelRefs;
}
protected List<NodeRef> getNodes(StoreRef storeRef, RepositoryLocation repositoryLocation, QName nodeType)
protected List<NodeRef> getNodes(StoreRef storeRef, RepositoryLocation repositoryLocation, QName nodeType)
{
List<NodeRef> nodeRefs = new ArrayList<NodeRef>();
NodeRef rootNodeRef = nodeService.getRootNode(storeRef);
if(repositoryLocation instanceof DynamicCreateRepositoryLocation)
{
((DynamicCreateRepositoryLocation)repositoryLocation).checkAndCreate(rootNodeRef);
}
String[] pathElements = repositoryLocation.getPathElements();
NodeRef folderNodeRef = rootNodeRef;
if (pathElements.length > 0)
{
@@ -575,10 +555,12 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme
{
if (logger.isDebugEnabled())
{
logger.debug("Loading model: " + modelName + " (from ["+ modelLocation.getStoreRef() + "]"+ modelLocation.getPath() + ")");
logger.debug("Loading model: " + modelName
+ " (from ["+ modelLocation.getStoreRef() + "]"+ modelLocation.getPath() + ")");
}
dictionaryDAO.putModel(model);
loadedModels.add(modelName);
}
catch (AlfrescoRuntimeException e)
@@ -666,8 +648,8 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme
dictionaryDAO.destroy();
// register with Dictionary Service to allow (re-)init
dictionaryDAO.register(this);
dictionaryDAO.registerListener(this);
// register with Message Service to allow (re-)init
messageService.register(this);
@@ -742,13 +724,36 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme
}
return parentNodeRef;
}
public PolicyComponent getPolicyComponent() {
return policyComponent;
/**
* Initialise the dictionary, ensuring that a transaction is available
*/
@Override
public void onDictionaryInit()
{
if(onLoadDynamicModelDelegate == null)
{
onLoadDynamicModelDelegate = policyComponent.registerClassPolicy(DynamicModelPolicies.OnLoadDynamicModel.class);
}
RetryingTransactionCallback<Void> initCallback = new RetryingTransactionCallback<Void>()
{
@Override
public Void execute() throws Throwable
{
onDictionaryInitInTxn();
return null;
}
};
transactionService.getRetryingTransactionHelper().doInTransaction(initCallback, true, false);
}
@Override
public void afterDictionaryDestroy()
{
}
public void setPolicyComponent(PolicyComponent policyComponent) {
this.policyComponent = policyComponent;
@Override
public void afterDictionaryInit()
{
}
}

View File

@@ -0,0 +1,150 @@
package org.alfresco.repo.dictionary;
import java.io.File;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import org.alfresco.repo.importer.ACPImportPackageHandler;
import org.alfresco.repo.importer.ImporterBootstrap;
import org.alfresco.repo.tenant.TenantUtil;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.view.ImportPackageHandler;
import org.alfresco.service.cmr.view.ImporterBinding;
import org.alfresco.service.cmr.view.ImporterContentCache;
import org.alfresco.service.cmr.view.ImporterProgress;
import org.alfresco.service.cmr.view.ImporterService;
import org.alfresco.service.cmr.view.Location;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.surf.util.I18NUtil;
/**
*
* @author sglover
*
*/
public class DynamicCreateRepositoryLocation extends RepositoryLocation
{
private static final Log logger = LogFactory.getLog(DynamicCreateRepositoryLocation.class);
private ImporterService importerService;
private String contentViewLocation;
private ResourceBundle bundle;
private NamespaceService namespaceService;
private SearchService searchService;
public void setSearchService(SearchService searchService)
{
this.searchService = searchService;
}
public void setNamespaceService(NamespaceService namespaceService)
{
this.namespaceService = namespaceService;
}
public void setContentViewLocation(String contentViewLocation)
{
this.contentViewLocation = contentViewLocation;
}
public void setImporterService(ImporterService importerService)
{
this.importerService = importerService;
}
public void setBundleName(String bundleName)
{
Locale bindingLocale = I18NUtil.getLocale();
this.bundle = ResourceBundle.getBundle(bundleName, bindingLocale);
}
public void checkAndCreate(NodeRef rootNodeRef)
{
// try
// {
// String[] pathElements = getPathElements();
List<NodeRef> nodes = searchService.selectNodes(rootNodeRef, getPath(), null, namespaceService, false);
if(nodes.size() == 0)
{
logger.info("Repository location " + getPath() + " does not exist for tenant "
+ TenantUtil.getCurrentDomain() + ", creating");
create();
}
// fileFolderService.resolveNamePath(rootNodeRef, Arrays.asList(pathElements));
// }
// catch(FileNotFoundException e)
// {
// logger.info("Repository location " + getPath() + " does not exist for tenant "
// + TenantUtil.getCurrentDomain() + ", creating");
// create();
// }
}
protected String getParentPath()
{
String parentPath = null;
String path = getPath();
int idx = path.lastIndexOf("/");
if(idx != -1)
{
parentPath = path.substring(0, idx);
}
else
{
parentPath = "/";
}
return parentPath;
}
protected void create()
{
final File viewFile = ImporterBootstrap.getFile(contentViewLocation);
ImportPackageHandler acpHandler = new ACPImportPackageHandler(viewFile, null);
Location location = new Location(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
location.setPath(getParentPath());
final ImporterBinding binding = new ImporterBinding()
{
@Override
public String getValue(String key)
{
return bundle.getString(key);
}
@Override
public UUID_BINDING getUUIDBinding()
{
return UUID_BINDING.CREATE_NEW;
}
@Override
public QName[] getExcludedClasses()
{
return null;
}
@Override
public boolean allowReferenceWithinTransaction()
{
return false;
}
@Override
public ImporterContentCache getImportConentCache()
{
return null;
}
};
importerService.importView(acpHandler, location, binding, (ImporterProgress) null);
}
}

View File

@@ -0,0 +1,17 @@
package org.alfresco.repo.dictionary;
import java.util.List;
import org.alfresco.service.namespace.QName;
/**
*
* @author sglover
*
*/
public interface ModelValidator
{
void setStoreUrls(List<String> storeUrls);
void validateModel(CompiledModel compiledModel);
void validateModelDelete(final QName modelName);
}

View File

@@ -0,0 +1,421 @@
package org.alfresco.repo.dictionary;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.tenant.Tenant;
import org.alfresco.repo.tenant.TenantAdminService;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.repo.tenant.TenantUtil;
import org.alfresco.repo.workflow.BPMEngineRegistry;
import org.alfresco.service.cmr.dictionary.AspectDefinition;
import org.alfresco.service.cmr.dictionary.ClassDefinition;
import org.alfresco.service.cmr.dictionary.ConstraintDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryException;
import org.alfresco.service.cmr.dictionary.ModelDefinition;
import org.alfresco.service.cmr.dictionary.NamespaceDefinition;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
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.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition;
import org.alfresco.service.namespace.NamespaceException;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
*
* @author sglover
*
*/
public class ModelValidatorImpl implements ModelValidator
{
private static final Log logger = LogFactory.getLog(ModelValidatorImpl.class);
private DictionaryDAO dictionaryDAO;
private NamespaceService namespaceService;
private WorkflowService workflowService;
private TenantService tenantService;
private TenantAdminService tenantAdminService;
private SearchService searchService;
private List<String> storeUrls; // stores against which model deletes should be validated
public void setStoreUrls(List<String> storeUrls)
{
this.storeUrls = storeUrls;
}
public void setDictionaryDAO(DictionaryDAO dictionaryDAO)
{
this.dictionaryDAO = dictionaryDAO;
}
public void setNamespaceService(NamespaceService namespaceService)
{
this.namespaceService = namespaceService;
}
public void setWorkflowService(WorkflowService workflowService)
{
this.workflowService = workflowService;
}
public void setTenantService(TenantService tenantService)
{
this.tenantService = tenantService;
}
public void setTenantAdminService(TenantAdminService tenantAdminService)
{
this.tenantAdminService = tenantAdminService;
}
public void setSearchService(SearchService searchService)
{
this.searchService = searchService;
}
private void checkCustomModelNamespace(M2Model model, String tenantDomain)
{
if(tenantDomain != null && !tenantDomain.equals(""))
{
// check only for "real" tenants
for(M2Namespace namespace : model.getNamespaces())
{
String namespaceURI = namespace.getUri();
if(namespaceURI.indexOf(tenantDomain) == -1)
{
throw new DictionaryException("Namespace " + namespaceURI + " does not contain the tenant " + tenantDomain);
}
}
}
}
/**
* validate against repository contents / workflows (e.g. when deleting an existing model)
*
* @param modelName
*/
public void validateModelDelete(final QName modelName)
{
// TODO add model locking during delete (would need to be tenant-aware & cluster-aware) to avoid potential
// for concurrent addition of new content/workflow as model is being deleted
final Collection<NamespaceDefinition> namespaceDefs;
final Collection<TypeDefinition> typeDefs;
final Collection<AspectDefinition> aspectDefs;
try
{
namespaceDefs = dictionaryDAO.getNamespaces(modelName);
typeDefs = dictionaryDAO.getTypes(modelName);
aspectDefs = dictionaryDAO.getAspects(modelName);
}
catch (DictionaryException e)
{
if (logger.isDebugEnabled())
{
logger.debug("Dictionary model '" + modelName + "' does not exist ... skip delete validation : " + e);
}
return;
}
// TODO - in case of MT we do not currently allow deletion of an overridden model (with usages) ... but could allow if (re-)inherited model is equivalent to an incremental update only ?
validateModelDelete(namespaceDefs, typeDefs, aspectDefs, false);
if (tenantService.isEnabled() && tenantService.isTenantUser() == false)
{
// shared model - need to check all tenants (whether enabled or disabled) unless they have overridden
List<Tenant> tenants = tenantAdminService.getAllTenants();
for (Tenant tenant : tenants)
{
// validate model delete within context of tenant domain
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
public Object doWork()
{
if (dictionaryDAO.isModelInherited(modelName))
{
validateModelDelete(namespaceDefs, typeDefs, aspectDefs, true);
}
return null;
}
}, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenant.getTenantDomain()));
}
}
}
private void validateModelDelete(Collection<NamespaceDefinition> namespaceDefs, Collection<TypeDefinition> typeDefs, Collection<AspectDefinition> aspectDefs, boolean sharedModel)
{
String tenantDomain = TenantService.DEFAULT_DOMAIN;
if (sharedModel)
{
tenantDomain = " for tenant [" + tenantService.getCurrentUserDomain() + "]";
}
List<WorkflowDefinition> workflowDefs = workflowService.getDefinitions();
if (workflowDefs.size() > 0)
{
if (namespaceDefs.size() > 0)
{
// check workflow namespace usage
for (WorkflowDefinition workflowDef : workflowDefs)
{
String workflowDefName = workflowDef.getName();
String workflowNamespaceURI = null;
try
{
workflowNamespaceURI = QName.createQName(BPMEngineRegistry.getLocalId(workflowDefName), namespaceService).getNamespaceURI();
}
catch (NamespaceException ne)
{
logger.warn("Skipped workflow when validating model delete - unknown namespace: "+ne);
continue;
}
for (NamespaceDefinition namespaceDef : namespaceDefs)
{
if (workflowNamespaceURI.equals(namespaceDef.getUri()))
{
throw new AlfrescoRuntimeException("Failed to validate model delete" + tenantDomain + " - found workflow process definition " + workflowDefName + " using model namespace '" + namespaceDef.getUri() + "'");
}
}
}
}
}
// check for type usages
for (TypeDefinition type : typeDefs)
{
validateClass(tenantDomain, type);
}
// check for aspect usages
for (AspectDefinition aspect : aspectDefs)
{
validateClass(tenantDomain, aspect);
}
}
private void validateClass(String tenantDomain, ClassDefinition classDef)
{
QName className = classDef.getName();
String classType = "TYPE";
if (classDef instanceof AspectDefinition)
{
classType = "ASPECT";
}
for (String storeUrl : this.storeUrls)
{
StoreRef store = new StoreRef(storeUrl);
// search for TYPE or ASPECT - TODO - alternative would be to extract QName and search by namespace ...
ResultSet rs = searchService.query(store, SearchService.LANGUAGE_LUCENE, classType+":\""+className+"\"");
try
{
if (rs.length() > 0)
{
throw new AlfrescoRuntimeException("Failed to validate model delete" + tenantDomain + " - found " + rs.length() + " nodes in store " + store + " with " + classType + " '" + className + "'" );
}
}
finally
{
rs.close();
}
}
// check against workflow task usage
for (WorkflowDefinition workflowDef : workflowService.getDefinitions())
{
for (WorkflowTaskDefinition workflowTaskDef : workflowService.getTaskDefinitions(workflowDef.getId()))
{
TypeDefinition workflowTypeDef = workflowTaskDef.metadata;
if (workflowTypeDef.getName().equals(className))
{
throw new AlfrescoRuntimeException("Failed to validate model delete" + tenantDomain + " - found task definition in workflow " + workflowDef.getName() + " with " + classType + " '" + className + "'");
}
}
}
}
/**
* validate against dictionary
*
* if new model
* then nothing to validate
*
* else if an existing model
* then could be updated (or unchanged) so validate to currently only allow incremental updates
* - addition of new types, aspects (except default aspects), properties, associations
* - no deletion of types, aspects or properties or associations
* - no addition, update or deletion of default/mandatory aspects
*
* @paramn modelName
* @param newOrUpdatedModel
*/
@Override
public void validateModel(CompiledModel compiledModel)
{
ModelDefinition modelDef = compiledModel.getModelDefinition();
QName modelName = modelDef.getName();
M2Model model = compiledModel.getM2Model();
checkCustomModelNamespace(model, TenantUtil.getCurrentDomain());
List<M2ModelDiff> modelDiffs = dictionaryDAO.diffModel(model);
for (M2ModelDiff modelDiff : modelDiffs)
{
if (modelDiff.getDiffType().equals(M2ModelDiff.DIFF_DELETED))
{
// TODO - check tenants if model is shared / inherited
if (modelDiff.getElementType().equals(M2ModelDiff.TYPE_PROPERTY))
{
validatePropertyDelete(modelName, modelDiff.getElementName(), false);
continue;
}
else if (modelDiff.getElementType().equals(M2ModelDiff.TYPE_CONSTRAINT))
{
validateConstraintDelete(compiledModel, modelDiff.getElementName(), false);
continue;
}
else
{
throw new AlfrescoRuntimeException("Failed to validate model update - found deleted " + modelDiff.getElementType() + " '" + modelDiff.getElementName() + "'");
}
}
if (modelDiff.getDiffType().equals(M2ModelDiff.DIFF_UPDATED))
{
throw new AlfrescoRuntimeException("Failed to validate model update - found non-incrementally updated " + modelDiff.getElementType() + " '" + modelDiff.getElementName() + "'");
}
}
// TODO validate that any deleted constraints are not being referenced - else currently will become anon - or push down into model compilation (check backwards compatibility ...)
}
private void validatePropertyDelete(QName modelName, QName propertyName, boolean sharedModel)
{
String tenantDomain = TenantService.DEFAULT_DOMAIN;
if (sharedModel)
{
tenantDomain = " for tenant [" + tenantService.getCurrentUserDomain() + "]";
}
boolean found = false;
// check for property usages
for (PropertyDefinition prop : dictionaryDAO.getProperties(modelName, null))
{
// TODO ... match property
if (prop.getName().equals(propertyName))
{
// found
found = true;
validateIndexedProperty(tenantDomain, prop);
break;
}
}
if (! found)
{
throw new AlfrescoRuntimeException("Failed to validate property delete" + tenantDomain + " - property definition '" + propertyName + "' not defined in model '" + modelName + "'");
}
}
private void validateIndexedProperty(String tenantDomain, PropertyDefinition propDef)
{
QName propName = propDef.getName();
if (! propDef.isIndexed())
{
// TODO ... implement DB-level referential integrity
throw new AlfrescoRuntimeException("Failed to validate property delete" + tenantDomain + " - cannot delete unindexed property definition '" + propName);
}
for (String storeUrl : this.storeUrls)
{
StoreRef store = new StoreRef(storeUrl);
// search for indexed PROPERTY
String escapePropName = propName.toPrefixString().replace(":", "\\:");
ResultSet rs = searchService.query(store, SearchService.LANGUAGE_LUCENE, "@"+escapePropName+":*");
try
{
if (rs.length() > 0)
{
throw new AlfrescoRuntimeException("Failed to validate property delete" + tenantDomain + " - found " + rs.length() + " nodes in store " + store + " with PROPERTY '" + propName + "'" );
}
}
finally
{
rs.close();
}
}
}
// validate delete of a referencable constraint def
private void validateConstraintDelete(CompiledModel compiledModel, QName constraintName, boolean sharedModel)
{
String tenantDomain = TenantService.DEFAULT_DOMAIN;
if (sharedModel)
{
tenantDomain = " for tenant [" + tenantService.getCurrentUserDomain() + "]";
}
Set<QName> referencedBy = new HashSet<QName>(0);
// check for references to constraint definition
// note: could be anon prop constraint (if no referenceable constraint)
Collection<QName> allModels = dictionaryDAO.getModels(true);
for (QName model : allModels)
{
Collection<PropertyDefinition> propDefs = null;
if (compiledModel.getModelDefinition().getName().equals(model))
{
// TODO deal with multiple pending model updates
propDefs = compiledModel.getProperties();
}
else
{
propDefs = dictionaryDAO.getProperties(model);
}
for (PropertyDefinition propDef : propDefs)
{
for (ConstraintDefinition conDef : propDef.getConstraints())
{
if (constraintName.equals(conDef.getRef()))
{
referencedBy.add(conDef.getName());
}
}
}
}
if (referencedBy.size() == 1)
{
throw new AlfrescoRuntimeException("Failed to validate constraint delete" + tenantDomain + " - constraint definition '" + constraintName + "' is being referenced by '" + referencedBy.toArray()[0] + "' property constraint");
}
else if (referencedBy.size() > 1)
{
throw new AlfrescoRuntimeException("Failed to validate constraint delete" + tenantDomain + " - constraint definition '" + constraintName + "' is being referenced by " + referencedBy.size() + " property constraints");
}
}
}

View File

@@ -39,11 +39,10 @@ public class RepositoryLocation
/** Search Language */
private String queryLanguage = "xpath"; // default
public static final String LANGUAGE_PATH = "path"; // lookup directly using node (prefix qname) path
public static final String LANGUAGE_CLASSPATH = "classpath";
/**
*/
public RepositoryLocation()
@@ -66,7 +65,7 @@ public class RepositoryLocation
setQueryLanguage(queryLanguage);
}
/**
/**
* Set the store protocol
*
* @param storeProtocol the store protocol

View File

@@ -23,8 +23,6 @@ import java.util.List;
import org.alfresco.repo.cache.DefaultSimpleCache;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.dictionary.DictionaryDAOImpl.DictionaryRegistry;
import org.alfresco.repo.dictionary.NamespaceDAOImpl.NamespaceRegistry;
import org.alfresco.repo.tenant.SingleTServiceImpl;
import org.alfresco.repo.tenant.TenantService;
@@ -81,12 +79,11 @@ public class TestModel
// construct dictionary dao
TenantService tenantService = new SingleTServiceImpl();
NamespaceDAOImpl namespaceDAO = new NamespaceDAOImpl();
namespaceDAO.setTenantService(tenantService);
// NamespaceDAOImpl namespaceDAO = new NamespaceDAOImpl();
// namespaceDAO.setTenantService(tenantService);
// initNamespaceCaches(namespaceDAO);
initNamespaceCaches(namespaceDAO);
DictionaryDAOImpl dictionaryDAO = new DictionaryDAOImpl(namespaceDAO);
DictionaryDAOImpl dictionaryDAO = new DictionaryDAOImpl();
dictionaryDAO.setTenantService(tenantService);
initDictionaryCaches(dictionaryDAO);
@@ -122,9 +119,9 @@ public class TestModel
dictionaryDAO.setDictionaryRegistryCache(dictionaryCache);
}
private static void initNamespaceCaches(NamespaceDAOImpl namespaceDAO)
{
SimpleCache<String, NamespaceRegistry> namespaceCache = new DefaultSimpleCache<String, NamespaceRegistry>();
namespaceDAO.setNamespaceRegistryCache(namespaceCache);
}
// private static void initNamespaceCaches(NamespaceDAOImpl namespaceDAO)
// {
// SimpleCache<String, NamespaceRegistry> namespaceCache = new DefaultSimpleCache<String, NamespaceRegistry>();
// namespaceDAO.setNamespaceRegistryCache(namespaceCache);
// }
}