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 @Override
145 public ClassRealm getMavenApiRealm() {
146 return mavenApiRealm;
147 }
148
149 @Override
150 public ClassRealm getMaven4ApiRealm() {
151 return maven4ApiRealm;
152 }
153
154
155
156
157
158
159
160
161
162
163
164
165
166 private ClassRealm createRealm(
167 String baseRealmId,
168 RealmType type,
169 ClassLoader parent,
170 List<String> parentImports,
171 Map<String, ClassLoader> foreignImports,
172 List<Artifact> artifacts) {
173 List<ClassRealmConstituent> constituents = new ArrayList<>(artifacts == null ? 0 : artifacts.size());
174
175 if (artifacts != null && !artifacts.isEmpty()) {
176 boolean v4api = foreignImports != null && foreignImports.containsValue(maven4ApiRealm);
177 for (Artifact artifact : artifacts) {
178 if (!isProvidedArtifact(artifact, v4api) && artifact.getFile() != null) {
179 constituents.add(new ArtifactClassRealmConstituent(artifact));
180 } else if (logger.isDebugEnabled()) {
181 logger.debug(" Excluded: {}", getId(artifact));
182 }
183 }
184 }
185
186 if (parentImports != null) {
187 parentImports = new ArrayList<>(parentImports);
188 } else {
189 parentImports = new ArrayList<>();
190 }
191
192 if (foreignImports != null) {
193 foreignImports = new TreeMap<>(foreignImports);
194 } else {
195 foreignImports = new TreeMap<>();
196 }
197
198 ClassRealm classRealm = newRealm(baseRealmId);
199
200 if (parent != null) {
201 classRealm.setParentClassLoader(parent);
202 }
203
204 callDelegates(classRealm, type, parent, parentImports, foreignImports, constituents);
205
206 wireRealm(classRealm, parentImports, foreignImports);
207
208 populateRealm(classRealm, constituents);
209
210 return classRealm;
211 }
212
213 @Override
214 public ClassRealm getCoreRealm() {
215 return containerRealm;
216 }
217
218 @Override
219 public ClassRealm createProjectRealm(Model model, List<Artifact> artifacts) {
220 Objects.requireNonNull(model, "model cannot be null");
221
222 ClassLoader parent = getMavenApiRealm();
223
224 return createRealm(getKey(model), RealmType.Project, parent, null, null, artifacts);
225 }
226
227 private static String getKey(Model model) {
228 return "project>" + model.getGroupId() + ":" + model.getArtifactId() + ":" + model.getVersion();
229 }
230
231 @Override
232 public ClassRealm createExtensionRealm(Plugin plugin, List<Artifact> artifacts) {
233 Objects.requireNonNull(plugin, "plugin cannot be null");
234
235 Map<String, ClassLoader> foreignImports = Collections.singletonMap("", getMavenApiRealm());
236
237 return createRealm(
238 getKey(plugin, true), RealmType.Extension, PARENT_CLASSLOADER, null, foreignImports, artifacts);
239 }
240
241 private boolean isProvidedArtifact(Artifact artifact, boolean v4api) {
242 Set<String> provided = v4api ? providedArtifactsV4 : providedArtifacts;
243 return provided.contains(artifact.getGroupId() + ":" + artifact.getArtifactId());
244 }
245
246 @Override
247 public ClassRealm createPluginRealm(
248 Plugin plugin,
249 ClassLoader parent,
250 List<String> parentImports,
251 Map<String, ClassLoader> foreignImports,
252 List<Artifact> artifacts) {
253 Objects.requireNonNull(plugin, "plugin cannot be null");
254
255 if (parent == null) {
256 parent = PARENT_CLASSLOADER;
257 }
258
259 return createRealm(getKey(plugin, false), RealmType.Plugin, parent, parentImports, foreignImports, artifacts);
260 }
261
262 private static String getKey(Plugin plugin, boolean extension) {
263 String version = ArtifactUtils.toSnapshotVersion(plugin.getVersion());
264 return (extension ? "extension>" : "plugin>") + plugin.getGroupId() + ":" + plugin.getArtifactId() + ":"
265 + version;
266 }
267
268 private static String getId(Artifact artifact) {
269 return getId(
270 artifact.getGroupId(),
271 artifact.getArtifactId(),
272 artifact.getExtension(),
273 artifact.getClassifier(),
274 artifact.getBaseVersion());
275 }
276
277 private static String getId(ClassRealmConstituent constituent) {
278 return getId(
279 constituent.getGroupId(),
280 constituent.getArtifactId(),
281 constituent.getType(),
282 constituent.getClassifier(),
283 constituent.getVersion());
284 }
285
286 private static String getId(String gid, String aid, String type, String cls, String ver) {
287 return gid + ':' + aid + ':' + type + ((cls != null && !cls.isEmpty()) ? ':' + cls : "") + ':' + ver;
288 }
289
290 private void callDelegates(
291 ClassRealm classRealm,
292 RealmType type,
293 ClassLoader parent,
294 List<String> parentImports,
295 Map<String, ClassLoader> foreignImports,
296 List<ClassRealmConstituent> constituents) {
297 List<ClassRealmManagerDelegate> delegates = new ArrayList<>(this.delegates);
298
299 if (!delegates.isEmpty()) {
300 ClassRealmRequest request =
301 new DefaultClassRealmRequest(type, parent, parentImports, foreignImports, constituents);
302
303 for (ClassRealmManagerDelegate delegate : delegates) {
304 try {
305 delegate.setupRealm(classRealm, request);
306 } catch (Exception e) {
307 logger.error(
308 delegate.getClass().getName() + " failed to setup class realm " + classRealm + ": "
309 + e.getMessage(),
310 e);
311 }
312 }
313 }
314 }
315
316 private void populateRealm(ClassRealm classRealm, List<ClassRealmConstituent> constituents) {
317 logger.debug("Populating class realm {}", classRealm.getId());
318
319 for (ClassRealmConstituent constituent : constituents) {
320 File file = constituent.getFile();
321
322 if (logger.isDebugEnabled()) {
323 String id = getId(constituent);
324 logger.debug(" Included: {}", id);
325 }
326
327 try {
328 classRealm.addURL(file.toURI().toURL());
329 } catch (MalformedURLException e) {
330
331 logger.error(e.getMessage(), e);
332 }
333 }
334 }
335
336 private void wireRealm(ClassRealm classRealm, List<String> parentImports, Map<String, ClassLoader> foreignImports) {
337 if (foreignImports != null && !foreignImports.isEmpty()) {
338 logger.debug("Importing foreign packages into class realm {}", classRealm.getId());
339
340 for (Map.Entry<String, ClassLoader> entry : foreignImports.entrySet()) {
341 ClassLoader importedRealm = entry.getValue();
342 String imp = entry.getKey();
343
344 logger.debug(" Imported: {} < {}", imp, getId(importedRealm));
345
346 classRealm.importFrom(importedRealm, imp);
347 }
348 }
349
350 if (parentImports != null && !parentImports.isEmpty()) {
351 logger.debug("Importing parent packages into class realm {}", classRealm.getId());
352
353 for (String imp : parentImports) {
354 logger.debug(" Imported: {} < {}", imp, getId(classRealm.getParentClassLoader()));
355
356 classRealm.importFromParent(imp);
357 }
358 }
359 }
360
361 private static Object getId(ClassLoader classLoader) {
362 if (classLoader instanceof ClassRealm classRealm) {
363 return classRealm.getId();
364 }
365 return classLoader;
366 }
367 }