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