1 package org.codehaus.mojo.jaxb2.shared.environment.classloading;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.plugin.logging.Log;
23 import org.codehaus.mojo.jaxb2.shared.Validate;
24
25 import java.io.File;
26 import java.io.UnsupportedEncodingException;
27 import java.net.MalformedURLException;
28 import java.net.URL;
29 import java.net.URLClassLoader;
30 import java.net.URLDecoder;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.List;
34
35 import static org.codehaus.mojo.jaxb2.shared.environment.classloading.ThreadContextClassLoaderBuilder.SupportedURLProtocols.*;
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65 public final class ThreadContextClassLoaderBuilder {
66
67
68
69
70 enum SupportedURLProtocols {
71
72 FILE,
73
74 JAR,
75
76 HTTP,
77
78 HTTPS,
79
80 BUNDLERESOURCE;
81
82
83
84
85
86
87 public boolean supports(final String protocol) {
88 return protocol != null && this.name().equalsIgnoreCase(protocol.trim());
89 }
90 }
91
92
93 private ClassLoader originalClassLoader;
94 private List<URL> urlList;
95 private Log log;
96 private String encoding;
97
98 private ThreadContextClassLoaderBuilder(final ClassLoader classLoader, final Log aLog, final String encoding) {
99 log = aLog;
100 originalClassLoader = classLoader;
101 urlList = new ArrayList<URL>();
102 this.encoding = encoding;
103 }
104
105
106
107
108
109
110
111
112 public ThreadContextClassLoaderBuilder addURL(final URL anURL) {
113
114
115 Validate.notNull(anURL, "anURL");
116
117
118 for (URL current : urlList) {
119 if (current.toString().equalsIgnoreCase(anURL.toString())) {
120
121 if (log.isWarnEnabled()) {
122 log.warn("Not adding URL [" + anURL.toString() + "] twice. Check your plugin configuration.");
123 }
124
125
126 return this;
127 }
128 }
129
130
131 if (log.isDebugEnabled()) {
132 log.debug("Adding URL [" + anURL.toString() + "]");
133 }
134
135
136
137
138
139
140
141
142
143
144
145 urlList.add(addSlashToDirectoryUrlIfRequired(anURL));
146
147 return this;
148 }
149
150
151
152
153
154
155
156
157 public ThreadContextClassLoaderBuilder addPath(final String path) {
158
159
160 Validate.notEmpty(path, "path");
161
162
163 final URL anUrl;
164 try {
165 anUrl = new File(path).toURI().toURL();
166 } catch (MalformedURLException e) {
167 throw new IllegalArgumentException("Could not convert path [" + path + "] to an URL.", e);
168 }
169
170
171 return addURL(anUrl);
172 }
173
174
175
176
177
178
179
180
181 public ThreadContextClassLoaderBuilder addPaths(final List<String> paths) {
182
183
184 Validate.notNull(paths, "paths");
185
186
187 for (String path : paths) {
188 addPath(path);
189 }
190
191 return this;
192 }
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207 public ThreadContextClassLoaderHolder buildAndSet() {
208
209
210 final URL[] allURLs = new URL[urlList.size()];
211 urlList.toArray(allURLs);
212 final URLClassLoader classLoader = new URLClassLoader(allURLs, originalClassLoader);
213
214
215 final Thread currentThread = Thread.currentThread();
216 currentThread.setContextClassLoader(classLoader);
217
218
219 StringBuilder builder = new StringBuilder();
220 try {
221 for (URL current : Collections.list(classLoader.getResources(""))) {
222
223 final String toAppend = getClassPathElement(current, encoding);
224 if (toAppend != null) {
225 builder.append(toAppend).append(File.pathSeparator);
226 }
227 }
228 } catch (Exception e) {
229
230 currentThread.setContextClassLoader(originalClassLoader);
231 throw new IllegalStateException("Could not synthesize classpath from original classloader.", e);
232 }
233
234 final String classPathString = builder.length() > 0
235 ? builder.toString().substring(0, builder.length() - File.pathSeparator.length())
236 : "";
237
238
239 return new DefaultHolder(currentThread, this.originalClassLoader, classPathString);
240 }
241
242
243
244
245
246
247
248
249
250
251
252
253 public static ThreadContextClassLoaderBuilder createFor(final ClassLoader classLoader,
254 final Log log,
255 final String encoding) {
256
257
258 Validate.notNull(classLoader, "classLoader");
259 Validate.notNull(log, "log");
260
261
262 return new ThreadContextClassLoaderBuilder(classLoader, log, encoding);
263 }
264
265
266
267
268
269
270
271
272
273
274 public static ThreadContextClassLoaderBuilder createFor(final Class<?> aClass,
275 final Log log,
276 final String encoding) {
277
278
279 Validate.notNull(aClass, "aClass");
280
281
282 return createFor(aClass.getClassLoader(), log, encoding);
283 }
284
285
286
287
288
289
290
291
292
293 public static String getClassPathElement(final URL anURL, final String encoding) throws IllegalArgumentException {
294
295
296 Validate.notNull(anURL, "anURL");
297
298 final String protocol = anURL.getProtocol();
299 String toReturn = null;
300
301 if (FILE.supports(protocol)) {
302
303 final String originalPath = anURL.getPath();
304 try {
305 return URLDecoder.decode(anURL.getPath(), encoding);
306 } catch (UnsupportedEncodingException e) {
307 throw new IllegalArgumentException("Could not URLDecode path [" + originalPath
308 + "] using encoding [" + encoding + "]", e);
309 }
310 } else if (JAR.supports(protocol)) {
311 toReturn = anURL.getPath();
312 } else if (HTTP.supports(protocol) || HTTPS.supports(protocol)) {
313 toReturn = anURL.toString();
314 } else if (BUNDLERESOURCE.supports(protocol)) {
315 toReturn = anURL.toString();
316 } else {
317 throw new IllegalArgumentException("Unknown protocol [" + protocol + "]; could not handle URL ["
318 + anURL + "]");
319 }
320
321 return toReturn;
322 }
323
324
325
326
327
328 private URL addSlashToDirectoryUrlIfRequired(final URL anURL) {
329
330
331 Validate.notNull(anURL, "anURL");
332
333 URL toReturn = anURL;
334 if ("file".equalsIgnoreCase(anURL.getProtocol())) {
335
336 final File theFile = new File(anURL.getPath());
337 if (theFile.isDirectory()) {
338 try {
339
340
341
342
343 toReturn = theFile.toURI().toURL();
344 } catch (MalformedURLException e) {
345
346 throw new IllegalArgumentException("Could not convert a File to an URL", e);
347 }
348 }
349 }
350
351
352 return toReturn;
353 }
354
355
356
357
358
359
360 @SuppressWarnings("all")
361 class DefaultHolder implements ThreadContextClassLoaderHolder {
362
363
364 private Thread affectedThread;
365 private ClassLoader originalClassLoader;
366 private String classPathArgument;
367
368
369
370
371
372
373
374
375
376
377
378 public DefaultHolder(final Thread affectedThread,
379 final ClassLoader originalClassLoader,
380 final String classPathArgument) {
381
382
383 Validate.notNull(affectedThread, "affectedThread");
384 Validate.notNull(originalClassLoader, "originalClassLoader");
385 Validate.notNull(classPathArgument, "classPathArgument");
386
387
388 this.affectedThread = affectedThread;
389 this.originalClassLoader = originalClassLoader;
390 this.classPathArgument = classPathArgument;
391 }
392
393
394
395
396 @Override
397 public void restoreClassLoaderAndReleaseThread() {
398
399 if (affectedThread != null) {
400
401
402 affectedThread.setContextClassLoader(originalClassLoader);
403
404
405 affectedThread = null;
406 originalClassLoader = null;
407 classPathArgument = null;
408 }
409 }
410
411
412
413
414 @Override
415 public String getClassPathAsArgument() {
416 return classPathArgument;
417 }
418
419
420
421
422 @Override
423 protected void finalize() throws Throwable {
424 try {
425
426 restoreClassLoaderAndReleaseThread();
427
428 } finally {
429
430
431 super.finalize();
432 }
433 }
434 }
435 }