diff --git a/groovy/README.txt b/groovy/README.txt
new file mode 100644
index 00000000000..dac8c449fd7
--- /dev/null
+++ b/groovy/README.txt
@@ -0,0 +1,4 @@
+groovy language interpreter
+
+project.name : ${project.name}
+project.version : ${project.version}
diff --git a/groovy/pom.xml b/groovy/pom.xml
new file mode 100644
index 00000000000..7b6fe724624
--- /dev/null
+++ b/groovy/pom.xml
@@ -0,0 +1,171 @@
+
+
+
+
+ 4.0.0
+
+
+ zeppelin
+ org.apache.zeppelin
+ 0.6.2
+ ..
+
+
+ org.apache.zeppelin
+ zeppelin-groovy
+ jar
+ 0.6.2
+ Zeppelin: Groovy interpreter
+
+
+
+ ${project.groupId}
+ zeppelin-interpreter
+ ${project.version}
+ provided
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+
+
+ org.codehaus.groovy
+ groovy-all
+ 2.4.7
+
+
+
+ junit
+ junit
+ test
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+
+ true
+
+
+
+
+
+ maven-enforcer-plugin
+ 1.3.1
+
+
+ enforce
+ none
+
+
+
+
+
+ maven-dependency-plugin
+ 2.8
+
+
+ copy-dependencies
+ package
+
+ copy-dependencies
+
+
+ ${project.build.directory}/../../interpreter/groovy
+ false
+ false
+ true
+ runtime
+
+
+
+ copy-artifact
+ package
+
+ copy
+
+
+ ${project.build.directory}/../../interpreter/groovy
+ false
+ false
+ true
+ runtime
+
+
+ ${project.groupId}
+ ${project.artifactId}
+ ${project.version}
+ ${project.packaging}
+
+
+
+
+
+
+
+ maven-assembly-plugin
+ 2.5.3
+
+ src/assembly/dep.xml
+
+
+
+ create-archive
+ package
+
+ single
+
+
+
+
+
+ maven-antrun-plugin
+
+
+ revision-info
+ prepare-package
+
+ run
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/groovy/src/assembly/dep.xml b/groovy/src/assembly/dep.xml
new file mode 100644
index 00000000000..47ef826262a
--- /dev/null
+++ b/groovy/src/assembly/dep.xml
@@ -0,0 +1,66 @@
+
+
+
+ bin
+ groovy
+
+ zip
+
+
+
+ ${project.basedir}
+ /
+ true
+
+ README*
+
+
+
+ ${project.build.directory}
+ /
+
+ *.jar
+ revision.txt
+
+
+
+ ${project.basedir}/src/main/groovy/
+ /classes/
+
+ *.groovy
+
+
+
+
+
+
+
+ /
+ false
+ runtime
+
+
+
+
diff --git a/groovy/src/assembly/readme.txt b/groovy/src/assembly/readme.txt
new file mode 100644
index 00000000000..dbc8369486c
--- /dev/null
+++ b/groovy/src/assembly/readme.txt
@@ -0,0 +1 @@
+to assemble groovy interpreter separately
\ No newline at end of file
diff --git a/groovy/src/main/groovy/HTTP.groovy b/groovy/src/main/groovy/HTTP.groovy
new file mode 100644
index 00000000000..fe4eb36401e
--- /dev/null
+++ b/groovy/src/main/groovy/HTTP.groovy
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+
+import groovy.json.JsonOutput
+
+/**
+ * simple http rest client for groovy
+ * by dlukyanov@ukr.net
+ */
+@groovy.transform.CompileStatic
+public class HTTP{
+ //default response handler
+ public static Closure TEXT_RECEIVER = {InputStream instr,Map ctx->
+ return instr.getText( (String)ctx.encoding );
+ }
+
+ public static Closure JSON_RECEIVER = { InputStream instr, Map ctx->
+ return new groovy.json.JsonSlurper().parse(instr,(String)ctx.encoding);
+ }
+
+ public static Closure FILE_RECEIVER(File f){
+ return { InputStream instr, Map ctx->
+ f< get(Map ctx)throws IOException{
+ ctx.put('method','GET');
+ return send(ctx);
+ }
+
+ public static Map post(Map ctx)throws IOException{
+ ctx.put('method','POST');
+ return send(ctx);
+ }
+
+ public static Map put(Map ctx)throws IOException{
+ ctx.put('method','PUT');
+ return send(ctx);
+ }
+
+ public static Map delete(Map ctx)throws IOException{
+ ctx.put('method','DELETE');
+ return send(ctx);
+ }
+
+ public static Map send(Map ctx)throws IOException{
+ String url = ctx.url;
+ Map headers = (Map)ctx.headers;
+ String method = ctx.method;
+ Object body = ctx.body;
+ String encoding = ctx.encoding?:"UTF-8";
+ Closure receiver = (Closure)ctx.receiver;
+ Map query = (Map)ctx.query;
+
+ //copy context and set default values
+ ctx = [:] + ctx;
+ ctx.encoding = encoding;
+ String contentType="";
+
+ if(query){
+ url+="?"+query.collect{k,v-> k+"="+URLEncoder.encode(v,'UTF-8') }.join('&')
+ }
+
+ HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
+
+ connection.setDoOutput(true);
+ connection.setRequestMethod(method);
+ if ( headers!=null && !headers.isEmpty() ) {
+ //add headers
+ for (Map.Entry entry : headers.entrySet()) {
+ connection.addRequestProperty(entry.getKey(), entry.getValue());
+ if("content-type".equals(entry.getKey().toLowerCase()))contentType=entry.getValue();
+ }
+ }
+
+ if(body!=null){
+ //write body
+ OutputStream out = connection.getOutputStream();
+ if( body instanceof Closure ){
+ ((Closure)body).call(out, ctx);
+ }else if(body instanceof InputStream){
+ out << (InputStream)body;
+ }else if(body instanceof Map){
+ if( contentType.matches("(?i)[^/]+/json") ){
+ out.withWriter((String)ctx.encoding){
+ it.append( JsonOutput.toJson((Map)body) );
+ it.flush();
+ }
+ }else{
+ throw new IOException("Map body type supported only for */json content-type");
+ }
+ }else if(body instanceof CharSequence){
+ out.withWriter((String)ctx.encoding){
+ it.append((CharSequence)body);
+ it.flush();
+ }
+ }else{
+ throw new IOException("Unsupported body type: "+body.getClass());
+ }
+ out.flush();
+ out.close();
+ out=null;
+ }
+
+ Map response = [:];
+ ctx.response = response;
+ response.code = connection.getResponseCode();
+ response.message = connection.getResponseMessage();
+ response.headers = connection.getHeaderFields();
+
+ InputStream instr = null;
+
+ if( ((int)response.code)>=400 ){
+ try{
+ instr = connection.getErrorStream();
+ }catch(Exception ei){}
+ }else{
+ try{
+ instr = connection.getInputStream();
+ }catch(java.io.IOException ei){
+ throw new IOException("fail to open InputStream for http code "+response.code+":"+ei);
+ }
+ }
+
+ if(instr!=null) {
+ instr = new BufferedInputStream(instr);
+ if(receiver==null){
+ if( response.headers['Content-Type']?.toString()?.indexOf('/json')>0 ){
+ receiver=JSON_RECEIVER;
+ } else receiver=TEXT_RECEIVER;
+ }
+ response.body = receiver(instr,ctx);
+ instr.close();
+ instr=null;
+ }
+ return ctx;
+ }
+}
diff --git a/groovy/src/main/groovy/readme.txt b/groovy/src/main/groovy/readme.txt
new file mode 100644
index 00000000000..a8df705b95a
--- /dev/null
+++ b/groovy/src/main/groovy/readme.txt
@@ -0,0 +1,2 @@
+folder to place some groovy helpers to simplify custom code
+as an example there is an HTTP.goovy simple class to call http/rest services
diff --git a/groovy/src/main/java/org/apache/zeppelin/groovy/GObject.java b/groovy/src/main/java/org/apache/zeppelin/groovy/GObject.java
new file mode 100644
index 00000000000..2bd03433158
--- /dev/null
+++ b/groovy/src/main/java/org/apache/zeppelin/groovy/GObject.java
@@ -0,0 +1,169 @@
+/*
+ * 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.zeppelin.groovy;
+
+import java.io.StringWriter;
+import org.slf4j.Logger;
+import java.util.Properties;
+import java.util.Collection;
+
+import groovy.xml.MarkupBuilder;
+import groovy.lang.Closure;
+
+import org.apache.zeppelin.interpreter.InterpreterContext;
+
+import org.apache.zeppelin.display.AngularObjectRegistry;
+import org.apache.zeppelin.display.AngularObject;
+
+/**
+ * Groovy interpreter for Zeppelin.
+ */
+public class GObject extends groovy.lang.GroovyObjectSupport {
+ Logger log;
+ StringWriter out;
+ Properties props;
+ InterpreterContext interpreterContext;
+
+ public GObject(Logger log, StringWriter out, Properties p, InterpreterContext ctx){
+ this.log=log;
+ this.out=out;
+ this.interpreterContext=ctx;
+ this.props=p;
+ }
+
+ public Object getProperty(String key){
+ if("log".equals(key))return log;
+ return props.getProperty(key);
+ }
+ public void setProperty(String key, Object value){
+ throw new RuntimeException("Set properties not supported: "+key+"="+value);
+ }
+ public Properties getProperties(){
+ return props;
+ }
+
+ private void startOutputType(String type){
+ StringBuffer sb=out.getBuffer();
+ if( sb.length()>0 ){
+ if( sb.length() g.html().with{
+ * h1("hello")
+ * h2("world")
+ * }
+ */
+ public MarkupBuilder html(){
+ startOutputType("%angular");
+ return new MarkupBuilder(out);
+ }
+
+ /**
+ * starts or continues rendering table rows
+ * @param obj:
+ * 1. List(rows) of List(columns) where first line is a header
+ */
+ public void table(Object obj){
+ if(obj==null)return;
+ StringBuffer sb=out.getBuffer();
+ startOutputType("%table");
+ if(obj instanceof groovy.lang.Closure){
+ //if closure run and get result collection
+ obj = ((Closure)obj).call();
+ }
+ if(obj instanceof Collection){
+ int count = 0;
+ for(Object row : ((Collection)obj)){
+ count++;
+ boolean rowStarted = false;
+ if(row instanceof Collection){
+ for( Object field: ((Collection)row) ){
+ if(rowStarted)sb.append('\t');
+ sb.append(field);
+ rowStarted=true;
+ }
+ }else{
+ sb.append(row);
+ }
+ sb.append('\n');
+ }
+ }else{
+ throw new RuntimeException("Not supported table value :"+obj.getClass());
+ }
+ }
+
+ private AngularObject getAngularObject(String name) {
+ AngularObjectRegistry registry = interpreterContext.getAngularObjectRegistry();
+ String noteId = interpreterContext.getNoteId();
+ // try get local object
+ AngularObject paragraphAo = registry.get(name, noteId, interpreterContext.getParagraphId());
+ AngularObject noteAo = registry.get(name, noteId, null);
+
+ AngularObject ao = paragraphAo != null ? paragraphAo : noteAo;
+
+ if (ao == null) {
+ // then global object
+ ao = registry.get(name, null, null);
+ }
+ return ao;
+ }
+
+ public void angularBind(String name, Object o, String noteId) {
+ AngularObjectRegistry registry = interpreterContext.getAngularObjectRegistry();
+
+ if (registry.get(name, noteId, null) == null) {
+ registry.add(name, o, noteId, null);
+ } else {
+ registry.get(name, noteId, null).set(o);
+ }
+ }
+
+
+ /**
+ * Get angular object. Look up notebook scope first and then global scope
+ * @param name variable name
+ * @return value
+ */
+ public Object angular(String name) {
+ AngularObject ao = getAngularObject(name);
+ if (ao == null) {
+ return null;
+ } else {
+ return ao.get();
+ }
+ }
+
+ /**
+ * Create angular variable in notebook scope and bind with front end Angular display system.
+ * If variable exists, it'll be overwritten.
+ * @param name name of the variable
+ * @param o value
+ */
+ public void angularBind(String name, Object o) {
+ angularBind(name, o, interpreterContext.getNoteId());
+ }
+
+}
diff --git a/groovy/src/main/java/org/apache/zeppelin/groovy/GroovyInterpreter.java b/groovy/src/main/java/org/apache/zeppelin/groovy/GroovyInterpreter.java
new file mode 100644
index 00000000000..151dfa13b61
--- /dev/null
+++ b/groovy/src/main/java/org/apache/zeppelin/groovy/GroovyInterpreter.java
@@ -0,0 +1,184 @@
+/*
+ * 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.zeppelin.groovy;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.PrintWriter;
+import java.io.File;
+import java.util.*;
+/*
+import org.apache.commons.exec.CommandLine;
+import org.apache.commons.exec.DefaultExecutor;
+import org.apache.commons.exec.ExecuteException;
+import org.apache.commons.exec.ExecuteWatchdog;
+import org.apache.commons.exec.Executor;
+import org.apache.commons.exec.PumpStreamHandler;
+import org.apache.commons.lang3.StringUtils;
+*/
+import org.apache.zeppelin.interpreter.Interpreter;
+import org.apache.zeppelin.interpreter.InterpreterContext;
+import org.apache.zeppelin.interpreter.InterpreterPropertyBuilder;
+import org.apache.zeppelin.interpreter.InterpreterResult;
+import org.apache.zeppelin.interpreter.InterpreterResult.Code;
+import org.apache.zeppelin.interpreter.InterpreterResult.Type;
+import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
+import org.apache.zeppelin.scheduler.Job;
+import org.apache.zeppelin.scheduler.Scheduler;
+import org.apache.zeppelin.scheduler.SchedulerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import groovy.lang.GroovyShell;
+import groovy.lang.Script;
+import org.codehaus.groovy.control.CompilerConfiguration;
+import org.codehaus.groovy.runtime.ResourceGroovyMethods;
+import org.codehaus.groovy.runtime.StackTraceUtils;
+
+/**
+ * Groovy interpreter for Zeppelin.
+ */
+public class GroovyInterpreter extends Interpreter {
+ Logger log = LoggerFactory.getLogger(GroovyInterpreter.class);
+ GroovyShell shell = null; //new GroovyShell();
+
+ public GroovyInterpreter(Properties property) {
+ super(property);
+ }
+
+ @Override
+ public void open() {
+ CompilerConfiguration conf = new CompilerConfiguration();
+ conf.setDebug(true);
+ shell = new GroovyShell(conf);
+ String classes = getProperty("GROOVY_CLASSES");
+ if(classes==null || classes.length()==0){
+ try {
+ File jar = new File(GroovyInterpreter.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
+ classes = new File(jar.getParentFile(),"classes").toString();
+ }catch(Exception e){}
+ }
+ log.info("groovy classes classpath: "+classes);
+ if(classes!=null && classes.length()>0){
+ shell.getClassLoader().addClasspath(classes);
+ }
+ }
+
+ @Override
+ public void close() {
+ shell = null;
+ }
+
+ @Override
+ public FormType getFormType() {
+ return FormType.NONE;
+ }
+
+ @Override
+ public int getProgress(InterpreterContext context) {
+ return 0;
+ }
+
+ @Override
+ public Scheduler getScheduler() {
+ return SchedulerFactory.singleton().createOrGetParallelScheduler(GroovyInterpreter.class.getName() + this.hashCode(), 10);
+ }
+
+ private Job getRunningJob(String paragraphId) {
+ Job foundJob = null;
+ Collection jobsRunning = getScheduler().getJobsRunning();
+ for (Job job : jobsRunning) {
+ if (job.getId().equals(paragraphId)) {
+ foundJob = job;
+ }
+ }
+ return foundJob;
+ }
+
+ @Override
+ public List completion(String buf, int cursor) {
+ return null;
+ }
+
+ Map> scriptCache = Collections.synchronizedMap( new WeakHashMap(1000) );
+ Script getGroovyScript(String id, String scriptText) /*throws SQLException*/ {
+ if(shell==null){
+ throw new RuntimeException("Groovy Shell is not initialized: null");
+ }
+ try{
+ Class