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