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.manager;
020
021import java.io.File;
022import java.util.ArrayList;
023import java.util.Date;
024import java.util.List;
025import java.util.Map;
026import java.util.concurrent.ConcurrentHashMap;
027
028import org.apache.maven.scm.CommandParameters;
029import org.apache.maven.scm.ScmBranch;
030import org.apache.maven.scm.ScmBranchParameters;
031import org.apache.maven.scm.ScmException;
032import org.apache.maven.scm.ScmFileSet;
033import org.apache.maven.scm.ScmTagParameters;
034import org.apache.maven.scm.ScmVersion;
035import org.apache.maven.scm.command.add.AddScmResult;
036import org.apache.maven.scm.command.blame.BlameScmRequest;
037import org.apache.maven.scm.command.blame.BlameScmResult;
038import org.apache.maven.scm.command.branch.BranchScmResult;
039import org.apache.maven.scm.command.changelog.ChangeLogScmRequest;
040import org.apache.maven.scm.command.changelog.ChangeLogScmResult;
041import org.apache.maven.scm.command.checkin.CheckInScmResult;
042import org.apache.maven.scm.command.checkout.CheckOutScmResult;
043import org.apache.maven.scm.command.diff.DiffScmResult;
044import org.apache.maven.scm.command.edit.EditScmResult;
045import org.apache.maven.scm.command.export.ExportScmResult;
046import org.apache.maven.scm.command.list.ListScmResult;
047import org.apache.maven.scm.command.mkdir.MkdirScmResult;
048import org.apache.maven.scm.command.remove.RemoveScmResult;
049import org.apache.maven.scm.command.status.StatusScmResult;
050import org.apache.maven.scm.command.tag.TagScmResult;
051import org.apache.maven.scm.command.unedit.UnEditScmResult;
052import org.apache.maven.scm.command.update.UpdateScmResult;
053import org.apache.maven.scm.provider.ScmProvider;
054import org.apache.maven.scm.provider.ScmProviderRepository;
055import org.apache.maven.scm.provider.ScmUrlUtils;
056import org.apache.maven.scm.repository.ScmRepository;
057import org.apache.maven.scm.repository.ScmRepositoryException;
058import org.apache.maven.scm.repository.UnknownRepositoryStructure;
059import org.slf4j.Logger;
060import org.slf4j.LoggerFactory;
061
062import static java.util.Objects.requireNonNull;
063
064/**
065 * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
066 * @author <a href="mailto:brett@apache.org">Brett Porter</a>
067 * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
068 */
069public abstract class AbstractScmManager implements ScmManager {
070    protected final Logger logger = LoggerFactory.getLogger(getClass());
071
072    private final Map<String, ScmProvider> scmProviders = new ConcurrentHashMap<>();
073
074    private final Map<String, String> userProviderTypes = new ConcurrentHashMap<>();
075
076    protected void setScmProviders(Map<String, ScmProvider> providers) {
077        requireNonNull(providers);
078        this.scmProviders.clear();
079        // first provider must not be overwritten by subsequent ones if they are registered for the same key
080        providers.forEach(this.scmProviders::putIfAbsent);
081    }
082
083    /**
084     * @param providerType the type of SCM, eg. <code>svn</code>, <code>git</code>
085     * @param provider     the provider that will be used for that SCM type
086     * @deprecated use {@link #setScmProvider(String, ScmProvider)} instead
087     */
088    @Deprecated
089    protected void addScmProvider(String providerType, ScmProvider provider) {
090        setScmProvider(providerType, provider);
091    }
092
093    /**
094     * Set a provider to be used for a type of SCM.
095     * If there was already a designed provider for that type it will be replaced.
096     *
097     * @param providerType the type of SCM, eg. <code>svn</code>, <code>git</code>
098     * @param provider     the provider that will be used for that SCM type
099     */
100    @Override
101    public void setScmProvider(String providerType, ScmProvider provider) {
102        requireNonNull(providerType);
103        requireNonNull(provider);
104        scmProviders.put(providerType, provider);
105    }
106
107    // ----------------------------------------------------------------------
108    // ScmManager Implementation
109    // ----------------------------------------------------------------------
110
111    /**
112     * {@inheritDoc}
113     */
114    @Override
115    public ScmProvider getProviderByUrl(String scmUrl) throws ScmRepositoryException, NoSuchScmProviderException {
116        requireNonNull(scmUrl, "The scm url cannot be null.");
117
118        String providerType = ScmUrlUtils.getProvider(scmUrl);
119
120        return getProviderByType(providerType);
121    }
122
123    /**
124     * {@inheritDoc}
125     */
126    @Override
127    public void setScmProviderImplementation(String providerType, String providerImplementation) {
128        requireNonNull(providerType);
129        requireNonNull(providerImplementation);
130        userProviderTypes.put(providerType, providerImplementation);
131    }
132
133    /**
134     * {@inheritDoc}
135     */
136    @Override
137    public ScmProvider getProviderByType(String providerType) throws NoSuchScmProviderException {
138        String usedProviderType = System.getProperty("maven.scm.provider." + providerType + ".implementation");
139
140        if (usedProviderType == null) {
141            usedProviderType = userProviderTypes.getOrDefault(providerType, providerType);
142        }
143
144        ScmProvider scmProvider = scmProviders.get(usedProviderType);
145
146        if (scmProvider == null) {
147            throw new NoSuchScmProviderException(usedProviderType);
148        }
149
150        return scmProvider;
151    }
152
153    /**
154     * {@inheritDoc}
155     */
156    @Override
157    public ScmProvider getProviderByRepository(ScmRepository repository) throws NoSuchScmProviderException {
158        return getProviderByType(repository.getProvider());
159    }
160
161    // ----------------------------------------------------------------------
162    // Repository
163    // ----------------------------------------------------------------------
164
165    /**
166     * {@inheritDoc}
167     */
168    @Override
169    public ScmRepository makeScmRepository(String scmUrl) throws ScmRepositoryException, NoSuchScmProviderException {
170        requireNonNull(scmUrl, "The scm url cannot be null.");
171
172        char delimiter = ScmUrlUtils.getDelimiter(scmUrl).charAt(0);
173
174        String providerType = ScmUrlUtils.getProvider(scmUrl);
175
176        ScmProvider provider = getProviderByType(providerType);
177
178        String scmSpecificUrl = cleanScmUrl(scmUrl.substring(providerType.length() + 5));
179
180        ScmProviderRepository providerRepository = provider.makeProviderScmRepository(scmSpecificUrl, delimiter);
181
182        return new ScmRepository(providerType, providerRepository);
183    }
184
185    /**
186     * Clean the SCM url by removing all ../ in path
187     *
188     * @param scmUrl the SCM url
189     * @return the cleaned SCM url
190     */
191    protected String cleanScmUrl(String scmUrl) {
192        requireNonNull(scmUrl, "The scm url cannot be null.");
193
194        String pathSeparator = "";
195
196        int indexOfDoubleDot = -1;
197
198        // Clean Unix path
199        if (scmUrl.indexOf("../") > 1) {
200            pathSeparator = "/";
201
202            indexOfDoubleDot = scmUrl.indexOf("../");
203        }
204
205        // Clean windows path
206        if (scmUrl.indexOf("..\\") > 1) {
207            pathSeparator = "\\";
208
209            indexOfDoubleDot = scmUrl.indexOf("..\\");
210        }
211
212        if (indexOfDoubleDot > 1) {
213            int startOfTextToRemove = scmUrl.substring(0, indexOfDoubleDot - 1).lastIndexOf(pathSeparator);
214
215            String beginUrl = "";
216            if (startOfTextToRemove >= 0) {
217                beginUrl = scmUrl.substring(0, startOfTextToRemove);
218            }
219
220            String endUrl = scmUrl.substring(indexOfDoubleDot + 3);
221
222            scmUrl = beginUrl + pathSeparator + endUrl;
223
224            // Check if we have other double dot
225            if (scmUrl.indexOf("../") > 1 || scmUrl.indexOf("..\\") > 1) {
226                scmUrl = cleanScmUrl(scmUrl);
227            }
228        }
229
230        return scmUrl;
231    }
232
233    /**
234     * {@inheritDoc}
235     */
236    @Override
237    public ScmRepository makeProviderScmRepository(String providerType, File path)
238            throws ScmRepositoryException, UnknownRepositoryStructure, NoSuchScmProviderException {
239        requireNonNull(providerType, "The provider type cannot be null.");
240
241        ScmProvider provider = getProviderByType(providerType);
242
243        ScmProviderRepository providerRepository = provider.makeProviderScmRepository(path);
244
245        return new ScmRepository(providerType, providerRepository);
246    }
247
248    /**
249     * {@inheritDoc}
250     */
251    @Override
252    public List<String> validateScmRepository(String scmUrl) {
253        List<String> messages = new ArrayList<>(ScmUrlUtils.validate(scmUrl));
254
255        String providerType = ScmUrlUtils.getProvider(scmUrl);
256
257        ScmProvider provider;
258
259        try {
260            provider = getProviderByType(providerType);
261        } catch (NoSuchScmProviderException e) {
262            messages.add("No such provider installed '" + providerType + "'.");
263
264            return messages;
265        }
266
267        String scmSpecificUrl = cleanScmUrl(scmUrl.substring(providerType.length() + 5));
268
269        List<String> providerMessages = provider.validateScmUrl(
270                scmSpecificUrl, ScmUrlUtils.getDelimiter(scmUrl).charAt(0));
271
272        requireNonNull(providerMessages, "The SCM provider cannot return null from validateScmUrl().");
273
274        messages.addAll(providerMessages);
275
276        return messages;
277    }
278
279    /**
280     * {@inheritDoc}
281     */
282    @Override
283    public AddScmResult add(ScmRepository repository, ScmFileSet fileSet) throws ScmException {
284        return this.getProviderByRepository(repository).add(repository, fileSet);
285    }
286
287    /**
288     * {@inheritDoc}
289     */
290    @Override
291    public AddScmResult add(ScmRepository repository, ScmFileSet fileSet, String message) throws ScmException {
292        return this.getProviderByRepository(repository).add(repository, fileSet, message);
293    }
294
295    /**
296     * {@inheritDoc}
297     */
298    @Override
299    public BranchScmResult branch(ScmRepository repository, ScmFileSet fileSet, String branchName) throws ScmException {
300        ScmBranchParameters scmBranchParameters = new ScmBranchParameters("");
301        return this.getProviderByRepository(repository).branch(repository, fileSet, branchName, scmBranchParameters);
302    }
303
304    /**
305     * {@inheritDoc}
306     */
307    @Override
308    public BranchScmResult branch(ScmRepository repository, ScmFileSet fileSet, String branchName, String message)
309            throws ScmException {
310        ScmBranchParameters scmBranchParameters = new ScmBranchParameters(message);
311        return this.getProviderByRepository(repository).branch(repository, fileSet, branchName, scmBranchParameters);
312    }
313
314    /**
315     * {@inheritDoc}
316     */
317    @Override
318    public ChangeLogScmResult changeLog(
319            ScmRepository repository, ScmFileSet fileSet, Date startDate, Date endDate, int numDays, ScmBranch branch)
320            throws ScmException {
321        return this.getProviderByRepository(repository)
322                .changeLog(repository, fileSet, startDate, endDate, numDays, branch);
323    }
324
325    /**
326     * {@inheritDoc}
327     */
328    @Override
329    public ChangeLogScmResult changeLog(
330            ScmRepository repository,
331            ScmFileSet fileSet,
332            Date startDate,
333            Date endDate,
334            int numDays,
335            ScmBranch branch,
336            String datePattern)
337            throws ScmException {
338        return this.getProviderByRepository(repository)
339                .changeLog(repository, fileSet, startDate, endDate, numDays, branch, datePattern);
340    }
341
342    /**
343     * {@inheritDoc}
344     */
345    @Override
346    public ChangeLogScmResult changeLog(ChangeLogScmRequest scmRequest) throws ScmException {
347        return this.getProviderByRepository(scmRequest.getScmRepository()).changeLog(scmRequest);
348    }
349
350    /**
351     * {@inheritDoc}
352     */
353    @Override
354    public ChangeLogScmResult changeLog(
355            ScmRepository repository, ScmFileSet fileSet, ScmVersion startVersion, ScmVersion endVersion)
356            throws ScmException {
357        return this.getProviderByRepository(repository).changeLog(repository, fileSet, startVersion, endVersion);
358    }
359
360    /**
361     * {@inheritDoc}
362     */
363    @Override
364    public ChangeLogScmResult changeLog(
365            ScmRepository repository,
366            ScmFileSet fileSet,
367            ScmVersion startRevision,
368            ScmVersion endRevision,
369            String datePattern)
370            throws ScmException {
371        return this.getProviderByRepository(repository)
372                .changeLog(repository, fileSet, startRevision, endRevision, datePattern);
373    }
374
375    /**
376     * {@inheritDoc}
377     */
378    @Override
379    public CheckInScmResult checkIn(ScmRepository repository, ScmFileSet fileSet, String message) throws ScmException {
380        return this.getProviderByRepository(repository).checkIn(repository, fileSet, message);
381    }
382
383    /**
384     * {@inheritDoc}
385     */
386    @Override
387    public CheckInScmResult checkIn(ScmRepository repository, ScmFileSet fileSet, ScmVersion revision, String message)
388            throws ScmException {
389        return this.getProviderByRepository(repository).checkIn(repository, fileSet, revision, message);
390    }
391
392    @Override
393    public CheckInScmResult checkIn(ScmRepository repository, ScmFileSet fileSet, CommandParameters commandParameters)
394            throws ScmException {
395        return this.getProviderByRepository(repository).checkIn(repository, fileSet, commandParameters);
396    }
397
398    /**
399     * {@inheritDoc}
400     */
401    @Override
402    public CheckOutScmResult checkOut(ScmRepository repository, ScmFileSet fileSet) throws ScmException {
403        return this.getProviderByRepository(repository).checkOut(repository, fileSet);
404    }
405
406    /**
407     * {@inheritDoc}
408     */
409    @Override
410    public CheckOutScmResult checkOut(ScmRepository repository, ScmFileSet fileSet, ScmVersion version)
411            throws ScmException {
412        return this.getProviderByRepository(repository).checkOut(repository, fileSet, version);
413    }
414
415    /**
416     * {@inheritDoc}
417     */
418    @Override
419    public CheckOutScmResult checkOut(ScmRepository repository, ScmFileSet fileSet, boolean recursive)
420            throws ScmException {
421        return this.getProviderByRepository(repository).checkOut(repository, fileSet, recursive);
422    }
423
424    /**
425     * {@inheritDoc}
426     */
427    @Override
428    public CheckOutScmResult checkOut(
429            ScmRepository repository, ScmFileSet fileSet, ScmVersion version, boolean recursive) throws ScmException {
430        return this.getProviderByRepository(repository).checkOut(repository, fileSet, version, recursive);
431    }
432
433    /**
434     * {@inheritDoc}
435     */
436    @Override
437    public DiffScmResult diff(
438            ScmRepository repository, ScmFileSet fileSet, ScmVersion startVersion, ScmVersion endVersion)
439            throws ScmException {
440        return this.getProviderByRepository(repository).diff(repository, fileSet, startVersion, endVersion);
441    }
442
443    /**
444     * {@inheritDoc}
445     */
446    @Override
447    public EditScmResult edit(ScmRepository repository, ScmFileSet fileSet) throws ScmException {
448        return this.getProviderByRepository(repository).edit(repository, fileSet);
449    }
450
451    /**
452     * {@inheritDoc}
453     */
454    @Override
455    public ExportScmResult export(ScmRepository repository, ScmFileSet fileSet) throws ScmException {
456        return this.getProviderByRepository(repository).export(repository, fileSet);
457    }
458
459    /**
460     * {@inheritDoc}
461     */
462    @Override
463    public ExportScmResult export(ScmRepository repository, ScmFileSet fileSet, ScmVersion version)
464            throws ScmException {
465        return this.getProviderByRepository(repository).export(repository, fileSet, version);
466    }
467
468    /**
469     * {@inheritDoc}
470     */
471    @Override
472    public ExportScmResult export(ScmRepository repository, ScmFileSet fileSet, String outputDirectory)
473            throws ScmException {
474        return this.getProviderByRepository(repository).export(repository, fileSet, (ScmVersion) null, outputDirectory);
475    }
476
477    /**
478     * {@inheritDoc}
479     */
480    @Override
481    public ExportScmResult export(
482            ScmRepository repository, ScmFileSet fileSet, ScmVersion version, String outputDirectory)
483            throws ScmException {
484        return this.getProviderByRepository(repository).export(repository, fileSet, version, outputDirectory);
485    }
486
487    /**
488     * {@inheritDoc}
489     */
490    @Override
491    public ListScmResult list(ScmRepository repository, ScmFileSet fileSet, boolean recursive, ScmVersion version)
492            throws ScmException {
493        return this.getProviderByRepository(repository).list(repository, fileSet, recursive, version);
494    }
495
496    /**
497     * {@inheritDoc}
498     */
499    @Override
500    public MkdirScmResult mkdir(ScmRepository repository, ScmFileSet fileSet, String message, boolean createInLocal)
501            throws ScmException {
502        return this.getProviderByRepository(repository).mkdir(repository, fileSet, message, createInLocal);
503    }
504
505    /**
506     * {@inheritDoc}
507     */
508    @Override
509    public RemoveScmResult remove(ScmRepository repository, ScmFileSet fileSet, String message) throws ScmException {
510        return this.getProviderByRepository(repository).remove(repository, fileSet, message);
511    }
512
513    /**
514     * {@inheritDoc}
515     */
516    @Override
517    public StatusScmResult status(ScmRepository repository, ScmFileSet fileSet) throws ScmException {
518        return this.getProviderByRepository(repository).status(repository, fileSet);
519    }
520
521    /**
522     * {@inheritDoc}
523     */
524    @Override
525    public TagScmResult tag(ScmRepository repository, ScmFileSet fileSet, String tagName) throws ScmException {
526        return this.tag(repository, fileSet, tagName, "");
527    }
528
529    /**
530     * {@inheritDoc}
531     */
532    @Override
533    public TagScmResult tag(ScmRepository repository, ScmFileSet fileSet, String tagName, String message)
534            throws ScmException {
535        ScmTagParameters scmTagParameters = new ScmTagParameters(message);
536        return this.getProviderByRepository(repository).tag(repository, fileSet, tagName, scmTagParameters);
537    }
538
539    /**
540     * {@inheritDoc}
541     */
542    @Override
543    public UnEditScmResult unedit(ScmRepository repository, ScmFileSet fileSet) throws ScmException {
544        return this.getProviderByRepository(repository).unedit(repository, fileSet);
545    }
546
547    /**
548     * {@inheritDoc}
549     */
550    @Override
551    public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet) throws ScmException {
552        return this.getProviderByRepository(repository).update(repository, fileSet);
553    }
554
555    /**
556     * {@inheritDoc}
557     */
558    @Override
559    public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, ScmVersion version)
560            throws ScmException {
561        return this.getProviderByRepository(repository).update(repository, fileSet, version);
562    }
563
564    /**
565     * {@inheritDoc}
566     */
567    @Override
568    public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, boolean runChangelog)
569            throws ScmException {
570        return this.getProviderByRepository(repository).update(repository, fileSet, runChangelog);
571    }
572
573    /**
574     * {@inheritDoc}
575     */
576    @Override
577    public UpdateScmResult update(
578            ScmRepository repository, ScmFileSet fileSet, ScmVersion version, boolean runChangelog)
579            throws ScmException {
580        return this.getProviderByRepository(repository).update(repository, fileSet, version, runChangelog);
581    }
582
583    /**
584     * {@inheritDoc}
585     */
586    @Override
587    public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, String datePattern)
588            throws ScmException {
589        return this.getProviderByRepository(repository).update(repository, fileSet, (ScmVersion) null, datePattern);
590    }
591
592    /**
593     * {@inheritDoc}
594     */
595    @Override
596    public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, ScmVersion version, String datePattern)
597            throws ScmException {
598        return this.getProviderByRepository(repository).update(repository, fileSet, version, datePattern);
599    }
600
601    /**
602     * {@inheritDoc}
603     */
604    @Override
605    public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, Date lastUpdate) throws ScmException {
606        return this.getProviderByRepository(repository).update(repository, fileSet, (ScmVersion) null, lastUpdate);
607    }
608
609    /**
610     * {@inheritDoc}
611     */
612    @Override
613    public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, ScmVersion version, Date lastUpdate)
614            throws ScmException {
615        return this.getProviderByRepository(repository).update(repository, fileSet, version, lastUpdate);
616    }
617
618    /**
619     * {@inheritDoc}
620     */
621    @Override
622    public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, Date lastUpdate, String datePattern)
623            throws ScmException {
624        return this.getProviderByRepository(repository)
625                .update(repository, fileSet, (ScmVersion) null, lastUpdate, datePattern);
626    }
627
628    /**
629     * {@inheritDoc}
630     */
631    @Override
632    public UpdateScmResult update(
633            ScmRepository repository, ScmFileSet fileSet, ScmVersion version, Date lastUpdate, String datePattern)
634            throws ScmException {
635        return this.getProviderByRepository(repository).update(repository, fileSet, version, lastUpdate, datePattern);
636    }
637
638    /**
639     * {@inheritDoc}
640     */
641    @Override
642    public BlameScmResult blame(ScmRepository repository, ScmFileSet fileSet, String filename) throws ScmException {
643        return this.getProviderByRepository(repository).blame(repository, fileSet, filename);
644    }
645
646    @Override
647    public BlameScmResult blame(BlameScmRequest blameScmRequest) throws ScmException {
648        return this.getProviderByRepository(blameScmRequest.getScmRepository()).blame(blameScmRequest);
649    }
650}