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