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