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