org.apache.zeppelin.interpreter.Interpreter.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.zeppelin.interpreter.Interpreter.java

Source

/*
 * 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 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.resource.Resource;
import org.apache.zeppelin.resource.ResourcePool;
import org.apache.zeppelin.scheduler.Scheduler;
import org.apache.zeppelin.scheduler.SchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 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() throws InterpreterException;

    /**
     * Closes interpreter. You may want to free your resources up here.
     * close() is called only once
     */
    @ZeppelinApi
    public abstract void close() throws InterpreterException;

    /**
     * Run precode if exists.
     */
    @ZeppelinApi
    public InterpreterResult executePrecode(InterpreterContext interpreterContext) throws InterpreterException {
        String simpleName = this.getClass().getSimpleName();
        String precode = getProperty(String.format("zeppelin.%s.precode", simpleName));
        if (StringUtils.isNotBlank(precode)) {
            return interpret(precode, interpreterContext);
        }
        return null;
    }

    protected String interpolate(String cmd, ResourcePool resourcePool) {
        Pattern zVariablePattern = Pattern.compile("([^{}]*)([{]+[^{}]*[}]+)(.*)", Pattern.DOTALL);
        StringBuilder sb = new StringBuilder();
        Matcher m;
        String st = cmd;
        while ((m = zVariablePattern.matcher(st)).matches()) {
            sb.append(m.group(1));
            String varPat = m.group(2);
            if (varPat.matches("[{][^{}]+[}]")) {
                // substitute {variable} only if 'variable' has a value ...
                Resource resource = resourcePool.get(varPat.substring(1, varPat.length() - 1));
                Object variableValue = resource == null ? null : resource.get();
                if (variableValue != null)
                    sb.append(variableValue);
                else
                    return cmd;
            } else if (varPat.matches("[{]{2}[^{}]+[}]{2}")) {
                // escape {{text}} ...
                sb.append("{").append(varPat.substring(2, varPat.length() - 2)).append("}");
            } else {
                // mismatched {{ }} or more than 2 braces ...
                return cmd;
            }
            st = m.group(3);
        }
        sb.append(st);
        return sb.toString();
    }

    /**
     * Run code and return result, in synchronous way.
     *
     * @param st statements to run
     */
    @ZeppelinApi
    public abstract InterpreterResult interpret(String st, InterpreterContext context) throws InterpreterException;

    /**
     * Optionally implement the canceling routine to abort interpret() method
     */
    @ZeppelinApi
    public abstract void cancel(InterpreterContext context) throws InterpreterException;

    /**
     * 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() throws InterpreterException;

    /**
     * get interpret() method running process in percentage.
     *
     * @return number between 0-100
     */
    @ZeppelinApi
    public abstract int getProgress(InterpreterContext context) throws InterpreterException;

    /**
     * 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)
            throws InterpreterException {
        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 properties;
    protected String userName;

    @ZeppelinApi
    public Interpreter(Properties properties) {
        this.properties = properties;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    @ZeppelinApi
    public Properties getProperties() {
        Properties p = new Properties();
        p.putAll(properties);
        replaceContextParameters(p);
        return p;
    }

    @ZeppelinApi
    public String getProperty(String key) {
        logger.debug("key: {}, value: {}", key, getProperties().getProperty(key));

        return getProperties().getProperty(key);
    }

    @ZeppelinApi
    public String getProperty(String key, String defaultValue) {
        logger.debug("key: {}, value: {}", key, getProperties().getProperty(key, defaultValue));

        return getProperties().getProperty(key, defaultValue);
    }

    @ZeppelinApi
    public void setProperty(String key, String value) {
        properties.setProperty(key, value);
    }

    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) throws InvalidHookException {
        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) throws InvalidHookException {
        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 <T> T getInterpreterInTheSameSessionByClassName(Class<T> interpreterClass, boolean open)
            throws InterpreterException {
        synchronized (interpreterGroup) {
            for (List<Interpreter> interpreters : interpreterGroup.values()) {
                boolean belongsToSameNoteGroup = false;
                Interpreter interpreterFound = null;
                for (Interpreter intp : interpreters) {
                    if (intp.getClassName().equals(interpreterClass.getName())) {
                        interpreterFound = intp;
                    }

                    Interpreter p = intp;
                    while (p instanceof WrappedInterpreter) {
                        p = ((WrappedInterpreter) p).getInnerInterpreter();
                    }
                    if (this == p) {
                        belongsToSameNoteGroup = true;
                    }
                }

                if (belongsToSameNoteGroup && interpreterFound != null) {
                    LazyOpenInterpreter lazy = null;
                    T innerInterpreter = null;
                    while (interpreterFound instanceof WrappedInterpreter) {
                        if (interpreterFound instanceof LazyOpenInterpreter) {
                            lazy = (LazyOpenInterpreter) interpreterFound;
                        }
                        interpreterFound = ((WrappedInterpreter) interpreterFound).getInnerInterpreter();
                    }
                    innerInterpreter = (T) interpreterFound;

                    if (lazy != null && open) {
                        lazy.open();
                    }
                    return innerInterpreter;
                }
            }
        }
        return null;
    }

    public <T> T getInterpreterInTheSameSessionByClassName(Class<T> interpreterClass) throws InterpreterException {
        return getInterpreterInTheSameSessionByClassName(interpreterClass, true);
    }

    /**
     * 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 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, DefaultInterpreterProperty> 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, DefaultInterpreterProperty> properties) {
            this(name, group, className, false, properties);
        }

        public RegisteredInterpreter(String name, String group, String className, boolean defaultInterpreter,
                Map<String, DefaultInterpreterProperty> 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, DefaultInterpreterProperty> 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 enum SchedulingMode {
        FIFO, PARALLEL
    }

}