001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2011 SonarSource 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 com.google.common.collect.Lists; 029 import org.apache.commons.lang.StringUtils; 030 import org.codehaus.plexus.classworlds.ClassWorld; 031 import org.codehaus.plexus.classworlds.realm.ClassRealm; 032 import org.codehaus.plexus.classworlds.realm.DuplicateRealmException; 033 import org.codehaus.plexus.classworlds.realm.NoSuchRealmException; 034 import org.sonar.api.utils.Logs; 035 import org.sonar.api.utils.SonarException; 036 037 /** 038 * Encapsulates manipulations with ClassLoaders, such as creation and establishing dependencies. Current implementation based on 039 * {@link ClassWorld}. 040 * 041 * <h3>IMPORTANT</h3> 042 * <p> 043 * If we have pluginA , then all classes and resources from package and subpackages of <b>org.sonar.plugins.pluginA.api</b> will be visible 044 * for all other plugins even if they located in dependent library. 045 * </p> 046 * 047 * <h4>Search order for {@link ClassRealm} :</h4> 048 * <ul> 049 * <li>parent class loader (passed via the constructor) if there is one</li> 050 * <li>imports</li> 051 * <li>realm's constituents</li> 052 * <li>parent realm</li> 053 * </ul> 054 * 055 * @since 2.4 056 */ 057 public class ClassLoadersCollection { 058 059 private static final String[] PREFIXES_TO_EXPORT = { "org.sonar.plugins.", "com.sonar.plugins.", "com.sonarsource.plugins." }; 060 061 private ClassWorld world = new ClassWorld(); 062 private ClassLoader baseClassLoader; 063 064 public ClassLoadersCollection(ClassLoader baseClassLoader) { 065 this.baseClassLoader = baseClassLoader; 066 } 067 068 /** 069 * Generates URLClassLoader with specified delegation model. 070 * 071 * @param key plugin key 072 * @param urls libraries 073 * @param childFirst true, if child-first delegation model required instead of parent-first 074 * @return created ClassLoader, but actually this method shouldn't return anything, because dependencies must be established - see 075 * {@link #done()}. 076 */ 077 public ClassLoader createClassLoader(String key, Collection<URL> urls, boolean childFirst) { 078 try { 079 List<URL> resources = Lists.newArrayList(); 080 List<URL> others = Lists.newArrayList(); 081 for (URL url : urls) { 082 if (isResource(url)) { 083 resources.add(url); 084 } else { 085 others.add(url); 086 } 087 } 088 ClassLoader parent; 089 if (resources.isEmpty()) { 090 parent = baseClassLoader; 091 } else { 092 parent = new ResourcesClassLoader(resources, baseClassLoader); 093 } 094 final ClassRealm realm; 095 if (childFirst) { 096 ClassRealm parentRealm = world.newRealm(key + "-parent", parent); 097 realm = parentRealm.createChildRealm(key); 098 } else { 099 realm = world.newRealm(key, parent); 100 } 101 for (URL url : others) { 102 realm.addURL(url); 103 } 104 return realm; 105 } catch (DuplicateRealmException e) { 106 throw new SonarException(e); 107 } 108 } 109 110 public void extend(String baseKey, String key, Collection<URL> urls) { 111 try { 112 ClassRealm base = world.getRealm(baseKey); 113 base.createChildRealm(key); // we create new realm to be able to return it by key without conversion to baseKey 114 for (URL url : urls) { 115 base.addURL(url); 116 } 117 } catch (NoSuchRealmException e) { 118 throw new SonarException(e); 119 } catch (DuplicateRealmException e) { 120 throw new SonarException(e); 121 } 122 } 123 124 /** 125 * Establishes dependencies among ClassLoaders. 126 */ 127 public void done() { 128 for (Object o : world.getRealms()) { 129 ClassRealm realm = (ClassRealm) o; 130 if (!StringUtils.endsWith(realm.getId(), "-parent")) { 131 String[] packagesToExport = new String[PREFIXES_TO_EXPORT.length]; 132 for (int i = 0; i < PREFIXES_TO_EXPORT.length; i++) { 133 // important to have dot at the end of package name only for classworlds 1.1 134 packagesToExport[i] = PREFIXES_TO_EXPORT[i] + realm.getId() + ".api"; 135 } 136 export(realm, packagesToExport); 137 } 138 } 139 } 140 141 /** 142 * Exports specified packages from given ClassRealm to all others. 143 */ 144 private void export(ClassRealm realm, String... packages) { 145 Logs.INFO.debug("Exporting " + Arrays.toString(packages) + " from " + realm.getId()); 146 for (Object o : world.getRealms()) { 147 ClassRealm dep = (ClassRealm) o; 148 if (!StringUtils.equals(dep.getId(), realm.getId())) { 149 try { 150 for (String packageName : packages) { 151 dep.importFrom(realm.getId(), packageName); 152 } 153 } catch (NoSuchRealmException e) { 154 // should never happen 155 throw new SonarException(e); 156 } 157 } 158 } 159 } 160 161 /** 162 * Note that this method should be called only after creation of all ClassLoaders - see {@link #done()}. 163 */ 164 public ClassLoader get(String key) { 165 try { 166 return world.getRealm(key); 167 } catch (NoSuchRealmException e) { 168 return null; 169 } 170 } 171 172 private boolean isResource(URL url) { 173 String path = url.getPath(); 174 return !StringUtils.endsWith(path, ".jar") && !StringUtils.endsWith(path, "/"); 175 } 176 177 }