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