/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-2010 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* $Id$
*/
package org.exist.backup;
import org.exist.EXistException;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
import org.exist.util.Configuration;
import org.exist.util.DatabaseConfigurationException;
import org.exist.util.SystemExitCodes;
import org.exist.xquery.TerminatedException;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
import org.exist.security.PermissionDeniedException;
import se.softhouse.jargo.Argument;
import se.softhouse.jargo.ArgumentException;
import se.softhouse.jargo.CommandLineParser;
import se.softhouse.jargo.ParsedArguments;
import static org.exist.util.ArgumentUtil.getBool;
import static org.exist.util.ArgumentUtil.getOpt;
import static se.softhouse.jargo.Arguments.*;
public class ExportMain {
/* general arguments */
private static final Argument<?> helpArg = helpArgument("-h", "--help");
private static final Argument<Boolean> verboseArg = optionArgument("-v", "--verbose")
.description("print processed resources to stdout")
.defaultValue(false)
.build();
/* control arguments */
private static final Argument<Boolean> noCheckArg = optionArgument("-n", "--nocheck")
.description("do not run a consistency check. Just export the data.")
.defaultValue(false)
.build();
private static final Argument<Boolean> checkDocsArg = optionArgument("-s", "--check-docs")
.description("scan every document to find errors in the the nodes stored (costs time)")
.defaultValue(false)
.build();
private static final Argument<Boolean> directAccessArg = optionArgument("-D", "--direct")
.description("use an (even more) direct access to the db, bypassing some index structures")
.defaultValue(false)
.build();
private static final Argument<Boolean> exportArg = optionArgument("-x", "--export")
.description("export database contents while preserving as much data as possible")
.defaultValue(false)
.build();
private static final Argument<Boolean> incrementalArg = optionArgument("-i", "--incremental")
.description("create incremental backup (use with --export|-x)")
.defaultValue(false)
.build();
private static final Argument<Boolean> zipArg = optionArgument("-z", "--zip")
.description("write output to a ZIP instead of a file system directory")
.defaultValue(false)
.build();
/* export parameters */
private static final Argument<File> configArg = fileArgument("-c", "--config")
.description("the database configuration (conf.xml) file to use for launching the db.")
.build();
private static final Argument<File> outputDirArg = fileArgument("-d", "--dir")
.description("the directory to which all output will be written.")
.required()
.defaultValue(Paths.get("export").toAbsolutePath().toFile())
.build();
protected static BrokerPool startDB(final Optional<Path> configFile) {
try {
Configuration config;
if (configFile.isPresent()) {
config = new Configuration(configFile.get().toAbsolutePath().toString(), Optional.empty());
} else {
config = new Configuration();
}
config.setProperty(BrokerPool.PROPERTY_EXPORT_ONLY, Boolean.TRUE);
BrokerPool.configure(1, 5, config);
return (BrokerPool.getInstance());
} catch (final DatabaseConfigurationException | EXistException e) {
System.err.println("ERROR: Failed to open database: " + e.getMessage());
}
return (null);
}
@SuppressWarnings("unchecked")
public static void main(final String[] args) {
try {
final ParsedArguments arguments = CommandLineParser
.withArguments(noCheckArg, checkDocsArg, directAccessArg, exportArg, incrementalArg, zipArg)
.andArguments(configArg, outputDirArg)
.andArguments(helpArg, verboseArg)
.parse(args);
process(arguments);
} catch (final ArgumentException e) {
System.out.println(e.getMessageAndUsage());
System.exit(SystemExitCodes.INVALID_ARGUMENT_EXIT_CODE);
}
}
private static void process(final ParsedArguments arguments) {
final boolean verbose = getBool(arguments, verboseArg);
final boolean noCheck = getBool(arguments, noCheckArg);
final boolean checkDocs = getBool(arguments, checkDocsArg);
final boolean direct = getBool(arguments, directAccessArg);
final boolean export = getBool(arguments, exportArg);
final boolean incremental = getBool(arguments, incrementalArg);
final boolean zip = getBool(arguments, zipArg);
final Optional<Path> dbConfig = getOpt(arguments, configArg).map(File::toPath);
final Path exportTarget = arguments.get(outputDirArg).toPath();
final BrokerPool pool = startDB(dbConfig);
if (pool == null) {
System.exit(SystemExitCodes.CATCH_ALL_GENERAL_ERROR_EXIT_CODE);
}
int retval = 0; // return value
try (final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) {
List<ErrorReport> errors = null;
if (!noCheck) {
final ConsistencyCheck checker = new ConsistencyCheck(broker, direct, checkDocs);
errors = checker.checkAll(new CheckCallback());
}
if (errors != null && errors.size() > 0) {
System.err.println("ERRORS FOUND.");
retval = 1;
} else {
System.out.println("No errors.");
}
if (export) {
if (!Files.exists(exportTarget)) {
Files.createDirectories(exportTarget);
} else if(!Files.isDirectory(exportTarget)) {
System.err.println("Output dir already exists and is a file: " + exportTarget.toAbsolutePath().toString());
System.exit(SystemExitCodes.INVALID_ARGUMENT_EXIT_CODE);
}
final SystemExport sysexport = new SystemExport(broker, new Callback(verbose), null, direct);
sysexport.export(exportTarget.toAbsolutePath().toString(), incremental, zip, errors);
}
} catch (final EXistException e) {
System.err.println("ERROR: Failed to retrieve database broker: " + e.getMessage());
retval = SystemExitCodes.NO_BROKER_EXIT_CODE;
} catch (final TerminatedException e) {
System.err.println("WARN: Export was terminated by db.");
retval = SystemExitCodes.TERMINATED_EARLY_EXIT_CODE;
} catch (final PermissionDeniedException pde) {
System.err.println("ERROR: Failed to retrieve database data: " + pde.getMessage());
retval = SystemExitCodes.PERMISSION_DENIED_EXIT_CODE;
} catch (final IOException ioe) {
System.err.println("ERROR: Failed to retrieve database data: " + ioe.getMessage());
retval = SystemExitCodes.IO_ERROR_EXIT_CODE;
} finally {
BrokerPool.stopAll(false);
}
System.exit(retval);
}
private static class Callback implements SystemExport.StatusCallback {
private boolean verbose = false;
public Callback(boolean verbose) {
this.verbose = verbose;
}
@Override
public void startCollection(String path) {
if (verbose) {
System.out.println("Entering collection " + path + " ...");
}
}
@Override
public void startDocument(String name, int count, int docsCount) {
if (verbose) {
System.out.println("Writing document " + name + " [" + (count + 1) + " of " + docsCount + ']');
}
}
@Override
public void error(String message, Throwable exception) {
System.err.println(message);
if (exception != null) {
exception.printStackTrace();
}
}
}
private static class CheckCallback implements org.exist.backup.ConsistencyCheck.ProgressCallback {
@Override
public void startDocument(String name, int current, int count) {
}
@Override
public void startCollection(String path) {
}
@Override
public void error(ErrorReport error) {
System.out.println(error.toString());
}
}
}