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

@@ -614,24 +614,6 @@
</bean> </bean>
<bean id="dictionaryModelType" class="org.alfresco.repo.dictionary.DictionaryModelType" init-method="init">
<property name="dictionaryDAO">
<ref bean="dictionaryDAO" />
</property>
<property name="namespaceDAO">
<ref bean="namespaceDAO" />
</property>
<property name="nodeService">
<ref bean="nodeService"/>
</property>
<property name="contentService">
<ref bean="contentService"/>
</property>
<property name="policyComponent">
<ref bean="policyComponent"/>
</property>
</bean>
<bean id="dictionaryDAO" class="org.alfresco.repo.dictionary.DictionaryDAOImpl"> <bean id="dictionaryDAO" class="org.alfresco.repo.dictionary.DictionaryDAOImpl">
<constructor-arg index="0"> <constructor-arg index="0">
<ref bean="namespaceDAO" /> <ref bean="namespaceDAO" />

View File

@@ -49,22 +49,30 @@ ok> show models
ok> deploy model <fileClassPath> ok> deploy model <fileClassPath>
Upload model to repository and into runtime data dictionary. This will also Upload model to repository and load into runtime data dictionary. This will also
set the model as active. set the repository model as active.
If a model is already deployed then it will be updated and re-deployed.
e.g. deploy model alfresco/extension/exampleModel.xml e.g. deploy model alfresco/extension/exampleModel.xml
ok> undeploy model <modelFileName> ok> undeploy model <modelFileName>
Permanently delete model from repository (all versions) and from runtime data dictionary. Permanently delete model from repository (all versions) and unload from runtime data dictionary.
e.g. undeploy model exampleModel.xml e.g. undeploy model exampleModel.xml
ok> reload model <modelFileName> ok> activate model <modelFileName>
Reload (or load for first time) from repository into runtime data dictionary. Set repository model to active and load into runtime data dictionary.
e.g. reload model exampleModel.xml e.g. activate model exampleModel.xml
ok> deactivate model <modelFileName>
Set repository model to inactive and unload from runtime data dictionary.
e.g. deactivate model exampleModel.xml
## ##
## Message Admin Commands ## Message Admin Commands

View File

@@ -11,18 +11,10 @@
<property name="contentService" ref="ContentService"/> <property name="contentService" ref="ContentService"/>
<property name="namespaceService" ref="namespaceService"/> <property name="namespaceService" ref="namespaceService"/>
<property name="messageService" ref="messageService"/> <property name="messageService" ref="messageService"/>
<property name="workflowService" ref="WorkflowService"/>
<property name="repositoryModelsLocation" ref="customModelsRepositoryLocation"/> <property name="repositoryModelsLocation" ref="customModelsRepositoryLocation"/>
<property name="repositoryMessagesLocation" ref="customMessagesRepositoryLocation"/> <property name="repositoryMessagesLocation" ref="customMessagesRepositoryLocation"/>
<property name="storeUrls">
<list>
<value>${spaces.store}</value>
<value>${spaces.archive.store}</value>
</list>
</property>
</bean> </bean>
<bean id="repoAdminInterpreter" class="org.alfresco.repo.admin.RepoAdminInterpreter"> <bean id="repoAdminInterpreter" class="org.alfresco.repo.admin.RepoAdminInterpreter">
@@ -63,38 +55,47 @@
<bean id="dictionaryRepositoryBootstrap" class="org.alfresco.repo.dictionary.DictionaryRepositoryBootstrap"> <bean id="dictionaryRepositoryBootstrap" class="org.alfresco.repo.dictionary.DictionaryRepositoryBootstrap">
<property name="dictionaryDAO"> <property name="dictionaryDAO" ref="dictionaryDAO"/>
<ref bean="dictionaryDAO"/> <property name="contentService" ref="ContentService"/>
</property> <property name="searchService" ref="SearchService"/>
<property name="contentService"> <property name="transactionService" ref="transactionComponent"/>
<ref bean="contentService"/> <property name="namespaceService" ref="namespaceService"/>
</property> <property name="nodeService" ref="NodeService"/>
<property name="searchService"> <property name="messageService" ref="messageService"/>
<ref bean="searchService"/> <property name="tenantService" ref="tenantService"/>
</property> <property name="tenantDeployerService" ref="tenantAdminService"/>
<property name="transactionService">
<ref bean="transactionComponent"/>
</property>
<property name="namespaceService"><ref bean="namespaceService"/></property> <property name="repositoryModelsLocations">
<property name="nodeService"><ref bean="NodeService"/></property> <list>
<property name="messageService"><ref bean="messageService"/></property> <ref bean="customModelsRepositoryLocation" />
</list>
</property>
<property name="tenantService"><ref bean="tenantService"/></property> <property name="repositoryMessagesLocations">
<property name="tenantDeployerService" ref="tenantAdminService"/> <list>
<ref bean="customMessagesRepositoryLocation" />
<property name="repositoryModelsLocations"> </list>
<list> </property>
<ref bean="customModelsRepositoryLocation" />
</list>
</property>
<property name="repositoryMessagesLocations">
<list>
<ref bean="customMessagesRepositoryLocation" />
</list>
</property>
</bean> </bean>
<bean id="dictionaryModelType" class="org.alfresco.repo.dictionary.DictionaryModelType" init-method="init">
<property name="dictionaryDAO" ref="dictionaryDAO"/>
<property name="namespaceDAO" ref="namespaceDAO"/>
<property name="nodeService" ref="NodeService"/>
<property name="contentService" ref="contentService"/>
<property name="policyComponent" ref="policyComponent"/>
<property name="workflowService" ref="WorkflowService"/>
<property name="searchService" ref="SearchService"/>
<property name="namespaceService" ref="namespaceService"/>
<property name="tenantService" ref="tenantService"/>
<property name="storeUrls">
<list>
<value>${spaces.store}</value>
<value>${spaces.archive.store}</value>
</list>
</property>
</bean>
</beans> </beans>

View File

@@ -255,8 +255,8 @@ public class RepoAdminInterpreter extends BaseInterpreter
InputStream fileStream = file.getInputStream(); InputStream fileStream = file.getInputStream();
String modelFileName = file.getFilename(); String modelFileName = file.getFilename();
QName modelQName = repoAdminService.deployModel(fileStream, modelFileName); repoAdminService.deployModel(fileStream, modelFileName);
out.println("Model deployed: " + modelFileName + " [" + modelQName + "]"); out.println("Model deployed: " + modelFileName);
} }
else if (command[1].equals("messages")) 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) if (command.length != 3)
{ {
@@ -282,15 +282,38 @@ public class RepoAdminInterpreter extends BaseInterpreter
else if (command[1].equals("model")) else if (command[1].equals("model"))
{ {
String modelFileName = command[2]; String modelFileName = command[2];
QName modelQName = repoAdminService.reloadModel(modelFileName); QName modelQName = repoAdminService.activateModel(modelFileName);
out.println("Model (re-)loaded: " + modelFileName + " [" + modelQName + "]"); 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")) else if (command[1].equals("messages"))
{ {
String bundleBaseName = command[2]; String bundleBaseName = command[2];
repoAdminService.reloadMessageBundle(bundleBaseName); repoAdminService.reloadMessageBundle(bundleBaseName);
out.println("Message resource bundle (re-)loaded: " + bundleBaseName); out.println("Message resource bundle reloaded: " + bundleBaseName);
} }
else else

View File

@@ -27,6 +27,7 @@ package org.alfresco.repo.admin;
import java.io.InputStream; import java.io.InputStream;
import java.util.List; import java.util.List;
import org.alfresco.service.Auditable;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
@@ -47,11 +48,13 @@ public interface RepoAdminService
public List<RepoModelDefinition> getModels(); 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 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 */ /* Custom message/resource bundles managed in the repository */

View File

@@ -24,10 +24,7 @@
*/ */
package org.alfresco.repo.admin; package org.alfresco.repo.admin;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; 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.M2Model;
import org.alfresco.repo.dictionary.RepositoryLocation; import org.alfresco.repo.dictionary.RepositoryLocation;
import org.alfresco.repo.i18n.MessageService; import org.alfresco.repo.i18n.MessageService;
import org.alfresco.repo.workflow.BPMEngineRegistry;
import org.alfresco.service.cmr.admin.RepoAdminService; import org.alfresco.service.cmr.admin.RepoAdminService;
import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.DictionaryException;
import org.alfresco.service.cmr.dictionary.ClassDefinition; 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.ChildAssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService; 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.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef; 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.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.NamespaceService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.alfresco.util.ParameterCheck; import org.alfresco.util.ParameterCheck;
@@ -88,13 +78,10 @@ public class RepoAdminServiceImpl implements RepoAdminService
private ContentService contentService; private ContentService contentService;
private NamespaceService namespaceService; private NamespaceService namespaceService;
private MessageService messageService; private MessageService messageService;
private WorkflowService workflowService;
private RepositoryLocation repoModelsLocation; private RepositoryLocation repoModelsLocation;
private RepositoryLocation repoMessagesLocation; 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 CRITERIA_ALL = "/*"; // immediate children only
public final static String defaultSubtypeOfDictionaryModel = "subtypeOf('cm:dictionaryModel')"; public final static String defaultSubtypeOfDictionaryModel = "subtypeOf('cm:dictionaryModel')";
@@ -131,10 +118,6 @@ public class RepoAdminServiceImpl implements RepoAdminService
this.messageService = messageService; this.messageService = messageService;
} }
public void setWorkflowService(WorkflowService workflowService)
{
this.workflowService = workflowService;
}
public void setRepositoryModelsLocation(RepositoryLocation repoModelsLocation) public void setRepositoryModelsLocation(RepositoryLocation repoModelsLocation)
@@ -147,11 +130,6 @@ public class RepoAdminServiceImpl implements RepoAdminService
this.repoMessagesLocation = repoMessagesLocation; this.repoMessagesLocation = repoMessagesLocation;
} }
public void setStoreUrls(List<String> storeUrls)
{
this.storeUrls = storeUrls;
}
/* /*
* (non-Javadoc) * (non-Javadoc)
@@ -162,53 +140,55 @@ public class RepoAdminServiceImpl implements RepoAdminService
StoreRef storeRef = repoModelsLocation.getStoreRef(); StoreRef storeRef = repoModelsLocation.getStoreRef();
NodeRef rootNode = nodeService.getRootNode(storeRef); 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>(); List<RepoModelDefinition> modelsInRepo = new ArrayList<RepoModelDefinition>();
if (nodeRefs.size() > 0) try
{ {
for (NodeRef nodeRef : nodeRefs) Collection<QName> models = dictionaryDAO.getModels();
{
String modelFileName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
String repoVersion = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL);
String modelName = null; List<String> dictionaryModels = new ArrayList<String>();
for (QName model : models)
{
dictionaryModels.add(model.toPrefixString());
}
try List<NodeRef> nodeRefs = searchService.selectNodes(rootNode, repoModelsLocation.getPath()+CRITERIA_ALL+"["+defaultSubtypeOfDictionaryModel+"]", null, namespaceService, false);
{
ContentReader cr = contentService.getReader(nodeRef, ContentModel.TYPE_CONTENT);
InputStream is = cr.getContentInputStream();
M2Model model = M2Model.createModel(is); if (nodeRefs.size() > 0)
is.close(); {
for (NodeRef nodeRef : nodeRefs)
{
String modelFileName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
String repoVersion = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL);
modelName = model.getName(); String modelName = null;
}
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)) ContentReader cr = contentService.getReader(nodeRef, ContentModel.TYPE_CONTENT);
{ InputStream is = cr.getContentInputStream();
// 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)); M2Model model = M2Model.createModel(is);
} is.close();
else
{ modelName = model.getName();
modelsInRepo.add(new RepoModelDefinition(modelFileName, repoVersion, null, false));
}
} // 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)
{
throw new AlfrescoRuntimeException("Failed to get models " + t);
} }
return modelsInRepo; return modelsInRepo;
@@ -218,28 +198,13 @@ public class RepoAdminServiceImpl implements RepoAdminService
* (non-Javadoc) * (non-Javadoc)
* @see org.alfresco.service.cmr.admin.RepoAdminService#deployModel(java.io.InputStream, java.lang.String) * @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 try
{ {
// TODO workaround due to issue with model.toXML() - see below // Check that all the passed values are not null
BufferedReader in = new BufferedReader(new InputStreamReader(modelStream)); ParameterCheck.mandatory("ModelStream", modelStream);
StringBuffer buffer = new StringBuffer(); ParameterCheck.mandatoryString("ModelFileName", modelFileName);
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();
Map<QName, Serializable> contentProps = new HashMap<QName, Serializable>(); Map<QName, Serializable> contentProps = new HashMap<QName, Serializable>();
contentProps.put(ContentModel.PROP_NAME, modelFileName); 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()); 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) if (nodeRefs.size() == 1)
{ {
// re-deploy existing model to the repository // re-deploy existing model to the repository
NodeRef modelNodeRef = nodeRefs.get(0); 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);
} }
else else
{ {
// deploy new model to the repository // deploy new model to the repository
// note: dictionary model type has associated policies that will be invoked // note: dictionary model type has associated policies that will be invoked
ChildAssociationRef association = nodeService.createNode(customModelsNodeRef, ChildAssociationRef association = nodeService.createNode(customModelsSpaceNodeRef,
ContentModel.ASSOC_CONTAINS, ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, modelFileName), QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, modelFileName),
ContentModel.TYPE_DICTIONARY_MODEL, ContentModel.TYPE_DICTIONARY_MODEL,
contentProps); // also invokes policies for DictionaryModelType - e.g. onUpdateProperties contentProps); // also invokes policies for DictionaryModelType - e.g. onUpdateProperties
NodeRef content = association.getChildRef(); modelNodeRef = association.getChildRef();
// add titled aspect (for Web Client display) // add titled aspect (for Web Client display)
Map<QName, Serializable> titledProps = new HashMap<QName, Serializable>(); Map<QName, Serializable> titledProps = new HashMap<QName, Serializable>();
titledProps.put(ContentModel.PROP_TITLE, modelFileName); titledProps.put(ContentModel.PROP_TITLE, modelFileName);
titledProps.put(ContentModel.PROP_DESCRIPTION, 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) // add versionable aspect (set auto-version)
Map<QName, Serializable> versionProps = new HashMap<QName, Serializable>(); Map<QName, Serializable> versionProps = new HashMap<QName, Serializable>();
versionProps.put(ContentModel.PROP_AUTO_VERSION, true); versionProps.put(ContentModel.PROP_AUTO_VERSION, true);
nodeService.addAspect(content, ContentModel.ASPECT_VERSIONABLE, versionProps); nodeService.addAspect(modelNodeRef, 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);
} }
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) catch (Throwable e)
{ {
throw new AlfrescoRuntimeException("Model deployment failed", e); throw new AlfrescoRuntimeException("Model deployment failed ", e);
} }
return modelQName;
} }
/* /*
* (non-Javadoc) * (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 // Check that all the passed values are not null
ParameterCheck.mandatoryString("modelFileName", modelFileName); ParameterCheck.mandatoryString("modelFileName", modelFileName);
QName modelQName = null;
StoreRef storeRef = repoModelsLocation.getStoreRef(); StoreRef storeRef = repoModelsLocation.getStoreRef();
NodeRef rootNode = nodeService.getRootNode(storeRef); NodeRef rootNode = nodeService.getRootNode(storeRef);
@@ -376,31 +334,73 @@ public class RepoAdminServiceImpl implements RepoAdminService
NodeRef modelNodeRef = nodeRefs.get(0); NodeRef modelNodeRef = nodeRefs.get(0);
try 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)
{ {
ContentReader cr = contentService.getReader(modelNodeRef, ContentModel.TYPE_CONTENT); try
InputStream is = cr.getContentInputStream(); {
modelDef = dictionaryDAO.getModel(modelQName);
// create model }
M2Model model = M2Model.createModel(is); catch (DictionaryException e)
is.close(); {
logger.warn(e);
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)
{
throw new AlfrescoRuntimeException("Model deployment failed", e);
} }
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; return modelQName;
} }
@@ -436,39 +436,39 @@ public class RepoAdminServiceImpl implements RepoAdminService
NodeRef modelNodeRef = nodeRefs.get(0); 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); try
InputStream is = cr.getContentInputStream(); {
modelDef = dictionaryDAO.getModel(modelQName);
M2Model model = M2Model.createModel(is); }
is.close(); catch (DictionaryException e)
{
modelName = model.getName(); logger.warn(e);
}
} }
catch (Throwable t)
{ if (isActive)
throw new AlfrescoRuntimeException("Failed to get model " + t); {
if (modelDef == null)
{
logger.warn("Model is set to active but not loaded in Dictionary - trying to undeploy...");
}
} }
// permanently remove model from repository // permanently remove model from repository
nodeService.addAspect(modelNodeRef, ContentModel.ASPECT_TEMPORARY, null); nodeService.addAspect(modelNodeRef, ContentModel.ASPECT_TEMPORARY, null);
nodeService.deleteNode(modelNodeRef); nodeService.deleteNode(modelNodeRef);
modelQName = QName.createQName(modelName, namespaceService); // note: deleted model will be unloaded as part of DictionaryModelType.beforeCommit()
// simple check for usages
validateModelDelete(modelQName);
dictionaryDAO.removeModel(modelQName);
logger.info("Model undeployed: " + modelQName);
} }
catch (Throwable e) catch (Throwable e)
{ {
throw new AlfrescoRuntimeException("Model undeployment failed", e); throw new AlfrescoRuntimeException("Model undeployment failed ", e);
} }
return modelQName; return modelQName;
@@ -770,74 +770,4 @@ public class RepoAdminServiceImpl implements RepoAdminService
throw new AlfrescoRuntimeException("Message resource re-load failed", e); 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.io.Serializable;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.ContentServicePolicies; import org.alfresco.repo.content.ContentServicePolicies;
import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.node.NodeServicePolicies;
import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent; 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.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionListener; 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.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.ContentReader;
import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; 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.NamespaceService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.alfresco.util.GUID; import org.alfresco.util.GUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/** /**
* Dictionary model type behaviour. * Dictionary model type behaviour.
@@ -54,6 +73,9 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
NodeServicePolicies.OnUpdatePropertiesPolicy, NodeServicePolicies.OnUpdatePropertiesPolicy,
NodeServicePolicies.BeforeDeleteNodePolicy NodeServicePolicies.BeforeDeleteNodePolicy
{ {
// Logger
private static Log logger = LogFactory.getLog(DictionaryModelType.class);
/** Key to the pending models */ /** Key to the pending models */
private static final String KEY_PENDING_MODELS = "dictionaryModelType.pendingModels"; private static final String KEY_PENDING_MODELS = "dictionaryModelType.pendingModels";
@@ -72,9 +94,24 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
/** The policy component */ /** The policy component */
private PolicyComponent policyComponent; 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 */ /** Transaction listener */
private DictionaryModelTypeTransactionListener transactionListener; private DictionaryModelTypeTransactionListener transactionListener;
private List<String> storeUrls; // stores against which model deletes should be validated
/** /**
* Set the dictionary DAO * Set the dictionary DAO
* *
@@ -125,6 +162,53 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
this.policyComponent = policyComponent; 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 * The initialise method
*/ */
@@ -215,10 +299,11 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
QName modelName = (QName)this.nodeService.getProperty(nodeRef, ContentModel.PROP_MODEL_NAME); QName modelName = (QName)this.nodeService.getProperty(nodeRef, ContentModel.PROP_MODEL_NAME);
if (modelName != null) if (modelName != null)
{ {
// Validate model delete against usages - content and/or workflows
validateModelDelete(modelName);
// Remove the model from the dictionary // Remove the model from the dictionary
dictionaryDAO.removeModel(modelName); dictionaryDAO.removeModel(modelName);
// TODO how can we make this transactional ??
} }
} }
} }
@@ -259,60 +344,73 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
if (pendingModels != null) if (pendingModels != null)
{ {
for (NodeRef nodeRef : pendingModels) for (final NodeRef nodeRef : pendingModels)
{ {
// Find out whether the model is active (by default it is) String tenantDomain = tenantService.getDomain(nodeRef.getStoreRef().getIdentifier());
boolean isActive = false; String tenantAdminUserName = tenantService.getDomainUser(TenantService.ADMIN_BASENAME, tenantDomain);
Boolean value = (Boolean)nodeService.getProperty(nodeRef, ContentModel.PROP_MODEL_ACTIVE);
if (value != null) AuthenticationUtil.runAs(new RunAsWork<Object>()
{ {
isActive = value.booleanValue(); public Object doWork()
}
// Ignore if the node is a working copy or if its inactive
if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY) == false)
{
if (isActive == true)
{ {
// 1. Compile the model and update the details on the node // Find out whether the model is active (by default it is)
// 2. Re-put the model boolean isActive = false;
Boolean value = (Boolean)nodeService.getProperty(nodeRef, ContentModel.PROP_MODEL_ACTIVE);
ContentReader contentReader = this.contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); if (value != null)
if (contentReader != null)
{ {
// Create a model from the current content isActive = value.booleanValue();
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);
} }
}
else // Ignore if the node is a working copy or if its inactive
{ if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY) == false)
QName modelName = (QName)this.nodeService.getProperty(nodeRef, ContentModel.PROP_MODEL_NAME);
if (modelName != null)
{ {
// Remove the model from the dictionary if (isActive == true)
dictionaryDAO.removeModel(modelName); {
// 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;
} }
} }, 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.List;
import java.util.Map; import java.util.Map;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.repo.i18n.MessageDeployer; import org.alfresco.repo.i18n.MessageDeployer;
import org.alfresco.repo.i18n.MessageService; import org.alfresco.repo.i18n.MessageService;
@@ -261,14 +262,17 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme
for (NodeRef dictionaryModel : nodeRefs) for (NodeRef dictionaryModel : nodeRefs)
{ {
// TODO - should validate in case of re-deploy - e.g. update or delete boolean isActive = ((Boolean)nodeService.getProperty(dictionaryModel, ContentModel.PROP_MODEL_ACTIVE)).booleanValue();
M2Model model = createM2Model(dictionaryModel); if (isActive)
if (model != null)
{ {
for (M2Namespace namespace : model.getNamespaces()) M2Model model = createM2Model(dictionaryModel);
{ if (model != null)
modelMap.put(namespace.getUri(), model); {
} 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 // an error will be raised during compilation
} }
dictionaryDAO.putModel(model); try
loadedModels.add(modelName); {
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"}) @Auditable(parameters = {"modelStream, modelFileName"})
public QName deployModel(InputStream modelStream, String modelFileName); public void deployModel(InputStream modelStream, String modelFileName);
/** /**
* Undeploy custom model * Undeploy custom model
@@ -78,10 +78,16 @@ public interface RepoAdminService
public QName undeployModel(String modelFileName); public QName undeployModel(String modelFileName);
/** /**
* Reload custom model * Activate custom model
*/ */
@Auditable(parameters = {"modelFileName"}) @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 // Custom Message Management