1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.classrealm;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.io.File;
26 import java.net.MalformedURLException;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Objects;
33 import java.util.Random;
34 import java.util.Set;
35 import java.util.TreeMap;
36 import java.util.stream.Collectors;
37
38 import org.apache.maven.artifact.ArtifactUtils;
39 import org.apache.maven.classrealm.ClassRealmRequest.RealmType;
40 import org.apache.maven.extension.internal.CoreExports;
41 import org.apache.maven.internal.CoreRealm;
42 import org.apache.maven.model.Model;
43 import org.apache.maven.model.Plugin;
44 import org.codehaus.plexus.classworlds.ClassWorld;
45 import org.codehaus.plexus.classworlds.realm.ClassRealm;
46 import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
47 import org.eclipse.aether.artifact.Artifact;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51
52
53
54
55
56
57 @Named
58 @Singleton
59 public class DefaultClassRealmManager implements ClassRealmManager {
60 public static final String API_REALMID = "maven.api";
61
62 public static final String API_V4_REALMID = "maven.api.v4";
63
64
65
66
67
68
69
70
71
72 private static final ClassLoader PARENT_CLASSLOADER = ClassWorld.class.getClassLoader();
73
74 private final Logger logger = LoggerFactory.getLogger(getClass());
75
76 private final ClassWorld world;
77
78 private final ClassRealm containerRealm;
79
80
81 private final List<ClassRealmManagerDelegate> delegates;
82
83 private final ClassRealm mavenApiRealm;
84
85 private final ClassRealm maven4ApiRealm;
86
87
88
89
90
91 private final Set<String> providedArtifacts;
92
93 private final Set<String> providedArtifactsV4;
94
95 @Inject
96 public DefaultClassRealmManager(
97 CoreRealm coreRealm, List<ClassRealmManagerDelegate> delegates, CoreExports exports) {
98 this.world = coreRealm.getClassWorld();
99 this.containerRealm = coreRealm.getRealm();
100 this.delegates = delegates;
101
102 Map<String, ClassLoader> foreignImports = exports.getExportedPackages();
103
104 this.mavenApiRealm = createRealm(
105 API_REALMID,
106 RealmType.Core,
107 null ,
108 null ,
109 foreignImports,
110 null );
111
112 Map<String, ClassLoader> apiV4Imports = new HashMap<>();
113 apiV4Imports.put("org.apache.maven.api", containerRealm);
114 apiV4Imports.put("org.slf4j", containerRealm);
115 this.maven4ApiRealm = createRealm(API_V4_REALMID, RealmType.Core, null, null, apiV4Imports, null);
116
117 this.providedArtifacts = exports.getExportedArtifacts();
118
119 this.providedArtifactsV4 = providedArtifacts.stream()
120 .filter(ga -> ga.startsWith("org.apache.maven:maven-api-"))
121 .collect(Collectors.toSet());
122 }
123
124 private ClassRealm newRealm(String id) {
125 synchronized (world) {
126 String realmId = id;
127
128 Random random = new Random();
129
130 while (true) {
131 try {
132 ClassRealm classRealm = world.newRealm(realmId, null);
133
134 logger.debug("Created new class realm {}", realmId);
135
136 return classRealm;
137 } catch (DuplicateRealmException e) {
138 realmId = id + '-' + random.nextInt();
139 }
140 }
141 }
142 }
143
144 public ClassRealm getMavenApiRealm() {
145 return mavenApiRealm;
146 }
147
148 @Override
149 public ClassRealm getMaven4ApiRealm() {
150 return maven4ApiRealm;
151 }
152
153
154
155
156
157
158
159
160
161
162
163
164
165 private ClassRealm createRealm(
166 String baseRealmId,
167 RealmType type,
168 ClassLoader parent,
169 List<String> parentImports,
170 Map<String, ClassLoader> foreignImports,
171 List<Artifact> artifacts) {
172 List<ClassRealmConstituent> constituents = new ArrayList<>(artifacts == null ? 0 : artifacts.size());
173
174 if (artifacts != null && !artifacts.isEmpty()) {
175 boolean v4api = foreignImports != null && foreignImports.containsValue(maven4ApiRealm);
176 for (Artifact artifact : artifacts) {
177 if (!isProvidedArtifact(artifact, v4api) && artifact.getFile() != null) {
178 constituents.add(new ArtifactClassRealmConstituent(artifact));
179 } else if (logger.isDebugEnabled()) {
180 logger.debug(" Excluded: {}", getId(artifact));
181 }
182 }
183 }
184
185 if (parentImports != null) {
186 parentImports = new ArrayList<>(parentImports);
187 } else {
188 parentImports = new ArrayList<>();
189 }
190
191 if (foreignImports != null) {
192 foreignImports = new TreeMap<>(foreignImports);
193 } else {
194 foreignImports = new TreeMap<>();
195 }
196
197 ClassRealm classRealm = newRealm(baseRealmId);
198
199 if (parent != null) {
200 classRealm.setParentClassLoader(parent);
201 }
202
203 callDelegates(classRealm, type, parent, parentImports, foreignImports, constituents);
204
205 wireRealm(classRealm, parentImports, foreignImports);
206
207 populateRealm(classRealm, constituents);
208
209 return classRealm;
210 }
211
212 public ClassRealm getCoreRealm() {
213 return containerRealm;
214 }
215
216 public ClassRealm createProjectRealm(Model model, List<Artifact> artifacts) {
217 Objects.requireNonNull(model, "model cannot be null");
218
219 ClassLoader parent = getMavenApiRealm();
220
221 return createRealm(getKey(model), RealmType.Project, parent, null, null, artifacts);
222 }
223
224 private static String getKey(Model model) {
225 return "project>" + model.getGroupId() + ":" + model.getArtifactId() + ":" + model.getVersion();
226 }
227
228 public ClassRealm createExtensionRealm(Plugin plugin, List<Artifact> artifacts) {
229 Objects.requireNonNull(plugin, "plugin cannot be null");
230
231 Map<String, ClassLoader> foreignImports = Collections.singletonMap("", getMavenApiRealm());
232
233 return createRealm(
234 getKey(plugin, true), RealmType.Extension, PARENT_CLASSLOADER, null, foreignImports, artifacts);
235 }
236
237 private boolean isProvidedArtifact(Artifact artifact, boolean v4api) {
238 Set<String> provided = v4api ? providedArtifactsV4 : providedArtifacts;
239 return provided.contains(artifact.getGroupId() + ":" + artifact.getArtifactId());
240 }
241
242 public ClassRealm createPluginRealm(
243 Plugin plugin,
244 ClassLoader parent,
245 List<String> parentImports,
246 Map<String, ClassLoader> foreignImports,
247 List<Artifact> artifacts) {
248 Objects.requireNonNull(plugin, "plugin cannot be null");
249
250 if (parent == null) {
251 parent = PARENT_CLASSLOADER;
252 }
253
254 return createRealm(getKey(plugin, false), RealmType.Plugin, parent, parentImports, foreignImports, artifacts);
255 }
256
257 private static String getKey(Plugin plugin, boolean extension) {
258 String version = ArtifactUtils.toSnapshotVersion(plugin.getVersion());
259 return (extension ? "extension>" : "plugin>") + plugin.getGroupId() + ":" + plugin.getArtifactId() + ":"
260 + version;
261 }
262
263 private static String getId(Artifact artifact) {
264 return getId(
265 artifact.getGroupId(),
266 artifact.getArtifactId(),
267 artifact.getExtension(),
268 artifact.getClassifier(),
269 artifact.getBaseVersion());
270 }
271
272 private static String getId(ClassRealmConstituent constituent) {
273 return getId(
274 constituent.getGroupId(),
275 constituent.getArtifactId(),
276 constituent.getType(),
277 constituent.getClassifier(),
278 constituent.getVersion());
279 }
280
281 private static String getId(String gid, String aid, String type, String cls, String ver) {
282 return gid + ':' + aid + ':' + type + ((cls != null && !cls.isEmpty()) ? ':' + cls : "") + ':' + ver;
283 }
284
285 private void callDelegates(
286 ClassRealm classRealm,
287 RealmType type,
288 ClassLoader parent,
289 List<String> parentImports,
290 Map<String, ClassLoader> foreignImports,
291 List<ClassRealmConstituent> constituents) {
292 List<ClassRealmManagerDelegate> delegates = new ArrayList<>(this.delegates);
293
294 if (!delegates.isEmpty()) {
295 ClassRealmRequest request =
296 new DefaultClassRealmRequest(type, parent, parentImports, foreignImports, constituents);
297
298 for (ClassRealmManagerDelegate delegate : delegates) {
299 try {
300 delegate.setupRealm(classRealm, request);
301 } catch (Exception e) {
302 logger.error(
303 delegate.getClass().getName() + " failed to setup class realm " + classRealm + ": "
304 + e.getMessage(),
305 e);
306 }
307 }
308 }
309 }
310
311 private void populateRealm(ClassRealm classRealm, List<ClassRealmConstituent> constituents) {
312 logger.debug("Populating class realm {}", classRealm.getId());
313
314 for (ClassRealmConstituent constituent : constituents) {
315 File file = constituent.getFile();
316
317 if (logger.isDebugEnabled()) {
318 String id = getId(constituent);
319 logger.debug(" Included: {}", id);
320 }
321
322 try {
323 classRealm.addURL(file.toURI().toURL());
324 } catch (MalformedURLException e) {
325
326 logger.error(e.getMessage(), e);
327 }
328 }
329 }
330
331 private void wireRealm(ClassRealm classRealm, List<String> parentImports, Map<String, ClassLoader> foreignImports) {
332 if (foreignImports != null && !foreignImports.isEmpty()) {
333 logger.debug("Importing foreign packages into class realm {}", classRealm.getId());
334
335 for (Map.Entry<String, ClassLoader> entry : foreignImports.entrySet()) {
336 ClassLoader importedRealm = entry.getValue();
337 String imp = entry.getKey();
338
339 logger.debug(" Imported: {} < {}", imp, getId(importedRealm));
340
341 classRealm.importFrom(importedRealm, imp);
342 }
343 }
344
345 if (parentImports != null && !parentImports.isEmpty()) {
346 logger.debug("Importing parent packages into class realm {}", classRealm.getId());
347
348 for (String imp : parentImports) {
349 logger.debug(" Imported: {} < {}", imp, getId(classRealm.getParentClassLoader()));
350
351 classRealm.importFromParent(imp);
352 }
353 }
354 }
355
356 private static Object getId(ClassLoader classLoader) {
357 if (classLoader instanceof ClassRealm) {
358 return ((ClassRealm) classLoader).getId();
359 }
360 return classLoader;
361 }
362 }