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 }