File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\ClassicPluginStrategy.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Jean-Baptiste Quenot, Tom Huybrechts
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson;
25
26 import com.google.common.collect.Iterables;
27 import com.google.common.collect.Lists;
28 import hudson.Plugin.DummyImpl;
29 import hudson.PluginWrapper.Dependency;
30 import hudson.model.Hudson;
31 import hudson.util.CyclicGraphDetector;
32 import hudson.util.CyclicGraphDetector.CycleDetectedException;
33 import hudson.util.IOException2;
34 import hudson.util.IOUtils;
35 import hudson.util.MaskingClassLoader;
36 import hudson.util.VersionNumber;
37 import jenkins.ClassLoaderReflectionToolkit;
38 import jenkins.ExtensionFilter;
39 import org.apache.tools.ant.AntClassLoader;
40 import org.apache.tools.ant.BuildException;
41 import org.apache.tools.ant.Project;
42 import org.apache.tools.ant.taskdefs.Expand;
43 import org.apache.tools.ant.types.FileSet;
44
45 import java.io.Closeable;
46 import java.io.File;
47 import java.io.FileInputStream;
48 import java.io.FilenameFilter;
49 import java.io.IOException;
50 import java.lang.reflect.Field;
51 import java.lang.reflect.InvocationTargetException;
52 import java.net.URL;
53 import java.net.URLClassLoader;
54 import java.util.ArrayList;
55 import java.util.Arrays;
56 import java.util.Collection;
57 import java.util.Collections;
58 import java.util.Enumeration;
59 import java.util.HashSet;
60 import java.util.Iterator;
61 import java.util.List;
62 import java.util.Vector;
63 import java.util.jar.Attributes;
64 import java.util.jar.Manifest;
65 import java.util.logging.Level;
66 import java.util.logging.Logger;
67
68 public class ClassicPluginStrategy implements PluginStrategy {
69 private final ClassLoaderReflectionToolkit clt = new ClassLoaderReflectionToolkit();
70
71 /**
72 * Filter for jar files.
73 */
74 private static final FilenameFilter JAR_FILTER = new FilenameFilter() {
75 public boolean accept(File dir,String name) {
76 return name.endsWith(".jar");
77 }
78 };
79
80 private PluginManager pluginManager;
81
82 /**
83 * All the plugins eventually delegate this classloader to load core, servlet APIs, and SE runtime.
84 */
85 private final MaskingClassLoader coreClassLoader = new MaskingClassLoader(getClass().getClassLoader());
86
87 public ClassicPluginStrategy(PluginManager pluginManager) {
88 this.pluginManager = pluginManager;
89 }
90
91 public PluginWrapper createPluginWrapper(File archive) throws IOException {
92 final Manifest manifest;
93 URL baseResourceURL;
94
95 File expandDir = null;
96 // if .hpi, this is the directory where war is expanded
97
98 boolean isLinked = archive.getName().endsWith(".hpl") || archive.getName().endsWith(".jpl");
99 if (isLinked) {
100 // resolve the .hpl file to the location of the manifest file
101 final String firstLine = IOUtils.readFirstLine(new FileInputStream(archive), "UTF-8");
102 if (firstLine.startsWith("Manifest-Version:")) {
103 // this is the manifest already
104 } else {
105 // indirection
106 archive = resolve(archive, firstLine);
107 }
108 // then parse manifest
109 FileInputStream in = new FileInputStream(archive);
110 try {
111 manifest = new Manifest(in);
112 } catch (IOException e) {
113 throw new IOException2("Failed to load " + archive, e);
114 } finally {
115 in.close();
116 }
117 } else {
118 if (archive.isDirectory()) {// already expanded
119 expandDir = archive;
120 } else {
121 expandDir = new File(archive.getParentFile(), PluginWrapper.getBaseName(archive));
122 explode(archive, expandDir);
123 }
124
125 File manifestFile = new File(expandDir, "META-INF/MANIFEST.MF");
126 if (!manifestFile.exists()) {
127 throw new IOException(
128 "Plugin installation failed. No manifest at "
129 + manifestFile);
130 }
131 FileInputStream fin = new FileInputStream(manifestFile);
132 try {
133 manifest = new Manifest(fin);
134 } finally {
135 fin.close();
136 }
137 }
138
139 final Attributes atts = manifest.getMainAttributes();
140
141 // TODO: define a mechanism to hide classes
142 // String export = manifest.getMainAttributes().getValue("Export");
143
144 List<File> paths = new ArrayList<File>();
145 if (isLinked) {
146 parseClassPath(manifest, archive, paths, "Libraries", ",");
147 parseClassPath(manifest, archive, paths, "Class-Path", " +"); // backward compatibility
148
149 baseResourceURL = resolve(archive,atts.getValue("Resource-Path")).toURI().toURL();
150 } else {
151 File classes = new File(expandDir, "WEB-INF/classes");
152 if (classes.exists())
153 paths.add(classes);
154 File lib = new File(expandDir, "WEB-INF/lib");
155 File[] libs = lib.listFiles(JAR_FILTER);
156 if (libs != null)
157 paths.addAll(Arrays.asList(libs));
158
159 baseResourceURL = expandDir.toURI().toURL();
160 }
161 File disableFile = new File(archive.getPath() + ".disabled");
162 if (disableFile.exists()) {
163 LOGGER.info("Plugin " + archive.getName() + " is disabled");
164 }
165
166 // compute dependencies
167 List<PluginWrapper.Dependency> dependencies = new ArrayList<PluginWrapper.Dependency>();
168 List<PluginWrapper.Dependency> optionalDependencies = new ArrayList<PluginWrapper.Dependency>();
169 String v = atts.getValue("Plugin-Dependencies");
170 if (v != null) {
171 for (String s : v.split(",")) {
172 PluginWrapper.Dependency d = new PluginWrapper.Dependency(s);
173 if (d.optional) {
174 optionalDependencies.add(d);
175 } else {
176 dependencies.add(d);
177 }
178 }
179 }
180 for (DetachedPlugin detached : DETACHED_LIST)
181 detached.fix(atts,optionalDependencies);
182
183 // Register global classpath mask. This is useful for hiding JavaEE APIs that you might see from the container,
184 // such as database plugin for JPA support. The Mask-Classes attribute is insufficient because those classes
185 // also need to be masked by all the other plugins that depend on the database plugin.
186 String masked = atts.getValue("Global-Mask-Classes");
187 if(masked!=null) {
188 for (String pkg : masked.trim().split("[ \t\r\n]+"))
189 coreClassLoader.add(pkg);
190 }
191
192 ClassLoader dependencyLoader = new DependencyClassLoader(coreClassLoader, archive, Util.join(dependencies,optionalDependencies));
193 dependencyLoader = getBaseClassLoader(atts, dependencyLoader);
194
195 return new PluginWrapper(pluginManager, archive, manifest, baseResourceURL,
196 createClassLoader(paths, dependencyLoader, atts), disableFile, dependencies, optionalDependencies);
197 }
198
199 @Deprecated
200 protected ClassLoader createClassLoader(List<File> paths, ClassLoader parent) throws IOException {
201 return createClassLoader( paths, parent, null );
202 }
203
204 /**
205 * Creates the classloader that can load all the specified jar files and delegate to the given parent.
206 */
207 protected ClassLoader createClassLoader(List<File> paths, ClassLoader parent, Attributes atts) throws IOException {
208 if (atts != null) {
209 String usePluginFirstClassLoader = atts.getValue( "PluginFirstClassLoader" );
210 if (Boolean.valueOf( usePluginFirstClassLoader )) {
211 PluginFirstClassLoader classLoader = new PluginFirstClassLoader();
212 classLoader.setParentFirst( false );
213 classLoader.setParent( parent );
214 classLoader.addPathFiles( paths );
215 return classLoader;
216 }
217 }
218 if(useAntClassLoader && !Closeable.class.isAssignableFrom(URLClassLoader.class)) {
219 // using AntClassLoader with Closeable so that we can predictably release jar files opened by URLClassLoader
220 AntClassLoader2 classLoader = new AntClassLoader2(parent);
221 classLoader.addPathFiles(paths);
222 return classLoader;
223 } else {
224 // Tom reported that AntClassLoader has a performance issue when Hudson keeps trying to load a class that doesn't exist,
225 // so providing a legacy URLClassLoader support, too
226 List<URL> urls = new ArrayList<URL>();
227 for (File path : paths)
228 urls.add(path.toURI().toURL());
229 return new URLClassLoader(urls.toArray(new URL[urls.size()]),parent);
230 }
231 }
232
233 /**
234 * Information about plugins that were originally in the core.
235 */
236 private static final class DetachedPlugin {
237 private final String shortName;
238 /**
239 * Plugins built for this Jenkins version (and earlier) will automatically be assumed to have
240 * this plugin in its dependency.
241 *
242 * When core/pom.xml version is 1.123-SNAPSHOT when the code is removed, then this value should
243 * be "1.123.*" (because 1.124 will be the first version that doesn't include the removed code.)
244 */
245 private final VersionNumber splitWhen;
246 private final String requireVersion;
247
248 private DetachedPlugin(String shortName, String splitWhen, String requireVersion) {
249 this.shortName = shortName;
250 this.splitWhen = new VersionNumber(splitWhen);
251 this.requireVersion = requireVersion;
252 }
253
254 private void fix(Attributes atts, List<PluginWrapper.Dependency> optionalDependencies) {
255 // don't fix the dependency for yourself, or else we'll have a cycle
256 String yourName = atts.getValue("Short-Name");
257 if (shortName.equals(yourName)) return;
258
259 // some earlier versions of maven-hpi-plugin apparently puts "null" as a literal in Hudson-Version. watch out for them.
260 String jenkinsVersion = atts.getValue("Jenkins-Version");
261 if (jenkinsVersion==null)
262 jenkinsVersion = atts.getValue("Hudson-Version");
263 if (jenkinsVersion == null || jenkinsVersion.equals("null") || new VersionNumber(jenkinsVersion).compareTo(splitWhen) <= 0)
264 optionalDependencies.add(new PluginWrapper.Dependency(shortName+':'+requireVersion));
265 }
266 }
267
268 private static final List<DetachedPlugin> DETACHED_LIST = Arrays.asList(
269 new DetachedPlugin("maven-plugin","1.296","1.296"),
270 new DetachedPlugin("subversion","1.310","1.0"),
271 new DetachedPlugin("cvs","1.340","0.1"),
272 new DetachedPlugin("ant","1.430.*","1.0"),
273 new DetachedPlugin("javadoc","1.430.*","1.0"),
274 new DetachedPlugin("external-monitor-job","1.467.*","1.0"),
275 new DetachedPlugin("ldap","1.467.*","1.0"),
276 new DetachedPlugin("pam-auth","1.467.*","1.0"),
277 new DetachedPlugin("mailer","1.493.*","1.2")
278 );
279
280 /**
281 * Computes the classloader that takes the class masking into account.
282 *
283 * <p>
284 * This mechanism allows plugins to have their own versions for libraries that core bundles.
285 */
286 private ClassLoader getBaseClassLoader(Attributes atts, ClassLoader base) {
287 String masked = atts.getValue("Mask-Classes");
288 if(masked!=null)
289 base = new MaskingClassLoader(base, masked.trim().split("[ \t\r\n]+"));
290 return base;
291 }
292
293 public void initializeComponents(PluginWrapper plugin) {
294 }
295
296 public <T> List<ExtensionComponent<T>> findComponents(Class<T> type, Hudson hudson) {
297
298 List<ExtensionFinder> finders;
299 if (type==ExtensionFinder.class) {
300 // Avoid infinite recursion of using ExtensionFinders to find ExtensionFinders
301 finders = Collections.<ExtensionFinder>singletonList(new ExtensionFinder.Sezpoz());
302 } else {
303 finders = hudson.getExtensionList(ExtensionFinder.class);
304 }
305
306 /**
307 * See {@link ExtensionFinder#scout(Class, Hudson)} for the dead lock issue and what this does.
308 */
309 if (LOGGER.isLoggable(Level.FINER))
310 LOGGER.log(Level.FINER,"Scout-loading ExtensionList: "+type, new Throwable());
311 for (ExtensionFinder finder : finders) {
312 finder.scout(type, hudson);
313 }
314
315 List<ExtensionComponent<T>> r = Lists.newArrayList();
316 for (ExtensionFinder finder : finders) {
317 try {
318 r.addAll(finder._find(type, hudson));
319 } catch (AbstractMethodError e) {
320 // backward compatibility
321 for (T t : finder.findExtensions(type, hudson))
322 r.add(new ExtensionComponent<T>(t));
323 }
324 }
325
326 List<ExtensionComponent<T>> filtered = Lists.newArrayList();
327 for (ExtensionComponent<T> e : r) {
328 if (ExtensionFilter.isAllowed(type,e))
329 filtered.add(e);
330 }
331
332 return filtered;
333 }
334
335 public void load(PluginWrapper wrapper) throws IOException {
336 // override the context classloader. This no longer makes sense,
337 // but it is left for the backward compatibility
338 ClassLoader old = Thread.currentThread().getContextClassLoader();
339 Thread.currentThread().setContextClassLoader(wrapper.classLoader);
340 try {
341 String className = wrapper.getPluginClass();
342 if(className==null) {
343 // use the default dummy instance
344 wrapper.setPlugin(new DummyImpl());
345 } else {
346 try {
347 Class<?> clazz = wrapper.classLoader.loadClass(className);
348 Object o = clazz.newInstance();
349 if(!(o instanceof Plugin)) {
350 throw new IOException(className+" doesn't extend from hudson.Plugin");
351 }
352 wrapper.setPlugin((Plugin) o);
353 } catch (LinkageError e) {
354 throw new IOException2("Unable to load " + className + " from " + wrapper.getShortName(),e);
355 } catch (ClassNotFoundException e) {
356 throw new IOException2("Unable to load " + className + " from " + wrapper.getShortName(),e);
357 } catch (IllegalAccessException e) {
358 throw new IOException2("Unable to create instance of " + className + " from " + wrapper.getShortName(),e);
359 } catch (InstantiationException e) {
360 throw new IOException2("Unable to create instance of " + className + " from " + wrapper.getShortName(),e);
361 }
362 }
363
364 // initialize plugin
365 try {
366 Plugin plugin = wrapper.getPlugin();
367 plugin.setServletContext(pluginManager.context);
368 startPlugin(wrapper);
369 } catch(Throwable t) {
370 // gracefully handle any error in plugin.
371 throw new IOException2("Failed to initialize",t);
372 }
373 } finally {
374 Thread.currentThread().setContextClassLoader(old);
375 }
376 }
377
378 public void startPlugin(PluginWrapper plugin) throws Exception {
379 plugin.getPlugin().start();
380 }
381
382 private static File resolve(File base, String relative) {
383 File rel = new File(relative);
384 if(rel.isAbsolute())
385 return rel;
386 else
387 return new File(base.getParentFile(),relative);
388 }
389
390 private static void parseClassPath(Manifest manifest, File archive, List<File> paths, String attributeName, String separator) throws IOException {
391 String classPath = manifest.getMainAttributes().getValue(attributeName);
392 if(classPath==null) return; // attribute not found
393 for (String s : classPath.split(separator)) {
394 File file = resolve(archive, s);
395 if(file.getName().contains("*")) {
396 // handle wildcard
397 FileSet fs = new FileSet();
398 File dir = file.getParentFile();
399 fs.setDir(dir);
400 fs.setIncludes(file.getName());
401 for( String included : fs.getDirectoryScanner(new Project()).getIncludedFiles() ) {
402 paths.add(new File(dir,included));
403 }
404 } else {
405 if(!file.exists())
406 throw new IOException("No such file: "+file);
407 paths.add(file);
408 }
409 }
410 }
411
412 /**
413 * Explodes the plugin into a directory, if necessary.
414 */
415 private static void explode(File archive, File destDir) throws IOException {
416 if(!destDir.exists())
417 destDir.mkdirs();
418
419 // timestamp check
420 File explodeTime = new File(destDir,".timestamp");
421 if(explodeTime.exists() && explodeTime.lastModified()==archive.lastModified())
422 return; // no need to expand
423
424 // delete the contents so that old files won't interfere with new files
425 Util.deleteRecursive(destDir);
426
427 try {
428 Expand e = new Expand();
429 e.setProject(new Project());
430 e.setTaskType("unzip");
431 e.setSrc(archive);
432 e.setDest(destDir);
433 e.execute();
434 } catch (BuildException x) {
435 throw new IOException2("Failed to expand " + archive,x);
436 }
437
438 try {
439 new FilePath(explodeTime).touch(archive.lastModified());
440 } catch (InterruptedException e) {
441 throw new AssertionError(e); // impossible
442 }
443 }
444
445 /**
446 * Used to load classes from dependency plugins.
447 */
448 final class DependencyClassLoader extends ClassLoader {
449 /**
450 * This classloader is created for this plugin. Useful during debugging.
451 */
452 private final File _for;
453
454 private List<Dependency> dependencies;
455
456 /**
457 * Topologically sorted list of transient dependencies.
458 */
459 private volatile List<PluginWrapper> transientDependencies;
460
461 public DependencyClassLoader(ClassLoader parent, File archive, List<Dependency> dependencies) {
462 super(parent);
463 this._for = archive;
464 this.dependencies = dependencies;
465 }
466
467 private List<PluginWrapper> getTransitiveDependencies() {
468 if (transientDependencies==null) {
469 CyclicGraphDetector<PluginWrapper> cgd = new CyclicGraphDetector<PluginWrapper>() {
470 @Override
471 protected List<PluginWrapper> getEdges(PluginWrapper pw) {
472 List<PluginWrapper> dep = new ArrayList<PluginWrapper>();
473 for (Dependency d : pw.getDependencies()) {
474 PluginWrapper p = pluginManager.getPlugin(d.shortName);
475 if (p!=null)
476 dep.add(p);
477 }
478 return dep;
479 }
480 };
481
482 try {
483 for (Dependency d : dependencies) {
484 PluginWrapper p = pluginManager.getPlugin(d.shortName);
485 if (p!=null)
486 cgd.run(Collections.singleton(p));
487 }
488 } catch (CycleDetectedException e) {
489 throw new AssertionError(e); // such error should have been reported earlier
490 }
491
492 transientDependencies = cgd.getSorted();
493 }
494 return transientDependencies;
495 }
496
497 // public List<PluginWrapper> getDependencyPluginWrappers() {
498 // List<PluginWrapper> r = new ArrayList<PluginWrapper>();
499 // for (Dependency d : dependencies) {
500 // PluginWrapper w = pluginManager.getPlugin(d.shortName);
501 // if (w!=null) r.add(w);
502 // }
503 // return r;
504 // }
505
506 @Override
507 protected Class<?> findClass(String name) throws ClassNotFoundException {
508 if (PluginManager.FAST_LOOKUP) {
509 for (PluginWrapper pw : getTransitiveDependencies()) {
510 try {
511 Class c = clt.findLoadedClass(pw.classLoader,name);
512 if (c!=null) return c;
513 return clt.findClass(pw.classLoader,name);
514 } catch (InvocationTargetException e) {
515 //not found. try next
516 }
517 }
518 } else {
519 for (Dependency dep : dependencies) {
520 PluginWrapper p = pluginManager.getPlugin(dep.shortName);
521 if(p!=null)
522 try {
523 return p.classLoader.loadClass(name);
524 } catch (ClassNotFoundException _) {
525 // try next
526 }
527 }
528 }
529
530 throw new ClassNotFoundException(name);
531 }
532
533 @Override
534 protected Enumeration<URL> findResources(String name) throws IOException {
535 HashSet<URL> result = new HashSet<URL>();
536
537 if (PluginManager.FAST_LOOKUP) {
538 try {
539 for (PluginWrapper pw : getTransitiveDependencies()) {
540 Enumeration<URL> urls = clt.findResources(pw.classLoader, name);
541 while (urls != null && urls.hasMoreElements())
542 result.add(urls.nextElement());
543 }
544 } catch (InvocationTargetException e) {
545 throw new Error(e);
546 }
547 } else {
548 for (Dependency dep : dependencies) {
549 PluginWrapper p = pluginManager.getPlugin(dep.shortName);
550 if (p!=null) {
551 Enumeration<URL> urls = p.classLoader.getResources(name);
552 while (urls != null && urls.hasMoreElements())
553 result.add(urls.nextElement());
554 }
555 }
556 }
557
558 return Collections.enumeration(result);
559 }
560
561 @Override
562 protected URL findResource(String name) {
563 if (PluginManager.FAST_LOOKUP) {
564 try {
565 for (PluginWrapper pw : getTransitiveDependencies()) {
566 URL url = clt.findResource(pw.classLoader,name);
567 if (url!=null) return url;
568 }
569 } catch (InvocationTargetException e) {
570 throw new Error(e);
571 }
572 } else {
573 for (Dependency dep : dependencies) {
574 PluginWrapper p = pluginManager.getPlugin(dep.shortName);
575 if(p!=null) {
576 URL url = p.classLoader.getResource(name);
577 if (url!=null)
578 return url;
579 }
580 }
581 }
582
583 return null;
584 }
585 }
586
587 /**
588 * {@link AntClassLoader} with a few methods exposed and {@link Closeable} support.
589 * Deprecated as of Java 7, retained only for Java 5/6.
590 */
591 private static final class AntClassLoader2 extends AntClassLoader implements Closeable {
592 private final Vector pathComponents;
593
594 private AntClassLoader2(ClassLoader parent) {
595 super(parent,true);
596
597 try {
598 Field $pathComponents = AntClassLoader.class.getDeclaredField("pathComponents");
599 $pathComponents.setAccessible(true);
600 pathComponents = (Vector)$pathComponents.get(this);
601 } catch (NoSuchFieldException e) {
602 throw new Error(e);
603 } catch (IllegalAccessException e) {
604 throw new Error(e);
605 }
606 }
607
608 public void addPathFiles(Collection<File> paths) throws IOException {
609 for (File f : paths)
610 addPathFile(f);
611 }
612
613 public void close() throws IOException {
614 cleanup();
615 }
616
617 /**
618 * As of 1.8.0, {@link AntClassLoader} doesn't implement {@link #findResource(String)}
619 * in any meaningful way, which breaks fast lookup. Implement it properly.
620 */
621 @Override
622 protected URL findResource(String name) {
623 URL url = null;
624
625 // try and load from this loader if the parent either didn't find
626 // it or wasn't consulted.
627 Enumeration e = pathComponents.elements();
628 while (e.hasMoreElements() && url == null) {
629 File pathComponent = (File) e.nextElement();
630 url = getResourceURL(pathComponent, name);
631 if (url != null) {
632 log("Resource " + name + " loaded from ant loader", Project.MSG_DEBUG);
633 }
634 }
635
636 return url;
637 }
638 }
639
640 public static boolean useAntClassLoader = Boolean.getBoolean(ClassicPluginStrategy.class.getName()+".useAntClassLoader");
641 private static final Logger LOGGER = Logger.getLogger(ClassicPluginStrategy.class.getName());
642 }
643