001/* 002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * bstefanescu, jcarsique 016 */ 017package org.nuxeo.connect.update.task.standalone; 018 019import java.io.File; 020import java.io.IOException; 021import java.io.Reader; 022import java.io.StringReader; 023import java.util.ArrayList; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Map; 027 028import javax.xml.parsers.DocumentBuilder; 029import javax.xml.parsers.DocumentBuilderFactory; 030import javax.xml.parsers.ParserConfigurationException; 031 032import org.nuxeo.common.utils.FileUtils; 033import org.nuxeo.connect.update.LocalPackage; 034import org.nuxeo.connect.update.PackageException; 035import org.nuxeo.connect.update.PackageUpdateService; 036import org.nuxeo.connect.update.ValidationStatus; 037import org.nuxeo.connect.update.task.Command; 038import org.nuxeo.connect.update.xml.XmlWriter; 039import org.w3c.dom.Document; 040import org.w3c.dom.Element; 041import org.w3c.dom.Node; 042import org.xml.sax.InputSource; 043import org.xml.sax.SAXException; 044 045/** 046 * A command based task. 047 * 048 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 049 */ 050public abstract class CommandsTask extends AbstractTask { 051 052 protected final List<Command> commands; 053 054 /** 055 * The log is generated in the inverse order of commands to ensure last command is rollbacked first. 056 */ 057 protected final LinkedList<Command> commandLog; 058 059 public CommandsTask(PackageUpdateService pus) { 060 super(pus); 061 commands = new ArrayList<Command>(); 062 commandLog = new LinkedList<Command>(); 063 } 064 065 /** 066 * Get the commands file from where to load commands for this task. 067 */ 068 protected abstract File getCommandsFile() throws PackageException; 069 070 @SuppressWarnings("hiding") 071 @Override 072 public void initialize(LocalPackage pkg, boolean restart) throws PackageException { 073 super.initialize(pkg, restart); 074 loadCommands(); 075 } 076 077 /** 078 * Load the commands of this task given the user parameters. The parameter map may be null. 079 */ 080 protected void loadCommands() throws PackageException { 081 try { 082 String content = loadParametrizedFile(getCommandsFile(), env); 083 StringReader reader = new StringReader(content); 084 readLog(reader); 085 } catch (IOException e) { 086 throw new PackageException("Failed to load commands file", e); 087 } 088 } 089 090 /** 091 * Gets the commands to execute. 092 */ 093 public List<Command> getCommands() { 094 return commands; 095 } 096 097 /** 098 * Gets the command log. These are the commands ran so far. 099 */ 100 public List<Command> getCommandLog() { 101 return commandLog; 102 } 103 104 /** 105 * Adds a command to this task. 106 */ 107 public void addCommand(Command command) { 108 commands.add(command); 109 } 110 111 /** 112 * User parameters are not handled by default. You need to implement your own task to do this. 113 */ 114 @Override 115 protected void doRun(Map<String, String> params) throws PackageException { 116 for (Command cmd : commands) { 117 Command rollbackCmd = cmd.run(this, params); 118 if (rollbackCmd != null) { 119 if (rollbackCmd.isPostInstall()) { 120 commandLog.add(rollbackCmd); 121 } else { 122 commandLog.addFirst(rollbackCmd); 123 } 124 } 125 } 126 // XXX: force a flush? 127 flush(); 128 } 129 130 /** 131 * @throws PackageException 132 * @since 5.6 133 */ 134 protected abstract void flush() throws PackageException; 135 136 @Override 137 protected void doRollback() throws PackageException { 138 while (!commandLog.isEmpty()) { 139 commandLog.removeFirst().run(this, null); 140 } 141 } 142 143 @Override 144 public void doValidate(ValidationStatus status) throws PackageException { 145 // the target platform is not checked at install 146 // check that commands can be run 147 for (Command cmd : commands) { 148 cmd.validate(this, status); 149 } 150 } 151 152 public void writeLog(File file) throws PackageException { 153 XmlWriter writer = new XmlWriter(); 154 writer.start("uninstall"); 155 writer.startContent(); 156 for (Command cmd : commandLog) { 157 cmd.writeTo(writer); 158 } 159 writer.end("uninstall"); 160 try { 161 // replace all occurrences of the installation path with the corresponding variable otherwise the uninstall 162 // will not work after renaming the installation directory 163 String content = parametrizePaths(writer.toString()); 164 // replace '//' by '/' if any 165 content = content.replace(File.separator.concat(File.separator), File.separator); 166 FileUtils.writeFile(file, content); 167 } catch (IOException e) { 168 throw new PackageException("Failed to write commands", e); 169 } 170 } 171 172 public String parametrizePaths(String content) { 173 return content.replace(serverPathPrefix, "${" + ENV_SERVER_HOME + "}/"); 174 } 175 176 public void readLog(Reader reader) throws PackageException { 177 try { 178 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 179 factory.setNamespaceAware(true); 180 DocumentBuilder builder = factory.newDocumentBuilder(); 181 Document document = builder.parse(new InputSource(reader)); 182 Element root = document.getDocumentElement(); 183 Node node = root.getFirstChild(); 184 while (node != null) { 185 if (node.getNodeType() == Node.ELEMENT_NODE) { 186 Element element = (Element) node; 187 String id = node.getNodeName(); 188 Command cmd = service.getCommand(id); 189 if (cmd == null) { // may be the name of an embedded class 190 try { 191 cmd = (Command) pkg.getData().loadClass(id).getConstructor().newInstance(); 192 } catch (ReflectiveOperationException t) { 193 throw new PackageException("Unknown command: " + id); 194 } 195 } 196 cmd.initialize(element); 197 commands.add(cmd); 198 } 199 node = node.getNextSibling(); 200 } 201 } catch (ParserConfigurationException | SAXException | IOException e) { 202 throw new PackageException("Failed to read commands", e); 203 } 204 } 205 206}