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;
027import java.util.regex.Matcher;
028import java.util.regex.Pattern;
029
030/**
031 * A repository on a remote server.
032 */
033public final class RemoteRepository
034    implements ArtifactRepository
035{
036
037    private static final Pattern URL_PATTERN =
038        Pattern.compile( "([^:/]+(:[^:/]{2,}+(?=://))?):(//([^@/]*@)?([^/:]+))?.*" );
039
040    private final String id;
041
042    private final String type;
043
044    private final String url;
045
046    private final String host;
047
048    private final String protocol;
049
050    private final RepositoryPolicy releasePolicy;
051
052    private final RepositoryPolicy snapshotPolicy;
053
054    private final Proxy proxy;
055
056    private final Authentication authentication;
057
058    private final List<RemoteRepository> mirroredRepositories;
059
060    private final boolean repositoryManager;
061
062    private boolean blocked;
063
064    RemoteRepository( Builder builder )
065    {
066        if ( builder.prototype != null )
067        {
068            id = ( builder.delta & Builder.ID ) != 0 ? builder.id : builder.prototype.id;
069            type = ( builder.delta & Builder.TYPE ) != 0 ? builder.type : builder.prototype.type;
070            url = ( builder.delta & Builder.URL ) != 0 ? builder.url : builder.prototype.url;
071            releasePolicy =
072                ( builder.delta & Builder.RELEASES ) != 0 ? builder.releasePolicy : builder.prototype.releasePolicy;
073            snapshotPolicy =
074                ( builder.delta & Builder.SNAPSHOTS ) != 0 ? builder.snapshotPolicy : builder.prototype.snapshotPolicy;
075            proxy = ( builder.delta & Builder.PROXY ) != 0 ? builder.proxy : builder.prototype.proxy;
076            authentication =
077                ( builder.delta & Builder.AUTH ) != 0 ? builder.authentication : builder.prototype.authentication;
078            repositoryManager =
079                ( builder.delta & Builder.REPOMAN ) != 0 ? builder.repositoryManager
080                                : builder.prototype.repositoryManager;
081            blocked = ( builder.delta & Builder.BLOCKED ) != 0 ? builder.blocked : builder.prototype.blocked;
082            mirroredRepositories =
083                ( builder.delta & Builder.MIRRORED ) != 0 ? copy( builder.mirroredRepositories )
084                                : builder.prototype.mirroredRepositories;
085        }
086        else
087        {
088            id = builder.id;
089            type = builder.type;
090            url = builder.url;
091            releasePolicy = builder.releasePolicy;
092            snapshotPolicy = builder.snapshotPolicy;
093            proxy = builder.proxy;
094            authentication = builder.authentication;
095            repositoryManager = builder.repositoryManager;
096            blocked = builder.blocked;
097            mirroredRepositories = copy( builder.mirroredRepositories );
098        }
099
100        Matcher m = URL_PATTERN.matcher( url );
101        if ( m.matches() )
102        {
103            protocol = m.group( 1 );
104            String host = m.group( 5 );
105            this.host = ( host != null ) ? host : "";
106        }
107        else
108        {
109            protocol = host = "";
110        }
111    }
112
113    private static List<RemoteRepository> copy( List<RemoteRepository> repos )
114    {
115        if ( repos == null || repos.isEmpty() )
116        {
117            return Collections.emptyList();
118        }
119        return Collections.unmodifiableList( Arrays.asList( repos.toArray( new RemoteRepository[repos.size()] ) ) );
120    }
121
122    public String getId()
123    {
124        return id;
125    }
126
127    public String getContentType()
128    {
129        return type;
130    }
131
132    /**
133     * Gets the (base) URL of this repository.
134     * 
135     * @return The (base) URL of this repository, never {@code null}.
136     */
137    public String getUrl()
138    {
139        return url;
140    }
141
142    /**
143     * Gets the protocol part from the repository's URL, for example {@code file} or {@code http}. As suggested by RFC
144     * 2396, section 3.1 "Scheme Component", the protocol name should be treated case-insensitively.
145     * 
146     * @return The protocol or an empty string if none, never {@code null}.
147     */
148    public String getProtocol()
149    {
150        return protocol;
151    }
152
153    /**
154     * Gets the host part from the repository's URL.
155     * 
156     * @return The host or an empty string if none, never {@code null}.
157     */
158    public String getHost()
159    {
160        return host;
161    }
162
163    /**
164     * Gets the policy to apply for snapshot/release artifacts.
165     * 
166     * @param snapshot {@code true} to retrieve the snapshot policy, {@code false} to retrieve the release policy.
167     * @return The requested repository policy, never {@code null}.
168     */
169    public RepositoryPolicy getPolicy( boolean snapshot )
170    {
171        return snapshot ? snapshotPolicy : releasePolicy;
172    }
173
174    /**
175     * Gets the proxy that has been selected for this repository.
176     * 
177     * @return The selected proxy or {@code null} if none.
178     */
179    public Proxy getProxy()
180    {
181        return proxy;
182    }
183
184    /**
185     * Gets the authentication that has been selected for this repository.
186     * 
187     * @return The selected authentication or {@code null} if none.
188     */
189    public Authentication getAuthentication()
190    {
191        return authentication;
192    }
193
194    /**
195     * Gets the repositories that this repository serves as a mirror for.
196     * 
197     * @return The (read-only) repositories being mirrored by this repository, never {@code null}.
198     */
199    public List<RemoteRepository> getMirroredRepositories()
200    {
201        return mirroredRepositories;
202    }
203
204    /**
205     * Indicates whether this repository refers to a repository manager or not.
206     * 
207     * @return {@code true} if this repository is a repository manager, {@code false} otherwise.
208     */
209    public boolean isRepositoryManager()
210    {
211        return repositoryManager;
212    }
213
214    /**
215     * Indicates whether this repository is blocked against any download request.
216     *
217     * @return {@code true} if this repository is blocked against any download request, {@code false} otherwise.
218     */
219    public boolean isBlocked()
220    {
221        return blocked;
222    }
223
224    @Override
225    public String toString()
226    {
227        StringBuilder buffer = new StringBuilder( 256 );
228        buffer.append( getId() );
229        buffer.append( " (" ).append( getUrl() );
230        buffer.append( ", " ).append( getContentType() );
231        boolean r = getPolicy( false ).isEnabled(), s = getPolicy( true ).isEnabled();
232        if ( r && s )
233        {
234            buffer.append( ", releases+snapshots" );
235        }
236        else if ( r )
237        {
238            buffer.append( ", releases" );
239        }
240        else if ( s )
241        {
242            buffer.append( ", snapshots" );
243        }
244        else
245        {
246            buffer.append( ", disabled" );
247        }
248        if ( isRepositoryManager() )
249        {
250            buffer.append( ", managed" );
251        }
252        if ( isBlocked() )
253        {
254            buffer.append( ", blocked" );
255
256        }
257        buffer.append( ")" );
258        return buffer.toString();
259    }
260
261    @Override
262    public boolean equals( Object obj )
263    {
264        if ( this == obj )
265        {
266            return true;
267        }
268        if ( obj == null || !getClass().equals( obj.getClass() ) )
269        {
270            return false;
271        }
272
273        RemoteRepository that = (RemoteRepository) obj;
274
275        return eq( url, that.url ) && eq( type, that.type ) && eq( id, that.id )
276            && eq( releasePolicy, that.releasePolicy ) && eq( snapshotPolicy, that.snapshotPolicy )
277            && eq( proxy, that.proxy ) && eq( authentication, that.authentication )
278            && eq( mirroredRepositories, that.mirroredRepositories ) && repositoryManager == that.repositoryManager;
279    }
280
281    private static <T> boolean eq( T s1, T s2 )
282    {
283        return s1 != null ? s1.equals( s2 ) : s2 == null;
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 = ( builder != null ) ? builder.equals( prototype ) : prototype == null;
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 = this.snapshotPolicy = ( policy != null ) ? policy : DEFAULT_POLICY;
453            if ( prototype != null )
454            {
455                delta( RELEASES, this.releasePolicy, prototype.getPolicy( false ) );
456                delta( SNAPSHOTS, this.snapshotPolicy, prototype.getPolicy( true ) );
457            }
458            return this;
459        }
460
461        /**
462         * Sets the policy to apply for release artifacts.
463         * 
464         * @param releasePolicy The repository policy to set, may be {@code null} to use a default policy.
465         * @return This builder for chaining, never {@code null}.
466         */
467        public Builder setReleasePolicy( RepositoryPolicy releasePolicy )
468        {
469            this.releasePolicy = ( releasePolicy != null ) ? releasePolicy : DEFAULT_POLICY;
470            if ( prototype != null )
471            {
472                delta( RELEASES, this.releasePolicy, prototype.getPolicy( false ) );
473            }
474            return this;
475        }
476
477        /**
478         * Sets the policy to apply for snapshot artifacts.
479         * 
480         * @param snapshotPolicy The repository policy to set, may be {@code null} to use a default policy.
481         * @return This builder for chaining, never {@code null}.
482         */
483        public Builder setSnapshotPolicy( RepositoryPolicy snapshotPolicy )
484        {
485            this.snapshotPolicy = ( snapshotPolicy != null ) ? snapshotPolicy : DEFAULT_POLICY;
486            if ( prototype != null )
487            {
488                delta( SNAPSHOTS, this.snapshotPolicy, prototype.getPolicy( true ) );
489            }
490            return this;
491        }
492
493        /**
494         * Sets the proxy to use in order to access the repository.
495         * 
496         * @param proxy The proxy to use, may be {@code null}.
497         * @return This builder for chaining, never {@code null}.
498         */
499        public Builder setProxy( Proxy proxy )
500        {
501            this.proxy = proxy;
502            if ( prototype != null )
503            {
504                delta( PROXY, this.proxy, prototype.getProxy() );
505            }
506            return this;
507        }
508
509        /**
510         * Sets the authentication to use in order to access the repository.
511         * 
512         * @param authentication The authentication to use, may be {@code null}.
513         * @return This builder for chaining, never {@code null}.
514         */
515        public Builder setAuthentication( Authentication authentication )
516        {
517            this.authentication = authentication;
518            if ( prototype != null )
519            {
520                delta( AUTH, this.authentication, prototype.getAuthentication() );
521            }
522            return this;
523        }
524
525        /**
526         * Sets the repositories being mirrored by the repository.
527         * 
528         * @param mirroredRepositories The repositories being mirrored by the repository, may be {@code null}.
529         * @return This builder for chaining, never {@code null}.
530         */
531        public Builder setMirroredRepositories( List<RemoteRepository> mirroredRepositories )
532        {
533            if ( this.mirroredRepositories == null )
534            {
535                this.mirroredRepositories = new ArrayList<RemoteRepository>();
536            }
537            else
538            {
539                this.mirroredRepositories.clear();
540            }
541            if ( mirroredRepositories != null )
542            {
543                this.mirroredRepositories.addAll( mirroredRepositories );
544            }
545            if ( prototype != null )
546            {
547                delta( MIRRORED, this.mirroredRepositories, prototype.getMirroredRepositories() );
548            }
549            return this;
550        }
551
552        /**
553         * Adds the specified repository to the list of repositories being mirrored by the repository. If this builder
554         * was {@link #RemoteRepository.Builder(RemoteRepository) constructed from a prototype}, the given repository
555         * will be added to the list of mirrored repositories from the prototype.
556         * 
557         * @param mirroredRepository The repository being mirrored by the repository, may be {@code null}.
558         * @return This builder for chaining, never {@code null}.
559         */
560        public Builder addMirroredRepository( RemoteRepository mirroredRepository )
561        {
562            if ( mirroredRepository != null )
563            {
564                if ( this.mirroredRepositories == null )
565                {
566                    this.mirroredRepositories = new ArrayList<RemoteRepository>();
567                    if ( prototype != null )
568                    {
569                        mirroredRepositories.addAll( prototype.getMirroredRepositories() );
570                    }
571                }
572                mirroredRepositories.add( mirroredRepository );
573                if ( prototype != null )
574                {
575                    delta |= MIRRORED;
576                }
577            }
578            return this;
579        }
580
581        /**
582         * Marks the repository as a repository manager or not.
583         * 
584         * @param repositoryManager {@code true} if the repository points at a repository manager, {@code false} if the
585         *            repository is just serving static contents.
586         * @return This builder for chaining, never {@code null}.
587         */
588        public Builder setRepositoryManager( boolean repositoryManager )
589        {
590            this.repositoryManager = repositoryManager;
591            if ( prototype != null )
592            {
593                delta( REPOMAN, this.repositoryManager, prototype.isRepositoryManager() );
594            }
595            return this;
596        }
597
598        /**
599         * Marks the repository as blocked or not.
600         *
601         * @param blocked {@code true} if the repository should not be allowed to get any request.
602         * @return This builder for chaining, never {@code null}.
603         */
604        public Builder setBlocked( boolean blocked )
605        {
606            this.blocked = blocked;
607            if ( prototype != null )
608            {
609                delta( BLOCKED, this.blocked, prototype.isBlocked() );
610            }
611            return this;
612        }
613    }
614
615}