You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by ty...@apache.org on 2014/09/24 17:47:42 UTC

git commit: Optimize java source-based UDF invocation

Repository: cassandra
Updated Branches:
  refs/heads/trunk 85401dc76 -> dad0bfac6


Optimize java source-based UDF invocation

Patch by Robert Stupp; reviewed by Benjamin Lerer for CASSANDRA-7924


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/dad0bfac
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/dad0bfac
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/dad0bfac

Branch: refs/heads/trunk
Commit: dad0bfac6e885176d83f802bc75763c5ba21d68e
Parents: 85401dc
Author: Robert Stupp <sn...@snazy.de>
Authored: Wed Sep 24 10:46:53 2014 -0500
Committer: Tyler Hobbs <ty...@datastax.com>
Committed: Wed Sep 24 10:46:53 2014 -0500

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 .../cql3/functions/AbstractJavaUDF.java         |  91 -------
 .../cql3/functions/JavaSourceBasedUDF.java      | 112 ---------
 .../cql3/functions/JavaSourceUDFFactory.java    | 252 +++++++++++++++++++
 .../cql3/functions/ReflectionBasedUDF.java      |  75 ++++--
 .../cassandra/cql3/functions/UDFunction.java    |  15 +-
 test/unit/org/apache/cassandra/cql3/UFTest.java |  31 ++-
 7 files changed, 343 insertions(+), 234 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/dad0bfac/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 267c4c2..cf8f263 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 3.0
+ * Optimize java source-based UDF invocation (CASSANDRA-7924)
  * Accept dollar quoted strings in CQL (CASSANDRA-7769)
  * Make assassinate a first class command (CASSANDRA-7935)
  * Support IN clause on any clustering column (CASSANDRA-4762)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/dad0bfac/src/java/org/apache/cassandra/cql3/functions/AbstractJavaUDF.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/functions/AbstractJavaUDF.java b/src/java/org/apache/cassandra/cql3/functions/AbstractJavaUDF.java
deleted file mode 100644
index f147f00..0000000
--- a/src/java/org/apache/cassandra/cql3/functions/AbstractJavaUDF.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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.cassandra.cql3.functions;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.nio.ByteBuffer;
-import java.util.List;
-
-import org.apache.cassandra.cql3.ColumnIdentifier;
-import org.apache.cassandra.db.marshal.AbstractType;
-import org.apache.cassandra.exceptions.InvalidRequestException;
-
-/**
- * UDF implementation base class for reflection and Java source UDFs.
- */
-abstract class AbstractJavaUDF extends UDFunction
-{
-    private final Method method;
-
-    AbstractJavaUDF(FunctionName name,
-                    List<ColumnIdentifier> argNames,
-                    List<AbstractType<?>> argTypes,
-                    AbstractType<?> returnType,
-                    String language,
-                    String body,
-                    boolean deterministic)
-    throws InvalidRequestException
-    {
-        super(name, argNames, argTypes, returnType, language, body, deterministic);
-        assert language.equals(requiredLanguage());
-        this.method = resolveMethod();
-    }
-
-    abstract String requiredLanguage();
-
-    abstract Method resolveMethod() throws InvalidRequestException;
-
-    protected Class<?>[] javaParamTypes()
-    {
-        Class<?> paramTypes[] = new Class[argTypes.size()];
-        for (int i = 0; i < paramTypes.length; i++)
-            paramTypes[i] = argTypes.get(i).getSerializer().getType();
-        return paramTypes;
-    }
-
-    protected Class<?> javaReturnType()
-    {
-        return returnType.getSerializer().getType();
-    }
-
-    public ByteBuffer execute(List<ByteBuffer> parameters) throws InvalidRequestException
-    {
-        Object[] parms = new Object[argTypes.size()];
-        for (int i = 0; i < parms.length; i++)
-        {
-            ByteBuffer bb = parameters.get(i);
-            if (bb != null)
-                parms[i] = argTypes.get(i).compose(bb);
-        }
-
-        Object result;
-        try
-        {
-            result = method.invoke(null, parms);
-            @SuppressWarnings("unchecked") ByteBuffer r = result != null ? ((AbstractType) returnType).decompose(result) : null;
-            return r;
-        }
-        catch (InvocationTargetException | IllegalAccessException e)
-        {
-            Throwable c = e.getCause();
-            logger.error("Invocation of function '{}' failed", this, c);
-            throw new InvalidRequestException("Invocation of function '" + this + "' failed: " + c);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/dad0bfac/src/java/org/apache/cassandra/cql3/functions/JavaSourceBasedUDF.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/functions/JavaSourceBasedUDF.java b/src/java/org/apache/cassandra/cql3/functions/JavaSourceBasedUDF.java
deleted file mode 100644
index 7e483a0..0000000
--- a/src/java/org/apache/cassandra/cql3/functions/JavaSourceBasedUDF.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * 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.cassandra.cql3.functions;
-
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import javassist.CannotCompileException;
-import javassist.ClassPool;
-import javassist.CtClass;
-import javassist.CtNewMethod;
-import org.apache.cassandra.cql3.ColumnIdentifier;
-import org.apache.cassandra.db.marshal.AbstractType;
-import org.apache.cassandra.exceptions.InvalidRequestException;
-
-/**
- * User-defined function using Java source code in UDF body.
- * <p/>
- * This is used when the LANGUAGE of the UDF definition is "java".
- */
-final class JavaSourceBasedUDF extends AbstractJavaUDF
-{
-    static final AtomicInteger clsIdgen = new AtomicInteger();
-
-    JavaSourceBasedUDF(FunctionName name,
-                       List<ColumnIdentifier> argNames,
-                       List<AbstractType<?>> argTypes,
-                       AbstractType<?> returnType,
-                       String language,
-                       String body,
-                       boolean deterministic)
-    throws InvalidRequestException
-    {
-        super(name, argNames, argTypes, returnType, language, body, deterministic);
-    }
-
-    String requiredLanguage()
-    {
-        return "java";
-    }
-
-    Method resolveMethod() throws InvalidRequestException
-    {
-        Class<?> jReturnType = javaReturnType();
-        Class<?>[] paramTypes = javaParamTypes();
-
-        StringBuilder code = new StringBuilder();
-        code.append("public static ").
-             append(jReturnType.getName()).append(' ').
-             append(name.name).append('(');
-        for (int i = 0; i < paramTypes.length; i++)
-        {
-            if (i > 0)
-                code.append(", ");
-            code.append(paramTypes[i].getName()).
-                 append(' ').
-                 append(argNames.get(i));
-        }
-        code.append(") { ");
-        code.append(body);
-        code.append('}');
-
-        ClassPool classPool = ClassPool.getDefault();
-        CtClass cc = classPool.makeClass("org.apache.cassandra.cql3.udf.gen.C" + javaIdentifierPart(name.toString()) + '_' + clsIdgen.incrementAndGet());
-        try
-        {
-            cc.addMethod(CtNewMethod.make(code.toString(), cc));
-            Class<?> clazz = cc.toClass();
-            return clazz.getMethod(name.name, paramTypes);
-        }
-        catch (LinkageError e)
-        {
-            throw new InvalidRequestException("Could not compile function '" + name + "' from Java source: " + e.getMessage());
-        }
-        catch (CannotCompileException e)
-        {
-            throw new InvalidRequestException("Could not compile function '" + name + "' from Java source: " + e.getReason());
-        }
-        catch (NoSuchMethodException e)
-        {
-            throw new InvalidRequestException("Could not build function '" + name + "' from Java source");
-        }
-    }
-
-    private static String javaIdentifierPart(String qualifiedName)
-    {
-        StringBuilder sb = new StringBuilder(qualifiedName.length());
-        for (int i = 0; i < qualifiedName.length(); i++)
-        {
-            char c = qualifiedName.charAt(i);
-            if (Character.isJavaIdentifierPart(c))
-                sb.append(c);
-        }
-        return sb.toString();
-    }
-}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/dad0bfac/src/java/org/apache/cassandra/cql3/functions/JavaSourceUDFFactory.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/functions/JavaSourceUDFFactory.java b/src/java/org/apache/cassandra/cql3/functions/JavaSourceUDFFactory.java
new file mode 100644
index 0000000..0f5fe48
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/functions/JavaSourceUDFFactory.java
@@ -0,0 +1,252 @@
+/*
+ * 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.cassandra.cql3.functions;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javassist.CannotCompileException;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtNewConstructor;
+import javassist.CtNewMethod;
+import javassist.NotFoundException;
+import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+
+/**
+ * Java source UDF code generation.
+ */
+public final class JavaSourceUDFFactory
+{
+    private static final String GENERATED_CODE_PACKAGE = "org.apache.cassandra.cql3.udf.gen.";
+
+    protected static final Logger logger = LoggerFactory.getLogger(JavaSourceUDFFactory.class);
+
+    private static final AtomicInteger classSequence = new AtomicInteger();
+
+    static UDFunction buildUDF(FunctionName name,
+                               List<ColumnIdentifier> argNames,
+                               List<AbstractType<?>> argTypes,
+                               AbstractType<?> returnType,
+                               String body,
+                               boolean deterministic)
+    throws InvalidRequestException
+    {
+        Class<?> javaReturnType = UDFunction.javaType(returnType);
+        Class<?>[] javaParamTypes = UDFunction.javaParamTypes(argTypes);
+
+        String clsName = generateClassName(name);
+
+        String codeCtor = generateConstructor(clsName);
+
+        // Generate 'execute' method (implements org.apache.cassandra.cql3.functions.Function.execute)
+        String codeExec = generateExecuteMethod(argNames, javaParamTypes);
+
+        // Generate the 'executeInternal' method
+        // It is separated to allow return type and argument type checks during compile time via javassist.
+        String codeExecInt = generateExecuteInternalMethod(argNames, body, javaReturnType, javaParamTypes);
+
+        if (logger.isDebugEnabled())
+            logger.debug("Generating java source UDF for {} with following c'tor and functions:\n{}\n{}\n{}",
+                         name, codeCtor, codeExecInt, codeExec);
+
+        try
+        {
+            ClassPool classPool = ClassPool.getDefault();
+
+            // get super class
+            CtClass base = classPool.get(UDFunction.class.getName());
+
+            // prepare class to generate
+            CtClass cc = classPool.makeClass(GENERATED_CODE_PACKAGE + clsName, base);
+            cc.setModifiers(cc.getModifiers() | Modifier.FINAL);
+
+            // add c'tor plus methods (order matters)
+            cc.addConstructor(CtNewConstructor.make(codeCtor, cc));
+            cc.addMethod(CtNewMethod.make(codeExecInt, cc));
+            cc.addMethod(CtNewMethod.make(codeExec, cc));
+
+            Constructor ctor =
+                cc.toClass().getDeclaredConstructor(
+                   FunctionName.class, List.class, List.class,
+                   AbstractType.class, String.class, boolean.class);
+            return (UDFunction) ctor.newInstance(name, argNames, argTypes, returnType, body, deterministic);
+        }
+        catch (NotFoundException | CannotCompileException | NoSuchMethodException | LinkageError | InstantiationException | IllegalAccessException e)
+        {
+            throw new InvalidRequestException(String.format("Could not compile function '%s' from Java source: %s", name, e));
+        }
+        catch (InvocationTargetException e)
+        {
+            // in case of an ITE, use the cause
+            throw new InvalidRequestException(String.format("Could not compile function '%s' from Java source: %s", name, e.getCause()));
+        }
+    }
+
+    private static String generateClassName(FunctionName name)
+    {
+        String qualifiedName = name.toString();
+
+        StringBuilder sb = new StringBuilder(qualifiedName.length()+10);
+        sb.append('C');
+        for (int i = 0; i < qualifiedName.length(); i++)
+        {
+            char c = qualifiedName.charAt(i);
+            if (Character.isJavaIdentifierPart(c))
+                sb.append(c);
+        }
+        sb.append('_');
+        sb.append(classSequence.incrementAndGet());
+        return sb.toString();
+    }
+
+    /**
+     * Generates constructor with just a call super class (UDFunction) constructor with constant 'java' as language.
+     */
+    private static String generateConstructor(String clsName)
+    {
+        return "public " + clsName +
+               "(org.apache.cassandra.cql3.functions.FunctionName name, " +
+               "java.util.List argNames, " +
+               "java.util.List argTypes, " +
+               "org.apache.cassandra.db.marshal.AbstractType returnType, " +
+               "String body," +
+               "boolean deterministic)\n{" +
+               "  super(name, argNames, argTypes, returnType, \"java\", body, deterministic);\n" +
+               "}";
+    }
+
+    /**
+     * Generate executeInternal method (just there to find return and argument type mismatches in UDF body).
+     *
+     * Generated looks like this:
+     * <code><pre>
+     * private <JAVA_RETURN_TYPE> executeInternal(<JAVA_ARG_TYPE> paramOne, <JAVA_ARG_TYPE> nextParam)
+     * {
+     *     <UDF_BODY>
+     * }
+     * </pre></code>
+     */
+    private static String generateExecuteInternalMethod(List<ColumnIdentifier> argNames, String body, Class<?> returnType, Class<?>[] paramTypes)
+    {
+        // initial builder size can just be a guess (prevent temp object allocations)
+        StringBuilder codeInt = new StringBuilder(64 + 32*paramTypes.length + body.length());
+        codeInt.append("private ").append(returnType.getName()).append(" executeInternal(");
+        for (int i = 0; i < paramTypes.length; i++)
+        {
+            if (i > 0)
+                codeInt.append(", ");
+            codeInt.append(paramTypes[i].getName()).
+                    append(' ').
+                    append(argNames.get(i));
+        }
+        codeInt.append(")\n{").
+                append(body).
+                append('}');
+        return codeInt.toString();
+    }
+
+    /**
+     *
+     * Generate public execute() method implementation.
+     *
+     * Generated looks like this:
+     * <code><pre>
+     *
+     * public java.nio.ByteBuffer execute(java.util.List params)
+     * throws org.apache.cassandra.exceptions.InvalidRequestException
+     * {
+     *     try
+     *     {
+     *         Object result = executeInternal(
+     *             (<cast to JAVA_ARG_TYPE>)org.apache.cassandra.cql3.functions.JavaSourceUDFFactory.compose(argTypes, params, 0)
+     *         );
+     *         return result != null ? returnType.decompose(result) : null;
+     *     }
+     *     catch (Throwable t)
+     *     {
+     *         logger.error("Invocation of function '{}' failed", this, t);
+     *         if (t instanceof VirtualMachineError)
+     *             throw (VirtualMachineError)t;
+     *         throw new org.apache.cassandra.exceptions.InvalidRequestException("Invocation of function '" + this + "' failed: " + t);
+     *     }
+     * }
+     * </pre></code>
+     */
+    private static String generateExecuteMethod(List<ColumnIdentifier> argNames, Class<?>[] paramTypes)
+    {
+        // usual methods are 700-800 chars long (prevent temp object allocations)
+        StringBuilder code = new StringBuilder(1024);
+        // overrides org.apache.cassandra.cql3.functions.Function.execute(java.util.List)
+        code.append("public java.nio.ByteBuffer execute(java.util.List params)\n" +
+                    "throws org.apache.cassandra.exceptions.InvalidRequestException\n" +
+                    "{\n" +
+                    "  try\n" +
+                    "  {\n" +
+                    "    Object result = executeInternal(");
+        for (int i = 0; i < paramTypes.length; i++)
+        {
+            if (i > 0)
+                code.append(',');
+
+            if (logger.isDebugEnabled())
+                code.append("\n      /* ").append(argNames.get(i)).append(" */ ");
+
+            code.
+                 // cast to Java type
+                 append("\n      (").append(paramTypes[i].getName()).append(")").
+                 // generate object representation of input parameter
+                 append("org.apache.cassandra.cql3.functions.JavaSourceUDFFactory.compose(argTypes, params, ").append(i).append(')');
+        }
+
+        code.append("\n    );\n" +
+                    // generate serialized return value (returnType is a field in AbstractFunction class)
+                    "    return result != null ? returnType.decompose(result) : null;\n" +
+                    //
+                    // error handling ...
+                    "  }\n" +
+                    "  catch (Throwable t)\n" +
+                    "  {\n" +
+                    "    logger.error(\"Invocation of function '{}' failed\", this, t);\n" +
+                    // handle OutOfMemoryError and other fatals not here!
+                    "    if (t instanceof VirtualMachineError)\n" +
+                    "      throw (VirtualMachineError)t;\n" +
+                    "    throw new org.apache.cassandra.exceptions.InvalidRequestException(\"Invocation of function '\" + this + \"' failed: \" + t);\n" +
+                    "  }\n" +
+                    "}");
+
+        return code.toString();
+    }
+
+    // Used by execute() implementations of generated java source UDFs.
+    public static Object compose(List<AbstractType<?>> argTypes, List<ByteBuffer> parameters, int param)
+    {
+        ByteBuffer bb = parameters.get(param);
+        return bb == null ? null : argTypes.get(param).compose(bb);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/dad0bfac/src/java/org/apache/cassandra/cql3/functions/ReflectionBasedUDF.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/functions/ReflectionBasedUDF.java b/src/java/org/apache/cassandra/cql3/functions/ReflectionBasedUDF.java
index e02147a..911537f 100644
--- a/src/java/org/apache/cassandra/cql3/functions/ReflectionBasedUDF.java
+++ b/src/java/org/apache/cassandra/cql3/functions/ReflectionBasedUDF.java
@@ -17,8 +17,10 @@
  */
 package org.apache.cassandra.cql3.functions;
 
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.List;
 
@@ -32,8 +34,10 @@ import org.apache.cassandra.exceptions.InvalidRequestException;
  *
  * This is used when the LANGUAGE of the UDF definition is "class".
  */
-final class ReflectionBasedUDF extends AbstractJavaUDF
+final class ReflectionBasedUDF extends UDFunction
 {
+    private final MethodHandle method;
+
     ReflectionBasedUDF(FunctionName name,
                        List<ColumnIdentifier> argNames,
                        List<AbstractType<?>> argTypes,
@@ -44,17 +48,14 @@ final class ReflectionBasedUDF extends AbstractJavaUDF
     throws InvalidRequestException
     {
         super(name, argNames, argTypes, returnType, language, body, deterministic);
+        assert language.equals("class");
+        this.method = resolveMethod();
     }
 
-    String requiredLanguage()
-    {
-        return "class";
-    }
-
-    Method resolveMethod() throws InvalidRequestException
+    private MethodHandle resolveMethod() throws InvalidRequestException
     {
-        Class<?> jReturnType = javaReturnType();
-        Class<?>[] paramTypes = javaParamTypes();
+        Class<?> jReturnType = javaType(returnType);
+        Class<?>[] paramTypes = javaParamTypes(argTypes);
 
         String className;
         String methodName;
@@ -73,18 +74,11 @@ final class ReflectionBasedUDF extends AbstractJavaUDF
         {
             Class<?> cls = Class.forName(className, false, Thread.currentThread().getContextClassLoader());
 
-            Method method = cls.getMethod(methodName, paramTypes);
-
-            if (!Modifier.isStatic(method.getModifiers()))
-                throw new InvalidRequestException("Method " + className + '.' + methodName + '(' + Arrays.toString(paramTypes) + ") is not static");
-
-            if (!jReturnType.isAssignableFrom(method.getReturnType()))
-            {
-                throw new InvalidRequestException("Method " + className + '.' + methodName + '(' + Arrays.toString(paramTypes) + ") " +
-                                                  "has incompatible return type " + method.getReturnType() + " (not assignable to " + jReturnType + ')');
-            }
+            MethodHandles.Lookup handles = MethodHandles.lookup();
+            MethodType methodType = MethodType.methodType(jReturnType, paramTypes);
+            MethodHandle handle = handles.findStatic(cls, methodName, methodType);
 
-            return method;
+            return handle;
         }
         catch (ClassNotFoundException e)
         {
@@ -92,7 +86,42 @@ final class ReflectionBasedUDF extends AbstractJavaUDF
         }
         catch (NoSuchMethodException e)
         {
-            throw new InvalidRequestException("Method " + className + '.' + methodName + '(' + Arrays.toString(paramTypes) + ") does not exist");
+            throw new InvalidRequestException("Method 'public static " + jReturnType.getName() + ' ' +
+                                              className + '.' + methodName + '(' + Arrays.toString(paramTypes) +
+                                              ")' does not exist - check for static, argument types and return type");
+        }
+        catch (IllegalAccessException e)
+        {
+            throw new InvalidRequestException("Method " + className + '.' + methodName + '(' + Arrays.toString(paramTypes) + ") is not accessible");
+        }
+    }
+
+    public ByteBuffer execute(List<ByteBuffer> parameters) throws InvalidRequestException
+    {
+        Object[] parms = new Object[argTypes.size()];
+        for (int i = 0; i < parms.length; i++)
+        {
+            ByteBuffer bb = parameters.get(i);
+            if (bb != null)
+                parms[i] = argTypes.get(i).compose(bb);
+        }
+
+        Object result;
+        try
+        {
+            result = method.invokeWithArguments(parms);
+            @SuppressWarnings("unchecked") ByteBuffer r = result != null ? ((AbstractType) returnType).decompose(result) : null;
+            return r;
+        }
+        catch (VirtualMachineError e)
+        {
+            // handle OutOfMemoryError and other fatals not here!
+            throw e;
+        }
+        catch (Throwable e)
+        {
+            logger.error("Invocation of function '{}' failed", this, e);
+            throw new InvalidRequestException("Invocation of function '" + this + "' failed: " + e);
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/dad0bfac/src/java/org/apache/cassandra/cql3/functions/UDFunction.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/functions/UDFunction.java b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java
index b4a706d..3ef5764 100644
--- a/src/java/org/apache/cassandra/cql3/functions/UDFunction.java
+++ b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java
@@ -77,11 +77,24 @@ public abstract class UDFunction extends AbstractFunction
         switch (language)
         {
             case "class": return new ReflectionBasedUDF(name, argNames, argTypes, returnType, language, body, deterministic);
-            case "java": return new JavaSourceBasedUDF(name, argNames, argTypes, returnType, language, body, deterministic);
+            case "java": return JavaSourceUDFFactory.buildUDF(name, argNames, argTypes, returnType, body, deterministic);
             default: throw new InvalidRequestException(String.format("Invalid language %s for '%s'", language, name));
         }
     }
 
+    static Class<?>[] javaParamTypes(List<AbstractType<?>> argTypes)
+    {
+        Class<?> paramTypes[] = new Class[argTypes.size()];
+        for (int i = 0; i < paramTypes.length; i++)
+            paramTypes[i] = javaType(argTypes.get(i));
+        return paramTypes;
+    }
+
+    static Class<?> javaType(AbstractType<?> type)
+    {
+        return type.getSerializer().getType();
+    }
+
     /**
      * It can happen that a function has been declared (is listed in the scheam) but cannot
      * be loaded (maybe only on some nodes). This is the case for instance if the class defining

http://git-wip-us.apache.org/repos/asf/cassandra/blob/dad0bfac/test/unit/org/apache/cassandra/cql3/UFTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/UFTest.java b/test/unit/org/apache/cassandra/cql3/UFTest.java
index 3a48500..5dd77bf 100644
--- a/test/unit/org/apache/cassandra/cql3/UFTest.java
+++ b/test/unit/org/apache/cassandra/cql3/UFTest.java
@@ -146,6 +146,9 @@ public class UFTest extends CQLTester
         // can't drop native functions
         assertInvalid("DROP FUNCTION dateof");
         assertInvalid("DROP FUNCTION uuid");
+
+        // sin() no longer exists
+        assertInvalid("SELECT key, sin(d) FROM %s");
     }
 
     @Test
@@ -213,6 +216,11 @@ public class UFTest extends CQLTester
     @Test
     public void testCreateOrReplaceJavaFunction() throws Throwable
     {
+        createTable("CREATE TABLE %s (key int primary key, val double)");
+        execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d);
+        execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2d);
+        execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3d);
+
         execute("create function foo::corjf ( input double ) returns double language java\n" +
                 "AS '\n" +
                 "  // parameter val is of type java.lang.Double\n" +
@@ -223,16 +231,25 @@ public class UFTest extends CQLTester
                 "  double v = Math.sin( input.doubleValue() );\n" +
                 "  return Double.valueOf(v);\n" +
                 "';");
+
+        // just check created function
+        assertRows(execute("SELECT key, val, foo::corjf(val) FROM %s"),
+                   row(1, 1d, Math.sin(1d)),
+                   row(2, 2d, Math.sin(2d)),
+                   row(3, 3d, Math.sin(3d))
+        );
+
         execute("create or replace function foo::corjf ( input double ) returns double language java\n" +
                 "AS '\n" +
-                "  // parameter val is of type java.lang.Double\n" +
-                "  /* return type is of type java.lang.Double */\n" +
-                "  if (input == null) {\n" +
-                "    return null;\n" +
-                "  }\n" +
-                "  double v = Math.sin( input.doubleValue() );\n" +
-                "  return Double.valueOf(v);\n" +
+                "  return input;\n" +
                 "';");
+
+        // check if replaced function returns correct result
+        assertRows(execute("SELECT key, val, foo::corjf(val) FROM %s"),
+                   row(1, 1d, 1d),
+                   row(2, 2d, 2d),
+                   row(3, 3d, 3d)
+        );
     }
 
     @Test