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 }