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