001package org.eclipse.aether.repository;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 * 
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 * 
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.Collections;
025import java.util.List;
026import static java.util.Objects.requireNonNull;
027
028import java.util.Objects;
029import java.util.regex.Matcher;
030import java.util.regex.Pattern;
031
032/**
033 * A repository on a remote server.
034 */
035public final class RemoteRepository
036    implements ArtifactRepository
037{
038
039    private static final Pattern URL_PATTERN =
040        Pattern.compile( "([^:/]+(:[^:/]{2,}+(?=://))?):(//([^@/]*@)?([^/:]+))?.*" );
041
042    private final String id;
043
044    private final String type;
045
046    private final String url;
047
048    private final String host;
049
050    private final String protocol;
051
052    private final RepositoryPolicy releasePolicy;
053
054    private final RepositoryPolicy snapshotPolicy;
055
056    private final Proxy proxy;
057
058    private final Authentication authentication;
059
060    private final List<RemoteRepository> mirroredRepositories;
061
062    private final boolean repositoryManager;
063
064    private final boolean blocked;
065
066    RemoteRepository( Builder builder )
067    {
068        if ( builder.prototype != null )
069        {
070            id = ( builder.delta & Builder.ID ) != 0 ? builder.id : builder.prototype.id;
071            type = ( builder.delta & Builder.TYPE ) != 0 ? builder.type : builder.prototype.type;
072            url = ( builder.delta & Builder.URL ) != 0 ? builder.url : builder.prototype.url;
073            releasePolicy =
074                ( builder.delta & Builder.RELEASES ) != 0 ? builder.releasePolicy : builder.prototype.releasePolicy;
075            snapshotPolicy =
076                ( builder.delta & Builder.SNAPSHOTS ) != 0 ? builder.snapshotPolicy : builder.prototype.snapshotPolicy;
077            proxy = ( builder.delta & Builder.PROXY ) != 0 ? builder.proxy : builder.prototype.proxy;
078            authentication =
079                ( builder.delta & Builder.AUTH ) != 0 ? builder.authentication : builder.prototype.authentication;
080            repositoryManager =
081                ( builder.delta & Builder.REPOMAN ) != 0 ? builder.repositoryManager
082                                : builder.prototype.repositoryManager;
083            blocked = ( builder.delta & Builder.BLOCKED ) != 0 ? builder.blocked : builder.prototype.blocked;
084            mirroredRepositories =
085                ( builder.delta & Builder.MIRRORED ) != 0 ? copy( builder.mirroredRepositories )
086                                : builder.prototype.mirroredRepositories;
087        }
088        else
089        {
090            id = builder.id;
091            type = builder.type;
092            url = builder.url;
093            releasePolicy = builder.releasePolicy;
094            snapshotPolicy = builder.snapshotPolicy;
095            proxy = builder.proxy;
096            authentication = builder.authentication;
097            repositoryManager = builder.repositoryManager;
098            blocked = builder.blocked;
099            mirroredRepositories = copy( builder.mirroredRepositories );
100        }
101
102        Matcher m = URL_PATTERN.matcher( url );
103        if ( m.matches() )
104        {
105            protocol = m.group( 1 );
106            String host = m.group( 5 );
107            this.host = ( host != null ) ? host : "";
108        }
109        else
110        {
111            protocol = "";
112            host = "";
113        }
114    }
115
116    private static List<RemoteRepository> copy( List<RemoteRepository> repos )
117    {
118        if ( repos == null || repos.isEmpty() )
119        {
120            return Collections.emptyList();
121        }
122        return Collections.unmodifiableList( Arrays.asList( repos.toArray( new RemoteRepository[repos.size()] ) ) );
123    }
124
125    public String getId()
126    {
127        return id;
128    }
129
130    public String getContentType()
131    {
132        return type;
133    }
134
135    /**
136     * Gets the (base) URL of this repository.
137     * 
138     * @return The (base) URL of this repository, never {@code null}.
139     */
140    public String getUrl()
141    {
142        return url;
143    }
144
145    /**
146     * Gets the protocol part from the repository's URL, for example {@code file} or {@code http}. As suggested by RFC
147     * 2396, section 3.1 "Scheme Component", the protocol name should be treated case-insensitively.
148     * 
149     * @return The protocol or an empty string if none, never {@code null}.
150     */
151    public String getProtocol()
152    {
153        return protocol;
154    }
155
156    /**
157     * Gets the host part from the repository's URL.
158     * 
159     * @return The host or an empty string if none, never {@code null}.
160     */
161    public String getHost()
162    {
163        return host;
164    }
165
166    /**
167     * Gets the policy to apply for snapshot/release artifacts.
168     * 
169     * @param snapshot {@code true} to retrieve the snapshot policy, {@code false} to retrieve the release policy.
170     * @return The requested repository policy, never {@code null}.
171     */
172    public RepositoryPolicy getPolicy( boolean snapshot )
173    {
174        return snapshot ? snapshotPolicy : releasePolicy;
175    }
176
177    /**
178     * Gets the proxy that has been selected for this repository.
179     * 
180     * @return The selected proxy or {@code null} if none.
181     */
182    public Proxy getProxy()
183    {
184        return proxy;
185    }
186
187    /**
188     * Gets the authentication that has been selected for this repository.
189     * 
190     * @return The selected authentication or {@code null} if none.
191     */
192    public Authentication getAuthentication()
193    {
194        return authentication;
195    }
196
197    /**
198     * Gets the repositories that this repository serves as a mirror for.
199     * 
200     * @return The (read-only) repositories being mirrored by this repository, never {@code null}.
201     */
202    public List<RemoteRepository> getMirroredRepositories()
203    {
204        return mirroredRepositories;
205    }
206
207    /**
208     * Indicates whether this repository refers to a repository manager or not.
209     * 
210     * @return {@code true} if this repository is a repository manager, {@code false} otherwise.
211     */
212    public boolean isRepositoryManager()
213    {
214        return repositoryManager;
215    }
216
217    /**
218     * Indicates whether this repository is blocked from performing any download requests.
219     * 
220     * @return {@code true} if this repository is blocked from performing any download requests,
221     *         {@code false} otherwise.
222     */
223    public boolean isBlocked()
224    {
225        return blocked;
226    }
227
228    @Override
229    public String toString()
230    {
231        StringBuilder buffer = new StringBuilder( 256 );
232        buffer.append( getId() );
233        buffer.append( " (" ).append( getUrl() );
234        buffer.append( ", " ).append( getContentType() );
235        boolean r = getPolicy( false ).isEnabled(), s = getPolicy( true ).isEnabled();
236        if ( r && s )
237        {
238            buffer.append( ", releases+snapshots" );
239        }
240        else if ( r )
241        {
242            buffer.append( ", releases" );
243        }
244        else if ( s )
245        {
246            buffer.append( ", snapshots" );
247        }
248        else
249        {
250            buffer.append( ", disabled" );
251        }
252        if ( isRepositoryManager() )
253        {
254            buffer.append( ", managed" );
255        }
256        if ( isBlocked() )
257        {
258            buffer.append( ", blocked" );
259        }
260        buffer.append( ")" );
261        return buffer.toString();
262    }
263
264    @Override
265    public boolean equals( Object obj )
266    {
267        if ( this == obj )
268        {
269            return true;
270        }
271        if ( obj == null || !getClass().equals( obj.getClass() ) )
272        {
273            return false;
274        }
275
276        RemoteRepository that = (RemoteRepository) obj;
277
278        return Objects.equals( url, that.url ) && Objects.equals( type, that.type )
279                && Objects.equals( id, that.id ) && Objects.equals( releasePolicy, that.releasePolicy )
280                && Objects.equals( snapshotPolicy, that.snapshotPolicy ) && Objects.equals( proxy, that.proxy )
281                && Objects.equals( authentication, that.authentication )
282                && Objects.equals( mirroredRepositories, that.mirroredRepositories )
283                && repositoryManager == that.repositoryManager;
284    }
285
286    @Override
287    public int hashCode()
288    {
289        int hash = 17;
290        hash = hash * 31 + hash( url );
291        hash = hash * 31 + hash( type );
292        hash = hash * 31 + hash( id );
293        hash = hash * 31 + hash( releasePolicy );
294        hash = hash * 31 + hash( snapshotPolicy );
295        hash = hash * 31 + hash( proxy );
296        hash = hash * 31 + hash( authentication );
297        hash = hash * 31 + hash( mirroredRepositories );
298        hash = hash * 31 + ( repositoryManager ? 1 : 0 );
299        return hash;
300    }
301
302    private static int hash( Object obj )
303    {
304        return obj != null ? obj.hashCode() : 0;
305    }
306
307    /**
308     * A builder to create remote repositories.
309     */
310    public static final class Builder
311    {
312
313        private static final RepositoryPolicy DEFAULT_POLICY = new RepositoryPolicy();
314
315        static final int ID = 0x0001, TYPE = 0x0002, URL = 0x0004, RELEASES = 0x0008, SNAPSHOTS = 0x0010,
316                        PROXY = 0x0020, AUTH = 0x0040, MIRRORED = 0x0080, REPOMAN = 0x0100, BLOCKED = 0x0200;
317
318        int delta;
319
320        RemoteRepository prototype;
321
322        String id;
323
324        String type;
325
326        String url;
327
328        RepositoryPolicy releasePolicy = DEFAULT_POLICY;
329
330        RepositoryPolicy snapshotPolicy = DEFAULT_POLICY;
331
332        Proxy proxy;
333
334        Authentication authentication;
335
336        List<RemoteRepository> mirroredRepositories;
337
338        boolean repositoryManager;
339
340        boolean blocked;
341
342        /**
343         * Creates a new repository builder.
344         * 
345         * @param id The identifier of the repository, may be {@code null}.
346         * @param type The type of the repository, may be {@code null}.
347         * @param url The (base) URL of the repository, may be {@code null}.
348         */
349        public Builder( String id, String type, String url )
350        {
351            this.id = ( id != null ) ? id : "";
352            this.type = ( type != null ) ? type : "";
353            this.url = ( url != null ) ? url : "";
354        }
355
356        /**
357         * Creates a new repository builder which uses the specified remote repository as a prototype for the new one.
358         * All properties which have not been set on the builder will be copied from the prototype when building the
359         * repository.
360         *
361         * @param prototype The remote repository to use as prototype, must not be {@code null}.
362         */
363        public Builder( RemoteRepository prototype )
364        {
365            this.prototype = requireNonNull( prototype, "remote repository prototype cannot be null" );
366        }
367
368        /**
369         * Builds a new remote repository from the current values of this builder. The state of the builder itself
370         * remains unchanged.
371         *
372         * @return The remote repository, never {@code null}.
373         */
374        public RemoteRepository build()
375        {
376            if ( prototype != null && delta == 0 )
377            {
378                return prototype;
379            }
380            return new RemoteRepository( this );
381        }
382
383        private <T> void delta( int flag, T builder, T prototype )
384        {
385            boolean equal = Objects.equals( builder, prototype );
386            if ( equal )
387            {
388                delta &= ~flag;
389            }
390            else
391            {
392                delta |= flag;
393            }
394        }
395
396        /**
397         * Sets the identifier of the repository.
398         * 
399         * @param id The identifier of the repository, may be {@code null}.
400         * @return This builder for chaining, never {@code null}.
401         */
402        public Builder setId( String id )
403        {
404            this.id = ( id != null ) ? id : "";
405            if ( prototype != null )
406            {
407                delta( ID, this.id, prototype.getId() );
408            }
409            return this;
410        }
411
412        /**
413         * Sets the type of the repository, e.g. "default".
414         * 
415         * @param type The type of the repository, may be {@code null}.
416         * @return This builder for chaining, never {@code null}.
417         */
418        public Builder setContentType( String type )
419        {
420            this.type = ( type != null ) ? type : "";
421            if ( prototype != null )
422            {
423                delta( TYPE, this.type, prototype.getContentType() );
424            }
425            return this;
426        }
427
428        /**
429         * Sets the (base) URL of the repository.
430         * 
431         * @param url The URL of the repository, may be {@code null}.
432         * @return This builder for chaining, never {@code null}.
433         */
434        public Builder setUrl( String url )
435        {
436            this.url = ( url != null ) ? url : "";
437            if ( prototype != null )
438            {
439                delta( URL, this.url, prototype.getUrl() );
440            }
441            return this;
442        }
443
444        /**
445         * Sets the policy to apply for snapshot and release artifacts.
446         * 
447         * @param policy The repository policy to set, may be {@code null} to use a default policy.
448         * @return This builder for chaining, never {@code null}.
449         */
450        public Builder setPolicy( RepositoryPolicy policy )
451        {
452            this.releasePolicy = ( policy != null ) ? policy : DEFAULT_POLICY;
453            this.snapshotPolicy = ( policy != null ) ? policy : DEFAULT_POLICY;
454            if ( prototype != null )
455            {
456                delta( RELEASES, this.releasePolicy, prototype.getPolicy( false ) );
457                delta( SNAPSHOTS, this.snapshotPolicy, prototype.getPolicy( true ) );
458            }
459            return this;
460        }
461
462        /**
463         * Sets the policy to apply for release artifacts.
464         * 
465         * @param releasePolicy The repository policy to set, may be {@code null} to use a default policy.
466         * @return This builder for chaining, never {@code null}.
467         */
468        public Builder setReleasePolicy( RepositoryPolicy releasePolicy )
469        {
470            this.releasePolicy = ( releasePolicy != null ) ? releasePolicy : DEFAULT_POLICY;
471            if ( prototype != null )
472            {
473                delta( RELEASES, this.releasePolicy, prototype.getPolicy( false ) );
474            }
475            return this;
476        }
477
478        /**
479         * Sets the policy to apply for snapshot artifacts.
480         * 
481         * @param snapshotPolicy The repository policy to set, may be {@code null} to use a default policy.
482         * @return This builder for chaining, never {@code null}.
483         */
484        public Builder setSnapshotPolicy( RepositoryPolicy snapshotPolicy )
485        {
486            this.snapshotPolicy = ( snapshotPolicy != null ) ? snapshotPolicy : DEFAULT_POLICY;
487            if ( prototype != null )
488            {
489                delta( SNAPSHOTS, this.snapshotPolicy, prototype.getPolicy( true ) );
490            }
491            return this;
492        }
493
494        /**
495         * Sets the proxy to use in order to access the repository.
496         * 
497         * @param proxy The proxy to use, may be {@code null}.
498         * @return This builder for chaining, never {@code null}.
499         */
500        public Builder setProxy( Proxy proxy )
501        {
502            this.proxy = proxy;
503            if ( prototype != null )
504            {
505                delta( PROXY, this.proxy, prototype.getProxy() );
506            }
507            return this;
508        }
509
510        /**
511         * Sets the authentication to use in order to access the repository.
512         * 
513         * @param authentication The authentication to use, may be {@code null}.
514         * @return This builder for chaining, never {@code null}.
515         */
516        public Builder setAuthentication( Authentication authentication )
517        {
518            this.authentication = authentication;
519            if ( prototype != null )
520            {
521                delta( AUTH, this.authentication, prototype.getAuthentication() );
522            }
523            return this;
524        }
525
526        /**
527         * Sets the repositories being mirrored by the repository.
528         * 
529         * @param mirroredRepositories The repositories being mirrored by the repository, may be {@code null}.
530         * @return This builder for chaining, never {@code null}.
531         */
532        public Builder setMirroredRepositories( List<RemoteRepository> mirroredRepositories )
533        {
534            if ( this.mirroredRepositories == null )
535            {
536                this.mirroredRepositories = new ArrayList<>();
537            }
538            else
539            {
540                this.mirroredRepositories.clear();
541            }
542            if ( mirroredRepositories != null )
543            {
544                this.mirroredRepositories.addAll( mirroredRepositories );
545            }
546            if ( prototype != null )
547            {
548                delta( MIRRORED, this.mirroredRepositories, prototype.getMirroredRepositories() );
549            }
550            return this;
551        }
552
553        /**
554         * Adds the specified repository to the list of repositories being mirrored by the repository. If this builder
555         * was {@link Builder constructed from a prototype}, the given repository
556         * will be added to the list of mirrored repositories from the prototype.
557         * 
558         * @param mirroredRepository The repository being mirrored by the repository, may be {@code null}.
559         * @return This builder for chaining, never {@code null}.
560         */
561        public Builder addMirroredRepository( RemoteRepository mirroredRepository )
562        {
563            if ( mirroredRepository != null )
564            {
565                if ( this.mirroredRepositories == null )
566                {
567                    this.mirroredRepositories = new ArrayList<>();
568                    if ( prototype != null )
569                    {
570                        mirroredRepositories.addAll( prototype.getMirroredRepositories() );
571                    }
572                }
573                mirroredRepositories.add( mirroredRepository );
574                if ( prototype != null )
575                {
576                    delta |= MIRRORED;
577                }
578            }
579            return this;
580        }
581
582        /**
583         * Marks the repository as a repository manager or not.
584         * 
585         * @param repositoryManager {@code true} if the repository points at a repository manager, {@code false} if the
586         *            repository is just serving static contents.
587         * @return This builder for chaining, never {@code null}.
588         */
589        public Builder setRepositoryManager( boolean repositoryManager )
590        {
591            this.repositoryManager = repositoryManager;
592            if ( prototype != null )
593            {
594                delta( REPOMAN, this.repositoryManager, prototype.isRepositoryManager() );
595            }
596            return this;
597        }
598
599
600        /**
601         * Marks the repository as blocked or not.
602         * 
603         * @param blocked {@code true} if the repository should not be allowed to perform any requests.
604         * @return This builder for chaining, never {@code null}.
605         */
606        public Builder setBlocked( boolean blocked )
607        {
608            this.blocked = blocked;
609            if ( prototype != null )
610            {
611                delta( BLOCKED, this.blocked, prototype.isBlocked() );
612            }
613            return this;
614        }
615    }
616
617}