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    }