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