1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
34
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 }