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