Java tutorial
/* * 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