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