View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.cli.internal;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  
24  import java.io.File;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.List;
28  import java.util.Set;
29  
30  import org.apache.maven.RepositoryUtils;
31  import org.apache.maven.cli.internal.extension.model.CoreExtension;
32  import org.apache.maven.execution.MavenExecutionRequest;
33  import org.apache.maven.extension.internal.CoreExports;
34  import org.apache.maven.extension.internal.CoreExtensionEntry;
35  import org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory;
36  import org.apache.maven.model.Plugin;
37  import org.apache.maven.plugin.PluginResolutionException;
38  import org.apache.maven.plugin.internal.DefaultPluginDependenciesResolver;
39  import org.codehaus.plexus.DefaultPlexusContainer;
40  import org.codehaus.plexus.PlexusContainer;
41  import org.codehaus.plexus.classworlds.ClassWorld;
42  import org.codehaus.plexus.classworlds.realm.ClassRealm;
43  import org.codehaus.plexus.interpolation.InterpolationException;
44  import org.codehaus.plexus.interpolation.Interpolator;
45  import org.codehaus.plexus.interpolation.MapBasedValueSource;
46  import org.codehaus.plexus.interpolation.StringSearchInterpolator;
47  import org.codehaus.plexus.logging.Logger;
48  import org.eclipse.aether.RepositorySystemSession;
49  import org.eclipse.aether.artifact.Artifact;
50  import org.eclipse.aether.graph.DependencyFilter;
51  import org.eclipse.aether.graph.DependencyNode;
52  import org.eclipse.aether.repository.RemoteRepository;
53  import org.eclipse.aether.util.filter.ExclusionsDependencyFilter;
54  import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
55  
56  /**
57   * BootstrapCoreExtensionManager
58   */
59  @Named
60  public class BootstrapCoreExtensionManager {
61      public static final String STRATEGY_PARENT_FIRST = "parent-first";
62      public static final String STRATEGY_PLUGIN = "plugin";
63      public static final String STRATEGY_SELF_FIRST = "self-first";
64  
65      private final Logger log;
66  
67      private final DefaultPluginDependenciesResolver pluginDependenciesResolver;
68  
69      private final DefaultRepositorySystemSessionFactory repositorySystemSessionFactory;
70  
71      private final CoreExports coreExports;
72  
73      private final ClassWorld classWorld;
74  
75      private final ClassRealm parentRealm;
76  
77      @Inject
78      public BootstrapCoreExtensionManager(
79              Logger log,
80              DefaultPluginDependenciesResolver pluginDependenciesResolver,
81              DefaultRepositorySystemSessionFactory repositorySystemSessionFactory,
82              CoreExports coreExports,
83              PlexusContainer container) {
84          this.log = log;
85          this.pluginDependenciesResolver = pluginDependenciesResolver;
86          this.repositorySystemSessionFactory = repositorySystemSessionFactory;
87          this.coreExports = coreExports;
88          this.classWorld = ((DefaultPlexusContainer) container).getClassWorld();
89          this.parentRealm = container.getContainerRealm();
90      }
91  
92      public List<CoreExtensionEntry> loadCoreExtensions(
93              MavenExecutionRequest request, Set<String> providedArtifacts, List<CoreExtension> extensions)
94              throws Exception {
95          RepositorySystemSession repoSession = repositorySystemSessionFactory.newRepositorySession(request);
96          List<RemoteRepository> repositories = RepositoryUtils.toRepos(request.getPluginArtifactRepositories());
97          Interpolator interpolator = createInterpolator(request);
98  
99          return resolveCoreExtensions(repoSession, repositories, providedArtifacts, extensions, interpolator);
100     }
101 
102     private List<CoreExtensionEntry> resolveCoreExtensions(
103             RepositorySystemSession repoSession,
104             List<RemoteRepository> repositories,
105             Set<String> providedArtifacts,
106             List<CoreExtension> configuration,
107             Interpolator interpolator)
108             throws Exception {
109         List<CoreExtensionEntry> extensions = new ArrayList<>();
110 
111         DependencyFilter dependencyFilter = new ExclusionsDependencyFilter(providedArtifacts);
112 
113         for (CoreExtension extension : configuration) {
114             List<Artifact> artifacts =
115                     resolveExtension(extension, repoSession, repositories, dependencyFilter, interpolator);
116             if (!artifacts.isEmpty()) {
117                 extensions.add(createExtension(extension, artifacts));
118             }
119         }
120 
121         return Collections.unmodifiableList(extensions);
122     }
123 
124     private CoreExtensionEntry createExtension(CoreExtension extension, List<Artifact> artifacts) throws Exception {
125         String realmId = "coreExtension>" + extension.getGroupId() + ":" + extension.getArtifactId() + ":"
126                 + extension.getVersion();
127         final ClassRealm realm = classWorld.newRealm(realmId, null);
128         Set<String> providedArtifacts = Collections.emptySet();
129         String classLoadingStrategy = extension.getClassLoadingStrategy();
130         if (STRATEGY_PARENT_FIRST.equals(classLoadingStrategy)) {
131             realm.importFrom(parentRealm, "");
132         } else if (STRATEGY_PLUGIN.equals(classLoadingStrategy)) {
133             coreExports.getExportedPackages().forEach((p, cl) -> realm.importFrom(cl, p));
134             providedArtifacts = coreExports.getExportedArtifacts();
135         } else if (STRATEGY_SELF_FIRST.equals(classLoadingStrategy)) {
136             realm.setParentRealm(parentRealm);
137         } else {
138             throw new IllegalArgumentException("Unsupported class-loading strategy '"
139                     + classLoadingStrategy + "'. Supported values are: " + STRATEGY_PARENT_FIRST
140                     + ", " + STRATEGY_PLUGIN + " and " + STRATEGY_SELF_FIRST);
141         }
142         log.debug("Populating class realm " + realm.getId());
143         for (Artifact artifact : artifacts) {
144             String id = artifact.getGroupId() + ":" + artifact.getArtifactId();
145             if (providedArtifacts.contains(id)) {
146                 log.debug("  Excluded " + id);
147             } else {
148                 File file = artifact.getFile();
149                 log.debug("  Included " + id + " located at " + file);
150                 realm.addURL(file.toURI().toURL());
151             }
152         }
153         return CoreExtensionEntry.discoverFrom(
154                 realm, Collections.singleton(artifacts.get(0).getFile()));
155     }
156 
157     private List<Artifact> resolveExtension(
158             CoreExtension extension,
159             RepositorySystemSession repoSession,
160             List<RemoteRepository> repositories,
161             DependencyFilter dependencyFilter,
162             Interpolator interpolator)
163             throws ExtensionResolutionException {
164         try {
165             /* TODO: Enhance the PluginDependenciesResolver to provide a
166              * resolveCoreExtension method which uses a CoreExtension
167              * object instead of a Plugin as this makes no sense.
168              */
169             Plugin plugin = new Plugin();
170             plugin.setGroupId(interpolator.interpolate(extension.getGroupId()));
171             plugin.setArtifactId(interpolator.interpolate(extension.getArtifactId()));
172             plugin.setVersion(interpolator.interpolate(extension.getVersion()));
173 
174             DependencyNode root = pluginDependenciesResolver.resolveCoreExtension(
175                     plugin, dependencyFilter, repositories, repoSession);
176             PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
177             root.accept(nlg);
178             List<Artifact> artifacts = nlg.getArtifacts(false);
179 
180             return artifacts;
181         } catch (PluginResolutionException e) {
182             throw new ExtensionResolutionException(extension, e.getCause());
183         } catch (InterpolationException e) {
184             throw new ExtensionResolutionException(extension, e);
185         }
186     }
187 
188     private static Interpolator createInterpolator(MavenExecutionRequest request) {
189         StringSearchInterpolator interpolator = new StringSearchInterpolator();
190         interpolator.addValueSource(new MapBasedValueSource(request.getUserProperties()));
191         interpolator.addValueSource(new MapBasedValueSource(request.getSystemProperties()));
192         return interpolator;
193     }
194 }