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