/*
* 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.interpreter;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.reflect.FieldUtils;
import org.apache.zeppelin.annotation.Experimental;
import org.apache.zeppelin.annotation.ZeppelinApi;
import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
import org.apache.zeppelin.scheduler.Scheduler;
import org.apache.zeppelin.scheduler.SchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Interface for interpreters.
* If you want to implement new Zeppelin interpreter, extend this class
*
* Please see,
* https://zeppelin.apache.org/docs/latest/development/writingzeppelininterpreter.html
*
* open(), close(), interpret() is three the most important method you need to implement.
* cancel(), getProgress(), completion() is good to have
* getFormType(), getScheduler() determine Zeppelin's behavior
*/
public abstract class Interpreter {
/**
* Opens interpreter. You may want to place your initialize routine here.
* open() is called only once
*/
@ZeppelinApi
public abstract void open();
/**
* Closes interpreter. You may want to free your resources up here.
* close() is called only once
*/
@ZeppelinApi
public abstract void close();
/**
* Run precode if exists.
*/
@ZeppelinApi
public InterpreterResult executePrecode(InterpreterContext interpreterContext) {
String simpleName = this.getClass().getSimpleName();
String precode = getProperty(String.format("zeppelin.%s.precode", simpleName));
if (StringUtils.isNotBlank(precode)) {
return interpret(precode, interpreterContext);
}
return null;
}
/**
* Run code and return result, in synchronous way.
*
* @param st statements to run
*/
@ZeppelinApi
public abstract InterpreterResult interpret(String st, InterpreterContext context);
/**
* Optionally implement the canceling routine to abort interpret() method
*/
@ZeppelinApi
public abstract void cancel(InterpreterContext context);
/**
* Dynamic form handling
* see http://zeppelin.apache.org/docs/dynamicform.html
*
* @return FormType.SIMPLE enables simple pattern replacement (eg. Hello ${name=world}),
* FormType.NATIVE handles form in API
*/
@ZeppelinApi
public abstract FormType getFormType();
/**
* get interpret() method running process in percentage.
*
* @return number between 0-100
*/
@ZeppelinApi
public abstract int getProgress(InterpreterContext context);
/**
* Get completion list based on cursor position.
* By implementing this method, it enables auto-completion.
*
* @param buf statements
* @param cursor cursor position in statements
* @param interpreterContext
* @return list of possible completion. Return empty list if there're nothing to return.
*/
@ZeppelinApi
public List<InterpreterCompletion> completion(String buf, int cursor,
InterpreterContext interpreterContext) {
return null;
}
/**
* Interpreter can implements it's own scheduler by overriding this method.
* There're two default scheduler provided, FIFO, Parallel.
* If your interpret() can handle concurrent request, use Parallel or use FIFO.
*
* You can get default scheduler by using
* SchedulerFactory.singleton().createOrGetFIFOScheduler()
* SchedulerFactory.singleton().createOrGetParallelScheduler()
*
* @return return scheduler instance. This method can be called multiple times and have to return
* the same instance. Can not return null.
*/
@ZeppelinApi
public Scheduler getScheduler() {
return SchedulerFactory.singleton().createOrGetFIFOScheduler("interpreter_" + this.hashCode());
}
public static Logger logger = LoggerFactory.getLogger(Interpreter.class);
private InterpreterGroup interpreterGroup;
private URL[] classloaderUrls;
protected Properties property;
private String userName;
@ZeppelinApi
public Interpreter(Properties property) {
logger.debug("Properties: {}", property);
this.property = property;
}
public void setProperty(Properties property) {
this.property = property;
}
@ZeppelinApi
public Properties getProperty() {
Properties p = new Properties();
p.putAll(property);
RegisteredInterpreter registeredInterpreter = Interpreter.findRegisteredInterpreterByClassName(
getClassName());
if (null != registeredInterpreter) {
Map<String, InterpreterProperty> defaultProperties = registeredInterpreter.getProperties();
for (String k : defaultProperties.keySet()) {
if (!p.containsKey(k)) {
String value = defaultProperties.get(k).getValue();
if (value != null) {
p.put(k, defaultProperties.get(k).getValue());
}
}
}
}
replaceContextParameters(p);
return p;
}
@ZeppelinApi
public String getProperty(String key) {
logger.debug("key: {}, value: {}", key, getProperty().getProperty(key));
return getProperty().getProperty(key);
}
public String getClassName() {
return this.getClass().getName();
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserName() {
return this.userName;
}
public void setInterpreterGroup(InterpreterGroup interpreterGroup) {
this.interpreterGroup = interpreterGroup;
}
@ZeppelinApi
public InterpreterGroup getInterpreterGroup() {
return this.interpreterGroup;
}
public URL[] getClassloaderUrls() {
return classloaderUrls;
}
public void setClassloaderUrls(URL[] classloaderUrls) {
this.classloaderUrls = classloaderUrls;
}
/**
* General function to register hook event
*
* @param noteId - Note to bind hook to
* @param event The type of event to hook to (pre_exec, post_exec)
* @param cmd The code to be executed by the interpreter on given event
*/
@Experimental
public void registerHook(String noteId, String event, String cmd) {
InterpreterHookRegistry hooks = interpreterGroup.getInterpreterHookRegistry();
String className = getClassName();
hooks.register(noteId, className, event, cmd);
}
/**
* registerHook() wrapper for global scope
*
* @param event The type of event to hook to (pre_exec, post_exec)
* @param cmd The code to be executed by the interpreter on given event
*/
@Experimental
public void registerHook(String event, String cmd) {
registerHook(null, event, cmd);
}
/**
* Get the hook code
*
* @param noteId - Note to bind hook to
* @param event The type of event to hook to (pre_exec, post_exec)
*/
@Experimental
public String getHook(String noteId, String event) {
InterpreterHookRegistry hooks = interpreterGroup.getInterpreterHookRegistry();
String className = getClassName();
return hooks.get(noteId, className, event);
}
/**
* getHook() wrapper for global scope
*
* @param event The type of event to hook to (pre_exec, post_exec)
*/
@Experimental
public String getHook(String event) {
return getHook(null, event);
}
/**
* Unbind code from given hook event
*
* @param noteId - Note to bind hook to
* @param event The type of event to hook to (pre_exec, post_exec)
*/
@Experimental
public void unregisterHook(String noteId, String event) {
InterpreterHookRegistry hooks = interpreterGroup.getInterpreterHookRegistry();
String className = getClassName();
hooks.unregister(noteId, className, event);
}
/**
* unregisterHook() wrapper for global scope
*
* @param event The type of event to hook to (pre_exec, post_exec)
*/
@Experimental
public void unregisterHook(String event) {
unregisterHook(null, event);
}
@ZeppelinApi
public Interpreter getInterpreterInTheSameSessionByClassName(String className) {
synchronized (interpreterGroup) {
for (List<Interpreter> interpreters : interpreterGroup.values()) {
boolean belongsToSameNoteGroup = false;
Interpreter interpreterFound = null;
for (Interpreter intp : interpreters) {
if (intp.getClassName().equals(className)) {
interpreterFound = intp;
}
Interpreter p = intp;
while (p instanceof WrappedInterpreter) {
p = ((WrappedInterpreter) p).getInnerInterpreter();
}
if (this == p) {
belongsToSameNoteGroup = true;
}
}
if (belongsToSameNoteGroup) {
return interpreterFound;
}
}
}
return null;
}
/**
* Replace markers #{contextFieldName} by values from {@link InterpreterContext} fields
* with same name and marker #{user}. If value == null then replace by empty string.
*/
private void replaceContextParameters(Properties properties) {
InterpreterContext interpreterContext = InterpreterContext.get();
if (interpreterContext != null) {
String markerTemplate = "#\\{%s\\}";
List<String> skipFields = Arrays.asList("paragraphTitle", "paragraphId", "paragraphText");
List typesToProcess = Arrays.asList(String.class, Double.class, Float.class, Short.class,
Byte.class, Character.class, Boolean.class, Integer.class, Long.class);
for (String key : properties.stringPropertyNames()) {
String p = properties.getProperty(key);
if (StringUtils.isNotEmpty(p)) {
for (Field field : InterpreterContext.class.getDeclaredFields()) {
Class clazz = field.getType();
if (!skipFields.contains(field.getName()) && (typesToProcess.contains(clazz)
|| clazz.isPrimitive())) {
Object value = null;
try {
value = FieldUtils.readField(field, interpreterContext, true);
} catch (Exception e) {
logger.error("Cannot read value of field {0}", field.getName());
}
p = p.replaceAll(String.format(markerTemplate, field.getName()),
value != null ? value.toString() : StringUtils.EMPTY);
}
}
p = p.replaceAll(String.format(markerTemplate, "user"),
StringUtils.defaultString(userName, StringUtils.EMPTY));
properties.setProperty(key, p);
}
}
}
}
/**
* Type of interpreter.
*/
public static enum FormType {
NATIVE, SIMPLE, NONE
}
/**
* Represent registered interpreter class
*/
public static class RegisteredInterpreter {
private String group;
private String name;
private String className;
private boolean defaultInterpreter;
private Map<String, InterpreterProperty> properties;
private Map<String, Object> editor;
private String path;
private InterpreterOption option;
private InterpreterRunner runner;
public RegisteredInterpreter(String name, String group, String className,
Map<String, InterpreterProperty> properties) {
this(name, group, className, false, properties);
}
public RegisteredInterpreter(String name, String group, String className,
boolean defaultInterpreter, Map<String, InterpreterProperty> properties) {
super();
this.name = name;
this.group = group;
this.className = className;
this.defaultInterpreter = defaultInterpreter;
this.properties = properties;
this.editor = new HashMap<>();
}
public String getName() {
return name;
}
public String getGroup() {
return group;
}
public String getClassName() {
return className;
}
public boolean isDefaultInterpreter() {
return defaultInterpreter;
}
public void setDefaultInterpreter(boolean defaultInterpreter) {
this.defaultInterpreter = defaultInterpreter;
}
public Map<String, InterpreterProperty> getProperties() {
return properties;
}
public Map<String, Object> getEditor() {
return editor;
}
public void setPath(String path) {
this.path = path;
}
public String getPath() {
return path;
}
public String getInterpreterKey() {
return getGroup() + "." + getName();
}
public InterpreterOption getOption() {
return option;
}
public InterpreterRunner getRunner() {
return runner;
}
}
/**
* Type of Scheduling.
*/
public static enum SchedulingMode {
FIFO, PARALLEL
}
public static Map<String, RegisteredInterpreter> registeredInterpreters = Collections
.synchronizedMap(new HashMap<String, RegisteredInterpreter>());
@Deprecated
public static void register(String name, String group, String className,
Map<String, InterpreterProperty> properties) {
register(name, group, className, false, properties);
}
@Deprecated
public static void register(String name, String group, String className,
boolean defaultInterpreter, Map<String, InterpreterProperty> properties) {
logger.warn("Static initialization is deprecated for interpreter {}, You should change it " +
"to use interpreter-setting.json in your jar or " +
"interpreter/{interpreter}/interpreter-setting.json", name);
register(new RegisteredInterpreter(name, group, className, defaultInterpreter, properties));
}
@Deprecated
public static void register(RegisteredInterpreter registeredInterpreter) {
String interpreterKey = registeredInterpreter.getInterpreterKey();
if (!registeredInterpreters.containsKey(interpreterKey)) {
registeredInterpreters.put(interpreterKey, registeredInterpreter);
} else {
RegisteredInterpreter existInterpreter = registeredInterpreters.get(interpreterKey);
if (!existInterpreter.getProperties().equals(registeredInterpreter.getProperties())) {
logger.error("exist registeredInterpreter with the same key but has different settings.");
}
}
}
public static RegisteredInterpreter findRegisteredInterpreterByClassName(String className) {
for (RegisteredInterpreter ri : registeredInterpreters.values()) {
if (ri.getClassName().equals(className)) {
return ri;
}
}
return null;
}
}