diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 837a533832..5bebbe3f9c 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -614,24 +614,6 @@ - - - - - - - - - - - - - - - - - - diff --git a/config/alfresco/messages/repoadmin-interpreter-help.txt b/config/alfresco/messages/repoadmin-interpreter-help.txt index 3c8a75bc49..c943b2e1d0 100755 --- a/config/alfresco/messages/repoadmin-interpreter-help.txt +++ b/config/alfresco/messages/repoadmin-interpreter-help.txt @@ -49,22 +49,30 @@ ok> show models ok> deploy model - 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 - 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 +ok> activate model - 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 + + Set repository model to inactive and unload from runtime data dictionary. + + e.g. deactivate model exampleModel.xml ## ## Message Admin Commands diff --git a/config/alfresco/repo-admin-context.xml b/config/alfresco/repo-admin-context.xml index 73b29d4755..c00c67880c 100755 --- a/config/alfresco/repo-admin-context.xml +++ b/config/alfresco/repo-admin-context.xml @@ -11,17 +11,9 @@ - - - - - ${spaces.store} - ${spaces.archive.store} - - @@ -63,38 +55,47 @@ - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + - + + + + + + + + + + + + + + + ${spaces.store} + ${spaces.archive.store} + + + + diff --git a/source/java/org/alfresco/repo/admin/RepoAdminInterpreter.java b/source/java/org/alfresco/repo/admin/RepoAdminInterpreter.java index bc37dc5d17..b41bd349cd 100755 --- a/source/java/org/alfresco/repo/admin/RepoAdminInterpreter.java +++ b/source/java/org/alfresco/repo/admin/RepoAdminInterpreter.java @@ -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 diff --git a/source/java/org/alfresco/repo/admin/RepoAdminService.java b/source/java/org/alfresco/repo/admin/RepoAdminService.java index 811f208515..1a524c00cd 100755 --- a/source/java/org/alfresco/repo/admin/RepoAdminService.java +++ b/source/java/org/alfresco/repo/admin/RepoAdminService.java @@ -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 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 */ diff --git a/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java b/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java index 1f41027b8a..2399827d42 100755 --- a/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java +++ b/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java @@ -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 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 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 models = dictionaryDAO.getModels(); - - List dictionaryModels = new ArrayList(); - for (QName model : models) - { - dictionaryModels.add(model.toPrefixString()); - } - - List nodeRefs = searchService.selectNodes(rootNode, repoModelsLocation.getPath()+CRITERIA_ALL+"["+defaultSubtypeOfDictionaryModel+"]", null, namespaceService, false); - List modelsInRepo = new ArrayList(); - if (nodeRefs.size() > 0) + try + { + Collection models = dictionaryDAO.getModels(); + + List dictionaryModels = new ArrayList(); + for (QName model : models) + { + dictionaryModels.add(model.toPrefixString()); + } + + List 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 contentProps = new HashMap(); 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 titledProps = new HashMap(); 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 versionProps = new HashMap(); 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 + "'"); - } - } - } } } diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java b/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java index 0a6876f99d..93ac146d74 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java @@ -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 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 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() { - 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 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 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 + "'"); + } + } + } + } } diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java b/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java index 1a138045e9..875334b249 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java @@ -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); + } } } diff --git a/source/java/org/alfresco/service/cmr/admin/RepoAdminService.java b/source/java/org/alfresco/service/cmr/admin/RepoAdminService.java index 647c0c34a9..921ad2fd4e 100755 --- a/source/java/org/alfresco/service/cmr/admin/RepoAdminService.java +++ b/source/java/org/alfresco/service/cmr/admin/RepoAdminService.java @@ -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