You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metron.apache.org by ce...@apache.org on 2016/09/26 22:00:38 UTC

incubator-metron git commit: METRON-453: Add a stellar shell function to open an external editor and return the editor's contents closes apache/incubator-metron#272

Repository: incubator-metron
Updated Branches:
  refs/heads/master c85c74269 -> 3d5f279ca


METRON-453: Add a stellar shell function to open an external editor and return the editor's contents closes apache/incubator-metron#272


Project: http://git-wip-us.apache.org/repos/asf/incubator-metron/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-metron/commit/3d5f279c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-metron/tree/3d5f279c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-metron/diff/3d5f279c

Branch: refs/heads/master
Commit: 3d5f279caea2b9349a05b3a7316ef19e2ca8cb11
Parents: c85c742
Author: cstella <ce...@gmail.com>
Authored: Mon Sep 26 18:00:22 2016 -0400
Committer: cstella <ce...@gmail.com>
Committed: Mon Sep 26 18:00:22 2016 -0400

----------------------------------------------------------------------
 .../common/dsl/FunctionResolverSingleton.java   |   3 +-
 .../common/stellar/shell/PausableInput.java     | 373 +++++++++++++++++++
 .../common/stellar/shell/StellarExecutor.java   |  13 +-
 .../common/stellar/shell/StellarShell.java      |   5 +-
 metron-platform/metron-management/README.md     |   5 +
 .../management/ParserConfigFunctions.java       |   3 +-
 .../metron/management/ShellFunctions.java       | 108 +++++-
 .../metron/management/ShellFunctionsTest.java   |   7 +
 8 files changed, 505 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/3d5f279c/metron-platform/metron-common/src/main/java/org/apache/metron/common/dsl/FunctionResolverSingleton.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-common/src/main/java/org/apache/metron/common/dsl/FunctionResolverSingleton.java b/metron-platform/metron-common/src/main/java/org/apache/metron/common/dsl/FunctionResolverSingleton.java
index ed57db0..4d41ac5 100644
--- a/metron-platform/metron-common/src/main/java/org/apache/metron/common/dsl/FunctionResolverSingleton.java
+++ b/metron-platform/metron-common/src/main/java/org/apache/metron/common/dsl/FunctionResolverSingleton.java
@@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory;
 
 import java.net.URL;
 import java.util.*;
+import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -142,7 +143,7 @@ public class FunctionResolverSingleton implements FunctionResolver {
   private void loadFunctions(final Map<String, StellarFunctionInfo> ret) {
     try {
       ClassLoader classLoader = getClass().getClassLoader();
-      Reflections reflections = new Reflections(new ConfigurationBuilder().setUrls(effectiveClassPathUrls(classLoader)));
+      Reflections reflections = new Reflections(new ConfigurationBuilder() .setUrls(effectiveClassPathUrls(classLoader)) );
       for (Class<?> clazz : reflections.getSubTypesOf(StellarFunction.class)) {
         if (clazz.isAnnotationPresent(Stellar.class)) {
           Map.Entry<String, StellarFunctionInfo> instance = create((Class<? extends StellarFunction>) clazz);

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/3d5f279c/metron-platform/metron-common/src/main/java/org/apache/metron/common/stellar/shell/PausableInput.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-common/src/main/java/org/apache/metron/common/stellar/shell/PausableInput.java b/metron-platform/metron-common/src/main/java/org/apache/metron/common/stellar/shell/PausableInput.java
new file mode 100644
index 0000000..1c26d95
--- /dev/null
+++ b/metron-platform/metron-common/src/main/java/org/apache/metron/common/stellar/shell/PausableInput.java
@@ -0,0 +1,373 @@
+/*
+ *
+ *  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.metron.common.stellar.shell;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An input stream which mirrors System.in, but allows you to 'pause' and 'unpause' it.
+ * The Aeshell has an external thread which is constantly polling System.in.  If you
+ * need to spawn a program externally (i.e. an editor) which shares stdin, this thread
+ * and the spawned program both share a buffer.  This causes contention and unpredictable
+ * results (e.g. an input may be consumed by either the aeshell thread or the spawned program)
+ *
+ * Because you can inject an input stream into the console, we create this which can act as a
+ * facade to System.in under normal 'unpaused' circumstances, and when paused, turn off the
+ * access to System.in.  This allows us to turn off access to aeshell while maintaining access
+ * to the external program.
+ *
+ */
+public class PausableInput extends InputStream {
+  InputStream in = System.in;
+  boolean paused = false;
+  private PausableInput() {
+    super();
+  }
+
+  /**
+   * Stop mirroring stdin
+   * @throws IOException
+   */
+  public void pause() {
+    paused = true;
+  }
+
+  /**
+   * Resume mirroring stdin.
+   * @throws IOException
+   */
+  public void unpause() throws IOException {
+    in.read(new byte[in.available()]);
+    paused = false;
+  }
+
+  public final static PausableInput INSTANCE = new PausableInput();
+
+  /**
+   * Reads the next byte of data from the input stream. The value byte is
+   * returned as an <code>int</code> in the range <code>0</code> to
+   * <code>255</code>. If no byte is available because the end of the stream
+   * has been reached, the value <code>-1</code> is returned. This method
+   * blocks until input data is available, the end of the stream is detected,
+   * or an exception is thrown.
+   * <p>
+   * <p> A subclass must provide an implementation of this method.
+   *
+   * @return the next byte of data, or <code>-1</code> if the end of the
+   * stream is reached.
+   * @throws IOException if an I/O error occurs.
+   */
+  @Override
+  public int read() throws IOException {
+
+    return in.read();
+  }
+
+  /**
+   * Reads some number of bytes from the input stream and stores them into
+   * the buffer array <code>b</code>. The number of bytes actually read is
+   * returned as an integer.  This method blocks until input data is
+   * available, end of file is detected, or an exception is thrown.
+   * <p>
+   * <p> If the length of <code>b</code> is zero, then no bytes are read and
+   * <code>0</code> is returned; otherwise, there is an attempt to read at
+   * least one byte. If no byte is available because the stream is at the
+   * end of the file, the value <code>-1</code> is returned; otherwise, at
+   * least one byte is read and stored into <code>b</code>.
+   * <p>
+   * <p> The first byte read is stored into element <code>b[0]</code>, the
+   * next one into <code>b[1]</code>, and so on. The number of bytes read is,
+   * at most, equal to the length of <code>b</code>. Let <i>k</i> be the
+   * number of bytes actually read; these bytes will be stored in elements
+   * <code>b[0]</code> through <code>b[</code><i>k</i><code>-1]</code>,
+   * leaving elements <code>b[</code><i>k</i><code>]</code> through
+   * <code>b[b.length-1]</code> unaffected.
+   * <p>
+   * <p> The <code>read(b)</code> method for class <code>InputStream</code>
+   * has the same effect as: <pre><code> read(b, 0, b.length) </code></pre>
+   *
+   * @param b the buffer into which the data is read.
+   * @return the total number of bytes read into the buffer, or
+   * <code>-1</code> if there is no more data because the end of
+   * the stream has been reached.
+   * @throws IOException          If the first byte cannot be read for any reason
+   *                              other than the end of the file, if the input stream has been closed, or
+   *                              if some other I/O error occurs.
+   * @throws NullPointerException if <code>b</code> is <code>null</code>.
+   * @see InputStream#read(byte[], int, int)
+   */
+  @Override
+  public int read(byte[] b) throws IOException {
+
+    if(paused) {
+      try {
+        Thread.sleep(1000);
+      } catch (InterruptedException e) {
+        e.printStackTrace();
+      }
+      return 0;
+    }
+    int ret = in.read(b);
+    return ret;
+  }
+
+  /**
+   * Reads up to <code>len</code> bytes of data from the input stream into
+   * an array of bytes.  An attempt is made to read as many as
+   * <code>len</code> bytes, but a smaller number may be read.
+   * The number of bytes actually read is returned as an integer.
+   * <p>
+   * <p> This method blocks until input data is available, end of file is
+   * detected, or an exception is thrown.
+   * <p>
+   * <p> If <code>len</code> is zero, then no bytes are read and
+   * <code>0</code> is returned; otherwise, there is an attempt to read at
+   * least one byte. If no byte is available because the stream is at end of
+   * file, the value <code>-1</code> is returned; otherwise, at least one
+   * byte is read and stored into <code>b</code>.
+   * <p>
+   * <p> The first byte read is stored into element <code>b[off]</code>, the
+   * next one into <code>b[off+1]</code>, and so on. The number of bytes read
+   * is, at most, equal to <code>len</code>. Let <i>k</i> be the number of
+   * bytes actually read; these bytes will be stored in elements
+   * <code>b[off]</code> through <code>b[off+</code><i>k</i><code>-1]</code>,
+   * leaving elements <code>b[off+</code><i>k</i><code>]</code> through
+   * <code>b[off+len-1]</code> unaffected.
+   * <p>
+   * <p> In every case, elements <code>b[0]</code> through
+   * <code>b[off]</code> and elements <code>b[off+len]</code> through
+   * <code>b[b.length-1]</code> are unaffected.
+   * <p>
+   * <p> The <code>read(b,</code> <code>off,</code> <code>len)</code> method
+   * for class <code>InputStream</code> simply calls the method
+   * <code>read()</code> repeatedly. If the first such call results in an
+   * <code>IOException</code>, that exception is returned from the call to
+   * the <code>read(b,</code> <code>off,</code> <code>len)</code> method.  If
+   * any subsequent call to <code>read()</code> results in a
+   * <code>IOException</code>, the exception is caught and treated as if it
+   * were end of file; the bytes read up to that point are stored into
+   * <code>b</code> and the number of bytes read before the exception
+   * occurred is returned. The default implementation of this method blocks
+   * until the requested amount of input data <code>len</code> has been read,
+   * end of file is detected, or an exception is thrown. Subclasses are encouraged
+   * to provide a more efficient implementation of this method.
+   *
+   * @param b   the buffer into which the data is read.
+   * @param off the start offset in array <code>b</code>
+   *            at which the data is written.
+   * @param len the maximum number of bytes to read.
+   * @return the total number of bytes read into the buffer, or
+   * <code>-1</code> if there is no more data because the end of
+   * the stream has been reached.
+   * @throws IOException               If the first byte cannot be read for any reason
+   *                                   other than end of file, or if the input stream has been closed, or if
+   *                                   some other I/O error occurs.
+   * @throws NullPointerException      If <code>b</code> is <code>null</code>.
+   * @throws IndexOutOfBoundsException If <code>off</code> is negative,
+   *                                   <code>len</code> is negative, or <code>len</code> is greater than
+   *                                   <code>b.length - off</code>
+   * @see InputStream#read()
+   */
+  @Override
+  public int read(byte[] b, int off, int len) throws IOException {
+    if(paused) {
+      try {
+        Thread.sleep(1000);
+      } catch (InterruptedException e) {
+        e.printStackTrace();
+      }
+      return 0;
+    }
+    int ret = in.read(b, off, len);
+    return ret;
+  }
+
+  /**
+   * Skips over and discards <code>n</code> bytes of data from this input
+   * stream. The <code>skip</code> method may, for a variety of reasons, end
+   * up skipping over some smaller number of bytes, possibly <code>0</code>.
+   * This may result from any of a number of conditions; reaching end of file
+   * before <code>n</code> bytes have been skipped is only one possibility.
+   * The actual number of bytes skipped is returned. If {@code n} is
+   * negative, the {@code skip} method for class {@code InputStream} always
+   * returns 0, and no bytes are skipped. Subclasses may handle the negative
+   * value differently.
+   * <p>
+   * <p> The <code>skip</code> method of this class creates a
+   * byte array and then repeatedly reads into it until <code>n</code> bytes
+   * have been read or the end of the stream has been reached. Subclasses are
+   * encouraged to provide a more efficient implementation of this method.
+   * For instance, the implementation may depend on the ability to seek.
+   *
+   * @param n the number of bytes to be skipped.
+   * @return the actual number of bytes skipped.
+   * @throws IOException if the stream does not support seek,
+   *                     or if some other I/O error occurs.
+   */
+  @Override
+  public long skip(long n) throws IOException {
+
+    return in.skip(n);
+  }
+
+  /**
+   * Returns an estimate of the number of bytes that can be read (or
+   * skipped over) from this input stream without blocking by the next
+   * invocation of a method for this input stream. The next invocation
+   * might be the same thread or another thread.  A single read or skip of this
+   * many bytes will not block, but may read or skip fewer bytes.
+   * <p>
+   * <p> Note that while some implementations of {@code InputStream} will return
+   * the total number of bytes in the stream, many will not.  It is
+   * never correct to use the return value of this method to allocate
+   * a buffer intended to hold all data in this stream.
+   * <p>
+   * <p> A subclass' implementation of this method may choose to throw an
+   * {@link IOException} if this input stream has been closed by
+   * invoking the {@link #close()} method.
+   * <p>
+   * <p> The {@code available} method for class {@code InputStream} always
+   * returns {@code 0}.
+   * <p>
+   * <p> This method should be overridden by subclasses.
+   *
+   * @return an estimate of the number of bytes that can be read (or skipped
+   * over) from this input stream without blocking or {@code 0} when
+   * it reaches the end of the input stream.
+   * @throws IOException if an I/O error occurs.
+   */
+  @Override
+  public int available() throws IOException {
+
+    return in.available();
+  }
+
+  /**
+   * Closes this input stream and releases any system resources associated
+   * with the stream.
+   * <p>
+   * <p> The <code>close</code> method of <code>InputStream</code> does
+   * nothing.
+   *
+   * @throws IOException if an I/O error occurs.
+   */
+  @Override
+  public void close() throws IOException {
+    in.close();
+  }
+
+  /**
+   * Marks the current position in this input stream. A subsequent call to
+   * the <code>reset</code> method repositions this stream at the last marked
+   * position so that subsequent reads re-read the same bytes.
+   * <p>
+   * <p> The <code>readlimit</code> arguments tells this input stream to
+   * allow that many bytes to be read before the mark position gets
+   * invalidated.
+   * <p>
+   * <p> The general contract of <code>mark</code> is that, if the method
+   * <code>markSupported</code> returns <code>true</code>, the stream somehow
+   * remembers all the bytes read after the call to <code>mark</code> and
+   * stands ready to supply those same bytes again if and whenever the method
+   * <code>reset</code> is called.  However, the stream is not required to
+   * remember any data at all if more than <code>readlimit</code> bytes are
+   * read from the stream before <code>reset</code> is called.
+   * <p>
+   * <p> Marking a closed stream should not have any effect on the stream.
+   * <p>
+   * <p> The <code>mark</code> method of <code>InputStream</code> does
+   * nothing.
+   *
+   * @param readlimit the maximum limit of bytes that can be read before
+   *                  the mark position becomes invalid.
+   * @see InputStream#reset()
+   */
+  @Override
+  public synchronized void mark(int readlimit) {
+    in.mark(readlimit);
+  }
+
+  /**
+   * Repositions this stream to the position at the time the
+   * <code>mark</code> method was last called on this input stream.
+   * <p>
+   * <p> The general contract of <code>reset</code> is:
+   * <p>
+   * <ul>
+   * <li> If the method <code>markSupported</code> returns
+   * <code>true</code>, then:
+   * <p>
+   * <ul><li> If the method <code>mark</code> has not been called since
+   * the stream was created, or the number of bytes read from the stream
+   * since <code>mark</code> was last called is larger than the argument
+   * to <code>mark</code> at that last call, then an
+   * <code>IOException</code> might be thrown.
+   * <p>
+   * <li> If such an <code>IOException</code> is not thrown, then the
+   * stream is reset to a state such that all the bytes read since the
+   * most recent call to <code>mark</code> (or since the start of the
+   * file, if <code>mark</code> has not been called) will be resupplied
+   * to subsequent callers of the <code>read</code> method, followed by
+   * any bytes that otherwise would have been the next input data as of
+   * the time of the call to <code>reset</code>. </ul>
+   * <p>
+   * <li> If the method <code>markSupported</code> returns
+   * <code>false</code>, then:
+   * <p>
+   * <ul><li> The call to <code>reset</code> may throw an
+   * <code>IOException</code>.
+   * <p>
+   * <li> If an <code>IOException</code> is not thrown, then the stream
+   * is reset to a fixed state that depends on the particular type of the
+   * input stream and how it was created. The bytes that will be supplied
+   * to subsequent callers of the <code>read</code> method depend on the
+   * particular type of the input stream. </ul></ul>
+   * <p>
+   * <p>The method <code>reset</code> for class <code>InputStream</code>
+   * does nothing except throw an <code>IOException</code>.
+   *
+   * @throws IOException if this stream has not been marked or if the
+   *                     mark has been invalidated.
+   * @see InputStream#mark(int)
+   * @see IOException
+   */
+  @Override
+  public synchronized void reset() throws IOException {
+    in.reset();
+  }
+
+  /**
+   * Tests if this input stream supports the <code>mark</code> and
+   * <code>reset</code> methods. Whether or not <code>mark</code> and
+   * <code>reset</code> are supported is an invariant property of a
+   * particular input stream instance. The <code>markSupported</code> method
+   * of <code>InputStream</code> returns <code>false</code>.
+   *
+   * @return <code>true</code> if this stream instance supports the mark
+   * and reset methods; <code>false</code> otherwise.
+   * @see InputStream#mark(int)
+   * @see InputStream#reset()
+   */
+  @Override
+  public boolean markSupported() {
+    return in.markSupported();
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/3d5f279c/metron-platform/metron-common/src/main/java/org/apache/metron/common/stellar/shell/StellarExecutor.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-common/src/main/java/org/apache/metron/common/stellar/shell/StellarExecutor.java b/metron-platform/metron-common/src/main/java/org/apache/metron/common/stellar/shell/StellarExecutor.java
index d39e8df..905011c 100644
--- a/metron-platform/metron-common/src/main/java/org/apache/metron/common/stellar/shell/StellarExecutor.java
+++ b/metron-platform/metron-common/src/main/java/org/apache/metron/common/stellar/shell/StellarExecutor.java
@@ -47,6 +47,7 @@ import static org.apache.metron.common.stellar.shell.StellarExecutor.OperationTy
 public class StellarExecutor {
 
   public static String SHELL_VARIABLES = "shellVariables";
+  public static String CONSOLE = "console";
 
   private ReadWriteLock indexLock = new ReentrantReadWriteLock();
 
@@ -69,7 +70,7 @@ public class StellarExecutor {
 
     @Override
     public String toString() {
-      String ret = result.toString();
+      String ret = "" + result;
       if(expression != null) {
         ret += " via " + expression;
       }
@@ -101,6 +102,7 @@ public class StellarExecutor {
    */
   private Context context;
 
+  private Console console;
 
   public enum OperationType {
     DOC,MAGIC,NORMAL;
@@ -135,16 +137,17 @@ public class StellarExecutor {
 
   }
 
-  public StellarExecutor() throws Exception {
-    this(null);
+  public StellarExecutor(Console console) throws Exception {
+    this(null, console);
   }
 
-  public StellarExecutor(String zookeeperUrl) throws Exception {
+  public StellarExecutor(String zookeeperUrl, Console console) throws Exception {
     this.variables = new HashMap<>();
     this.functionResolver = new StellarFunctions().FUNCTION_RESOLVER();
     this.client = createClient(zookeeperUrl);
     this.context = createContext();
     this.autocompleteIndex = initializeIndex();
+    this.console = console;
     //Asynchronously update the index with function names found from a classpath scan.
     new Thread( () -> {
         Iterable<StellarFunctionInfo> functions = functionResolver.getFunctionInfo();
@@ -225,11 +228,13 @@ public class StellarExecutor {
               .with(Context.Capabilities.GLOBAL_CONFIG, () -> global)
               .with(Context.Capabilities.ZOOKEEPER_CLIENT, () -> client.get())
               .with(SHELL_VARIABLES, () -> variables)
+              .with(CONSOLE, () -> console)
               .build();
     }
     else {
       context = new Context.Builder()
               .with(SHELL_VARIABLES, () -> variables)
+              .with(CONSOLE, () -> console)
               .build();
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/3d5f279c/metron-platform/metron-common/src/main/java/org/apache/metron/common/stellar/shell/StellarShell.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-common/src/main/java/org/apache/metron/common/stellar/shell/StellarShell.java b/metron-platform/metron-common/src/main/java/org/apache/metron/common/stellar/shell/StellarShell.java
index e318776..db41863 100644
--- a/metron-platform/metron-common/src/main/java/org/apache/metron/common/stellar/shell/StellarShell.java
+++ b/metron-platform/metron-common/src/main/java/org/apache/metron/common/stellar/shell/StellarShell.java
@@ -128,6 +128,7 @@ public class StellarShell extends AeshConsoleCallback implements Completion {
                                                     .enableMan(true)
                                                     .ansi(useAnsi)
                                                     .parseOperators(false)
+                                                    .inputStream(PausableInput.INSTANCE)
                                                     ;
     if(commandLine.hasOption("irc")) {
       settings = settings.inputrc(new File(commandLine.getOptionValue("irc")));
@@ -137,10 +138,10 @@ public class StellarShell extends AeshConsoleCallback implements Completion {
     // create the executor
     if(commandLine.hasOption("z")) {
       String zookeeperUrl = commandLine.getOptionValue("z");
-      executor = new StellarExecutor(zookeeperUrl);
+      executor = new StellarExecutor(zookeeperUrl, console);
 
     } else {
-      executor = new StellarExecutor();
+      executor = new StellarExecutor(console);
     }
 
     if(commandLine.hasOption("v")) {

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/3d5f279c/metron-platform/metron-management/README.md
----------------------------------------------------------------------
diff --git a/metron-platform/metron-management/README.md b/metron-platform/metron-management/README.md
index 1f5481f..7b4b061 100644
--- a/metron-platform/metron-management/README.md
+++ b/metron-platform/metron-management/README.md
@@ -26,6 +26,11 @@ The functions are split roughly into a few sections:
 
 ### Shell Functions 
 
+* `SHELL_EDIT`
+  * Description: Open an editor (optionally initialized with text) and return whatever is saved from the editor.  The editor to use is pulled from `EDITOR` or `VISUAL` environment variable.
+  * Input:
+    * string - (Optional) A string whose content is used to initialize the editor.
+  * Returns: The content that the editor saved after editor exit.
 * `SHELL_GET_EXPRESSION`
   * Description: Get a stellar expression from a variable
   * Input:

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/3d5f279c/metron-platform/metron-management/src/main/java/org/apache/metron/management/ParserConfigFunctions.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-management/src/main/java/org/apache/metron/management/ParserConfigFunctions.java b/metron-platform/metron-management/src/main/java/org/apache/metron/management/ParserConfigFunctions.java
index 7ceb7af..d85516e 100644
--- a/metron-platform/metron-management/src/main/java/org/apache/metron/management/ParserConfigFunctions.java
+++ b/metron-platform/metron-management/src/main/java/org/apache/metron/management/ParserConfigFunctions.java
@@ -37,7 +37,7 @@ import java.util.*;
 import static org.apache.metron.common.configuration.ConfigurationType.PARSER;
 
 public class ParserConfigFunctions {
-  private static final Logger LOG = Logger.getLogger(ConfigurationFunctions.class);
+  private static final Logger LOG = Logger.getLogger(ParserConfigFunctions.class);
 
   private static void pruneEmptyStellarTransformers(SensorParserConfig config) {
     List<FieldTransformer> toRemove = new ArrayList<>();
@@ -187,6 +187,7 @@ public class ParserConfigFunctions {
 
       }
       List<String> output = new ArrayList<>();
+
       output.addAll(stellarTransformer.getConfig().keySet());
       stellarTransformer.setOutput(output);
 

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/3d5f279c/metron-platform/metron-management/src/main/java/org/apache/metron/management/ShellFunctions.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-management/src/main/java/org/apache/metron/management/ShellFunctions.java b/metron-platform/metron-management/src/main/java/org/apache/metron/management/ShellFunctions.java
index 300805c..cf7afeb 100644
--- a/metron-platform/metron-management/src/main/java/org/apache/metron/management/ShellFunctions.java
+++ b/metron-platform/metron-management/src/main/java/org/apache/metron/management/ShellFunctions.java
@@ -18,17 +18,27 @@
 package org.apache.metron.management;
 
 import com.jakewharton.fliptables.FlipTable;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.input.NullInputStream;
 import org.apache.commons.lang3.text.WordUtils;
+import org.apache.log4j.Logger;
 import org.apache.metron.common.dsl.*;
+import org.apache.metron.common.stellar.shell.PausableInput;
 import org.apache.metron.common.stellar.shell.StellarExecutor;
 import org.apache.metron.common.utils.ConversionUtils;
+import org.jboss.aesh.console.AeshProcess;
+import org.jboss.aesh.console.Console;
 
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.charset.Charset;
+import java.util.*;
+
+import static org.apache.metron.common.stellar.shell.StellarExecutor.CONSOLE;
 
 public class ShellFunctions {
+  private static final Logger LOG = Logger.getLogger(ShellFunctions.class);
 
   @Stellar(
            namespace = "SHELL"
@@ -185,4 +195,94 @@ public class ShellFunctions {
       return true;
     }
   }
+
+  @Stellar(
+           namespace = "SHELL"
+          ,name = "EDIT"
+          ,description = "Open an editor (optionally initialized with text) and return " +
+                         "whatever is saved from the editor.  The editor to use is pulled " +
+                         "from `EDITOR` or `VISUAL` environment variable."
+          ,params = {   "string - (Optional) A string whose content is used to initialize the editor."
+                    }
+          ,returns = "The content that the editor saved after editor exit."
+          )
+  public static class Edit implements StellarFunction {
+
+    private String getEditor() {
+      String editor = System.getenv().get("EDITOR");
+      if(editor == null) {
+        editor = System.getenv("VISUAL");
+      }
+      if(editor == null) {
+        editor = System.getProperty("EDITOR");
+      }
+      if(editor == null) {
+        editor = "/bin/vi";
+      }
+      return editor;
+    }
+
+    @Override
+    public Object apply(List<Object> args, Context context) throws ParseException {
+      File outFile = null;
+      String editor = getEditor();
+      try {
+        outFile = File.createTempFile("stellar_shell", "out");
+        if(args.size() > 0) {
+          String arg = (String)args.get(0);
+          try(PrintWriter pw = new PrintWriter(outFile)) {
+            IOUtils.write(arg, pw);
+          }
+        }
+      } catch (IOException e) {
+        String message = "Unable to create temp file: " + e.getMessage();
+        LOG.error(message, e);
+        throw new IllegalStateException(message, e);
+      }
+      Optional<Object> console =  context.getCapability(CONSOLE, false);
+      try {
+        PausableInput.INSTANCE.pause();
+        //shut down the IO for the console
+        ProcessBuilder processBuilder = new ProcessBuilder(editor, outFile.getAbsolutePath());
+        processBuilder.redirectInput(ProcessBuilder.Redirect.INHERIT);
+        processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
+        processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);
+        try {
+          Process p = processBuilder.start();
+          // wait for termination.
+          p.waitFor();
+          try (BufferedReader br = new BufferedReader(new FileReader(outFile))) {
+            String ret = IOUtils.toString(br).trim();
+            return ret;
+          }
+        } catch (Exception e) {
+          String message = "Unable to read output: " + e.getMessage();
+          LOG.error(message, e);
+          return null;
+        }
+      } finally {
+        try {
+          PausableInput.INSTANCE.unpause();
+          if(console.isPresent()) {
+            ((Console)console.get()).pushToInputStream("\b\n");
+          }
+        } catch (IOException e) {
+          LOG.error("Unable to unpause: " + e.getMessage(), e);
+        }
+        if(outFile.exists()) {
+          outFile.delete();
+        }
+      }
+    }
+
+    @Override
+    public void initialize(Context context) {
+
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return true;
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/3d5f279c/metron-platform/metron-management/src/test/java/org/apache/metron/management/ShellFunctionsTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-management/src/test/java/org/apache/metron/management/ShellFunctionsTest.java b/metron-platform/metron-management/src/test/java/org/apache/metron/management/ShellFunctionsTest.java
index 92b3550..d05e6ed 100644
--- a/metron-platform/metron-management/src/test/java/org/apache/metron/management/ShellFunctionsTest.java
+++ b/metron-platform/metron-management/src/test/java/org/apache/metron/management/ShellFunctionsTest.java
@@ -162,4 +162,11 @@ public class ShellFunctionsTest {
     Assert.assertNull(out );
   }
 
+  @Test
+  public void testEdit() throws Exception {
+    System.getProperties().put("EDITOR", "/bin/cat");
+    Object out = StellarTest.run("TO_UPPER(SHELL_EDIT(foo))", ImmutableMap.of("foo", "foo"), context);
+    Assert.assertEquals("FOO", out);
+  }
+
 }