org.apache.drill.jdbc.proxy.InvocationReporterImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.drill.jdbc.proxy.InvocationReporterImpl.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.drill.jdbc.proxy;

import java.lang.reflect.Method;
import java.sql.DriverPropertyInfo;

import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * Implementation of InvocationReporter.
 * <p>
 *   Currently, just writes to System.err.
 * </p>
 */
class InvocationReporterImpl implements InvocationReporter {
    private static final String LINE_PREFIX = "TRACER: ";
    private static final String SETUP_LINE_PREFIX = LINE_PREFIX + "SETUP: ";
    private static final String WARNING_LINE_PREFIX = LINE_PREFIX + "WARNING: ";
    private static final String CALL_LINE_PREFIX = LINE_PREFIX + "CALL:   ";
    private static final String RETURN_LINE_PREFIX = LINE_PREFIX + "RETURN: ";
    private static final String THROW_LINE_PREFIX = LINE_PREFIX + "THROW:  ";

    private static final Set<Package> JDBC_PACKAGES;
    static {
        // Load some class in each JDBC package below so package exists for
        // getPackage():
        // Suppressed because we intentionally are only assigning to the variable.
        @SuppressWarnings("unused")
        Class<?> someReference;
        someReference = java.sql.Connection.class;
        someReference = javax.sql.PooledConnection.class;
        someReference = javax.sql.rowset.BaseRowSet.class;
        someReference = javax.sql.rowset.serial.SerialJavaObject.class;
        someReference = javax.sql.rowset.spi.SyncFactory.class;

        Set<Package> set = new HashSet<>();
        set.add(Package.getPackage("java.sql"));
        set.add(Package.getPackage("javax.sql"));
        set.add(Package.getPackage("javax.sql.rowset"));
        set.add(Package.getPackage("javax.sql.rowset.serial"));
        set.add(Package.getPackage("javax.sql.rowset.spi"));
        for (Package p : set) {
            assert null != p : "null Package; missing reference to class in that package?";
        }
        JDBC_PACKAGES = Collections.unmodifiableSet(set);
    }

    /** Common packages whose names to suppress in rendered type names. */
    // (Is sorted for abbreviated-packages message to user.)
    private static final SortedSet<Package> PACKAGES_TO_ABBREVIATE;
    static {
        SortedSet<Package> set = new TreeSet<Package>(new Comparator<Package>() {
            @Override
            public int compare(Package o1, Package o2) {
                return null == o1 ? -1 : null == o2 ? 1 : o1.getName().compareTo(o2.getName());
            }
        });
        set.addAll(JDBC_PACKAGES);
        set.add(Package.getPackage("java.util"));
        set.add(Package.getPackage("java.lang"));
        PACKAGES_TO_ABBREVIATE = Collections.unmodifiableSortedSet(set);
    }

    private int lastObjNum = 0;
    private Map<Object, String> objectsToIdsMap = new IdentityHashMap<>();

    void reportAbbreviatedPackages() {
        final List<String> names = new ArrayList<>();
        for (Package p : PACKAGES_TO_ABBREVIATE) {
            names.add(p.getName());
        }
        setupMessage("Abbreviating (unique) class names in packages " + StringUtils.join(names, ", ") + ".");
    }

    ////////////////////
    // Line-output output methods:

    // TODO:  When needed, allow output to something other then System.err
    // (e.g., appending to file, System.out).  Decide control--probably parameter
    // in proxy URL and/or JVM system property (checked higher up and configuring
    // this class).

    /**
     * Prints a line to the tracing log.
     * <p>
     *   Is it intended that all tracing output goes through this method.
     * </p>
     */
    private void printTraceLine(final String line) {
        System.err.println(line);
    }

    /**
     * For warnings such as warning about encountering a type that for which
     * rendering isn't known to show values well.
     */
    private void printWarningLine(final String line) {
        printTraceLine(WARNING_LINE_PREFIX + line);
    }

    ////////////////////
    // Type, value, exception, and arguments formatting methods:

    private String getObjectId(final Object object) {
        String id;
        if (null == object) {
            id = "n/a";
        } else {
            id = objectsToIdsMap.get(object);
            if (null == id) {
                ++lastObjNum;
                id = Integer.toString(lastObjNum);
                objectsToIdsMap.put(object, id);
            }
        }
        return id;
    }

    /**
     * Renders a type name.  Uses simple names for common types (JDBC interfaces
     * and {code java.lang.*}).
     */
    private String formatType(final Class<?> type) {
        final String result;
        if (type.isArray()) {
            result = formatType(type.getComponentType()) + "[]";
        } else {
            // Suppress package name for common (JDBC and java.lang) types, except
            // when would be ambiguous (e.g., java.sql.Date vs. java.util.Date).
            if (PACKAGES_TO_ABBREVIATE.contains(type.getPackage())) {
                int sameSimpleNameCount = 0;
                for (Package p : PACKAGES_TO_ABBREVIATE) {
                    try {
                        Class.forName(p.getName() + "." + type.getSimpleName());
                        sameSimpleNameCount++;
                    } catch (ClassNotFoundException e) {
                        // Nothing to do.
                    }
                }
                if (1 == sameSimpleNameCount) {
                    result = type.getSimpleName();
                } else {
                    // Multiple classes with same simple name, so would be ambiguous to
                    // abbreviate, so use fully qualified name.
                    result = type.getName();
                }
            } else {
                result = type.getName();
            }
        }
        return result;
    }

    private String formatString(final String value) {
        return "\"" + (((String) value).replace("\\", "\\\\") // first encode backslashes (esc. char.)
                .replace("\"", "\\\"") // then encode quotes (via backslash)
                .replace("\n", "\\n") // then encode newlines
        // Anything else?
        ) + "\"";
    }

    private String formatDriverPropertyInfo(final DriverPropertyInfo info) {
        return "[ " + "name = " + formatValue(info.name) + ", value = " + formatValue(info.value) + ", required = "
                + info.required + ", choices = " + formatValue(info.choices) + ", description = "
                + formatValue(info.description) + " ]";
    }

    private String formatValue(final Object value) {
        final String result;
        if (null == value) {
            result = "null";
        } else {
            final Class<?> rawActualType = value.getClass();
            if (String.class == rawActualType) {
                result = formatString((String) value);
            } else if (rawActualType.isArray() && !rawActualType.getComponentType().isPrimitive()) {
                // Array of non-primitive type

                final StringBuilder buffer = new StringBuilder();
                /* Decide whether to includes this:
                buffer.append( formatType( elemType ) );
                buffer.append( "[] " );
                */
                buffer.append("{ ");
                boolean first = true;
                for (Object elemVal : (Object[]) value) {
                    if (!first) {
                        buffer.append(", ");
                    }
                    first = false;
                    buffer.append(formatValue(elemVal));
                }
                buffer.append(" }");
                result = buffer.toString();
            } else if (DriverPropertyInfo.class == rawActualType) {
                result = formatDriverPropertyInfo((DriverPropertyInfo) value);
            } else if (
            // Is type seen and whose toString() renders value well.
            false || rawActualType == java.lang.Boolean.class || rawActualType == java.lang.Byte.class
                    || rawActualType == java.lang.Double.class || rawActualType == java.lang.Float.class
                    || rawActualType == java.lang.Integer.class || rawActualType == java.lang.Long.class
                    || rawActualType == java.lang.Short.class || rawActualType == java.math.BigDecimal.class
                    || rawActualType == java.lang.Class.class || rawActualType == java.sql.Date.class
                    || rawActualType == java.sql.Timestamp.class) {
                result = value.toString();
            } else if (
            // Is type seen and whose toString() has rendered value well--in cases
            // seen so far.
            false || rawActualType == java.util.Properties.class || rawActualType.isEnum()) {
                result = value.toString();
            } else if (
            // Is type to warn about (one case).
            false || rawActualType == org.apache.drill.jdbc.DrillResultSet.class) {
                printWarningLine("Class " + rawActualType.getName() + " should be an interface."
                        + " (While it's a class, it can't be proxied, and some methods can't" + " be traced.)");
                result = value.toString();
            } else if (
            // Is type to warn about (second case).
            false || rawActualType == org.apache.hadoop.io.Text.class || rawActualType == org.joda.time.Period.class
                    || rawActualType == org.apache.drill.exec.vector.accessor.sql.TimePrintMillis.class) {
                printWarningLine("Should " + rawActualType + " be appearing at JDBC interface?");
                result = value.toString();
            } else {
                // Is other type--unknown whether it already formats well.
                // (No handled yet: byte[].)
                printWarningLine("Unnoted type encountered in formatting (value might" + " not render well): "
                        + rawActualType + ".");
                result = value.toString();
            }
        }
        return result;
    }

    /**
     * Renders a value with its corresponding <em>declared</em> type.
     *
     * @param  declaredType
     *         the corresponding declared method parameter or return type
     * @value  value
     *         the value to render
     */
    private String formatTypeAndValue(Class<?> declaredType, Object value) {
        final String declaredTypePart = "(" + formatType(declaredType) + ") ";

        final String actualTypePart;
        final String actualValuePart;
        if (null == value) {
            // Null--show no actual type or object ID.
            actualTypePart = "";
            actualValuePart = formatValue(value);
        } else {
            // Non-null value--show at least some representation of value.
            Class<?> rawActualType = value.getClass();
            Class<?> origActualType = declaredType.isPrimitive() ? declaredType : rawActualType;
            if (String.class == rawActualType) {
                // String--show no actual type or object ID.
                actualTypePart = "";
                actualValuePart = formatValue(value);
            } else if (origActualType.isPrimitive()) {
                // Primitive type--show no actual type or object ID.
                actualTypePart = "";
                // (Remember--primitive type is wrapped here.)
                actualValuePart = value.toString();
            } else {
                // Non-primitive, non-String value--include object ID.
                final String idPrefix = "<id=" + getObjectId(value) + "> ";
                if (declaredType.isInterface() && JDBC_PACKAGES.contains(declaredType.getPackage())) {
                    // JDBC interface implementation class--show no actual type or value
                    // (because object is proxied and therefore all uses will be traced).
                    actualTypePart = "";
                    actualValuePart = idPrefix + "...";
                } else if (origActualType == declaredType) {
                    // Actual type is same as declared--don't show redundant actual type.
                    actualTypePart = "";
                    actualValuePart = idPrefix + formatValue(value);
                } else {
                    // Other--show actual type and (try to) show value.
                    actualTypePart = "(" + formatType(rawActualType) + ") ";
                    actualValuePart = idPrefix + formatValue(value);
                }
            }
        }
        final String result = declaredTypePart + actualTypePart + actualValuePart;
        return result;
    }

    /**
     * Renders given type and value of target (receiver) of method call.
     */
    private String formatTargetTypeAndValue(Class<?> declaredType, Object value) {
        return formatTypeAndValue(declaredType, value);
    }

    /**
     * Renders given type and value of method call argument.
     */
    // Expect JDBC interface types; (mostly) doesn't expect other JDBC types;
    // expect mostly primitives and String.
    private String formatArgTypeAndValue(Class<?> declaredType, Object value) {
        return formatTypeAndValue(declaredType, value);
    }

    /**
     * Renders given type and value of method return value.
     */
    // Expect declared type Object (need actual type); expect primitive types
    // (maybe wrapper classes too?)
    private String formatReturnTypeAndValue(Class<?> declaredType, Object value) {
        return formatTypeAndValue(declaredType, value);
    }

    /**
     * Renders given exception.
     * Includes test of chained exceptions.
     */
    private String formatThrowable(final Throwable thrown) {
        final StringBuffer s = new StringBuffer();
        boolean first = true;
        Throwable current = thrown;
        while (null != current) {
            if (!first) {
                s.append(" ==> ");
            }
            first = false;

            s.append("(");
            s.append(formatType(current.getClass()));
            s.append(") ");
            s.append(formatString(current.toString()));
            current = current.getCause();
        }
        final String result = s.toString();
        return result;
    }

    /**
     * Renders corresponding given sequence of declared types and given sequence
     * of values from method call.
     */
    private String formatArgs(Class<?>[] declaredTypes, Object[] argValues) {
        final String result;
        if (null == argValues) {
            result = "()";
        } else {
            final StringBuilder s = new StringBuilder();
            s.append("( ");
            for (int ax = 0; ax < argValues.length; ax++) {
                if (ax > 0) {
                    s.append(", ");
                }
                s.append(formatArgTypeAndValue(declaredTypes[ax], argValues[ax]));
            }
            s.append(" )");
            result = s.toString();
        }
        return result;
    }

    /**
     * Renders the call part for a method call, method return, or exception-thrown
     * event.
     * @param target
     *        the target (receiver) of the method call
     * @param targetType
     *        the interface containing called method
     * @param method
     *        the called method
     * @param argValues
     *        the argument values (represented as for {@link Method#invoke})
     *
     */
    private String formatCallPart(final Object target, final Class<?> targetType, final Method method,
            final Object[] argValues) {
        return "(" + formatTargetTypeAndValue(targetType, target) + ") . " + method.getName()
                + formatArgs(method.getParameterTypes(), argValues);
    }

    ////////////////////
    // Invocation-level methods:

    @Override
    public void setupMessage(final String message) {
        printTraceLine(SETUP_LINE_PREFIX + message);
    }

    @Override
    public void methodCalled(final Object target, final Class<?> targetType, final Method method,
            final Object[] args) {
        printTraceLine(CALL_LINE_PREFIX + formatCallPart(target, targetType, method, args));
    }

    @Override
    public void methodReturned(final Object target, final Class<?> targetType, final Method method,
            final Object[] args, final Object result) {
        final String callPart = RETURN_LINE_PREFIX + formatCallPart(target, targetType, method, args);
        if (void.class == method.getReturnType()) {
            assert null == result : "unexpected non-null result value " + result + " for method returning "
                    + method.getReturnType();
            printTraceLine(callPart + ", RESULT: (none--void) ");
        } else {
            printTraceLine(callPart + ", RESULT: " + formatReturnTypeAndValue(method.getReturnType(), result));
        }
    }

    @Override
    public void methodThrew(final Object target, final Class<?> targetType, final Method method,
            final Object[] args, final Throwable exception) {
        printTraceLine(THROW_LINE_PREFIX + formatCallPart(target, targetType, method, args) + ", threw: "
                + formatThrowable(exception));
    }

} // class SimpleInvocationReporter