001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017 package org.apache.logging.log4j.core.appender.db.jpa.converter;
018
019 import java.lang.reflect.Constructor;
020 import java.lang.reflect.Field;
021 import java.util.ArrayList;
022 import java.util.Arrays;
023 import java.util.List;
024 import java.util.ListIterator;
025
026 import javax.persistence.AttributeConverter;
027 import javax.persistence.Converter;
028
029 import org.apache.logging.log4j.core.util.Loader;
030 import org.apache.logging.log4j.util.Strings;
031
032 /**
033 * A JPA 2.1 attribute converter for {@link Throwable}s in {@link org.apache.logging.log4j.core.LogEvent}s. This
034 * converter is capable of converting both to and from {@link String}s.
035 */
036 @Converter(autoApply = false)
037 public class ThrowableAttributeConverter implements AttributeConverter<Throwable, String> {
038 private static final int CAUSED_BY_STRING_LENGTH = 10;
039
040 private static final Field THROWABLE_CAUSE;
041
042 private static final Field THROWABLE_MESSAGE;
043
044 static {
045 try {
046 THROWABLE_CAUSE = Throwable.class.getDeclaredField("cause");
047 THROWABLE_CAUSE.setAccessible(true);
048 THROWABLE_MESSAGE = Throwable.class.getDeclaredField("detailMessage");
049 THROWABLE_MESSAGE.setAccessible(true);
050 } catch (final NoSuchFieldException e) {
051 throw new IllegalStateException("Something is wrong with java.lang.Throwable.", e);
052 }
053 }
054
055 @Override
056 public String convertToDatabaseColumn(final Throwable throwable) {
057 if (throwable == null) {
058 return null;
059 }
060
061 final StringBuilder builder = new StringBuilder();
062 this.convertThrowable(builder, throwable);
063 return builder.toString();
064 }
065
066 private void convertThrowable(final StringBuilder builder, final Throwable throwable) {
067 builder.append(throwable.toString()).append('\n');
068 for (final StackTraceElement element : throwable.getStackTrace()) {
069 builder.append("\tat ").append(element).append('\n');
070 }
071 if (throwable.getCause() != null) {
072 builder.append("Caused by ");
073 this.convertThrowable(builder, throwable.getCause());
074 }
075 }
076
077 @Override
078 public Throwable convertToEntityAttribute(final String s) {
079 if (Strings.isEmpty(s)) {
080 return null;
081 }
082
083 final List<String> lines = Arrays.asList(s.split("(\n|\r\n)"));
084 return this.convertString(lines.listIterator(), false);
085 }
086
087 private Throwable convertString(final ListIterator<String> lines, final boolean removeCausedBy) {
088 String firstLine = lines.next();
089 if (removeCausedBy) {
090 firstLine = firstLine.substring(CAUSED_BY_STRING_LENGTH);
091 }
092 final int colon = firstLine.indexOf(":");
093 String throwableClassName;
094 String message = null;
095 if (colon > 1) {
096 throwableClassName = firstLine.substring(0, colon);
097 if (firstLine.length() > colon + 1) {
098 message = firstLine.substring(colon + 1).trim();
099 }
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 }