Package org.kitesdk.morphline.scriptengine.java

Source Code of org.kitesdk.morphline.scriptengine.java.ScriptEvaluator

/*
* Copyright 2013 Cloudera Inc.
*
* Licensed 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.kitesdk.morphline.scriptengine.java;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicLong;

import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
* Creates and compiles the given Java code block, wrapped into a Java method with the given return
* type and parameter types, along with a Java class definition that contains the given import
* statements.
* <p>
* Compilation is done in main memory, i.e. without writing to the filesystem.
* <p>
* The result is an object that can be executed (and reused) any number of times. This is a high
* performance implementation, using an optimized variant of https://scripting.dev.java.net/" (JSR
* 223 Java Scripting). Calling {@link #evaluate(Object...)} just means calling
* {@link Method#invoke(Object, Object...)} and as such has the same minimal runtime cost, i.e.
* O(100M calls/sec/core).
*
* Instances of this class are thread-safe if the user provided script statements are thread-safe.
*/
public class ScriptEvaluator<T> {

  private final FastJavaScriptEngine.JavaCompiledScript compiledScript;
  private final String javaCodeBlock;
  private final String parseLocation;
 
  private static final AtomicLong nextClassNum = new AtomicLong();
 
  private static final String METHOD_NAME = "eval";

  private static final Logger LOG = LoggerFactory.getLogger(ScriptEvaluator.class);
 
  public ScriptEvaluator(String javaImports, String javaCodeBlock, Class<T> returnType,
      String[] parameterNames, Class[] parameterTypes,
      String parseLocation) throws ScriptException {
   
    if (parameterNames.length != parameterTypes.length) {
      throw new IllegalArgumentException(
        "Lengths of parameterNames (" + parameterNames.length
            + ") and parameterTypes (" + parameterTypes.length
            + ") do not match");
    }
   
    this.javaCodeBlock = javaCodeBlock;
    this.parseLocation = parseLocation;   
    String myPackageName = getClass().getName();
    myPackageName = myPackageName.substring(0, myPackageName.lastIndexOf('.'));
    String className = "MyJavaClass" + nextClassNum.incrementAndGet();
    String returnTypeName = (returnType == Void.class ? "void" : returnType.getCanonicalName());
   
    String script =
      "package " + myPackageName + ".scripts;"
      + "\n"
      + javaImports
      + "\n"
      + "\n public final class " + className + " {"   
      + "\n   public static " + returnTypeName + " " + METHOD_NAME + "(";
   
    for (int i = 0; i < parameterNames.length; i++) {
      if (i > 0) {
        script += ", ";
      }
      script += parameterTypes[i].getCanonicalName() + " " + parameterNames[i];
    }
    script += ") { " + javaCodeBlock + " }";    
    script += "\n }";
    LOG.trace("Compiling script: \n{}", script);   
   
    FastJavaScriptEngine engine = new FastJavaScriptEngine();
    StringWriter errorWriter = new StringWriter();
    engine.getContext().setErrorWriter(errorWriter);
    engine.getContext().setAttribute(ScriptEngine.FILENAME, className + ".java", ScriptContext.ENGINE_SCOPE);
    ClassLoader[] loaders = getClassLoaders();
    engine.getContext().setAttribute("parentLoader", loaders[0], ScriptContext.ENGINE_SCOPE);
    try {
      compiledScript = (FastJavaScriptEngine.JavaCompiledScript) engine.compile(script, METHOD_NAME, parameterTypes);
    } catch (ScriptException e) {
      String errorMsg = errorWriter.toString();
      if (errorMsg.length() > 0) {
        errorMsg = ": " + errorMsg;
      }
      throwScriptCompilationException(parseLocation, e.getMessage() + errorMsg, null);
      throw null; // keep compiler happy
    }   
    engine.getContext().setErrorWriter(new PrintWriter(System.err, true)); // reset
  }
 
  @SuppressWarnings("unchecked")
  public T evaluate(Object... params) throws ScriptException {
    // TODO: consider restricting permissions/sandboxing; also see http://worldwizards.blogspot.com/2009/08/java-scripting-api-sandbox.html
    try {
      return (T) compiledScript.eval(params);
    } catch (ScriptException e) {       
      throwScriptExecutionException(parseLocation + " near: '" + javaCodeBlock + "'", params, e);
    }
    return null; // keep compiler happy
  }

  private ClassLoader[] getClassLoaders() {
    ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
    ClassLoader myLoader = getClass().getClassLoader();
    if (contextLoader == null) {
      return new ClassLoader[] { myLoader };
    } else if (contextLoader == myLoader || myLoader == null) {
      return new ClassLoader[] { contextLoader };
    } else {
      return new ClassLoader[] { contextLoader, myLoader };
    }
  }
 
  private static void throwScriptCompilationException(String parseLocation, String msg, Throwable t)
      throws ScriptException {
   
    if (t == null) {
      throw new ScriptException("Cannot compile script: " + parseLocation + " caused by " + msg);
    } else {
      ScriptException se = new ScriptException("Cannot compile script: " + parseLocation + " caused by " + msg);
      se.initCause(t);
      throw se;
    }
  }
 
  private static void throwScriptExecutionException(String parseLocation, Object[] params, Throwable e)
      throws ScriptException {
   
    ScriptException se = new ScriptException("Cannot execute script: " + parseLocation + " for params " + Arrays.asList(params).toString());
    se.initCause(e);
    throw se;
  }
 
}
TOP

Related Classes of org.kitesdk.morphline.scriptengine.java.ScriptEvaluator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.