/*
* (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* bstefanescu, jcarsique
*/
package org.nuxeo.connect.update.task.standalone;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.io.FileUtils;
import org.nuxeo.connect.update.LocalPackage;
import org.nuxeo.connect.update.PackageException;
import org.nuxeo.connect.update.PackageUpdateService;
import org.nuxeo.connect.update.ValidationStatus;
import org.nuxeo.connect.update.task.Command;
import org.nuxeo.connect.update.xml.XmlWriter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* A command based task.
*
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
public abstract class CommandsTask extends AbstractTask {
protected final List<Command> commands;
/**
* The log is generated in the inverse order of commands to ensure last command is rollbacked first.
*/
protected final LinkedList<Command> commandLog;
public CommandsTask(PackageUpdateService pus) {
super(pus);
commands = new ArrayList<>();
commandLog = new LinkedList<>();
}
/**
* Get the commands file from where to load commands for this task.
*/
protected abstract File getCommandsFile() throws PackageException;
@SuppressWarnings("hiding")
@Override
public void initialize(LocalPackage pkg, boolean restart) throws PackageException {
super.initialize(pkg, restart);
loadCommands();
}
/**
* Load the commands of this task given the user parameters. The parameter map may be null.
*/
protected void loadCommands() throws PackageException {
try {
String content = loadParametrizedFile(getCommandsFile(), env);
StringReader reader = new StringReader(content);
readLog(reader);
} catch (IOException e) {
throw new PackageException("Failed to load commands file", e);
}
}
/**
* Gets the commands to execute.
*/
public List<Command> getCommands() {
return commands;
}
/**
* Gets the command log. These are the commands ran so far.
*/
public List<Command> getCommandLog() {
return commandLog;
}
/**
* Adds a command to this task.
*/
public void addCommand(Command command) {
commands.add(command);
}
/**
* User parameters are not handled by default. You need to implement your own task to do this.
*/
@Override
protected void doRun(Map<String, String> params) throws PackageException {
for (Command cmd : commands) {
Command rollbackCmd = cmd.run(this, params);
if (rollbackCmd != null) {
if (rollbackCmd.isPostInstall()) {
commandLog.add(rollbackCmd);
} else {
commandLog.addFirst(rollbackCmd);
}
}
}
// XXX: force a flush?
flush();
}
/**
* @throws PackageException
* @since 5.6
*/
protected abstract void flush() throws PackageException;
@Override
protected void doRollback() throws PackageException {
while (!commandLog.isEmpty()) {
commandLog.removeFirst().run(this, null);
}
}
@Override
public void doValidate(ValidationStatus status) throws PackageException {
// the target platform is not checked at install
// check that commands can be run
for (Command cmd : commands) {
cmd.validate(this, status);
}
}
public void writeLog(File file) throws PackageException {
XmlWriter writer = new XmlWriter();
writer.start("uninstall");
writer.startContent();
for (Command cmd : commandLog) {
cmd.writeTo(writer);
}
writer.end("uninstall");
try {
// replace all occurrences of the installation path with the corresponding variable otherwise the uninstall
// will not work after renaming the installation directory
String content = parametrizePaths(writer.toString());
// replace '//' by '/' if any
content = content.replace(File.separator.concat(File.separator), File.separator);
FileUtils.writeStringToFile(file, content);
} catch (IOException e) {
throw new PackageException("Failed to write commands", e);
}
}
public String parametrizePaths(String content) {
return content.replace(serverPathPrefix, "${" + ENV_SERVER_HOME + "}/");
}
public void readLog(Reader reader) throws PackageException {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new InputSource(reader));
Element root = document.getDocumentElement();
Node node = root.getFirstChild();
while (node != null) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String id = node.getNodeName();
Command cmd = service.getCommand(id);
if (cmd == null) { // may be the name of an embedded class
try {
cmd = (Command) pkg.getData().loadClass(id).getConstructor().newInstance();
} catch (ReflectiveOperationException t) {
throw new PackageException("Unknown command: " + id);
}
}
cmd.initialize(element);
cmd.setPackageUpdateService(service);
commands.add(cmd);
}
node = node.getNextSibling();
}
} catch (ParserConfigurationException | SAXException | IOException e) {
throw new PackageException("Failed to read commands", e);
}
}
}