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