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    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            mirroredRepositories =
082                ( builder.delta & Builder.MIRRORED ) != 0 ? copy( builder.mirroredRepositories )
083                                : builder.prototype.mirroredRepositories;
084        }
085        else
086        {
087            id = builder.id;
088            type = builder.type;
089            url = builder.url;
090            releasePolicy = builder.releasePolicy;
091            snapshotPolicy = builder.snapshotPolicy;
092            proxy = builder.proxy;
093            authentication = builder.authentication;
094            repositoryManager = builder.repositoryManager;
095            mirroredRepositories = copy( builder.mirroredRepositories );
096        }
097
098        Matcher m = URL_PATTERN.matcher( url );
099        if ( m.matches() )
100        {
101            protocol = m.group( 1 );
102            String host = m.group( 5 );
103            this.host = ( host != null ) ? host : "";
104        }
105        else
106        {
107            protocol = "";
108            host = "";
109        }
110    }
111
112    private static List<RemoteRepository> copy( List<RemoteRepository> repos )
113    {
114        if ( repos == null || repos.isEmpty() )
115        {
116            return Collections.emptyList();
117        }
118        return Collections.unmodifiableList( Arrays.asList( repos.toArray( new RemoteRepository[repos.size()] ) ) );
119    }
120
121    public String getId()
122    {
123        return id;
124    }
125
126    public String getContentType()
127    {
128        return type;
129    }
130
131    /**
132     * Gets the (base) URL of this repository.
133     * 
134     * @return The (base) URL of this repository, never {@code null}.
135     */
136    public String getUrl()
137    {
138        return url;
139    }
140
141    /**
142     * Gets the protocol part from the repository's URL, for example {@code file} or {@code http}. As suggested by RFC
143     * 2396, section 3.1 "Scheme Component", the protocol name should be treated case-insensitively.
144     * 
145     * @return The protocol or an empty string if none, never {@code null}.
146     */
147    public String getProtocol()
148    {
149        return protocol;
150    }
151
152    /**
153     * Gets the host part from the repository's URL.
154     * 
155     * @return The host or an empty string if none, never {@code null}.
156     */
157    public String getHost()
158    {
159        return host;
160    }
161
162    /**
163     * Gets the policy to apply for snapshot/release artifacts.
164     * 
165     * @param snapshot {@code true} to retrieve the snapshot policy, {@code false} to retrieve the release policy.
166     * @return The requested repository policy, never {@code null}.
167     */
168    public RepositoryPolicy getPolicy( boolean snapshot )
169    {
170        return snapshot ? snapshotPolicy : releasePolicy;
171    }
172
173    /**
174     * Gets the proxy that has been selected for this repository.
175     * 
176     * @return The selected proxy or {@code null} if none.
177     */
178    public Proxy getProxy()
179    {
180        return proxy;
181    }
182
183    /**
184     * Gets the authentication that has been selected for this repository.
185     * 
186     * @return The selected authentication or {@code null} if none.
187     */
188    public Authentication getAuthentication()
189    {
190        return authentication;
191    }
192
193    /**
194     * Gets the repositories that this repository serves as a mirror for.
195     * 
196     * @return The (read-only) repositories being mirrored by this repository, never {@code null}.
197     */
198    public List<RemoteRepository> getMirroredRepositories()
199    {
200        return mirroredRepositories;
201    }
202
203    /**
204     * Indicates whether this repository refers to a repository manager or not.
205     * 
206     * @return {@code true} if this repository is a repository manager, {@code false} otherwise.
207     */
208    public boolean isRepositoryManager()
209    {
210        return repositoryManager;
211    }
212
213    @Override
214    public String toString()
215    {
216        StringBuilder buffer = new StringBuilder( 256 );
217        buffer.append( getId() );
218        buffer.append( " (" ).append( getUrl() );
219        buffer.append( ", " ).append( getContentType() );
220        boolean r = getPolicy( false ).isEnabled(), s = getPolicy( true ).isEnabled();
221        if ( r && s )
222        {
223            buffer.append( ", releases+snapshots" );
224        }
225        else if ( r )
226        {
227            buffer.append( ", releases" );
228        }
229        else if ( s )
230        {
231            buffer.append( ", snapshots" );
232        }
233        else
234        {
235            buffer.append( ", disabled" );
236        }
237        if ( isRepositoryManager() )
238        {
239            buffer.append( ", managed" );
240        }
241        buffer.append( ")" );
242        return buffer.toString();
243    }
244
245    @Override
246    public boolean equals( Object obj )
247    {
248        if ( this == obj )
249        {
250            return true;
251        }
252        if ( obj == null || !getClass().equals( obj.getClass() ) )
253        {
254            return false;
255        }
256
257        RemoteRepository that = (RemoteRepository) obj;
258
259        return Objects.equals( url, that.url ) && Objects.equals( type, that.type )
260                && Objects.equals( id, that.id ) && Objects.equals( releasePolicy, that.releasePolicy )
261                && Objects.equals( snapshotPolicy, that.snapshotPolicy ) && Objects.equals( proxy, that.proxy )
262                && Objects.equals( authentication, that.authentication )
263                && Objects.equals( mirroredRepositories, that.mirroredRepositories )
264                && repositoryManager == that.repositoryManager;
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 = Objects.equals( builder, prototype );
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 = ( policy != null ) ? policy : DEFAULT_POLICY;
432            this.snapshotPolicy = ( policy != null ) ? policy : DEFAULT_POLICY;
433            if ( prototype != null )
434            {
435                delta( RELEASES, this.releasePolicy, prototype.getPolicy( false ) );
436                delta( SNAPSHOTS, this.snapshotPolicy, prototype.getPolicy( true ) );
437            }
438            return this;
439        }
440
441        /**
442         * Sets the policy to apply for release artifacts.
443         * 
444         * @param releasePolicy The repository policy to set, may be {@code null} to use a default policy.
445         * @return This builder for chaining, never {@code null}.
446         */
447        public Builder setReleasePolicy( RepositoryPolicy releasePolicy )
448        {
449            this.releasePolicy = ( releasePolicy != null ) ? releasePolicy : DEFAULT_POLICY;
450            if ( prototype != null )
451            {
452                delta( RELEASES, this.releasePolicy, prototype.getPolicy( false ) );
453            }
454            return this;
455        }
456
457        /**
458         * Sets the policy to apply for snapshot artifacts.
459         * 
460         * @param snapshotPolicy The repository policy to set, may be {@code null} to use a default policy.
461         * @return This builder for chaining, never {@code null}.
462         */
463        public Builder setSnapshotPolicy( RepositoryPolicy snapshotPolicy )
464        {
465            this.snapshotPolicy = ( snapshotPolicy != null ) ? snapshotPolicy : DEFAULT_POLICY;
466            if ( prototype != null )
467            {
468                delta( SNAPSHOTS, this.snapshotPolicy, prototype.getPolicy( true ) );
469            }
470            return this;
471        }
472
473        /**
474         * Sets the proxy to use in order to access the repository.
475         * 
476         * @param proxy The proxy to use, may be {@code null}.
477         * @return This builder for chaining, never {@code null}.
478         */
479        public Builder setProxy( Proxy proxy )
480        {
481            this.proxy = proxy;
482            if ( prototype != null )
483            {
484                delta( PROXY, this.proxy, prototype.getProxy() );
485            }
486            return this;
487        }
488
489        /**
490         * Sets the authentication to use in order to access the repository.
491         * 
492         * @param authentication The authentication to use, may be {@code null}.
493         * @return This builder for chaining, never {@code null}.
494         */
495        public Builder setAuthentication( Authentication authentication )
496        {
497            this.authentication = authentication;
498            if ( prototype != null )
499            {
500                delta( AUTH, this.authentication, prototype.getAuthentication() );
501            }
502            return this;
503        }
504
505        /**
506         * Sets the repositories being mirrored by the repository.
507         * 
508         * @param mirroredRepositories The repositories being mirrored by the repository, may be {@code null}.
509         * @return This builder for chaining, never {@code null}.
510         */
511        public Builder setMirroredRepositories( List<RemoteRepository> mirroredRepositories )
512        {
513            if ( this.mirroredRepositories == null )
514            {
515                this.mirroredRepositories = new ArrayList<>();
516            }
517            else
518            {
519                this.mirroredRepositories.clear();
520            }
521            if ( mirroredRepositories != null )
522            {
523                this.mirroredRepositories.addAll( mirroredRepositories );
524            }
525            if ( prototype != null )
526            {
527                delta( MIRRORED, this.mirroredRepositories, prototype.getMirroredRepositories() );
528            }
529            return this;
530        }
531
532        /**
533         * Adds the specified repository to the list of repositories being mirrored by the repository. If this builder
534         * was {@link Builder constructed from a prototype}, the given repository
535         * will be added to the list of mirrored repositories from the prototype.
536         * 
537         * @param mirroredRepository The repository being mirrored by the repository, may be {@code null}.
538         * @return This builder for chaining, never {@code null}.
539         */
540        public Builder addMirroredRepository( RemoteRepository mirroredRepository )
541        {
542            if ( mirroredRepository != null )
543            {
544                if ( this.mirroredRepositories == null )
545                {
546                    this.mirroredRepositories = new ArrayList<>();
547                    if ( prototype != null )
548                    {
549                        mirroredRepositories.addAll( prototype.getMirroredRepositories() );
550                    }
551                }
552                mirroredRepositories.add( mirroredRepository );
553                if ( prototype != null )
554                {
555                    delta |= MIRRORED;
556                }
557            }
558            return this;
559        }
560
561        /**
562         * Marks the repository as a repository manager or not.
563         * 
564         * @param repositoryManager {@code true} if the repository points at a repository manager, {@code false} if the
565         *            repository is just serving static contents.
566         * @return This builder for chaining, never {@code null}.
567         */
568        public Builder setRepositoryManager( boolean repositoryManager )
569        {
570            this.repositoryManager = repositoryManager;
571            if ( prototype != null )
572            {
573                delta( REPOMAN, this.repositoryManager, prototype.isRepositoryManager() );
574            }
575            return this;
576        }
577
578    }
579
580}