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 java.io.File;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.Set;
26  import javax.inject.Inject;
27  import javax.inject.Named;
28  import org.apache.maven.RepositoryUtils;
29  import org.apache.maven.api.model.Plugin;
30  import org.apache.maven.cli.internal.extension.model.CoreExtension;
31  import org.apache.maven.execution.MavenExecutionRequest;
32  import org.apache.maven.extension.internal.CoreExports;
33  import org.apache.maven.extension.internal.CoreExtensionEntry;
34  import org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory;
35  import org.apache.maven.plugin.PluginResolutionException;
36  import org.apache.maven.plugin.internal.DefaultPluginDependenciesResolver;
37  import org.codehaus.plexus.DefaultPlexusContainer;
38  import org.codehaus.plexus.PlexusContainer;
39  import org.codehaus.plexus.classworlds.ClassWorld;
40  import org.codehaus.plexus.classworlds.realm.ClassRealm;
41  import org.codehaus.plexus.interpolation.InterpolationException;
42  import org.codehaus.plexus.interpolation.Interpolator;
43  import org.codehaus.plexus.interpolation.MapBasedValueSource;
44  import org.codehaus.plexus.interpolation.StringSearchInterpolator;
45  import org.eclipse.aether.RepositorySystemSession;
46  import org.eclipse.aether.artifact.Artifact;
47  import org.eclipse.aether.graph.DependencyFilter;
48  import org.eclipse.aether.graph.DependencyNode;
49  import org.eclipse.aether.repository.RemoteRepository;
50  import org.eclipse.aether.util.filter.ExclusionsDependencyFilter;
51  import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
52  import org.slf4j.Logger;
53  import org.slf4j.LoggerFactory;
54  
55  /**
56   * BootstrapCoreExtensionManager
57   */
58  @Named
59  public class BootstrapCoreExtensionManager {
60      public static final String STRATEGY_PARENT_FIRST = "parent-first";
61      public static final String STRATEGY_PLUGIN = "plugin";
62      public static final String STRATEGY_SELF_FIRST = "self-first";
63  
64      private final Logger log = LoggerFactory.getLogger(getClass());
65  
66      private final DefaultPluginDependenciesResolver pluginDependenciesResolver;
67  
68      private final DefaultRepositorySystemSessionFactory repositorySystemSessionFactory;
69  
70      private final CoreExports coreExports;
71  
72      private final ClassWorld classWorld;
73  
74      private final ClassRealm parentRealm;
75  
76      @Inject
77      public BootstrapCoreExtensionManager(
78              DefaultPluginDependenciesResolver pluginDependenciesResolver,
79              DefaultRepositorySystemSessionFactory repositorySystemSessionFactory,
80              CoreExports coreExports,
81              PlexusContainer container) {
82          this.pluginDependenciesResolver = pluginDependenciesResolver;
83          this.repositorySystemSessionFactory = repositorySystemSessionFactory;
84          this.coreExports = coreExports;
85          this.classWorld = ((DefaultPlexusContainer) container).getClassWorld();
86          this.parentRealm = container.getContainerRealm();
87      }
88  
89      public List<CoreExtensionEntry> loadCoreExtensions(
90              MavenExecutionRequest request, Set<String> providedArtifacts, List<CoreExtension> extensions)
91              throws Exception {
92          RepositorySystemSession repoSession = repositorySystemSessionFactory.newRepositorySession(request);
93          List<RemoteRepository> repositories = RepositoryUtils.toRepos(request.getPluginArtifactRepositories());
94          Interpolator interpolator = createInterpolator(request);
95  
96          return resolveCoreExtensions(repoSession, repositories, providedArtifacts, extensions, interpolator);
97      }
98  
99      private List<CoreExtensionEntry> resolveCoreExtensions(
100             RepositorySystemSession repoSession,
101             List<RemoteRepository> repositories,
102             Set<String> providedArtifacts,
103             List<CoreExtension> configuration,
104             Interpolator interpolator)
105             throws Exception {
106         List<CoreExtensionEntry> extensions = new ArrayList<>();
107 
108         DependencyFilter dependencyFilter = new ExclusionsDependencyFilter(providedArtifacts);
109 
110         for (CoreExtension extension : configuration) {
111             List<Artifact> artifacts =
112                     resolveExtension(extension, repoSession, repositories, dependencyFilter, interpolator);
113             if (!artifacts.isEmpty()) {
114                 extensions.add(createExtension(extension, artifacts));
115             }
116         }
117 
118         return Collections.unmodifiableList(extensions);
119     }
120 
121     private CoreExtensionEntry createExtension(CoreExtension extension, List<Artifact> artifacts) throws Exception {
122         String realmId = "coreExtension>" + extension.getGroupId() + ":" + extension.getArtifactId() + ":"
123                 + extension.getVersion();
124         final ClassRealm realm = classWorld.newRealm(realmId, null);
125         Set<String> providedArtifacts = Collections.emptySet();
126         String classLoadingStrategy = extension.getClassLoadingStrategy();
127         if (STRATEGY_PARENT_FIRST.equals(classLoadingStrategy)) {
128             realm.importFrom(parentRealm, "");
129         } else if (STRATEGY_PLUGIN.equals(classLoadingStrategy)) {
130             coreExports.getExportedPackages().forEach((p, cl) -> realm.importFrom(cl, p));
131             providedArtifacts = coreExports.getExportedArtifacts();
132         } else if (STRATEGY_SELF_FIRST.equals(classLoadingStrategy)) {
133             realm.setParentRealm(parentRealm);
134         } else {
135             throw new IllegalArgumentException("Unsupported class-loading strategy '"
136                     + classLoadingStrategy + "'. Supported values are: " + STRATEGY_PARENT_FIRST
137                     + ", " + STRATEGY_PLUGIN + " and " + STRATEGY_SELF_FIRST);
138         }
139         log.debug("Populating class realm {}", realm.getId());
140         for (Artifact artifact : artifacts) {
141             String id = artifact.getGroupId() + ":" + artifact.getArtifactId();
142             if (providedArtifacts.contains(id)) {
143                 log.debug("  Excluded {}", id);
144             } else {
145                 File file = artifact.getFile();
146                 log.debug("  Included {} located at {}", id, file);
147                 realm.addURL(file.toURI().toURL());
148             }
149         }
150         return CoreExtensionEntry.discoverFrom(
151                 realm, Collections.singleton(artifacts.get(0).getFile()));
152     }
153 
154     private List<Artifact> resolveExtension(
155             CoreExtension extension,
156             RepositorySystemSession repoSession,
157             List<RemoteRepository> repositories,
158             DependencyFilter dependencyFilter,
159             Interpolator interpolator)
160             throws ExtensionResolutionException {
161         try {
162             /* TODO: Enhance the PluginDependenciesResolver to provide a
163              * resolveCoreExtension method which uses a CoreExtension
164              * object instead of a Plugin as this makes no sense.
165              */
166             Plugin plugin = Plugin.newBuilder()
167                     .groupId(interpolator.interpolate(extension.getGroupId()))
168                     .artifactId(interpolator.interpolate(extension.getArtifactId()))
169                     .version(interpolator.interpolate(extension.getVersion()))
170                     .build();
171 
172             DependencyNode root = pluginDependenciesResolver.resolveCoreExtension(
173                     new org.apache.maven.model.Plugin(plugin), dependencyFilter, repositories, repoSession);
174             PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
175             root.accept(nlg);
176 
177             return nlg.getArtifacts(false);
178         } catch (PluginResolutionException e) {
179             throw new ExtensionResolutionException(extension, e.getCause());
180         } catch (InterpolationException e) {
181             throw new ExtensionResolutionException(extension, e);
182         }
183     }
184 
185     private static Interpolator createInterpolator(MavenExecutionRequest request) {
186         StringSearchInterpolator interpolator = new StringSearchInterpolator();
187         interpolator.addValueSource(new MapBasedValueSource(request.getUserProperties()));
188         interpolator.addValueSource(new MapBasedValueSource(request.getSystemProperties()));
189         return interpolator;
190     }
191 }