Author: mreutegg
Date: Wed May 21 12:29:56 2014
New Revision: 1596557

URL: http://svn.apache.org/r1596557
Log:
OAK-1805: Debugging console

Initial version of a command line tool

Added:
    
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/console/
    
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/console/Command.java
    
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/console/Console.java
    
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/console/ConsoleSession.java
Modified:
    
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java

Added: 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/console/Command.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/console/Command.java?rev=1596557&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/console/Command.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/console/Command.java
 Wed May 21 12:29:56 2014
@@ -0,0 +1,527 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.console;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.NodeDocument;
+import org.apache.jackrabbit.oak.plugins.document.util.Utils;
+import org.apache.jackrabbit.oak.spi.state.AbstractNodeState;
+import org.json.simple.JSONObject;
+import org.json.simple.JSONValue;
+
+import com.google.common.base.StandardSystemProperty;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES;
+
+/**
+ * All available console commands.
+ */
+public abstract class Command {
+
+    private static final char[] SPACES = new char[4];
+
+    static {
+        Arrays.fill(SPACES, ' ');
+    }
+
+    protected String args;
+    protected String description;
+    protected String usage;
+
+    @Nonnull
+    public static Command create(String line) throws IOException {
+        StringBuilder sb = new StringBuilder();
+        int i = 0;
+        for (; i < line.length(); i++) {
+            char c = line.charAt(i);
+            if (Character.isWhitespace(c)) {
+                if (sb.length() == 0) {
+                    continue;
+                } else {
+                    break;
+                }
+            }
+            if (sb.length() == 0) {
+                c = Character.toUpperCase(c);
+            } else {
+                c = Character.toLowerCase(c);
+            }
+            sb.append(c);
+        }
+
+        if (sb.length() == 0) {
+            return new NoOp();
+        }
+
+        Command cmd;
+        try {
+            cmd = Class.forName(Command.class.getName() + "$" + sb.toString())
+                    .asSubclass(Command.class).newInstance();
+        } catch (Exception e) {
+            cmd = new Unknown(sb.toString().toLowerCase(Locale.US));
+        }
+        cmd.init(line.substring(i).trim());
+        return cmd;
+    }
+
+    protected final void init(String args) {
+        this.args = args;
+    }
+
+    public abstract void execute(@Nonnull ConsoleSession session,
+                                 @Nonnull InputStream in,
+                                 @Nonnull OutputStream out) throws Exception;
+
+    public String getName() {
+        return getClass().getSimpleName().toLowerCase(Locale.US);
+    }
+
+    protected void println(Object value, OutputStream out) throws IOException {
+        Writer writer = new OutputStreamWriter(out);
+        if (value == null) {
+            writer.write("[null]");
+        } else {
+            writer.write(value.toString());
+        }
+        println(writer);
+        writer.flush();
+    }
+
+    protected static void println(Writer writer) throws IOException {
+        writer.write(StandardSystemProperty.LINE_SEPARATOR.value());
+    }
+
+    private static final class Unknown extends Command {
+
+        private final String cmd;
+
+        private Unknown(String cmd) {
+            this.cmd = cmd;
+        }
+
+        @Override
+        public void execute(@Nonnull ConsoleSession session,
+                            @Nonnull InputStream in,
+                            @Nonnull OutputStream out) throws IOException {
+            println("Unknown command: " + cmd + " " + args, out);
+        }
+    }
+
+    private static final class NoOp extends Command {
+
+        @Override
+        public void execute(@Nonnull ConsoleSession session,
+                            @Nonnull InputStream in,
+                            @Nonnull OutputStream out) throws IOException {
+        }
+    }
+
+    public static final class Help extends Command {
+
+        public Help() {
+            this.description = "List all available command.";
+        }
+
+        @Override
+        public void execute(@Nonnull ConsoleSession session,
+                            @Nonnull InputStream in,
+                            @Nonnull OutputStream out) throws Exception {
+            SortedMap<String, Command> commands = Maps.newTreeMap();
+            SortedMap<String, Command> docCommands = Maps.newTreeMap();
+            Class[] classes = Command.class.getDeclaredClasses();
+            int maxCmdLength = 0;
+            for (Class clazz : classes) {
+                if (!Modifier.isPublic(clazz.getModifiers())
+                        || !Command.class.isAssignableFrom(clazz)) {
+                    continue;
+                }
+                Command cmd = (Command) clazz.newInstance();
+                if (DocumentNodeStoreCommand.class.isAssignableFrom(clazz)) {
+                    docCommands.put(cmd.getName(), cmd);
+                } else {
+                    commands.put(cmd.getName(), cmd);
+                }
+                maxCmdLength = Math.max(maxCmdLength, cmd.getName().length());
+            }
+            println("Generic commands:", out);
+            Writer writer = new OutputStreamWriter(out);
+            printCommands(commands, maxCmdLength, writer);
+            println(writer);
+            writer.flush();
+            println("DocumentNodeStore specific commands:", out);
+            printCommands(docCommands, maxCmdLength, writer);
+            writer.flush();
+        }
+
+        private void printCommands(SortedMap<String, Command> commands,
+                                   int maxCmdLength,
+                                   Writer writer) throws IOException {
+            for (String c : commands.keySet()) {
+                Command cmd = commands.get(c);
+                writer.write(SPACES);
+                writer.write(c);
+                int numSpaces = maxCmdLength - c.length() + 4;
+                for (int i = 0; i < numSpaces; i++) {
+                    writer.write(" ");
+                }
+                if (cmd.description != null) {
+                    writer.write(cmd.description);
+                }
+                println(writer);
+            }
+        }
+    }
+
+    public static final class Pwd extends Command {
+
+        public Pwd() {
+            this.description = "Print the full path of the current working 
node.";
+        }
+
+        @Override
+        public void execute(@Nonnull ConsoleSession session,
+                            @Nonnull InputStream in,
+                            @Nonnull OutputStream out) throws IOException {
+            println(session.getWorkingPath(), out);
+        }
+    }
+
+    public static final class Exit extends Command {
+
+        public Exit() {
+            this.description = "Quit this console.";
+        }
+
+        @Override
+        public void execute(@Nonnull ConsoleSession session,
+                            @Nonnull InputStream in,
+                            @Nonnull OutputStream out) throws IOException {
+            println("Good bye.", out);
+        }
+    }
+
+    public static final class Cd extends Command {
+
+        public Cd() {
+            this.description = "Change the current working directory to a 
specific node.";
+        }
+
+        @Override
+        public void execute(@Nonnull ConsoleSession session,
+                            @Nonnull InputStream in,
+                            @Nonnull OutputStream out) throws IOException {
+            if (!PathUtils.isValid(args)) {
+                println("Not a valid path: " + args, out);
+                return;
+            }
+            String path;
+            if (PathUtils.isAbsolute(args)) {
+                path = args;
+            } else {
+                path = PathUtils.concat(session.getWorkingPath(), args);
+            }
+            List<String> elements = Lists.newArrayList();
+            for (String element : PathUtils.elements(path)) {
+                if (PathUtils.denotesParent(element)) {
+                    if (!elements.isEmpty()) {
+                        elements.remove(elements.size() - 1);
+                    }
+                } else if (!PathUtils.denotesCurrent(element)) {
+                    elements.add(element);
+                }
+            }
+            path = PathUtils.concat("/", elements.toArray(new 
String[elements.size()]));
+            String old = session.setWorkingPath(path);
+            if (!session.getWorkingNode().exists()) {
+                session.setWorkingPath(old);
+                println("No such node", out);
+            }
+        }
+    }
+
+    public static final class Ls extends Command {
+
+        public Ls() {
+            this.description = "List the names of the children of the current 
node.";
+        }
+
+        @Override
+        public void execute(@Nonnull ConsoleSession session,
+                            @Nonnull InputStream in,
+                            @Nonnull OutputStream out) throws IOException {
+            for (String name : session.getWorkingNode().getChildNodeNames()) {
+                println(name, out);
+            }
+        }
+    }
+
+    public static final class Pn extends Command {
+
+        public Pn() {
+            this.description = "Print the current node.";
+        }
+
+        @Override
+        public void execute(@Nonnull ConsoleSession session,
+                            @Nonnull InputStream in,
+                            @Nonnull OutputStream out) throws IOException {
+            println(AbstractNodeState.toString(session.getWorkingNode()), out);
+        }
+    }
+
+    public static final class Refresh extends Command {
+
+        public Refresh() {
+            this.description = "Control how the current session is refreshed.";
+        }
+
+        @Override
+        public void execute(@Nonnull ConsoleSession session,
+                            @Nonnull InputStream in,
+                            @Nonnull OutputStream out) throws IOException {
+            if (args.contains("auto")) {
+                session.setAutoRefresh(true);
+                println("Enabled auto refresh", out);
+            } else if (args.contains("manual")) {
+                session.setAutoRefresh(false);
+                println("Disabled auto refresh", out);
+            } else if (args.trim().length() == 0) {
+                session.refresh();
+                println("Session refreshed", out);
+            } else {
+                println("Unrecognized arguments: " + args, out);
+            }
+
+        }
+    }
+
+    public static final class Checkpoint extends Command {
+
+        public Checkpoint() {
+            this.description = "Create a checkpoint.";
+        }
+
+        @Override
+        public void execute(@Nonnull ConsoleSession session,
+                            @Nonnull InputStream in,
+                            @Nonnull OutputStream out) throws Exception {
+            long time = TimeUnit.HOURS.toSeconds(1);
+            if (args.trim().length() > 0) {
+                try {
+                    time = Long.parseLong(args.trim());
+                } catch (NumberFormatException e) {
+                    println("Not a number: " + args, out);
+                    return;
+                }
+            }
+            StringBuilder msg = new StringBuilder();
+            msg.append("Checkpoint created: ");
+            msg.append(session.checkpoint(time));
+            msg.append(" (expires: ");
+            Calendar cal = Calendar.getInstance();
+            cal.add(Calendar.SECOND, (int) time);
+            msg.append(cal.getTime().toString());
+            msg.append(").");
+            println(msg, out);
+        }
+    }
+
+    public static final class Retrieve extends Command {
+
+        public Retrieve() {
+            this.description = "Retrieve a snapshot of the given checkpoint";
+        }
+
+        @Override
+        public void execute(@Nonnull ConsoleSession session,
+                            @Nonnull InputStream in,
+                            @Nonnull OutputStream out) throws Exception {
+            if (session.isAutoRefresh()) {
+                session.setAutoRefresh(false);
+                println("Auto refresh disabled.", out);
+            }
+            session.retrieve(args.trim());
+            println("Root node is now at " + args.trim(), out);
+        }
+    }
+
+    //-----------------------< DocumentNodeStore specific 
>---------------------
+
+    abstract static class DocumentNodeStoreCommand extends Command {
+        @Override
+        public void execute(@Nonnull ConsoleSession session,
+                            @Nonnull InputStream in,
+                            @Nonnull OutputStream out) throws IOException {
+            if (session.getStore() instanceof DocumentNodeStore) {
+                execute(session, (DocumentNodeStore) session.getStore(), in, 
out);
+            } else {
+                println("Can only execute command on a DocumentNodeStore", 
out);
+            }
+        }
+
+        protected abstract void execute(@Nonnull ConsoleSession session,
+                                        @Nonnull DocumentNodeStore store,
+                                        @Nonnull InputStream in,
+                                        @Nonnull OutputStream out)
+                throws IOException;
+    }
+
+    public static final class Pd extends DocumentNodeStoreCommand {
+
+        public Pd() {
+            this.description = "Print the current document.";
+        }
+
+        @Override
+        protected void execute(@Nonnull ConsoleSession session,
+                               @Nonnull DocumentNodeStore store,
+                               @Nonnull InputStream in,
+                               @Nonnull OutputStream out) throws IOException {
+            String id = Utils.getIdFromPath(session.getWorkingPath());
+            NodeDocument doc = store.getDocumentStore().find(NODES, id);
+            if (doc == null) {
+                println("[null]", out);
+            } else {
+                println(doc, out);
+            }
+        }
+
+        private void println(NodeDocument doc, OutputStream out)
+                throws IOException {
+            int level = 1;
+            Writer writer = new OutputStreamWriter(out);
+            Set<String> mapKeys = Sets.newHashSet();
+            writer.write('{');
+            String comma = "";
+            for (String key : doc.keySet()) {
+                Object value = doc.get(key);
+                if (value instanceof Map) {
+                    mapKeys.add(key);
+                    continue;
+                }
+                writer.write(comma);
+                comma = ",";
+                println(writer);
+                printIndent(level, writer);
+                printJson(key, value, writer);
+            }
+            for (String key : mapKeys) {
+                writer.write(comma);
+                comma = ",";
+                println(writer);
+                printIndent(level, writer);
+                writer.write(JSONObject.escape(key));
+                writer.write(": {");
+                println((Map) doc.get(key), level + 1, writer);
+                println(writer);
+                printIndent(level, writer);
+                writer.write("}");
+            }
+            println(writer);
+            writer.write('}');
+            println(writer);
+            writer.flush();
+        }
+
+        private void println(Map map, int level, Writer writer)
+                throws IOException {
+            String comma = "";
+            for (Object obj : map.keySet()) {
+                String key = obj.toString();
+                Object value = map.get(obj);
+                writer.write(comma);
+                comma = ",";
+                println(writer);
+                printIndent(level, writer);
+                printJson(key, value, writer);
+            }
+        }
+
+        private void printJson(String key, Object value, Writer writer)
+                throws IOException {
+            writer.write(JSONObject.escape(key));
+            writer.write(": ");
+            writer.write(jsonEscape(value));
+        }
+
+        private String jsonEscape(Object value) {
+            String escaped = JSONValue.toJSONString(value);
+            return escaped.replaceAll("\\\\/", "/");
+        }
+
+        private void printIndent(int level, Writer writer) throws IOException {
+            for (int i = 0; i < level; i++) {
+                writer.write(SPACES);
+            }
+        }
+    }
+
+    public static final class Lsd extends DocumentNodeStoreCommand {
+
+        public Lsd() {
+            this.description = "List the identifiers of the child documents at 
the current path.";
+        }
+
+        @Override
+        protected void execute(@Nonnull ConsoleSession session,
+                               @Nonnull DocumentNodeStore store,
+                               @Nonnull InputStream in,
+                               @Nonnull OutputStream out) throws IOException {
+            Writer writer = new OutputStreamWriter(out);
+            String path = session.getWorkingPath();
+            String fromKey = Utils.getKeyLowerLimit(path);
+            String toKey = Utils.getKeyUpperLimit(path);
+            int num = 0;
+            for (NodeDocument doc : store.getDocumentStore().query(
+                    NODES, fromKey, toKey, Integer.MAX_VALUE)) {
+                writer.write(doc.getId());
+                println(writer);
+                num++;
+            }
+            println(writer);
+            writer.write("Found " + num + " document");
+            if (num != 1) {
+                writer.write("s");
+            }
+            writer.write(".");
+            println(writer);
+            writer.flush();
+        }
+    }
+
+}

Added: 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/console/Console.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/console/Console.java?rev=1596557&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/console/Console.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/console/Console.java
 Wed May 21 12:29:56 2014
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.console;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Set;
+
+import org.apache.jackrabbit.oak.fixture.OakFixture;
+import org.apache.jackrabbit.oak.plugins.document.DocumentMK;
+import org.apache.jackrabbit.oak.plugins.document.util.MongoConnection;
+import org.apache.jackrabbit.oak.plugins.segment.SegmentNodeStore;
+import org.apache.jackrabbit.oak.plugins.segment.file.FileStore;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+import joptsimple.OptionSpec;
+
+/**
+ * A command line console.
+ */
+public class Console {
+
+    public static void main(String[] args) throws Exception {
+        OptionParser parser = new OptionParser();
+        OptionSpec<File> base = parser.accepts("base", "Base directory")
+                .withRequiredArg().ofType(File.class)
+                .defaultsTo(new File("."));
+        OptionSpec<String> host = parser.accepts("host", "MongoDB host")
+                .withRequiredArg().defaultsTo("localhost");
+        OptionSpec<Integer> port = parser.accepts("port", "MongoDB port")
+                .withRequiredArg().ofType(Integer.class).defaultsTo(27017);
+        OptionSpec<String> dbName = parser.accepts("db", "MongoDB database")
+                .withRequiredArg().defaultsTo("oak");
+        OptionSpec<Integer> clusterId = parser.accepts("clusterId", "MongoMK 
clusterId")
+                .withRequiredArg().ofType(Integer.class).defaultsTo(1);
+        OptionSpec<Integer> cache = parser.accepts("cache", "cache size (MB)")
+                .withRequiredArg().ofType(Integer.class).defaultsTo(100);
+
+        OptionSet options = parser.parse(args);
+        Set<String> argset = Sets.newHashSet(options.nonOptionArguments());
+
+        String uri = "mongodb://" + host.value(options) + ":" +
+                port.value(options) + "/" + dbName.value(options);
+        NodeStore store;
+        if (argset.contains(OakFixture.OAK_MONGO)) {
+            MongoConnection mongo = new MongoConnection(uri);
+            store = new DocumentMK.Builder().
+                    setMongoDB(mongo.getDB()).
+                    memoryCacheSize(cache.value(options)).
+                    setClusterId(clusterId.value(options)).getNodeStore();
+        } else if (argset.contains(OakFixture.OAK_TAR)) {
+            store = new SegmentNodeStore(new FileStore(base.value(options), 
256));
+        } else {
+            System.out.println("No repository specified. " +
+                    "Was expecting one of: " + Lists.newArrayList(
+                    OakFixture.OAK_TAR, OakFixture.OAK_MONGO));
+            System.exit(1);
+            return;
+        }
+
+        System.exit(new Console(store, System.in, System.out).run());
+    }
+
+    private final NodeStore store;
+    private final InputStream in;
+    private final OutputStream out;
+
+    public Console(NodeStore store, InputStream in, OutputStream out) {
+        this.store = store;
+        this.in = in;
+        this.out = out;
+    }
+
+    public int run() throws Exception {
+        int code = 1;
+        ConsoleSession session = ConsoleSession.create(store);
+        for (;;) {
+            prompt();
+            String line = readLine(in);
+            if (line == null) {
+                break;
+            }
+            Command c = Command.create(line);
+            try {
+                c.execute(session, in, out);
+            } catch (Exception e) {
+                e.printStackTrace(new PrintWriter(out));
+            }
+            if (c.getName().equals("exit")) {
+                code = 0;
+                break;
+            }
+
+        }
+        return code;
+    }
+
+    private void prompt() throws IOException {
+        out.write("> ".getBytes());
+    }
+
+    private static String readLine(InputStream in) throws IOException {
+        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+        return reader.readLine();
+    }
+}

Added: 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/console/ConsoleSession.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/console/ConsoleSession.java?rev=1596557&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/console/ConsoleSession.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/console/ConsoleSession.java
 Wed May 21 12:29:56 2014
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.console;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+
+import com.google.common.collect.Maps;
+
+/**
+ * Light weight session to a NodeStore, holding context information.
+ */
+public abstract class ConsoleSession {
+
+    private final Map<String, Object> context = Maps.newHashMap();
+
+    private final NodeStore store;
+
+    private ConsoleSession(NodeStore store) {
+        this.store = store;
+    }
+
+    public static ConsoleSession create(NodeStore store) {
+        return new DefaultSession(store);
+    }
+
+    /**
+     * Returns the current working path. This method will return the path
+     * of the root node if none is set explicity.
+     *
+     * @return the current working path.
+     */
+    public String getWorkingPath() {
+        String path = (String) context.get("workingPath");
+        if (path == null) {
+            path = "/";
+            context.put("workingPath", path);
+        }
+        return path;
+    }
+
+    /**
+     * Sets a new working path and returns the previously set.
+     *
+     * @param path the new working path.
+     * @return the previously set working path.
+     */
+    public String setWorkingPath(String path) {
+        String old = getWorkingPath();
+        context.put("workingPath", path);
+        return old;
+    }
+
+    /**
+     * Creates and returns a checkpoint with the given lifetime in seconds.
+     *
+     * @param lifetimeSeconds the lifetime of the checkpoint in seconds.
+     * @return the checkpoint reference.
+     */
+    public String checkpoint(long lifetimeSeconds) {
+        return store.checkpoint(TimeUnit.SECONDS.toMillis(lifetimeSeconds));
+    }
+
+    /**
+     * Retrieves the root node from a previously created checkpoint. The root
+     * node is available with {@link #getRoot()}.
+     *
+     * @param checkpoint the checkpoint reference.
+     */
+    public void retrieve(String checkpoint) {
+        context.put("root", store.retrieve(checkpoint));
+    }
+
+    /**
+     * Returns the currently set root node. If {@link #isAutoRefresh()} is set,
+     * a fresh root node is retrieved from the store.
+     *
+     * @return the current root node.
+     */
+    public NodeState getRoot() {
+        NodeState root = (NodeState) context.get("root");
+        if (root == null || isAutoRefresh()) {
+            root = store.getRoot();
+            context.put("root", root);
+        }
+        return root;
+    }
+
+    /**
+     * @return the underlying node store.
+     */
+    public NodeStore getStore() {
+        return store;
+    }
+
+    /**
+     * The node state for the current working path. Possibly non-existent.
+     *
+     * @return the working node state.
+     */
+    @Nonnull
+    public NodeState getWorkingNode() {
+        NodeState current = getRoot();
+        for (String element : PathUtils.elements(getWorkingPath())) {
+            current = current.getChildNode(element);
+        }
+        return current;
+    }
+
+    /**
+     * Enables or disables auto-refresh of the root node state on
+     * {@link #getRoot()}.
+     *
+     * @param enable enables or disables auto-refresh.
+     */
+    public void setAutoRefresh(boolean enable) {
+        if (enable) {
+            context.put("auto-refresh", "true");
+        } else {
+            context.remove("auto-refresh");
+        }
+    }
+
+    /**
+     * @return <code>true</code> if auto-refresh is enabled; <code>false</code>
+     *          otherwise.
+     */
+    public boolean isAutoRefresh() {
+        return context.containsKey("auto-refresh");
+    }
+
+    /**
+     * Performs a manual refresh of the root node state.
+     */
+    public void refresh() {
+        context.put("root", store.getRoot());
+    }
+
+    private static final class DefaultSession extends ConsoleSession {
+
+        DefaultSession(NodeStore store) {
+            super(store);
+        }
+    }
+}

Modified: 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java?rev=1596557&r1=1596556&r2=1596557&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java
 Wed May 21 12:29:56 2014
@@ -48,6 +48,7 @@ import org.apache.jackrabbit.oak.Oak;
 import org.apache.jackrabbit.oak.api.ContentRepository;
 import org.apache.jackrabbit.oak.benchmark.BenchmarkRunner;
 import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.console.Console;
 import org.apache.jackrabbit.oak.fixture.OakFixture;
 import org.apache.jackrabbit.oak.http.OakServlet;
 import org.apache.jackrabbit.oak.jcr.Jcr;
@@ -99,6 +100,9 @@ public class Main {
             case BENCHMARK:
                 BenchmarkRunner.main(args);
                 break;
+            case CONSOLE:
+                Console.main(args);
+                break;
             case DEBUG:
                 debug(args);
                 break;
@@ -487,6 +491,7 @@ public class Main {
 
         BACKUP("backup"),
         BENCHMARK("benchmark"),
+        CONSOLE("debug"),
         DEBUG("debug"),
         SERVER("server"),
         UPGRADE("upgrade"),


Reply via email to