1 /**
2  * Licensed under the Apache License, Version 2.0 (the "License");
3  * you may not use this file except in compliance with the License.
4  * You may obtain a copy of the License at
5  *
6  *      http://www.apache.org/licenses/LICENSE-2.0
7  *
8  * Unless required by applicable law or agreed to in writing, software
9  * distributed under the License is distributed on an "AS IS" BASIS,
10  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  * See the License for the specific language governing permissions and
12  * limitations under the License.
13  */

14 package net.logstash.logback;
15
16 import java.io.IOException;
17 import java.io.OutputStream;
18 import java.lang.reflect.InvocationTargetException;
19 import java.lang.reflect.Method;
20
21 import ch.qos.logback.core.encoder.Encoder;
22
23 /**
24  * Provides backwards compatibility at runtime with logback 1.1.x.
25  * 
26  * In logback version 1.2.0, the {@link Encoder} interface was changed from
27  * writing to an {@link OutputStream} to returning byte arrays.
28  * This was a backwards incompatible change, therefore some fancy runtime reflection
29  * is required to make logstash-logback-encoder work with both pre- and post-1.2 logback versions.
30  * 
31  * This class is used to determine if logback 1.1 is on the runtime classpath ({@link #isLogback11OrBefore()}),
32  * and invoke the old methods on the {@link Encoder} interface.
33  */

34 public class Logback11Support {
35
36     public static final Logback11Support INSTANCE = new Logback11Support();
37     
38     private static final Method ENCODER_INIT_METHOD = getMethod(Encoder.class"init", OutputStream.class);
39     private static final Method ENCODER_DO_ENCODE_METHOD = getMethod(Encoder.class"doEncode", Object.class);
40     private static final Method ENCODER_CLOSE_METHOD = getMethod(Encoder.class"close");
41     private static final boolean IS_LOGBACK_1_1 = ENCODER_INIT_METHOD != null;
42
43     /**
44      * @return true if logback 1.1.x or earlier is on the runtime classpath.
45      *         false if logback 1.2.x or later is on the runtime classpath
46      */

47     public boolean isLogback11OrBefore() {
48         return IS_LOGBACK_1_1;
49     }
50
51     /**
52      * Called by logic that should only execute if logback 1.1.x or earlier is on the runtime classpath.
53      * 
54      * @throws IllegalStateException if the logback version is >= 1.2
55      */

56     public void verifyLogback11OrBefore() {
57         if (!isLogback11OrBefore()) {
58             throw new IllegalStateException("Logback 1.1 only method called, but Logback version is >= 1.2");
59         }
60     }
61     /**
62      * Called by logic that should only execute if logback 1.2.x or later is on the runtime classpath.
63      * 
64      * @throws IllegalStateException if the logback version is < 1.2
65      */

66     public void verifyLogback12OrAfter() {
67         if (isLogback11OrBefore()) {
68             throw new IllegalStateException("Logback 1.2+ method called, but Logback version is < 1.2");
69         }
70     }
71     
72     /**
73      * Invokes the init method of a logback 1.1 encoder, with the given outputStream as the argument.
74      *
75      * @param encoder the encoder to initialize
76      * @param outputStream the output stream with which to initialize the encoder
77      * @throws IOException if an exception occurs during initialization
78      */

79     public void init(Encoder<?> encoder, OutputStream outputStream) throws IOException {
80         verifyLogback11OrBefore();
81         try {
82             ENCODER_INIT_METHOD.invoke(encoder, outputStream);
83         } catch (IllegalArgumentException e) {
84             throw new IllegalStateException("Unable to initialize logback 1.1 encoder " + encoder, e);
85         } catch (IllegalAccessException e) {
86             throw new IllegalStateException("Unable to initialize logback 1.1 encoder " + encoder, e);
87         } catch (InvocationTargetException e) {
88             if (e.getCause() instanceof IOException) {
89                 throw (IOException) e.getCause();
90             } else if (e.getCause() instanceof RuntimeException) {
91                 throw (RuntimeException) e.getCause();
92             } else {
93                 throw new IllegalStateException("Unable to initialize logback 1.1 encoder " + encoder, e.getCause());
94             }
95         }
96     }
97     
98     /**
99      * Invokes the doEncode method of a logback 1.1 encoder, with the given event as the argument.
100      * @param encoder the encoder to use to encode the event
101      * @param event the event to encode
102      * @throws IOException if an exception occurs during encoding
103      */

104     public void doEncode(Encoder<?> encoder, Object event) throws IOException {
105         verifyLogback11OrBefore();
106         try {
107             ENCODER_DO_ENCODE_METHOD.invoke(encoder, event);
108         } catch (IllegalArgumentException e) {
109             throw new IllegalStateException("Unable to encode event with logback 1.1 encoder " + encoder, e);
110         } catch (IllegalAccessException e) {
111             throw new IllegalStateException("Unable to encode event with logback 1.1 encoder " + encoder, e);
112         } catch (InvocationTargetException e) {
113             if (e.getCause() instanceof IOException) {
114                 throw (IOException) e.getCause();
115             } else if (e.getCause() instanceof RuntimeException) {
116                 throw (RuntimeException) e.getCause();
117             } else {
118                 throw new IllegalStateException("Unable to encode event with logback 1.1 encoder " + encoder, e.getCause());
119             }
120         }
121     }
122     
123     /**
124      * Invokes the close method of a logback 1.1 encoder.
125      *
126      * @param encoder the encoder to close
127      * @throws IOException if an exception occurs during close
128      */

129     public void close(Encoder<?> encoder) throws IOException {
130         verifyLogback11OrBefore();
131         try {
132             ENCODER_CLOSE_METHOD.invoke(encoder);
133         } catch (IllegalArgumentException e) {
134             throw new IllegalStateException("Unable to close logback 1.1 encoder " + encoder, e);
135         } catch (IllegalAccessException e) {
136             throw new IllegalStateException("Unable to close logback 1.1 encoder " + encoder, e);
137         } catch (InvocationTargetException e) {
138             if (e.getCause() instanceof IOException) {
139                 throw (IOException) e.getCause();
140             } else if (e.getCause() instanceof RuntimeException) {
141                 throw (RuntimeException) e.getCause();
142             } else {
143                 throw new IllegalStateException("Unable to close logback 1.1 encoder " + encoder, e.getCause());
144             }
145         }
146     }
147
148     /**
149      * Returns the specified method of the given class, or null if it can't be found.
150      *
151      * @param clazz the class from which to retrieve the method
152      * @param methodName the name of the method to retrieve
153      * @param parameterTypes the parameter types of the method to retrieve
154      * @return the method from the class with the given methodName and parameterTypes (or null if not found)
155      */

156     private static Method getMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
157         try {
158             return clazz.getMethod(methodName, parameterTypes);
159         } catch (SecurityException e) {
160             return null;
161         } catch (NoSuchMethodException e) {
162             return null;
163         }
164     }
165     
166 }