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 }