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.NoSuchElementException;
29  import java.util.Set;
30  import java.util.function.Function;
31  import java.util.stream.Collectors;
32  
33  import org.apache.maven.RepositoryUtils;
34  import org.apache.maven.api.Service;
35  import org.apache.maven.api.Session;
36  import org.apache.maven.api.cli.extensions.CoreExtension;
37  import org.apache.maven.api.model.Plugin;
38  import org.apache.maven.api.services.ArtifactCoordinatesFactory;
39  import org.apache.maven.api.services.ArtifactManager;
40  import org.apache.maven.api.services.ArtifactResolver;
41  import org.apache.maven.api.services.Interpolator;
42  import org.apache.maven.api.services.InterpolatorException;
43  import org.apache.maven.api.services.RepositoryFactory;
44  import org.apache.maven.api.services.VersionParser;
45  import org.apache.maven.api.services.VersionRangeResolver;
46  import org.apache.maven.execution.DefaultMavenExecutionResult;
47  import org.apache.maven.execution.MavenExecutionRequest;
48  import org.apache.maven.execution.MavenSession;
49  import org.apache.maven.extension.internal.CoreExports;
50  import org.apache.maven.extension.internal.CoreExtensionEntry;
51  import org.apache.maven.internal.impl.DefaultArtifactCoordinatesFactory;
52  import org.apache.maven.internal.impl.DefaultArtifactManager;
53  import org.apache.maven.internal.impl.DefaultArtifactResolver;
54  import org.apache.maven.internal.impl.DefaultModelVersionParser;
55  import org.apache.maven.internal.impl.DefaultRepositoryFactory;
56  import org.apache.maven.internal.impl.DefaultSession;
57  import org.apache.maven.internal.impl.DefaultVersionParser;
58  import org.apache.maven.internal.impl.DefaultVersionRangeResolver;
59  import org.apache.maven.internal.impl.InternalSession;
60  import org.apache.maven.internal.impl.model.DefaultInterpolator;
61  import org.apache.maven.plugin.PluginResolutionException;
62  import org.apache.maven.plugin.internal.DefaultPluginDependenciesResolver;
63  import org.apache.maven.resolver.MavenChainedWorkspaceReader;
64  import org.apache.maven.resolver.RepositorySystemSessionFactory;
65  import org.codehaus.plexus.DefaultPlexusContainer;
66  import org.codehaus.plexus.PlexusContainer;
67  import org.codehaus.plexus.classworlds.ClassWorld;
68  import org.codehaus.plexus.classworlds.realm.ClassRealm;
69  import org.eclipse.aether.RepositorySystem;
70  import org.eclipse.aether.RepositorySystemSession;
71  import org.eclipse.aether.RepositorySystemSession.CloseableSession;
72  import org.eclipse.aether.artifact.Artifact;
73  import org.eclipse.aether.graph.DependencyFilter;
74  import org.eclipse.aether.internal.impl.DefaultChecksumPolicyProvider;
75  import org.eclipse.aether.internal.impl.DefaultRemoteRepositoryManager;
76  import org.eclipse.aether.internal.impl.DefaultUpdatePolicyAnalyzer;
77  import org.eclipse.aether.repository.RemoteRepository;
78  import org.eclipse.aether.repository.WorkspaceReader;
79  import org.eclipse.aether.resolution.ArtifactResult;
80  import org.eclipse.aether.resolution.DependencyResult;
81  import org.eclipse.aether.util.filter.ExclusionsDependencyFilter;
82  import org.eclipse.aether.util.version.GenericVersionScheme;
83  import org.eclipse.sisu.Nullable;
84  import org.slf4j.Logger;
85  import org.slf4j.LoggerFactory;
86  
87  /**
88   * BootstrapCoreExtensionManager
89   */
90  @Deprecated
91  @Named
92  public class BootstrapCoreExtensionManager {
93      public static final String STRATEGY_PARENT_FIRST = "parent-first";
94      public static final String STRATEGY_PLUGIN = "plugin";
95      public static final String STRATEGY_SELF_FIRST = "self-first";
96  
97      private final Logger log = LoggerFactory.getLogger(getClass());
98  
99      private final DefaultPluginDependenciesResolver pluginDependenciesResolver;
100 
101     private final RepositorySystemSessionFactory repositorySystemSessionFactory;
102 
103     private final CoreExports coreExports;
104 
105     private final ClassWorld classWorld;
106 
107     private final ClassRealm parentRealm;
108 
109     private final WorkspaceReader ideWorkspaceReader;
110 
111     private final RepositorySystem repoSystem;
112 
113     @Inject
114     public BootstrapCoreExtensionManager(
115             DefaultPluginDependenciesResolver pluginDependenciesResolver,
116             RepositorySystemSessionFactory repositorySystemSessionFactory,
117             CoreExports coreExports,
118             PlexusContainer container,
119             @Nullable @Named("ide") WorkspaceReader ideWorkspaceReader,
120             RepositorySystem repoSystem) {
121         this.pluginDependenciesResolver = pluginDependenciesResolver;
122         this.repositorySystemSessionFactory = repositorySystemSessionFactory;
123         this.coreExports = coreExports;
124         this.classWorld = ((DefaultPlexusContainer) container).getClassWorld();
125         this.parentRealm = container.getContainerRealm();
126         this.ideWorkspaceReader = ideWorkspaceReader;
127         this.repoSystem = repoSystem;
128     }
129 
130     public List<CoreExtensionEntry> loadCoreExtensions(
131             MavenExecutionRequest request, Set<String> providedArtifacts, List<CoreExtension> extensions)
132             throws Exception {
133         try (CloseableSession repoSession = repositorySystemSessionFactory
134                 .newRepositorySessionBuilder(request)
135                 .setWorkspaceReader(new MavenChainedWorkspaceReader(request.getWorkspaceReader(), ideWorkspaceReader))
136                 .build()) {
137             MavenSession mSession = new MavenSession(repoSession, request, new DefaultMavenExecutionResult());
138             InternalSession iSession = new SimpleSession(mSession, repoSystem, null);
139             InternalSession.associate(repoSession, iSession);
140 
141             List<RemoteRepository> repositories = RepositoryUtils.toRepos(request.getPluginArtifactRepositories());
142             Function<String, String> interpolator = createInterpolator(request);
143 
144             return resolveCoreExtensions(repoSession, repositories, providedArtifacts, extensions, interpolator);
145         }
146     }
147 
148     private List<CoreExtensionEntry> resolveCoreExtensions(
149             RepositorySystemSession repoSession,
150             List<RemoteRepository> repositories,
151             Set<String> providedArtifacts,
152             List<CoreExtension> configuration,
153             Function<String, String> interpolator)
154             throws Exception {
155         List<CoreExtensionEntry> extensions = new ArrayList<>();
156 
157         DependencyFilter dependencyFilter = new ExclusionsDependencyFilter(providedArtifacts);
158 
159         for (CoreExtension extension : configuration) {
160             List<Artifact> artifacts =
161                     resolveExtension(extension, repoSession, repositories, dependencyFilter, interpolator);
162             if (!artifacts.isEmpty()) {
163                 extensions.add(createExtension(extension, artifacts));
164             }
165         }
166 
167         return Collections.unmodifiableList(extensions);
168     }
169 
170     private CoreExtensionEntry createExtension(CoreExtension extension, List<Artifact> artifacts) throws Exception {
171         String realmId = "coreExtension>" + extension.getGroupId() + ":" + extension.getArtifactId() + ":"
172                 + extension.getVersion();
173         final ClassRealm realm = classWorld.newRealm(realmId, null);
174         Set<String> providedArtifacts = Collections.emptySet();
175         String classLoadingStrategy = extension.getClassLoadingStrategy();
176         if (STRATEGY_PARENT_FIRST.equals(classLoadingStrategy)) {
177             realm.importFrom(parentRealm, "");
178         } else if (STRATEGY_PLUGIN.equals(classLoadingStrategy)) {
179             coreExports.getExportedPackages().forEach((p, cl) -> realm.importFrom(cl, p));
180             providedArtifacts = coreExports.getExportedArtifacts();
181         } else if (STRATEGY_SELF_FIRST.equals(classLoadingStrategy)) {
182             realm.setParentRealm(parentRealm);
183         } else {
184             throw new IllegalArgumentException("Unsupported class-loading strategy '"
185                     + classLoadingStrategy + "'. Supported values are: " + STRATEGY_PARENT_FIRST
186                     + ", " + STRATEGY_PLUGIN + " and " + STRATEGY_SELF_FIRST);
187         }
188         log.debug("Populating class realm {}", realm.getId());
189         for (Artifact artifact : artifacts) {
190             String id = artifact.getGroupId() + ":" + artifact.getArtifactId();
191             if (providedArtifacts.contains(id)) {
192                 log.debug("  Excluded {}", id);
193             } else {
194                 File file = artifact.getFile();
195                 log.debug("  Included {} located at {}", id, file);
196                 realm.addURL(file.toURI().toURL());
197             }
198         }
199         return CoreExtensionEntry.discoverFrom(
200                 realm,
201                 Collections.singleton(artifacts.get(0).getFile()),
202                 extension.getGroupId() + ":" + extension.getArtifactId(),
203                 extension.getConfiguration());
204     }
205 
206     private List<Artifact> resolveExtension(
207             CoreExtension extension,
208             RepositorySystemSession repoSession,
209             List<RemoteRepository> repositories,
210             DependencyFilter dependencyFilter,
211             Function<String, String> interpolator)
212             throws ExtensionResolutionException {
213         try {
214             /* TODO: Enhance the PluginDependenciesResolver to provide a
215              * resolveCoreExtension method which uses a CoreExtension
216              * object instead of a Plugin as this makes no sense.
217              */
218             Plugin plugin = Plugin.newBuilder()
219                     .groupId(interpolator.apply(extension.getGroupId()))
220                     .artifactId(interpolator.apply(extension.getArtifactId()))
221                     .version(interpolator.apply(extension.getVersion()))
222                     .build();
223 
224             DependencyResult result = pluginDependenciesResolver.resolveCoreExtension(
225                     new org.apache.maven.model.Plugin(plugin), dependencyFilter, repositories, repoSession);
226             return result.getArtifactResults().stream()
227                     .filter(ArtifactResult::isResolved)
228                     .map(ArtifactResult::getArtifact)
229                     .collect(Collectors.toList());
230         } catch (PluginResolutionException | InterpolatorException e) {
231             throw new ExtensionResolutionException(extension, e);
232         }
233     }
234 
235     private static Function<String, String> createInterpolator(MavenExecutionRequest request) {
236         Interpolator interpolator = new DefaultInterpolator();
237         Function<String, String> callback = v -> {
238             String r = request.getUserProperties().getProperty(v);
239             if (r == null) {
240                 r = request.getSystemProperties().getProperty(v);
241             }
242             return r != null ? r : v;
243         };
244         return v -> interpolator.interpolate(v, callback);
245     }
246 
247     static class SimpleSession extends DefaultSession {
248         SimpleSession(
249                 MavenSession session,
250                 RepositorySystem repositorySystem,
251                 List<org.apache.maven.api.RemoteRepository> repositories) {
252             super(session, repositorySystem, repositories, null, null, null);
253         }
254 
255         @Override
256         protected Session newSession(
257                 MavenSession mavenSession, List<org.apache.maven.api.RemoteRepository> repositories) {
258             return new SimpleSession(mavenSession, getRepositorySystem(), repositories);
259         }
260 
261         @Override
262         public <T extends Service> T getService(Class<T> clazz) throws NoSuchElementException {
263             if (clazz == ArtifactCoordinatesFactory.class) {
264                 return (T) new DefaultArtifactCoordinatesFactory();
265             } else if (clazz == VersionParser.class) {
266                 return (T) new DefaultVersionParser(new DefaultModelVersionParser(new GenericVersionScheme()));
267             } else if (clazz == VersionRangeResolver.class) {
268                 return (T) new DefaultVersionRangeResolver(repositorySystem);
269             } else if (clazz == ArtifactResolver.class) {
270                 return (T) new DefaultArtifactResolver();
271             } else if (clazz == ArtifactManager.class) {
272                 return (T) new DefaultArtifactManager(this);
273             } else if (clazz == RepositoryFactory.class) {
274                 return (T) new DefaultRepositoryFactory(new DefaultRemoteRepositoryManager(
275                         new DefaultUpdatePolicyAnalyzer(), new DefaultChecksumPolicyProvider()));
276             } else if (clazz == Interpolator.class) {
277                 return (T) new DefaultInterpolator();
278                 // } else if (clazz == ModelResolver.class) {
279                 //    return (T) new DefaultModelResolver();
280             }
281             throw new NoSuchElementException("No service for " + clazz.getName());
282         }
283     }
284 }