001package 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 022import java.io.File; 023import java.net.MalformedURLException; 024import java.util.ArrayList; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.LinkedHashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Random; 031import java.util.Set; 032import java.util.TreeMap; 033 034import org.apache.maven.artifact.ArtifactUtils; 035import org.apache.maven.classrealm.ClassRealmRequest.RealmType; 036import org.apache.maven.model.Model; 037import org.apache.maven.model.Plugin; 038import org.codehaus.plexus.MutablePlexusContainer; 039import org.codehaus.plexus.PlexusContainer; 040import org.codehaus.plexus.classworlds.ClassWorld; 041import org.codehaus.plexus.classworlds.realm.ClassRealm; 042import org.codehaus.plexus.classworlds.realm.DuplicateRealmException; 043import org.codehaus.plexus.component.annotations.Component; 044import org.codehaus.plexus.component.annotations.Requirement; 045import org.codehaus.plexus.component.repository.exception.ComponentLookupException; 046import org.codehaus.plexus.logging.Logger; 047import org.codehaus.plexus.util.StringUtils; 048import 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 ) 058public 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.internal.impl", coreRealm ); 173 imports.put( "org.eclipse.aether.installation", coreRealm ); 174 imports.put( "org.eclipse.aether.metadata", coreRealm ); 175 imports.put( "org.eclipse.aether.repository", coreRealm ); 176 imports.put( "org.eclipse.aether.resolution", coreRealm ); 177 imports.put( "org.eclipse.aether.spi", coreRealm ); 178 imports.put( "org.eclipse.aether.transfer", coreRealm ); 179 imports.put( "org.eclipse.aether.version", coreRealm ); 180 181 // plexus-classworlds 182 imports.put( "org.codehaus.plexus.classworlds", coreRealm ); 183 184 // classworlds (for legacy code) 185 imports.put( "org.codehaus.classworlds", coreRealm ); 186 187 // plexus-utils (for DOM-type fields in maven-model) 188 imports.put( "org.codehaus.plexus.util.xml.Xpp3Dom", coreRealm ); 189 imports.put( "org.codehaus.plexus.util.xml.pull.XmlPullParser", coreRealm ); 190 imports.put( "org.codehaus.plexus.util.xml.pull.XmlPullParserException", coreRealm ); 191 imports.put( "org.codehaus.plexus.util.xml.pull.XmlSerializer", coreRealm ); 192 193 // plexus-container, plexus-component-annotations 194 imports.put( "org.codehaus.plexus.*", coreRealm ); 195 imports.put( "org.codehaus.plexus.component", coreRealm ); 196 imports.put( "org.codehaus.plexus.configuration", coreRealm ); 197 imports.put( "org.codehaus.plexus.container", coreRealm ); 198 imports.put( "org.codehaus.plexus.context", coreRealm ); 199 imports.put( "org.codehaus.plexus.lifecycle", coreRealm ); 200 imports.put( "org.codehaus.plexus.logging", coreRealm ); 201 imports.put( "org.codehaus.plexus.personality", coreRealm ); 202 203 // javax.inject (JSR-330) 204 imports.put( "javax.inject.*", coreRealm ); 205 imports.put( "javax.enterprise.inject.*", coreRealm ); 206 207 // com.google 208 // 209 // We may potentially want to export these, but right now I'm not sure that anything Guice specific needs 210 // to be made available to plugin authors. If we find people are getting fancy and want to take advantage 211 // of Guice specifics we can expose that later. Really some testing needs to be done to see full hiding 212 // of Guice has any impact on what we may categorize as a standard JSR-330 based Tesla/Maven plugin. 213 // 214 // imports.put( "com.google.inject.*", coreRealm ); 215 // imports.put( "com.google.inject.binder.*", coreRealm ); 216 // imports.put( "com.google.inject.matcher.*", coreRealm ); 217 // imports.put( "com.google.inject.name.*", coreRealm ); 218 // imports.put( "com.google.inject.spi.*", coreRealm ); 219 // imports.put( "com.google.inject.util.*", coreRealm ); 220 221 // SLF4J 222 imports.put( "org.slf4j.*", coreRealm ); 223 } 224 225 /** 226 * Creates a new class realm with the specified parent and imports. 227 * 228 * @param baseRealmId The base id to use for the new realm, must not be {@code null}. 229 * @param type The type of the class realm, must not be {@code null}. 230 * @param parent The parent realm for the new realm, may be {@code null}. 231 * @param parentImports The packages/types to import from the parent realm, may be {@code null}. 232 * @param foreignImports The packages/types to import from foreign realms, may be {@code null}. 233 * @param artifacts The artifacts to add to the realm, may be {@code null}. Unresolved artifacts (i.e. with a 234 * missing file) will automatically be excluded from the realm. 235 * @return The created class realm, never {@code null}. 236 */ 237 private ClassRealm createRealm( String baseRealmId, RealmType type, ClassLoader parent, List<String> parentImports, 238 Map<String, ClassLoader> foreignImports, List<Artifact> artifacts ) 239 { 240 Set<String> artifactIds = new LinkedHashSet<String>(); 241 242 List<ClassRealmConstituent> constituents = new ArrayList<ClassRealmConstituent>(); 243 244 if ( artifacts != null ) 245 { 246 for ( Artifact artifact : artifacts ) 247 { 248 artifactIds.add( getId( artifact ) ); 249 if ( artifact.getFile() != null ) 250 { 251 constituents.add( new ArtifactClassRealmConstituent( artifact ) ); 252 } 253 } 254 } 255 256 if ( parentImports != null ) 257 { 258 parentImports = new ArrayList<String>( parentImports ); 259 } 260 else 261 { 262 parentImports = new ArrayList<String>(); 263 } 264 265 if ( foreignImports != null ) 266 { 267 foreignImports = new TreeMap<String, ClassLoader>( foreignImports ); 268 } 269 else 270 { 271 foreignImports = new TreeMap<String, ClassLoader>(); 272 } 273 274 ClassRealm classRealm = newRealm( baseRealmId ); 275 276 if ( parent != null ) 277 { 278 classRealm.setParentClassLoader( parent ); 279 } 280 281 callDelegates( classRealm, type, parent, parentImports, foreignImports, constituents ); 282 283 wireRealm( classRealm, parentImports, foreignImports ); 284 285 Set<String> includedIds = populateRealm( classRealm, constituents ); 286 287 if ( logger.isDebugEnabled() ) 288 { 289 artifactIds.removeAll( includedIds ); 290 291 for ( String id : artifactIds ) 292 { 293 logger.debug( " Excluded: " + id ); 294 } 295 } 296 297 return classRealm; 298 } 299 300 public ClassRealm getCoreRealm() 301 { 302 return container.getContainerRealm(); 303 } 304 305 public ClassRealm createProjectRealm( Model model, List<Artifact> artifacts ) 306 { 307 if ( model == null ) 308 { 309 throw new IllegalArgumentException( "model missing" ); 310 } 311 312 ClassLoader parent = getMavenApiRealm(); 313 314 return createRealm( getKey( model ), RealmType.Project, parent, null, null, artifacts ); 315 } 316 317 private static String getKey( Model model ) 318 { 319 return "project>" + model.getGroupId() + ":" + model.getArtifactId() + ":" + model.getVersion(); 320 } 321 322 public ClassRealm createExtensionRealm( Plugin plugin, List<Artifact> artifacts ) 323 { 324 if ( plugin == null ) 325 { 326 throw new IllegalArgumentException( "extension plugin missing" ); 327 } 328 329 ClassLoader parent = ClassLoader.getSystemClassLoader(); 330 331 Map<String, ClassLoader> foreignImports = 332 Collections.<String, ClassLoader> singletonMap( "", getMavenApiRealm() ); 333 334 return createRealm( getKey( plugin, true ), RealmType.Extension, parent, null, foreignImports, artifacts ); 335 } 336 337 public ClassRealm createPluginRealm( Plugin plugin, ClassLoader parent, List<String> parentImports, 338 Map<String, ClassLoader> foreignImports, List<Artifact> artifacts ) 339 { 340 if ( plugin == null ) 341 { 342 throw new IllegalArgumentException( "plugin missing" ); 343 } 344 345 if ( parent == null ) 346 { 347 parent = ClassLoader.getSystemClassLoader(); 348 } 349 350 return createRealm( getKey( plugin, false ), RealmType.Plugin, parent, parentImports, foreignImports, artifacts ); 351 } 352 353 private static String getKey( Plugin plugin, boolean extension ) 354 { 355 String version = ArtifactUtils.toSnapshotVersion( plugin.getVersion() ); 356 return ( extension ? "extension>" : "plugin>" ) + plugin.getGroupId() + ":" + plugin.getArtifactId() + ":" 357 + version; 358 } 359 360 private static String getId( Artifact artifact ) 361 { 362 return getId( artifact.getGroupId(), artifact.getArtifactId(), artifact.getExtension(), 363 artifact.getClassifier(), artifact.getBaseVersion() ); 364 } 365 366 private static String getId( ClassRealmConstituent constituent ) 367 { 368 return getId( constituent.getGroupId(), constituent.getArtifactId(), constituent.getType(), 369 constituent.getClassifier(), constituent.getVersion() ); 370 } 371 372 private static String getId( String gid, String aid, String type, String cls, String ver ) 373 { 374 return gid + ':' + aid + ':' + type + ( StringUtils.isNotEmpty( cls ) ? ':' + cls : "" ) + ':' + ver; 375 } 376 377 private List<ClassRealmManagerDelegate> getDelegates() 378 { 379 try 380 { 381 return container.lookupList( ClassRealmManagerDelegate.class ); 382 } 383 catch ( ComponentLookupException e ) 384 { 385 logger.error( "Failed to lookup class realm delegates: " + e.getMessage(), e ); 386 387 return Collections.emptyList(); 388 } 389 } 390 391 private void callDelegates( ClassRealm classRealm, RealmType type, ClassLoader parent, List<String> parentImports, 392 Map<String, ClassLoader> foreignImports, List<ClassRealmConstituent> constituents ) 393 { 394 List<ClassRealmManagerDelegate> delegates = getDelegates(); 395 396 if ( !delegates.isEmpty() ) 397 { 398 ClassRealmRequest request = 399 new DefaultClassRealmRequest( type, parent, parentImports, foreignImports, constituents ); 400 401 for ( ClassRealmManagerDelegate delegate : delegates ) 402 { 403 try 404 { 405 delegate.setupRealm( classRealm, request ); 406 } 407 catch ( Exception e ) 408 { 409 logger.error( delegate.getClass().getName() + " failed to setup class realm " + classRealm + ": " 410 + e.getMessage(), e ); 411 } 412 } 413 } 414 } 415 416 private Set<String> populateRealm( ClassRealm classRealm, List<ClassRealmConstituent> constituents ) 417 { 418 Set<String> includedIds = new LinkedHashSet<String>(); 419 420 if ( logger.isDebugEnabled() ) 421 { 422 logger.debug( "Populating class realm " + classRealm.getId() ); 423 } 424 425 for ( ClassRealmConstituent constituent : constituents ) 426 { 427 File file = constituent.getFile(); 428 429 String id = getId( constituent ); 430 includedIds.add( id ); 431 432 if ( logger.isDebugEnabled() ) 433 { 434 logger.debug( " Included: " + id ); 435 } 436 437 try 438 { 439 classRealm.addURL( file.toURI().toURL() ); 440 } 441 catch ( MalformedURLException e ) 442 { 443 // Not going to happen 444 logger.error( e.getMessage(), e ); 445 } 446 } 447 448 return includedIds; 449 } 450 451 private void wireRealm( ClassRealm classRealm, List<String> parentImports, Map<String, ClassLoader> foreignImports ) 452 { 453 if ( foreignImports != null && !foreignImports.isEmpty() ) 454 { 455 if ( logger.isDebugEnabled() ) 456 { 457 logger.debug( "Importing foreign packages into class realm " + classRealm.getId() ); 458 } 459 460 for ( Map.Entry<String, ClassLoader> entry : foreignImports.entrySet() ) 461 { 462 ClassLoader importedRealm = entry.getValue(); 463 String imp = entry.getKey(); 464 465 if ( logger.isDebugEnabled() ) 466 { 467 logger.debug( " Imported: " + imp + " < " + getId( importedRealm ) ); 468 } 469 470 classRealm.importFrom( importedRealm, imp ); 471 } 472 } 473 474 if ( parentImports != null && !parentImports.isEmpty() ) 475 { 476 if ( logger.isDebugEnabled() ) 477 { 478 logger.debug( "Importing parent packages into class realm " + classRealm.getId() ); 479 } 480 481 for ( String imp : parentImports ) 482 { 483 if ( logger.isDebugEnabled() ) 484 { 485 logger.debug( " Imported: " + imp + " < " + getId( classRealm.getParentClassLoader() ) ); 486 } 487 488 classRealm.importFromParent( imp ); 489 } 490 } 491 } 492 493 private String getId( ClassLoader classLoader ) 494 { 495 if ( classLoader instanceof ClassRealm ) 496 { 497 return ( (ClassRealm) classLoader ).getId(); 498 } 499 return String.valueOf( classLoader ); 500 } 501 502}