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.cling.invoker;
20  
21  import javax.xml.stream.XMLStreamException;
22  
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.PrintWriter;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.nio.file.Paths;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.HashMap;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Properties;
35  import java.util.function.Function;
36  import java.util.stream.Collectors;
37  
38  import org.apache.maven.api.Constants;
39  import org.apache.maven.api.annotations.Nullable;
40  import org.apache.maven.api.cli.InvokerRequest;
41  import org.apache.maven.api.cli.Options;
42  import org.apache.maven.api.cli.Parser;
43  import org.apache.maven.api.cli.ParserException;
44  import org.apache.maven.api.cli.ParserRequest;
45  import org.apache.maven.api.cli.extensions.CoreExtension;
46  import org.apache.maven.cling.internal.extension.io.CoreExtensionsStaxReader;
47  import org.apache.maven.cling.props.MavenPropertiesLoader;
48  import org.apache.maven.cling.utils.CLIReportingUtils;
49  import org.apache.maven.properties.internal.EnvironmentUtils;
50  import org.apache.maven.properties.internal.SystemProperties;
51  
52  import static java.util.Objects.requireNonNull;
53  import static org.apache.maven.api.Constants.MAVEN_HOME;
54  import static org.apache.maven.api.Constants.MAVEN_INSTALLATION_CONF;
55  import static org.apache.maven.cling.invoker.Utils.getCanonicalPath;
56  import static org.apache.maven.cling.invoker.Utils.or;
57  import static org.apache.maven.cling.invoker.Utils.prefix;
58  import static org.apache.maven.cling.invoker.Utils.stripLeadingAndTrailingQuotes;
59  import static org.apache.maven.cling.invoker.Utils.toMap;
60  
61  public abstract class BaseParser implements Parser {
62  
63      @SuppressWarnings("VisibilityModifier")
64      public static class LocalContext {
65          public final ParserRequest parserRequest;
66          public final Map<String, String> systemPropertiesOverrides;
67  
68          public LocalContext(ParserRequest parserRequest) {
69              this.parserRequest = parserRequest;
70              this.systemPropertiesOverrides = new HashMap<>();
71          }
72  
73          public Path cwd;
74          public Path installationDirectory;
75          public Path userHomeDirectory;
76          public Map<String, String> systemProperties;
77          public Map<String, String> userProperties;
78          public Path topDirectory;
79  
80          @Nullable
81          public Path rootDirectory;
82  
83          public List<CoreExtension> extensions;
84          public Options options;
85  
86          public Map<String, String> extraInterpolationSource() {
87              Map<String, String> extra = new HashMap<>();
88              extra.put("session.topDirectory", topDirectory.toString());
89              if (rootDirectory != null) {
90                  extra.put("session.rootDirectory", rootDirectory.toString());
91              }
92              return extra;
93          }
94      }
95  
96      @Override
97      public InvokerRequest parseInvocation(ParserRequest parserRequest) throws ParserException, IOException {
98          requireNonNull(parserRequest);
99  
100         LocalContext context = new LocalContext(parserRequest);
101 
102         // the basics
103         context.cwd = requireNonNull(getCwd(context));
104         context.installationDirectory = requireNonNull(getInstallationDirectory(context));
105         context.userHomeDirectory = requireNonNull(getUserHomeDirectory(context));
106 
107         // top/root
108         context.topDirectory = requireNonNull(getTopDirectory(context));
109         context.rootDirectory = getRootDirectory(context);
110 
111         // options
112         List<Options> parsedOptions = parseCliOptions(context);
113 
114         // warn about deprecated options
115         PrintWriter printWriter = new PrintWriter(parserRequest.out() != null ? parserRequest.out() : System.out, true);
116         parsedOptions.forEach(o -> o.warnAboutDeprecatedOptions(parserRequest, printWriter::println));
117 
118         // assemble options if needed
119         context.options = assembleOptions(parsedOptions);
120 
121         // system and user properties
122         context.systemProperties = populateSystemProperties(context);
123         context.userProperties = populateUserProperties(context);
124 
125         // options: interpolate
126         context.options = context.options.interpolate(
127                 Arrays.asList(context.extraInterpolationSource(), context.userProperties, context.systemProperties));
128 
129         // core extensions
130         context.extensions = readCoreExtensionsDescriptor(context);
131 
132         return getInvokerRequest(context);
133     }
134 
135     protected abstract InvokerRequest getInvokerRequest(LocalContext context);
136 
137     protected Path getCwd(LocalContext context) throws ParserException {
138         if (context.parserRequest.cwd() != null) {
139             Path result = getCanonicalPath(context.parserRequest.cwd());
140             context.systemPropertiesOverrides.put("user.dir", result.toString());
141             return result;
142         } else {
143             return getCanonicalPath(Paths.get(System.getProperty("user.dir")));
144         }
145     }
146 
147     protected Path getInstallationDirectory(LocalContext context) throws ParserException {
148         Path result;
149         if (context.parserRequest.mavenHome() != null) {
150             result = getCanonicalPath(context.parserRequest.mavenHome());
151             context.systemPropertiesOverrides.put(MAVEN_HOME, result.toString());
152         } else {
153             String mavenHome = System.getProperty(Constants.MAVEN_HOME);
154             if (mavenHome == null) {
155                 throw new ParserException("local mode requires " + Constants.MAVEN_HOME + " Java System Property set");
156             }
157             result = getCanonicalPath(Paths.get(mavenHome));
158         }
159         return result;
160     }
161 
162     protected Path getUserHomeDirectory(LocalContext context) throws ParserException {
163         if (context.parserRequest.userHome() != null) {
164             Path result = getCanonicalPath(context.parserRequest.userHome());
165             context.systemPropertiesOverrides.put("user.home", result.toString());
166             return result;
167         } else {
168             return getCanonicalPath(Paths.get(System.getProperty("user.home")));
169         }
170     }
171 
172     protected Path getTopDirectory(LocalContext context) throws ParserException {
173         // We need to locate the top level project which may be pointed at using
174         // the -f/--file option.
175         Path topDirectory = requireNonNull(context.cwd);
176         boolean isAltFile = false;
177         for (String arg : context.parserRequest.args()) {
178             if (isAltFile) {
179                 // this is the argument following -f/--file
180                 Path path = topDirectory.resolve(stripLeadingAndTrailingQuotes(arg));
181                 if (Files.isDirectory(path)) {
182                     topDirectory = path;
183                 } else if (Files.isRegularFile(path)) {
184                     topDirectory = path.getParent();
185                     if (!Files.isDirectory(topDirectory)) {
186                         throw new ParserException("Directory " + topDirectory
187                                 + " extracted from the -f/--file command-line argument " + arg + " does not exist");
188                     }
189                 } else {
190                     throw new ParserException(
191                             "POM file " + arg + " specified with the -f/--file command line argument does not exist");
192                 }
193                 break;
194             } else {
195                 // Check if this is the -f/--file option
196                 isAltFile = arg.equals("-f") || arg.equals("--file");
197             }
198         }
199         return getCanonicalPath(topDirectory);
200     }
201 
202     @Nullable
203     protected Path getRootDirectory(LocalContext context) throws ParserException {
204         return Utils.findRoot(context.topDirectory);
205     }
206 
207     protected Map<String, String> populateSystemProperties(LocalContext context) throws ParserException {
208         Properties systemProperties = new Properties();
209 
210         // ----------------------------------------------------------------------
211         // Load environment and system properties
212         // ----------------------------------------------------------------------
213 
214         EnvironmentUtils.addEnvVars(systemProperties);
215         SystemProperties.addSystemProperties(systemProperties);
216 
217         // ----------------------------------------------------------------------
218         // Properties containing info about the currently running version of Maven
219         // These override any corresponding properties set on the command line
220         // ----------------------------------------------------------------------
221 
222         Properties buildProperties = CLIReportingUtils.getBuildProperties();
223 
224         String mavenVersion = buildProperties.getProperty(CLIReportingUtils.BUILD_VERSION_PROPERTY);
225         systemProperties.setProperty("maven.version", mavenVersion);
226 
227         String mavenBuildVersion = CLIReportingUtils.createMavenVersionString(buildProperties);
228         systemProperties.setProperty("maven.build.version", mavenBuildVersion);
229 
230         Map<String, String> result = toMap(systemProperties);
231         result.putAll(context.systemPropertiesOverrides);
232         return result;
233     }
234 
235     protected Map<String, String> populateUserProperties(LocalContext context) throws ParserException, IOException {
236         Properties userProperties = new Properties();
237 
238         // ----------------------------------------------------------------------
239         // Options that are set on the command line become system properties
240         // and therefore are set in the session properties. System properties
241         // are most dominant.
242         // ----------------------------------------------------------------------
243 
244         Map<String, String> userSpecifiedProperties =
245                 context.options.userProperties().orElse(new HashMap<>());
246 
247         // ----------------------------------------------------------------------
248         // Load config files
249         // ----------------------------------------------------------------------
250         Map<String, String> paths = context.extraInterpolationSource();
251         Function<String, String> callback =
252                 or(paths::get, prefix("cli.", userSpecifiedProperties::get), context.systemProperties::get);
253 
254         Path mavenConf;
255         if (context.systemProperties.get(MAVEN_INSTALLATION_CONF) != null) {
256             mavenConf = context.installationDirectory.resolve(context.systemProperties.get(MAVEN_INSTALLATION_CONF));
257         } else if (context.systemProperties.get("maven.conf") != null) {
258             mavenConf = context.installationDirectory.resolve(context.systemProperties.get("maven.conf"));
259         } else if (context.systemProperties.get(MAVEN_HOME) != null) {
260             mavenConf = context.installationDirectory
261                     .resolve(context.systemProperties.get(MAVEN_HOME))
262                     .resolve("conf");
263         } else {
264             mavenConf = context.installationDirectory.resolve("");
265         }
266         Path propertiesFile = mavenConf.resolve("maven.properties");
267         MavenPropertiesLoader.loadProperties(userProperties, propertiesFile, callback, false);
268 
269         // CLI specified properties are most dominant
270         userProperties.putAll(userSpecifiedProperties);
271 
272         return toMap(userProperties);
273     }
274 
275     protected abstract List<Options> parseCliOptions(LocalContext context) throws ParserException, IOException;
276 
277     protected abstract Options assembleOptions(List<Options> parsedOptions);
278 
279     protected List<CoreExtension> readCoreExtensionsDescriptor(LocalContext context)
280             throws ParserException, IOException {
281         ArrayList<CoreExtension> extensions = new ArrayList<>();
282         String installationExtensionsFile = context.userProperties.get(Constants.MAVEN_INSTALLATION_EXTENSIONS);
283         extensions.addAll(readCoreExtensionsDescriptorFromFile(
284                 context.installationDirectory.resolve(installationExtensionsFile)));
285 
286         String projectExtensionsFile = context.userProperties.get(Constants.MAVEN_PROJECT_EXTENSIONS);
287         extensions.addAll(readCoreExtensionsDescriptorFromFile(context.cwd.resolve(projectExtensionsFile)));
288 
289         String userExtensionsFile = context.userProperties.get(Constants.MAVEN_USER_EXTENSIONS);
290         extensions.addAll(readCoreExtensionsDescriptorFromFile(context.userHomeDirectory.resolve(userExtensionsFile)));
291 
292         return extensions;
293     }
294 
295     protected List<CoreExtension> readCoreExtensionsDescriptorFromFile(Path extensionsFile)
296             throws ParserException, IOException {
297         try {
298             if (extensionsFile != null && Files.exists(extensionsFile)) {
299                 try (InputStream is = Files.newInputStream(extensionsFile)) {
300                     return new CoreExtensionsStaxReader().read(is, true).getExtensions();
301                 }
302             }
303             return List.of();
304         } catch (XMLStreamException e) {
305             throw new ParserException("Failed to parse extensions file: " + extensionsFile, e);
306         }
307     }
308 
309     protected List<String> getJvmArguments(Path rootDirectory) throws ParserException {
310         if (rootDirectory != null) {
311             Path jvmConfig = rootDirectory.resolve(".mvn/jvm.config");
312             if (Files.exists(jvmConfig)) {
313                 try {
314                     return Files.readAllLines(jvmConfig).stream()
315                             .filter(l -> !l.isBlank() && !l.startsWith("#"))
316                             .flatMap(l -> Arrays.stream(l.split(" ")))
317                             .collect(Collectors.toList());
318                 } catch (IOException e) {
319                     throw new ParserException("Failed to read JVM configuration file: " + jvmConfig, e);
320                 }
321             }
322         }
323         return null;
324     }
325 }