Dynamic model activation / deactivation - via WebClient UI or RepoAdmin console - also verified in MT env

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@6712 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Jan Vonka
2007-09-10 11:37:01 +00:00
parent c730f41aba
commit 3b58addf08
9 changed files with 546 additions and 400 deletions

View File

@@ -255,8 +255,8 @@ public class RepoAdminInterpreter extends BaseInterpreter
InputStream fileStream = file.getInputStream();
String modelFileName = file.getFilename();
QName modelQName = repoAdminService.deployModel(fileStream, modelFileName);
out.println("Model deployed: " + modelFileName + " [" + modelQName + "]");
repoAdminService.deployModel(fileStream, modelFileName);
out.println("Model deployed: " + modelFileName);
}
else if (command[1].equals("messages"))
@@ -272,7 +272,7 @@ public class RepoAdminInterpreter extends BaseInterpreter
}
}
else if (command[0].equals("reload"))
else if (command[0].equals("activate"))
{
if (command.length != 3)
{
@@ -282,15 +282,38 @@ public class RepoAdminInterpreter extends BaseInterpreter
else if (command[1].equals("model"))
{
String modelFileName = command[2];
QName modelQName = repoAdminService.reloadModel(modelFileName);
out.println("Model (re-)loaded: " + modelFileName + " [" + modelQName + "]");
QName modelQName = repoAdminService.activateModel(modelFileName);
out.println("Model activated: " + modelFileName + " [" + modelQName + "]");
}
}
else if (command[0].equals("deactivate"))
{
if (command.length != 3)
{
return "Syntax Error.\n";
}
else if (command[1].equals("model"))
{
String modelFileName = command[2];
QName modelQName = repoAdminService.deactivateModel(modelFileName);
out.println("Model deactivated: " + modelFileName + " [" + modelQName + "]");
}
}
else if (command[0].equals("reload"))
{
if (command.length != 3)
{
return "Syntax Error.\n";
}
else if (command[1].equals("messages"))
{
String bundleBaseName = command[2];
repoAdminService.reloadMessageBundle(bundleBaseName);
out.println("Message resource bundle (re-)loaded: " + bundleBaseName);
out.println("Message resource bundle reloaded: " + bundleBaseName);
}
else

View File

@@ -27,6 +27,7 @@ package org.alfresco.repo.admin;
import java.io.InputStream;
import java.util.List;
import org.alfresco.service.Auditable;
import org.alfresco.service.namespace.QName;
@@ -47,11 +48,13 @@ public interface RepoAdminService
public List<RepoModelDefinition> getModels();
public QName deployModel(InputStream modelStream, String modelFileName);
public void deployModel(InputStream modelStream, String modelFileName);
public QName undeployModel(String modelFileName);
public QName reloadModel(String modelFileName);
public QName activateModel(String modelFileName);
public QName deactivateModel(String modelFileName);
/* Custom message/resource bundles managed in the repository */

View File

@@ -24,10 +24,7 @@
*/
package org.alfresco.repo.admin;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
@@ -43,12 +40,9 @@ import org.alfresco.repo.dictionary.DictionaryDAO;
import org.alfresco.repo.dictionary.M2Model;
import org.alfresco.repo.dictionary.RepositoryLocation;
import org.alfresco.repo.i18n.MessageService;
import org.alfresco.repo.workflow.BPMEngineRegistry;
import org.alfresco.service.cmr.admin.RepoAdminService;
import org.alfresco.service.cmr.dictionary.AspectDefinition;
import org.alfresco.service.cmr.dictionary.ClassDefinition;
import org.alfresco.service.cmr.dictionary.NamespaceDefinition;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryException;
import org.alfresco.service.cmr.dictionary.ModelDefinition;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
@@ -56,11 +50,7 @@ import org.alfresco.service.cmr.repository.ContentWriter;
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.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.ParameterCheck;
@@ -88,13 +78,10 @@ public class RepoAdminServiceImpl implements RepoAdminService
private ContentService contentService;
private NamespaceService namespaceService;
private MessageService messageService;
private WorkflowService workflowService;
private RepositoryLocation repoModelsLocation;
private RepositoryLocation repoMessagesLocation;
private List<String> storeUrls; // stores against which model changes (updates/deletes) should be validated
public final static String CRITERIA_ALL = "/*"; // immediate children only
public final static String defaultSubtypeOfDictionaryModel = "subtypeOf('cm:dictionaryModel')";
@@ -130,11 +117,7 @@ public class RepoAdminServiceImpl implements RepoAdminService
{
this.messageService = messageService;
}
public void setWorkflowService(WorkflowService workflowService)
{
this.workflowService = workflowService;
}
public void setRepositoryModelsLocation(RepositoryLocation repoModelsLocation)
@@ -147,11 +130,6 @@ public class RepoAdminServiceImpl implements RepoAdminService
this.repoMessagesLocation = repoMessagesLocation;
}
public void setStoreUrls(List<String> storeUrls)
{
this.storeUrls = storeUrls;
}
/*
* (non-Javadoc)
@@ -162,53 +140,55 @@ public class RepoAdminServiceImpl implements RepoAdminService
StoreRef storeRef = repoModelsLocation.getStoreRef();
NodeRef rootNode = nodeService.getRootNode(storeRef);
Collection<QName> models = dictionaryDAO.getModels();
List<String> dictionaryModels = new ArrayList<String>();
for (QName model : models)
{
dictionaryModels.add(model.toPrefixString());
}
List<NodeRef> nodeRefs = searchService.selectNodes(rootNode, repoModelsLocation.getPath()+CRITERIA_ALL+"["+defaultSubtypeOfDictionaryModel+"]", null, namespaceService, false);
List<RepoModelDefinition> modelsInRepo = new ArrayList<RepoModelDefinition>();
if (nodeRefs.size() > 0)
try
{
Collection<QName> models = dictionaryDAO.getModels();
List<String> dictionaryModels = new ArrayList<String>();
for (QName model : models)
{
dictionaryModels.add(model.toPrefixString());
}
List<NodeRef> nodeRefs = searchService.selectNodes(rootNode, repoModelsLocation.getPath()+CRITERIA_ALL+"["+defaultSubtypeOfDictionaryModel+"]", null, namespaceService, false);
if (nodeRefs.size() > 0)
{
for (NodeRef nodeRef : nodeRefs)
{
String modelFileName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
String repoVersion = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL);
String modelName = null;
ContentReader cr = contentService.getReader(nodeRef, ContentModel.TYPE_CONTENT);
InputStream is = cr.getContentInputStream();
M2Model model = M2Model.createModel(is);
is.close();
modelName = model.getName();
// check against models loaded in dictionary and give warning if not found
if (dictionaryModels.contains(modelName))
{
// note: uses dictionary model cache, rather than getting content from repo and re-compiling
modelsInRepo.add(new RepoModelDefinition(modelFileName, repoVersion, dictionaryDAO.getModel(QName.createQName(modelName, namespaceService)), true));
}
else
{
modelsInRepo.add(new RepoModelDefinition(modelFileName, repoVersion, null, false));
}
}
}
}
catch (Throwable t)
{
for (NodeRef nodeRef : nodeRefs)
{
String modelFileName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
String repoVersion = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL);
String modelName = null;
try
{
ContentReader cr = contentService.getReader(nodeRef, ContentModel.TYPE_CONTENT);
InputStream is = cr.getContentInputStream();
M2Model model = M2Model.createModel(is);
is.close();
modelName = model.getName();
}
catch (Throwable t)
{
throw new AlfrescoRuntimeException("Failed to getModels " + t);
}
// check against models loaded in dictionary and give warning if not found
if (dictionaryModels.contains(modelName))
{
// note: uses dictionary model cache, rather than getting content from repo and re-compiling
modelsInRepo.add(new RepoModelDefinition(modelFileName, repoVersion, dictionaryDAO.getModel(QName.createQName(modelName, namespaceService)), true));
}
else
{
modelsInRepo.add(new RepoModelDefinition(modelFileName, repoVersion, null, false));
}
}
throw new AlfrescoRuntimeException("Failed to get models " + t);
}
return modelsInRepo;
@@ -218,28 +198,13 @@ public class RepoAdminServiceImpl implements RepoAdminService
* (non-Javadoc)
* @see org.alfresco.service.cmr.admin.RepoAdminService#deployModel(java.io.InputStream, java.lang.String)
*/
public QName deployModel(InputStream modelStream, String modelFileName)
public void deployModel(InputStream modelStream, String modelFileName)
{
// Check that all the passed values are not null
ParameterCheck.mandatory("ModelStream", modelStream);
ParameterCheck.mandatoryString("ModelFileName", modelFileName);
QName modelQName = null;
try
{
// TODO workaround due to issue with model.toXML() - see below
BufferedReader in = new BufferedReader(new InputStreamReader(modelStream));
StringBuffer buffer = new StringBuffer();
String line = null;
while ((line = in.readLine()) != null) {
buffer.append(line);
}
InputStream is = new ByteArrayInputStream(buffer.toString().getBytes());
M2Model model = M2Model.createModel(is);
is.close();
{
// Check that all the passed values are not null
ParameterCheck.mandatory("ModelStream", modelStream);
ParameterCheck.mandatoryString("ModelFileName", modelFileName);
Map<QName, Serializable> contentProps = new HashMap<QName, Serializable>();
contentProps.put(ContentModel.PROP_NAME, modelFileName);
@@ -259,106 +224,99 @@ public class RepoAdminServiceImpl implements RepoAdminService
throw new AlfrescoRuntimeException("Found multiple custom models location " + repoModelsLocation.getPath());
}
NodeRef customModelsNodeRef = nodeRefs.get(0);
NodeRef customModelsSpaceNodeRef = nodeRefs.get(0);
nodeRefs = searchService.selectNodes(customModelsNodeRef, "*[@cm:name='"+modelFileName+"' and "+defaultSubtypeOfDictionaryModel+"]", null, namespaceService, false);
nodeRefs = searchService.selectNodes(customModelsSpaceNodeRef, "*[@cm:name='"+modelFileName+"' and "+defaultSubtypeOfDictionaryModel+"]", null, namespaceService, false);
NodeRef modelNodeRef = null;
if (nodeRefs.size() == 1)
{
// re-deploy existing model to the repository
NodeRef modelNodeRef = nodeRefs.get(0);
ContentWriter writer = contentService.getWriter(modelNodeRef, ContentModel.PROP_CONTENT, true);
writer.setMimetype(MimetypeMap.MIMETYPE_XML);
writer.setEncoding("UTF-8");
is = new ByteArrayInputStream(buffer.toString().getBytes());
writer.putContent(is); // also invokes policies for DictionaryModelType - e.g. onContentUpdate
is.close();
/* TODO
ByteArrayOutputStream out = new ByteArrayOutputStream();
model.toXML(out); // fails with NPE in JIBX - see also: http://issues.alfresco.com/browse/AR-1304
writer.putContent(out.toString("UTF-8"));
*/
// validate model against dictionary - could be new, unchanged or updated
dictionaryDAO.validateModel(model);
// parse and update model in the dictionary
modelQName = dictionaryDAO.putModel(model);
logger.info("Model re-deployed: " + modelQName);
modelNodeRef = nodeRefs.get(0);
}
else
{
// deploy new model to the repository
// note: dictionary model type has associated policies that will be invoked
ChildAssociationRef association = nodeService.createNode(customModelsNodeRef,
ChildAssociationRef association = nodeService.createNode(customModelsSpaceNodeRef,
ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, modelFileName),
ContentModel.TYPE_DICTIONARY_MODEL,
contentProps); // also invokes policies for DictionaryModelType - e.g. onUpdateProperties
NodeRef content = association.getChildRef();
modelNodeRef = association.getChildRef();
// add titled aspect (for Web Client display)
Map<QName, Serializable> titledProps = new HashMap<QName, Serializable>();
titledProps.put(ContentModel.PROP_TITLE, modelFileName);
titledProps.put(ContentModel.PROP_DESCRIPTION, modelFileName);
nodeService.addAspect(content, ContentModel.ASPECT_TITLED, titledProps);
nodeService.addAspect(modelNodeRef, ContentModel.ASPECT_TITLED, titledProps);
// add versionable aspect (set auto-version)
Map<QName, Serializable> versionProps = new HashMap<QName, Serializable>();
versionProps.put(ContentModel.PROP_AUTO_VERSION, true);
nodeService.addAspect(content, ContentModel.ASPECT_VERSIONABLE, versionProps);
ContentWriter writer = contentService.getWriter(content, ContentModel.PROP_CONTENT, true);
writer.setMimetype(MimetypeMap.MIMETYPE_XML);
writer.setEncoding("UTF-8");
is = new ByteArrayInputStream(buffer.toString().getBytes());
writer.putContent(is); // also invokes policies for DictionaryModelType - e.g. onContentUpdate
is.close();
/* TODO
ByteArrayOutputStream out = new ByteArrayOutputStream();
model.toXML(out); // fails with NPE in JIBX - see also: http://issues.alfresco.com/browse/AR-1304
writer.putContent(out.toString("UTF-8"));
*/
// validate model against dictionary - could be new, unchanged or updated
dictionaryDAO.validateModel(model);
// parse and add model to dictionary
modelQName = dictionaryDAO.putModel(model);
logger.info("Model deployed: " + modelQName);
nodeService.addAspect(modelNodeRef, ContentModel.ASPECT_VERSIONABLE, versionProps);
}
ContentWriter writer = contentService.getWriter(modelNodeRef, ContentModel.PROP_CONTENT, true);
writer.setMimetype(MimetypeMap.MIMETYPE_XML);
writer.setEncoding("UTF-8");
writer.putContent(modelStream); // also invokes policies for DictionaryModelType - e.g. onContentUpdate
modelStream.close();
// activate the model
nodeService.setProperty(modelNodeRef, ContentModel.PROP_MODEL_ACTIVE, new Boolean(true));
// note: model will be loaded as part of DictionaryModelType.beforeCommit()
}
catch (Throwable e)
{
throw new AlfrescoRuntimeException("Model deployment failed", e);
}
return modelQName;
throw new AlfrescoRuntimeException("Model deployment failed ", e);
}
}
/*
* (non-Javadoc)
* @see org.alfresco.service.cmr.admin.RepoAdminService#reloadModel(java.lang.String)
* @see org.alfresco.service.cmr.admin.RepoAdminService#activateModel(java.lang.String)
*/
public QName reloadModel(String modelFileName)
public QName activateModel(String modelFileName)
{
try
{
return activateOrDeactivate(modelFileName, true);
}
catch (Throwable e)
{
throw new AlfrescoRuntimeException("Model activation failed ", e);
}
}
/*
* (non-Javadoc)
* @see org.alfresco.service.cmr.admin.RepoAdminService#deactivateModel(java.lang.String)
*/
public QName deactivateModel(String modelFileName)
{
try
{
return activateOrDeactivate(modelFileName, false);
}
catch (Throwable e)
{
throw new AlfrescoRuntimeException("Model deactivation failed ", e);
}
}
private QName activateOrDeactivate(String modelFileName, boolean activate)
{
// Check that all the passed values are not null
ParameterCheck.mandatoryString("modelFileName", modelFileName);
QName modelQName = null;
StoreRef storeRef = repoModelsLocation.getStoreRef();
NodeRef rootNode = nodeService.getRootNode(storeRef);
@@ -376,32 +334,74 @@ public class RepoAdminServiceImpl implements RepoAdminService
NodeRef modelNodeRef = nodeRefs.get(0);
try
{
ContentReader cr = contentService.getReader(modelNodeRef, ContentModel.TYPE_CONTENT);
InputStream is = cr.getContentInputStream();
// create model
M2Model model = M2Model.createModel(is);
is.close();
if (model != null)
{
// validate model against dictionary - could be new, unchanged or updated
dictionaryDAO.validateModel(model);
// parse and update model in the dictionary
modelQName = dictionaryDAO.putModel(model);
logger.info("Model loaded: " + modelQName);
}
}
catch (Throwable e)
boolean isActive = ((Boolean)nodeService.getProperty(modelNodeRef, ContentModel.PROP_MODEL_ACTIVE)).booleanValue();
QName modelQName = (QName)nodeService.getProperty(modelNodeRef, ContentModel.PROP_MODEL_NAME);
ModelDefinition modelDef = null;
if (modelQName != null)
{
throw new AlfrescoRuntimeException("Model deployment failed", e);
try
{
modelDef = dictionaryDAO.getModel(modelQName);
}
catch (DictionaryException e)
{
logger.warn(e);
}
}
return modelQName;
if (activate)
{
// activate
if (isActive)
{
if (modelDef != null)
{
// model is already activated
throw new AlfrescoRuntimeException("Model activation failed - model '" + modelQName + "' is already activated");
}
else
{
logger.warn("Model is set to active but not loaded in Dictionary - trying to load...");
}
}
else
{
if (modelDef != null)
{
logger.warn("Model is loaded in Dictionary but is not set to active - trying to activate...");
}
}
}
else
{
// deactivate
if (!isActive)
{
if (modelDef == null)
{
// model is already deactivated
throw new AlfrescoRuntimeException("Model deactivation failed - model '" + modelQName + "' is already deactivated");
}
else
{
logger.warn("Model is set to inactive but loaded in Dictionary - trying to unload...");
}
}
else
{
if (modelDef == null)
{
logger.warn("Model is not loaded in Dictionary but is set to active - trying to deactivate...");
}
}
}
// activate/deactivate the model
nodeService.setProperty(modelNodeRef, ContentModel.PROP_MODEL_ACTIVE, new Boolean(activate));
// note: model will be loaded/unloaded as part of DictionaryModelType.beforeCommit()
return modelQName;
}
/*
@@ -436,39 +436,39 @@ public class RepoAdminServiceImpl implements RepoAdminService
NodeRef modelNodeRef = nodeRefs.get(0);
String modelName = null;
boolean isActive = ((Boolean)nodeService.getProperty(modelNodeRef, ContentModel.PROP_MODEL_ACTIVE)).booleanValue();
modelQName = (QName)nodeService.getProperty(modelNodeRef, ContentModel.PROP_MODEL_NAME);
try
ModelDefinition modelDef = null;
if (modelQName != null)
{
ContentReader cr = contentService.getReader(modelNodeRef, ContentModel.TYPE_CONTENT);
InputStream is = cr.getContentInputStream();
M2Model model = M2Model.createModel(is);
is.close();
modelName = model.getName();
try
{
modelDef = dictionaryDAO.getModel(modelQName);
}
catch (DictionaryException e)
{
logger.warn(e);
}
}
catch (Throwable t)
{
throw new AlfrescoRuntimeException("Failed to get model " + t);
if (isActive)
{
if (modelDef == null)
{
logger.warn("Model is set to active but not loaded in Dictionary - trying to undeploy...");
}
}
// permanently remove model from repository
nodeService.addAspect(modelNodeRef, ContentModel.ASPECT_TEMPORARY, null);
nodeService.deleteNode(modelNodeRef);
modelQName = QName.createQName(modelName, namespaceService);
// simple check for usages
validateModelDelete(modelQName);
dictionaryDAO.removeModel(modelQName);
logger.info("Model undeployed: " + modelQName);
// note: deleted model will be unloaded as part of DictionaryModelType.beforeCommit()
}
catch (Throwable e)
{
throw new AlfrescoRuntimeException("Model undeployment failed", e);
throw new AlfrescoRuntimeException("Model undeployment failed ", e);
}
return modelQName;
@@ -769,75 +769,5 @@ public class RepoAdminServiceImpl implements RepoAdminService
{
throw new AlfrescoRuntimeException("Message resource re-load failed", e);
}
}
/**
* validate against repository contents / workflows (e.g. when deleting an existing model)
*
* @param modelName
*/
private void validateModelDelete(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
for (WorkflowDefinition workflowDef : workflowService.getDefinitions())
{
String workflowDefName = workflowDef.getName();
String workflowNamespaceURI = QName.createQName(BPMEngineRegistry.getLocalId(workflowDefName), namespaceService).getNamespaceURI();
for (NamespaceDefinition namespace : dictionaryDAO.getNamespaces(modelName))
{
if (workflowNamespaceURI.equals(namespace.getUri()))
{
throw new AlfrescoRuntimeException("Failed to validate model delete - found workflow process definition " + workflowDefName + " using model namespace '" + namespace.getUri() + "'");
}
}
}
for (TypeDefinition type : dictionaryDAO.getTypes(modelName))
{
validateClass(type);
}
for (AspectDefinition aspect : dictionaryDAO.getAspects(modelName))
{
validateClass(aspect);
}
}
private void validateClass(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+"\"");
if (rs.length() > 0)
{
throw new AlfrescoRuntimeException("Failed to validate model delete - found " + rs.length() + " nodes in store " + store + " with " + classType + " '" + className + "'");
}
}
// check against workflow usage
for (WorkflowDefinition workflowDef : workflowService.getDefinitions())
{
for (WorkflowTaskDefinition workflowTaskDef : workflowService.getTaskDefinitions(workflowDef.getId()))
{
TypeDefinition workflowTypeDef = workflowTaskDef.metadata;
if (workflowTypeDef.getName().toString().equals(className))
{
throw new AlfrescoRuntimeException("Failed to validate model delete - found task definition in workflow " + workflowDef.getName() + " with " + classType + " '" + className + "'");
}
}
}
}
}

View File

@@ -26,24 +26,43 @@ package org.alfresco.repo.dictionary;
import java.io.Serializable;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.ContentServicePolicies;
import org.alfresco.repo.node.NodeServicePolicies;
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.TenantService;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionListener;
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.DictionaryException;
import org.alfresco.service.cmr.dictionary.ModelDefinition;
import org.alfresco.service.cmr.dictionary.NamespaceDefinition;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
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.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.GUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Dictionary model type behaviour.
@@ -54,6 +73,9 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
NodeServicePolicies.OnUpdatePropertiesPolicy,
NodeServicePolicies.BeforeDeleteNodePolicy
{
// Logger
private static Log logger = LogFactory.getLog(DictionaryModelType.class);
/** Key to the pending models */
private static final String KEY_PENDING_MODELS = "dictionaryModelType.pendingModels";
@@ -72,9 +94,24 @@ 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;
/** Transaction listener */
private DictionaryModelTypeTransactionListener transactionListener;
private List<String> storeUrls; // stores against which model deletes should be validated
/**
* Set the dictionary DAO
*
@@ -125,6 +162,53 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
this.policyComponent = policyComponent;
}
/**
* Set the workflow service
*
* @param workflowService the workflow service
*/
public void setWorkflowService(WorkflowService workflowService)
{
this.workflowService = workflowService;
}
/**
* Set the search service
*
* @param searchService the search service
*/
public void setSearchService(SearchService searchService)
{
this.searchService = searchService;
}
/**
* Set the namespace service
*
* @param namespaceService the namespace service
*/
public void setNamespaceService(NamespaceService namespaceService)
{
this.namespaceService = namespaceService;
}
/**
* Set the tenant service
*
* @param tenantService the tenant service
*/
public void setTenantService(TenantService tenantService)
{
this.tenantService = tenantService;
}
public void setStoreUrls(List<String> storeUrls)
{
this.storeUrls = storeUrls;
}
/**
* The initialise method
*/
@@ -215,10 +299,11 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
QName modelName = (QName)this.nodeService.getProperty(nodeRef, ContentModel.PROP_MODEL_NAME);
if (modelName != null)
{
// Validate model delete against usages - content and/or workflows
validateModelDelete(modelName);
// Remove the model from the dictionary
dictionaryDAO.removeModel(modelName);
// TODO how can we make this transactional ??
}
}
}
@@ -259,60 +344,73 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
if (pendingModels != null)
{
for (NodeRef nodeRef : pendingModels)
for (final NodeRef nodeRef : pendingModels)
{
// Find out whether the model is active (by default it is)
boolean isActive = false;
Boolean value = (Boolean)nodeService.getProperty(nodeRef, ContentModel.PROP_MODEL_ACTIVE);
if (value != null)
{
isActive = value.booleanValue();
}
String tenantDomain = tenantService.getDomain(nodeRef.getStoreRef().getIdentifier());
String tenantAdminUserName = tenantService.getDomainUser(TenantService.ADMIN_BASENAME, tenantDomain);
// Ignore if the node is a working copy or if its inactive
if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY) == false)
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
if (isActive == true)
{
// 1. Compile the model and update the details on the node
// 2. Re-put the model
public Object doWork()
{
// Find out whether the model is active (by default it is)
boolean isActive = false;
Boolean value = (Boolean)nodeService.getProperty(nodeRef, ContentModel.PROP_MODEL_ACTIVE);
if (value != null)
{
isActive = value.booleanValue();
}
ContentReader contentReader = this.contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
if (contentReader != null)
// Ignore if the node is a working copy or if its inactive
if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY) == false)
{
// Create a model from the current content
M2Model m2Model = M2Model.createModel(contentReader.getContentInputStream());
// TODO what do we do if we don't have a model??
// Try and compile the model
ModelDefinition modelDefintion = m2Model.compile(dictionaryDAO, namespaceDAO).getModelDefinition();
// TODO what do we do if the model does not compile
// Update the meta data for the model
Map<QName, Serializable> props = this.nodeService.getProperties(nodeRef);
props.put(ContentModel.PROP_MODEL_NAME, modelDefintion.getName());
props.put(ContentModel.PROP_MODEL_DESCRIPTION, modelDefintion.getDescription());
props.put(ContentModel.PROP_MODEL_AUTHOR, modelDefintion.getAuthor());
props.put(ContentModel.PROP_MODEL_PUBLISHED_DATE, modelDefintion.getPublishedDate());
props.put(ContentModel.PROP_MODEL_VERSION, modelDefintion.getVersion());
this.nodeService.setProperties(nodeRef, props);
// TODO how do we get the dependancies for this model ??
// Put the model
dictionaryDAO.putModel(m2Model);
if (isActive == true)
{
// 1. Compile the model and update the details on the node
// 2. Re-put the model
ContentReader contentReader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
if (contentReader != null)
{
// Create a model from the current content
M2Model m2Model = M2Model.createModel(contentReader.getContentInputStream());
// Try and compile the model
ModelDefinition modelDefintion = m2Model.compile(dictionaryDAO, namespaceDAO).getModelDefinition();
// Update the meta data for the model
Map<QName, Serializable> props = nodeService.getProperties(nodeRef);
props.put(ContentModel.PROP_MODEL_NAME, modelDefintion.getName());
props.put(ContentModel.PROP_MODEL_DESCRIPTION, modelDefintion.getDescription());
props.put(ContentModel.PROP_MODEL_AUTHOR, modelDefintion.getAuthor());
props.put(ContentModel.PROP_MODEL_PUBLISHED_DATE, modelDefintion.getPublishedDate());
props.put(ContentModel.PROP_MODEL_VERSION, modelDefintion.getVersion());
nodeService.setProperties(nodeRef, props);
// Validate model against dictionary - could be new, unchanged or updated
dictionaryDAO.validateModel(m2Model);
// Put the model
dictionaryDAO.putModel(m2Model);
}
}
else
{
QName modelName = (QName)nodeService.getProperty(nodeRef, ContentModel.PROP_MODEL_NAME);
if (modelName != null)
{
// Validate model delete against usages - content and/or workflows
validateModelDelete(modelName);
// Remove the model from the dictionary
dictionaryDAO.removeModel(modelName);
}
}
}
return null;
}
else
{
QName modelName = (QName)this.nodeService.getProperty(nodeRef, ContentModel.PROP_MODEL_NAME);
if (modelName != null)
{
// Remove the model from the dictionary
dictionaryDAO.removeModel(modelName);
}
}
}
}, tenantAdminUserName);
}
}
}
@@ -360,4 +458,87 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
}
}
}
/**
* validate against repository contents / workflows (e.g. when deleting an existing model)
*
* @param modelName
*/
private void validateModelDelete(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
try
{
dictionaryDAO.getModel(modelName); // ignore returned model definition
}
catch (DictionaryException e)
{
logger.warn("Model ' + modelName + ' does not exist ... skip delete validation : " + e);
return;
}
// check workflow namespace usage
for (WorkflowDefinition workflowDef : workflowService.getDefinitions())
{
String workflowDefName = workflowDef.getName();
String workflowNamespaceURI = QName.createQName(BPMEngineRegistry.getLocalId(workflowDefName), namespaceService).getNamespaceURI();
for (NamespaceDefinition namespace : dictionaryDAO.getNamespaces(modelName))
{
if (workflowNamespaceURI.equals(namespace.getUri()))
{
throw new AlfrescoRuntimeException("Failed to validate model delete - found workflow process definition " + workflowDefName + " using model namespace '" + namespace.getUri() + "'");
}
}
}
// check for type usages
for (TypeDefinition type : dictionaryDAO.getTypes(modelName))
{
validateClass(type);
}
// check for aspect usages
for (AspectDefinition aspect : dictionaryDAO.getAspects(modelName))
{
validateClass(aspect);
}
}
private void validateClass(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+"\"");
if (rs.length() > 0)
{
throw new AlfrescoRuntimeException("Failed to validate model delete - found " + rs.length() + " nodes in store " + store + " with " + classType + " '" + className + "'");
}
}
// check against workflow task usage
for (WorkflowDefinition workflowDef : workflowService.getDefinitions())
{
for (WorkflowTaskDefinition workflowTaskDef : workflowService.getTaskDefinitions(workflowDef.getId()))
{
TypeDefinition workflowTypeDef = workflowTaskDef.metadata;
if (workflowTypeDef.getName().toString().equals(className))
{
throw new AlfrescoRuntimeException("Failed to validate model delete - found task definition in workflow " + workflowDef.getName() + " with " + classType + " '" + className + "'");
}
}
}
}
}

View File

@@ -29,6 +29,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.i18n.MessageDeployer;
import org.alfresco.repo.i18n.MessageService;
@@ -260,15 +261,18 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme
false);
for (NodeRef dictionaryModel : nodeRefs)
{
// TODO - should validate in case of re-deploy - e.g. update or delete
M2Model model = createM2Model(dictionaryModel);
if (model != null)
{
for (M2Namespace namespace : model.getNamespaces())
{
modelMap.put(namespace.getUri(), model);
}
{
boolean isActive = ((Boolean)nodeService.getProperty(dictionaryModel, ContentModel.PROP_MODEL_ACTIVE)).booleanValue();
if (isActive)
{
M2Model model = createM2Model(dictionaryModel);
if (model != null)
{
for (M2Namespace namespace : model.getNamespaces())
{
modelMap.put(namespace.getUri(), model);
}
}
}
}
}
@@ -377,8 +381,16 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme
// an error will be raised during compilation
}
dictionaryDAO.putModel(model);
loadedModels.add(modelName);
try
{
dictionaryDAO.putModel(model);
loadedModels.add(modelName);
}
catch (AlfrescoRuntimeException e)
{
// note: skip with warning - to allow server to start, and hence allow the possibility of fixing the broken model(s)
logger.warn("Failed to load model '" + modelName + "' : " + e);
}
}
}

View File

@@ -64,7 +64,7 @@ public interface RepoAdminService
*
*/
@Auditable(parameters = {"modelStream, modelFileName"})
public QName deployModel(InputStream modelStream, String modelFileName);
public void deployModel(InputStream modelStream, String modelFileName);
/**
* Undeploy custom model
@@ -78,10 +78,16 @@ public interface RepoAdminService
public QName undeployModel(String modelFileName);
/**
* Reload custom model
* Activate custom model
*/
@Auditable(parameters = {"modelFileName"})
public QName reloadModel(String modelFileName);
public QName activateModel(String modelFileName);
/**
* Deactivate custom model
*/
@Auditable(parameters = {"modelFileName"})
public QName deactivateModel(String modelFileName);
//
// Custom Message Management