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.util.Loader; 030import 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) 037public 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}