001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2009 SonarSource SA 004 * mailto:contact AT sonarsource DOT com 005 * 006 * Sonar is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * Sonar is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with Sonar; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 019 */ 020 021 package org.sonar.core.classloaders; 022 023 import java.net.URL; 024 import java.util.Arrays; 025 import java.util.Collection; 026 import java.util.List; 027 028 import org.apache.commons.lang.StringUtils; 029 import org.codehaus.plexus.classworlds.ClassWorld; 030 import org.codehaus.plexus.classworlds.realm.ClassRealm; 031 import org.codehaus.plexus.classworlds.realm.DuplicateRealmException; 032 import org.codehaus.plexus.classworlds.realm.NoSuchRealmException; 033 import org.sonar.api.utils.Logs; 034 import org.sonar.api.utils.SonarException; 035 036 import com.google.common.collect.Lists; 037 038 /** 039 * Encapsulates manipulations with ClassLoaders, such as creation and establishing dependencies. Current implementation based on 040 * {@link ClassWorld}. 041 * 042 * <h3>IMPORTANT</h3> 043 * <p> 044 * If we have pluginA , then all classes and resources from package and subpackages of <b>org.sonar.plugins.pluginA.api</b> will be visible 045 * for all other plugins even if they located in dependent library. 046 * </p> 047 * 048 * <h4>Search order for {@link ClassRealm} :</h4> 049 * <ul> 050 * <li>parent class loader (passed via the constructor) if there is one</li> 051 * <li>imports</li> 052 * <li>realm's constituents</li> 053 * <li>parent realm</li> 054 * </ul> 055 * 056 * @since 2.4 057 */ 058 public class ClassLoadersCollection { 059 060 private static final String[] PREFIXES_TO_EXPORT = { "org.sonar.plugins.", "com.sonar.plugins.", "com.sonarsource.plugins." }; 061 062 private ClassWorld world = new ClassWorld(); 063 private ClassLoader baseClassLoader; 064 065 public ClassLoadersCollection(ClassLoader baseClassLoader) { 066 this.baseClassLoader = baseClassLoader; 067 } 068 069 /** 070 * Generates URLClassLoader with specified delegation model. 071 * 072 * @param key plugin key 073 * @param urls libraries 074 * @param childFirst true, if child-first delegation model required instead of parent-first 075 * @return created ClassLoader, but actually this method shouldn't return anything, because dependencies must be established - see 076 * {@link #done()}. 077 */ 078 public ClassLoader createClassLoader(String key, Collection<URL> urls, boolean childFirst) { 079 try { 080 List<URL> resources = Lists.newArrayList(); 081 List<URL> others = Lists.newArrayList(); 082 for (URL url : urls) { 083 if (isResource(url)) { 084 resources.add(url); 085 } else { 086 others.add(url); 087 } 088 } 089 ClassLoader parent; 090 if (resources.isEmpty()) { 091 parent = baseClassLoader; 092 } else { 093 parent = new ResourcesClassLoader(resources, baseClassLoader); 094 } 095 final ClassRealm realm; 096 if (childFirst) { 097 ClassRealm parentRealm = world.newRealm(key + "-parent", parent); 098 realm = parentRealm.createChildRealm(key); 099 } else { 100 realm = world.newRealm(key, parent); 101 } 102 for (URL url : others) { 103 realm.addURL(url); 104 } 105 return realm; 106 } catch (DuplicateRealmException e) { 107 throw new SonarException(e); 108 } 109 } 110 111 /** 112 * Establishes dependencies among ClassLoaders. 113 */ 114 public void done() { 115 for (Object o : world.getRealms()) { 116 ClassRealm realm = (ClassRealm) o; 117 if ( !StringUtils.endsWith(realm.getId(), "-parent")) { 118 String[] packagesToExport = new String[PREFIXES_TO_EXPORT.length]; 119 for (int i = 0; i < PREFIXES_TO_EXPORT.length; i++) { 120 // important to have dot at the end of package name only for classworlds 1.1 121 packagesToExport[i] = PREFIXES_TO_EXPORT[i] + realm.getId() + ".api"; 122 } 123 export(realm, packagesToExport); 124 } 125 } 126 } 127 128 /** 129 * Exports specified packages from given ClassRealm to all others. 130 */ 131 private void export(ClassRealm realm, String... packages) { 132 Logs.INFO.debug("Exporting " + Arrays.toString(packages) + " from " + realm.getId()); 133 for (Object o : world.getRealms()) { 134 ClassRealm dep = (ClassRealm) o; 135 if ( !StringUtils.equals(dep.getId(), realm.getId())) { 136 try { 137 for (String packageName : packages) { 138 dep.importFrom(realm.getId(), packageName); 139 } 140 } catch (NoSuchRealmException e) { 141 // should never happen 142 throw new SonarException(e); 143 } 144 } 145 } 146 } 147 148 /** 149 * Note that this method should be called only after creation of all ClassLoaders - see {@link #done()}. 150 */ 151 public ClassLoader get(String key) { 152 try { 153 return world.getRealm(key); 154 } catch (NoSuchRealmException e) { 155 return null; 156 } 157 } 158 159 private boolean isResource(URL url) { 160 String path = url.getPath(); 161 return !StringUtils.endsWith(path, ".jar") && !StringUtils.endsWith(path, "/"); 162 } 163 164 }