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}