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  import javax.persistence.AttributeConverter;
26  import javax.persistence.Converter;
27  
28  /**
29   * A JPA 2.1 attribute converter for {@link Throwable}s in {@link org.apache.logging.log4j.core.LogEvent}s. This
30   * converter is capable of converting both to and from {@link String}s.
31   */
32  @Converter(autoApply = false)
33  public class ThrowableAttributeConverter implements AttributeConverter<Throwable, String> {
34      private static final int CAUSED_BY_STRING_LENGTH = 10;
35  
36      private static final Field THROWABLE_CAUSE;
37  
38      private static final Field THROWABLE_MESSAGE;
39  
40      static {
41          try {
42              THROWABLE_CAUSE = Throwable.class.getDeclaredField("cause");
43              THROWABLE_CAUSE.setAccessible(true);
44              THROWABLE_MESSAGE = Throwable.class.getDeclaredField("detailMessage");
45              THROWABLE_MESSAGE.setAccessible(true);
46          } catch (NoSuchFieldException e) {
47              throw new IllegalStateException("Something is wrong with java.lang.Throwable.", e);
48          }
49      }
50  
51      @Override
52      public String convertToDatabaseColumn(final Throwable throwable) {
53          StringBuilder builder = new StringBuilder();
54          this.convertThrowable(builder, throwable);
55          return builder.toString();
56      }
57  
58      private void convertThrowable(final StringBuilder builder, final Throwable throwable) {
59          builder.append(throwable.toString()).append('\n');
60          for (StackTraceElement element : throwable.getStackTrace()) {
61              builder.append("\tat ").append(element).append('\n');
62          }
63          if (throwable.getCause() != null) {
64              builder.append("Caused by ");
65              this.convertThrowable(builder, throwable.getCause());
66          }
67      }
68  
69      @Override
70      public Throwable convertToEntityAttribute(final String s) {
71          if (s == null || s.length() == 0) {
72              return null;
73          }
74  
75          List<String> lines = Arrays.asList(s.split("(\n|\r\n)"));
76          return this.convertString(lines.listIterator(), false);
77      }
78  
79      private Throwable convertString(final ListIterator<String> lines, boolean removeCausedBy) {
80          String firstLine = lines.next();
81          if (removeCausedBy) {
82              firstLine = firstLine.substring(CAUSED_BY_STRING_LENGTH);
83          }
84          int colon = firstLine.indexOf(":");
85          String throwableClassName;
86          String message = null;
87          if (colon > 1) {
88              throwableClassName = firstLine.substring(0, colon);
89              if (firstLine.length() > colon + 1) {
90                  message = firstLine.substring(colon + 1).trim();
91              }
92          } else {
93              throwableClassName = firstLine;
94          }
95  
96          List<StackTraceElement> stackTrace = new ArrayList<StackTraceElement>();
97          Throwable cause = null;
98          while (lines.hasNext()) {
99              String line = lines.next();
100 
101             if (line.startsWith("Caused by ")) {
102                 lines.previous();
103                 cause = convertString(lines, true);
104                 break;
105             }
106 
107             stackTrace.add(
108                     StackTraceElementAttributeConverter.convertString(line.trim().substring(3).trim())
109             );
110         }
111 
112         return this.getThrowable(throwableClassName, message, cause,
113                 stackTrace.toArray(new StackTraceElement[stackTrace.size()]));
114     }
115 
116     private Throwable getThrowable(final String throwableClassName, final String message, final Throwable cause,
117                                    final StackTraceElement[] stackTrace) {
118         try {
119             @SuppressWarnings("unchecked")
120             Class<Throwable> throwableClass = (Class<Throwable>) Class.forName(throwableClassName);
121 
122             if (!Throwable.class.isAssignableFrom(throwableClass)) {
123                 return null;
124             }
125 
126             Throwable throwable;
127             if (message != null && cause != null) {
128                 throwable = this.getThrowable(throwableClass, message, cause);
129                 if (throwable == null) {
130                     throwable = this.getThrowable(throwableClass, cause);
131                     if (throwable == null) {
132                         throwable = this.getThrowable(throwableClass, message);
133                         if (throwable == null) {
134                             throwable = this.getThrowable(throwableClass);
135                             if (throwable != null) {
136                                 THROWABLE_MESSAGE.set(throwable, message);
137                                 THROWABLE_CAUSE.set(throwable, cause);
138                             }
139                         } else {
140                             THROWABLE_CAUSE.set(throwable, cause);
141                         }
142                     } else {
143                         THROWABLE_MESSAGE.set(throwable, message);
144                     }
145                 }
146             } else if (cause != null) {
147                 throwable = this.getThrowable(throwableClass, cause);
148                 if (throwable == null) {
149                     throwable = this.getThrowable(throwableClass);
150                     if (throwable != null) {
151                         THROWABLE_CAUSE.set(throwable, cause);
152                     }
153                 }
154             } else if (message != null) {
155                 throwable = this.getThrowable(throwableClass, message);
156                 if (throwable == null) {
157                     throwable = this.getThrowable(throwableClass);
158                     if (throwable != null) {
159                         THROWABLE_MESSAGE.set(throwable, cause);
160                     }
161                 }
162             } else {
163                 throwable = this.getThrowable(throwableClass);
164             }
165 
166             if (throwable == null) {
167                 return null;
168             }
169             throwable.setStackTrace(stackTrace);
170             return throwable;
171         } catch (Exception e) {
172             return null;
173         }
174     }
175 
176     private Throwable getThrowable(final Class<Throwable> throwableClass, final String message, final Throwable cause) {
177         try {
178             @SuppressWarnings("unchecked")
179             Constructor<Throwable>[] constructors = (Constructor<Throwable>[]) throwableClass.getConstructors();
180             for (Constructor<Throwable> constructor : constructors) {
181                 Class<?>[] parameterTypes = constructor.getParameterTypes();
182                 if (parameterTypes.length == 2) {
183                     if (String.class == parameterTypes[0] && Throwable.class.isAssignableFrom(parameterTypes[1])) {
184                         return constructor.newInstance(message, cause);
185                     } else if (String.class == parameterTypes[1] &&
186                             Throwable.class.isAssignableFrom(parameterTypes[0])) {
187                         return constructor.newInstance(cause, message);
188                     }
189                 }
190             }
191             return null;
192         } catch (Exception e) {
193             return null;
194         }
195     }
196 
197     private Throwable getThrowable(final Class<Throwable> throwableClass, final Throwable cause) {
198         try {
199             @SuppressWarnings("unchecked")
200             Constructor<Throwable>[] constructors = (Constructor<Throwable>[]) throwableClass.getConstructors();
201             for (Constructor<Throwable> constructor : constructors) {
202                 Class<?>[] parameterTypes = constructor.getParameterTypes();
203                 if (parameterTypes.length == 1 && Throwable.class.isAssignableFrom(parameterTypes[0])) {
204                     return constructor.newInstance(cause);
205                 }
206             }
207             return null;
208         } catch (Exception e) {
209             return null;
210         }
211     }
212 
213     private Throwable getThrowable(final Class<Throwable> throwableClass, final String message) {
214         try {
215             return throwableClass.getConstructor(String.class).newInstance(message);
216         } catch (Exception e) {
217             return null;
218         }
219     }
220 
221     private Throwable getThrowable(final Class<Throwable> throwableClass) {
222         try {
223             return throwableClass.newInstance();
224         } catch (Exception e) {
225             return null;
226         }
227     }
228 }