grakn.core.console.ConsoleSession.java Source code

Java tutorial

Introduction

Here is the source code for grakn.core.console.ConsoleSession.java

Source

/*
 * GRAKN.AI - THE KNOWLEDGE GRAPH
 * Copyright (C) 2018 Grakn Labs Ltd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package grakn.core.console;

import grakn.client.GraknClient;
import grakn.core.common.exception.GraknException;
import grakn.core.console.exception.GraknConsoleException;
import grakn.core.concept.printer.Printer;
import graql.lang.Graql;
import graql.lang.query.GraqlQuery;
import jline.console.ConsoleReader;
import jline.console.history.FileHistory;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;

import static org.apache.commons.lang.StringEscapeUtils.unescapeJava;

/**
 * A Grakn Console Session that allows a user to interact with the Grakn Server
 */
public class ConsoleSession implements AutoCloseable {

    private static final String COPYRIGHT = "\n" + "Welcome to Grakn Console. You are now in Grakn Wonderland!\n"
            + "Copyright (C) 2019 Grakn Labs Limited\n\n";

    private static final String EDITOR = "editor";
    private static final String COMMIT = "commit";
    private static final String ROLLBACK = "rollback";
    private static final String LOAD = "load";
    private static final String CLEAR = "clear";
    private static final String EXIT = "exit";
    private static final String CLEAN = "clean";

    private static final String ANSI_PURPLE = "\u001B[35m";
    private static final String ANSI_RESET = "\u001B[0m";

    private static final String HISTORY_FILE = System.getProperty("user.home") + "/.grakn-console-history";
    private static final String UNIX_EDITOR_DEFAULT = "vim";
    private static final String WINDOWS_EDITOR_DEFAULT = "notepad";

    private static final String EDITOR_FILE = "/grakn-console-editor.gql";

    private final boolean infer;
    private final String keyspace;
    private final PrintStream printErr;
    private final ConsoleReader consoleReader;
    private final Printer<?> printer = Printer.stringPrinter(true);

    private final FileHistory historyFile;

    private final GraknClient client;
    private final GraknClient.Session session;
    private GraknClient.Transaction tx;
    private final AtomicBoolean terminated = new AtomicBoolean(false);

    ConsoleSession(String serverAddress, String keyspace, boolean infer, PrintStream printOut, PrintStream printErr)
            throws IOException {
        this.keyspace = keyspace;
        this.infer = infer;
        this.client = new GraknClient(serverAddress);
        this.session = client.session(keyspace);
        this.consoleReader = new ConsoleReader(System.in, printOut);
        this.consoleReader.setPrompt(ANSI_PURPLE + session.keyspace().name() + ANSI_RESET + "> ");
        this.printErr = printErr;

        File file = new File(HISTORY_FILE);
        file.createNewFile();
        this.historyFile = new FileHistory(file);
        this.consoleReader.setHistory(this.historyFile);
    }

    void load(Path filePath) throws IOException {
        consoleReader.println("Loading: " + filePath.toString());
        consoleReader.println("...");
        consoleReader.flush();

        tx = session.transaction().write();

        try {
            String queries = readFile(filePath);
            executeQuery(queries, false);
            commit();
            consoleReader.println("Successful commit: " + filePath.getFileName().toString());
        } catch (GraknException e) {
            String error = "Failed to load file: " + filePath.getFileName().toString();
            throw new GraknConsoleException(error, e);
        } finally {
            consoleReader.flush();
        }
    }

    void run() throws IOException, InterruptedException {
        consoleReader.setExpandEvents(false); // Disable JLine feature when seeing a '!'
        consoleReader.print(COPYRIGHT);

        tx = session.transaction().write();
        String input;

        while ((input = consoleReader.readLine()) != null && !terminated.get()) {
            if (input.equals(EDITOR)) {
                executeQuery(openTextEditor());

            } else if (input.startsWith(LOAD + ' ')) {
                try {
                    input = readFile(Paths.get(unescapeJava(input.substring(LOAD.length() + 1))));
                    executeQuery(input);
                } catch (NoSuchFileException e) {
                    System.err.println("File not found: " + e.getMessage());
                }
            } else if (input.equals(COMMIT)) {
                commit();

            } else if (input.equals(ROLLBACK)) {
                rollback();

            } else if (input.equals(CLEAN)) {
                clean();
                consoleReader.flush();
                return;

            } else if (input.equals(CLEAR)) {
                consoleReader.clearScreen();

            } else if (input.equals(EXIT)) {
                consoleReader.flush();
                return;

            } else if (!input.isEmpty()) {
                executeQuery(input);

            } // We ignore empty commands
        }
    }

    private static String readFile(Path filePath) throws IOException {
        List<String> lines = Files.readAllLines(filePath, StandardCharsets.UTF_8);
        return String.join("\n", lines);
    }

    private void executeQuery(String queryString) throws IOException {
        executeQuery(queryString, true);
    }

    private void executeQuery(String queryString, boolean catchRuntimeException) throws IOException {
        // We'll use streams so we can print the answer out much faster and smoother
        try {
            // Parse the string to get a stream of Graql Queries
            Stream<GraqlQuery> queries = Graql.parseList(queryString);

            // Get the stream of answers for each query (query.stream())
            // Get the  stream of printed answers (printer.toStream(..))
            // Combine the stream of printed answers into one stream (queries.flatMap(..))
            Stream<String> answers = queries.flatMap(query -> printer.toStream(tx.stream(query, infer)));

            // For each printed answer, print them on one line
            answers.forEach(answer -> {
                try {
                    consoleReader.println(answer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        } catch (RuntimeException e) {
            // Flush out all answers from previous iterators in the stream
            consoleReader.flush();

            if (catchRuntimeException) {
                if (!e.getMessage().isEmpty()) {
                    printErr.println("Error: " + e.getMessage());
                } else {
                    printErr.println("Error: " + e.getClass().getName());
                }
                printErr.println("All uncommitted data is cleared");
                //If console session was terminated by shutdown hook thread, do not try to reopen transaction
                if (terminated.get())
                    return;
                reopenTransaction();
            } else {
                throw e;
            }
        }

        consoleReader.flush(); // Flush the ConsoleReader before the next command

        // It is important that we DO NOT close the transaction at the end of a query
        // The user may want to do consecutive operations onto the database
        // The transaction will only close once the user decides to COMMIT or ROLLBACK
    }

    private void commit() {
        try {
            tx.commit();
        } catch (RuntimeException e) {
            printErr.println(e.getMessage());
            printErr.println("All uncommitted data is cleared");
        } finally {
            reopenTransaction();
        }
    }

    private void rollback() {
        try {
            tx.close();
        } catch (RuntimeException e) {
            printErr.println(e.getMessage());
        } finally {
            reopenTransaction();
        }
    }

    private void clean() throws IOException {
        // Get user confirmation to clean graph
        consoleReader.println("Are you sure? CLEAN command will delete the current keyspace and its content.");
        consoleReader.println("Type 'confirm' to continue: ");

        String line = consoleReader.readLine();

        if (line != null && line.equals("confirm")) {
            consoleReader.println("Cleaning keyspace: " + keyspace);
            consoleReader.println("...");
            consoleReader.flush();
            client.keyspaces().delete(keyspace);
            consoleReader.println("Keyspace deleted: " + keyspace);
        } else {
            consoleReader.println("Clean command cancelled");
        }
    }

    private void reopenTransaction() {
        if (!tx.isClosed())
            tx.close();
        tx = session.transaction().write();
    }

    @Override
    public final void close() {
        terminated.set(true);
        tx.close();
        session.close();
        client.close();
        try {
            historyFile.flush();
        } catch (IOException e) {
            // Print stacktrace to any available stream
            // nothing more to do here
            e.printStackTrace();
        }
    }

    /**
     * Open the user's preferred editor to write a query
     *
     * @return the string written in the editor
     */
    private String openTextEditor() throws IOException, InterruptedException {
        File tempFile = new File(System.getProperty("java.io.tmpdir") + EDITOR_FILE);
        tempFile.createNewFile();

        ProcessBuilder builder;

        if (isWindows()) {
            String editor = Optional.ofNullable(System.getenv().get("EDITOR")).orElse(WINDOWS_EDITOR_DEFAULT);
            builder = new ProcessBuilder("cmd", "/c", editor + " " + tempFile.getAbsolutePath());
        } else {
            String editor = Optional.ofNullable(System.getenv().get("EDITOR")).orElse(UNIX_EDITOR_DEFAULT);
            // Run the editor, pipe input into and out of tty so we can provide the input/output to the editor via Graql
            builder = new ProcessBuilder("/bin/bash", "-c",
                    editor + " </dev/tty >/dev/tty " + tempFile.getAbsolutePath());
        }

        builder.start().waitFor();
        return String.join("\n", Files.readAllLines(tempFile.toPath()));
    }

    private boolean isWindows() {
        return System.getProperty("os.name").toLowerCase().contains("win");
    }

}