All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.exist.client.InteractiveClient Maven / Gradle / Ivy

/*
 * eXist-db Open Source Native XML Database
 * Copyright (C) 2001 The eXist-db Authors
 *
 * [email protected]
 * http://www.exist-db.org
 *
 * This library 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.1 of the License, or (at your option) any later version.
 *
 * This library 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 Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.exist.client;

import java.awt.Dimension;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.swing.ImageIcon;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;

import org.apache.tools.ant.DirectoryScanner;
import org.exist.SystemProperties;
import org.exist.dom.persistent.XMLUtil;
import org.exist.security.ACLPermission;
import org.exist.security.Account;
import org.exist.security.Permission;
import org.exist.security.SecurityManager;
import org.exist.security.internal.aider.UserAider;
import org.exist.start.CompatibleJavaVersionCheck;
import org.exist.start.StartException;
import org.exist.storage.ElementIndex;
import org.exist.util.*;
import org.exist.util.serializer.SAXSerializer;
import org.exist.util.serializer.SerializerPool;
import org.exist.xmldb.EXistCollectionManagementService;
import org.exist.xmldb.DatabaseInstanceManager;
import org.exist.xmldb.EXistResource;
import org.exist.xmldb.ExtendedResource;
import org.exist.xmldb.IndexQueryService;
import org.exist.xmldb.UserManagementService;
import org.exist.xmldb.EXistXPathQueryService;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.Constants;
import org.jline.reader.*;
import org.jline.reader.impl.history.DefaultHistory;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.xmldb.api.DatabaseManager;
import org.xmldb.api.base.*;
import org.xmldb.api.base.Collection;
import org.xmldb.api.modules.BinaryResource;
import org.xmldb.api.modules.XUpdateQueryService;
import se.softhouse.jargo.ArgumentException;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * Command-line client based on the XML:DB API.
 *
 * @author wolf
 */
public class InteractiveClient {

    // ANSI colors for ls display
    // private final static String ANSI_BLUE = "\033[0;34m";
    private final static String ANSI_CYAN = "\033[0;36m";
    private final static String ANSI_WHITE = "\033[0;37m";

    private final static String EOL = System.getProperty("line.separator");

    // properties

    // keys
    public static final String USER = "user";
    public static final String PASSWORD = "password";
    public static final String URI = "uri";
    public static final String PERMISSIONS = "permissions";
    public static final String INDENT = "indent";
    public static final String ENCODING = "encoding";
    public static final String COLORS = "colors";
    public static final String EDITOR = "editor";
    public static final String EXPAND_XINCLUDES = "expand-xincludes";
    public static final String CONFIGURATION = "configuration";
    public static final String DRIVER = "driver";
    public static final String SSL_ENABLE = "ssl-enable";
    public static final String CREATE_DATABASE = "create-database";
    public static final String LOCAL_MODE = "local-mode-opt";
    public static final String NO_EMBED_MODE = "NO_EMBED_MODE";

    // values
    protected static final String EDIT_CMD = "emacsclient -t $file";
    protected static final Charset ENCODING_DEFAULT = StandardCharsets.UTF_8;
    protected static final String URI_DEFAULT = "xmldb:exist://localhost:8080/exist/xmlrpc";
    protected static final String SSL_ENABLE_DEFAULT = "FALSE";
    protected static final String LOCAL_MODE_DEFAULT = "FALSE";
    protected static final String NO_EMBED_MODE_DEFAULT = "FALSE";
    protected static final String USER_DEFAULT = SecurityManager.DBA_USER;

    protected static final String driver = "org.exist.xmldb.DatabaseImpl";

    // Set
    private final static Properties defaultProps = new Properties();
    static {
        defaultProps.setProperty(DRIVER, driver);
        defaultProps.setProperty(URI, URI_DEFAULT);
        defaultProps.setProperty(USER, USER_DEFAULT);
        defaultProps.setProperty(EDITOR, EDIT_CMD);
        defaultProps.setProperty(INDENT, "true");
        defaultProps.setProperty(ENCODING, ENCODING_DEFAULT.name());
        defaultProps.setProperty(COLORS, "false");
        defaultProps.setProperty(PERMISSIONS, "false");
        defaultProps.setProperty(EXPAND_XINCLUDES, "true");
        defaultProps.setProperty(SSL_ENABLE, SSL_ENABLE_DEFAULT);
    }

    protected static final int colSizes[] = new int[]{10, 10, 10, -1};

    protected static String configuration = null;

    protected final TreeSet completitions = new TreeSet<>();
    protected final LinkedList queryHistory = new LinkedList<>();
    protected Path queryHistoryFile;
    protected Path historyFile;

    protected LineReader console = null;

    private Database database = null;
    protected Collection current = null;
    protected int nextInSet = 1;
    protected Properties properties;

    protected String[] resources = null;
    protected ResourceSet result = null;
    protected final Map namespaceMappings = new HashMap<>();

    /**
     * number of files of a recursive store
     */
    protected int filesCount = 0;

    /**
     * total length of a recursive store
     */
    protected long totalLength = 0;

    protected ClientFrame frame;

    //XXX:make pluggable
    private static boolean havePluggableCommands = false;

    static {
        try {
            Class.forName("org.exist.plugin.command.Commands");
            havePluggableCommands = true;
        } catch (final Exception e) {
            havePluggableCommands = false;
        }
    }

    //*************************************

    private CommandlineOptions options;
    protected XmldbURI path = XmldbURI.ROOT_COLLECTION_URI;
    private Optional lazyTraceWriter = Optional.empty();

    private static final NamedThreadGroupFactory clientThreadGroupFactory = new NamedThreadGroupFactory("java-admin-client");
    private final ThreadGroup clientThreadGroup = clientThreadGroupFactory.newThreadGroup(null);

    /**
     * Display help on commands
     */
    protected void displayHelp() {
        messageln("--- general commands ---");
        messageln("ls                   list collection contents");
        messageln("cd [collection|..]   change current collection");
        messageln("put [file pattern]   upload file or directory to the database");
        messageln("putgz [file pattern] upload possibly gzip compressed file or directory to the database");
        messageln("putzip [file pattern] upload the contents of a ZIP archive to the database");
        messageln("edit [resource] open the resource for editing");
        messageln("mkcol collection     create new sub-collection in current collection");
        messageln("rm document          remove document from current collection");
        messageln("rmcol collection     remove collection");
        messageln("set [key=value]      set property. Calling set without ");
        messageln("                     argument shows current settings.");
        messageln(EOL + "--- search commands ---");
        messageln("find xpath-expr      execute the given XPath expression.");
        messageln("show [position]      display query result value at position.");
        messageln(EOL + "--- user management (may require dba rights) ---");
        messageln("users                list existing users.");
        messageln("adduser username     create a new user.");
        messageln("passwd username      change password for user. ");
        messageln("chown user group [resource]");
        messageln("                     change resource ownership. chown without");
        messageln("                     resource changes ownership of the current");
        messageln("                     collection.");
        messageln("chmod [resource] permissions");
        messageln("                     change resource permissions. Format:");
        messageln("                     [user|group|other]=[+|-][read|write|execute].");
        messageln("                     chmod without resource changes permissions for");
        messageln("                     the current collection.");
        messageln("lock resource        put a write lock on the specified resource.");
        messageln("unlock resource      remove a write lock from the specified resource.");
        if (havePluggableCommands) {
            messageln("svn                  subversion command-line client.");
            messageln("threads              threads debug information.");
        }
        messageln("quit                 quit the program");
    }

    /**
     * The main program for the InteractiveClient class.
     *
     * @param args The command line arguments
     */
    public static void main(final String[] args) {
        try {
            CompatibleJavaVersionCheck.checkForCompatibleJavaVersion();

            final InteractiveClient client = new InteractiveClient();
            if (!client.run(args)) {
                System.exit(SystemExitCodes.CATCH_ALL_GENERAL_ERROR_EXIT_CODE); // return non-zero exit status on failure
            }

        } catch (final StartException e) {
            if (e.getMessage() != null && !e.getMessage().isEmpty()) {
                System.err.println(e.getMessage());
            }
            System.exit(e.getErrorCode());

        } catch(final ArgumentException e) {
            System.out.println(e.getMessageAndUsage());
            System.exit(SystemExitCodes.INVALID_ARGUMENT_EXIT_CODE);

        } catch (final Exception e) {
            e.printStackTrace();
            System.exit(SystemExitCodes.CATCH_ALL_GENERAL_ERROR_EXIT_CODE); // return non-zero exit status on exception
        }
    }

    /**
     * Create a new thread for this client instance.
     *
     * @param threadName the name of the thread
     * @param runnable the function to execute on the thread
     *
     * @return the thread
     */
    Thread newClientThread(final String threadName, final Runnable runnable) {
        return new Thread(clientThreadGroup, runnable, clientThreadGroup.getName() + "." + threadName);
    }

    /**
     * Register XML:DB driver and retrieve root collection.
     *
     * @throws Exception Description of the Exception
     */
    protected void connect() throws Exception {
        System.out.println("Connecting to database...");

        final String uri = properties.getProperty(InteractiveClient.URI);
        if (options.startGUI && frame != null) {
            frame.setStatus("connecting to " + uri);
        }

        // Create database
        final Class cl = Class.forName(properties.getProperty(DRIVER));
        database = (Database) cl.newInstance();

        // Configure database
        database.setProperty(CREATE_DATABASE, "true");
        database.setProperty(SSL_ENABLE, properties.getProperty(SSL_ENABLE));

        // secure empty configuration
        final String configProp = properties.getProperty(InteractiveClient.CONFIGURATION);

        if (configProp != null && (!configProp.isEmpty())) {
            database.setProperty(CONFIGURATION, configProp);
        }

        DatabaseManager.registerDatabase(database);

        final String collectionUri = uri + path;
        current = DatabaseManager.getCollection(collectionUri, properties.getProperty(USER), properties.getProperty(PASSWORD));
        if (options.startGUI && frame != null) {
            frame.setStatus("connected to " + uri + " as user " + properties.getProperty(USER));
        }

        if (database.getProperty(CONFIGURATION) != null) {
            System.out.println("Using config: " + database.getProperty(CONFIGURATION));
        }

        System.out.println("Connected :-)");
    }

    /**
     * Returns the current collection.
     *
     * @return the current collection
     */
    protected Collection getCollection() {
        return current;
    }

    public Properties getProperties() {
        return properties;
    }

    public void reloadCollection() throws XMLDBException {
        current = DatabaseManager.getCollection(properties.getProperty(URI)
                        + path, properties.getProperty(USER),
                properties.getProperty(PASSWORD));
        getResources();
    }

    protected void setProperties() throws XMLDBException {
        String key;
        for (Object o : properties.keySet()) {
            key = (String) o;
            current.setProperty(key, properties.getProperty(key));
        }
    }

    private String getOwnerName(final Permission perm) {
        if (perm.getOwner() == null) {
            return "?";
        } else {
            return perm.getOwner().getName();
        }
    }

    private String getGroupName(final Permission perm) {
        if (perm.getOwner() == null) {
            return "?";
        } else {
            return perm.getGroup().getName();
        }
    }

    /**
     * Get list of resources contained in collection.
     *
     * @throws XMLDBException Description of the Exception
     */
    protected void getResources() throws XMLDBException {
        if (current == null) {
            return;
        }
        setProperties();
        final UserManagementService mgtService = (UserManagementService) current
                .getService("UserManagementService", "1.0");
        final String childCollections[] = current.listChildCollections();
        final String childResources[] = current.listResources();

        resources = new String[childCollections.length + childResources.length];
        //Collection child;
        Permission perm;

        final List tableData = new ArrayList<>(resources.length); // A list of ResourceDescriptor for the GUI

        int i = 0;
        for (; i < childCollections.length; i++) {
            //child = current.getChildCollection(childCollections[i]);

            perm = mgtService.getSubCollectionPermissions(current, childCollections[i]);

            final Date created = mgtService.getSubCollectionCreationTime(current, childCollections[i]);

            if ("true".equals(properties.getProperty(PERMISSIONS))) {
                resources[i] = 'c' + perm.toString() + '\t' + getOwnerName(perm)
                        + '\t' + getGroupName(perm) + '\t'
                        + created.toString() + '\t'
                        + childCollections[i];
            } else {
                resources[i] = childCollections[i];
            }

            if (options.startGUI) {
                try {
                    tableData.add(
                        new ResourceDescriptor.Collection(
                            XmldbURI.xmldbUriFor(childCollections[i]),
                            perm,
                            created
                        )
                    );
                } catch (final URISyntaxException e) {
                    errorln("could not parse collection name into a valid URI: " + e.getMessage());
                }
            }
            completitions.add(childCollections[i]);
        }
        Resource res;
        for (int j = 0; j < childResources.length; i++, j++) {
            res = current.getResource(childResources[j]);
            perm = mgtService.getPermissions(res);
            if (perm == null) {
                System.out.println("null"); //TODO this is not useful!
            }

            final Date lastModificationTime = ((EXistResource) res).getLastModificationTime();

            if ("true".equals(properties.getProperty(PERMISSIONS))) {
                resources[i] = '-' + perm.toString() + '\t' + getOwnerName(perm)
                        + '\t' + getGroupName(perm) + '\t'
                        + lastModificationTime.toString() + '\t'
                        + childResources[j];
            } else {
                resources[i] = childResources[j];
            }

            if (options.startGUI) {
                try {
                    tableData.add(
                        new ResourceDescriptor.Document(
                            XmldbURI.xmldbUriFor(childResources[j]),
                            perm,
                            lastModificationTime
                        )
                    );
                } catch (final URISyntaxException e) {
                    errorln("could not parse document name into a valid URI: " + e.getMessage());
                }
            }
            completitions.add(childResources[j]);
        }
        if (options.startGUI) {
            frame.setResources(tableData);
        }
    }

    /**
     * Display document on screen, by 24 lines.
     *
     * @param str string containing the document.
     */
    protected void more(final String str) {
        final LineNumberReader reader = new LineNumberReader(new StringReader(str));
        String line;
        // int count = 0;
        int ch;
        try {
            while (System.in.available() > 0) {
                System.in.read();
            }

            while ((line = reader.readLine()) != null) {
                if (reader.getLineNumber() % 24 == 0) {
                    System.out.print("line: " + reader.getLineNumber()
                            + "; press [return] for more or [q] for quit.");
                    ch = System.in.read();
                    if (ch == 'q' || ch == 'Q') {
                        return;
                    }
                }
                System.out.println(line);
            }
        } catch (final IOException ioe) {
            System.err.println("IOException: " + ioe);
        }
    }

    /**
     * In interactive mode, process a line entered by the user.
     *
     * @param line the line entered
     * @return true if command != quit
     */
    protected boolean process(final String line) {
        if (options.startGUI) {
            frame.setPath(path);
        }
        final String args[];
        if (line.startsWith("find")) {
            args = new String[2];
            args[0] = "find";
            args[1] = line.substring(5);
        } else {
            final StreamTokenizer tok = new StreamTokenizer(new StringReader(line));
            tok.resetSyntax();
            tok.wordChars(0x21, 0x7FFF);
            tok.quoteChar('"');
            tok.whitespaceChars(0x20, 0x20);

            final List argList = new ArrayList<>(3);
            // int i = 0;
            int token;
            try {
                while ((token = tok.nextToken()) != StreamTokenizer.TT_EOF) {
                    if (token == StreamTokenizer.TT_WORD || token == '"') {
                        argList.add(tok.sval);
                    }
                }
            } catch (final IOException e) {
                System.err.println("Could not parse command line.");
                return true;
            }
            args = new String[argList.size()];
            argList.toArray(args);
        }

        if (args.length == 0) {
            return true;
        }

        try {
            XmldbURI newPath = path;
            final XmldbURI currUri = XmldbURI.xmldbUriFor(properties.getProperty(URI)).resolveCollectionPath(path);
            if (args[0].equalsIgnoreCase("ls")) {
                // list collection contents
                getResources();
                if ("true".equals(properties.getProperty(PERMISSIONS))) {
                    for (String resource : resources) {
                        messageln(resource);
                    }
                } else {
                    for (int i = 0; i < resources.length; i++) {
                        final StringBuilder buf = new StringBuilder();
                        int k = 0;
                        for (int j = 0; i < resources.length && j < 5; i++, j++) {
                            buf.append(resources[i]);
                            buf.append('\t');
                            k = j;
                        }
                        if (k == 4 && i < resources.length) {
                            i--;
                        }
                        messageln(buf.toString());
                    }
                }
            } else if (args[0].equalsIgnoreCase("cd")) {
                // change current collection
                completitions.clear();
                Collection temp;
                XmldbURI collectionPath;
                if (args.length < 2 || args[1] == null) {
                    collectionPath = XmldbURI.ROOT_COLLECTION_URI;
                } else {
                    collectionPath = XmldbURI.xmldbUriFor(args[1]);
                }
                collectionPath = currUri.resolveCollectionPath(collectionPath);
                if (collectionPath.numSegments() == 0) {
                    collectionPath = currUri.resolveCollectionPath(XmldbURI.ROOT_COLLECTION_URI);
                    messageln("cannot go above " + XmldbURI.ROOT_COLLECTION_URI.toString());
                }
                temp = DatabaseManager.getCollection(
                        collectionPath.toString(),
                        properties.getProperty(USER),
                        properties.getProperty(PASSWORD));
                if (temp != null) {
                    current.close();
                    current = temp;
                    newPath = collectionPath.toCollectionPathURI();
                    if (options.startGUI) {
                        frame.setPath(collectionPath.toCollectionPathURI());
                    }
                } else {
                    messageln("no such collection.");
                }
                getResources();
            } else if (args[0].equalsIgnoreCase("cp")) {
                if (args.length != 3) {
                    messageln("cp requires two arguments.");
                    return true;
                }
                final XmldbURI src;
                final XmldbURI dest;
                try {
                    src = XmldbURI.xmldbUriFor(args[1]);
                    dest = XmldbURI.xmldbUriFor(args[2]);
                } catch (final URISyntaxException e) {
                    errorln("could not parse collection name into a valid URI: " + e.getMessage());
                    return false;
                }
                copy(src, dest);
                getResources();

            } else if (args[0].equalsIgnoreCase("edit")) {
                if (args.length == 2) {
                    final XmldbURI resource;
                    try {
                        resource = XmldbURI.xmldbUriFor(args[1]);
                    } catch (final URISyntaxException e) {
                        errorln("could not parse resource name into a valid URI: " + e.getMessage());
                        return false;
                    }
                    editResource(resource);
                } else {
                    messageln("Please specify a resource.");
                }
            } else if (args[0].equalsIgnoreCase("get")) {
                if (args.length < 2) {
                    System.err.println("wrong number of arguments.");
                    return true;
                }
                final XmldbURI resource;
                try {
                    resource = XmldbURI.xmldbUriFor(args[1]);
                } catch (final URISyntaxException e) {
                    errorln("could not parse resource name into a valid URI: " + e.getMessage());
                    return false;
                }
                final Resource res = retrieve(resource);
                // display document
                if (res != null) {
                    final String data;
                    if ("XMLResource".equals(res.getResourceType())) {
                        data = (String) res.getContent();
                    } else {
                        data = new String((byte[]) res.getContent());
                    }
                    if (options.startGUI) {
                        frame.setEditable(false);
                        frame.display(data);
                        frame.setEditable(true);
                    } else {
                        final String content = data;
                        more(content);
                    }
                }
                return true;
            } else if (args[0].equalsIgnoreCase("find")) {
                // search
                if (args.length < 2) {
                    messageln("no query argument found.");
                    return true;
                }
                messageln(args[1]);
                final long start = System.currentTimeMillis();
                result = find(args[1]);
                if (result == null) {
                    messageln("nothing found");
                } else {
                    messageln("found " + result.getSize() + " hits in "
                            + (System.currentTimeMillis() - start) + "ms.");
                }

                nextInSet = 1;

            } else if (args[0].equalsIgnoreCase("run")) {
                if (args.length < 2) {
                    messageln("please specify a query file.");
                    return true;
                }
                try(final BufferedReader reader = Files.newBufferedReader(Paths.get(args[1]))) {
                    final StringBuilder buf = new StringBuilder();
                    String nextLine;
                    while ((nextLine = reader.readLine()) != null) {
                        buf.append(nextLine);
                        buf.append(EOL);
                    }
                    args[1] = buf.toString();
                    final long start = System.currentTimeMillis();
                    result = find(args[1]);
                    if (result == null) {
                        messageln("nothing found");
                    } else {
                        messageln("found " + result.getSize() + " hits in "
                                + (System.currentTimeMillis() - start) + "ms.");
                    }

                    nextInSet = 1;
                } catch (final Exception e) {
                    errorln("An error occurred: " + e.getMessage());
                }
            } else if (args[0].equalsIgnoreCase("show")) {
                // show search results
                if (result == null) {
                    messageln("no result set.");
                    return true;
                }
                try {
                    int start = nextInSet;
                    int count = 1;
                    if (args.length > 1) {
                        start = Integer.parseInt(args[1]);
                    }

                    if (args.length > 2) {
                        count = Integer.parseInt(args[2]);
                    }

                    final int s = (int) result.getSize();
                    if (start < 1 || start > s) {
                        messageln("start offset out of range");
                        return true;
                    }
                    --start;
                    if (start + count > s) {
                        count = s - start;
                    }

                    nextInSet = start + count + 1;
                    for (int i = start; i < start + count; i++) {
                        final Resource r = result.getResource(i);
                        if (options.startGUI) {
                            frame.display((String) r.getContent());
                        } else {
                            more((String) r.getContent());
                        }
                    }
                    messageln("displayed items " + (start + 1) + " to "
                            + (start + count) + " of " + result.getSize());
                } catch (final NumberFormatException nfe) {
                    errorln("wrong argument");
                    return true;
                }

            } else if (args[0].equalsIgnoreCase("mkcol")) {
                // create collection
                if (args.length < 2) {
                    messageln("missing argument.");
                    return true;
                }
                final XmldbURI collUri;
                try {
                    collUri = XmldbURI.xmldbUriFor(args[1]);
                } catch (final URISyntaxException e) {
                    errorln("could not parse collection name into a valid URI: " + e.getMessage());
                    return false;
                }
                final EXistCollectionManagementService mgtService = (EXistCollectionManagementService) current
                        .getService("CollectionManagementService", "1.0");
                final Collection newCollection = mgtService.createCollection(collUri);
                if (newCollection == null) {
                    messageln("could not create collection.");
                } else {
                    messageln("created collection.");
                }

                // re-read current collection
                current = DatabaseManager.getCollection(properties
                        .getProperty(URI)
                        + path, properties.getProperty(USER), properties
                        .getProperty("password"));
                getResources();

            } else if (args[0].equalsIgnoreCase("put")) {
                // put a document or directory into the database
                if (args.length < 2) {
                    messageln("missing argument.");
                    return true;
                }
                final boolean r = parse(Paths.get(args[1]));
                getResources();
                return r;

            } else if (args[0].equalsIgnoreCase("putzip")) {
                // put the contents of a zip archive into the database
                if (args.length < 2) {
                    messageln("missing argument.");
                    return true;
                }
                final boolean r = parseZip(Paths.get(args[1]));
                getResources();
                return r;

            } else if (args[0].equalsIgnoreCase("putgz")) {
                // put the contents of a zip archive into the database
                if (args.length < 2) {
                    messageln("missing argument.");
                    return true;
                }
                final boolean r = parseGZip(args[1]);
                getResources();
                return r;

            } else if (args[0].equalsIgnoreCase("blob")) {
                // put a document or directory into the database
                if (args.length < 2) {
                    messageln("missing argument.");
                    return true;
                }
                storeBinary(args[1]);
                getResources();

            } else if (args[0].equalsIgnoreCase("rm")) {
                // remove document
                if (args.length < 2) {
                    messageln("missing argument.");
                    return true;
                }

                remove(args[1]);

                // re-read current collection
                current = DatabaseManager.getCollection(properties
                        .getProperty("uri")
                        + path, properties.getProperty(USER), properties
                        .getProperty("password"));
                getResources();

            } else if (args[0].equalsIgnoreCase("rmcol")) {
                // remove collection
                if (args.length < 2) {
                    messageln("wrong argument count.");
                    return true;
                }
                final XmldbURI collUri;
                try {
                    collUri = XmldbURI.xmldbUriFor(args[1]);
                } catch (final URISyntaxException e) {
                    errorln("could not parse collection name into a valid URI: " + e.getMessage());
                    return false;
                }
                rmcol(collUri);
                // re-read current collection
                current = DatabaseManager.getCollection(properties
                        .getProperty(URI)
                        + path, properties.getProperty(USER), properties
                        .getProperty(PASSWORD));
                getResources();
            } else if (args[0].equalsIgnoreCase("adduser")) {
                if (args.length < 2) {
                    System.err.println("Usage: adduser name");
                    return true;
                }
                if (options.startGUI) {
                    messageln("command not supported in GUI mode. Please use the \"Edit users\" menu option.");
                    return true;
                }
                try {
                    final UserManagementService mgtService = (UserManagementService) current
                            .getService("UserManagementService", "1.0");

                    String p1;
                    String p2;
                    while (true) {
                        p1 = console.readLine("password: ", '*');
                        p2 = console.readLine("re-enter password: ", '*');
                        if (p1.equals(p2)) {
                            break;
                        }
                        messageln("Entered passwords differ. Try again...");

                    }
                    final UserAider user = new UserAider(args[1]);
                    user.setPassword(p1);
                    final String groups = console.readLine("enter groups: ");
                    final StringTokenizer tok = new StringTokenizer(groups, " ,");
                    while (tok.hasMoreTokens()) {
                        final String group = tok.nextToken();
                        if (group.length() > 0) {
                            user.addGroup(group);
                        }
                    }

                    if (user.getGroups().length == 0) {
                        messageln("No groups specified, will be a member of the '" + SecurityManager.GUEST_GROUP + "' group!");
                        user.addGroup(SecurityManager.GUEST_GROUP);
                    }

                    mgtService.addAccount(user);
                    messageln("User '" + user.getName() + "' created.");
                } catch (final Exception e) {
                    errorln("ERROR: " + e.getMessage());
                    e.printStackTrace();
                }
            } else if (args[0].equalsIgnoreCase("users")) {
                final UserManagementService mgtService = (UserManagementService) current
                        .getService("UserManagementService", "1.0");
                final Account users[] = mgtService.getAccounts();
                messageln("User\t\tGroups");
                messageln("-----------------------------------------");
                for (Account user : users) {
                    System.out.print(user.getName() + "\t\t");
                    final String[] groups = user.getGroups();
                    for (int j = 0; j < groups.length; j++) {
                        System.out.print(groups[j]);
                        if (j + 1 < groups.length) {
                            System.out.print(", ");
                        }
                    }
                    System.out.println();
                }
            } else if (args[0].equalsIgnoreCase("passwd")) {
                if (options.startGUI) {
                    messageln("command not supported in GUI mode. Please use the \"Edit users\" menu option.");
                    return true;
                }
                if (args.length < 2) {
                    messageln("Usage: passwd username");
                    return true;
                }
                try {
                    final UserManagementService mgtService = (UserManagementService) current
                            .getService("UserManagementService", "1.0");
                    final Account user = mgtService.getAccount(args[1]);
                    if (user == null) {
                        messageln("no such user.");
                        return true;
                    }
                    String p1;
                    String p2;
                    while (true) {
                        p1 = console.readLine("password: ", '*');
                        p2 = console.readLine("re-enter password: ", '*');
                        if (p1.equals(p2)) {
                            break;
                        }
                        System.out.println(EOL + "entered passwords differ. Try again...");
                    }
                    user.setPassword(p1);
                    mgtService.updateAccount(user);
                    properties.setProperty(PASSWORD, p1);
                } catch (final Exception e) {
                    errorln("ERROR: " + e.getMessage());
                    e.printStackTrace();
                }
            } else if (args[0].equalsIgnoreCase("chmod")) {
                if (args.length < 2) {
                    System.out.println("Usage: chmod [resource] mode");
                    return true;
                }

                final Collection temp;
                if (args.length == 3) {
                    System.out.println("trying collection: " + args[1]);
                    temp = current.getChildCollection(args[1]);
                    if (temp == null) {
                        System.out.println(EOL + "trying resource: " + args[1]);
                        final Resource r = current.getResource(args[1]);
                        if (r != null) {
                            final UserManagementService mgtService = (UserManagementService) current
                                    .getService("UserManagementService", "1.0");
                            mgtService.chmod(r, args[2]);
                        } else {
                            System.err.println("Resource " + args[1]
                                    + " not found.");
                        }
                    } else {
                        final UserManagementService mgtService = (UserManagementService) temp
                                .getService("UserManagementService", "1.0");
                        mgtService.chmod(args[2]);
                    }
                } else {
                    final UserManagementService mgtService = (UserManagementService) current
                            .getService("UserManagementService", "1.0");
                    mgtService.chmod(args[1]);
                }
                // re-read current collection
                current = DatabaseManager.getCollection(properties
                        .getProperty(URI)
                        + path, properties.getProperty(USER), properties
                        .getProperty(PASSWORD));
                getResources();
            } else if (args[0].equalsIgnoreCase("chown")) {
                if (args.length < 3) {
                    System.out
                            .println("Usage: chown username group [resource]");
                    return true;
                }

                final Collection temp;
                if (args.length == 4) {
                    temp = current.getChildCollection(args[3]);
                } else {
                    temp = current;
                }
                if (temp != null) {
                    final UserManagementService mgtService = (UserManagementService) temp
                            .getService("UserManagementService", "1.0");
                    final Account u = mgtService.getAccount(args[1]);
                    if (u == null) {
                        System.out.println("unknown user");
                        return true;
                    }
                    mgtService.chown(u, args[2]);
                    System.out.println("owner changed.");
                    getResources();
                    return true;
                }
                final Resource res = current.getResource(args[3]);
                if (res != null) {
                    final UserManagementService mgtService = (UserManagementService) current
                            .getService("UserManagementService", "1.0");
                    final Account u = mgtService.getAccount(args[1]);
                    if (u == null) {
                        System.out.println("unknown user");
                        return true;
                    }
                    mgtService.chown(res, u, args[2]);
                    getResources();
                    return true;
                }
                System.err.println("Resource " + args[3] + " not found.");

            } else if (args[0].equalsIgnoreCase("lock") || args[0].equalsIgnoreCase("unlock")) {
                if (args.length < 2) {
                    messageln("Usage: lock resource");
                    return true;
                }
                final Resource res = current.getResource(args[1]);
                if (res != null) {
                    final UserManagementService mgtService = (UserManagementService)
                            current.getService("UserManagementService", "1.0");
                    final Account user = mgtService.getAccount(properties.getProperty(USER, "guest"));
                    if (args[0].equalsIgnoreCase("lock")) {
                        mgtService.lockResource(res, user);
                    } else {
                        mgtService.unlockResource(res);
                    }
                }

            } else if (args[0].equalsIgnoreCase("elements")) {
                System.out.println("Element occurrences in collection "
                        + current.getName());
                System.out
                        .println("--------------------------------------------"
                                + "-----------");
                final IndexQueryService service = (IndexQueryService) current
                        .getService("IndexQueryService", "1.0");
                final Occurrences[] elements = service.getIndexedElements(true);
                for (Occurrences element : elements) {
                    System.out
                            .println(formatString(element.getTerm().toString(),
                                    Integer.toString(element
                                            .getOccurrences()), 50));
                }
                return true;

            } else if (args[0].equalsIgnoreCase("xupdate")) {
                if (options.startGUI) {
                    messageln("command not supported in GUI mode.");
                    return true;
                }
                final StringBuilder command = new StringBuilder();
                try {
                    while (true) {
                        final String lastLine = console.readLine("| ");
                        if (lastLine == null || lastLine.length() == 0) {
                            break;
                        }
                        command.append(lastLine);
                    }
                } catch (final UserInterruptException e) {
                    //TODO report error?
                }
                final String xupdate = ""
                        + command.toString() + "";
                final XUpdateQueryService service = (XUpdateQueryService) current
                        .getService("XUpdateQueryService", "1.0");
                final long mods = service.update(xupdate);
                System.out.println(mods + " modifications processed.");

            } else if (args[0].equalsIgnoreCase("map")) {
                final StringTokenizer tok = new StringTokenizer(args[1], "= ");
                final String prefix;
                if (args[1].startsWith("=")) {
                    prefix = "";
                } else {
                    if (tok.countTokens() < 2) {
                        messageln("please specify a namespace/prefix mapping as: prefix=namespaceURI");
                        return true;
                    }
                    prefix = tok.nextToken();
                }
                final String uri = tok.nextToken();
                namespaceMappings.put(prefix, uri);

            } else if (args[0].equalsIgnoreCase("set")) {
                if (args.length == 1) {
                    properties.list(System.out);
                } else {
                    try {
                        final StringTokenizer tok = new StringTokenizer(args[1], "= ");
                        if (tok.countTokens() < 2) {
                            System.err.println("please specify a key=value pair");
                            return true;
                        }
                        final String key = tok.nextToken();
                        final String val = tok.nextToken();
                        properties.setProperty(key, val);
                        current.setProperty(key, val);
                        getResources();
                    } catch (final Exception e) {
                        System.err.println("Exception: " + e.getMessage());
                    }
                }
            } else if (args[0].equalsIgnoreCase("shutdown")) {
                final DatabaseInstanceManager mgr = (DatabaseInstanceManager) current
                        .getService("DatabaseInstanceManager", "1.0");
                if (mgr == null) {
                    messageln("Service is not available");
                    return true;
                }
                mgr.shutdown();
                return true;
            } else if (args[0].equalsIgnoreCase("help") || "?".equals(args[0])) {
                displayHelp();
            } else if (args[0].equalsIgnoreCase("quit")) {
                return false;
                //XXX:make it pluggable
            } else if (havePluggableCommands) {
                final EXistCollectionManagementService mgtService = (EXistCollectionManagementService) current.getService("CollectionManagementService", "1.0");
                try {
                    mgtService.runCommand(args);
                } catch(final XMLDBException e) {
                    if(e.getCause() != null && e.getCause().getClass().getName().equals("org.exist.plugin.command.CommandNotFoundException")) {
                        messageln("unknown command: '" + args[0] + "'");
                        return true;
                    } else {
                        throw e;
                    }
                }
                //****************************************************************
            } else {
                messageln("unknown command: '" + args[0] + "'");
                return true;
            }
            path = newPath;
            return true;
        } catch (final Throwable e) {
            if (options.startGUI) {
                ClientFrame.showErrorMessage(getExceptionMessage(e), e);
            } else {
                errorln(getExceptionMessage(e));
                e.printStackTrace();
            }
            return true;
        }
    }

    /**
     * @param name
     */
    private void editResource(final XmldbURI name) {
        try {
            final Resource doc = retrieve(name, properties.getProperty(OutputKeys.INDENT, "yes")); //$NON-NLS-1$
            final DocumentView view = new DocumentView(this, name, doc, properties);
            view.setSize(new Dimension(640, 400));
            view.viewDocument();
        } catch (final XMLDBException ex) {
            errorln("XMLDB error: " + ex.getMessage());
        }
    }

    private Optional getTraceWriter() {

        //should there be a trace writer?
        if(options.traceQueriesFile.isPresent()) {

            //lazy initialization
            if(!lazyTraceWriter.isPresent()) {
                try {
                    final Writer traceWriter = Files.newBufferedWriter(options.traceQueriesFile.get(), UTF_8);
                    traceWriter.write("" + EOL);
                    traceWriter.write("" + EOL);
                    this.lazyTraceWriter = Optional.of(traceWriter);
                } catch(final IOException ioe) {
                    errorln("Cannot open file " + options.traceQueriesFile.get());
                    return Optional.empty();
                }
            }

            return lazyTraceWriter;

        } else {
            return Optional.empty();
        }
    }

    private ResourceSet find(String xpath) throws XMLDBException {
        if (xpath.substring(xpath.length() - EOL.length()).equals(EOL)) {
            xpath = xpath.substring(0, xpath.length() - EOL.length());
        }

        final String xpathCopy = xpath;
        getTraceWriter().ifPresent(writer -> {
            try {
                writer.write("");
                writer.write(xpathCopy);
                writer.write("");
                writer.write(EOL);
            } catch (final IOException e) {
                //TODO report error?
            }
        });

        String sortBy = null;
        final int p = xpath.indexOf(" sort by ");
        if (p != Constants.STRING_NOT_FOUND) {
            final String xp = xpath.substring(0, p);
            sortBy = xpath.substring(p + " sort by ".length());
            xpath = xp;
            System.out.println("XPath =   " + xpath);
            System.out.println("Sort-by = " + sortBy);
        }

        final EXistXPathQueryService service = (EXistXPathQueryService) current.getService("XPathQueryService", "1.0");
        service.setProperty(OutputKeys.INDENT, properties.getProperty(INDENT));
        service.setProperty(OutputKeys.ENCODING, properties.getProperty(ENCODING));

        for (final Map.Entry mapping : namespaceMappings.entrySet()) {
            service.setNamespace(mapping.getKey(), mapping.getValue());
        }

        return (sortBy == null) ? service.query(xpath) : service.query(xpath, sortBy);
    }

    protected final Resource retrieve(final XmldbURI resource) throws XMLDBException {
        return retrieve(resource, properties.getProperty(INDENT));
    }

    protected final Resource retrieve(final XmldbURI resource, final String indent) throws XMLDBException {
        final Resource res = current.getResource(resource.toString());
        if (res == null) {
            messageln("document not found.");
            return null;
        }
        return res;
    }

    private void remove(final String pattern) throws XMLDBException {
        final Collection collection = current;
        if (pattern.startsWith("/")) {
            System.err.println("path pattern should be relative to current collection");
            return;
        }
        final Resource resources[];
        final Resource res = collection.getResource(pattern);
        if (res == null) {
            resources = CollectionScanner.scan(collection, "", pattern);
        } else {
            resources = new Resource[1];
            resources[0] = res;
        }
        for (Resource resource : resources) {
            message("removing document " + resource.getId() + " ...");
            final Collection parent = resource.getParentCollection();
            parent.removeResource(resource);
            messageln("done.");
        }
    }

    private void xupdate(final Optional resource, final Path file) throws XMLDBException, IOException {
        if (!(Files.exists(file) && Files.isReadable(file))) {
            messageln("cannot read file " + file.normalize().toAbsolutePath().toString());
            return;
        }
        final String commands = XMLUtil.readFile(file, UTF_8);
        final XUpdateQueryService service = (XUpdateQueryService) current.getService("XUpdateQueryService", "1.0");
        final long modifications;
        if (resource.isPresent()) {
            modifications = service.updateResource(resource.get(), commands);
        } else {
            modifications = service.update(commands);

        }
        messageln(modifications + " modifications processed " + "successfully.");
    }

    private void rmcol(final XmldbURI collection) throws XMLDBException {
        final EXistCollectionManagementService mgtService = (EXistCollectionManagementService) current.getService("CollectionManagementService", "1.0");
        message("removing collection " + collection + " ...");
        mgtService.removeCollection(collection);
        messageln("done.");
    }

    private void copy(final XmldbURI source, XmldbURI destination) throws XMLDBException {
        try {
            final EXistCollectionManagementService mgtService = (EXistCollectionManagementService) current.getService("CollectionManagementService", "1.0");
            final XmldbURI destName = destination.lastSegment();
            final Collection destCol = resolveCollection(destination);
            if (destCol == null) {
                if (destination.numSegments() == 1) {
                    destination = XmldbURI.xmldbUriFor(current.getName());
                } else {
                    destination = destination.removeLastSegment();
                }
            }
            final Resource srcDoc = resolveResource(source);
            if (srcDoc != null) {
                final XmldbURI resourcePath = XmldbURI.xmldbUriFor(srcDoc.getParentCollection().getName()).append(srcDoc.getId());
                messageln("Copying resource '" + resourcePath + "' to '" + destination + "'");
                mgtService.copyResource(resourcePath, destination, destName);
            } else {
                messageln("Copying collection '" + source + "' to '" + destination + "'");
                mgtService.copy(source, destination, destName);
            }
        } catch (final URISyntaxException e) {
            errorln("could not parse name into a valid URI: " + e.getMessage());
        }
    }

    private void reindex() throws XMLDBException {
        final IndexQueryService service = (IndexQueryService)
                current.getService("IndexQueryService", "1.0");
        message("reindexing collection " + current.getName());
        service.reindexCollection();
        messageln("done.");
    }

    private void storeBinary(final String fileName) throws XMLDBException {
        final Path file = Paths.get(fileName).normalize();
        if (Files.isReadable(file)) {
            final MimeType mime = MimeTable.getInstance().getContentTypeFor(FileUtils.fileName(file));
            final BinaryResource resource = (BinaryResource) current.createResource(FileUtils.fileName(file), BinaryResource.RESOURCE_TYPE);
            resource.setContent(file);
            ((EXistResource) resource).setMimeType(mime == null ? "application/octet-stream" : mime.getName());
            current.storeResource(resource);
        }
    }

    private synchronized boolean findRecursive(final Collection collection, final Path dir, final XmldbURI base) throws XMLDBException {
        Collection c;
        Resource document;
        EXistCollectionManagementService mgtService;
        //The XmldbURIs here aren't really used...
        XmldbURI next;
        MimeType mimeType;

        try {
            final List files = FileUtils.list(dir);
            int i = 0;
            for (final Path file : files) {
                next = base.append(FileUtils.fileName(file));
                try {
                    if (Files.isDirectory(file)) {
                        messageln("entering directory " + file.toAbsolutePath());
                        c = collection.getChildCollection(FileUtils.fileName(file));
                        if (c == null) {
                            mgtService = (EXistCollectionManagementService) collection.getService("CollectionManagementService", "1.0");
                            c = mgtService.createCollection(XmldbURI.xmldbUriFor(FileUtils.fileName(file)));
                        }

                        if (c instanceof Observable && options.verbose) {
                            final ProgressObserver observer = new ProgressObserver();
                            ((Observable) c).addObserver(observer);
                        }
                        findRecursive(c, file, next);
                    } else {
                        final long start1 = System.currentTimeMillis();
                        mimeType = MimeTable.getInstance().getContentTypeFor(FileUtils.fileName(file));
                        if (mimeType == null) {
                            messageln("File " + FileUtils.fileName(file) + " has an unknown suffix. Cannot determine file type.");
                            mimeType = MimeType.BINARY_TYPE;
                        }
                        message("storing document " + FileUtils.fileName(file) + " (" + i + " of " + files.size() + ") " + "...");
                        document = collection.createResource(FileUtils.fileName(file), mimeType.getXMLDBType());
                        document.setContent(file);
                        ((EXistResource) document).setMimeType(mimeType.getName());
                        collection.storeResource(document);
                        ++filesCount;
                        messageln(" " + FileUtils.sizeQuietly(file) + " bytes in " + (System.currentTimeMillis() - start1) + "ms.");
                    }
                } catch (final URISyntaxException e) {
                    errorln("uri syntax exception parsing " + file.toAbsolutePath() + ": " + e.getMessage());
                }
                i++;
            }
            return true;
        } catch(final IOException e) {
            throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, e);
        }
    }

    /**
     * Stores given Resource
     *
     * @param file file or directory
     * @return TRUE if file or files in directory were all correctly stored.
     * @throws XMLDBException An error was detected.
     */
    protected synchronized boolean parse(final Path file) throws XMLDBException {
        try {
            Resource document;
            // String xml;

            if (current instanceof Observable && options.verbose) {
                final ProgressObserver observer = new ProgressObserver();
                ((Observable) current).addObserver(observer);
            }

            List files = new ArrayList<>();
            if (Files.isReadable(file)) {
                // TODO, same logic as for the graphic client
                if (Files.isDirectory(file)) {
                    if (options.reindexRecurse) {
                        filesCount = 0;
                        final long start = System.currentTimeMillis();
                        final boolean result = findRecursive(current, file, path);
                        messageln("storing " + filesCount + " files took " + ((System.currentTimeMillis() - start) / 1000) + "sec.");
                        return result;
                    }
                    files = FileUtils.list(file);
                } else {
                    files.add(file);
                }
            } else {
                final DirectoryScanner directoryScanner = new DirectoryScanner();
                directoryScanner.setIncludes(new String[] { file.toString() });
                //TODO(AR) do we need to call scanner.setBasedir()?
                directoryScanner.setCaseSensitive(true);
                directoryScanner.scan();
                for (final String includedFile : directoryScanner.getIncludedFiles()) {
//                    files.add(baseDir.resolve(includedFile));
                    files.add(Paths.get(includedFile));
                }
            }

            final long start0 = System.currentTimeMillis();
            long bytes = 0;
            MimeType mimeType;
            for (int i = 0; i < files.size(); i++) {
                if (Files.isDirectory(files.get(i))) {
                    continue;
                }
                final long start = System.currentTimeMillis();
                mimeType = MimeTable.getInstance().getContentTypeFor(FileUtils.fileName(files.get(i)));
                if (mimeType == null) {
                    mimeType = MimeType.BINARY_TYPE;
                }
                document = current.createResource(FileUtils.fileName(files.get(i)), mimeType.getXMLDBType());
                message("storing document " + FileUtils.fileName(files.get(i)) + " (" + (i + 1) + " of " + files.size() + ") ...");
                document.setContent(files.get(i));
                ((EXistResource) document).setMimeType(mimeType.getName());
                current.storeResource(document);
                messageln("done.");
                messageln("parsing " + FileUtils.sizeQuietly(files.get(i)) + " bytes took " + (System.currentTimeMillis() - start) + "ms." + EOL);
                bytes += FileUtils.sizeQuietly(files.get(i));
            }
            messageln("parsed " + bytes + " bytes in " + (System.currentTimeMillis() - start0) + "ms.");
            return true;
        } catch (final IOException e) {
            e.printStackTrace();
            throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, e);
        }
    }


    private synchronized boolean findGZipRecursive(final Collection collection, final Path dir, final XmldbURI base) throws XMLDBException, IOException {

        final List files = FileUtils.list(dir);
        Collection c;
        Resource document;
        EXistCollectionManagementService mgtService;
        //The XmldbURIs here aren't really used...
        XmldbURI next;
        MimeType mimeType;
        int i = 0;
        for (final Path file : files) {
            i++;
            next = base.append(FileUtils.fileName(file));
            try {
                if (Files.isDirectory(file)) {
                    messageln("entering directory " + file.toAbsolutePath().toString());
                    c = collection.getChildCollection(FileUtils.fileName(file));
                    if (c == null) {
                        mgtService = (EXistCollectionManagementService) collection.getService("CollectionManagementService", "1.0");
                        c = mgtService.createCollection(XmldbURI.xmldbUriFor(FileUtils.fileName(file)));
                    }
                    if (c instanceof Observable && options.verbose) {
                        final ProgressObserver observer = new ProgressObserver();
                        ((Observable) c).addObserver(observer);
                    }
                    findGZipRecursive(c, file, next);
                } else {
                    final long start1 = System.currentTimeMillis();
                    final String compressedName = FileUtils.fileName(file);
                    String localName = compressedName;
                    final String[] cSuffix = {".gz", ".Z"};
                    boolean isCompressed = false;
                    for (final String suf : cSuffix) {
                        if (localName.endsWith(suf)) {
                            // Removing compressed prefix to validate
                            localName = compressedName.substring(0, localName.length() - suf.length());
                            isCompressed = true;
                            break;
                        }
                    }
                    mimeType = MimeTable.getInstance().getContentTypeFor(localName);
                    if (mimeType == null) {
                        messageln("File " + compressedName + " has an unknown suffix. Cannot determine file type.");
                        mimeType = MimeType.BINARY_TYPE;
                    }
                    message("storing document " + compressedName + " (" + i + " of " + files.size() + ") " + "...");
                    document = collection.createResource(compressedName, mimeType.getXMLDBType());
                    document.setContent(isCompressed ? new GZIPInputSource(file) : file);
                    ((EXistResource) document).setMimeType(mimeType.getName());
                    collection.storeResource(document);
                    ++filesCount;
                    messageln(" " + Files.size(file) + (isCompressed ? " compressed" : "") + " bytes in "
                            + (System.currentTimeMillis() - start1) + "ms.");
                }
            } catch (final URISyntaxException e) {
                errorln("uri syntax exception parsing " + file.toAbsolutePath().toString() + ": " + e.getMessage());
            }
        }
        return true;
    }

    /**
     * stores given Resource
     *
     * @param fileName simple file or directory
     * @throws XMLDBException in case of database error storing the resource
     * @throws IOException in case of a read error
     * @return true if the operation succeeded
     */
    protected synchronized boolean parseGZip(String fileName) throws XMLDBException, IOException {
        //TODO : why is this test for ? Fileshould make it, shouldn't it ? -pb
        fileName = fileName.replace('/', java.io.File.separatorChar).replace('\\',
                java.io.File.separatorChar);
        final Path file = Paths.get(fileName);
        Resource document;
        // String xml;
        if (current instanceof Observable && options.verbose) {
            final ProgressObserver observer = new ProgressObserver();
            ((Observable) current).addObserver(observer);
        }
        final List files;
        if (Files.isReadable(file)) {
            // TODO, same logic as for the graphic client
            if (Files.isDirectory(file)) {
                if (options.reindexRecurse) {
                    filesCount = 0;
                    final long start = System.currentTimeMillis();
                    final boolean result = findGZipRecursive(current, file, path);
                    messageln("storing " + filesCount + " compressed files took "
                            + ((System.currentTimeMillis() - start) / 1000)
                            + "sec.");
                    return result;
                }
                files = FileUtils.list(file);
            } else {
                files = new ArrayList<>();
                files.add(file);
            }
        } else {
            final DirectoryScanner directoryScanner = new DirectoryScanner();
            directoryScanner.setIncludes(new String[] { fileName });
            //TODO(AR) do we need to call scanner.setBasedir()?
            directoryScanner.setCaseSensitive(true);
            directoryScanner.scan();

            final String[] includedFiles = directoryScanner.getIncludedFiles();
            files = new ArrayList<>(includedFiles.length);
            for (final String includedFile : includedFiles) {
//                files.add(baseDir.resolve(includedFile));
                files.add(Paths.get(includedFile));
            }
        }

        final long start0 = System.currentTimeMillis();
        long bytes = 0;
        MimeType mimeType;
        int i = 0;
        for (final Path p : files) {
            i++;
            if (Files.isDirectory(p)) {
                continue;
            }
            final long start = System.currentTimeMillis();
            final String compressedName = FileUtils.fileName(p);
            String localName = compressedName;
            final String[] cSuffix = {".gz", ".Z"};
            boolean isCompressed = false;
            for (final String suf : cSuffix) {
                if (localName.endsWith(suf)) {
                    // Removing compressed prefix to validate
                    localName = compressedName.substring(0, localName.length() - suf.length());
                    isCompressed = true;
                    break;
                }
            }
            mimeType = MimeTable.getInstance().getContentTypeFor(localName);
            if (mimeType == null) {
                mimeType = MimeType.BINARY_TYPE;
            }
            document = current.createResource(compressedName, mimeType.getXMLDBType());
            message("storing document " + compressedName + " (" + i
                    + " of " + Files.size(p) + ") ...");
            document.setContent(isCompressed ? new GZIPInputSource(p) : p);
            ((EXistResource) document).setMimeType(mimeType.getName());
            current.storeResource(document);
            messageln("done.");
            messageln("parsing " + Files.size(p) + (isCompressed ? " compressed" : "") + " bytes took "
                    + (System.currentTimeMillis() - start) + "ms." + EOL);
            bytes += Files.size(p);
        }
        messageln("parsed " + bytes + " compressed bytes in "
                + (System.currentTimeMillis() - start0) + "ms.");
        return true;
    }

    /**
     * stores given Resource.
     *
     * @param zipPath Path to a zip file
     *
     * @throws XMLDBException in case of error writing to the database
     * @return true if operation succeeded
     */
    protected synchronized boolean parseZip(final Path zipPath) throws XMLDBException {
        try {
            final ZipFile zfile = new ZipFile(zipPath.toFile());
            if (current instanceof Observable && options.verbose) {
                final ProgressObserver observer = new ProgressObserver();
                ((Observable) current).addObserver(observer);
            }

            final long start0 = System.currentTimeMillis();
            long bytes = 0;
            final Enumeration e = zfile.entries();
            int number = 0;

            Collection base = current;
            String baseStr = "";
            while (e.hasMoreElements()) {
                number++;
                final ZipEntry ze = e.nextElement();
                final String zeName = ze.getName().replace('\\', '/');

                if (!Paths.get("/db").resolve(zeName).normalize().startsWith(Paths.get("/db"))) {
                    throw new IOException("Detected archive exit attack! zipFile=" + zipPath.toAbsolutePath().toString() + ", entry=" + ze.getName());
                }

                final String[] pathSteps = zeName.split("/");
                final StringBuilder currStr = new StringBuilder(pathSteps[0]);
                for (int i = 1; i < pathSteps.length - 1; i++) {
                    currStr
                            .append("/")
                            .append(pathSteps[i]);
                }
                if (!baseStr.equals(currStr)) {
                    base = current;
                    for (int i = 0; i < pathSteps.length - 1; i++) {
                        Collection c = base.getChildCollection(pathSteps[i]);
                        if (c == null) {
                            final EXistCollectionManagementService mgtService = (EXistCollectionManagementService) base.getService("CollectionManagementService", "1.0");
                            c = mgtService.createCollection(XmldbURI.xmldbUriFor(pathSteps[i]));
                        }
                        base = c;
                    }
                    if (base instanceof Observable && options.verbose) {
                        final ProgressObserver observer = new ProgressObserver();
                        ((Observable) base).addObserver(observer);
                    }
                    baseStr = currStr.toString();
                    messageln("entering directory " + baseStr);
                }
                if (!ze.isDirectory()) {
                    final String localName = pathSteps[pathSteps.length - 1];
                    final long start = System.currentTimeMillis();
                    MimeType mimeType = MimeTable.getInstance().getContentTypeFor(localName);
                    if (mimeType == null) {
                        mimeType = MimeType.BINARY_TYPE;
                    }
                    final Resource document = base.createResource(localName, mimeType.getXMLDBType());
                    message("storing Zip-entry document " + localName + " (" + (number)
                            + " of " + zfile.size() + ") ...");
                    document.setContent(new ZipEntryInputSource(zfile, ze));
                    ((EXistResource) document).setMimeType(mimeType.getName());
                    base.storeResource(document);
                    messageln("done.");
                    messageln("parsing " + ze.getSize() + " bytes took "
                            + (System.currentTimeMillis() - start) + "ms." + EOL);
                    bytes += ze.getSize();
                }
            }
            messageln("parsed " + bytes + " bytes in "
                    + (System.currentTimeMillis() - start0) + "ms.");
        } catch (final URISyntaxException e) {
            errorln("uri syntax exception parsing a ZIP entry from " +  zipPath.toString()+ ": " + e.getMessage());
        } catch (final IOException e) {
            errorln("could not parse ZIP file " + zipPath.toAbsolutePath() + ": " + e.getMessage());
        }
        return true;
    }

    /**
     * Method called by the store Dialog
     *
     * @param files  : selected
     * @param upload : GUI object
     * @throws XMLDBException in case of an error uploading the resources
     * @return true if the operation succeeded
     */
    protected synchronized boolean parse(final List files, final UploadDialog upload) throws XMLDBException {
        final Collection uploadRootCollection = current;
        if (!upload.isVisible()) {
            upload.setVisible(true);
        }

        if (uploadRootCollection instanceof Observable) {
            ((Observable) uploadRootCollection).addObserver(upload.getObserver());
        }
        upload.setTotalSize(FileUtils.sizeQuietly(files));
        for (final Path file : files) {
            if (upload.isCancelled()) {
                break;
            }
            // should replace the lines above
            store(uploadRootCollection, file, upload);
        }
        if (uploadRootCollection instanceof Observable) {
            ((Observable) uploadRootCollection).deleteObservers();
        }
        upload.uploadCompleted();
        return true;
    }

    /**
     * Pass to this method a java file object
     * (may be a file or a directory), GUI object
     * will create relative collections or resources
     * recursively
     */

    private void store(final Collection collection, final Path file, final UploadDialog upload) {

        // cancel, stop crawl
        if (upload.isCancelled()) {
            return;
        }

        // can't read there, inform client
        if (!Files.isReadable(file)) {
            upload.showMessage(file.toAbsolutePath() + " impossible to read ");
            return;
        }

        final XmldbURI filenameUri;
        try {
            filenameUri = XmldbURI.xmldbUriFor(FileUtils.fileName(file));
        } catch (final URISyntaxException e1) {
            upload.showMessage(file.toAbsolutePath() + " could not be encoded as a URI");
            return;
        }

        // Directory, create collection, and crawl it
        if (Files.isDirectory(file)) {
            Collection c = null;
            try {
                c = collection.getChildCollection(filenameUri.toString());
                if (c == null) {
                    final EXistCollectionManagementService mgtService = (EXistCollectionManagementService) collection.getService("CollectionManagementService", "1.0");
                    c = mgtService.createCollection(filenameUri);
                }
            } catch (final XMLDBException e) {
                upload.showMessage("Impossible to create a collection " + file.toAbsolutePath() + ": " + e.getMessage());
                e.printStackTrace();
            }

            // change displayed collection if it's OK
            upload.setCurrentDir(file.toAbsolutePath().toString());
            if (c instanceof Observable) {
                ((Observable) c).addObserver(upload.getObserver());
            }
            // maybe a depth or recurs flag could be added here
            final Collection childCollection = c;
            try(final Stream children = Files.list(file)) {
                children.forEach(child -> store(childCollection, child, upload));
            } catch (final IOException e) {
                upload.showMessage("Impossible to upload " + file.toAbsolutePath() + ": " + e.getMessage());
                e.printStackTrace();
            }

            return;
        }

        // File, create and store resource
        if (!Files.isDirectory(file)) {
            upload.reset();
            upload.setCurrent(FileUtils.fileName(file));
            final long fileSize = FileUtils.sizeQuietly(file);
            upload.setCurrentSize(fileSize);

            MimeType mimeType = MimeTable.getInstance().getContentTypeFor(FileUtils.fileName(file));
            // unknown mime type, here prefered is to do nothing
            if (mimeType == null) {
                upload.showMessage(file.toAbsolutePath() +
                        " - unknown suffix. No matching mime-type found in : " +
                        MimeTable.getInstance().getSrc());

                // if some one prefers to store it as binary by default, but dangerous
                mimeType = MimeType.BINARY_TYPE;
            }

            try {
                final Resource res = collection.createResource(filenameUri.toString(), mimeType.getXMLDBType());
                ((EXistResource) res).setMimeType(mimeType.getName());
                res.setContent(file);
                collection.storeResource(res);
                ++filesCount;
                this.totalLength += fileSize;
                upload.setStoredSize(this.totalLength);
            } catch (final XMLDBException e) {
                upload.showMessage("Impossible to store a resource "
                        + file.toAbsolutePath() + ": " + e.getMessage());
            }
        }
    }


    private void mkcol(final XmldbURI collPath) throws XMLDBException {
        messageln("creating '" + collPath + "'");
        final XmldbURI[] segments = collPath.getPathSegments();
        XmldbURI p = XmldbURI.ROOT_COLLECTION_URI;
        for (int i = 1; i < segments.length; i++) {
            p = p.append(segments[i]);
            final Collection c = DatabaseManager.getCollection(properties.getProperty(URI) + p, properties.getProperty(USER), properties.getProperty(PASSWORD));
            if (c == null) {
                final EXistCollectionManagementService mgtService = (EXistCollectionManagementService) current.getService("CollectionManagementService", "1.0");
                current = mgtService.createCollection(segments[i]);
            } else {
                current = c;
            }
        }
        path = p;
    }

    protected Collection getCollection(final String path) throws XMLDBException {
        return DatabaseManager.getCollection(properties.getProperty(URI) + path, properties.getProperty(USER), properties.getProperty(PASSWORD));
    }
    
    /*private char[] readPassword(InputStream in) throws IOException {
        
        char[] lineBuffer;
        char[] buf;
        // int i;
        
        buf = lineBuffer = new char[128];
        
        int room = buf.length;
        int offset = 0;
        int c;
        
        loop : while (true)
            switch (c = in.read()) {
                case -1 :
                case '\n' :
                    break loop;
                case '\r' :
                    int c2 = in.read();
                    if ((c2 != '\n') && (c2 != -1)) {
                        if (!(in instanceof PushbackInputStream))
                            in = new PushbackInputStream(in);
                        
                        ((PushbackInputStream) in).unread(c2);
                    } else
                        break loop;
                default :
                    if (--room < 0) {
                        buf = new char[offset + 128];
                        room = buf.length - offset - 1;
                        System.arraycopy(lineBuffer, 0, buf, 0, offset);
                        Arrays.fill(lineBuffer, ' ');
                        lineBuffer = buf;
                    }
                    buf[offset++] = (char) c;
                    break;
            }
            
            if (offset == 0)
                return null;
            
            char[] ret = new char[offset];
            System.arraycopy(buf, 0, ret, 0, offset);
            Arrays.fill(buf, ' ');
            
            return ret;
    }*/

    private Properties loadClientProperties() {
        try {
            final Properties properties = ConfigurationHelper.loadProperties("client.properties", getClass());
            if (properties != null) {
                return properties;
            }

            System.err.println("WARN - Unable to find client.properties");

        } catch (final IOException e) {
            System.err.println("WARN - Unable to load client.properties: " + e.getMessage());
        }

        // return new empty properties
        return new Properties();
    }

    /**
     * Set any relevant properties from command line arguments
     *
     * @param options  CommandLineOptions
     * @param props Client configuration
     */
    protected void setPropertiesFromCommandLine(final CommandlineOptions options, final Properties props) {
        options.options.forEach(properties::setProperty);

        options.username.ifPresent(username -> props.setProperty(USER, username));
        options.password.ifPresent(password -> props.setProperty(PASSWORD, password));
        boolean needPassword = options.username.isPresent() && !options.password.isPresent();
        if(options.useSSL) {
            props.setProperty(SSL_ENABLE, "TRUE");
        }
        if(options.embedded) {
            props.setProperty(LOCAL_MODE, "TRUE");
            props.setProperty(URI, XmldbURI.EMBEDDED_SERVER_URI.toString());
        }
        options.embeddedConfig.ifPresent(config -> properties.setProperty(CONFIGURATION, config.toAbsolutePath().toString()));
        if(options.noEmbeddedMode) {
            props.setProperty(NO_EMBED_MODE, "TRUE");
        }
    }

    /**
     * Process the command line options
     *
     * @return true if all are successful, otherwise false
     * @throws java.lang.Exception
     */
    private boolean processCommandLineActions() throws Exception {
        final boolean foundCollection = options.setCol.isPresent();

        // process command-line actions
        if (options.reindex) {
            if (!foundCollection) {
                System.err.println("Please specify target collection with --collection");
                shutdown(false);
                return false;
            }
            try {
                reindex();
            } catch (final XMLDBException e) {
                System.err.println("XMLDBException while reindexing collection: " + getExceptionMessage(e));
                e.printStackTrace();
                return false;
            }
        }

        if (options.rmCol.isPresent()) {
            try {
                rmcol(options.rmCol.get());
            } catch (final XMLDBException e) {
                System.err.println("XMLDBException while removing collection: " + getExceptionMessage(e));
                e.printStackTrace();
                return false;
            }
        }

        if (options.mkCol.isPresent()) {
            try {
                mkcol(options.mkCol.get());
            } catch (final XMLDBException e) {
                System.err.println("XMLDBException during mkcol: " + getExceptionMessage(e));
                e.printStackTrace();
                return false;
            }
        }

        if (options.getDoc.isPresent()) {
            try {
                final Resource res = retrieve(options.getDoc.get());
                if (res != null) {
                    // String data;
                    if ("XMLResource".equals(res.getResourceType())) {
                        if (options.outputFile.isPresent()) {
                            writeOutputFile(options.outputFile.get(), res.getContent());
                        } else {
                            System.out.println(res.getContent().toString());
                        }
                    } else {
                        if (options.outputFile.isPresent()) {
                            ((ExtendedResource) res).getContentIntoAFile(options.outputFile.get());
                            ((EXistResource) res).freeResources();
                        } else {
                            ((ExtendedResource) res).getContentIntoAStream(System.out);
                            System.out.println();
                        }
                    }
                }
            } catch (final XMLDBException e) {
                System.err.println("XMLDBException while trying to retrieve document: " + getExceptionMessage(e));
                e.printStackTrace();
                return false;
            }
        } else if (options.rmDoc.isPresent()) {
            if (!foundCollection) {
                System.err.println("Please specify target collection with --collection");
            } else {
                try {
                    remove(options.rmDoc.get());
                } catch (final XMLDBException e) {
                    System.err.println("XMLDBException during parse: " + getExceptionMessage(e));
                    e.printStackTrace();
                    return false;
                }
            }
        } else if (!options.parseDocs.isEmpty()) {
            if (!foundCollection) {
                System.err.println("Please specify target collection with --collection");
            } else {
                for (final Path path : options.parseDocs) {
                    try {
                        parse(path);
                    } catch (final XMLDBException e) {
                        System.err.println("XMLDBException during parse: " + getExceptionMessage(e));
                        e.printStackTrace();
                        return false;
                    }
                }
            }
        } else if (options.xpath.isPresent() || !options.queryFiles.isEmpty()) {

            String xpath = null;

            if (!options.queryFiles.isEmpty()) {
                try (final BufferedReader reader = Files.newBufferedReader(options.queryFiles.get(0))) {
                    final StringBuilder buf = new StringBuilder();
                    String line;
                    while ((line = reader.readLine()) != null) {
                        buf.append(line);
                        buf.append(EOL);
                    }
                    xpath = buf.toString();
                }
            }

            // if no argument has been found, read query from stdin
            if (options.xpath.isPresent()) {

                final String xpathStr = options.xpath.get();
                if(!xpathStr.equals(CommandlineOptions.XPATH_STDIN)) {
                    xpath = xpathStr;
                } else {
                    // read from stdin
                    try (final BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in))) {
                        final StringBuilder buf = new StringBuilder();
                        String line;
                        while ((line = stdin.readLine()) != null) {
                            buf.append(line);
                            buf.append(EOL);
                        }
                        xpath = buf.toString();
                    } catch (final IOException e) {
                        System.err.println("failed to read query from stdin");
                        xpath = null;
                        return false;
                    }
                }
            }

            if (xpath != null) {
                try {
                    final ResourceSet result = find(xpath);

                    final int maxResults = options.howManyResults.filter(n -> n > 0).orElse((int)result.getSize());
                    if (options.outputFile.isPresent()) {
                        try(final OutputStream fos = new BufferedOutputStream(Files.newOutputStream(options.outputFile.get()));
                            final BufferedOutputStream bos = new BufferedOutputStream(fos);
                            final PrintStream ps = new PrintStream(bos)
                        ) {

                            for (int i = 0; i < maxResults && i < result.getSize(); i++) {
                                final Resource res = result.getResource(i);
                                if (res instanceof ExtendedResource) {
                                    ((ExtendedResource) res).getContentIntoAStream(ps);
                                } else {
                                    ps.print(res.getContent().toString());
                                }
                            }
                        }
                    } else {
                        for (int i = 0; i < maxResults && i < result.getSize(); i++) {
                            final Resource res = result.getResource(i);
                            if (res instanceof ExtendedResource) {
                                ((ExtendedResource) res).getContentIntoAStream(System.out);
                            } else {
                                System.out.println(res.getContent());
                            }
                        }
                    }
                } catch (final XMLDBException e) {
                    System.err.println("XMLDBException during query: " + getExceptionMessage(e));
                    e.printStackTrace();
                    return false;
                }
            }

        } else if (options.xupdateFile.isPresent()) {
            try {
                xupdate(options.setDoc, options.xupdateFile.get());
            } catch (final XMLDBException e) {
                System.err.println("XMLDBException during xupdate: " + getExceptionMessage(e));
                return false;
            } catch (final IOException e) {
                System.err.println("IOException during xupdate: " + getExceptionMessage(e));
                return false;
            }
        }

        return true;
    }

    /**
     * Ask user for login data using gui.
     *
     * @param props Client properties
     * @return FALSE when pressed cancel, TRUE is sucessfull.
     */
    private boolean getGuiLoginData(final Properties props) {

        final Properties loginData = ClientFrame.getLoginData(props);
        if (loginData == null || loginData.isEmpty()) {
            // User pressed 
            return false;
        }
        props.putAll(loginData);

        return true;
    }

    /**
     * Reusable method for connecting to database. Exits process on failure.
     */
    private void connectToDatabase() {
        try {
            connect();
        } catch (final Exception cnf) {
            if (options.startGUI && frame != null) {
                frame.setStatus("Connection to database failed; message: " + cnf.getMessage());
            } else {
                System.err.println("Connection to database failed; message: " + cnf.getMessage());
            }
            cnf.printStackTrace();
            System.exit(SystemExitCodes.CATCH_ALL_GENERAL_ERROR_EXIT_CODE);
        }
    }

    /**
     * Main processing method for the InteractiveClient object
     *
     * @param args command line arguments
     * @return true on success, false on failure
     * @throws Exception if an error occurs
     */
    public boolean run(final String args[]) throws Exception {

        // parse command-line options
        this.options = CommandlineOptions.parse(args);
        this.path = options.setCol.orElse(XmldbURI.ROOT_COLLECTION_URI);

        // initialize with default properties, before add client properties
        properties = new Properties(defaultProps);

        // get eXist home
        final Optional home = ConfigurationHelper.getExistHome();

        // get default configuration filename from the driver class and set it in properties
        Optional configFile = ConfigurationHelper.getFromSystemProperty();
        if (!configFile.isPresent()) {
            final Class cl = Class.forName(properties.getProperty(DRIVER));
            final Field CONF_XML = cl.getDeclaredField("CONF_XML");
            if (CONF_XML != null && home.isPresent()) {
                configFile = Optional.ofNullable(ConfigurationHelper.lookup((String) CONF_XML.get("")));
            }
        }
        configFile.ifPresent(value -> properties.setProperty(CONFIGURATION, value.toAbsolutePath().toString()));

        properties.putAll(loadClientProperties());

        setPropertiesFromCommandLine(options, properties);

        // print copyright notice - after parsing command line options, or it can't be silenced!
        if (!options.quiet) {
            printNotice();
        }

        // Fix "uri" property: Excalibur CLI can't parse dashes, so we need to URL encode them:
        properties.setProperty(URI, URLDecoder.decode(properties.getProperty(URI), "UTF-8"));

        boolean interactive = true;
        if((!options.parseDocs.isEmpty()) || options.rmDoc.isPresent() || options.getDoc.isPresent()
                || options.rmCol.isPresent() || options.xpath.isPresent() || (!options.queryFiles.isEmpty())
                || options.xupdateFile.isPresent() || options.reindex) {
            interactive = false;
        }

        // prompt for password if needed
        if (!hasLoginDetails(options)) {
            if (interactive && options.startGUI) {
                final boolean haveLoginData = getGuiLoginData(properties);
                if (!haveLoginData) {
                    return false;
                }

            } else if (options.username.isPresent() && !options.password.isPresent()) {
                try {
                    properties.setProperty(PASSWORD, console.readLine("password: ", '*'));
                } catch (final Exception e) {
                }
            }
        }


        historyFile = home.map(h -> h.resolve(".exist_history")).orElse(Paths.get(".exist_history"));
        queryHistoryFile = home.map(h -> h.resolve(".exist_query_history")).orElse(Paths.get(".exist_query_history"));

        if (Files.isReadable(queryHistoryFile)) {
            readQueryHistory();
        }

        if (interactive) {
            // in gui mode we use Readline for history management
            // initialize Readline library
            final Terminal terminal = TerminalBuilder.builder()
                    .build();

            final History history = new DefaultHistory();

            console = LineReaderBuilder.builder()
                    .terminal(terminal)
                    .variable(LineReader.HISTORY_FILE, historyFile)
                    .history(history)
                    .completer(new CollectionCompleter())
                    .build();
        }

        // connect to the db
        connectToDatabase();

        if (current == null) {
            if (options.startGUI && frame != null) {
                frame.setStatus("Could not retrieve collection " + path);
            } else {
                System.err.println("Could not retrieve collection " + path);
            }
            shutdown(false);
            return false;
        }

        final boolean processingOK = processCommandLineActions();
        if (!processingOK) {
            return false;
        }

        if (interactive) {
            if (options.startGUI) {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (final UnsupportedLookAndFeelException ulafe) {
                    System.err.println("Warning: Unable to set native look and feel: " + ulafe.getMessage());
                }

                frame = new ClientFrame(this, path, properties);
                frame.setLocation(100, 100);
                frame.setSize(500, 500);
                frame.setVisible(true);
            }

            // enter interactive mode
            if ((!options.startGUI) || (frame == null)) {

                // No gui
                try {
                    getResources();
                } catch (final XMLDBException e) {
                    System.err.println("XMLDBException while "
                            + "retrieving collection contents: "
                            + getExceptionMessage(e));
                    e.getCause().printStackTrace();
                    return false;
                }

            } else {

                // with gui ; re-login posibility
                boolean retry = true;

                while (retry) {

                    String errorMessage = "";
                    try {
                        getResources();
                    } catch (final XMLDBException e) {

                        errorMessage = getExceptionMessage(e);
                        ClientFrame.showErrorMessage(
                                "XMLDBException occurred while retrieving collection: "
                                        + errorMessage, e);
                    }

                    // Determine error text. For special reasons we can retry
                    // to connect.
                    if (errorMessage.matches("^.*Invalid password for user.*$") ||
                            errorMessage.matches("^.*User .* unknown.*") ||
                            errorMessage.matches("^.*Connection refused: connect.*")) {

                        final boolean haveLoginData = getGuiLoginData(properties);
                        if (!haveLoginData) {
                            // pressed cancel
                            return false;
                        }

                        // Need to shutdown ?? ask wolfgang
                        shutdown(false);

                        // connect to the db
                        connectToDatabase();

                    } else {

                        if (!errorMessage.isEmpty()) {
                            // No pattern match, but we have an error. stop here
                            frame.dispose();
                            return false;
                        } else {
                            // No error message, continue startup.
                            retry = false;
                        }
                    }
                }
            }

            messageln(EOL + "type help or ? for help.");

            if (options.openQueryGUI) {
                final QueryDialog qd = new QueryDialog(this, current, properties);
                qd.setLocation(100, 100);
                qd.setVisible(true);
            } else if (!options.startGUI) {
                readlineInputLoop();
            } else {
                frame.displayPrompt();
            }
        } else {
            shutdown(false);
        }
        return true;
    }

    private boolean hasLoginDetails(final CommandlineOptions options) {
        return options.username.isPresent()
                && options.password.isPresent()
                && (options.embedded || options.options.containsKey("uri"));
    }

    public static String getExceptionMessage(Throwable e) {
        Throwable cause;
        while ((cause = e.getCause()) != null) {
            e = cause;
        }
        return e.getMessage();
    }

    /**
     * Read Query History file.
     */
    protected void readQueryHistory() {
        try {
            final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            final DocumentBuilder builder = factory.newDocumentBuilder();
            final Document doc = builder.parse(queryHistoryFile.toFile());
            final NodeList nodes = doc.getElementsByTagName("query");
            for (int i = 0; i < nodes.getLength(); i++) {
                final Element query = (Element) nodes.item(i);
                final StringBuilder value = new StringBuilder();
                Node next = query.getFirstChild();
                while (next != null) {
                    value.append(next.getTextContent());
                    next = next.getNextSibling();
                }
                queryHistory.addLast(value.toString());
            }
        } catch (final Exception e) {
            if (options.startGUI) {
                ClientFrame.showErrorMessage(
                        "Error while reading query history: " + e.getMessage(),
                        e);
            } else {
                errorln("Error while reading query history: "
                        + e.getMessage());
            }
        }
    }

    protected void addToHistory(final String query) {
        queryHistory.add(query);
    }

    protected void writeQueryHistory() {
        try {
            console.getHistory().save();
        } catch (final IOException e) {
            System.err.println("Could not write history File to " + historyFile.toAbsolutePath().toString());
        }

        final SAXSerializer serializer = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
        try (final BufferedWriter writer = Files.newBufferedWriter(queryHistoryFile, StandardCharsets.UTF_8)) {
            serializer.setOutput(writer, null);
            int p = 0;
            if (queryHistory.size() > 20) {
                p = queryHistory.size() - 20;
            }
            final AttributesImpl attrs = new AttributesImpl();
            serializer.startDocument();
            serializer.startElement(XMLConstants.NULL_NS_URI, "history", "history", attrs);
            for (final ListIterator i = queryHistory.listIterator(p); i.hasNext(); ) {
                serializer.startElement(XMLConstants.NULL_NS_URI, "query", "query", attrs);
                final String next = i.next();
                serializer.characters(next.toCharArray(), 0, next.length());
                serializer.endElement(XMLConstants.NULL_NS_URI, "query", "query");
            }
            serializer.endElement(XMLConstants.NULL_NS_URI, "history", "history");
            serializer.endDocument();
        } catch (final IOException e) {
            System.err.println("IO error while writing query history.");
        } catch (final SAXException e) {
            System.err.println("SAX exception while writing query history.");
        } finally {
            SerializerPool.getInstance().returnObject(serializer);
        }

    }

    public void readlineInputLoop() {
        String line;
        boolean cont = true;
        while (cont) {
            try {
                if ("true".equals(properties.getProperty(COLORS))) {
                    line = console.readLine(ANSI_CYAN + "exist:" + path + "> "
                            + ANSI_WHITE);
                } else {
                    line = console.readLine("exist:" + path + "> ");
                }
                if (line != null) {
                    cont = process(line);
                }

            } catch (final EndOfFileException e) {
                break;
            } catch (final Exception e) {
                System.err.println(e);
            }
        }

        try {
            console.getHistory().save();
        } catch (final IOException e) {
            System.err.println("Could not write history File to " + historyFile.toAbsolutePath().toString());
        }
        shutdown(false);
        messageln("quit.");
    }

    protected final void shutdown(final boolean force) {
        lazyTraceWriter.ifPresent(writer -> {
            try {
                writer.write("");
                writer.close();
            } catch (final IOException e1) {
            }
        });

        try {
            final DatabaseInstanceManager mgr = (DatabaseInstanceManager) current.getService("DatabaseInstanceManager", "1.0");
            if (mgr == null) {
                System.err.println("service is not available");
            } else if (mgr.isLocalInstance() || force) {
                System.out.println("shutting down database...");
                mgr.shutdown();
            }
        } catch (final XMLDBException e) {
            System.err.println("database shutdown failed: " + e.getMessage());
            e.printStackTrace();
        } finally {
            try {
                current.close();
                current = null;

                DatabaseManager.deregisterDatabase(database);
                database = null;
            } catch (final XMLDBException e) {
                System.err.println("unable to close collection: " + e.getMessage());
                e.printStackTrace();
            }
        }
    }

    public void printNotice() {
        messageln(getNotice());
    }

    public String getNotice() {
        final StringBuilder builder = new StringBuilder();
        builder.append(SystemProperties.getInstance().getSystemProperty("product-name", "eXist-db"));
        builder.append(" version ");
        builder.append(SystemProperties.getInstance().getSystemProperty("product-version", "unknown"));
        if (!"".equals(SystemProperties.getInstance().getSystemProperty("git-commit", ""))) {
            builder.append(" (");
            builder.append(SystemProperties.getInstance().getSystemProperty("git-commit", "(unknown Git commit ID)"));
            builder.append(")");
        }
        builder.append(", Copyright (C) 2001-");
        builder.append(Calendar.getInstance().get(Calendar.YEAR));
        builder.append(" The eXist-db Project");
        builder.append(EOL);
        builder.append("eXist-db comes with ABSOLUTELY NO WARRANTY.");
        builder.append(EOL);
        builder.append("This is free software, and you are welcome to redistribute it");
        builder.append(EOL);
        builder.append("under certain conditions; for details read the license file.");
        builder.append(EOL);
        return builder.toString();
    }

    private void message(final String msg) {
        if (!options.quiet) {
            if (options.startGUI && frame != null) {
                frame.display(msg);
            } else {
                System.out.print(msg);
            }
        }
    }

    private void messageln(final String msg) {
        if (!options.quiet) {
            if (options.startGUI && frame != null) {
                frame.display(msg + EOL);
            } else {
                System.out.println(msg);
            }
        }
    }

    private void errorln(final String msg) {
        if (options.startGUI && frame != null) {
            frame.display(msg + EOL);
        } else {
            System.err.println(msg);
        }
    }

    private Collection resolveCollection(final XmldbURI path) throws XMLDBException {
        return DatabaseManager.getCollection(
                properties.getProperty(URI) + path,
                properties.getProperty(USER),
                properties.getProperty(PASSWORD));
    }

    private Resource resolveResource(final XmldbURI path) throws XMLDBException {
        try {
            final XmldbURI collectionPath =
                path.numSegments() == 1 ?
                    XmldbURI.xmldbUriFor(current.getName()) : path.removeLastSegment();

            final XmldbURI resourceName = path.lastSegment();

            final Collection collection = resolveCollection(collectionPath);

            if (collection == null) {
                messageln("Collection " + collectionPath + " not found.");
                return null;
            }

            messageln("Locating resource " + resourceName + " in collection " + collection.getName());

            return collection.getResource(resourceName.toString());
        } catch (final URISyntaxException e) {
            errorln("could not parse collection name into a valid URI: " + e.getMessage());
        }
        return null;
    }

    private class CollectionCompleter implements Completer {

        @Override
        public void complete(final LineReader lineReader, final ParsedLine parsedLine, final List candidates) {
            final String buffer = parsedLine.line();
            int p = buffer.lastIndexOf(' ');
            final String toComplete;
            if (p > -1 && ++p < buffer.length()) {
                toComplete = buffer.substring(p);
            } else {
                toComplete = buffer;
            }
//            System.out.println("\nbuffer: '" + toComplete + "'; cursor: " + cursor);
            final Set set = completitions.tailSet(toComplete);
            if (set != null && set.size() > 0) {
                for (final String next : completitions.tailSet(toComplete)) {
                    if (next.startsWith(toComplete)) {
                        candidates.add(new Candidate(next, next, null, null, null, null, true));
                    }
                }
            }
        }
    }

    public static class ProgressObserver implements Observer {

        final ProgressBar elementsProgress = new ProgressBar("storing elements");
        Observable lastObservable = null;
        final ProgressBar parseProgress = new ProgressBar("storing nodes   ");

        @Override
        public void update(final Observable o, final Object obj) {
            final ProgressIndicator ind = (ProgressIndicator) obj;
            if (lastObservable == null || o != lastObservable) {
                System.out.println();
            }

            if (o instanceof ElementIndex) {
                elementsProgress.set(ind.getValue(), ind.getMax());
            } else {
                parseProgress.set(ind.getValue(), ind.getMax());
            }

            lastObservable = o;
        }
    }

    private void writeOutputFile(final Path file, final Object data) throws Exception {
        try (final OutputStream os = new BufferedOutputStream(Files.newOutputStream(file))) {
            if (data instanceof byte[]) {
                os.write((byte[]) data);
            } else {
                try(final Writer writer = new OutputStreamWriter(os, Charset.forName(properties.getProperty(ENCODING)))) {
                    writer.write(data.toString());
                }
            }
        }
    }

    private static String formatString(String s1, final String s2, final int width) {
        final StringBuilder buf = new StringBuilder(width);
        if (s1.length() > width) {
            s1 = s1.substring(0, width - 1);
        }
        buf.append(s1);
        final int fill = width - (s1.length() + s2.length());
        for (int i = 0; i < fill; i++) {
            buf.append(' ');
        }
        buf.append(s2);
        return buf.toString();
    }

    private static String formatString(final String[] args, final int[] sizes) {
        final StringBuilder buf = new StringBuilder();
        for (int i = 0; i < args.length; i++) {
            if (sizes[i] < 0) {
                buf.append(args[i]);
            } else {
                for (int j = 0; j < sizes[i] && j < args[i].length(); j++) {
                    buf.append(args[i].charAt(j));
                }
            }
            for (int j = 0; j < sizes[i] - args[i].length(); j++) {
                buf.append(' ');
            }
        }
        return buf.toString();
    }

    public static Properties getSystemProperties() {

        final Properties sysProperties = new Properties();
        try {
            sysProperties.load(InteractiveClient.class.getClassLoader().getResourceAsStream("org/exist/system.properties"));
        } catch (final IOException e) {
            System.err.println("Unable to load system.properties from class loader");
        }

        return sysProperties;
    }

    public static ImageIcon getExistIcon(final Class clazz) {
        return new javax.swing.ImageIcon(clazz.getResource("/org/exist/client/icons/x.png"));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy