Repository: metron
Updated Branches:
  refs/heads/feature/METRON-1136-extensions-parsers 1c63c1eb3 -> 24b668b0b


http://git-wip-us.apache.org/repos/asf/metron/blob/24b668b0/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/SetFunctions.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/SetFunctions.java
 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/SetFunctions.java
new file mode 100644
index 0000000..1b4df1e
--- /dev/null
+++ 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/SetFunctions.java
@@ -0,0 +1,287 @@
+/**
+ * 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.stellar.dsl.functions;
+
+import com.google.common.collect.Iterables;
+import org.apache.metron.stellar.dsl.BaseStellarFunction;
+import org.apache.metron.stellar.dsl.Stellar;
+
+import java.util.*;
+
+public class SetFunctions {
+  @Stellar(name="INIT"
+          , namespace="SET"
+          , description="Creates a new set"
+          , params = { "input? - An initialization of the set"}
+          , returns = "A Set"
+  )
+  public static class SetInit extends BaseStellarFunction {
+    @Override
+    public Object apply(List<Object> list) {
+      LinkedHashSet<Object> ret = new LinkedHashSet<>();
+      if(list.size() == 1) {
+        Object o = list.get(0);
+        if(o != null) {
+          if (o instanceof Iterable) {
+            Iterables.addAll(ret, (Iterable) o);
+          }
+          else {
+            throw new IllegalArgumentException("Expected an Iterable, but " + 
o + " is of type " + o.getClass());
+          }
+        }
+
+      }
+      return ret;
+    }
+  }
+
+  @Stellar(name="ADD"
+          , namespace="SET"
+          , description="Adds to a set"
+          , params = {"set - The set to add to"
+                     ,"o - object to add to set"
+                     }
+          , returns = "A Set"
+  )
+  public static class SetAdd extends BaseStellarFunction {
+    @Override
+    public Object apply(List<Object> list) {
+      if(list.size() < 1) {
+        return null;
+      }
+      LinkedHashSet<Object> ret = (LinkedHashSet<Object>)list.get(0);
+      if(ret == null) {
+        ret = new LinkedHashSet<>();
+      }
+      for(int i = 1;i < list.size();++i) {
+        Object o = list.get(i);
+        if (o != null) {
+          ret.add(o);
+        }
+      }
+      return ret;
+    }
+  }
+
+  @Stellar(name="REMOVE"
+          , namespace="SET"
+          , description="Removes from a set"
+          , params = {"set - The set to add to"
+                     ,"o - object to add to set"
+                     }
+          , returns = "A Set"
+  )
+  public static class SetRemove extends BaseStellarFunction {
+    @Override
+    public Object apply(List<Object> list) {
+      if(list.size() < 1) {
+        return null;
+      }
+      LinkedHashSet<Object> ret = (LinkedHashSet<Object>)list.get(0);
+      if(ret == null) {
+        ret = new LinkedHashSet<>();
+      }
+      for(int i = 1;i < list.size();++i) {
+        Object o = list.get(i);
+        if (o != null) {
+          ret.remove(o);
+        }
+      }
+      return ret;
+    }
+  }
+
+  @Stellar(name="MERGE"
+          , namespace="SET"
+          , description="Merges a list of sets"
+          , params = {"sets - A collection of sets to merge"
+                     }
+          , returns = "A Set"
+  )
+  public static class SetMerge extends BaseStellarFunction {
+    @Override
+    public Object apply(List<Object> list) {
+      if(list.size() < 1) {
+        return null;
+      }
+      LinkedHashSet<Object> ret = new LinkedHashSet<>();
+      if(list.size() > 0) {
+        Object o = list.get(0);
+        if(o != null) {
+          if(!(o instanceof Iterable)) {
+            throw new IllegalArgumentException("Expected an Iterable, but " + 
o + " is of type " + o.getClass());
+          }
+          Iterable<? extends Iterable> sets = (Iterable<? extends Iterable>) o;
+
+          for (Iterable s : sets) {
+            if (s != null) {
+              if(!(s instanceof Iterable)) {
+                throw new IllegalArgumentException("Expected an Iterable, but 
" + s + " is of type " + s.getClass());
+              }
+              Iterables.addAll(ret, s);
+            }
+          }
+        }
+      }
+      return ret;
+    }
+  }
+
+  @Stellar(name="INIT"
+          , namespace="MULTISET"
+          , description="Creates an empty multiset, which is a map associating 
objects to their instance counts."
+          , params = { "input? - An initialization of the multiset"}
+          , returns = "A multiset"
+  )
+  public static class MultiSetInit extends BaseStellarFunction {
+    @Override
+    public Object apply(List<Object> list) {
+      LinkedHashMap<Object, Integer> ret = new LinkedHashMap<>();
+      if (list.size() >= 1) {
+        Object o = list.get(0);
+        if (o != null) {
+          if (!(o instanceof Iterable)) {
+            throw new IllegalArgumentException("Expected an Iterable, but " + 
o + " is of type " + o.getClass());
+          }
+          for (Object obj : (Iterable) o) {
+            ret.merge(obj, 1, (k, one) -> k + one);
+          }
+        }
+      }
+      return ret;
+    }
+  }
+
+  @Stellar(name="ADD"
+          , namespace="MULTISET"
+          , description="Adds to a multiset, which is a map associating 
objects to their instance counts."
+          , params = {"set - The multiset to add to"
+                     ,"o - object to add to multiset"
+                     }
+          , returns = "A multiset"
+  )
+  public static class MultiSetAdd extends BaseStellarFunction {
+    @Override
+    public Object apply(List<Object> list) {
+      if(list.size() < 1) {
+        return null;
+      }
+      LinkedHashMap<Object, Integer> ret = (LinkedHashMap<Object, 
Integer>)list.get(0);
+      if(ret == null) {
+        ret = new LinkedHashMap<>();
+      }
+      for(int i = 1;i < list.size();++i) {
+        Object o = list.get(i);
+        if (o != null) {
+          ret.merge(o, 1, (k, one) -> k + one);
+        }
+      }
+      return ret;
+    }
+  }
+
+  @Stellar(name="REMOVE"
+          , namespace="MULTISET"
+          , description="Removes from a multiset, which is a map associating 
objects to their instance counts."
+          , params = {"set - The multiset to add to"
+                     ,"o - object to remove from multiset"
+                     }
+          , returns = "A multiset"
+  )
+  public static class MultiSetRemove extends BaseStellarFunction {
+    @Override
+    public Object apply(List<Object> list) {
+      if(list.size() < 1) {
+        return null;
+      }
+      LinkedHashMap<Object, Integer> ret = (LinkedHashMap<Object, 
Integer>)list.get(0);
+      if(ret == null) {
+        ret = new LinkedHashMap<>();
+      }
+      for(int i = 1;i < list.size();++i) {
+        Object o = list.get(i);
+        if (o != null) {
+          Integer cnt = ret.get(o);
+          if(cnt == null) {
+            continue;
+          }
+          if(cnt == 1) {
+            ret.remove(o);
+          }
+          else {
+            ret.put(o, cnt - 1);
+          }
+        }
+      }
+      return ret;
+    }
+  }
+
+  @Stellar(name="MERGE"
+          , namespace="MULTISET"
+          , description="Merges a list of multisets, which is a map 
associating objects to their instance counts."
+          , params = {"sets - A collection of multisets to merge"
+                     }
+          , returns = "A multiset"
+  )
+  public static class MultiSetMerge extends BaseStellarFunction {
+    @Override
+    public Object apply(List<Object> list) {
+      if(list.size() < 1) {
+        return null;
+      }
+      LinkedHashMap<Object, Integer> ret = new LinkedHashMap<>();
+      if(list.size() > 0) {
+        Iterable<Map<Object, Integer>> maps = (Iterable<Map<Object, 
Integer>>)list.get(0);
+        for(Map<Object, Integer> s : maps) {
+          if(s != null) {
+            for (Map.Entry<Object, Integer> kv : s.entrySet()) {
+              ret.merge(kv.getKey(), kv.getValue(), (k, cnt) -> k + cnt);
+            }
+          }
+        }
+      }
+      return ret;
+    }
+  }
+
+
+  @Stellar(name="TO_SET"
+          , namespace="MULTISET"
+          , description="Create a set out of a multiset, which is a map 
associating objects to their instance counts."
+          , params = {"multiset - The multiset to convert."
+                     }
+          , returns = "The set of objects in the multiset ignoring 
multiplicity"
+  )
+  public static class MultiSetToSet extends BaseStellarFunction {
+    @Override
+    public Object apply(List<Object> list) {
+      if(list.size() < 1) {
+        return null;
+      }
+      LinkedHashSet<Object> ret = new LinkedHashSet<>();
+      if(list.size() == 1) {
+        Map<Object, Integer> multiset = (Map<Object, Integer>)list.get(0);
+        if(multiset != null) {
+          ret.addAll(multiset.keySet());
+        }
+      }
+      return ret;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/24b668b0/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/StringFunctions.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/StringFunctions.java
 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/StringFunctions.java
index 289fa7f..4dc4790 100644
--- 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/StringFunctions.java
+++ 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/StringFunctions.java
@@ -18,15 +18,20 @@
 
 package org.apache.metron.stellar.dsl.functions;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.core.type.TypeReference;
 import com.google.common.base.Joiner;
 import com.google.common.base.Splitter;
 import com.google.common.collect.Iterables;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.metron.stellar.common.utils.JSONUtils;
 import org.apache.metron.stellar.dsl.BaseStellarFunction;
 import org.apache.metron.stellar.dsl.ParseException;
 import org.apache.metron.stellar.dsl.Stellar;
 import org.apache.metron.stellar.common.utils.ConversionUtils;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -321,6 +326,58 @@ public class StringFunctions {
     }
   }
 
+  @Stellar( name="SUBSTRING"
+          , description = "Returns a substring of a string"
+          , params = {
+                "input - The string to take the substring of",
+                "start - The starting position (0-based and inclusive)",
+                "end? - The ending position (0-based and exclusive)"
+                     }
+          , returns = "The substring of the input"
+  )
+  public static class Substring extends BaseStellarFunction {
+
+    @Override
+    public Object apply(List<Object> strings) {
+
+      if(strings == null || strings.size() < 2 ) {
+        throw new IllegalArgumentException("SUBSTRING requires (at least) 2 
arguments: the input and the start position (inclusive)");
+      }
+      Object varObj = strings.get(0);
+      if(varObj != null && !(varObj instanceof String)) {
+        throw new IllegalArgumentException("SUBSTRING input must be a String");
+      }
+      String var = varObj == null?null: (String) varObj;
+      Object startObj = strings.get(1);
+      if(startObj != null && !(startObj instanceof Number)) {
+        throw new IllegalArgumentException("SUBSTRING start must be an 
Number");
+      }
+      Integer start = startObj == null?null:((Number)startObj).intValue();
+      Integer end = null;
+      if(strings.size() > 2) {
+        Object endObj = strings.get(2);
+        if(endObj != null && !(endObj instanceof Number)) {
+          throw new IllegalArgumentException("SUBSTRING end must be an 
Number");
+        }
+        end = endObj == null ? null : ((Number) endObj).intValue();
+      }
+      if(var == null || start == null) {
+        return null;
+      }
+      else if(var.length() == 0) {
+        return var;
+      }
+      else {
+        if(end == null) {
+          return var.substring(start);
+        }
+        else {
+          return var.substring(start, end);
+        }
+      }
+    }
+  }
+
   @Stellar( name="CHOMP"
           , description = "Removes one newline from end of a String if it's 
there, otherwise leave it alone. A newline is \"\\n\", \"\\r\", or \"\\r\\n\""
           , params = { "the String to chomp a newline from, may be null"}
@@ -450,4 +507,114 @@ public class StringFunctions {
     }
   }
 
+  @Stellar(name = "TO_JSON_OBJECT"
+          , description = "Returns a JSON object for the specified JSON string"
+          , params = {
+            "str - the JSON String to convert, may be null"
+          }
+          , returns = "an Object containing the parsed JSON string"
+  )
+  public static class ToJsonObject extends BaseStellarFunction {
+
+    @Override
+    public Object apply(List<Object> strings) {
+
+      if (strings == null || strings.size() == 0) {
+        throw new IllegalArgumentException("[TO_JSON_OBJECT] incorrect 
arguments. Usage: TO_JSON_OBJECT <String>");
+      }
+      String var = (strings.get(0) == null) ? null : (String) strings.get(0);
+      if (var == null) {
+        return null;
+      } else if (var.length() == 0) {
+        return var;
+      } else {
+        if (!(strings.get(0) instanceof String)) {
+          throw new ParseException("Valid JSON string not supplied");
+        }
+        // Return JSON Object
+        try {
+          return JSONUtils.INSTANCE.load((String) strings.get(0), 
Object.class);
+        } catch (JsonProcessingException ex) {
+          throw new ParseException("Valid JSON string not supplied", ex);
+        } catch (IOException e) {
+          e.printStackTrace();
+        }
+      }
+      return new ParseException("Unable to parse JSON string");
+    }
+  }
+
+  @Stellar(name = "TO_JSON_MAP"
+          , description = "Returns a MAP object for the specified JSON string"
+          , params = {
+          "str - the JSON String to convert, may be null"
+  }
+          , returns = "a MAP object containing the parsed JSON string"
+  )
+  public static class ToJsonMap extends BaseStellarFunction {
+
+    @Override
+    public Object apply(List<Object> strings) {
+
+      if (strings == null || strings.size() == 0) {
+        throw new IllegalArgumentException("[TO_JSON_MAP] incorrect arguments. 
Usage: TO_JSON_MAP <JSON String>");
+      }
+      String var = (strings.get(0) == null) ? null : (String) strings.get(0);
+      if (var == null) {
+        return null;
+      } else if (var.length() == 0) {
+        return var;
+      } else {
+        if (!(strings.get(0) instanceof String)) {
+          throw new ParseException("Valid JSON string not supplied");
+        }
+        // Return parsed JSON Object as a HashMap
+        try {
+          return JSONUtils.INSTANCE.load((String) strings.get(0), new 
TypeReference<Map<String, Object>>(){});
+        } catch (JsonProcessingException ex) {
+          throw new ParseException("Valid JSON string not supplied", ex);
+        } catch (IOException e) {
+          e.printStackTrace();
+        }
+      }
+      return new ParseException("Unable to parse JSON string");
+    }
+  }
+
+  @Stellar(name = "TO_JSON_LIST"
+          , description = "Returns a List object for the specified JSON string"
+          , params = {
+          "str - the JSON String to convert, may be null"
+  }
+          , returns = "a List object containing the parsed JSON string"
+  )
+  public static class ToJsonList extends BaseStellarFunction {
+
+    @Override
+    public Object apply(List<Object> strings) {
+
+      if (strings == null || strings.size() == 0) {
+        throw new IllegalArgumentException("[TO_JSON_LIST] incorrect 
arguments. Usage: TO_JSON_LIST <JSON String>");
+      }
+      String var = (strings.get(0) == null) ? null : (String) strings.get(0);
+      if (var == null) {
+        return null;
+      } else if (var.length() == 0) {
+        return var;
+      } else {
+        if (!(strings.get(0) instanceof String)) {
+          throw new ParseException("Valid JSON string not supplied");
+        }
+        // Return parsed JSON Object as a List
+        try {
+          return (List) JSONUtils.INSTANCE.load((String) strings.get(0), new 
TypeReference<List<Object>>(){});
+        } catch (JsonProcessingException ex) {
+          throw new ParseException("Valid JSON string not supplied", ex);
+        } catch (IOException e) {
+          e.printStackTrace();
+          throw new ParseException("Valid JSON string not supplied", e);
+        }
+      }
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/24b668b0/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/BasicStellarTest.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/BasicStellarTest.java
 
b/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/BasicStellarTest.java
index d6c3713..af86902 100644
--- 
a/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/BasicStellarTest.java
+++ 
b/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/BasicStellarTest.java
@@ -625,6 +625,18 @@ public class BasicStellarTest {
   }
 
   @Test
+
+  public void testShortCircuit_mixedBoolOps() throws Exception {
+    final Map<String, String> variableMap = new HashMap<String, String>();
+    Assert.assertTrue(runPredicate("(false && true) || true"
+            , new DefaultVariableResolver(v -> variableMap.get(v),v -> 
variableMap.containsKey(v))));
+    Assert.assertTrue(runPredicate("(false && false) || true"
+            , new DefaultVariableResolver(v -> variableMap.get(v),v -> 
variableMap.containsKey(v))));
+    Assert.assertFalse(runPredicate("(true || true) && false"
+            , new DefaultVariableResolver(v -> variableMap.get(v),v -> 
variableMap.containsKey(v))));
+  }
+
+  @Test
   public void testInString() throws Exception {
     final Map<String, String> variableMap = new HashMap<String, String>() {{
       put("foo", "casey");

http://git-wip-us.apache.org/repos/asf/metron/blob/24b668b0/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/SetFunctionsTest.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/SetFunctionsTest.java
 
b/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/SetFunctionsTest.java
new file mode 100644
index 0000000..1aa8eb4
--- /dev/null
+++ 
b/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/SetFunctionsTest.java
@@ -0,0 +1,326 @@
+/**
+ * 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.stellar.dsl.functions;
+
+import org.apache.metron.stellar.common.utils.StellarProcessorUtils;
+import org.apache.metron.stellar.dsl.ParseException;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class SetFunctionsTest {
+
+  @Test(expected=ParseException.class)
+  public void multisetInitTest_wrongType() throws Exception {
+    Map<Object, Integer> s = (Map<Object, Integer>) 
StellarProcessorUtils.run("MULTISET_INIT({ 'foo' : 'bar'})", new HashMap<>());
+  }
+
+  @Test
+  public void multisetInitTest() throws Exception {
+    {
+      Map<Object, Integer> s = (Map<Object, Integer>) 
StellarProcessorUtils.run("MULTISET_INIT()", new HashMap<>());
+      Assert.assertEquals(0, s.size());
+    }
+    //int initialization
+    {
+      Map<Object, Integer> s = (Map<Object, Integer>) 
StellarProcessorUtils.run("MULTISET_INIT([1,2,3,2])", new HashMap<>());
+      Assert.assertEquals(3, s.size());
+      Assert.assertTrue(s.containsKey(1));
+      Assert.assertEquals(1,(int)s.get(1));
+      Assert.assertTrue(s.containsKey(2));
+      Assert.assertEquals(2,(int)s.get(2));
+      Assert.assertTrue(s.containsKey(3));
+      Assert.assertEquals(1,(int)s.get(3));
+    }
+    //string initialization
+    {
+      Map<Object, Integer> s = (Map<Object, Integer>) 
StellarProcessorUtils.run("MULTISET_INIT(['one','two','three','two'])", new 
HashMap<>());
+      Assert.assertEquals(3, s.size());
+      Assert.assertTrue(s.containsKey("one"));
+      Assert.assertEquals(1,(int)s.get("one"));
+      Assert.assertTrue(s.containsKey("two"));
+      Assert.assertEquals(2,(int)s.get("two"));
+      Assert.assertTrue(s.containsKey("three"));
+      Assert.assertEquals(1,(int)s.get("three"));
+
+    }
+  }
+
+  @Test
+  public void multisetAddTest() throws Exception {
+    {
+      Map<Object, Integer> s = (Map<Object, Integer>) 
StellarProcessorUtils.run("MULTISET_ADD(MULTISET_INIT(), 1)", new HashMap<>());
+      Assert.assertEquals(1, s.size());
+      Assert.assertTrue(s.containsKey(1));
+      Assert.assertEquals(1,(int)s.get(1));
+    }
+    {
+      Map<Object, Integer> s = (Map<Object, Integer>) 
StellarProcessorUtils.run("MULTISET_ADD(null, 1)", new HashMap<>());
+      Assert.assertEquals(1, s.size());
+      Assert.assertTrue(s.containsKey(1));
+      Assert.assertEquals(1,(int)s.get(1));
+    }
+    //int
+    {
+      Map<Object, Integer> s = (Map<Object, Integer>) 
StellarProcessorUtils.run("MULTISET_ADD(MULTISET_INIT([1,2,3,4,4]), 4)", new 
HashMap<>());
+      Assert.assertEquals(4, s.size());
+      Assert.assertTrue(s.containsKey(1));
+      Assert.assertEquals(1,(int)s.get(1));
+      Assert.assertTrue(s.containsKey(2));
+      Assert.assertEquals(1,(int)s.get(2));
+      Assert.assertTrue(s.containsKey(3));
+      Assert.assertEquals(1,(int)s.get(3));
+      Assert.assertTrue(s.containsKey(4));
+      Assert.assertEquals(3,(int)s.get(4));
+    }
+    //string
+    {
+      Map<Object, Integer> s = (Map<Object, Integer>) 
StellarProcessorUtils.run("MULTISET_ADD(MULTISET_INIT(['one','two','three', 
'four', 'four']), 'four')", new HashMap<>());
+      Assert.assertEquals(4, s.size());
+      Assert.assertTrue(s.containsKey("one"));
+      Assert.assertEquals(1,(int)s.get("one"));
+      Assert.assertTrue(s.containsKey("two"));
+      Assert.assertEquals(1,(int)s.get("two"));
+      Assert.assertTrue(s.containsKey("three"));
+      Assert.assertEquals(1,(int)s.get("three"));
+      Assert.assertTrue(s.containsKey("four"));
+      Assert.assertEquals(3,(int)s.get("four"));
+    }
+  }
+@Test
+  public void multisetRemoveTest() throws Exception {
+    {
+      Map<Object, Integer> s = (Map<Object, Integer>) 
StellarProcessorUtils.run("MULTISET_REMOVE(MULTISET_INIT([1]), 1)", new 
HashMap<>());
+      Assert.assertEquals(0, s.size());
+    }
+    {
+      Map<Object, Integer> s = (Map<Object, Integer>) 
StellarProcessorUtils.run("MULTISET_REMOVE(null, 1)", new HashMap<>());
+      Assert.assertEquals(0, s.size());
+    }
+    //int
+    {
+      Map<Object, Integer> s = (Map<Object, Integer>) 
StellarProcessorUtils.run("MULTISET_REMOVE(MULTISET_INIT([1,2,3,2]), 2)", new 
HashMap<>());
+      Assert.assertEquals(3, s.size());
+      Assert.assertTrue(s.containsKey(1));
+      Assert.assertEquals(1, (int)s.get(1));
+      Assert.assertTrue(s.containsKey(2));
+      Assert.assertEquals(1, (int)s.get(2));
+      Assert.assertTrue(s.containsKey(3));
+      Assert.assertEquals(1, (int)s.get(3));
+    }
+    //string
+    {
+      Map<Object, Integer> s = (Map<Object, Integer>) 
StellarProcessorUtils.run("MULTISET_REMOVE(MULTISET_INIT(['one','two','three', 
'two']), 'two')", new HashMap<>());
+      Assert.assertEquals(3, s.size());
+      Assert.assertTrue(s.containsKey("one"));
+      Assert.assertEquals(1, (int)s.get("one"));
+      Assert.assertTrue(s.containsKey("two"));
+      Assert.assertEquals(1, (int)s.get("two"));
+      Assert.assertTrue(s.containsKey("three"));
+      Assert.assertEquals(1, (int)s.get("three"));
+    }
+  }
+
+  @Test(expected=ParseException.class)
+  public void multisetMergeTest_wrongType() throws Exception {
+
+    Map<Object, Integer> s = (Map<Object, Integer>) 
StellarProcessorUtils.run("MULTISET_MERGE({ 'bar' : 'foo' } )", new 
HashMap<>());
+  }
+
+  @Test
+  public void multisetMergeTest() throws Exception {
+    {
+      Map<Object, Integer> s = (Map<Object, Integer>) 
StellarProcessorUtils.run("MULTISET_MERGE([MULTISET_INIT(), 
MULTISET_INIT(null), null])", new HashMap<>());
+      Assert.assertEquals(0, s.size());
+    }
+    //int
+    {
+      Map<Object, Integer> s = (Map<Object, Integer>) 
StellarProcessorUtils.run("MULTISET_MERGE([MULTISET_INIT([1,2]), 
MULTISET_INIT([2,3]), null, MULTISET_INIT()])", new HashMap<>());
+      Assert.assertEquals(3, s.size());
+      Assert.assertTrue(s.containsKey(1));
+      Assert.assertEquals(1, (int)s.get(1));
+      Assert.assertTrue(s.containsKey(2));
+      Assert.assertEquals(2, (int)s.get(2));
+      Assert.assertTrue(s.containsKey(3));
+      Assert.assertEquals(1, (int)s.get(3));
+    }
+    //string
+    {
+      Map<Object, Integer> s = (Map<Object, 
Integer>)StellarProcessorUtils.run("MULTISET_MERGE([MULTISET_INIT(['one','two']),
 MULTISET_INIT(['two', 'three'])])", new HashMap<>());
+      Assert.assertEquals(3, s.size());
+      Assert.assertTrue(s.containsKey("one"));
+      Assert.assertEquals(1, (int)s.get("one"));
+      Assert.assertTrue(s.containsKey("two"));
+      Assert.assertEquals(2, (int)s.get("two"));
+      Assert.assertTrue(s.containsKey("three"));
+      Assert.assertEquals(1, (int)s.get("three"));
+    }
+  }
+
+  @Test(expected=ParseException.class)
+  public void setInitTest_wrongType() throws Exception {
+      Set s = (Set) StellarProcessorUtils.run("SET_INIT({ 'foo' : 2})", new 
HashMap<>());
+  }
+
+  @Test
+  public void setInitTest() throws Exception {
+    {
+      Set s = (Set) StellarProcessorUtils.run("SET_INIT()", new HashMap<>());
+      Assert.assertEquals(0, s.size());
+    }
+    //int initialization
+    {
+      Set s = (Set) StellarProcessorUtils.run("SET_INIT([1,2,3])", new 
HashMap<>());
+      Assert.assertEquals(3, s.size());
+      Assert.assertTrue(s.contains(1));
+      Assert.assertTrue(s.contains(2));
+      Assert.assertTrue(s.contains(3));
+    }
+    //string initialization
+    {
+      Set s = (Set) 
StellarProcessorUtils.run("SET_INIT(['one','two','three'])", new HashMap<>());
+      Assert.assertEquals(3, s.size());
+      Assert.assertTrue(s.contains("one"));
+      Assert.assertTrue(s.contains("two"));
+      Assert.assertTrue(s.contains("three"));
+    }
+  }
+
+  @Test
+  public void multisetToSetTest() throws Exception {
+    {
+      Set s = (Set) 
StellarProcessorUtils.run("MULTISET_TO_SET(MULTISET_ADD(MULTISET_INIT(), 1))", 
new HashMap<>());
+      Assert.assertEquals(1, s.size());
+      Assert.assertTrue(s.contains(1));
+    }
+    {
+      Set s = (Set) 
StellarProcessorUtils.run("MULTISET_TO_SET(MULTISET_ADD(null, 1))", new 
HashMap<>());
+      Assert.assertEquals(1, s.size());
+      Assert.assertTrue(s.contains(1));
+    }
+    //int
+    {
+      Set s = (Set) 
StellarProcessorUtils.run("MULTISET_TO_SET(MULTISET_ADD(MULTISET_INIT([1,2,3]), 
4))", new HashMap<>());
+      Assert.assertEquals(4, s.size());
+      Assert.assertTrue(s.contains(1));
+      Assert.assertTrue(s.contains(2));
+      Assert.assertTrue(s.contains(3));
+      Assert.assertTrue(s.contains(4));
+    }
+    //string
+    {
+      Set s = (Set) 
StellarProcessorUtils.run("MULTISET_TO_SET(MULTISET_ADD(MULTISET_INIT(['one','two','three']),
 'four'))", new HashMap<>());
+      Assert.assertEquals(4, s.size());
+      Assert.assertTrue(s.contains("one"));
+      Assert.assertTrue(s.contains("two"));
+      Assert.assertTrue(s.contains("three"));
+      Assert.assertTrue(s.contains("four"));
+    }
+  }
+
+  @Test
+  public void setAddTest() throws Exception {
+    {
+      Set s = (Set) StellarProcessorUtils.run("SET_ADD(SET_INIT(), 1)", new 
HashMap<>());
+      Assert.assertEquals(1, s.size());
+      Assert.assertTrue(s.contains(1));
+    }
+    {
+      Set s = (Set) StellarProcessorUtils.run("SET_ADD(null, 1)", new 
HashMap<>());
+      Assert.assertEquals(1, s.size());
+      Assert.assertTrue(s.contains(1));
+    }
+    //int
+    {
+      Set s = (Set) StellarProcessorUtils.run("SET_ADD(SET_INIT([1,2,3]), 4)", 
new HashMap<>());
+      Assert.assertEquals(4, s.size());
+      Assert.assertTrue(s.contains(1));
+      Assert.assertTrue(s.contains(2));
+      Assert.assertTrue(s.contains(3));
+      Assert.assertTrue(s.contains(4));
+    }
+    //string
+    {
+      Set s = (Set) 
StellarProcessorUtils.run("SET_ADD(SET_INIT(['one','two','three']), 'four')", 
new HashMap<>());
+      Assert.assertEquals(4, s.size());
+      Assert.assertTrue(s.contains("one"));
+      Assert.assertTrue(s.contains("two"));
+      Assert.assertTrue(s.contains("three"));
+      Assert.assertTrue(s.contains("four"));
+    }
+  }
+
+  @Test
+  public void setRemoveTest() throws Exception {
+    {
+      Set s = (Set) StellarProcessorUtils.run("SET_REMOVE(SET_INIT([1]), 1)", 
new HashMap<>());
+      Assert.assertEquals(0, s.size());
+    }
+    {
+      Set s = (Set) StellarProcessorUtils.run("SET_REMOVE(null, 1)", new 
HashMap<>());
+      Assert.assertEquals(0, s.size());
+    }
+    //int
+    {
+      Set s = (Set) StellarProcessorUtils.run("SET_REMOVE(SET_INIT([1,2,3]), 
2)", new HashMap<>());
+      Assert.assertEquals(2, s.size());
+      Assert.assertTrue(s.contains(1));
+      Assert.assertTrue(s.contains(3));
+    }
+    //string
+    {
+      Set s = (Set) 
StellarProcessorUtils.run("SET_REMOVE(SET_INIT(['one','two','three']), 
'three')", new HashMap<>());
+      Assert.assertEquals(2, s.size());
+      Assert.assertTrue(s.contains("one"));
+      Assert.assertTrue(s.contains("two"));
+    }
+  }
+
+  @Test(expected=ParseException.class)
+  public void setMergeTest_wrongType() throws Exception {
+    Set s = (Set) StellarProcessorUtils.run("SET_MERGE({ 'foo' : 'bar'} )", 
new HashMap<>());
+  }
+
+  @Test
+  public void setMergeTest() throws Exception {
+    {
+      Set s = (Set) StellarProcessorUtils.run("SET_MERGE([SET_INIT(), 
SET_INIT(null), null])", new HashMap<>());
+      Assert.assertEquals(0, s.size());
+    }
+    //int
+    {
+      Set s = (Set) StellarProcessorUtils.run("SET_MERGE([SET_INIT([1,2]), 
SET_INIT([3]), null, SET_INIT()])", new HashMap<>());
+      Assert.assertEquals(3, s.size());
+      Assert.assertTrue(s.contains(1));
+      Assert.assertTrue(s.contains(2));
+      Assert.assertTrue(s.contains(3));
+    }
+    //string
+    {
+      Set s = (Set) 
StellarProcessorUtils.run("SET_MERGE([SET_INIT(['one','two']), 
SET_INIT(['three'])])", new HashMap<>());
+      Assert.assertEquals(3, s.size());
+      Assert.assertTrue(s.contains("one"));
+      Assert.assertTrue(s.contains("two"));
+      Assert.assertTrue(s.contains("three"));
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/24b668b0/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/StringFunctionsTest.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/StringFunctionsTest.java
 
b/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/StringFunctionsTest.java
index 858a043..418bf2d 100644
--- 
a/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/StringFunctionsTest.java
+++ 
b/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/StringFunctionsTest.java
@@ -20,15 +20,18 @@ package org.apache.metron.stellar.dsl.functions;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import org.adrianwalker.multilinestring.Multiline;
 import org.apache.commons.collections4.map.HashedMap;
 import org.apache.metron.stellar.dsl.DefaultVariableResolver;
 import org.apache.metron.stellar.dsl.ParseException;
 import org.junit.Assert;
 import org.junit.Test;
 
+import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import static org.apache.metron.stellar.common.utils.StellarProcessorUtils.run;
@@ -408,6 +411,41 @@ public class StringFunctionsTest {
 
   }
 
+  @Test
+  public void testSubstring() throws Exception {
+    Map<String, Object> variables = ImmutableMap.of("s", "apache metron");
+    Assert.assertEquals("metron", run("SUBSTRING(s, 7)", variables));
+    Assert.assertEquals("me", run("SUBSTRING(s, 7, 9)", variables));
+    Assert.assertNull(run("SUBSTRING(null, 7, 9)", new HashMap<>()));
+    Assert.assertNull(run("SUBSTRING(null, null, 9)", new HashMap<>()));
+    Assert.assertNull(run("SUBSTRING(s, null, 9)", variables));
+    Assert.assertNull(run("SUBSTRING(null, null, null)", new HashMap<>()));
+    Assert.assertEquals("metron", run("SUBSTRING(s, 7, null)", variables));
+  }
+
+  @Test(expected=ParseException.class)
+  public void testSubstring_invalidEmpty() throws Exception {
+    Assert.assertEquals("metron", run("SUBSTRING()", new HashMap<>()));
+  }
+
+  @Test(expected=ParseException.class)
+  public void testSubstring_invalidWrongTypeStart() throws Exception {
+    Map<String, Object> variables = ImmutableMap.of("s", "apache metron");
+    Assert.assertEquals("metron", (String) run("SUBSTRING(s, '7')", 
variables));
+  }
+
+  @Test(expected=ParseException.class)
+  public void testSubstring_invalidWrongTypeEnd() throws Exception {
+    Map<String, Object> variables = ImmutableMap.of("s", "apache metron");
+    Assert.assertEquals("metron", (String) run("SUBSTRING(s, 7, '9')", 
variables));
+  }
+
+  @Test(expected=ParseException.class)
+  public void testSubstring_invalidWrongTypeInput() throws Exception {
+    Map<String, Object> variables = ImmutableMap.of("s", 7);
+    Assert.assertEquals("metron", (String) run("SUBSTRING(s, 7, '9')", 
variables));
+  }
+
   /**
    * COUNT_MATCHES StringFunction
    */
@@ -449,4 +487,262 @@ public class StringFunctionsTest {
     Assert.assertTrue(thrown);
 
   }
+
+  /**
+   * TO_JSON_OBJECT StringFunction
+   */
+
+  // Input strings to be used
+  /**
+   { "foo" : 2 }
+   */
+  @Multiline
+  private String string1;
+
+  /**
+   {
+     "foo" : "abc",
+     "bar" : "def"
+   }
+   */
+  @Multiline
+  private String string2;
+
+  /**
+   [ "foo", 2 ]
+   */
+  @Multiline
+  private String string3;
+
+  /**
+   [ "foo", "bar", "car" ]
+   */
+  @Multiline
+  private String string4;
+
+  /**
+   [
+     {
+       "foo1":"abc",
+       "bar1":"def"
+     },
+     {
+       "foo2":"ghi",
+       "bar2":"jkl"
+     }
+   ]
+   */
+  @Multiline
+  private String string5;
+
+  @Test
+  public void testToJsonObject() throws Exception {
+    //JSON Object
+    Object ret1 = run("TO_JSON_OBJECT(msg)", ImmutableMap.of("msg", string1));
+    Assert.assertNotNull(ret1);
+    Assert.assertTrue (ret1 instanceof HashMap);
+
+    Object ret2 = run("TO_JSON_OBJECT(msg)", ImmutableMap.of("msg", string2));
+    Assert.assertNotNull(ret2);
+    Assert.assertTrue (ret2 instanceof HashMap);
+    Assert.assertEquals("def", run("MAP_GET( 'bar', returnval)", 
ImmutableMap.of("returnval", ret2)));
+
+    //Simple Arrays
+    Object ret3 = run("TO_JSON_OBJECT(msg)", ImmutableMap.of("msg", string3));
+    Assert.assertNotNull(ret3);
+    Assert.assertTrue (ret3 instanceof ArrayList);
+    List<Object> result3 = (List<Object>) ret3;
+    Assert.assertEquals(2, result3.get(1));
+
+    Object ret4 = run("TO_JSON_OBJECT(msg)", ImmutableMap.of("msg", string4));
+    Assert.assertNotNull(ret4);
+    Assert.assertTrue (ret4 instanceof ArrayList);
+    List<Object> result4 = (List<Object>) ret4;
+    Assert.assertEquals("car", result4.get(2));
+
+    //JSON Array
+    Object ret5 = run( "TO_JSON_OBJECT(msg)", ImmutableMap.of("msg", string5));
+    Assert.assertNotNull(ret5);
+    Assert.assertTrue (ret5 instanceof ArrayList);
+    List<List<Object>> result5 = (List<List<Object>>) ret5;
+    HashMap<String,String> results5Map1 = (HashMap) result5.get(0);
+    Assert.assertEquals("def", results5Map1.get("bar1"));
+    HashMap<String,String> results5Map2 = (HashMap) result5.get(1);
+    Assert.assertEquals("ghi", results5Map2.get("foo2"));
+
+    // No input
+    boolean thrown = false;
+    try {
+      run("TO_JSON_OBJECT()", Collections.emptyMap());
+    } catch (ParseException pe) {
+      thrown = true;
+      Assert.assertTrue(pe.getMessage().contains("Unable to parse"));
+    }
+    Assert.assertTrue(thrown);
+    thrown = false;
+
+    // Invalid input
+    try {
+      run("TO_JSON_OBJECT('123, 456')", new HashedMap<>());
+    } catch (ParseException pe) {
+      thrown = true;
+      Assert.assertTrue(pe.getMessage().contains("Valid JSON string not 
supplied"));
+    }
+    Assert.assertTrue(thrown);
+    thrown = false;
+
+    // Malformed JSON String
+    try {
+      run("TO_JSON_OBJECT('{\"foo\" : 2')", new HashedMap<>());
+    } catch (ParseException pe) {
+      thrown = true;
+      Assert.assertTrue(pe.getMessage().contains("Valid JSON string not 
supplied"));
+    }
+    Assert.assertTrue(thrown);
+    thrown = false;
+  }
+
+  @Test
+  public void testToJsonMap() throws Exception {
+    //JSON Object
+    Object ret1 = run("TO_JSON_MAP(msg)", ImmutableMap.of("msg", string1));
+    Assert.assertNotNull(ret1);
+    Assert.assertTrue (ret1 instanceof HashMap);
+
+    Object ret2 = run("TO_JSON_MAP(msg)", ImmutableMap.of("msg", string2));
+    Assert.assertNotNull(ret2);
+    Assert.assertTrue (ret2 instanceof HashMap);
+    Assert.assertEquals("def", run("MAP_GET( 'bar', returnval)", 
ImmutableMap.of("returnval", ret2)));
+
+    //Simple Arrays
+    boolean thrown = false;
+    try {
+      run("TO_JSON_MAP(msg)", ImmutableMap.of("msg", string3));
+    } catch (ParseException pe) {
+      thrown = true;
+    }
+    Assert.assertTrue(thrown);
+
+    thrown = false;
+    try {
+      run("TO_JSON_MAP(msg)", ImmutableMap.of("msg", string4));
+    } catch (ParseException pe) {
+      thrown = true;
+    }
+    Assert.assertTrue (thrown);
+
+    //JSON Array
+    thrown = false;
+    try {
+      run("TO_JSON_MAP(msg)", ImmutableMap.of("msg", string5));
+    } catch (ParseException pe) {
+      thrown = true;
+    }
+    Assert.assertTrue(thrown);
+
+
+    // No input
+    try {
+      run("TO_JSON_MAP()", Collections.emptyMap());
+    } catch (ParseException pe) {
+      thrown = true;
+      Assert.assertTrue(pe.getMessage().contains("Unable to parse"));
+    }
+    Assert.assertTrue(thrown);
+    thrown = false;
+
+    // Invalid input
+    try {
+      run("TO_JSON_MAP('123, 456')", new HashedMap<>());
+    } catch (ParseException pe) {
+      thrown = true;
+      Assert.assertTrue(pe.getMessage().contains("Valid JSON string not 
supplied"));
+    }
+    Assert.assertTrue(thrown);
+    thrown = false;
+
+    // Malformed JSON String
+    try {
+      run("TO_JSON_MAP('{\"foo\" : 2')", new HashedMap<>());
+    } catch (ParseException pe) {
+      thrown = true;
+      Assert.assertTrue(pe.getMessage().contains("Valid JSON string not 
supplied"));
+    }
+    Assert.assertTrue(thrown);
+    thrown = false;
+  }
+
+  @Test
+  public void testToJsonList() throws Exception {
+    //Simple Arrays
+    Object ret3 = run("TO_JSON_LIST(msg)", ImmutableMap.of("msg", string3));
+    Assert.assertNotNull(ret3);
+    Assert.assertTrue (ret3 instanceof ArrayList);
+    List<Object> result3 = (List<Object>) ret3;
+    Assert.assertEquals(2, result3.get(1));
+
+    Object ret4 = run("TO_JSON_LIST(msg)", ImmutableMap.of("msg", string4));
+    Assert.assertNotNull(ret4);
+    Assert.assertTrue (ret4 instanceof ArrayList);
+    List<Object> result4 = (List<Object>) ret4;
+    Assert.assertEquals("car", result4.get(2));
+
+    //JSON Array
+    Object ret5 = run( "TO_JSON_LIST(msg)", ImmutableMap.of("msg", string5));
+    Assert.assertNotNull(ret5);
+    Assert.assertTrue (ret5 instanceof ArrayList);
+    List<List<Object>> result5 = (List<List<Object>>) ret5;
+    HashMap<String,String> results5Map1 = (HashMap) result5.get(0);
+    Assert.assertEquals("def", results5Map1.get("bar1"));
+    HashMap<String,String> results5Map2 = (HashMap) result5.get(1);
+    Assert.assertEquals("ghi", results5Map2.get("foo2"));
+
+    //JSON Object - throws exception
+    boolean thrown = false;
+    try {
+      run("TO_JSON_LIST(msg)", ImmutableMap.of("msg", string1));
+    } catch (ParseException pe) {
+      thrown = true;
+    }
+    Assert.assertTrue(thrown);
+
+    thrown = false;
+    try {
+      run("TO_JSON_LIST(msg)", ImmutableMap.of("msg", string2));
+    } catch (ParseException pe) {
+      thrown = true;
+    }
+    Assert.assertTrue (thrown);
+
+    // No input
+    thrown = false;
+    try {
+      run("TO_JSON_LIST()", Collections.emptyMap());
+    } catch (ParseException pe) {
+      thrown = true;
+      Assert.assertTrue(pe.getMessage().contains("Unable to parse"));
+    }
+    Assert.assertTrue(thrown);
+
+    // Invalid input
+    thrown = false;
+    try {
+      run("TO_JSON_LIST('123, 456')", new HashedMap<>());
+    } catch (ParseException pe) {
+      thrown = true;
+      Assert.assertTrue(pe.getMessage().contains("Valid JSON string not 
supplied"));
+    }
+    Assert.assertTrue(thrown);
+
+    // Malformed JSON String
+    thrown = false;
+    try {
+      run("TO_JSON_LIST('{\"foo\" : 2')", new HashedMap<>());
+    } catch (ParseException pe) {
+      thrown = true;
+      Assert.assertTrue(pe.getMessage().contains("Valid JSON string not 
supplied"));
+    }
+    Assert.assertTrue(thrown);
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/24b668b0/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 9a86314..dcadfe8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -344,8 +344,12 @@
                         <!-- ACE editor assets are covered in the 
metron-config NOTICE file -->
                         <exclude>**/src/assets/ace/**</exclude>
                         <exclude>dist/assets/ace/**</exclude>
-                        <!-- Generated svg containing Font Awesome fonts are 
covered in the metron-config README and NOTICE file -->
+                        <!-- Generated svg and bundle.css containing Font 
Awesome fonts are covered in the
+                             metron-interface/metron-config README and NOTICE 
files -->
                         <exclude>dist/*.svg</exclude>
+                        
<exclude>dist/styles.a0b6b99c10d9a13dc67e.bundle.css</exclude>
+                        <!-- 3rdpartylicenses.txt is an empty file carried 
along by imported libraries -->
+                        <exclude>dist/3rdpartylicenses.txt</exclude>
 
                         <exclude>e2e/*.js.map</exclude>
                         <!-- Checkstyle is LGPL.  We derive ours from their 
base, but don't ship it, so it's fine use.

http://git-wip-us.apache.org/repos/asf/metron/blob/24b668b0/use-cases/README.md
----------------------------------------------------------------------
diff --git a/use-cases/README.md b/use-cases/README.md
new file mode 100644
index 0000000..02be32d
--- /dev/null
+++ b/use-cases/README.md
@@ -0,0 +1,4 @@
+# Worked Examples
+
+The following are worked examples of use-cases that showcase some (or
+many) component(s) of Metron.

http://git-wip-us.apache.org/repos/asf/metron/blob/24b668b0/use-cases/geographic_login_outliers/README.md
----------------------------------------------------------------------
diff --git a/use-cases/geographic_login_outliers/README.md 
b/use-cases/geographic_login_outliers/README.md
new file mode 100644
index 0000000..99e9a5b
--- /dev/null
+++ b/use-cases/geographic_login_outliers/README.md
@@ -0,0 +1,267 @@
+# Problem Statement
+
+One way to find anomalous behavior in a network is by inspecting user
+login behavior.  In particular, if a user is logging in via vastly
+differing geographic locations in a short period of time, this may be
+evidence of malicious behavior.
+
+More formally, we can encode this potentially malicious event in terms
+of how far from the geographic centroid of the user's historic logins
+as compared to all users.  For instance, if we track all users and the
+median distance from the central geographic location of all of their
+logins for the last 2 hours is 3 km and the standard deviation is 1 km,
+if we see a user logging in 1700 km from the central geographic location of
+their logins for the last 2 hours, then they MAY be exhibiting a
+deviation that we want to monitor since it would be hard to travel that
+distance in 4 hours.  On the other hand, the user may have
+just used a VPN or proxy.  Ultimately, this sort of analytic must be
+considered only one piece of evidence in addition to many others before
+we want to indicate an alert.
+
+# Demonstration Design
+For the purposes of demonstration, we will construct synthetic data
+whereby 2 users are logging into a system rather quickly (once per
+second) from various hosts.  Each user's locations share the same first
+2 octets, but will choose the last 2 randomly.  We will then inject a
+data point indicating `user1` is logging in via a russian IP address.
+
+## Preliminaries
+We assume that the following environment variables are set:
+* `METRON_HOME` - the home directory for metron
+* `ZOOKEEPER` - The zookeeper quorum (comma separated with port specified: 
e.g. `node1:2181` for full-dev)
+* `BROKERLIST` - The Kafka broker list (comma separated with port specified: 
e.g. `node1:6667` for full-dev)
+* `ES_HOST` - The elasticsearch master (and port) e.g. `node1:9200` for 
full-dev.
+
+Also, this does not assume that you are using a kerberized cluster.  If you 
are, then the parser start command will adjust slightly to include the security 
protocol.
+
+Before editing configurations, be sure to pull the configs from zookeeper 
locally via
+```
+$METRON_HOME/bin/zk_load_configs.sh --mode PULL -z $ZOOKEEPER -o 
$METRON_HOME/config/zookeeper/ -f
+```
+
+## Configure the Profiler
+First, we'll configure the profiler to emit a profiler every 1 minute:
+* In Ambari, set the profiler period duration to `1` minute via the Profiler 
config section.
+* Adjust `$METRON_HOME/config/zookeeper/global.json` to adjust the capture 
duration:
+```
+ "profiler.client.period.duration" : "1",
+ "profiler.client.period.duration.units" : "MINUTES"
+```
+
+## Create the Data Generator
+We want to create a new sensor for our synthetic data called `auth`.  To
+feed it, we need a synthetic data generator.  In particular, we want a
+process which will feed authentication events per second for a set of
+users where the IPs are randomly chosen, but each user's login ip
+addresses share the same first 2 octets.
+
+Edit `~/gen_data.py` and paste the following into it:
+```
+#!/usr/bin/python
+
+import random
+import sys
+import time
+
+domains = { 'user1' : '173.90', 'user2' : '156.33' }
+
+def get_ip(base):
+  return base + '.' + str(random.randint(1,255)) + '.' + str(random.randint(1, 
255))
+
+def main():
+  freq_s = 1
+  while True:
+    user='user' + str(random.randint(1,len(domains)))
+    epoch_time = int(time.time())
+    ip=get_ip(domains[user])
+    print user + ',' + ip + ',' + str(epoch_time)
+    sys.stdout.flush()
+    time.sleep(freq_s)
+
+if __name__ == '__main__':
+  main()
+```
+
+## Create the `auth` Parser
+
+The message format for our simple synthetic data is a CSV with:
+* username
+* login ip address
+* timestamp
+
+We will need to parse this via our `CSVParser` and add the geohash of the 
login ip address.
+
+* To create this parser, edit 
`$METRON_HOME/config/zookeeper/parsers/auth.json` and paste the following:
+```
+{
+  "parserClassName" : "org.apache.metron.parsers.csv.CSVParser"
+ ,"sensorTopic" : "auth"
+ ,"parserConfig" : {
+    "columns" : {
+      "user" : 0,
+      "ip" : 1,
+      "timestamp" : 2
+                }
+                   }
+ ,"fieldTransformations" : [
+    {
+    "transformation" : "STELLAR"
+   ,"output" : [ "hash" ]
+   ,"config" : {
+      "hash" : "GEOHASH_FROM_LOC(GEO_GET(ip))"
+               }
+    }
+                           ]
+}
+```
+* Create the kafka topic via:
+```
+/usr/hdp/current/kafka-broker/bin/kafka-topics.sh --zookeeper $ZOOKEEPER 
--create --topic auth --partitions 1 --replication-factor 1
+```
+
+## Create the Profiles for Enrichment
+
+We will need to track 2 profiles to accomplish this task:
+* `locations_by_user` - The geohashes of the locations the user has logged in 
from.  This is a multiset of geohashes per user.  Note that the multiset in 
this case is effectively a map of geohashes to occurrance counts.
+* `geo_distribution_from_centroid` - The statistical distribution of the 
distance between a login location and the geographic centroid of the user's 
previous logins from the last 2 minutes. Note, in a real installation this 
would be a larger temporal lookback.
+
+We can represent these in the `$METRON_HOME/config/zookeeper/profiler.json` 
via the following:
+```
+{
+  "profiles": [
+    {
+      "profile": "geo_distribution_from_centroid",
+      "foreach": "'global'",
+      "onlyif": "exists(geo_distance) && geo_distance != null",
+      "init" : {
+        "s": "STATS_INIT()"
+               },
+      "update": {
+        "s": "STATS_ADD(s, geo_distance)"
+                },
+      "result": "s"
+    },
+    {
+      "profile": "locations_by_user",
+      "foreach": "user",
+      "onlyif": "exists(hash) && hash != null && LENGTH(hash) > 0",
+      "init" : {
+        "s": "MULTISET_INIT()"
+               },
+      "update": {
+        "s": "MULTISET_ADD(s, hash)"
+                },
+      "result": "s"
+    }
+  ]
+}
+```
+
+## Enrich authentication Events
+
+We will need to enrich the authentication records in a couple of ways to use 
in the threat triage section as well as the profiles:
+* `geo_distance`: representing the distance between the current geohash and 
the geographic centroid for the last 2 minutes.
+* `geo_centroid`: representing the geographic centroid for the last 2 minutes
+
+Beyond that, we will need to determine if the authentication event is a 
geographic outlier by computing the following fields:
+* `dist_median` : representing the median distance between a user's login 
location and the geographic centroid for the last 2 minutes (essentially the 
median of the `geo_distance` values across all users).
+* `dist_sd` : representing the standard deviation of the distance between a 
user's login location and the geographic centroid for the last 2 minutes 
(essentially the standard deviation of the `geo_distance` values across all 
users).
+* `geo_outlier` : whether `geo_distance` is more than 5 standard deviations 
from the median across all users.
+
+We also want to set up a triage rule associating a score and setting an alert 
if `geo_outlier` is true.  In reality, this would be more complex as this 
metric is at best circumstantial and would need supporting evidence, but for 
simplicity we'll deal with the false positives.
+
+* Edit `$METRON_HOME/config/zookeeper/enrichments/auth.json` and paste the 
following:
+```
+{
+  "enrichment": {
+    "fieldMap": {
+      "stellar" : {
+        "config" : [
+          "geo_locations := MULTISET_MERGE( PROFILE_GET( 'locations_by_user', 
user, PROFILE_FIXED( 2, 'MINUTES')))",
+          "geo_centroid := GEOHASH_CENTROID(geo_locations)",
+          "geo_distance := TO_INTEGER(GEOHASH_DIST(geo_centroid, hash))",
+          "geo_locations := null"
+        ]
+      }
+    }
+  ,"fieldToTypeMap": { }
+  },
+  "threatIntel": {
+    "fieldMap": {
+      "stellar" : {
+        "config" : [
+          "geo_distance_distr:= STATS_MERGE( PROFILE_GET( 
'geo_distribution_from_centroid', 'global', PROFILE_FIXED( 2, 'MINUTES')))",
+          "dist_median := STATS_PERCENTILE(geo_distance_distr, 50.0)",
+          "dist_sd := STATS_SD(geo_distance_distr)",
+          "geo_outlier := ABS(dist_median - geo_distance) >= 5*dist_sd",
+          "is_alert := exists(is_alert) && is_alert",
+          "is_alert := is_alert || (geo_outlier != null && geo_outlier == 
true)",
+          "geo_distance_distr := null"
+        ]
+      }
+
+    },
+    "fieldToTypeMap": { },
+    "triageConfig" : {
+      "riskLevelRules" : [
+        {
+          "name" : "Geographic Outlier",
+          "comment" : "Determine if the user's geographic distance from the 
centroid of the historic logins is an outlier as compared to all users.",
+          "rule" : "geo_outlier != null && geo_outlier",
+          "score" : 10,
+          "reason" : "FORMAT('user %s has a distance (%d) from the centroid of 
their last login is 5 std deviations (%f) from the median (%f)', user, 
geo_distance, dist_sd, dist_median)"
+        }
+      ],
+      "aggregator" : "MAX"
+    }
+  }
+}
+```
+
+## Execute Demonstration
+
+From here, we've set up our configuration and can push the configs:
+* Push the configs to zookeeper via
+```
+$METRON_HOME/bin/zk_load_configs.sh --mode PUSH -z node1:2181 -i 
$METRON_HOME/config/zookeeper/
+```
+* Start the parser via:
+```
+$METRON_HOME/bin/start_parser_topology.sh -k $BROKERLIST -z $ZOOKEEPER -s auth
+```
+* Push synthetic data into the `auth` topic via
+```
+python ~/gen_data.py | 
/usr/hdp/current/kafka-broker/bin/kafka-console-producer.sh --broker-list 
node1:6667 --topic auth
+```
+* Wait for about `5` minutes and kill the previous command
+* Push a synthetic record indicating `user1` has logged in from a russian IP 
(`109.252.227.173`):
+```
+echo -e "import time\nprint 'user1,109.252.227.173,'+str(int(time.time()))" | 
python | /usr/hdp/current/kafka-broker/bin/kafka-console-producer.sh 
--broker-list $BROKERLIST --topic auth
+```
+* Execute the following to search elasticsearch for our geographic login 
outliers: 
+```
+curl -XPOST "http://$ES_HOST/auth*/_search?pretty"; -d '
+{
+  "_source" : [ "is_alert", "threat:triage:rules:0:reason", "user", "ip", 
"geo_distance" ],
+  "query": { "exists" : { "field" : "threat:triage:rules:0:reason" } }
+}
+'
+```
+
+You should see, among a few other false positive results, something like the 
following:
+```
+{
+  "_index" : "auth_index_2017.09.07.20",
+    "_type" : "auth_doc",
+    "_id" : "f5bdbf76-9d78-48cc-b21d-bc434c96e62e",
+    "_score" : 1.0,
+    "_source" : {
+      "geo_distance" : 7879,
+      "threat:triage:rules:0:reason" : "user user1 has a distance (7879) from 
the centroid of their last login is 5 std deviations (334.814719) from the 
median (128.000000)",
+      "ip" : "109.252.227.173",
+      "is_alert" : "true",
+      "user" : "user1"
+    }
+}
+```
+

Reply via email to