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.sonatype.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.sonatype.aether.*", coreRealm ); 167 imports.put( "org.sonatype.aether.artifact", coreRealm ); 168 imports.put( "org.sonatype.aether.collection", coreRealm ); 169 imports.put( "org.sonatype.aether.deployment", coreRealm ); 170 imports.put( "org.sonatype.aether.graph", coreRealm ); 171 imports.put( "org.sonatype.aether.impl", coreRealm ); 172 imports.put( "org.sonatype.aether.installation", coreRealm ); 173 imports.put( "org.sonatype.aether.metadata", coreRealm ); 174 imports.put( "org.sonatype.aether.repository", coreRealm ); 175 imports.put( "org.sonatype.aether.resolution", coreRealm ); 176 imports.put( "org.sonatype.aether.spi", coreRealm ); 177 imports.put( "org.sonatype.aether.transfer", coreRealm ); 178 imports.put( "org.sonatype.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-container, plexus-component-annotations 187 imports.put( "org.codehaus.plexus.*", coreRealm ); 188 imports.put( "org.codehaus.plexus.component", coreRealm ); 189 imports.put( "org.codehaus.plexus.configuration", coreRealm ); 190 imports.put( "org.codehaus.plexus.container", coreRealm ); 191 imports.put( "org.codehaus.plexus.context", coreRealm ); 192 imports.put( "org.codehaus.plexus.lifecycle", coreRealm ); 193 imports.put( "org.codehaus.plexus.logging", coreRealm ); 194 imports.put( "org.codehaus.plexus.personality", coreRealm ); 195 196 // plexus-utils (for maven-model) 197 imports.put( "org.codehaus.plexus.util.xml.Xpp3Dom", coreRealm ); 198 imports.put( "org.codehaus.plexus.util.xml.pull.XmlPullParser", coreRealm ); 199 imports.put( "org.codehaus.plexus.util.xml.pull.XmlPullParserException", coreRealm ); 200 imports.put( "org.codehaus.plexus.util.xml.pull.XmlSerializer", coreRealm ); 201 } 202 203 /** 204 * Creates a new class realm with the specified parent and imports. 205 * 206 * @param baseRealmId The base id to use for the new realm, must not be {@code null}. 207 * @param type The type of the class realm, must not be {@code null}. 208 * @param parent The parent realm for the new realm, may be {@code null}. 209 * @param parentImports The packages/types to import from the parent realm, may be {@code null}. 210 * @param foreignImports The packages/types to import from foreign realms, may be {@code null}. 211 * @param artifacts The artifacts to add to the realm, may be {@code null}. Unresolved artifacts (i.e. with a 212 * missing file) will automatically be excluded from the realm. 213 * @return The created class realm, never {@code null}. 214 */ 215 private ClassRealm createRealm( String baseRealmId, RealmType type, ClassLoader parent, List<String> parentImports, 216 Map<String, ClassLoader> foreignImports, List<Artifact> artifacts ) 217 { 218 Set<String> artifactIds = new LinkedHashSet<String>(); 219 220 List<ClassRealmConstituent> constituents = new ArrayList<ClassRealmConstituent>(); 221 222 if ( artifacts != null ) 223 { 224 for ( Artifact artifact : artifacts ) 225 { 226 artifactIds.add( getId( artifact ) ); 227 if ( artifact.getFile() != null ) 228 { 229 constituents.add( new ArtifactClassRealmConstituent( artifact ) ); 230 } 231 } 232 } 233 234 if ( parentImports != null ) 235 { 236 parentImports = new ArrayList<String>( parentImports ); 237 } 238 else 239 { 240 parentImports = new ArrayList<String>(); 241 } 242 243 if ( foreignImports != null ) 244 { 245 foreignImports = new TreeMap<String, ClassLoader>( foreignImports ); 246 } 247 else 248 { 249 foreignImports = new TreeMap<String, ClassLoader>(); 250 } 251 252 ClassRealm classRealm = newRealm( baseRealmId ); 253 254 if ( parent != null ) 255 { 256 classRealm.setParentClassLoader( parent ); 257 } 258 259 callDelegates( classRealm, type, parent, parentImports, foreignImports, constituents ); 260 261 wireRealm( classRealm, parentImports, foreignImports ); 262 263 Set<String> includedIds = populateRealm( classRealm, constituents ); 264 265 if ( logger.isDebugEnabled() ) 266 { 267 artifactIds.removeAll( includedIds ); 268 269 for ( String id : artifactIds ) 270 { 271 logger.debug( " Excluded: " + id ); 272 } 273 } 274 275 return classRealm; 276 } 277 278 public ClassRealm getCoreRealm() 279 { 280 return container.getContainerRealm(); 281 } 282 283 public ClassRealm createProjectRealm( Model model, List<Artifact> artifacts ) 284 { 285 if ( model == null ) 286 { 287 throw new IllegalArgumentException( "model missing" ); 288 } 289 290 ClassLoader parent = getMavenApiRealm(); 291 292 return createRealm( getKey( model ), RealmType.Project, parent, null, null, artifacts ); 293 } 294 295 private static String getKey( Model model ) 296 { 297 return "project>" + model.getGroupId() + ":" + model.getArtifactId() + ":" + model.getVersion(); 298 } 299 300 public ClassRealm createExtensionRealm( Plugin plugin, List<Artifact> artifacts ) 301 { 302 if ( plugin == null ) 303 { 304 throw new IllegalArgumentException( "extension plugin missing" ); 305 } 306 307 ClassLoader parent = ClassLoader.getSystemClassLoader(); 308 309 Map<String, ClassLoader> foreignImports = 310 Collections.<String, ClassLoader> singletonMap( "", getMavenApiRealm() ); 311 312 return createRealm( getKey( plugin, true ), RealmType.Extension, parent, null, foreignImports, artifacts ); 313 } 314 315 public ClassRealm createPluginRealm( Plugin plugin, ClassLoader parent, List<String> parentImports, 316 Map<String, ClassLoader> foreignImports, List<Artifact> artifacts ) 317 { 318 if ( plugin == null ) 319 { 320 throw new IllegalArgumentException( "plugin missing" ); 321 } 322 323 if ( parent == null ) 324 { 325 parent = ClassLoader.getSystemClassLoader(); 326 } 327 328 return createRealm( getKey( plugin, false ), RealmType.Plugin, parent, parentImports, foreignImports, artifacts ); 329 } 330 331 private static String getKey( Plugin plugin, boolean extension ) 332 { 333 String version = ArtifactUtils.toSnapshotVersion( plugin.getVersion() ); 334 return ( extension ? "extension>" : "plugin>" ) + plugin.getGroupId() + ":" + plugin.getArtifactId() + ":" 335 + version; 336 } 337 338 private static String getId( Artifact artifact ) 339 { 340 return getId( artifact.getGroupId(), artifact.getArtifactId(), artifact.getExtension(), 341 artifact.getClassifier(), artifact.getBaseVersion() ); 342 } 343 344 private static String getId( ClassRealmConstituent constituent ) 345 { 346 return getId( constituent.getGroupId(), constituent.getArtifactId(), constituent.getType(), 347 constituent.getClassifier(), constituent.getVersion() ); 348 } 349 350 private static String getId( String gid, String aid, String type, String cls, String ver ) 351 { 352 return gid + ':' + aid + ':' + type + ( StringUtils.isNotEmpty( cls ) ? ':' + cls : "" ) + ':' + ver; 353 } 354 355 private List<ClassRealmManagerDelegate> getDelegates() 356 { 357 try 358 { 359 return container.lookupList( ClassRealmManagerDelegate.class ); 360 } 361 catch ( ComponentLookupException e ) 362 { 363 logger.error( "Failed to lookup class realm delegates: " + e.getMessage(), e ); 364 365 return Collections.emptyList(); 366 } 367 } 368 369 private void callDelegates( ClassRealm classRealm, RealmType type, ClassLoader parent, List<String> parentImports, 370 Map<String, ClassLoader> foreignImports, List<ClassRealmConstituent> constituents ) 371 { 372 List<ClassRealmManagerDelegate> delegates = getDelegates(); 373 374 if ( !delegates.isEmpty() ) 375 { 376 ClassRealmRequest request = 377 new DefaultClassRealmRequest( type, parent, parentImports, foreignImports, constituents ); 378 379 for ( ClassRealmManagerDelegate delegate : delegates ) 380 { 381 try 382 { 383 delegate.setupRealm( classRealm, request ); 384 } 385 catch ( Exception e ) 386 { 387 logger.error( delegate.getClass().getName() + " failed to setup class realm " + classRealm + ": " 388 + e.getMessage(), e ); 389 } 390 } 391 } 392 } 393 394 private Set<String> populateRealm( ClassRealm classRealm, List<ClassRealmConstituent> constituents ) 395 { 396 Set<String> includedIds = new LinkedHashSet<String>(); 397 398 if ( logger.isDebugEnabled() ) 399 { 400 logger.debug( "Populating class realm " + classRealm.getId() ); 401 } 402 403 for ( ClassRealmConstituent constituent : constituents ) 404 { 405 File file = constituent.getFile(); 406 407 String id = getId( constituent ); 408 includedIds.add( id ); 409 410 if ( logger.isDebugEnabled() ) 411 { 412 logger.debug( " Included: " + id ); 413 } 414 415 try 416 { 417 classRealm.addURL( file.toURI().toURL() ); 418 } 419 catch ( MalformedURLException e ) 420 { 421 // Not going to happen 422 logger.error( e.getMessage(), e ); 423 } 424 } 425 426 return includedIds; 427 } 428 429 private void wireRealm( ClassRealm classRealm, List<String> parentImports, Map<String, ClassLoader> foreignImports ) 430 { 431 if ( foreignImports != null && !foreignImports.isEmpty() ) 432 { 433 if ( logger.isDebugEnabled() ) 434 { 435 logger.debug( "Importing foreign packages into class realm " + classRealm.getId() ); 436 } 437 438 for ( Map.Entry<String, ClassLoader> entry : foreignImports.entrySet() ) 439 { 440 ClassLoader importedRealm = entry.getValue(); 441 String imp = entry.getKey(); 442 443 if ( logger.isDebugEnabled() ) 444 { 445 logger.debug( " Imported: " + imp + " < " + getId( importedRealm ) ); 446 } 447 448 classRealm.importFrom( importedRealm, imp ); 449 } 450 } 451 452 if ( parentImports != null && !parentImports.isEmpty() ) 453 { 454 if ( logger.isDebugEnabled() ) 455 { 456 logger.debug( "Importing parent packages into class realm " + classRealm.getId() ); 457 } 458 459 for ( String imp : parentImports ) 460 { 461 if ( logger.isDebugEnabled() ) 462 { 463 logger.debug( " Imported: " + imp + " < " + getId( classRealm.getParentClassLoader() ) ); 464 } 465 466 classRealm.importFromParent( imp ); 467 } 468 } 469 } 470 471 private String getId( ClassLoader classLoader ) 472 { 473 if ( classLoader instanceof ClassRealm ) 474 { 475 return ( (ClassRealm) classLoader ).getId(); 476 } 477 return String.valueOf( classLoader ); 478 } 479 480 }