View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.repository;
20  
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.Objects;
26  import java.util.regex.Matcher;
27  import java.util.regex.Pattern;
28  
29  import static java.util.Objects.requireNonNull;
30  
31  /**
32   * A repository on a remote server.
33   * <p>
34   * If use of instances of this class are meant to be used as keys, see {@link #toBareRemoteRepository()} method.
35   */
36  public final class RemoteRepository implements ArtifactRepository {
37      /**
38       * The intent this repository is to be used for. Newly created repositories are usually "bare", and before
39       * their actual use (caller or repository system) adapts them, equip with auth/proxy info and even mirrors, if
40       * environment is configured for them. Note: "bare" does not always mean "without authentication", as client
41       * code may create with all required properties, but {@link org.eclipse.aether.RepositorySystem} will process
42       * them anyway, marking their "intent".
43       * <p>
44       * <em>Important consequence:</em> the change of {@link Intent} on repository may affect the use cases when
45       * they are used as keys (they are suitable for that). To use {@link RemoteRepository} instances as key,
46       * you should use instances returned by method {@link #toBareRemoteRepository()}, that returns "normalized"
47       * repository instances usable as keys. Also, in "key usage case" two instances of remote repository are
48       * considered equal if following stands: {@code Objects.equals(r1.toBareRemoteRepository(), r2.toBareRemoteRepository())}.
49       *
50       * @see org.eclipse.aether.RepositorySystem#newResolutionRepositories(RepositorySystemSession, List)
51       * @see org.eclipse.aether.RepositorySystem#newDeploymentRepository(RepositorySystemSession, RemoteRepository)
52       * @since 2.0.14
53       */
54      public enum Intent {
55          BARE,
56          RESOLUTION,
57          DEPLOYMENT
58      }
59  
60      private static final Pattern URL_PATTERN =
61              Pattern.compile("([^:/]+(:[^:/]{2,}+(?=://))?):(//([^@/]*@)?([^/:]+))?.*");
62  
63      private final String id;
64  
65      private final String type;
66  
67      private final String url;
68  
69      private final String host;
70  
71      private final String protocol;
72  
73      private final RepositoryPolicy releasePolicy;
74  
75      private final RepositoryPolicy snapshotPolicy;
76  
77      private final Proxy proxy;
78  
79      private final Authentication authentication;
80  
81      private final List<RemoteRepository> mirroredRepositories;
82  
83      private final boolean repositoryManager;
84  
85      private final boolean blocked;
86  
87      private final Intent intent;
88  
89      private final int hashCode;
90  
91      RemoteRepository(Builder builder) {
92          if (builder.prototype != null) {
93              id = (builder.delta & Builder.ID) != 0 ? builder.id : builder.prototype.id;
94              type = (builder.delta & Builder.TYPE) != 0 ? builder.type : builder.prototype.type;
95              url = (builder.delta & Builder.URL) != 0 ? builder.url : builder.prototype.url;
96              releasePolicy =
97                      (builder.delta & Builder.RELEASES) != 0 ? builder.releasePolicy : builder.prototype.releasePolicy;
98              snapshotPolicy = (builder.delta & Builder.SNAPSHOTS) != 0
99                      ? builder.snapshotPolicy
100                     : builder.prototype.snapshotPolicy;
101             proxy = (builder.delta & Builder.PROXY) != 0 ? builder.proxy : builder.prototype.proxy;
102             authentication =
103                     (builder.delta & Builder.AUTH) != 0 ? builder.authentication : builder.prototype.authentication;
104             repositoryManager = (builder.delta & Builder.REPOMAN) != 0
105                     ? builder.repositoryManager
106                     : builder.prototype.repositoryManager;
107             blocked = (builder.delta & Builder.BLOCKED) != 0 ? builder.blocked : builder.prototype.blocked;
108             mirroredRepositories = (builder.delta & Builder.MIRRORED) != 0
109                     ? copy(builder.mirroredRepositories)
110                     : builder.prototype.mirroredRepositories;
111             intent = (builder.delta & Builder.INTENT) != 0 ? builder.intent : builder.prototype.intent;
112         } else {
113             id = builder.id;
114             type = builder.type;
115             url = builder.url;
116             releasePolicy = builder.releasePolicy;
117             snapshotPolicy = builder.snapshotPolicy;
118             proxy = builder.proxy;
119             authentication = builder.authentication;
120             repositoryManager = builder.repositoryManager;
121             blocked = builder.blocked;
122             mirroredRepositories = copy(builder.mirroredRepositories);
123             intent = builder.intent;
124         }
125 
126         Matcher m = URL_PATTERN.matcher(url);
127         if (m.matches()) {
128             String h = m.group(5);
129             this.host = (h != null) ? h : "";
130             this.protocol = m.group(1);
131         } else {
132             this.host = "";
133             this.protocol = "";
134         }
135 
136         this.hashCode = Objects.hash(
137                 id,
138                 type,
139                 url, // host, protocol derived from url
140                 releasePolicy,
141                 snapshotPolicy,
142                 proxy,
143                 authentication,
144                 mirroredRepositories,
145                 repositoryManager,
146                 blocked,
147                 intent);
148     }
149 
150     private static List<RemoteRepository> copy(List<RemoteRepository> repos) {
151         if (repos == null || repos.isEmpty()) {
152             return Collections.emptyList();
153         }
154         return Collections.unmodifiableList(Arrays.asList(repos.toArray(new RemoteRepository[0])));
155     }
156 
157     @Override
158     public String getId() {
159         return id;
160     }
161 
162     @Override
163     public String getContentType() {
164         return type;
165     }
166 
167     /**
168      * Gets the (base) URL of this repository.
169      *
170      * @return The (base) URL of this repository, never {@code null}.
171      */
172     public String getUrl() {
173         return url;
174     }
175 
176     /**
177      * Gets the protocol part from the repository's URL, for example {@code file} or {@code http}. As suggested by RFC
178      * 2396, section 3.1 "Scheme Component", the protocol name should be treated case-insensitively.
179      *
180      * @return The protocol or an empty string if none, never {@code null}.
181      */
182     public String getProtocol() {
183         return protocol;
184     }
185 
186     /**
187      * Gets the host part from the repository's URL.
188      *
189      * @return The host or an empty string if none, never {@code null}.
190      */
191     public String getHost() {
192         return host;
193     }
194 
195     /**
196      * Gets the policy to apply for snapshot/release artifacts.
197      *
198      * @param snapshot {@code true} to retrieve the snapshot policy, {@code false} to retrieve the release policy.
199      * @return The requested repository policy, never {@code null}.
200      */
201     public RepositoryPolicy getPolicy(boolean snapshot) {
202         return snapshot ? snapshotPolicy : releasePolicy;
203     }
204 
205     /**
206      * Gets the proxy that has been selected for this repository.
207      *
208      * @return The selected proxy or {@code null} if none.
209      */
210     public Proxy getProxy() {
211         return proxy;
212     }
213 
214     /**
215      * Gets the authentication that has been selected for this repository.
216      *
217      * @return The selected authentication or {@code null} if none.
218      */
219     public Authentication getAuthentication() {
220         return authentication;
221     }
222 
223     /**
224      * Gets the repositories that this repository serves as a mirror for.
225      *
226      * @return The (read-only) repositories being mirrored by this repository, never {@code null}.
227      */
228     public List<RemoteRepository> getMirroredRepositories() {
229         return mirroredRepositories;
230     }
231 
232     /**
233      * Indicates whether this repository refers to a repository manager or not.
234      *
235      * @return {@code true} if this repository is a repository manager, {@code false} otherwise.
236      */
237     public boolean isRepositoryManager() {
238         return repositoryManager;
239     }
240 
241     /**
242      * Indicates whether this repository is blocked from performing any download requests.
243      *
244      * @return {@code true} if this repository is blocked from performing any download requests,
245      *         {@code false} otherwise.
246      */
247     public boolean isBlocked() {
248         return blocked;
249     }
250 
251     /**
252      * Returns the intent this repository is prepared for.
253      *
254      * @return The intent this repository is prepared for.
255      * @since 2.0.14
256      */
257     public Intent getIntent() {
258         return intent;
259     }
260 
261     @Override
262     public String toString() {
263         StringBuilder buffer = new StringBuilder(256);
264         buffer.append(getId());
265         buffer.append(" (").append(getUrl());
266         buffer.append(", ").append(getContentType());
267         boolean r = getPolicy(false).isEnabled(), s = getPolicy(true).isEnabled();
268         if (r && s) {
269             buffer.append(", releases+snapshots");
270         } else if (r) {
271             buffer.append(", releases");
272         } else if (s) {
273             buffer.append(", snapshots");
274         } else {
275             buffer.append(", disabled");
276         }
277         if (isRepositoryManager()) {
278             buffer.append(", managed");
279         }
280         if (isBlocked()) {
281             buffer.append(", blocked");
282         }
283         buffer.append(")");
284         return buffer.toString();
285     }
286 
287     @Override
288     public boolean equals(Object obj) {
289         if (this == obj) {
290             return true;
291         }
292         if (obj == null || !getClass().equals(obj.getClass())) {
293             return false;
294         }
295 
296         RemoteRepository that = (RemoteRepository) obj;
297 
298         return Objects.equals(url, that.url)
299                 && Objects.equals(type, that.type)
300                 && Objects.equals(id, that.id)
301                 && Objects.equals(releasePolicy, that.releasePolicy)
302                 && Objects.equals(snapshotPolicy, that.snapshotPolicy)
303                 && Objects.equals(proxy, that.proxy)
304                 && Objects.equals(authentication, that.authentication)
305                 && Objects.equals(mirroredRepositories, that.mirroredRepositories)
306                 && repositoryManager == that.repositoryManager
307                 && blocked == that.blocked
308                 && intent == that.intent;
309     }
310 
311     @Override
312     public int hashCode() {
313         return hashCode;
314     }
315 
316     /**
317      * Makes "bare" repository out of this instance, usable as keys within one single session, by applying following
318      * changes to repository (returns new instance):
319      * <ul>
320      *     <li>sets intent to {@link Intent#BARE}</li>
321      *     <li>nullifies proxy</li>
322      *     <li>nullifies authentication</li>
323      *     <li>nullifies mirrors</li>
324      *     <li>sets repositoryManager to {@code false}</li>
325      * </ul>
326      * These properties are managed by repository system, based on configuration. See {@link org.eclipse.aether.RepositorySystem}
327      * and (internal component) {@code org.eclipse.aether.impl.RemoteRepositoryManager}.
328      *
329      * @since 2.0.14
330      */
331     public RemoteRepository toBareRemoteRepository() {
332         return new Builder(this)
333                 .setIntent(Intent.BARE)
334                 .setProxy(null)
335                 .setAuthentication(null)
336                 .setMirroredRepositories(null)
337                 .setRepositoryManager(false)
338                 .build();
339     }
340 
341     /**
342      * A builder to create remote repositories.
343      */
344     public static final class Builder {
345 
346         private static final RepositoryPolicy DEFAULT_POLICY = new RepositoryPolicy();
347 
348         static final int ID = 0x0001,
349                 TYPE = 0x0002,
350                 URL = 0x0004,
351                 RELEASES = 0x0008,
352                 SNAPSHOTS = 0x0010,
353                 PROXY = 0x0020,
354                 AUTH = 0x0040,
355                 MIRRORED = 0x0080,
356                 REPOMAN = 0x0100,
357                 BLOCKED = 0x0200,
358                 INTENT = 0x0400;
359 
360         int delta;
361 
362         RemoteRepository prototype;
363 
364         String id;
365 
366         String type;
367 
368         String url;
369 
370         RepositoryPolicy releasePolicy = DEFAULT_POLICY;
371 
372         RepositoryPolicy snapshotPolicy = DEFAULT_POLICY;
373 
374         Proxy proxy;
375 
376         Authentication authentication;
377 
378         List<RemoteRepository> mirroredRepositories;
379 
380         boolean repositoryManager;
381 
382         boolean blocked;
383 
384         Intent intent = Intent.BARE;
385 
386         /**
387          * Creates a new repository builder.
388          *
389          * @param id The identifier of the repository, may be {@code null}.
390          * @param type The type of the repository, may be {@code null}.
391          * @param url The (base) URL of the repository, may be {@code null}.
392          */
393         public Builder(String id, String type, String url) {
394             this.id = (id != null) ? id : "";
395             this.type = (type != null) ? type : "";
396             this.url = (url != null) ? url : "";
397         }
398 
399         /**
400          * Creates a new repository builder which uses the specified remote repository as a prototype for the new one.
401          * All properties which have not been set on the builder will be copied from the prototype when building the
402          * repository.
403          *
404          * @param prototype The remote repository to use as prototype, must not be {@code null}.
405          */
406         public Builder(RemoteRepository prototype) {
407             this.prototype = requireNonNull(prototype, "remote repository prototype cannot be null");
408         }
409 
410         /**
411          * Builds a new remote repository from the current values of this builder. The state of the builder itself
412          * remains unchanged.
413          *
414          * @return The remote repository, never {@code null}.
415          */
416         public RemoteRepository build() {
417             if (prototype != null && delta == 0) {
418                 return prototype;
419             }
420             return new RemoteRepository(this);
421         }
422 
423         private <T> void delta(int flag, T builder, T prototype) {
424             boolean equal = Objects.equals(builder, prototype);
425             if (equal) {
426                 delta &= ~flag;
427             } else {
428                 delta |= flag;
429             }
430         }
431 
432         /**
433          * Sets the identifier of the repository.
434          *
435          * @param id The identifier of the repository, may be {@code null}.
436          * @return This builder for chaining, never {@code null}.
437          */
438         public Builder setId(String id) {
439             this.id = (id != null) ? id : "";
440             if (prototype != null) {
441                 delta(ID, this.id, prototype.getId());
442             }
443             return this;
444         }
445 
446         /**
447          * Sets the type of the repository, e.g. "default".
448          *
449          * @param type The type of the repository, may be {@code null}.
450          * @return This builder for chaining, never {@code null}.
451          */
452         public Builder setContentType(String type) {
453             this.type = (type != null) ? type : "";
454             if (prototype != null) {
455                 delta(TYPE, this.type, prototype.getContentType());
456             }
457             return this;
458         }
459 
460         /**
461          * Sets the (base) URL of the repository.
462          *
463          * @param url The URL of the repository, may be {@code null}.
464          * @return This builder for chaining, never {@code null}.
465          */
466         public Builder setUrl(String url) {
467             this.url = (url != null) ? url : "";
468             if (prototype != null) {
469                 delta(URL, this.url, prototype.getUrl());
470             }
471             return this;
472         }
473 
474         /**
475          * Sets the policy to apply for snapshot and release artifacts.
476          *
477          * @param policy The repository policy to set, may be {@code null} to use a default policy.
478          * @return This builder for chaining, never {@code null}.
479          */
480         public Builder setPolicy(RepositoryPolicy policy) {
481             this.releasePolicy = (policy != null) ? policy : DEFAULT_POLICY;
482             this.snapshotPolicy = (policy != null) ? policy : DEFAULT_POLICY;
483             if (prototype != null) {
484                 delta(RELEASES, this.releasePolicy, prototype.getPolicy(false));
485                 delta(SNAPSHOTS, this.snapshotPolicy, prototype.getPolicy(true));
486             }
487             return this;
488         }
489 
490         /**
491          * Sets the policy to apply for release artifacts.
492          *
493          * @param releasePolicy The repository policy to set, may be {@code null} to use a default policy.
494          * @return This builder for chaining, never {@code null}.
495          */
496         public Builder setReleasePolicy(RepositoryPolicy releasePolicy) {
497             this.releasePolicy = (releasePolicy != null) ? releasePolicy : DEFAULT_POLICY;
498             if (prototype != null) {
499                 delta(RELEASES, this.releasePolicy, prototype.getPolicy(false));
500             }
501             return this;
502         }
503 
504         /**
505          * Sets the policy to apply for snapshot artifacts.
506          *
507          * @param snapshotPolicy The repository policy to set, may be {@code null} to use a default policy.
508          * @return This builder for chaining, never {@code null}.
509          */
510         public Builder setSnapshotPolicy(RepositoryPolicy snapshotPolicy) {
511             this.snapshotPolicy = (snapshotPolicy != null) ? snapshotPolicy : DEFAULT_POLICY;
512             if (prototype != null) {
513                 delta(SNAPSHOTS, this.snapshotPolicy, prototype.getPolicy(true));
514             }
515             return this;
516         }
517 
518         /**
519          * Sets the proxy to use in order to access the repository.
520          *
521          * @param proxy The proxy to use, may be {@code null}.
522          * @return This builder for chaining, never {@code null}.
523          */
524         public Builder setProxy(Proxy proxy) {
525             this.proxy = proxy;
526             if (prototype != null) {
527                 delta(PROXY, this.proxy, prototype.getProxy());
528             }
529             return this;
530         }
531 
532         /**
533          * Sets the authentication to use in order to access the repository.
534          *
535          * @param authentication The authentication to use, may be {@code null}.
536          * @return This builder for chaining, never {@code null}.
537          */
538         public Builder setAuthentication(Authentication authentication) {
539             this.authentication = authentication;
540             if (prototype != null) {
541                 delta(AUTH, this.authentication, prototype.getAuthentication());
542             }
543             return this;
544         }
545 
546         /**
547          * Sets the repositories being mirrored by the repository.
548          *
549          * @param mirroredRepositories The repositories being mirrored by the repository, may be {@code null}.
550          * @return This builder for chaining, never {@code null}.
551          */
552         public Builder setMirroredRepositories(List<RemoteRepository> mirroredRepositories) {
553             if (this.mirroredRepositories == null) {
554                 this.mirroredRepositories = new ArrayList<>();
555             } else {
556                 this.mirroredRepositories.clear();
557             }
558             if (mirroredRepositories != null) {
559                 this.mirroredRepositories.addAll(mirroredRepositories);
560             }
561             if (prototype != null) {
562                 delta(MIRRORED, this.mirroredRepositories, prototype.getMirroredRepositories());
563             }
564             return this;
565         }
566 
567         /**
568          * Adds the specified repository to the list of repositories being mirrored by the repository. If this builder
569          * was {@link Builder constructed from a prototype}, the given repository
570          * will be added to the list of mirrored repositories from the prototype.
571          *
572          * @param mirroredRepository The repository being mirrored by the repository, may be {@code null}.
573          * @return This builder for chaining, never {@code null}.
574          */
575         public Builder addMirroredRepository(RemoteRepository mirroredRepository) {
576             if (mirroredRepository != null) {
577                 if (this.mirroredRepositories == null) {
578                     this.mirroredRepositories = new ArrayList<>();
579                     if (prototype != null) {
580                         mirroredRepositories.addAll(prototype.getMirroredRepositories());
581                     }
582                 }
583                 mirroredRepositories.add(mirroredRepository);
584                 if (prototype != null) {
585                     delta |= MIRRORED;
586                 }
587             }
588             return this;
589         }
590 
591         /**
592          * Marks the repository as a repository manager or not.
593          *
594          * @param repositoryManager {@code true} if the repository points at a repository manager, {@code false} if the
595          *            repository is just serving static contents.
596          * @return This builder for chaining, never {@code null}.
597          */
598         public Builder setRepositoryManager(boolean repositoryManager) {
599             this.repositoryManager = repositoryManager;
600             if (prototype != null) {
601                 delta(REPOMAN, this.repositoryManager, prototype.isRepositoryManager());
602             }
603             return this;
604         }
605 
606         /**
607          * Marks the repository as blocked or not.
608          *
609          * @param blocked {@code true} if the repository should not be allowed to perform any requests.
610          * @return This builder for chaining, never {@code null}.
611          */
612         public Builder setBlocked(boolean blocked) {
613             this.blocked = blocked;
614             if (prototype != null) {
615                 delta(BLOCKED, this.blocked, prototype.isBlocked());
616             }
617             return this;
618         }
619 
620         /**
621          * Marks the intent for this repository.
622          *
623          * @param intent the intent with this remote repository.
624          * @return This builder for chaining, never {@code null}.
625          * @since 2.0.14
626          */
627         public Builder setIntent(Intent intent) {
628             this.intent = intent;
629             if (prototype != null) {
630                 delta(INTENT, this.intent, prototype.intent);
631             }
632             return this;
633         }
634     }
635 }