    001    /*
    002     * Copyright (c) 2007-2014 Concurrent, Inc. All Rights Reserved.
    003     *
    004     * Project and contact information: http://www.cascading.org/
    005     *
    006     * This file is part of the Cascading project.
    007     *
    008     * Licensed under the Apache License, Version 2.0 (the "License");
    009     * you may not use this file except in compliance with the License.
    010     * You may obtain a copy of the License at
    011     *
    012     *     http://www.apache.org/licenses/LICENSE-2.0
    013     *
    014     * Unless required by applicable law or agreed to in writing, software
    015     * distributed under the License is distributed on an "AS IS" BASIS,
    016     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    017     * See the License for the specific language governing permissions and
    018     * limitations under the License.
    019     */
    020
    021    package cascading.util;
    022
    023    import java.util.Map;
    024    import java.util.concurrent.ConcurrentHashMap;
    025    import java.util.regex.Pattern;
    026
    027    import cascading.operation.BaseOperation;
    028    import cascading.operation.Operation;
    029    import cascading.pipe.Pipe;
    030    import cascading.scheme.Scheme;
    031    import cascading.tap.Tap;
    032
    033    /**
    034     *
    035     */
    036    public class TraceUtil
    037      {
    038      /**
    039       * The set of regex patterns specifying fully qualified class or package names that serve as boundaries
    040       * for collecting traces and apiCalls in captureDebugTraceAndApiCall.
    041       */
    042      private static final Map<String, Pattern> registeredApiBoundaries = new ConcurrentHashMap<String, Pattern>();
    043
    044      private static interface TraceFormatter
    045        {
    046        String format( String trace );
    047        }
    048
    049      /**
    050       * Add a regex that serves as a boundary for tracing. That is to say any trace
    051       * captured by {@link #captureDebugTrace(Object)} will be from a caller that comes higher in the
    052       * stack than any apiBoundary package or class.
    053       *
    054       * @param apiBoundary
    055       */
    056      public static void registerApiBoundary( String apiBoundary )
    057        {
    058        registeredApiBoundaries.put( apiBoundary, Pattern.compile( apiBoundary ) );
    059        }
    060
    061      /**
    062       * Remove a regex as a boundary for tracing. See registerApiBoundary.
    063       *
    064       * @param apiBoundary
    065       */
    066      public static void unregisterApiBoundary( String apiBoundary )
    067        {
    068        registeredApiBoundaries.remove( apiBoundary );
    069        }
    070
    071      /**
    072       * Allows for custom trace fields on Pipe, Tap, and Scheme types
    073       *
    074       * @param object
    075       * @param trace
    076       */
    077      public static void setTrace( Object object, String trace )
    078        {
    079        Util.setInstanceFieldIfExists( object, "trace", trace );
    080        }
    081
    082      private static String formatTrace( Traceable traceable, String message, TraceFormatter formatter )
    083        {
    084        if( traceable == null )
    085          return message;
    086
    087        String trace = traceable.getTrace();
    088
    089        if( trace == null )
    090          return message;
    091
    092        return formatter.format( trace ) + " " + message;
    093        }
    094
    095      public static String formatTrace( final Scheme scheme, String message )
    096        {
    097        return formatTrace( scheme, message, new TraceFormatter()
    098        {
    099        @Override
    100        public String format( String trace )
    101          {
    102          return "[" + Util.truncate( scheme.toString(), 25 ) + "][" + trace + "]";
    103          }
    104        } );
    105        }
    106
    107      /**
    108       * Method formatRawTrace does not include the pipe name
    109       *
    110       * @param pipe    of type Pipe
    111       * @param message of type String
    112       * @return String
    113       */
    114      public static String formatRawTrace( Pipe pipe, String message )
    115        {
    116        return formatTrace( pipe, message, new TraceFormatter()
    117        {
    118        @Override
    119        public String format( String trace )
    120          {
    121          return "[" + trace + "]";
    122          }
    123        } );
    124        }
    125
    126      public static String formatTrace( final Pipe pipe, String message )
    127        {
    128        return formatTrace( pipe, message, new TraceFormatter()
    129        {
    130        @Override
    131        public String format( String trace )
    132          {
    133          return "[" + Util.truncate( pipe.getName(), 25 ) + "][" + trace + "]";
    134          }
    135        } );
    136        }
    137
    138      public static String formatTrace( final Tap tap, String message )
    139        {
    140        return formatTrace( tap, message, new TraceFormatter()
    141        {
    142        @Override
    143        public String format( String trace )
    144          {
    145          return "[" + Util.truncate( tap.toString(), 25 ) + "][" + trace + "]";
    146          }
    147        } );
    148        }
    149
    150      public static String formatTrace( Operation operation, String message )
    151        {
    152        if( !( operation instanceof BaseOperation ) )
    153          return message;
    154
    155        String trace = ( (BaseOperation) operation ).getTrace();
    156
    157        if( trace == null )
    158          return message;
    159
    160        return "[" + trace + "] " + message;
    161        }
    162
    163      public static String captureDebugTrace( Object target )
    164        {
    165        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
    166
    167        StackTraceElement candidateUserCodeElement = null;
    168        StackTraceElement apiCallElement = null;
    169        Class<?> tracingBoundary = target.getClass();
    170        String boundaryClassName = tracingBoundary.getName();
    171
    172        // walk from the bottom of the stack(which is at the end of the array) upwards towards any boundary.
    173        // The apiCall is the element at the boundary and the previous stack element is the user code
    174        for( int i = stackTrace.length - 1; i >= 0; i-- )
    175          {
    176          StackTraceElement stackTraceElement = stackTrace[ i ];
    177          String stackClassName = stackTraceElement.getClassName();
    178
    179          boolean atApiBoundary = atApiBoundary( stackTraceElement.toString() );
    180
    181          if( ( stackClassName != null && ( stackClassName.startsWith( boundaryClassName ) ) || atApiBoundary ) )
    182            {
    183            // only record the apiCallElement if we're at an apiBoundary. That means
    184            // Trace will only have "apiMethod() @ call_location" when a registered
    185            // api boundary is found. The default for Cascading will just have
    186            // call_location.
    187            if( atApiBoundary )
    188              apiCallElement = stackTraceElement;
    189
    190            break;
    191            }
    192
    193          candidateUserCodeElement = stackTraceElement;
    194          }
    195
    196        String userCode = candidateUserCodeElement == null ? "" : candidateUserCodeElement.toString();
    197        String apiCall = "";
    198
    199        if( apiCallElement != null )
    200          {
    201          String method = apiCallElement.getMethodName();
    202
    203          if( method.equals( "<init>" ) )
    204            apiCall = String.format( "new %s()", getSimpleClassName( apiCallElement.getClassName() ) );
    205          else
    206            apiCall = String.format( "%s()", method );
    207          }
    208
    209        return userCode.isEmpty() ? apiCall : apiCall.isEmpty() ? userCode : String.format( "%s @ %s", apiCall, userCode );
    210        }
    211
    212      private static Object getSimpleClassName( String className )
    213        {
    214        if( className == null || className.isEmpty() )
    215          return "";
    216
    217        String parts[] = className.split( "\\." );
    218
    219        if( parts.length == 0 )
    220          return "";
    221
    222        return parts[ parts.length - 1 ];
    223        }
    224
    225      private static boolean atApiBoundary( String stackTraceElement )
    226        {
    227        for( Pattern boundary : registeredApiBoundaries.values() )
    228          {
    229          if( boundary.matcher( stackTraceElement ).matches() )
    230            return true;
    231          }
    232
    233        return false;
    234        }
    235      }