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:[email protected]">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 * @throws PackageException 136 * @since 5.6 137 */ 138 protected abstract void flush() throws PackageException; 139 140 @Override 141 protected void doRollback() throws PackageException { 142 while (!commandLog.isEmpty()) { 143 commandLog.removeFirst().run(this, null); 144 } 145 } 146 147 @Override 148 public void doValidate(ValidationStatus status) throws PackageException { 149 // the target platform is not checked at install 150 // check that commands can be run 151 for (Command cmd : commands) { 152 cmd.validate(this, status); 153 } 154 } 155 156 public void writeLog(File file) throws PackageException { 157 XmlWriter writer = new XmlWriter(); 158 writer.start("uninstall"); 159 writer.startContent(); 160 for (Command cmd : commandLog) { 161 cmd.writeTo(writer); 162 } 163 writer.end("uninstall"); 164 try { 165 // replace all occurrences of the installation path with the corresponding variable otherwise the uninstall 166 // will not work after renaming the installation directory 167 String content = parametrizePaths(writer.toString()); 168 // replace '//' by '/' if any 169 content = content.replace(File.separator.concat(File.separator), File.separator); 170 FileUtils.writeStringToFile(file, content, UTF_8); 171 } catch (IOException e) { 172 throw new PackageException("Failed to write commands", e); 173 } 174 } 175 176 public String parametrizePaths(String content) { 177 return content.replace(serverPathPrefix, "${" + ENV_SERVER_HOME + "}/"); 178 } 179 180 public void readLog(Reader reader) throws PackageException { 181 try { 182 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 183 factory.setNamespaceAware(true); 184 DocumentBuilder builder = factory.newDocumentBuilder(); 185 Document document = builder.parse(new InputSource(reader)); 186 Element root = document.getDocumentElement(); 187 Node node = root.getFirstChild(); 188 while (node != null) { 189 if (node.getNodeType() == Node.ELEMENT_NODE) { 190 Element element = (Element) node; 191 String id = node.getNodeName(); 192 Command cmd = service.getCommand(id); 193 if (cmd == null) { // may be the name of an embedded class 194 try { 195 cmd = (Command) pkg.getData().loadClass(id).getConstructor().newInstance(); 196 } catch (ReflectiveOperationException t) { 197 throw new PackageException("Unknown command: " + id); 198 } 199 } 200 cmd.initialize(element); 201 cmd.setPackageUpdateService(service); 202 commands.add(cmd); 203 } 204 node = node.getNextSibling(); 205 } 206 } catch (ParserConfigurationException | SAXException | IOException e) { 207 throw new PackageException("Failed to read commands", e); 208 } 209 } 210 211}