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 */ 017package org.apache.logging.log4j.core.appender.db.jpa.converter; 018 019import java.lang.reflect.Constructor; 020import java.lang.reflect.Field; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.List; 024import java.util.ListIterator; 025 026import javax.persistence.AttributeConverter; 027import javax.persistence.Converter; 028 029import org.apache.logging.log4j.core.helpers.Strings; 030 031/** 032 * A JPA 2.1 attribute converter for {@link Throwable}s in {@link org.apache.logging.log4j.core.LogEvent}s. This 033 * converter is capable of converting both to and from {@link String}s. 034 */ 035@Converter(autoApply = false) 036public class ThrowableAttributeConverter implements AttributeConverter<Throwable, String> { 037 private static final int CAUSED_BY_STRING_LENGTH = 10; 038 039 private static final Field THROWABLE_CAUSE; 040 041 private static final Field THROWABLE_MESSAGE; 042 043 static { 044 try { 045 THROWABLE_CAUSE = Throwable.class.getDeclaredField("cause"); 046 THROWABLE_CAUSE.setAccessible(true); 047 THROWABLE_MESSAGE = Throwable.class.getDeclaredField("detailMessage"); 048 THROWABLE_MESSAGE.setAccessible(true); 049 } catch (final NoSuchFieldException e) { 050 throw new IllegalStateException("Something is wrong with java.lang.Throwable.", e); 051 } 052 } 053 054 @Override 055 public String convertToDatabaseColumn(final Throwable throwable) { 056 if (throwable == null) { 057 return null; 058 } 059 060 final StringBuilder builder = new StringBuilder(); 061 this.convertThrowable(builder, throwable); 062 return builder.toString(); 063 } 064 065 private void convertThrowable(final StringBuilder builder, final Throwable throwable) { 066 builder.append(throwable.toString()).append('\n'); 067 for (final StackTraceElement element : throwable.getStackTrace()) { 068 builder.append("\tat ").append(element).append('\n'); 069 } 070 if (throwable.getCause() != null) { 071 builder.append("Caused by "); 072 this.convertThrowable(builder, throwable.getCause()); 073 } 074 } 075 076 @Override 077 public Throwable convertToEntityAttribute(final String s) { 078 if (Strings.isEmpty(s)) { 079 return null; 080 } 081 082 final List<String> lines = Arrays.asList(s.split("(\n|\r\n)")); 083 return this.convertString(lines.listIterator(), false); 084 } 085 086 private Throwable convertString(final ListIterator<String> lines, final boolean removeCausedBy) { 087 String firstLine = lines.next(); 088 if (removeCausedBy) { 089 firstLine = firstLine.substring(CAUSED_BY_STRING_LENGTH); 090 } 091 final int colon = firstLine.indexOf(":"); 092 String throwableClassName; 093 String message = null; 094 if (colon > 1) { 095 throwableClassName = firstLine.substring(0, colon); 096 if (firstLine.length() > colon + 1) { 097 message = firstLine.substring(colon + 1).trim(); 098 } 099 } 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}