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.util.StringUtils;
48 import org.eclipse.aether.artifact.Artifact;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52
53
54
55
56
57
58
59 @Named
60 @Singleton
61 public class DefaultClassRealmManager implements ClassRealmManager {
62 public static final String API_REALMID = "maven.api";
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
86
87
88
89 private final Set<String> providedArtifacts;
90
91 @Inject
92 public DefaultClassRealmManager(
93 PlexusContainer container, List<ClassRealmManagerDelegate> delegates, CoreExports exports) {
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 Map<String, ClassLoader> foreignImports = Collections.singletonMap("", getMavenApiRealm());
226
227 return createRealm(
228 getKey(plugin, true), RealmType.Extension, PARENT_CLASSLOADER, null, foreignImports, artifacts);
229 }
230
231 private boolean isProvidedArtifact(Artifact artifact) {
232 return providedArtifacts.contains(artifact.getGroupId() + ":" + artifact.getArtifactId());
233 }
234
235 public ClassRealm createPluginRealm(
236 Plugin plugin,
237 ClassLoader parent,
238 List<String> parentImports,
239 Map<String, ClassLoader> foreignImports,
240 List<Artifact> artifacts) {
241 Objects.requireNonNull(plugin, "plugin cannot be null");
242
243 if (parent == null) {
244 parent = PARENT_CLASSLOADER;
245 }
246
247 return createRealm(getKey(plugin, false), RealmType.Plugin, parent, parentImports, foreignImports, artifacts);
248 }
249
250 private static String getKey(Plugin plugin, boolean extension) {
251 String version = ArtifactUtils.toSnapshotVersion(plugin.getVersion());
252 return (extension ? "extension>" : "plugin>") + plugin.getGroupId() + ":" + plugin.getArtifactId() + ":"
253 + version;
254 }
255
256 private static String getId(Artifact artifact) {
257 return getId(
258 artifact.getGroupId(),
259 artifact.getArtifactId(),
260 artifact.getExtension(),
261 artifact.getClassifier(),
262 artifact.getBaseVersion());
263 }
264
265 private static String getId(ClassRealmConstituent constituent) {
266 return getId(
267 constituent.getGroupId(),
268 constituent.getArtifactId(),
269 constituent.getType(),
270 constituent.getClassifier(),
271 constituent.getVersion());
272 }
273
274 private static String getId(String gid, String aid, String type, String cls, String ver) {
275 return gid + ':' + aid + ':' + type + (StringUtils.isNotEmpty(cls) ? ':' + cls : "") + ':' + ver;
276 }
277
278 private void callDelegates(
279 ClassRealm classRealm,
280 RealmType type,
281 ClassLoader parent,
282 List<String> parentImports,
283 Map<String, ClassLoader> foreignImports,
284 List<ClassRealmConstituent> constituents) {
285 List<ClassRealmManagerDelegate> delegates = new ArrayList<>(this.delegates);
286
287 if (!delegates.isEmpty()) {
288 ClassRealmRequest request =
289 new DefaultClassRealmRequest(type, parent, parentImports, foreignImports, constituents);
290
291 for (ClassRealmManagerDelegate delegate : delegates) {
292 try {
293 delegate.setupRealm(classRealm, request);
294 } catch (Exception e) {
295 logger.error(
296 delegate.getClass().getName() + " failed to setup class realm " + classRealm + ": "
297 + e.getMessage(),
298 e);
299 }
300 }
301 }
302 }
303
304 private Set<String> populateRealm(ClassRealm classRealm, List<ClassRealmConstituent> constituents) {
305 Set<String> includedIds = new LinkedHashSet<>();
306
307 if (logger.isDebugEnabled()) {
308 logger.debug("Populating class realm " + classRealm.getId());
309 }
310
311 for (ClassRealmConstituent constituent : constituents) {
312 File file = constituent.getFile();
313
314 String id = getId(constituent);
315 includedIds.add(id);
316
317 if (logger.isDebugEnabled()) {
318 logger.debug(" Included: " + id);
319 }
320
321 try {
322 classRealm.addURL(file.toURI().toURL());
323 } catch (MalformedURLException e) {
324
325 logger.error(e.getMessage(), e);
326 }
327 }
328
329 return includedIds;
330 }
331
332 private void wireRealm(ClassRealm classRealm, List<String> parentImports, Map<String, ClassLoader> foreignImports) {
333 if (foreignImports != null && !foreignImports.isEmpty()) {
334 if (logger.isDebugEnabled()) {
335 logger.debug("Importing foreign packages into class realm " + classRealm.getId());
336 }
337
338 for (Map.Entry<String, ClassLoader> entry : foreignImports.entrySet()) {
339 ClassLoader importedRealm = entry.getValue();
340 String imp = entry.getKey();
341
342 if (logger.isDebugEnabled()) {
343 logger.debug(" Imported: " + imp + " < " + getId(importedRealm));
344 }
345
346 classRealm.importFrom(importedRealm, imp);
347 }
348 }
349
350 if (parentImports != null && !parentImports.isEmpty()) {
351 if (logger.isDebugEnabled()) {
352 logger.debug("Importing parent packages into class realm " + classRealm.getId());
353 }
354
355 for (String imp : parentImports) {
356 if (logger.isDebugEnabled()) {
357 logger.debug(" Imported: " + imp + " < " + getId(classRealm.getParentClassLoader()));
358 }
359
360 classRealm.importFromParent(imp);
361 }
362 }
363 }
364
365 private String getId(ClassLoader classLoader) {
366 if (classLoader instanceof ClassRealm) {
367 return ((ClassRealm) classLoader).getId();
368 }
369 return String.valueOf(classLoader);
370 }
371 }