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