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