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 }