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