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    }