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     * @see <a href="https://maven.apache.org/scm/authentication.html">Authentication</a>
099     */
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}