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.helpers.Strings;
30  
31  /**
32   * A JPA 2.1 attribute converter for {@link Throwable}s in {@link org.apache.logging.log4j.core.LogEvent}s. This
33   * converter is capable of converting both to and from {@link String}s.
34   */
35  @Converter(autoApply = false)
36  public class ThrowableAttributeConverter implements AttributeConverter<Throwable, String> {
37      private static final int CAUSED_BY_STRING_LENGTH = 10;
38  
39      private static final Field THROWABLE_CAUSE;
40  
41      private static final Field THROWABLE_MESSAGE;
42  
43      static {
44          try {
45              THROWABLE_CAUSE = Throwable.class.getDeclaredField("cause");
46              THROWABLE_CAUSE.setAccessible(true);
47              THROWABLE_MESSAGE = Throwable.class.getDeclaredField("detailMessage");
48              THROWABLE_MESSAGE.setAccessible(true);
49          } catch (final NoSuchFieldException e) {
50              throw new IllegalStateException("Something is wrong with java.lang.Throwable.", e);
51          }
52      }
53  
54      @Override
55      public String convertToDatabaseColumn(final Throwable throwable) {
56          if (throwable == null) {
57              return null;
58          }
59  
60          final StringBuilder builder = new StringBuilder();
61          this.convertThrowable(builder, throwable);
62          return builder.toString();
63      }
64  
65      private void convertThrowable(final StringBuilder builder, final Throwable throwable) {
66          builder.append(throwable.toString()).append('\n');
67          for (final StackTraceElement element : throwable.getStackTrace()) {
68              builder.append("\tat ").append(element).append('\n');
69          }
70          if (throwable.getCause() != null) {
71              builder.append("Caused by ");
72              this.convertThrowable(builder, throwable.getCause());
73          }
74      }
75  
76      @Override
77      public Throwable convertToEntityAttribute(final String s) {
78          if (Strings.isEmpty(s)) {
79              return null;
80          }
81  
82          final List<String> lines = Arrays.asList(s.split("(\n|\r\n)"));
83          return this.convertString(lines.listIterator(), false);
84      }
85  
86      private Throwable convertString(final ListIterator<String> lines, final boolean removeCausedBy) {
87          String firstLine = lines.next();
88          if (removeCausedBy) {
89              firstLine = firstLine.substring(CAUSED_BY_STRING_LENGTH);
90          }
91          final int colon = firstLine.indexOf(":");
92          String throwableClassName;
93          String message = null;
94          if (colon > 1) {
95              throwableClassName = firstLine.substring(0, colon);
96              if (firstLine.length() > colon + 1) {
97                  message = firstLine.substring(colon + 1).trim();
98              }
99          } else {
100             throwableClassName = firstLine;
101         }
102 
103         final List<StackTraceElement> stackTrace = new ArrayList<StackTraceElement>();
104         Throwable cause = null;
105         while (lines.hasNext()) {
106             final String line = lines.next();
107 
108             if (line.startsWith("Caused by ")) {
109                 lines.previous();
110                 cause = convertString(lines, true);
111                 break;
112             }
113 
114             stackTrace.add(
115                     StackTraceElementAttributeConverter.convertString(line.trim().substring(3).trim())
116             );
117         }
118 
119         return this.getThrowable(throwableClassName, message, cause,
120                 stackTrace.toArray(new StackTraceElement[stackTrace.size()]));
121     }
122 
123     private Throwable getThrowable(final String throwableClassName, final String message, final Throwable cause,
124                                    final StackTraceElement[] stackTrace) {
125         try {
126             @SuppressWarnings("unchecked")
127             final
128             Class<Throwable> throwableClass = (Class<Throwable>) Class.forName(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 }