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.scm.plugin;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Map.Entry;
28  import java.util.Objects;
29  import java.util.Properties;
30  
31  import org.apache.commons.lang3.StringUtils;
32  import org.apache.maven.plugin.AbstractMojo;
33  import org.apache.maven.plugin.MojoExecutionException;
34  import org.apache.maven.plugins.annotations.Parameter;
35  import org.apache.maven.scm.ScmBranch;
36  import org.apache.maven.scm.ScmException;
37  import org.apache.maven.scm.ScmFileSet;
38  import org.apache.maven.scm.ScmResult;
39  import org.apache.maven.scm.ScmRevision;
40  import org.apache.maven.scm.ScmTag;
41  import org.apache.maven.scm.ScmVersion;
42  import org.apache.maven.scm.manager.ScmManager;
43  import org.apache.maven.scm.provider.ScmProviderRepository;
44  import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost;
45  import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository;
46  import org.apache.maven.scm.repository.ScmRepository;
47  import org.apache.maven.scm.repository.ScmRepositoryException;
48  import org.apache.maven.settings.Server;
49  import org.apache.maven.settings.Settings;
50  import org.apache.maven.settings.building.SettingsProblem;
51  import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
52  import org.apache.maven.settings.crypto.SettingsDecrypter;
53  import org.apache.maven.settings.crypto.SettingsDecryptionResult;
54  import org.apache.maven.shared.model.fileset.FileSet;
55  import org.apache.maven.shared.model.fileset.util.FileSetManager;
56  
57  /**
58   * @author <a href="evenisse@apache.org">Emmanuel Venisse</a>
59   * @author Olivier Lamy
60   */
61  public abstract class AbstractScmMojo extends AbstractMojo {
62  
63      protected static final String VERSION_TYPE_BRANCH = "branch";
64  
65      protected static final String VERSION_TYPE_REVISION = "revision";
66  
67      protected static final String VERSION_TYPE_TAG = "tag";
68  
69      protected static final String[] VALID_VERSION_TYPES = {VERSION_TYPE_BRANCH, VERSION_TYPE_REVISION, VERSION_TYPE_TAG
70      };
71  
72      /**
73       * The SCM connection URL.
74       */
75      @Parameter(property = "connectionUrl", defaultValue = "${project.scm.connection}")
76      private String connectionUrl;
77  
78      /**
79       * The SCM connection URL for developers.
80       */
81      @Parameter(property = "developerConnectionUrl", defaultValue = "${project.scm.developerConnection}")
82      private String developerConnectionUrl;
83  
84      /**
85       * The type of connection to use (connection or developerConnection).
86       */
87      @Parameter(property = "connectionType", defaultValue = "connection")
88      private String connectionType;
89  
90      /**
91       * The working directory.
92       */
93      @Parameter(property = "workingDirectory")
94      private File workingDirectory;
95  
96      /**
97       * The user name.
98       * @see <a href="https://maven.apache.org/scm/authentication.html">Authentication</a>
99       */
100     @Parameter(property = "username")
101     private String username;
102 
103     /**
104      * The user password.
105      * @see <a href="https://maven.apache.org/scm/authentication.html">Authentication</a>
106      */
107     @Parameter(property = "password")
108     private String password;
109 
110     /**
111      * The private key.
112      * @see <a href="https://maven.apache.org/scm/authentication.html">Authentication</a>
113      */
114     @Parameter(property = "privateKey")
115     private String privateKey;
116 
117     /**
118      * The passphrase.
119      * @see <a href="https://maven.apache.org/scm/authentication.html">Authentication</a>
120      */
121     @Parameter(property = "passphrase")
122     private String passphrase;
123 
124     /**
125      * The server id of the server which provides the credentials for the SCM in the <a href="https://maven.apache.org/settings.html">settings.xml</a> file.
126      * If not set the default lookup uses the SCM URL to construct the server id like this:
127      * {@code server-id=scm-host[":"scm-port]}.
128      * <p>
129      * Currently the POM does not allow to specify a server id for the SCM section.
130      * <p>
131      * Explicit authentication information provided via {@link #username}, {@link #password} or {@link #privateKey} will take precedence.
132      * @see <a href="https://maven.apache.org/scm/authentication.html">Authentication</a>
133      * @since 2.2.0
134      */
135     @Parameter(property = "project.scm.id")
136     private String serverId;
137 
138     /**
139      * The url of tags base directory (used by svn protocol). It is not
140      * necessary to set it if you use the standard svn layout
141      * (branches/tags/trunk).
142      */
143     @Parameter(property = "tagBase")
144     private String tagBase;
145 
146     /**
147      * Comma separated list of includes file pattern.
148      */
149     @Parameter(property = "includes")
150     private String includes;
151 
152     /**
153      * Comma separated list of excludes file pattern.
154      */
155     @Parameter(property = "excludes")
156     private String excludes;
157 
158     /**
159      * The base directory.
160      */
161     @Parameter(property = "basedir", required = true)
162     private File basedir;
163 
164     @Parameter(defaultValue = "${settings}", readonly = true)
165     private Settings settings;
166 
167     /**
168      * List of System properties to set before executing the SCM command.
169      */
170     @Parameter
171     private Properties systemProperties;
172 
173     /**
174      * List of remapped provider implementations. Allows to bind a different implementation than the default one to a provider id.
175      * The key is the remapped provider id, the value is the default provider id the implementation is bound to.
176      * @see <a href="https://maven.apache.org/scm/scms-overview.html">Supported SCMs</a>
177      */
178     @Parameter
179     private Map<String, String> providerImplementations;
180 
181     /**
182      * Should distributed changes be pushed to the central repository?
183      * For many distributed SCMs like Git, a change like a commit
184      * is only stored in your local copy of the repository.  Pushing
185      * the change allows your to more easily share it with other users.
186      *
187      * @since 1.4
188      */
189     @Parameter(property = "pushChanges", defaultValue = "true")
190     private boolean pushChanges;
191 
192     /**
193      * A workItem for SCMs like RTC, TFS etc, that may require additional
194      * information to perform a pushChange operation.
195      *
196      * @since 1.9.5
197      */
198     @Parameter(property = "workItem")
199     @Deprecated
200     private String workItem;
201 
202     private final ScmManager manager;
203 
204     private final SettingsDecrypter settingsDecrypter;
205 
206     protected AbstractScmMojo(ScmManager manager, SettingsDecrypter settingsDecrypter) {
207         this.manager = manager;
208         this.settingsDecrypter = settingsDecrypter;
209     }
210 
211     /** {@inheritDoc} */
212     public void execute() throws MojoExecutionException {
213         if (systemProperties != null) {
214             // Add all system properties configured by the user
215             Iterator<Object> iter = systemProperties.keySet().iterator();
216 
217             while (iter.hasNext()) {
218                 String key = (String) iter.next();
219 
220                 String value = systemProperties.getProperty(key);
221 
222                 System.setProperty(key, value);
223             }
224         }
225 
226         if (providerImplementations != null && !providerImplementations.isEmpty()) {
227             for (Entry<String, String> entry : providerImplementations.entrySet()) {
228                 String providerType = entry.getKey();
229                 String providerImplementation = entry.getValue();
230                 getLog().info("Change the default '" + providerType + "' provider implementation to '"
231                         + providerImplementation + "'.");
232                 getScmManager().setScmProviderImplementation(providerType, providerImplementation);
233             }
234         }
235     }
236 
237     protected void setConnectionType(String connectionType) {
238         this.connectionType = connectionType;
239     }
240 
241     public String getConnectionUrl() {
242         boolean requireDeveloperConnection = !"connection".equals(connectionType.toLowerCase());
243         if ((connectionUrl != null && !connectionUrl.isEmpty()) && !requireDeveloperConnection) {
244             return connectionUrl;
245         } else if (developerConnectionUrl != null && !developerConnectionUrl.isEmpty()) {
246             return developerConnectionUrl;
247         }
248         if (requireDeveloperConnection) {
249             throw new NullPointerException("You need to define a developerConnectionUrl parameter");
250         } else {
251             throw new NullPointerException("You need to define a connectionUrl parameter");
252         }
253     }
254 
255     public void setConnectionUrl(String connectionUrl) {
256         this.connectionUrl = connectionUrl;
257     }
258 
259     public File getWorkingDirectory() {
260         if (workingDirectory == null) {
261             return basedir;
262         }
263 
264         return workingDirectory;
265     }
266 
267     public File getBasedir() {
268         return this.basedir;
269     }
270 
271     public void setWorkingDirectory(File workingDirectory) {
272         this.workingDirectory = workingDirectory;
273     }
274 
275     public ScmManager getScmManager() {
276         return manager;
277     }
278 
279     public ScmFileSet getFileSet() throws IOException {
280         if (includes != null || excludes != null) {
281             return new ScmFileSet(getWorkingDirectory(), includes, excludes);
282         } else {
283             return new ScmFileSet(getWorkingDirectory());
284         }
285     }
286 
287     public ScmRepository getScmRepository() throws ScmException {
288         ScmRepository repository;
289 
290         try {
291             repository = getScmManager().makeScmRepository(getConnectionUrl());
292 
293             ScmProviderRepository providerRepo = repository.getProviderRepository();
294 
295             providerRepo.setPushChanges(pushChanges);
296 
297             if (!(workItem == null || workItem.isEmpty())) {
298                 providerRepo.setWorkItem(workItem);
299             }
300 
301             if (!(username == null || username.isEmpty())) {
302                 providerRepo.setUser(username);
303             }
304 
305             if (!(password == null || password.isEmpty())) {
306                 providerRepo.setPassword(password);
307             }
308 
309             if (repository.getProviderRepository() instanceof ScmProviderRepositoryWithHost) {
310                 ScmProviderRepositoryWithHost repo = (ScmProviderRepositoryWithHost) repository.getProviderRepository();
311 
312                 loadInfosFromSettings(repo);
313 
314                 if (!(username == null || username.isEmpty())) {
315                     repo.setUser(username);
316                 }
317 
318                 if (!(password == null || password.isEmpty())) {
319                     repo.setPassword(password);
320                 }
321 
322                 if (!(privateKey == null || privateKey.isEmpty())) {
323                     repo.setPrivateKey(privateKey);
324                 }
325 
326                 if (!(passphrase == null || passphrase.isEmpty())) {
327                     repo.setPassphrase(passphrase);
328                 }
329             }
330 
331             if (!(tagBase == null || tagBase.isEmpty())
332                     && repository.getProvider().equals("svn")) {
333                 SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) repository.getProviderRepository();
334 
335                 svnRepo.setTagBase(tagBase);
336             }
337         } catch (ScmRepositoryException e) {
338             if (!e.getValidationMessages().isEmpty()) {
339                 for (String message : e.getValidationMessages()) {
340                     getLog().error(message);
341                 }
342             }
343 
344             throw new ScmException("Can't load the scm provider.", e);
345         } catch (Exception e) {
346             throw new ScmException("Can't load the scm provider.", e);
347         }
348 
349         return repository;
350     }
351 
352     /**
353      * Load username password from settings if user has not set them in JVM properties
354      *
355      * @param repo not null
356      */
357     private void loadInfosFromSettings(ScmProviderRepositoryWithHost repo) {
358         if (username == null || password == null) {
359             String serverId = this.serverId;
360             if (serverId == null || serverId.isEmpty()) {
361                 // construct server id from scm repository host and port
362                 serverId = repo.getHost();
363                 int port = repo.getPort();
364                 if (port > 0) {
365                     serverId += ":" + port;
366                 }
367             }
368 
369             Server server = this.settings.getServer(serverId);
370 
371             if (server != null) {
372                 server = decrypt(server);
373 
374                 if (username == null) {
375                     username = server.getUsername();
376                 }
377 
378                 if (password == null) {
379                     password = server.getPassword();
380                 }
381 
382                 if (privateKey == null) {
383                     privateKey = server.getPrivateKey();
384                 }
385 
386                 if (passphrase == null) {
387                     passphrase = server.getPassphrase();
388                 }
389             }
390         }
391     }
392 
393     private Server decrypt(Server server) {
394         SettingsDecryptionResult result = settingsDecrypter.decrypt(new DefaultSettingsDecryptionRequest(server));
395         for (SettingsProblem problem : result.getProblems()) {
396             getLog().error(problem.getMessage(), problem.getException());
397         }
398 
399         return result.getServer();
400     }
401 
402     public void checkResult(ScmResult result) throws MojoExecutionException {
403         if (!result.isSuccess()) {
404             getLog().error("Provider message:");
405 
406             getLog().error(result.getProviderMessage() == null ? "" : result.getProviderMessage());
407 
408             getLog().error("Command output:");
409 
410             getLog().error(result.getCommandOutput() == null ? "" : result.getCommandOutput());
411 
412             throw new MojoExecutionException("Command failed: " + Objects.toString(result.getProviderMessage()));
413         }
414     }
415 
416     public String getIncludes() {
417         return includes;
418     }
419 
420     public void setIncludes(String includes) {
421         this.includes = includes;
422     }
423 
424     public String getExcludes() {
425         return excludes;
426     }
427 
428     public void setExcludes(String excludes) {
429         this.excludes = excludes;
430     }
431 
432     public ScmVersion getScmVersion(String versionType, String version) throws MojoExecutionException {
433         if ((versionType == null || versionType.isEmpty()) && (version != null && !version.isEmpty())) {
434             throw new MojoExecutionException("You must specify the version type.");
435         }
436 
437         if (version == null || version.isEmpty()) {
438             return null;
439         }
440 
441         if (VERSION_TYPE_BRANCH.equals(versionType)) {
442             return new ScmBranch(version);
443         }
444 
445         if (VERSION_TYPE_TAG.equals(versionType)) {
446             return new ScmTag(version);
447         }
448 
449         if (VERSION_TYPE_REVISION.equals(versionType)) {
450             return new ScmRevision(version);
451         }
452 
453         throw new MojoExecutionException("Unknown '" + versionType + "' version type.");
454     }
455 
456     protected void handleExcludesIncludesAfterCheckoutAndExport(File checkoutDirectory) throws MojoExecutionException {
457         List<String> includes = new ArrayList<>();
458 
459         if (!StringUtils.isBlank(this.getIncludes())) {
460             String[] tokens = StringUtils.split(this.getIncludes(), ",");
461             for (int i = 0; i < tokens.length; ++i) {
462                 includes.add(tokens[i]);
463             }
464         }
465 
466         List<String> excludes = new ArrayList<>();
467 
468         if (!StringUtils.isBlank(this.getExcludes())) {
469             String[] tokens = StringUtils.split(this.getExcludes(), ",");
470             for (int i = 0; i < tokens.length; ++i) {
471                 excludes.add(tokens[i]);
472             }
473         }
474 
475         if (includes.isEmpty() && excludes.isEmpty()) {
476             return;
477         }
478 
479         FileSetManager fileSetManager = new FileSetManager();
480 
481         FileSet fileset = new FileSet();
482         fileset.setDirectory(checkoutDirectory.getAbsolutePath());
483         fileset.setIncludes(excludes); // revert the order to do the delete
484         fileset.setExcludes(includes);
485         fileset.setUseDefaultExcludes(false);
486 
487         try {
488             fileSetManager.delete(fileset);
489         } catch (IOException e) {
490             throw new MojoExecutionException(
491                     "Error found while cleaning up output directory base on " + "excludes/includes configurations.", e);
492         }
493     }
494 }