Line 0
Link Here
|
|
|
1 |
/* |
2 |
* Licensed to the Apache Software Foundation (ASF) under one or more |
3 |
* contributor license agreements. See the NOTICE file distributed with |
4 |
* this work for additional information regarding copyright ownership. |
5 |
* The ASF licenses this file to You under the Apache License, Version 2.0 |
6 |
* (the "License"); you may not use this file except in compliance with |
7 |
* the License. You may obtain a copy of the License at |
8 |
* |
9 |
* http://www.apache.org/licenses/LICENSE-2.0 |
10 |
* |
11 |
* Unless required by applicable law or agreed to in writing, software |
12 |
* distributed under the License is distributed on an "AS IS" BASIS, |
13 |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 |
* See the License for the specific language governing permissions and |
15 |
* limitations under the License. |
16 |
*/ |
17 |
package org.apache.catalina.loader; |
18 |
|
19 |
import java.io.File; |
20 |
import java.io.FileOutputStream; |
21 |
import java.io.IOException; |
22 |
import java.io.InputStream; |
23 |
import java.lang.instrument.ClassFileTransformer; |
24 |
import java.lang.reflect.Method; |
25 |
import java.security.ProtectionDomain; |
26 |
|
27 |
import static org.junit.Assert.assertEquals; |
28 |
import static org.junit.Assert.assertNotNull; |
29 |
import static org.junit.Assert.assertSame; |
30 |
import static org.junit.Assert.fail; |
31 |
|
32 |
import org.junit.After; |
33 |
import org.junit.AfterClass; |
34 |
import org.junit.Before; |
35 |
import org.junit.BeforeClass; |
36 |
import org.junit.Test; |
37 |
|
38 |
import org.apache.catalina.Context; |
39 |
import org.apache.catalina.startup.Tomcat; |
40 |
import org.apache.catalina.startup.TomcatBaseTest; |
41 |
import org.apache.tomcat.util.http.fileupload.FileUtils; |
42 |
|
43 |
public class TestWebappClassLoaderWeaving extends TomcatBaseTest { |
44 |
|
45 |
private static final String PACKAGE_PREFIX = "org/apache/catalina/loader"; |
46 |
|
47 |
private static String WEBAPP_DOC_BASE; |
48 |
|
49 |
@BeforeClass |
50 |
public static void setUpClass() throws Exception { |
51 |
|
52 |
WEBAPP_DOC_BASE = System.getProperty("java.io.tmpdir") + "/TestWebappClassLoaderWeaving"; |
53 |
File classes = new File(WEBAPP_DOC_BASE + "/WEB-INF/classes/" + PACKAGE_PREFIX); |
54 |
classes.mkdirs(); |
55 |
|
56 |
copyResource(PACKAGE_PREFIX + "/TesterNeverWeavedClass.class", |
57 |
new File(classes, "TesterNeverWeavedClass.class")); |
58 |
copyResource(PACKAGE_PREFIX + "/TesterUnweavedClass.class", |
59 |
new File(classes, "TesterUnweavedClass.class")); |
60 |
|
61 |
} |
62 |
|
63 |
@AfterClass |
64 |
public static void tearDownClass() throws Exception { |
65 |
|
66 |
FileUtils.deleteDirectory(new File(WEBAPP_DOC_BASE)); |
67 |
|
68 |
} |
69 |
|
70 |
private Tomcat tomcat; |
71 |
private Context context; |
72 |
private WebappClassLoader loader; |
73 |
|
74 |
@Before |
75 |
@Override |
76 |
public void setUp() throws Exception { |
77 |
|
78 |
super.setUp(); |
79 |
|
80 |
this.tomcat = getTomcatInstance(); |
81 |
this.context = this.tomcat.addContext("/weaving", WEBAPP_DOC_BASE); |
82 |
this.tomcat.start(); |
83 |
|
84 |
ClassLoader loader = this.context.getLoader().getClassLoader(); |
85 |
assertNotNull("The class loader should not be null.", loader); |
86 |
assertSame("The class loader is not correct.", WebappClassLoader.class, loader.getClass()); |
87 |
|
88 |
this.loader = (WebappClassLoader) loader; |
89 |
|
90 |
} |
91 |
|
92 |
@After |
93 |
@Override |
94 |
public void tearDown() throws Exception { |
95 |
|
96 |
try { |
97 |
this.loader = null; |
98 |
|
99 |
this.context.stop(); |
100 |
this.tomcat.getHost().removeChild(this.context); |
101 |
this.context = null; |
102 |
} finally { |
103 |
super.tearDown(); |
104 |
} |
105 |
|
106 |
} |
107 |
|
108 |
@Test |
109 |
public void testNoWeaving() throws Exception { |
110 |
|
111 |
String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass"); |
112 |
assertEquals("The first result is not correct.", "This will never be weaved.", result); |
113 |
|
114 |
result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass"); |
115 |
assertEquals("The second result is not correct.", "Hello, Unweaved World!", result); |
116 |
|
117 |
} |
118 |
|
119 |
@Test |
120 |
public void testAddingNullTransformerThrowsException() throws Exception { |
121 |
|
122 |
try { |
123 |
this.loader.addTransformer(null); |
124 |
fail("Expected exception IllegalArgumentException, got no exception."); |
125 |
} catch (IllegalArgumentException ignore) { |
126 |
// good |
127 |
} |
128 |
|
129 |
// class loading should still work, no weaving |
130 |
String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass"); |
131 |
assertEquals("The first result is not correct.", "This will never be weaved.", result); |
132 |
|
133 |
result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass"); |
134 |
assertEquals("The second result is not correct.", "Hello, Unweaved World!", result); |
135 |
|
136 |
} |
137 |
|
138 |
@Test |
139 |
public void testAddedTransformerInstrumentsClass1() throws Exception { |
140 |
|
141 |
this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_1)); |
142 |
|
143 |
String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass"); |
144 |
assertEquals("The first result is not correct.", "This will never be weaved.", result); |
145 |
|
146 |
result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass"); |
147 |
assertEquals("The second result is not correct.", "Hello, Weaver #1!", result); |
148 |
|
149 |
} |
150 |
|
151 |
@Test |
152 |
public void testAddedTransformerInstrumentsClass2() throws Exception { |
153 |
|
154 |
this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_2)); |
155 |
|
156 |
String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass"); |
157 |
assertEquals("The first result is not correct.", "This will never be weaved.", result); |
158 |
|
159 |
result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass"); |
160 |
assertEquals("The second result is not correct.", "Hello, Weaver #2!", result); |
161 |
|
162 |
} |
163 |
|
164 |
@Test |
165 |
public void testTransformersExecuteInOrderAdded1() throws Exception { |
166 |
|
167 |
this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_1)); |
168 |
this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_2)); |
169 |
|
170 |
String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass"); |
171 |
assertEquals("The first result is not correct.", "This will never be weaved.", result); |
172 |
|
173 |
result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass"); |
174 |
assertEquals("The second result is not correct.", "Hello, Weaver #2!", result); |
175 |
|
176 |
} |
177 |
|
178 |
@Test |
179 |
public void testTransformersExecuteInOrderAdded2() throws Exception { |
180 |
|
181 |
this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_2)); |
182 |
this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_1)); |
183 |
|
184 |
String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass"); |
185 |
assertEquals("The first result is not correct.", "This will never be weaved.", result); |
186 |
|
187 |
result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass"); |
188 |
assertEquals("The second result is not correct.", "Hello, Weaver #1!", result); |
189 |
|
190 |
} |
191 |
|
192 |
@Test |
193 |
public void testRemovedTransformerNoLongerInstruments1() throws Exception { |
194 |
|
195 |
ReplacementTransformer removed = new ReplacementTransformer(WEAVED_REPLACEMENT_1); |
196 |
this.loader.addTransformer(removed); |
197 |
this.loader.removeTransformer(removed); |
198 |
|
199 |
String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass"); |
200 |
assertEquals("The first result is not correct.", "This will never be weaved.", result); |
201 |
|
202 |
result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass"); |
203 |
assertEquals("The second result is not correct.", "Hello, Unweaved World!", result); |
204 |
|
205 |
} |
206 |
|
207 |
@Test |
208 |
public void testRemovedTransformerNoLongerInstruments2() throws Exception { |
209 |
|
210 |
this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_1)); |
211 |
|
212 |
ReplacementTransformer removed = new ReplacementTransformer(WEAVED_REPLACEMENT_2); |
213 |
this.loader.addTransformer(removed); |
214 |
this.loader.removeTransformer(removed); |
215 |
|
216 |
String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass"); |
217 |
assertEquals("The first result is not correct.", "This will never be weaved.", result); |
218 |
|
219 |
result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass"); |
220 |
assertEquals("The second result is not correct.", "Hello, Weaver #1!", result); |
221 |
|
222 |
} |
223 |
|
224 |
@Test |
225 |
public void testRemovedTransformerNoLongerInstruments3() throws Exception { |
226 |
|
227 |
this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_2)); |
228 |
|
229 |
ReplacementTransformer removed = new ReplacementTransformer(WEAVED_REPLACEMENT_1); |
230 |
this.loader.addTransformer(removed); |
231 |
this.loader.removeTransformer(removed); |
232 |
|
233 |
String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass"); |
234 |
assertEquals("The first result is not correct.", "This will never be weaved.", result); |
235 |
|
236 |
result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass"); |
237 |
assertEquals("The second result is not correct.", "Hello, Weaver #2!", result); |
238 |
|
239 |
} |
240 |
|
241 |
@Test |
242 |
public void testCopiedClassLoaderExcludesResourcesAndTransformers() throws Exception { |
243 |
|
244 |
this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_1)); |
245 |
this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_2)); |
246 |
|
247 |
String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass"); |
248 |
assertEquals("The first result is not correct.", "This will never be weaved.", result); |
249 |
|
250 |
result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass"); |
251 |
assertEquals("The second result is not correct.", "Hello, Weaver #2!", result); |
252 |
|
253 |
WebappClassLoader copiedLoader = this.loader.copyWithoutTransformers(); |
254 |
|
255 |
result = invokeDoMethodOnClass(copiedLoader, "TesterNeverWeavedClass"); |
256 |
assertEquals("The third result is not correct.", "This will never be weaved.", result); |
257 |
|
258 |
result = invokeDoMethodOnClass(copiedLoader, "TesterUnweavedClass"); |
259 |
assertEquals("The fourth result is not correct.", "Hello, Unweaved World!", result); |
260 |
|
261 |
assertEquals("getAntiJARLocking did not match.", this.loader.getAntiJARLocking(), |
262 |
copiedLoader.getAntiJARLocking()); |
263 |
assertEquals("getClearReferencesHttpClientKeepAliveThread did not match.", |
264 |
this.loader.getClearReferencesHttpClientKeepAliveThread(), |
265 |
copiedLoader.getClearReferencesHttpClientKeepAliveThread()); |
266 |
assertEquals("getClearReferencesLogFactoryRelease did not match.", |
267 |
this.loader.getClearReferencesLogFactoryRelease(), |
268 |
copiedLoader.getClearReferencesLogFactoryRelease()); |
269 |
assertEquals("getClearReferencesStatic did not match.", |
270 |
this.loader.getClearReferencesStatic(), |
271 |
copiedLoader.getClearReferencesStatic()); |
272 |
assertEquals("getClearReferencesStopThreads did not match.", |
273 |
this.loader.getClearReferencesStopThreads(), |
274 |
copiedLoader.getClearReferencesStopThreads()); |
275 |
assertEquals("getClearReferencesStopTimerThreads did not match.", |
276 |
this.loader.getClearReferencesStopTimerThreads(), |
277 |
copiedLoader.getClearReferencesStopTimerThreads()); |
278 |
assertEquals("getContextName did not match.", this.loader.getContextName(), |
279 |
copiedLoader.getContextName()); |
280 |
assertEquals("getDelegate did not match.", this.loader.getDelegate(), |
281 |
copiedLoader.getDelegate()); |
282 |
assertEquals("getJarPath did not match.", this.loader.getJarPath(), |
283 |
copiedLoader.getJarPath()); |
284 |
assertEquals("getURLs did not match.", this.loader.getURLs().length, |
285 |
copiedLoader.getURLs().length); |
286 |
assertSame("getParent did not match.", this.loader.getParent(), copiedLoader.getParent()); |
287 |
|
288 |
} |
289 |
|
290 |
private static void copyResource(String name, File file) throws Exception { |
291 |
|
292 |
InputStream is = TestWebappClassLoaderWeaving.class.getClassLoader() |
293 |
.getResourceAsStream(name); |
294 |
if (is == null) { |
295 |
throw new IOException("Resource " + name + " not found on classpath."); |
296 |
} |
297 |
|
298 |
FileOutputStream os = new FileOutputStream(file); |
299 |
try { |
300 |
for (int b = is.read(); b >= 0; b = is.read()) { |
301 |
os.write(b); |
302 |
} |
303 |
} finally { |
304 |
is.close(); |
305 |
os.close(); |
306 |
} |
307 |
|
308 |
} |
309 |
|
310 |
private static String invokeDoMethodOnClass(WebappClassLoader loader, String className) |
311 |
throws Exception { |
312 |
|
313 |
Class<?> c = loader.findClass("org.apache.catalina.loader." + className); |
314 |
assertNotNull("The loaded class should not be null.", c); |
315 |
|
316 |
Method m = c.getMethod("doMethod"); |
317 |
|
318 |
Object o = c.newInstance(); |
319 |
return (String) m.invoke(o); |
320 |
|
321 |
} |
322 |
|
323 |
private static class ReplacementTransformer implements ClassFileTransformer { |
324 |
|
325 |
private static final String CLASS_TO_WEAVE = PACKAGE_PREFIX + "/TesterUnweavedClass"; |
326 |
|
327 |
private final byte[] replacement; |
328 |
|
329 |
ReplacementTransformer(byte[] replacement) throws IOException { |
330 |
|
331 |
this.replacement = replacement; |
332 |
|
333 |
} |
334 |
|
335 |
@Override |
336 |
public byte[] transform(ClassLoader loader, String className, Class<?> x, |
337 |
ProtectionDomain y, byte[] b) { |
338 |
|
339 |
if (CLASS_TO_WEAVE.equals(className)) { |
340 |
return this.replacement; |
341 |
} |
342 |
|
343 |
return null; |
344 |
|
345 |
} |
346 |
|
347 |
} |
348 |
|
349 |
/** |
350 |
* Compiled version of org.apache.catalina.loader.TesterUnweavedClass, except that |
351 |
* the doMethod method returns "Hello, Weaver #1!". Compiled with Oracle Java 1.6.0_51. |
352 |
*/ |
353 |
private static final byte[] WEAVED_REPLACEMENT_1 = new byte[] { |
354 |
-54, -2, -70, -66, 0, 0, 0, 50, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0, 16, 1, |
355 |
0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, |
356 |
15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 8, 100, |
357 |
111, 77, 101, 116, 104, 111, 100, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, |
358 |
110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, |
359 |
105, 108, 101, 1, 0, 24, 84, 101, 115, 116, 101, 114, 85, 110, 119, 101, 97, 118, 101, |
360 |
100, 67, 108, 97, 115, 115, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 17, 72, 101, |
361 |
108, 108, 111, 44, 32, 87, 101, 97, 118, 101, 114, 32, 35, 49, 33, 1, 0, 46, 111, 114, |
362 |
103, 47, 97, 112, 97, 99, 104, 101, 47, 99, 97, 116, 97, 108, 105, 110, 97, 47, 108, |
363 |
111, 97, 100, 101, 114, 47, 84, 101, 115, 116, 101, 114, 85, 110, 119, 101, 97, 118, |
364 |
101, 100, 67, 108, 97, 115, 115, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, |
365 |
79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5, 0, 6, 0, 1, |
366 |
0, 7, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 8, 0, 0, |
367 |
0, 6, 0, 1, 0, 0, 0, 19, 0, 1, 0, 9, 0, 10, 0, 1, 0, 7, 0, 0, 0, 27, 0, 1, 0, 1, 0, 0, |
368 |
0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 22, 0, 1, 0, 11, 0, 0, 0, |
369 |
2, 0, 12 |
370 |
}; |
371 |
|
372 |
/** |
373 |
* Compiled version of org.apache.catalina.loader.TesterUnweavedClass, except that |
374 |
* the doMethod method returns "Hello, Weaver #2!". Compiled with Oracle Java 1.6.0_51. |
375 |
*/ |
376 |
private static final byte[] WEAVED_REPLACEMENT_2 = new byte[] { |
377 |
-54, -2, -70, -66, 0, 0, 0, 50, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0, 16, 1, |
378 |
0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, |
379 |
15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 8, 100, |
380 |
111, 77, 101, 116, 104, 111, 100, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, |
381 |
110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, |
382 |
105, 108, 101, 1, 0, 24, 84, 101, 115, 116, 101, 114, 85, 110, 119, 101, 97, 118, 101, |
383 |
100, 67, 108, 97, 115, 115, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 17, 72, 101, |
384 |
108, 108, 111, 44, 32, 87, 101, 97, 118, 101, 114, 32, 35, 50, 33, 1, 0, 46, 111, 114, |
385 |
103, 47, 97, 112, 97, 99, 104, 101, 47, 99, 97, 116, 97, 108, 105, 110, 97, 47, 108, |
386 |
111, 97, 100, 101, 114, 47, 84, 101, 115, 116, 101, 114, 85, 110, 119, 101, 97, 118, |
387 |
101, 100, 67, 108, 97, 115, 115, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, |
388 |
79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5, 0, 6, 0, 1, |
389 |
0, 7, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 8, 0, 0, |
390 |
0, 6, 0, 1, 0, 0, 0, 19, 0, 1, 0, 9, 0, 10, 0, 1, 0, 7, 0, 0, 0, 27, 0, 1, 0, 1, 0, 0, |
391 |
0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 22, 0, 1, 0, 11, 0, 0, 0, |
392 |
2, 0, 12 |
393 |
}; |
394 |
|
395 |
/* |
396 |
* The WEAVED_REPLACEMENT_1 and WEAVED_REPLACEMENT_2 field contents are generated using the |
397 |
* following code. To regenerate them, alter the TesterUnweavedClass code as desired, recompile, |
398 |
* and run this main method. |
399 |
*/ |
400 |
/*public static void main(String... arguments) throws Exception { |
401 |
InputStream input = Translator.class.getClassLoader() |
402 |
.getResourceAsStream("org/apache/catalina/loader/TesterUnweavedClass.class"); |
403 |
|
404 |
StringBuilder builder = new StringBuilder(); |
405 |
builder.append(" "); |
406 |
|
407 |
System.out.println(" private static final byte[] WEAVED_REPLACEMENT_1 = new byte[] {"); |
408 |
try { |
409 |
for (int i = 0, b = input.read(); b >= 0; i++, b = input.read()) { |
410 |
String value = "" + ((byte)b); |
411 |
if (builder.length() + value.length() > 97) { |
412 |
builder.append(","); |
413 |
System.out.println(builder.toString()); |
414 |
builder = new StringBuilder(); |
415 |
builder.append(" ").append(value); |
416 |
} else { |
417 |
if (i > 0) { |
418 |
builder.append(", "); |
419 |
} |
420 |
builder.append(value); |
421 |
} |
422 |
} |
423 |
System.out.println(builder.toString()); |
424 |
} finally { |
425 |
input.close(); |
426 |
} |
427 |
System.out.println(" }"); |
428 |
}*/ |
429 |
|
430 |
} |