All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glowroot.shaded.h2.engine.FunctionAlias Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2004-2013 H2 Group. Multiple-Licensed under the H2 License,
 * Version 1.0, and under the Eclipse Public License, Version 1.0
 * (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.glowroot.shaded.h2.engine;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Arrays;
import org.glowroot.shaded.h2.Driver;
import org.glowroot.shaded.h2.api.ErrorCode;
import org.glowroot.shaded.h2.command.Parser;
import org.glowroot.shaded.h2.expression.Expression;
import org.glowroot.shaded.h2.message.DbException;
import org.glowroot.shaded.h2.message.Trace;
import org.glowroot.shaded.h2.schema.Schema;
import org.glowroot.shaded.h2.schema.SchemaObjectBase;
import org.glowroot.shaded.h2.table.Table;
import org.glowroot.shaded.h2.util.New;
import org.glowroot.shaded.h2.util.SourceCompiler;
import org.glowroot.shaded.h2.util.StatementBuilder;
import org.glowroot.shaded.h2.util.StringUtils;
import org.glowroot.shaded.h2.util.Utils;
import org.glowroot.shaded.h2.value.DataType;
import org.glowroot.shaded.h2.value.Value;
import org.glowroot.shaded.h2.value.ValueArray;
import org.glowroot.shaded.h2.value.ValueNull;

/**
 * Represents a user-defined function, or alias.
 *
 * @author Thomas Mueller
 * @author Gary Tong
 */
public class FunctionAlias extends SchemaObjectBase {

    private String className;
    private String methodName;
    private String source;
    private JavaMethod[] javaMethods;
    private boolean deterministic;
    private boolean bufferResultSetToLocalTemp = true;

    private FunctionAlias(Schema schema, int id, String name) {
        initSchemaObjectBase(schema, id, name, Trace.FUNCTION);
    }

    /**
     * Create a new alias based on a method name.
     *
     * @param schema the schema
     * @param id the id
     * @param name the name
     * @param javaClassMethod the class and method name
     * @param force create the object even if the class or method does not exist
     * @param bufferResultSetToLocalTemp whether the result should be buffered
     * @return the database object
     */
    public static FunctionAlias newInstance(
            Schema schema, int id, String name, String javaClassMethod,
            boolean force, boolean bufferResultSetToLocalTemp) {
        FunctionAlias alias = new FunctionAlias(schema, id, name);
        int paren = javaClassMethod.indexOf('(');
        int lastDot = javaClassMethod.lastIndexOf('.', paren < 0 ?
                javaClassMethod.length() : paren);
        if (lastDot < 0) {
            throw DbException.get(ErrorCode.SYNTAX_ERROR_1, javaClassMethod);
        }
        alias.className = javaClassMethod.substring(0, lastDot);
        alias.methodName = javaClassMethod.substring(lastDot + 1);
        alias.bufferResultSetToLocalTemp = bufferResultSetToLocalTemp;
        alias.init(force);
        return alias;
    }

    /**
     * Create a new alias based on source code.
     *
     * @param schema the schema
     * @param id the id
     * @param name the name
     * @param source the source code
     * @param force create the object even if the class or method does not exist
     * @param bufferResultSetToLocalTemp whether the result should be buffered
     * @return the database object
     */
    public static FunctionAlias newInstanceFromSource(
            Schema schema, int id, String name, String source, boolean force,
            boolean bufferResultSetToLocalTemp) {
        FunctionAlias alias = new FunctionAlias(schema, id, name);
        alias.source = source;
        alias.bufferResultSetToLocalTemp = bufferResultSetToLocalTemp;
        alias.init(force);
        return alias;
    }

    private void init(boolean force) {
        try {
            // at least try to compile the class, otherwise the data type is not
            // initialized if it could be
            load();
        } catch (DbException e) {
            if (!force) {
                throw e;
            }
        }
    }

    private synchronized void load() {
        if (javaMethods != null) {
            return;
        }
        if (source != null) {
            loadFromSource();
        } else {
            loadClass();
        }
    }

    private void loadFromSource() {
        SourceCompiler compiler = database.getCompiler();
        synchronized (compiler) {
            String fullClassName = Constants.USER_PACKAGE + "." + getName();
            compiler.setSource(fullClassName, source);
            try {
                Method m = compiler.getMethod(fullClassName);
                JavaMethod method = new JavaMethod(m, 0);
                javaMethods = new JavaMethod[] {
                        method
                };
            } catch (DbException e) {
                throw e;
            } catch (Exception e) {
                throw DbException.get(ErrorCode.SYNTAX_ERROR_1, e, source);
            }
        }
    }

    private void loadClass() {
        Class javaClass = Utils.loadUserClass(className);
        Method[] methods = javaClass.getMethods();
        ArrayList list = New.arrayList();
        for (int i = 0, len = methods.length; i < len; i++) {
            Method m = methods[i];
            if (!Modifier.isStatic(m.getModifiers())) {
                continue;
            }
            if (m.getName().equals(methodName) ||
                    getMethodSignature(m).equals(methodName)) {
                JavaMethod javaMethod = new JavaMethod(m, i);
                for (JavaMethod old : list) {
                    if (old.getParameterCount() == javaMethod.getParameterCount()) {
                        throw DbException.get(ErrorCode.
                                METHODS_MUST_HAVE_DIFFERENT_PARAMETER_COUNTS_2,
                                old.toString(), javaMethod.toString());
                    }
                }
                list.add(javaMethod);
            }
        }
        if (list.size() == 0) {
            throw DbException.get(
                    ErrorCode.PUBLIC_STATIC_JAVA_METHOD_NOT_FOUND_1,
                    methodName + " (" + className + ")");
        }
        javaMethods = new JavaMethod[list.size()];
        list.toArray(javaMethods);
        // Sort elements. Methods with a variable number of arguments must be at
        // the end. Reason: there could be one method without parameters and one
        // with a variable number. The one without parameters needs to be used
        // if no parameters are given.
        Arrays.sort(javaMethods);
    }

    private static String getMethodSignature(Method m) {
        StatementBuilder buff = new StatementBuilder(m.getName());
        buff.append('(');
        for (Class p : m.getParameterTypes()) {
            // do not use a space here, because spaces are removed
            // in CreateFunctionAlias.setJavaClassMethod()
            buff.appendExceptFirst(",");
            if (p.isArray()) {
                buff.append(p.getComponentType().getName()).append("[]");
            } else {
                buff.append(p.getName());
            }
        }
        return buff.append(')').toString();
    }

    @Override
    public String getCreateSQLForCopy(Table table, String quotedName) {
        throw DbException.throwInternalError();
    }

    @Override
    public String getDropSQL() {
        return "DROP ALIAS IF EXISTS " + getSQL();
    }

    @Override
    public String getSQL() {
        // TODO can remove this method once FUNCTIONS_IN_SCHEMA is enabled
        if (database.getSettings().functionsInSchema ||
                !getSchema().getName().equals(Constants.SCHEMA_MAIN)) {
            return super.getSQL();
        }
        return Parser.quoteIdentifier(getName());
    }

    @Override
    public String getCreateSQL() {
        StringBuilder buff = new StringBuilder("CREATE FORCE ALIAS ");
        buff.append(getSQL());
        if (deterministic) {
            buff.append(" DETERMINISTIC");
        }
        if (!bufferResultSetToLocalTemp) {
            buff.append(" NOBUFFER");
        }
        if (source != null) {
            buff.append(" AS ").append(StringUtils.quoteStringSQL(source));
        } else {
            buff.append(" FOR ").append(Parser.quoteIdentifier(
                    className + "." + methodName));
        }
        return buff.toString();
    }

    @Override
    public int getType() {
        return DbObject.FUNCTION_ALIAS;
    }

    @Override
    public synchronized void removeChildrenAndResources(Session session) {
        database.removeMeta(session, getId());
        className = null;
        methodName = null;
        javaMethods = null;
        invalidate();
    }

    @Override
    public void checkRename() {
        throw DbException.getUnsupportedException("RENAME");
    }

    /**
     * Find the Java method that matches the arguments.
     *
     * @param args the argument list
     * @return the Java method
     * @throws DbException if no matching method could be found
     */
    public JavaMethod findJavaMethod(Expression[] args) {
        load();
        int parameterCount = args.length;
        for (JavaMethod m : javaMethods) {
            int count = m.getParameterCount();
            if (count == parameterCount || (m.isVarArgs() &&
                    count <= parameterCount + 1)) {
                return m;
            }
        }
        throw DbException.get(ErrorCode.METHOD_NOT_FOUND_1, getName() + " (" +
                className + ", parameter count: " + parameterCount + ")");
    }

    public String getJavaClassName() {
        return this.className;
    }

    public String getJavaMethodName() {
        return this.methodName;
    }

    /**
     * Get the Java methods mapped by this function.
     *
     * @return the Java methods.
     */
    public JavaMethod[] getJavaMethods() {
        load();
        return javaMethods;
    }

    public void setDeterministic(boolean deterministic) {
        this.deterministic = deterministic;
    }

    public boolean isDeterministic() {
        return deterministic;
    }

    public String getSource() {
        return source;
    }

    /**
     * Checks if the given method takes a variable number of arguments. For Java
     * 1.4 and older, false is returned. Example:
     * 
     * public static double mean(double... values)
     * 
* * @param m the method to test * @return true if the method takes a variable number of arguments. */ static boolean isVarArgs(Method m) { if ("1.5".compareTo(SysProperties.JAVA_SPECIFICATION_VERSION) > 0) { return false; } try { Method isVarArgs = m.getClass().getMethod("isVarArgs"); Boolean result = (Boolean) isVarArgs.invoke(m); return result.booleanValue(); } catch (Exception e) { return false; } } /** * Should the return value ResultSet be buffered in a local temporary file? * * @return true if yes */ public boolean isBufferResultSetToLocalTemp() { return bufferResultSetToLocalTemp; } /** * There may be multiple Java methods that match a function name. * Each method must have a different number of parameters however. * This helper class represents one such method. */ public static class JavaMethod implements Comparable { private final int id; private final Method method; private final int dataType; private boolean hasConnectionParam; private boolean varArgs; private Class varArgClass; private int paramCount; JavaMethod(Method method, int id) { this.method = method; this.id = id; Class[] paramClasses = method.getParameterTypes(); paramCount = paramClasses.length; if (paramCount > 0) { Class paramClass = paramClasses[0]; if (Connection.class.isAssignableFrom(paramClass)) { hasConnectionParam = true; paramCount--; } } if (paramCount > 0) { Class lastArg = paramClasses[paramClasses.length - 1]; if (lastArg.isArray() && FunctionAlias.isVarArgs(method)) { varArgs = true; varArgClass = lastArg.getComponentType(); } } Class returnClass = method.getReturnType(); dataType = DataType.getTypeFromClass(returnClass); } @Override public String toString() { return method.toString(); } /** * Check if this function requires a database connection. * * @return if the function requires a connection */ public boolean hasConnectionParam() { return this.hasConnectionParam; } /** * Call the user-defined function and return the value. * * @param session the session * @param args the argument list * @param columnList true if the function should only return the column * list * @return the value */ public Value getValue(Session session, Expression[] args, boolean columnList) { Class[] paramClasses = method.getParameterTypes(); Object[] params = new Object[paramClasses.length]; int p = 0; if (hasConnectionParam && params.length > 0) { params[p++] = session.createConnection(columnList); } // allocate array for varArgs parameters Object varArg = null; if (varArgs) { int len = args.length - params.length + 1 + (hasConnectionParam ? 1 : 0); varArg = Array.newInstance(varArgClass, len); params[params.length - 1] = varArg; } for (int a = 0, len = args.length; a < len; a++, p++) { boolean currentIsVarArg = varArgs && p >= paramClasses.length - 1; Class paramClass; if (currentIsVarArg) { paramClass = varArgClass; } else { paramClass = paramClasses[p]; } int type = DataType.getTypeFromClass(paramClass); Value v = args[a].getValue(session); Object o; if (Value.class.isAssignableFrom(paramClass)) { o = v; } else if (v.getType() == Value.ARRAY && paramClass.isArray() && paramClass.getComponentType() != Object.class) { Value[] array = ((ValueArray) v).getList(); Object[] objArray = (Object[]) Array.newInstance( paramClass.getComponentType(), array.length); int componentType = DataType.getTypeFromClass( paramClass.getComponentType()); for (int i = 0; i < objArray.length; i++) { objArray[i] = array[i].convertTo(componentType).getObject(); } o = objArray; } else { v = v.convertTo(type); o = v.getObject(); } if (o == null) { if (paramClass.isPrimitive()) { if (columnList) { // If the column list is requested, the parameters // may be null. Need to set to default value, // otherwise the function can't be called at all. o = DataType.getDefaultForPrimitiveType(paramClass); } else { // NULL for a java primitive: return NULL return ValueNull.INSTANCE; } } } else { if (!paramClass.isAssignableFrom(o.getClass()) && !paramClass.isPrimitive()) { o = DataType.convertTo(session.createConnection(false), v, paramClass); } } if (currentIsVarArg) { Array.set(varArg, p - params.length + 1, o); } else { params[p] = o; } } boolean old = session.getAutoCommit(); Value identity = session.getLastScopeIdentity(); boolean defaultConnection = session.getDatabase(). getSettings().defaultConnection; try { session.setAutoCommit(false); Object returnValue; try { if (defaultConnection) { Driver.setDefaultConnection( session.createConnection(columnList)); } returnValue = method.invoke(null, params); if (returnValue == null) { return ValueNull.INSTANCE; } } catch (InvocationTargetException e) { StatementBuilder buff = new StatementBuilder(method.getName()); buff.append('('); for (Object o : params) { buff.appendExceptFirst(", "); buff.append(o == null ? "null" : o.toString()); } buff.append(')'); throw DbException.convertInvocation(e, buff.toString()); } catch (Exception e) { throw DbException.convert(e); } if (Value.class.isAssignableFrom(method.getReturnType())) { return (Value) returnValue; } Value ret = DataType.convertToValue(session, returnValue, dataType); return ret.convertTo(dataType); } finally { session.setLastScopeIdentity(identity); session.setAutoCommit(old); if (defaultConnection) { Driver.setDefaultConnection(null); } } } public Class[] getColumnClasses() { return method.getParameterTypes(); } public int getDataType() { return dataType; } public int getParameterCount() { return paramCount; } public boolean isVarArgs() { return varArgs; } @Override public int compareTo(JavaMethod m) { if (varArgs != m.varArgs) { return varArgs ? 1 : -1; } if (paramCount != m.paramCount) { return paramCount - m.paramCount; } if (hasConnectionParam != m.hasConnectionParam) { return hasConnectionParam ? 1 : -1; } return id - m.id; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy