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