001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.maven.scm.plugin;
020
021import java.io.File;
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Map;
027import java.util.Map.Entry;
028import java.util.Objects;
029import java.util.Properties;
030
031import org.apache.commons.lang3.StringUtils;
032import org.apache.maven.plugin.AbstractMojo;
033import org.apache.maven.plugin.MojoExecutionException;
034import org.apache.maven.plugins.annotations.Parameter;
035import org.apache.maven.scm.ScmBranch;
036import org.apache.maven.scm.ScmException;
037import org.apache.maven.scm.ScmFileSet;
038import org.apache.maven.scm.ScmResult;
039import org.apache.maven.scm.ScmRevision;
040import org.apache.maven.scm.ScmTag;
041import org.apache.maven.scm.ScmVersion;
042import org.apache.maven.scm.manager.ScmManager;
043import org.apache.maven.scm.provider.ScmProviderRepository;
044import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost;
045import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository;
046import org.apache.maven.scm.repository.ScmRepository;
047import org.apache.maven.scm.repository.ScmRepositoryException;
048import org.apache.maven.settings.Server;
049import org.apache.maven.settings.Settings;
050import org.apache.maven.settings.building.SettingsProblem;
051import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
052import org.apache.maven.settings.crypto.SettingsDecrypter;
053import org.apache.maven.settings.crypto.SettingsDecryptionResult;
054import org.apache.maven.shared.model.fileset.FileSet;
055import org.apache.maven.shared.model.fileset.util.FileSetManager;
056
057/**
058 * @author <a href="evenisse@apache.org">Emmanuel Venisse</a>
059 * @author Olivier Lamy
060 */
061public abstract class AbstractScmMojo extends AbstractMojo {
062
063    protected static final String VERSION_TYPE_BRANCH = "branch";
064
065    protected static final String VERSION_TYPE_REVISION = "revision";
066
067    protected static final String VERSION_TYPE_TAG = "tag";
068
069    protected static final String[] VALID_VERSION_TYPES = {VERSION_TYPE_BRANCH, VERSION_TYPE_REVISION, VERSION_TYPE_TAG
070    };
071
072    /**
073     * The SCM connection URL.
074     */
075    @Parameter(property = "connectionUrl", defaultValue = "${project.scm.connection}")
076    private String connectionUrl;
077
078    /**
079     * The SCM connection URL for developers.
080     */
081    @Parameter(property = "developerConnectionUrl", defaultValue = "${project.scm.developerConnection}")
082    private String developerConnectionUrl;
083
084    /**
085     * The type of connection to use (connection or developerConnection).
086     */
087    @Parameter(property = "connectionType", defaultValue = "connection")
088    private String connectionType;
089
090    /**
091     * The working directory.
092     */
093    @Parameter(property = "workingDirectory")
094    private File workingDirectory;
095
096    /**
097     * The user name.
098     *
099     * @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}