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(RepositorySystemSession, List) 051 * @see org.eclipse.aether.RepositorySystem#newDeploymentRepository(RepositorySystemSession, RemoteRepository) 052 * @since 2.0.14 053 */ 054 public enum Intent { 055 BARE, 056 RESOLUTION, 057 DEPLOYMENT 058 } 059 060 private static final Pattern URL_PATTERN = 061 Pattern.compile("([^:/]+(:[^:/]{2,}+(?=://))?):(//([^@/]*@)?([^/:]+))?.*"); 062 063 private final String id; 064 065 private final String type; 066 067 private final String url; 068 069 private final String host; 070 071 private final String protocol; 072 073 private final RepositoryPolicy releasePolicy; 074 075 private final RepositoryPolicy snapshotPolicy; 076 077 private final Proxy proxy; 078 079 private final Authentication authentication; 080 081 private final List<RemoteRepository> mirroredRepositories; 082 083 private final boolean repositoryManager; 084 085 private final boolean blocked; 086 087 private final Intent intent; 088 089 private final int hashCode; 090 091 RemoteRepository(Builder builder) { 092 if (builder.prototype != null) { 093 id = (builder.delta & Builder.ID) != 0 ? builder.id : builder.prototype.id; 094 type = (builder.delta & Builder.TYPE) != 0 ? builder.type : builder.prototype.type; 095 url = (builder.delta & Builder.URL) != 0 ? builder.url : builder.prototype.url; 096 releasePolicy = 097 (builder.delta & Builder.RELEASES) != 0 ? builder.releasePolicy : builder.prototype.releasePolicy; 098 snapshotPolicy = (builder.delta & Builder.SNAPSHOTS) != 0 099 ? 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}