001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.repository; 020 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collections; 024import java.util.List; 025import java.util.Objects; 026import java.util.regex.Matcher; 027import java.util.regex.Pattern; 028 029import static java.util.Objects.requireNonNull; 030 031/** 032 * A repository on a remote server. 033 * <p> 034 * If use of instances of this class are meant to be used as keys, see {@link #toBareRemoteRepository()} method. 035 */ 036public final class RemoteRepository implements ArtifactRepository { 037 /** 038 * The intent this repository is to be used for. Newly created repositories are usually "bare", and before 039 * their actual use (caller or repository system) adapts them, equip with auth/proxy info and even mirrors, if 040 * environment is configured for them. Note: "bare" does not always mean "without authentication", as client 041 * code may create with all required properties, but {@link org.eclipse.aether.RepositorySystem} will process 042 * them anyway, marking their "intent". 043 * <p> 044 * <em>Important consequence:</em> the change of {@link Intent} on repository may affect the use cases when 045 * they are used as keys (they are suitable for that). To use {@link RemoteRepository} instances as key, 046 * you should use instances returned by method {@link #toBareRemoteRepository()}, that returns "normalized" 047 * repository instances usable as keys. Also, in "key usage case" two instances of remote repository are 048 * considered equal if following stands: {@code Objects.equals(r1.toBareRemoteRepository(), r2.toBareRemoteRepository())}. 049 * 050 * @see org.eclipse.aether.RepositorySystem#newResolutionRepositories(org.eclipse.aether.RepositorySystemSession, List) 051 * @see org.eclipse.aether.RepositorySystem#newDeploymentRepository(org.eclipse.aether.RepositorySystemSession, RemoteRepository) 052 * @since 2.0.14 053 */ 054 public enum Intent { 055 /** The repository is newly created and not yet adapted for a particular use. */ 056 BARE, 057 /** The repository is used to resolve artifacts or metadata. */ 058 RESOLUTION, 059 /** The repository is used to deploy artifacts or metadata. */ 060 DEPLOYMENT 061 } 062 063 private static final Pattern URL_PATTERN = 064 Pattern.compile("([^:/]+(:[^:/]{2,}+(?=://))?):(//([^@/]*@)?([^/:]+))?.*"); 065 066 private final String id; 067 068 private final String type; 069 070 private final String url; 071 072 private final String host; 073 074 private final String protocol; 075 076 private final RepositoryPolicy releasePolicy; 077 078 private final RepositoryPolicy snapshotPolicy; 079 080 private final Proxy proxy; 081 082 private final Authentication authentication; 083 084 private final List<RemoteRepository> mirroredRepositories; 085 086 private final boolean repositoryManager; 087 088 private final boolean blocked; 089 090 private final Intent intent; 091 092 private final int hashCode; 093 094 RemoteRepository(Builder builder) { 095 if (builder.prototype != null) { 096 id = (builder.delta & Builder.ID) != 0 ? builder.id : builder.prototype.id; 097 type = (builder.delta & Builder.TYPE) != 0 ? builder.type : builder.prototype.type; 098 url = (builder.delta & Builder.URL) != 0 ? builder.url : builder.prototype.url; 099 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}