View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements. See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache license, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License. You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the license for the specific language governing permissions and
    15   * limitations under the license.
    16   */
    17  package org.apache.logging.log4j.core.appender.db.jpa.converter;
    18
    19  import java.lang.reflect.Constructor;
    20  import java.lang.reflect.Field;
    21  import java.util.ArrayList;
    22  import java.util.Arrays;
    23  import java.util.List;
    24  import java.util.ListIterator;
    25
    26  import javax.persistence.AttributeConverter;
    27  import javax.persistence.Converter;
    28
    29  import org.apache.logging.log4j.core.util.Loader;
    30  import org.apache.logging.log4j.util.Strings;
    31
    32  /**
    33   * A JPA 2.1 attribute converter for {@link Throwable}s in {@link org.apache.logging.log4j.core.LogEvent}s. This
    34   * converter is capable of converting both to and from {@link String}s.
    35   */
    36  @Converter(autoApply = false)
    37  public class ThrowableAttributeConverter implements AttributeConverter<Throwable, String> {
    38      private static final int CAUSED_BY_STRING_LENGTH = 10;
    39
    40      private static final Field THROWABLE_CAUSE;
    41
    42      private static final Field THROWABLE_MESSAGE;
    43
    44      static {
    45          try {
    46              THROWABLE_CAUSE = Throwable.class.getDeclaredField("cause");
    47              THROWABLE_CAUSE.setAccessible(true);
    48              THROWABLE_MESSAGE = Throwable.class.getDeclaredField("detailMessage");
    49              THROWABLE_MESSAGE.setAccessible(true);
    50          } catch (final NoSuchFieldException e) {
    51              throw new IllegalStateException("Something is wrong with java.lang.Throwable.", e);
    52          }
    53      }
    54
    55      @Override
    56      public String convertToDatabaseColumn(final Throwable throwable) {
    57          if (throwable == null) {
    58              return null;
    59          }
    60
    61          final StringBuilder builder = new StringBuilder();
    62          this.convertThrowable(builder, throwable);
    63          return builder.toString();
    64      }
    65
    66      private void convertThrowable(final StringBuilder builder, final Throwable throwable) {
    67          builder.append(throwable.toString()).append('\n');
    68          for (final StackTraceElement element : throwable.getStackTrace()) {
    69              builder.append("\tat ").append(element).append('\n');
    70          }
    71          if (throwable.getCause() != null) {
    72              builder.append("Caused by ");
    73              this.convertThrowable(builder, throwable.getCause());
    74          }
    75      }
    76
    77      @Override
    78      public Throwable convertToEntityAttribute(final String s) {
    79          if (Strings.isEmpty(s)) {
    80              return null;
    81          }
    82
    83          final List<String> lines = Arrays.asList(s.split("(\n|\r\n)"));
    84          return this.convertString(lines.listIterator(), false);
    85      }
    86
    87      private Throwable convertString(final ListIterator<String> lines, final boolean removeCausedBy) {
    88          String firstLine = lines.next();
    89          if (removeCausedBy) {
    90              firstLine = firstLine.substring(CAUSED_BY_STRING_LENGTH);
    91          }
    92          final int colon = firstLine.indexOf(":");
    93          String throwableClassName;
    94          String message = null;
    95          if (colon > 1) {
    96              throwableClassName = firstLine.substring(0, colon);
    97              if (firstLine.length() > colon + 1) {
    98                  message = firstLine.substring(colon + 1).trim();
    99              }
    100         } else {
    101             throwableClassName = firstLine;
    102         }
    103
    104         final List<StackTraceElement> stackTrace = new ArrayList<StackTraceElement>();
    105         Throwable cause = null;
    106         while (lines.hasNext()) {
    107             final String line = lines.next();
    108
    109             if (line.startsWith("Caused by ")) {
    110                 lines.previous();
    111                 cause = convertString(lines, true);
    112                 break;
    113             }
    114
    115             stackTrace.add(
    116                     StackTraceElementAttributeConverter.convertString(line.trim().substring(3).trim())
    117             );
    118         }
    119
    120         return this.getThrowable(throwableClassName, message, cause,
    121                 stackTrace.toArray(new StackTraceElement[stackTrace.size()]));
    122     }
    123
    124     private Throwable getThrowable(final String throwableClassName, final String message, final Throwable cause,
    125                                    final StackTraceElement[] stackTrace) {
    126         try {
    127             @SuppressWarnings("unchecked")
    128             final Class<Throwable> throwableClass = (Class<Throwable>) Loader.loadClass(throwableClassName);
    129
    130             if (!Throwable.class.isAssignableFrom(throwableClass)) {
    131                 return null;
    132             }
    133
    134             Throwable throwable;
    135             if (message != null && cause != null) {
    136                 throwable = this.getThrowable(throwableClass, message, cause);
    137                 if (throwable == null) {
    138                     throwable = this.getThrowable(throwableClass, cause);
    139                     if (throwable == null) {
    140                         throwable = this.getThrowable(throwableClass, message);
    141                         if (throwable == null) {
    142                             throwable = this.getThrowable(throwableClass);
    143                             if (throwable != null) {
    144                                 THROWABLE_MESSAGE.set(throwable, message);
    145                                 THROWABLE_CAUSE.set(throwable, cause);
    146                             }
    147                         } else {
    148                             THROWABLE_CAUSE.set(throwable, cause);
    149                         }
    150                     } else {
    151                         THROWABLE_MESSAGE.set(throwable, message);
    152                     }
    153                 }
    154             } else if (cause != null) {
    155                 throwable = this.getThrowable(throwableClass, cause);
    156                 if (throwable == null) {
    157                     throwable = this.getThrowable(throwableClass);
    158                     if (throwable != null) {
    159                         THROWABLE_CAUSE.set(throwable, cause);
    160                     }
    161                 }
    162             } else if (message != null) {
    163                 throwable = this.getThrowable(throwableClass, message);
    164                 if (throwable == null) {
    165                     throwable = this.getThrowable(throwableClass);
    166                     if (throwable != null) {
    167                         THROWABLE_MESSAGE.set(throwable, cause);
    168                     }
    169                 }
    170             } else {
    171                 throwable = this.getThrowable(throwableClass);
    172             }
    173
    174             if (throwable == null) {
    175                 return null;
    176             }
    177             throwable.setStackTrace(stackTrace);
    178             return throwable;
    179         } catch (final Exception e) {
    180             return null;
    181         }
    182     }
    183
    184     private Throwable getThrowable(final Class<Throwable> throwableClass, final String message, final Throwable cause) {
    185         try {
    186             @SuppressWarnings("unchecked")
    187             final
    188             Constructor<Throwable>[] constructors = (Constructor<Throwable>[]) throwableClass.getConstructors();
    189             for (final Constructor<Throwable> constructor : constructors) {
    190                 final Class<?>[] parameterTypes = constructor.getParameterTypes();
    191                 if (parameterTypes.length == 2) {
    192                     if (String.class == parameterTypes[0] && Throwable.class.isAssignableFrom(parameterTypes[1])) {
    193                         return constructor.newInstance(message, cause);
    194                     } else if (String.class == parameterTypes[1] &&
    195                             Throwable.class.isAssignableFrom(parameterTypes[0])) {
    196                         return constructor.newInstance(cause, message);
    197                     }
    198                 }
    199             }
    200             return null;
    201         } catch (final Exception e) {
    202             return null;
    203         }
    204     }
    205
    206     private Throwable getThrowable(final Class<Throwable> throwableClass, final Throwable cause) {
    207         try {
    208             @SuppressWarnings("unchecked")
    209             final
    210             Constructor<Throwable>[] constructors = (Constructor<Throwable>[]) throwableClass.getConstructors();
    211             for (final Constructor<Throwable> constructor : constructors) {
    212                 final Class<?>[] parameterTypes = constructor.getParameterTypes();
    213                 if (parameterTypes.length == 1 && Throwable.class.isAssignableFrom(parameterTypes[0])) {
    214                     return constructor.newInstance(cause);
    215                 }
    216             }
    217             return null;
    218         } catch (final Exception e) {
    219             return null;
    220         }
    221     }
    222
    223     private Throwable getThrowable(final Class<Throwable> throwableClass, final String message) {
    224         try {
    225             return throwableClass.getConstructor(String.class).newInstance(message);
    226         } catch (final Exception e) {
    227             return null;
    228         }
    229     }
    230
    231     private Throwable getThrowable(final Class<Throwable> throwableClass) {
    232         try {
    233             return throwableClass.newInstance();
    234         } catch (final Exception e) {
    235             return null;
    236         }
    237     }
    238 }