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 }