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 }