mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
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:
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
17
source/java/org/alfresco/repo/dictionary/ModelValidator.java
Normal file
17
source/java/org/alfresco/repo/dictionary/ModelValidator.java
Normal 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);
|
||||
}
|
421
source/java/org/alfresco/repo/dictionary/ModelValidatorImpl.java
Normal file
421
source/java/org/alfresco/repo/dictionary/ModelValidatorImpl.java
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
|
@@ -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);
|
||||
// }
|
||||
}
|
Reference in New Issue
Block a user