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