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.LinkedHashSet; 027import java.util.List; 028import java.util.Map; 029import java.util.Random; 030import java.util.Set; 031import java.util.TreeMap; 032 033import javax.inject.Inject; 034import javax.inject.Named; 035import javax.inject.Singleton; 036 037import org.apache.maven.artifact.ArtifactUtils; 038import org.apache.maven.classrealm.ClassRealmRequest.RealmType; 039import org.apache.maven.extension.internal.CoreExportsProvider; 040import org.apache.maven.model.Model; 041import org.apache.maven.model.Plugin; 042import org.codehaus.plexus.MutablePlexusContainer; 043import org.codehaus.plexus.PlexusContainer; 044import org.codehaus.plexus.classworlds.ClassWorld; 045import org.codehaus.plexus.classworlds.realm.ClassRealm; 046import org.codehaus.plexus.classworlds.realm.DuplicateRealmException; 047import org.codehaus.plexus.logging.Logger; 048import org.codehaus.plexus.util.StringUtils; 049import org.eclipse.aether.artifact.Artifact; 050 051/** 052 * Manages the class realms used by Maven. <strong>Warning:</strong> This is an internal utility class that is only 053 * public for technical reasons, it is not part of the public API. In particular, this class can be changed or deleted 054 * without prior notice. 055 * 056 * @author Benjamin Bentmann 057 */ 058@Named 059@Singleton 060public class DefaultClassRealmManager 061 implements ClassRealmManager 062{ 063 public static final String API_REALMID = "maven.api"; 064 065 /** 066 * During normal command line build, ClassWorld is loaded by jvm system classloader, which only includes 067 * plexus-classworlds jar and possibly javaagent classes, see http://jira.codehaus.org/browse/MNG-4747. 068 * <p> 069 * Using ClassWorld to determine plugin/extensions realm parent classloaders gives m2e and integration test harness 070 * flexibility to load multiple version of maven into dedicated classloaders without assuming state of jvm system 071 * classloader. 072 */ 073 private static final ClassLoader PARENT_CLASSLOADER = ClassWorld.class.getClassLoader(); 074 075 private final Logger logger; 076 077 private final ClassWorld world; 078 079 private final ClassRealm containerRealm; 080 081 // this is a live injected collection 082 private final List<ClassRealmManagerDelegate> delegates; 083 084 private final ClassRealm mavenApiRealm; 085 086 /** 087 * Patterns of artifacts provided by maven core and exported via maven api realm. These artifacts are filtered from 088 * plugin and build extensions realms to avoid presence of duplicate and possibly conflicting classes on classpath. 089 */ 090 private final Set<String> providedArtifacts; 091 092 @Inject 093 public DefaultClassRealmManager( Logger logger, PlexusContainer container, 094 List<ClassRealmManagerDelegate> delegates, CoreExportsProvider exports ) 095 { 096 this.logger = logger; 097 this.world = ( (MutablePlexusContainer) container ).getClassWorld(); 098 this.containerRealm = container.getContainerRealm(); 099 this.delegates = delegates; 100 101 Map<String, ClassLoader> foreignImports = exports.get().getExportedPackages(); 102 103 this.mavenApiRealm = 104 createRealm( API_REALMID, RealmType.Core, null /* parent */, null /* parentImports */, 105 foreignImports, null /* artifacts */ ); 106 107 this.providedArtifacts = exports.get().getExportedArtifacts(); 108 } 109 110 private ClassRealm newRealm( String id ) 111 { 112 synchronized ( world ) 113 { 114 String realmId = id; 115 116 Random random = new Random(); 117 118 while ( true ) 119 { 120 try 121 { 122 ClassRealm classRealm = world.newRealm( realmId, null ); 123 124 if ( logger.isDebugEnabled() ) 125 { 126 logger.debug( "Created new class realm " + realmId ); 127 } 128 129 return classRealm; 130 } 131 catch ( DuplicateRealmException e ) 132 { 133 realmId = id + '-' + random.nextInt(); 134 } 135 } 136 } 137 } 138 139 public ClassRealm getMavenApiRealm() 140 { 141 return mavenApiRealm; 142 } 143 144 /** 145 * Creates a new class realm with the specified parent and imports. 146 * 147 * @param baseRealmId The base id to use for the new realm, must not be {@code null}. 148 * @param type The type of the class realm, must not be {@code null}. 149 * @param parent The parent realm for the new realm, may be {@code null}. 150 * @param parentImports The packages/types to import from the parent realm, may be {@code null}. 151 * @param foreignImports The packages/types to import from foreign realms, may be {@code null}. 152 * @param artifacts The artifacts to add to the realm, may be {@code null}. Unresolved artifacts (i.e. with a 153 * missing file) will automatically be excluded from the realm. 154 * @return The created class realm, never {@code null}. 155 */ 156 private ClassRealm createRealm( String baseRealmId, RealmType type, ClassLoader parent, List<String> parentImports, 157 Map<String, ClassLoader> foreignImports, List<Artifact> artifacts ) 158 { 159 Set<String> artifactIds = new LinkedHashSet<String>(); 160 161 List<ClassRealmConstituent> constituents = new ArrayList<ClassRealmConstituent>(); 162 163 if ( artifacts != null ) 164 { 165 for ( Artifact artifact : artifacts ) 166 { 167 if ( !isProvidedArtifact( artifact ) ) 168 { 169 artifactIds.add( getId( artifact ) ); 170 if ( artifact.getFile() != null ) 171 { 172 constituents.add( new ArtifactClassRealmConstituent( artifact ) ); 173 } 174 } 175 } 176 } 177 178 if ( parentImports != null ) 179 { 180 parentImports = new ArrayList<String>( parentImports ); 181 } 182 else 183 { 184 parentImports = new ArrayList<String>(); 185 } 186 187 if ( foreignImports != null ) 188 { 189 foreignImports = new TreeMap<String, ClassLoader>( foreignImports ); 190 } 191 else 192 { 193 foreignImports = new TreeMap<String, ClassLoader>(); 194 } 195 196 ClassRealm classRealm = newRealm( baseRealmId ); 197 198 if ( parent != null ) 199 { 200 classRealm.setParentClassLoader( parent ); 201 } 202 203 callDelegates( classRealm, type, parent, parentImports, foreignImports, constituents ); 204 205 wireRealm( classRealm, parentImports, foreignImports ); 206 207 Set<String> includedIds = populateRealm( classRealm, constituents ); 208 209 if ( logger.isDebugEnabled() ) 210 { 211 artifactIds.removeAll( includedIds ); 212 213 for ( String id : artifactIds ) 214 { 215 logger.debug( " Excluded: " + id ); 216 } 217 } 218 219 return classRealm; 220 } 221 222 public ClassRealm getCoreRealm() 223 { 224 return containerRealm; 225 } 226 227 public ClassRealm createProjectRealm( Model model, List<Artifact> artifacts ) 228 { 229 if ( model == null ) 230 { 231 throw new IllegalArgumentException( "model missing" ); 232 } 233 234 ClassLoader parent = getMavenApiRealm(); 235 236 return createRealm( getKey( model ), RealmType.Project, parent, null, null, artifacts ); 237 } 238 239 private static String getKey( Model model ) 240 { 241 return "project>" + model.getGroupId() + ":" + model.getArtifactId() + ":" + model.getVersion(); 242 } 243 244 public ClassRealm createExtensionRealm( Plugin plugin, List<Artifact> artifacts ) 245 { 246 if ( plugin == null ) 247 { 248 throw new IllegalArgumentException( "extension plugin missing" ); 249 } 250 251 ClassLoader parent = PARENT_CLASSLOADER; 252 253 Map<String, ClassLoader> foreignImports = 254 Collections.<String, ClassLoader>singletonMap( "", getMavenApiRealm() ); 255 256 return createRealm( getKey( plugin, true ), RealmType.Extension, parent, null, foreignImports, artifacts ); 257 } 258 259 private boolean isProvidedArtifact( Artifact artifact ) 260 { 261 return providedArtifacts.contains( artifact.getGroupId() + ":" + artifact.getArtifactId() ); 262 } 263 264 public ClassRealm createPluginRealm( Plugin plugin, ClassLoader parent, List<String> parentImports, 265 Map<String, ClassLoader> foreignImports, List<Artifact> artifacts ) 266 { 267 if ( plugin == null ) 268 { 269 throw new IllegalArgumentException( "plugin missing" ); 270 } 271 272 if ( parent == null ) 273 { 274 parent = PARENT_CLASSLOADER; 275 } 276 277 return createRealm( getKey( plugin, false ), RealmType.Plugin, parent, parentImports, foreignImports, 278 artifacts ); 279 } 280 281 private static String getKey( Plugin plugin, boolean extension ) 282 { 283 String version = ArtifactUtils.toSnapshotVersion( plugin.getVersion() ); 284 return ( extension ? "extension>" : "plugin>" ) + plugin.getGroupId() + ":" + plugin.getArtifactId() + ":" 285 + version; 286 } 287 288 private static String getId( Artifact artifact ) 289 { 290 return getId( artifact.getGroupId(), artifact.getArtifactId(), artifact.getExtension(), 291 artifact.getClassifier(), artifact.getBaseVersion() ); 292 } 293 294 private static String getId( ClassRealmConstituent constituent ) 295 { 296 return getId( constituent.getGroupId(), constituent.getArtifactId(), constituent.getType(), 297 constituent.getClassifier(), constituent.getVersion() ); 298 } 299 300 private static String getId( String gid, String aid, String type, String cls, String ver ) 301 { 302 return gid + ':' + aid + ':' + type + ( StringUtils.isNotEmpty( cls ) ? ':' + cls : "" ) + ':' + ver; 303 } 304 305 private void callDelegates( ClassRealm classRealm, RealmType type, ClassLoader parent, List<String> parentImports, 306 Map<String, ClassLoader> foreignImports, List<ClassRealmConstituent> constituents ) 307 { 308 List<ClassRealmManagerDelegate> delegates = new ArrayList<ClassRealmManagerDelegate>( this.delegates ); 309 310 if ( !delegates.isEmpty() ) 311 { 312 ClassRealmRequest request = 313 new DefaultClassRealmRequest( type, parent, parentImports, foreignImports, constituents ); 314 315 for ( ClassRealmManagerDelegate delegate : delegates ) 316 { 317 try 318 { 319 delegate.setupRealm( classRealm, request ); 320 } 321 catch ( Exception e ) 322 { 323 logger.error( delegate.getClass().getName() + " failed to setup class realm " + classRealm + ": " 324 + e.getMessage(), e ); 325 } 326 } 327 } 328 } 329 330 private Set<String> populateRealm( ClassRealm classRealm, List<ClassRealmConstituent> constituents ) 331 { 332 Set<String> includedIds = new LinkedHashSet<String>(); 333 334 if ( logger.isDebugEnabled() ) 335 { 336 logger.debug( "Populating class realm " + classRealm.getId() ); 337 } 338 339 for ( ClassRealmConstituent constituent : constituents ) 340 { 341 File file = constituent.getFile(); 342 343 String id = getId( constituent ); 344 includedIds.add( id ); 345 346 if ( logger.isDebugEnabled() ) 347 { 348 logger.debug( " Included: " + id ); 349 } 350 351 try 352 { 353 classRealm.addURL( file.toURI().toURL() ); 354 } 355 catch ( MalformedURLException e ) 356 { 357 // Not going to happen 358 logger.error( e.getMessage(), e ); 359 } 360 } 361 362 return includedIds; 363 } 364 365 private void wireRealm( ClassRealm classRealm, List<String> parentImports, Map<String, ClassLoader> foreignImports ) 366 { 367 if ( foreignImports != null && !foreignImports.isEmpty() ) 368 { 369 if ( logger.isDebugEnabled() ) 370 { 371 logger.debug( "Importing foreign packages into class realm " + classRealm.getId() ); 372 } 373 374 for ( Map.Entry<String, ClassLoader> entry : foreignImports.entrySet() ) 375 { 376 ClassLoader importedRealm = entry.getValue(); 377 String imp = entry.getKey(); 378 379 if ( logger.isDebugEnabled() ) 380 { 381 logger.debug( " Imported: " + imp + " < " + getId( importedRealm ) ); 382 } 383 384 classRealm.importFrom( importedRealm, imp ); 385 } 386 } 387 388 if ( parentImports != null && !parentImports.isEmpty() ) 389 { 390 if ( logger.isDebugEnabled() ) 391 { 392 logger.debug( "Importing parent packages into class realm " + classRealm.getId() ); 393 } 394 395 for ( String imp : parentImports ) 396 { 397 if ( logger.isDebugEnabled() ) 398 { 399 logger.debug( " Imported: " + imp + " < " + getId( classRealm.getParentClassLoader() ) ); 400 } 401 402 classRealm.importFromParent( imp ); 403 } 404 } 405 } 406 407 private String getId( ClassLoader classLoader ) 408 { 409 if ( classLoader instanceof ClassRealm ) 410 { 411 return ( (ClassRealm) classLoader ).getId(); 412 } 413 return String.valueOf( classLoader ); 414 } 415 416}