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