diff --git a/config/alfresco/application-context.xml b/config/alfresco/application-context.xml index e44a9415df..efddc1ba9d 100644 --- a/config/alfresco/application-context.xml +++ b/config/alfresco/application-context.xml @@ -30,6 +30,9 @@ + + + diff --git a/config/alfresco/messages/repoadmin-interpreter-help.properties b/config/alfresco/messages/repoadmin-interpreter-help.properties new file mode 100755 index 0000000000..c89dd0ab24 --- /dev/null +++ b/config/alfresco/messages/repoadmin-interpreter-help.properties @@ -0,0 +1 @@ +repoadmin_console.help=alfresco/messages/repoadmin-interpreter-help.txt diff --git a/config/alfresco/messages/repoadmin-interpreter-help.txt b/config/alfresco/messages/repoadmin-interpreter-help.txt new file mode 100755 index 0000000000..3c8a75bc49 --- /dev/null +++ b/config/alfresco/messages/repoadmin-interpreter-help.txt @@ -0,0 +1,97 @@ +## +## Meta commands +## + +ok> help + + List this help. + +ok> r + + Repeat last command. + + +ok> quit | exit + + Quit this console. + +## +## General Repo Admin Commands +## + +ok> show file + + Output the contents of the file located at . + + class path to a file + + e.g. show file alfresco/extension/xxxModel.xml + e.g. show file alfresco/extension/yyy-messages.properties + +ok> show file-list + + Show list of files located at with first match being listed for each filename. + + class path to a list of files. Wildcard * is allowed. For example, to see + a list of message resource bundles that would be loaded, use: /path1/path2/bundlename*.properties + + e.g. show file-list alfresco/extension/* + e.g. show file-list alfresco/extension/*Model.xml + e.g. show file-list alfresco/extension/zzz-messages*.properties + +## +## Model Admin Commands +## + +ok> show models + + Show deployed models - that are stored in the repository data dictionary. + +ok> deploy model + + Upload model to repository and into runtime data dictionary. This will also + set the model as active. + + e.g. deploy model alfresco/extension/exampleModel.xml + +ok> undeploy model + + Permanently delete model from repository (all versions) and from runtime data dictionary. + + e.g. undeploy model exampleModel.xml + +ok> reload model + + Reload (or load for first time) from repository into runtime data dictionary. + + e.g. reload model exampleModel.xml + +## +## Message Admin Commands +## + +ok> show messages + + Show deployed message resource bundles - that are stored in the repository data dictionary. + +ok> deploy messages + + Upload message resource bundle to repository and runtime message service. + + e.g. deploy messages alfresco/extension/lifecycle-messages + +ok> undeploy messages + + Remove message resource bundle from repository and from runtime message service. + + e.g. undeploy messages lifecycle-messages + +ok> reload messages + + Reload message resource bundle from repository into runtime message service. + + e.g. undeploy messages lifecycle-messages + +## +## end +## \ No newline at end of file diff --git a/config/alfresco/public-services-context.xml b/config/alfresco/public-services-context.xml index 84ecf31aed..2a9a35f464 100644 --- a/config/alfresco/public-services-context.xml +++ b/config/alfresco/public-services-context.xml @@ -1419,4 +1419,36 @@ + + + + + + org.alfresco.service.cmr.admin.RepoAdminService + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.readOnly} + ${server.transaction.mode.default} + + + + diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml index a20bb7a19f..e09f634d42 100644 --- a/config/alfresco/public-services-security-context.xml +++ b/config/alfresco/public-services-security-context.xml @@ -795,4 +795,12 @@ + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/repo-admin-context.xml b/config/alfresco/repo-admin-context.xml new file mode 100755 index 0000000000..1233dc614d --- /dev/null +++ b/config/alfresco/repo-admin-context.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + alfresco.messages.repoadmin-interpreter-help + + + + + + + + + + + + + /app:company_home/app:dictionary/app:models + + + + + + + /app:company_home/app:dictionary/app:messages + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/java/org/alfresco/repo/admin/RepoAdminInterpreter.java b/source/java/org/alfresco/repo/admin/RepoAdminInterpreter.java new file mode 100755 index 0000000000..3316e8bf49 --- /dev/null +++ b/source/java/org/alfresco/repo/admin/RepoAdminInterpreter.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.admin; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.service.cmr.admin.RepoAdminService; +import org.alfresco.service.namespace.QName; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; + +/** + * An interactive console for (first cut) Repository Admin Service / API. + * + */ +public class RepoAdminInterpreter extends BaseInterpreter +{ + // dependencies + private RepoAdminService repoAdminService; + + + public void setRepoAdminService(RepoAdminService repoAdminService) + { + this.repoAdminService = repoAdminService; + } + + + /** + * + */ + public static BaseInterpreter getConsoleBean(ApplicationContext context) + { + return (RepoAdminInterpreter)context.getBean("repoAdminInterpreter"); + } + + protected boolean hasAuthority(String username) + { + // must be an "admin" for repository administration + return ((username != null) && (tenantService.getBaseNameUser(username).equals(BaseInterpreter.DEFAULT_ADMIN))); + } + + + /** + * Execute a single command using the BufferedReader passed in for any data needed. + * + * TODO: Use decent parser! + * + * @param line The unparsed command + * @return The textual output of the command. + */ + protected String executeCommand(String line) + throws IOException + { + String[] command = line.split(" "); + if (command.length == 0) + { + command = new String[1]; + command[0] = line; + } + + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + + // repeat last command? + if (command[0].equals("r")) + { + if (lastCommand == null) + { + return "No command entered yet."; + } + return "repeating command " + lastCommand + "\n\n" + executeCommand(lastCommand); + } + + // remember last command + lastCommand = line; + + // execute command + if (command[0].equals("help")) + { + String helpFile = I18NUtil.getMessage("repoadmin_console.help"); + ClassPathResource helpResource = new ClassPathResource(helpFile); + byte[] helpBytes = new byte[500]; + InputStream helpStream = helpResource.getInputStream(); + try + { + int read = helpStream.read(helpBytes); + while (read != -1) + { + bout.write(helpBytes, 0, read); + read = helpStream.read(helpBytes); + } + } + finally + { + helpStream.close(); + } + } + + else if (command[0].equals("show")) + { + if (command.length < 2) + { + return "Syntax Error.\n"; + } + + else if (command[1].equals("file")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + + ClassPathResource file = new ClassPathResource(command[2]); + InputStream fileStream = file.getInputStream(); + + if (fileStream != null) + { + byte[] fileBytes = new byte[500]; + try + { + int read = fileStream.read(fileBytes); + while (read != -1) + { + bout.write(fileBytes, 0, read); + read = fileStream.read(fileBytes); + } + } + finally + { + fileStream.close(); + } + } + else + { + out.println("No matching file found: " + command[2]); + } + + out.println(); + } + + else if (command[1].equals("file-list")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + + // note: classpath should be in form path1/path2/path3/name* + // wildcard * is allowed, e.g. abc/def/workflow-messages*.properties + String pattern = "classpath*:" + command[2]; + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + + Resource[] resources = resolver.getResources(pattern); + ArrayList names = new ArrayList(); + + if (resources != null) + { + for (int i = 0; i < resources.length; i++) + { + String filename = resources[i].getFilename(); + if (! names.contains(filename)) + { + out.println("resource: " + filename + ", url: " + resources[i].getURL()); + names.add(filename); + } + } + } + else + { + out.println("No matching files found: " + command[2]); + } + } + + else if (command[1].equals("models")) + { + List models = repoAdminService.getModels(); + + if ((models != null) && (models.size() > 0)) + { + for (RepoModelDefinition model : models) + { + out.println(model.toString()); + } + } + else + { + out.println("No additional models have been deployed to the Alfresco Repository"); + } + } + + else if (command[1].equals("messages")) + { + List messageResources = repoAdminService.getMessageBundles(); + + if ((messageResources != null) && (messageResources.size() > 0)) + { + for (String messageResourceName : messageResources) + { + out.println("message resource bundle: " + messageResourceName); + } + } + else + { + out.println("No additional messages resource bundles have been deployed to the Alfresco Repository"); + } + } + + else + { + return "No such sub-command, try 'help'.\n"; + } + } + + else if (command[0].equals("deploy")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + + if (command[1].equals("model")) + { + ClassPathResource file = new ClassPathResource(command[2]); + + InputStream fileStream = file.getInputStream(); + + String modelFileName = file.getFilename(); + QName modelQName = repoAdminService.deployModel(fileStream, modelFileName); + out.println("Model deployed: " + modelFileName + " [" + modelQName + "]"); + } + + else if (command[1].equals("messages")) + { + String bundleBasePath = command[2]; + String bundleBaseName = repoAdminService.deployMessageBundle(bundleBasePath); + out.println("Message resource bundle deployed: " + bundleBaseName); + } + + else + { + return "No such sub-command, try 'help'.\n"; + } + } + + else if (command[0].equals("reload")) + { + if (command.length != 2) + { + return "Syntax Error.\n"; + } + + else if (command[1].equals("model")) + { + String modelFileName = command[2]; + QName modelQName = repoAdminService.reloadModel(modelFileName); + out.println("Model (re-)loaded: " + modelFileName + " [" + modelQName + "]"); + } + + else if (command[1].equals("messages")) + { + String bundleBaseName = command[2]; + repoAdminService.reloadMessageBundle(bundleBaseName); + out.println("Message resource bundle (re-)loaded: " + bundleBaseName); + } + + else + { + return "No such sub-command, try 'help'.\n"; + } + } + + else if (command[0].equals("undeploy")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + + if (command[1].equals("model")) + { + String modelFileName = command[2]; + QName modelQName = repoAdminService.undeployModel(modelFileName); + out.println("Model undeployed: " + modelFileName + " [" + modelQName + "]"); + + out.println(""); + out.println("Remaining models:"); + out.print(executeCommand("show models")); + } + + else if (command[1].equals("messages")) + { + String bundleBaseName = command[2]; + repoAdminService.undeployMessageBundle(bundleBaseName); + out.println("Message resource bundle undeployed: " + bundleBaseName); + + out.println(""); + out.println("Remaining message resource bundles:"); + out.print(executeCommand("show messages")); + } + + else + { + return "No such sub-command, try 'help'.\n"; + } + } + + else + { + return "No such command, try 'help'.\n"; + } + + out.flush(); + String retVal = new String(bout.toByteArray()); + out.close(); + return retVal; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/admin/RepoAdminService.java b/source/java/org/alfresco/repo/admin/RepoAdminService.java new file mode 100755 index 0000000000..811f208515 --- /dev/null +++ b/source/java/org/alfresco/repo/admin/RepoAdminService.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.admin; + +import java.io.InputStream; +import java.util.List; + +import org.alfresco.service.namespace.QName; + + +/** + * Repository Admin Service interface. + *

+ * This interface provides certain repository administrative methods to: + * + * - deploy/undeploy custom content models to/from repository + * - deploy/undeploy custom messages resources to/from repository + * + * Initially, this will support models and messages used by workflow process definitions. + */ + +public interface RepoAdminService +{ + /* Custom models managed in the repository */ + + public List getModels(); + + public QName deployModel(InputStream modelStream, String modelFileName); + + public QName undeployModel(String modelFileName); + + public QName reloadModel(String modelFileName); + + /* Custom message/resource bundles managed in the repository */ + + public List getMessageBundles(); + + public String deployMessageBundle(String resourceClasspath); + + public void undeployMessageBundle(String bundleBaseName); + + public void reloadMessageBundle(String bundleBaseName); + +} diff --git a/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java b/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java new file mode 100755 index 0000000000..4d14d277c4 --- /dev/null +++ b/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java @@ -0,0 +1,741 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +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; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ApplicationModel; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +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.service.cmr.admin.RepoAdminService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.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.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ParameterCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; + +/** + * Repository Admin Service Implementation. + *

+ * @see RepoAdminService interface + * + */ + +public class RepoAdminServiceImpl implements RepoAdminService +{ + // Logging support + private static Log logger = LogFactory.getLog("org.alfresco.repo.admin.RepoAdminServiceImpl"); + + // dependencies + private DictionaryDAO dictionaryDAO; + private SearchService searchService; + private NodeService nodeService; + private ContentService contentService; + private NamespaceService namespaceService; + private MessageService messageService; + + private RepositoryLocation repoModelsLocation; + private RepositoryLocation repoMessagesLocation; + + public final static String CRITERIA_ALL = "/*"; // immediate children only + + public final static String defaultSubtypeOfDictionaryModel = "subtypeOf('cm:dictionaryModel')"; + public final static String defaultSubtypeOfContent = "subtypeOf('cm:content')"; + + + public void setDictionaryDAO(DictionaryDAO dictionaryDAO) + { + this.dictionaryDAO = dictionaryDAO; + } + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void setmessageService(MessageService messageService) + { + this.messageService = messageService; + } + + + public void setRepositoryModelsLocation(RepositoryLocation repoModelsLocation) + { + this.repoModelsLocation = repoModelsLocation; + } + + public void setRepositoryMessagesLocation(RepositoryLocation repoMessagesLocation) + { + this.repoMessagesLocation = repoMessagesLocation; + } + + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.admin.RepoAdminService#getModels() + */ + public List getModels() + { + 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) + { + 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(nodeRefs.get(0), 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)); + } + } + } + + return modelsInRepo; + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.admin.RepoAdminService#deployModel(java.io.InputStream, java.lang.String) + */ + public QName deployModel(InputStream modelStream, String modelFileName) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("ModelStream", modelStream); + ParameterCheck.mandatoryString("ModelFileName", modelFileName); + + QName modelName = 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 contentProps = new HashMap(); + contentProps.put(ContentModel.PROP_NAME, modelFileName); + + StoreRef storeRef = repoModelsLocation.getStoreRef(); + NodeRef rootNode = nodeService.getRootNode(storeRef); + + List nodeRefs = searchService.selectNodes(rootNode, repoModelsLocation.getPath(), null, namespaceService, false); + + if (nodeRefs.size() == 0) + { + throw new AlfrescoRuntimeException("Could not find custom models location " + repoModelsLocation.getPath()); + } + else if (nodeRefs.size() > 1) + { + // unexpected: should not find multiple nodes with same name + throw new AlfrescoRuntimeException("Found multiple custom models location " + repoModelsLocation.getPath()); + } + + NodeRef customModelsNodeRef = nodeRefs.get(0); + + nodeRefs = searchService.selectNodes(customModelsNodeRef, "*[@cm:name='"+modelFileName+"' and "+defaultSubtypeOfDictionaryModel+"]", null, namespaceService, false); + + 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")); + */ + + // parse and update model in the dictionary + modelName = dictionaryDAO.putModel(model); + + logger.info("Model re-deployed: " + modelName); + } + else + { + // deploy new model to the repository + + // note: dictionary model type has associated policies that will be invoked + ChildAssociationRef association = nodeService.createNode(customModelsNodeRef, + 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(); + + // 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); + + // 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")); + */ + + // parse and add model to dictionary + modelName = dictionaryDAO.putModel(model); + + logger.info("Model deployed: " + modelName); + } + } + catch (Throwable e) + { + throw new AlfrescoRuntimeException("Model deployment failed", e); + } + + return modelName; + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.admin.RepoAdminService#reloadModel(java.lang.String) + */ + public QName reloadModel(String modelFileName) + { + // 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); + + List nodeRefs = searchService.selectNodes(rootNode, repoModelsLocation.getPath()+"//.[@cm:name='"+modelFileName+"' and "+defaultSubtypeOfDictionaryModel+"]", null, namespaceService, false); + + if (nodeRefs.size() == 0) + { + throw new AlfrescoRuntimeException("Could not find custom model " + modelFileName); + } + else if (nodeRefs.size() > 1) + { + // unexpected: should not find multiple nodes with same name + throw new AlfrescoRuntimeException("Found multiple custom models " + modelFileName); + } + + 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) + { + String modelName = model.getName(); + + // parse and update model in the dictionary + modelQName = dictionaryDAO.putModel(model); + + logger.info("Model loaded: " + modelName); + } + } + catch (Throwable e) + { + throw new AlfrescoRuntimeException("Model deployment failed", e); + } + + return modelQName; + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.admin.RepoAdminService#undeployModel(java.lang.String) + */ + public QName undeployModel(String modelFileName) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("modelFileName", modelFileName); + + QName modelQName = null; + + try + { + // find model in repository + + StoreRef storeRef = repoModelsLocation.getStoreRef(); + NodeRef rootNode = nodeService.getRootNode(storeRef); + + List nodeRefs = searchService.selectNodes(rootNode, repoModelsLocation.getPath()+"//.[@cm:name='"+modelFileName+"' and "+defaultSubtypeOfDictionaryModel+"]", null, namespaceService, false); + + if (nodeRefs.size() == 0) + { + throw new AlfrescoRuntimeException("Could not find custom model " + modelFileName); + } + else if (nodeRefs.size() > 1) + { + // unexpected: should not find multiple nodes with same name + throw new AlfrescoRuntimeException("Found multiple custom models " + modelFileName); + } + + NodeRef modelNodeRef = nodeRefs.get(0); + + String modelName = null; + + try + { + ContentReader cr = contentService.getReader(modelNodeRef, 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 get model " + t); + } + + // permanently remove model from repository + nodeService.addAspect(modelNodeRef, ContentModel.ASPECT_TEMPORARY, null); + nodeService.deleteNode(modelNodeRef); + + modelQName = QName.createQName(modelName, namespaceService); + + dictionaryDAO.removeModel(modelQName); + + logger.info("Model undeployed: " + modelFileName); + } + catch (Throwable e) + { + throw new AlfrescoRuntimeException("Model undeployment failed", e); + } + + return modelQName; + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.admin.RepoAdminService#getMessageBundles() + */ + public List getMessageBundles() + { + StoreRef storeRef = repoMessagesLocation.getStoreRef(); + NodeRef rootNode = nodeService.getRootNode(storeRef); + + Collection registeredBundles = messageService.getRegisteredBundles(); + + List nodeRefs = searchService.selectNodes(rootNode, repoMessagesLocation.getPath()+CRITERIA_ALL+"["+defaultSubtypeOfContent+"]", null, namespaceService, false); + + List resourceBundlesInRepo = new ArrayList(); + + for (NodeRef nodeRef : nodeRefs) + { + String resourceName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + String resourceBundleBaseName = null; + int idx1 = resourceName.indexOf("_"); + if (idx1 > 0) + { + resourceBundleBaseName = resourceName.substring(0, idx1); + } + else + { + int idx2 = resourceName.indexOf("."); + if (idx2 > 0) + { + resourceBundleBaseName = resourceName.substring(0, idx2); + } + else + { + // Unexpected format + logger.warn("Unexpected message resource name: " + resourceName); + } + } + + if (registeredBundles != null) + { + for (String registeredBundlePath : registeredBundles) + { + if (registeredBundlePath.endsWith(resourceBundleBaseName) && (! resourceBundlesInRepo.contains(resourceBundleBaseName))) + { + resourceBundlesInRepo.add(resourceBundleBaseName); + } + } + } + else + { + // unexpected + logger.error("Message bundle not registered: " + resourceBundleBaseName); + } + } + + return resourceBundlesInRepo; + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.admin.RepoAdminService#deployMessageBundle(java.lang.String) + */ + public String deployMessageBundle(String resourceClasspath) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("ResourceClasspath", resourceClasspath); + + String bundleBaseName = null; + + // note: resource path should be in form path1/path2/path3/bundlebasename + int idx = resourceClasspath.lastIndexOf("/"); + + if ((idx != -1) && (idx < (resourceClasspath.length()-1))) + { + bundleBaseName = resourceClasspath.substring(idx+1); + } + + if (bundleBaseName == null) + { + throw new AlfrescoRuntimeException("Message deployment failed - missing bundle base name (path = " + resourceClasspath + ")"); + } + + if (bundleBaseName.indexOf("_") != -1) + { + // currently limited due to parser in DictionaryRepositoryBootstrap + throw new AlfrescoRuntimeException("Message deployment failed - bundle base name '" + bundleBaseName + "' should not contain '_' (underscore)"); + } + + if (bundleBaseName.indexOf(".") != -1) + { + throw new AlfrescoRuntimeException("Message deployment failed - bundle base name '" + bundleBaseName + "' should not contain '.' (period)"); + } + + String pattern = "classpath*:" + resourceClasspath + "*.properties"; + + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + + try + { + Resource[] resources = resolver.getResources(pattern); + + if ((resources != null) && (resources.length > 0)) + { + ArrayList names = new ArrayList(); + ArrayList filteredResources = new ArrayList(); + + for (int i = 0; i < resources.length; i++) + { + String filename = resources[i].getFilename(); + if (! names.contains(filename)) + { + names.add(filename); + filteredResources.add(resources[i]); + } + } + + for (Resource resource : filteredResources) + { + InputStream fileStream = resource.getInputStream(); + String filename = resource.getFilename(); + deployMessageResourceFile(resourceClasspath, filename, fileStream, false); + } + + // register bundle + + StoreRef storeRef = repoMessagesLocation.getStoreRef(); + String repoBundlePath = storeRef.toString() + repoMessagesLocation.getPath() + "/cm:" + bundleBaseName; + messageService.registerResourceBundle(repoBundlePath); + + logger.info("Message resource bundle deployed: " + bundleBaseName); + } + else + { + logger.warn("No message resources found: " + resourceClasspath); + throw new AlfrescoRuntimeException("No message resources found: " + resourceClasspath); + } + } + catch (Throwable e) + { + throw new AlfrescoRuntimeException("Message resource bundle deployment failed ", e); + } + + return bundleBaseName; + } + + /* + * Deploy message resource file + */ + private void deployMessageResourceFile(String bundleBasePath, String name, InputStream resourceStream, boolean registerResourceBundle) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("BundleBasePath", bundleBasePath); + ParameterCheck.mandatory("Name", name); + ParameterCheck.mandatory("ResourceStream", resourceStream); + + try + { + Map contentProps = new HashMap(); + contentProps.put(ContentModel.PROP_NAME, name); + + StoreRef storeRef = repoMessagesLocation.getStoreRef(); + NodeRef rootNode = nodeService.getRootNode(storeRef); + + List nodeRefs = searchService.selectNodes(rootNode, repoMessagesLocation.getPath(), null, namespaceService, false); + + if (nodeRefs.size() == 0) + { + throw new AlfrescoRuntimeException("Could not find custom labels location " + repoMessagesLocation.getPath()); + } + else if (nodeRefs.size() > 1) + { + // unexpected: should not find multiple nodes with same name + throw new AlfrescoRuntimeException("Found multiple custom labels location " + repoMessagesLocation.getPath()); + } + + NodeRef customLabelsNodeRef = nodeRefs.get(0); + + ChildAssociationRef association = nodeService.createNode(customLabelsNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name), + ContentModel.TYPE_CONTENT, + contentProps); + + NodeRef content = association.getChildRef(); + + // add titled aspect (for Web Client display) + Map titledProps = new HashMap(); + titledProps.put(ContentModel.PROP_TITLE, name); + titledProps.put(ContentModel.PROP_DESCRIPTION, name); + nodeService.addAspect(content, ContentModel.ASPECT_TITLED, titledProps); + + // add inline-editable aspect + Map editProps = new HashMap(1, 1.0f); + editProps.put(ApplicationModel.PROP_EDITINLINE, true); + nodeService.addAspect(content, ApplicationModel.ASPECT_INLINEEDITABLE, editProps); + + ContentWriter writer = contentService.getWriter(content, ContentModel.PROP_CONTENT, true); + + writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + writer.setEncoding("UTF-8"); + + writer.putContent(resourceStream); + resourceStream.close(); + + if (registerResourceBundle == true) + { + String bundleBaseName = null; + int idx = bundleBasePath.lastIndexOf("/"); + if ((idx != -1) && (idx != bundleBasePath.length() - 1)) + { + bundleBaseName = bundleBasePath.substring(idx+1); + } + else + { + bundleBaseName = bundleBasePath; + } + + String repoBundlePath = storeRef.toString() + repoMessagesLocation.getPath() + "/cm:" + bundleBaseName; + messageService.registerResourceBundle(repoBundlePath); + } + + logger.info("Message resource deployed: " + name); + } + catch (Throwable e) + { + throw new AlfrescoRuntimeException("Message resource deployment failed", e); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.admin.RepoAdminService#undeployMessageBundle(java.lang.String) + */ + public void undeployMessageBundle(String bundleBaseName) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("bundleBaseName", bundleBaseName); + + try + { + StoreRef storeRef = repoMessagesLocation.getStoreRef(); + + // unregister bundle + String repoBundlePath = storeRef.toString() + repoMessagesLocation.getPath() + "/cm:" + bundleBaseName; + messageService.unregisterResourceBundle(repoBundlePath); + + NodeRef rootNode = nodeService.getRootNode(storeRef); + + List nodeRefs = searchService.selectNodes(rootNode, repoMessagesLocation.getPath()+CRITERIA_ALL, null, namespaceService, false); + + for (NodeRef nodeRef : nodeRefs) + { + String customLabelName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + + if (customLabelName.startsWith(bundleBaseName)) + { + // remove message resource file from the repository + nodeService.deleteNode(nodeRef); + } + } + + logger.info("Message resources undeployed: " + bundleBaseName); + } + catch (Throwable t) + { + throw new AlfrescoRuntimeException("Messages undeployment failed ", t); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.admin.RepoAdminService#reloadMessageBundle(java.lang.String) + */ + public void reloadMessageBundle(String bundleBaseName) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("bundleBaseName", bundleBaseName); + + try + { + StoreRef storeRef = repoMessagesLocation.getStoreRef(); + + // re-register bundle + + String repoBundlePath = storeRef.toString() + repoMessagesLocation.getPath() + "/cm:" + bundleBaseName; + + messageService.unregisterResourceBundle(repoBundlePath); + messageService.registerResourceBundle(repoBundlePath); + + logger.info("Message resources re-loaded: " + bundleBaseName); + } + catch (Throwable e) + { + throw new AlfrescoRuntimeException("Message resource re-load failed", e); + } + } +} diff --git a/source/java/org/alfresco/repo/admin/RepoModelDefinition.java b/source/java/org/alfresco/repo/admin/RepoModelDefinition.java new file mode 100755 index 0000000000..812a524d94 --- /dev/null +++ b/source/java/org/alfresco/repo/admin/RepoModelDefinition.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.admin; + +import org.alfresco.service.cmr.dictionary.ModelDefinition; + + +/** + * Repository-stored Model Definition + * + * + */ +public class RepoModelDefinition +{ + private String repoName; + private String repoVersion; + private ModelDefinition model; + + private boolean loaded; + + RepoModelDefinition(String repoName, String repoVersion, ModelDefinition model, boolean loaded) + { + this.repoName = repoName; + this.repoVersion = repoVersion; + this.model = model; + this.loaded = loaded; + } + + + public String getRepoName() + { + return repoName; + } + + public String getRepoVersion() + { + return repoVersion; + } + + public ModelDefinition getModel() + { + return model; + } + + // JanV - temp + public boolean isLoaded() + { + return loaded; + } + + public String toString() + { + StringBuffer sb = new StringBuffer(); + sb.append("IsLoaded: " + (loaded ? "Y" : "N") + " , "); + sb.append("RepoVersion: " + repoVersion + " , "); + sb.append("RepoName: " + repoName + " , "); + sb.append("ModelQName: " + (model == null ? "n/a" : model.getName()) + " , "); + sb.append("Description: " + (model == null ? "n/a" : model.getDescription()) + " , "); + sb.append("Author: " + (model == null ? "n/a" : model.getAuthor()) + " , "); + sb.append("Published: " + (model == null ? "n/a" : model.getPublishedDate()) + " , "); + sb.append("Version: " + (model == null ? "n/a" : model.getVersion())); + + return sb.toString(); + } +} diff --git a/source/java/org/alfresco/service/cmr/admin/RepoAdminService.java b/source/java/org/alfresco/service/cmr/admin/RepoAdminService.java new file mode 100755 index 0000000000..647c0c34a9 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/admin/RepoAdminService.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.service.cmr.admin; + +import java.io.InputStream; +import java.util.List; + +import org.alfresco.repo.admin.RepoModelDefinition; +import org.alfresco.service.Auditable; +import org.alfresco.service.PublicService; +import org.alfresco.service.namespace.QName; + + +/** + * Repository Admin Service. + * + * Client facing API for interacting with Alfresco Repository Admin services. + * + */ +@PublicService +public interface RepoAdminService +{ + // + // Custom Model Management + // + + /** + * Get list of deployed custom models + * + * - those that are runtime/repo managed only + */ + @Auditable + public List getModels(); + + /** + * Deploy custom model + * + * - allows creation of new models + * - allows update of existing models (*) + * + * (*) TODO - currently no validation (or locking) so can break existing usages + * + */ + @Auditable(parameters = {"modelStream, modelFileName"}) + public QName deployModel(InputStream modelStream, String modelFileName); + + /** + * Undeploy custom model + * + * - allows delete of existing models (*) + * - permanently removes definition from repository (all versions) + * + * (*) TODO - currently no validation (or locking) so can break existing usages + */ + @Auditable(parameters = {"modelFileName"}) + public QName undeployModel(String modelFileName); + + /** + * Reload custom model + */ + @Auditable(parameters = {"modelFileName"}) + public QName reloadModel(String modelFileName); + + // + // Custom Message Management + // + + /** + * Get deployed custom messages resource bundles + * + * - those that are runtime/repo managed only + */ + @Auditable + public List getMessageBundles(); + + /** + * Deploy custom message resource bundle + * + */ + @Auditable(parameters = {"resourceClasspath"}) + public String deployMessageBundle(String resourceClasspath); + + /** + * Undeploy custom message resource bundle + */ + @Auditable(parameters = {"bundleBaseName"}) + public void undeployMessageBundle(String bundleBaseName); + + /** + * Reload custom message resource bundle + */ + @Auditable(parameters = {"bundleBaseName"}) + public void reloadMessageBundle(String bundleBaseName); + +}