001 package org.apache.maven.classrealm;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements. See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership. The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License. You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied. See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022 import java.io.File;
023 import java.net.MalformedURLException;
024 import java.util.ArrayList;
025 import java.util.Collections;
026 import java.util.HashMap;
027 import java.util.LinkedHashSet;
028 import java.util.List;
029 import java.util.Map;
030 import java.util.Random;
031 import java.util.Set;
032 import java.util.TreeMap;
033
034 import org.apache.maven.artifact.ArtifactUtils;
035 import org.apache.maven.classrealm.ClassRealmRequest.RealmType;
036 import org.apache.maven.model.Model;
037 import org.apache.maven.model.Plugin;
038 import org.codehaus.plexus.MutablePlexusContainer;
039 import org.codehaus.plexus.PlexusContainer;
040 import org.codehaus.plexus.classworlds.ClassWorld;
041 import org.codehaus.plexus.classworlds.realm.ClassRealm;
042 import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
043 import org.codehaus.plexus.component.annotations.Component;
044 import org.codehaus.plexus.component.annotations.Requirement;
045 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
046 import org.codehaus.plexus.logging.Logger;
047 import org.codehaus.plexus.util.StringUtils;
048 import org.eclipse.aether.artifact.Artifact;
049
050 /**
051 * Manages the class realms used by Maven. <strong>Warning:</strong> This is an internal utility class that is only
052 * public for technical reasons, it is not part of the public API. In particular, this class can be changed or deleted
053 * without prior notice.
054 *
055 * @author Benjamin Bentmann
056 */
057 @Component( role = ClassRealmManager.class )
058 public class DefaultClassRealmManager
059 implements ClassRealmManager
060 {
061
062 @Requirement
063 private Logger logger;
064
065 @Requirement
066 protected PlexusContainer container;
067
068 private ClassRealm mavenRealm;
069
070 private ClassWorld getClassWorld()
071 {
072 return ( (MutablePlexusContainer) container ).getClassWorld();
073 }
074
075 private ClassRealm newRealm( String id )
076 {
077 ClassWorld world = getClassWorld();
078
079 synchronized ( world )
080 {
081 String realmId = id;
082
083 Random random = new Random();
084
085 while ( true )
086 {
087 try
088 {
089 ClassRealm classRealm = world.newRealm( realmId, null );
090
091 if ( logger.isDebugEnabled() )
092 {
093 logger.debug( "Created new class realm " + realmId );
094 }
095
096 return classRealm;
097 }
098 catch ( DuplicateRealmException e )
099 {
100 realmId = id + '-' + random.nextInt();
101 }
102 }
103 }
104 }
105
106 public synchronized ClassRealm getMavenApiRealm()
107 {
108 if ( mavenRealm == null )
109 {
110 mavenRealm = newRealm( "maven.api" );
111
112 List<ClassRealmConstituent> constituents = new ArrayList<ClassRealmConstituent>();
113
114 List<String> parentImports = new ArrayList<String>();
115
116 Map<String, ClassLoader> foreignImports = new HashMap<String, ClassLoader>();
117 importMavenApi( foreignImports );
118
119 callDelegates( mavenRealm, RealmType.Core, mavenRealm.getParentClassLoader(), parentImports,
120 foreignImports, constituents );
121
122 wireRealm( mavenRealm, parentImports, foreignImports );
123
124 populateRealm( mavenRealm, constituents );
125 }
126
127 return mavenRealm;
128 }
129
130 private void importMavenApi( Map<String, ClassLoader> imports )
131 {
132 ClassRealm coreRealm = getCoreRealm();
133
134 // maven-*
135 imports.put( "org.apache.maven.*", coreRealm );
136 imports.put( "org.apache.maven.artifact", coreRealm );
137 imports.put( "org.apache.maven.classrealm", coreRealm );
138 imports.put( "org.apache.maven.cli", coreRealm );
139 imports.put( "org.apache.maven.configuration", coreRealm );
140 imports.put( "org.apache.maven.exception", coreRealm );
141 imports.put( "org.apache.maven.execution", coreRealm );
142 imports.put( "org.apache.maven.lifecycle", coreRealm );
143 imports.put( "org.apache.maven.model", coreRealm );
144 imports.put( "org.apache.maven.monitor", coreRealm );
145 imports.put( "org.apache.maven.plugin", coreRealm );
146 imports.put( "org.apache.maven.profiles", coreRealm );
147 imports.put( "org.apache.maven.project", coreRealm );
148 imports.put( "org.apache.maven.reporting", coreRealm );
149 imports.put( "org.apache.maven.repository", coreRealm );
150 imports.put( "org.apache.maven.rtinfo", coreRealm );
151 imports.put( "org.apache.maven.settings", coreRealm );
152 imports.put( "org.apache.maven.toolchain", coreRealm );
153 imports.put( "org.apache.maven.usability", coreRealm );
154
155 // wagon-api
156 imports.put( "org.apache.maven.wagon.*", coreRealm );
157 imports.put( "org.apache.maven.wagon.authentication", coreRealm );
158 imports.put( "org.apache.maven.wagon.authorization", coreRealm );
159 imports.put( "org.apache.maven.wagon.events", coreRealm );
160 imports.put( "org.apache.maven.wagon.observers", coreRealm );
161 imports.put( "org.apache.maven.wagon.proxy", coreRealm );
162 imports.put( "org.apache.maven.wagon.repository", coreRealm );
163 imports.put( "org.apache.maven.wagon.resource", coreRealm );
164
165 // aether-api, aether-spi, aether-impl
166 imports.put( "org.eclipse.aether.*", coreRealm );
167 imports.put( "org.eclipse.aether.artifact", coreRealm );
168 imports.put( "org.eclipse.aether.collection", coreRealm );
169 imports.put( "org.eclipse.aether.deployment", coreRealm );
170 imports.put( "org.eclipse.aether.graph", coreRealm );
171 imports.put( "org.eclipse.aether.impl", coreRealm );
172 imports.put( "org.eclipse.aether.installation", coreRealm );
173 imports.put( "org.eclipse.aether.metadata", coreRealm );
174 imports.put( "org.eclipse.aether.repository", coreRealm );
175 imports.put( "org.eclipse.aether.resolution", coreRealm );
176 imports.put( "org.eclipse.aether.spi", coreRealm );
177 imports.put( "org.eclipse.aether.transfer", coreRealm );
178 imports.put( "org.eclipse.aether.version", coreRealm );
179
180 // plexus-classworlds
181 imports.put( "org.codehaus.plexus.classworlds", coreRealm );
182
183 // classworlds (for legacy code)
184 imports.put( "org.codehaus.classworlds", coreRealm );
185
186 // plexus-utils (for DOM-type fields in maven-model)
187 imports.put( "org.codehaus.plexus.util.xml.Xpp3Dom", coreRealm );
188 imports.put( "org.codehaus.plexus.util.xml.pull.XmlPullParser", coreRealm );
189 imports.put( "org.codehaus.plexus.util.xml.pull.XmlPullParserException", coreRealm );
190 imports.put( "org.codehaus.plexus.util.xml.pull.XmlSerializer", coreRealm );
191
192 // plexus-container, plexus-component-annotations
193 imports.put( "org.codehaus.plexus.*", coreRealm );
194 imports.put( "org.codehaus.plexus.component", coreRealm );
195 imports.put( "org.codehaus.plexus.configuration", coreRealm );
196 imports.put( "org.codehaus.plexus.container", coreRealm );
197 imports.put( "org.codehaus.plexus.context", coreRealm );
198 imports.put( "org.codehaus.plexus.lifecycle", coreRealm );
199 imports.put( "org.codehaus.plexus.logging", coreRealm );
200 imports.put( "org.codehaus.plexus.personality", coreRealm );
201
202 // javax.inject (JSR-330)
203 imports.put( "javax.inject.*", coreRealm );
204 imports.put( "javax.enterprise.inject.*", coreRealm );
205
206 // com.google
207 //
208 // We may potentially want to export these, but right now I'm not sure that anything Guice specific needs
209 // to be made available to plugin authors. If we find people are getting fancy and want to take advantage
210 // of Guice specifics we can expose that later. Really some testing needs to be done to see full hiding
211 // of Guice has any impact on what we may categorize as a standard JSR-330 based Tesla/Maven plugin.
212 //
213 // imports.put( "com.google.inject.*", coreRealm );
214 // imports.put( "com.google.inject.binder.*", coreRealm );
215 // imports.put( "com.google.inject.matcher.*", coreRealm );
216 // imports.put( "com.google.inject.name.*", coreRealm );
217 // imports.put( "com.google.inject.spi.*", coreRealm );
218 // imports.put( "com.google.inject.util.*", coreRealm );
219
220 // SLF4J
221 imports.put( "org.slf4j.*", coreRealm );
222 }
223
224 /**
225 * Creates a new class realm with the specified parent and imports.
226 *
227 * @param baseRealmId The base id to use for the new realm, must not be {@code null}.
228 * @param type The type of the class realm, must not be {@code null}.
229 * @param parent The parent realm for the new realm, may be {@code null}.
230 * @param parentImports The packages/types to import from the parent realm, may be {@code null}.
231 * @param foreignImports The packages/types to import from foreign realms, may be {@code null}.
232 * @param artifacts The artifacts to add to the realm, may be {@code null}. Unresolved artifacts (i.e. with a
233 * missing file) will automatically be excluded from the realm.
234 * @return The created class realm, never {@code null}.
235 */
236 private ClassRealm createRealm( String baseRealmId, RealmType type, ClassLoader parent, List<String> parentImports,
237 Map<String, ClassLoader> foreignImports, List<Artifact> artifacts )
238 {
239 Set<String> artifactIds = new LinkedHashSet<String>();
240
241 List<ClassRealmConstituent> constituents = new ArrayList<ClassRealmConstituent>();
242
243 if ( artifacts != null )
244 {
245 for ( Artifact artifact : artifacts )
246 {
247 artifactIds.add( getId( artifact ) );
248 if ( artifact.getFile() != null )
249 {
250 constituents.add( new ArtifactClassRealmConstituent( artifact ) );
251 }
252 }
253 }
254
255 if ( parentImports != null )
256 {
257 parentImports = new ArrayList<String>( parentImports );
258 }
259 else
260 {
261 parentImports = new ArrayList<String>();
262 }
263
264 if ( foreignImports != null )
265 {
266 foreignImports = new TreeMap<String, ClassLoader>( foreignImports );
267 }
268 else
269 {
270 foreignImports = new TreeMap<String, ClassLoader>();
271 }
272
273 ClassRealm classRealm = newRealm( baseRealmId );
274
275 if ( parent != null )
276 {
277 classRealm.setParentClassLoader( parent );
278 }
279
280 callDelegates( classRealm, type, parent, parentImports, foreignImports, constituents );
281
282 wireRealm( classRealm, parentImports, foreignImports );
283
284 Set<String> includedIds = populateRealm( classRealm, constituents );
285
286 if ( logger.isDebugEnabled() )
287 {
288 artifactIds.removeAll( includedIds );
289
290 for ( String id : artifactIds )
291 {
292 logger.debug( " Excluded: " + id );
293 }
294 }
295
296 return classRealm;
297 }
298
299 public ClassRealm getCoreRealm()
300 {
301 return container.getContainerRealm();
302 }
303
304 public ClassRealm createProjectRealm( Model model, List<Artifact> artifacts )
305 {
306 if ( model == null )
307 {
308 throw new IllegalArgumentException( "model missing" );
309 }
310
311 ClassLoader parent = getMavenApiRealm();
312
313 return createRealm( getKey( model ), RealmType.Project, parent, null, null, artifacts );
314 }
315
316 private static String getKey( Model model )
317 {
318 return "project>" + model.getGroupId() + ":" + model.getArtifactId() + ":" + model.getVersion();
319 }
320
321 public ClassRealm createExtensionRealm( Plugin plugin, List<Artifact> artifacts )
322 {
323 if ( plugin == null )
324 {
325 throw new IllegalArgumentException( "extension plugin missing" );
326 }
327
328 ClassLoader parent = ClassLoader.getSystemClassLoader();
329
330 Map<String, ClassLoader> foreignImports =
331 Collections.<String, ClassLoader> singletonMap( "", getMavenApiRealm() );
332
333 return createRealm( getKey( plugin, true ), RealmType.Extension, parent, null, foreignImports, artifacts );
334 }
335
336 public ClassRealm createPluginRealm( Plugin plugin, ClassLoader parent, List<String> parentImports,
337 Map<String, ClassLoader> foreignImports, List<Artifact> artifacts )
338 {
339 if ( plugin == null )
340 {
341 throw new IllegalArgumentException( "plugin missing" );
342 }
343
344 if ( parent == null )
345 {
346 parent = ClassLoader.getSystemClassLoader();
347 }
348
349 return createRealm( getKey( plugin, false ), RealmType.Plugin, parent, parentImports, foreignImports, artifacts );
350 }
351
352 private static String getKey( Plugin plugin, boolean extension )
353 {
354 String version = ArtifactUtils.toSnapshotVersion( plugin.getVersion() );
355 return ( extension ? "extension>" : "plugin>" ) + plugin.getGroupId() + ":" + plugin.getArtifactId() + ":"
356 + version;
357 }
358
359 private static String getId( Artifact artifact )
360 {
361 return getId( artifact.getGroupId(), artifact.getArtifactId(), artifact.getExtension(),
362 artifact.getClassifier(), artifact.getBaseVersion() );
363 }
364
365 private static String getId( ClassRealmConstituent constituent )
366 {
367 return getId( constituent.getGroupId(), constituent.getArtifactId(), constituent.getType(),
368 constituent.getClassifier(), constituent.getVersion() );
369 }
370
371 private static String getId( String gid, String aid, String type, String cls, String ver )
372 {
373 return gid + ':' + aid + ':' + type + ( StringUtils.isNotEmpty( cls ) ? ':' + cls : "" ) + ':' + ver;
374 }
375
376 private List<ClassRealmManagerDelegate> getDelegates()
377 {
378 try
379 {
380 return container.lookupList( ClassRealmManagerDelegate.class );
381 }
382 catch ( ComponentLookupException e )
383 {
384 logger.error( "Failed to lookup class realm delegates: " + e.getMessage(), e );
385
386 return Collections.emptyList();
387 }
388 }
389
390 private void callDelegates( ClassRealm classRealm, RealmType type, ClassLoader parent, List<String> parentImports,
391 Map<String, ClassLoader> foreignImports, List<ClassRealmConstituent> constituents )
392 {
393 List<ClassRealmManagerDelegate> delegates = getDelegates();
394
395 if ( !delegates.isEmpty() )
396 {
397 ClassRealmRequest request =
398 new DefaultClassRealmRequest( type, parent, parentImports, foreignImports, constituents );
399
400 for ( ClassRealmManagerDelegate delegate : delegates )
401 {
402 try
403 {
404 delegate.setupRealm( classRealm, request );
405 }
406 catch ( Exception e )
407 {
408 logger.error( delegate.getClass().getName() + " failed to setup class realm " + classRealm + ": "
409 + e.getMessage(), e );
410 }
411 }
412 }
413 }
414
415 private Set<String> populateRealm( ClassRealm classRealm, List<ClassRealmConstituent> constituents )
416 {
417 Set<String> includedIds = new LinkedHashSet<String>();
418
419 if ( logger.isDebugEnabled() )
420 {
421 logger.debug( "Populating class realm " + classRealm.getId() );
422 }
423
424 for ( ClassRealmConstituent constituent : constituents )
425 {
426 File file = constituent.getFile();
427
428 String id = getId( constituent );
429 includedIds.add( id );
430
431 if ( logger.isDebugEnabled() )
432 {
433 logger.debug( " Included: " + id );
434 }
435
436 try
437 {
438 classRealm.addURL( file.toURI().toURL() );
439 }
440 catch ( MalformedURLException e )
441 {
442 // Not going to happen
443 logger.error( e.getMessage(), e );
444 }
445 }
446
447 return includedIds;
448 }
449
450 private void wireRealm( ClassRealm classRealm, List<String> parentImports, Map<String, ClassLoader> foreignImports )
451 {
452 if ( foreignImports != null && !foreignImports.isEmpty() )
453 {
454 if ( logger.isDebugEnabled() )
455 {
456 logger.debug( "Importing foreign packages into class realm " + classRealm.getId() );
457 }
458
459 for ( Map.Entry<String, ClassLoader> entry : foreignImports.entrySet() )
460 {
461 ClassLoader importedRealm = entry.getValue();
462 String imp = entry.getKey();
463
464 if ( logger.isDebugEnabled() )
465 {
466 logger.debug( " Imported: " + imp + " < " + getId( importedRealm ) );
467 }
468
469 classRealm.importFrom( importedRealm, imp );
470 }
471 }
472
473 if ( parentImports != null && !parentImports.isEmpty() )
474 {
475 if ( logger.isDebugEnabled() )
476 {
477 logger.debug( "Importing parent packages into class realm " + classRealm.getId() );
478 }
479
480 for ( String imp : parentImports )
481 {
482 if ( logger.isDebugEnabled() )
483 {
484 logger.debug( " Imported: " + imp + " < " + getId( classRealm.getParentClassLoader() ) );
485 }
486
487 classRealm.importFromParent( imp );
488 }
489 }
490 }
491
492 private String getId( ClassLoader classLoader )
493 {
494 if ( classLoader instanceof ClassRealm )
495 {
496 return ( (ClassRealm) classLoader ).getId();
497 }
498 return String.valueOf( classLoader );
499 }
500
501 }