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 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">
<constructor-arg index="0">
<ref bean="namespaceDAO" />

View File

@@ -49,22 +49,30 @@ ok> show models
ok> deploy model <fileClassPath>
Upload model to repository and into runtime data dictionary. This will also
set the model as active.
Upload model to repository and load into runtime data dictionary. This will also
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
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
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

View File

@@ -11,18 +11,10 @@
<property name="contentService" ref="ContentService"/>
<property name="namespaceService" ref="namespaceService"/>
<property name="messageService" ref="messageService"/>
<property name="workflowService" ref="WorkflowService"/>
<property name="repositoryModelsLocation" ref="customModelsRepositoryLocation"/>
<property name="repositoryMessagesLocation" ref="customMessagesRepositoryLocation"/>
<property name="storeUrls">
<list>
<value>${spaces.store}</value>
<value>${spaces.archive.store}</value>
</list>
</property>
</bean>
<bean id="repoAdminInterpreter" class="org.alfresco.repo.admin.RepoAdminInterpreter">
@@ -63,24 +55,14 @@
<bean id="dictionaryRepositoryBootstrap" class="org.alfresco.repo.dictionary.DictionaryRepositoryBootstrap">
<property name="dictionaryDAO">
<ref bean="dictionaryDAO"/>
</property>
<property name="contentService">
<ref bean="contentService"/>
</property>
<property name="searchService">
<ref bean="searchService"/>
</property>
<property name="transactionService">
<ref bean="transactionComponent"/>
</property>
<property name="namespaceService"><ref bean="namespaceService"/></property>
<property name="nodeService"><ref bean="NodeService"/></property>
<property name="messageService"><ref bean="messageService"/></property>
<property name="tenantService"><ref bean="tenantService"/></property>
<property name="dictionaryDAO" ref="dictionaryDAO"/>
<property name="contentService" ref="ContentService"/>
<property name="searchService" ref="SearchService"/>
<property name="transactionService" ref="transactionComponent"/>
<property name="namespaceService" ref="namespaceService"/>
<property name="nodeService" ref="NodeService"/>
<property name="messageService" ref="messageService"/>
<property name="tenantService" ref="tenantService"/>
<property name="tenantDeployerService" ref="tenantAdminService"/>
<property name="repositoryModelsLocations">
@@ -97,4 +79,23 @@
</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>

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')";
@@ -131,10 +118,6 @@ 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,6 +140,10 @@ public class RepoAdminServiceImpl implements RepoAdminService
StoreRef storeRef = repoModelsLocation.getStoreRef();
NodeRef rootNode = nodeService.getRootNode(storeRef);
List<RepoModelDefinition> modelsInRepo = new ArrayList<RepoModelDefinition>();
try
{
Collection<QName> models = dictionaryDAO.getModels();
List<String> dictionaryModels = new ArrayList<String>();
@@ -172,8 +154,6 @@ public class RepoAdminServiceImpl implements RepoAdminService
List<NodeRef> nodeRefs = searchService.selectNodes(rootNode, repoModelsLocation.getPath()+CRITERIA_ALL+"["+defaultSubtypeOfDictionaryModel+"]", null, namespaceService, false);
List<RepoModelDefinition> modelsInRepo = new ArrayList<RepoModelDefinition>();
if (nodeRefs.size() > 0)
{
for (NodeRef nodeRef : nodeRefs)
@@ -183,8 +163,7 @@ public class RepoAdminServiceImpl implements RepoAdminService
String modelName = null;
try
{
ContentReader cr = contentService.getReader(nodeRef, ContentModel.TYPE_CONTENT);
InputStream is = cr.getContentInputStream();
@@ -192,11 +171,7 @@ public class RepoAdminServiceImpl implements RepoAdminService
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))
@@ -210,6 +185,11 @@ public class RepoAdminServiceImpl implements RepoAdminService
}
}
}
}
catch (Throwable t)
{
throw new AlfrescoRuntimeException("Failed to get models " + t);
}
return modelsInRepo;
}
@@ -218,29 +198,14 @@ 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)
{
try
{
// 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();
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);
nodeService.addAspect(modelNodeRef, ContentModel.ASPECT_VERSIONABLE, versionProps);
}
ContentWriter writer = contentService.getWriter(content, ContentModel.PROP_CONTENT, true);
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();
writer.putContent(modelStream); // also invokes policies for DictionaryModelType - e.g. onContentUpdate
modelStream.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"));
*/
// activate the model
nodeService.setProperty(modelNodeRef, ContentModel.PROP_MODEL_ACTIVE, new Boolean(true));
// 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);
}
// note: model will be loaded as part of DictionaryModelType.beforeCommit()
}
catch (Throwable e)
{
throw new AlfrescoRuntimeException("Model deployment failed", e);
throw new AlfrescoRuntimeException("Model deployment failed ", e);
}
return modelQName;
}
/*
* (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,31 +334,73 @@ public class RepoAdminServiceImpl implements RepoAdminService
NodeRef modelNodeRef = nodeRefs.get(0);
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)
{
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)
modelDef = dictionaryDAO.getModel(modelQName);
}
catch (DictionaryException e)
{
// 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);
logger.warn(e);
}
}
catch (Throwable e)
if (activate)
{
throw new AlfrescoRuntimeException("Model deployment failed", e);
// 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);
ModelDefinition modelDef = null;
if (modelQName != null)
{
try
{
ContentReader cr = contentService.getReader(modelNodeRef, ContentModel.TYPE_CONTENT);
InputStream is = cr.getContentInputStream();
M2Model model = M2Model.createModel(is);
is.close();
modelName = model.getName();
modelDef = dictionaryDAO.getModel(modelQName);
}
catch (Throwable t)
catch (DictionaryException e)
{
throw new AlfrescoRuntimeException("Failed to get model " + t);
logger.warn(e);
}
}
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;
@@ -770,74 +770,4 @@ 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,7 +344,14 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
if (pendingModels != null)
{
for (NodeRef nodeRef : pendingModels)
for (final NodeRef nodeRef : pendingModels)
{
String tenantDomain = tenantService.getDomain(nodeRef.getStoreRef().getIdentifier());
String tenantAdminUserName = tenantService.getDomainUser(TenantService.ADMIN_BASENAME, tenantDomain);
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
public Object doWork()
{
// Find out whether the model is active (by default it is)
boolean isActive = false;
@@ -277,27 +369,26 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
// 1. Compile the model and update the details on the node
// 2. Re-put the model
ContentReader contentReader = this.contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
ContentReader contentReader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
if (contentReader != null)
{
// 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);
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());
this.nodeService.setProperties(nodeRef, props);
nodeService.setProperties(nodeRef, props);
// TODO how do we get the dependancies for this model ??
// Validate model against dictionary - could be new, unchanged or updated
dictionaryDAO.validateModel(m2Model);
// Put the model
dictionaryDAO.putModel(m2Model);
@@ -305,14 +396,21 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda
}
else
{
QName modelName = (QName)this.nodeService.getProperty(nodeRef, ContentModel.PROP_MODEL_NAME);
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.Map;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.i18n.MessageDeployer;
import org.alfresco.repo.i18n.MessageService;
@@ -261,7 +262,9 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme
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();
if (isActive)
{
M2Model model = createM2Model(dictionaryModel);
if (model != null)
{
@@ -273,6 +276,7 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme
}
}
}
}
// Load the models ensuring that they are loaded in the correct order
List<String> loadedModels = new ArrayList<String>();
@@ -377,9 +381,17 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme
// an error will be raised during compilation
}
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