Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions zeppelin-interpreter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,11 @@
<artifactId>commons-lang3</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-jexl</artifactId>
<version>2.1.1</version>
</dependency>

</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* 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.display;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.jexl2.Expression;
import org.apache.commons.jexl2.JexlContext;
import org.apache.commons.jexl2.JexlEngine;
import org.apache.commons.jexl2.MapContext;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The evaluator helper class.
*
* This class is initialized using the fully qualified name of an
* utility class having one or more static methods.
*
* Example:com.company.custom.udf.UDFUtility
*
* Utility class is resolved using ZEPPELIN_UTILITY_CLASS nv variable or
* 'zeppelin.utility.class' JVM property.
*
* When an expression of the type: "eval:doSomething(...)" is passed from Zeppelin,
* the Evaluator class tries to resolve the something(...) method in the utility class provided
* at initialization time.
*
* Passing an empty utility class at initialization time also works, but then it is mandatory
* to use the fully qualified name when writing the expression in Zeppelin.
*
* Example: eval:com.company.custom.udf.UDFUtility.doSomething(...)
*
* The command coming from Zeppelin notebook is evaluated using 'commons-jexl'.
*
*/
public class Evaluator {

public static final String EVAL_PREFIX = "eval:";
private static final int EVAL_PREFIX_LENGTH = EVAL_PREFIX.length();
private static Pattern REGEX = Pattern.compile("(?<clazz>.+\\..+)\\.(?<method>.+)");


private static Logger LOG = LoggerFactory.getLogger(Evaluator.class);
Class utilityClass;

/**
* Constructs an evaluator class given an user-defined Utility class fully qualified name.
*
* @param classImpl Utility class containing.
* @throws ClassNotFoundException if utility class is not in the classpath.
*/
public Evaluator(String classImpl) throws ClassNotFoundException {
if (StringUtils.isEmpty(classImpl))
LOG.debug("Only full qualified expressions will be executed... Be careful!");
else
this.utilityClass = Class.forName(classImpl);
}

/**
* Evaluates the given command coming verbatim from Zeppelin notebook.
*
* The command coming from Zeppelin notebook is evaluated using 'commons-jexl'.
*
* @param command expression to eval
* @return the result of
*/
public Object eval(String command) throws UnsupportedOperationException {

Object obj = null;

// Check if expression has to be evaluated.
if (!command.startsWith(EVAL_PREFIX)) {
return command;
}

String expressionToEval = command.substring(EVAL_PREFIX_LENGTH);

try {
Map<String, String> mapFQN = getFQN(expressionToEval);

// First, we try with fully qualified name
JexlEngine jexl = new JexlEngine();
String expression = "utils." + mapFQN.get("method");
Expression expr = jexl.createExpression(expression);
JexlContext jc = new MapContext();

jc.set("utils", Class.forName(mapFQN.get("clazz")));
obj = expr.evaluate(jc);
if (obj != null)
return obj;
} catch (Exception e) {
LOG.debug("Error trying to use the FQN class.");
}

// If it fails, we apply the default utility class
LOG.debug("Trying the default utility class");
try {
JexlEngine jexl = new JexlEngine();
String expression = "utils." + expressionToEval;
Expression expr = jexl.createExpression(expression);
JexlContext jc = new MapContext();
jc.set("utils", utilityClass);

obj = expr.evaluate(jc);
} catch (Exception e) {
LOG.debug("Error using configured utility class");
throw new UnsupportedOperationException("Could not evaluate expression.");
}

if (obj == null)
throw new UnsupportedOperationException("Could not evaluate expression.");

return obj;
}

/**
* This method extracts the class and the method with arguments from a fully qualified class.
*
* @param function FQN class to interpret
* @return Map with both clazz and method to execute
*/
private static HashMap<String, String> getFQN(String function) {
HashMap<String, String> map = new HashMap<>();
Matcher matcher = REGEX.matcher(function);

matcher.find();

String matcherClazz = matcher.group("clazz");
String matcherMethod = matcher.group("method");

map.put("clazz", matcherClazz);
map.put("method", matcherMethod);

return map;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -87,7 +89,7 @@ public Input(String name, Object defaultValue, ParamOption[] options) {
this.options = options;
}


public Input(String name, String displayName, String type, Object defaultValue,
ParamOption[] options, boolean hidden) {
super();
Expand Down Expand Up @@ -300,6 +302,65 @@ public static String getSimpleQuery(Map<String, Object> params, String script) {
}


/**
* This method is similar to getSimpleQuery. Main differences are that params
* argument is not changed at all, and the method look for expressions to eval
* on the params map (the value will starts with 'eval:')
* @param params
* @param script
* @param classUtility This is the utility class used to eval expressions. The utility
* class should define the statisc methods to interpret
* @return
* @throws Exception
*/
public static String getSimpleQueryForEvaluation(Map<String, Object> params, String script
, String classUtility) throws Exception {
String replaced = script;
Evaluator evaluator = new Evaluator(classUtility);

// Recorremos todos los parametros evaluando sii es posible
Iterator entrySet = params.entrySet().iterator();
Map<String, Object> aux = new HashMap<String, Object>();
while (entrySet.hasNext()) {
Entry thisEntry = (Entry) entrySet.next();
String key = thisEntry.getKey().toString();
String value = thisEntry.getValue().toString();
aux.put(key, evaluator.eval(value));
}

for (String key : aux.keySet()) {
Object value = aux.get(key);
if (value == null) continue;
replaced = replaced.replaceAll("[_]?[$][{]([^:]*[:])?" + key
+ "([(][^)]*[)])?(=[^}]*)?[}]", value.toString());
}

Pattern pattern = Pattern.compile("[$][{]([^=}]*[=][^}]*)[}]");
while (true) {
Matcher match = pattern.matcher(replaced);
if (match != null && match.find()) {
String m = match.group(1);
int p = m.indexOf('=');
String replacement = m.substring(p + 1);
int optionP = replacement.indexOf(",");
if (optionP > 0) {
replacement = replacement.substring(0, optionP);
}
replaced = replaced.replaceFirst(
"[_]?[$][{]"
+ m.replaceAll("[(]", ".").replaceAll("[)]", ".")
.replaceAll("[|]", ".") + "[}]", replacement);
} else {
break;
}
}

replaced = replaced.replace("[_]?[$][{]([^=}]*)[}]", "");
return replaced;
}



public static String[] split(String str) {
return str.split(";(?=([^\"']*\"[^\"']*\")*[^\"']*$)");

Expand Down Expand Up @@ -443,7 +504,7 @@ && getBlockStr(blockStart[b]).compareTo(str.substring(blockStartPos, i)) == 0) {
// check if block is started
for (int b = 0; b < blockStart.length; b++) {
if (curString.substring(lastEscapeOffset + 1)
.endsWith(getBlockStr(blockStart[b])) == true) {
.endsWith(getBlockStr(blockStart[b]))) {
blockStack.add(0, b); // block is started
blockStartPos = i;
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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.display;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import junit.framework.Assert;

import org.junit.Test;

public class RegexForFQNTest {

@Test
public void test() {
String function = "org.keedio.sample.Utility.suma(1,2)";
String clazz = "org.keedio.sample.Utility";
String method = "suma(1,2)";

String pattern = "(?<clazz>.+\\..+)\\.(?<method>.+)";

Pattern regex = Pattern.compile(pattern);
Matcher matcher = regex.matcher(function);

matcher.find();

String matcherClazz = matcher.group("clazz");
String matcherMethod = matcher.group("method");

Assert.assertTrue(clazz.equals(matcherClazz));
Assert.assertTrue(method.equals(matcherMethod));
}

}
Loading