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