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.util.repository;
20  
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.List;
24  
25  import org.eclipse.aether.repository.MirrorSelector;
26  import org.eclipse.aether.repository.RemoteRepository;
27  
28  import static java.util.Objects.requireNonNull;
29  
30  /**
31   * A simple mirror selector that selects mirrors based on repository identifiers.
32   */
33  public final class DefaultMirrorSelector implements MirrorSelector {
34  
35      private static final String WILDCARD = "*";
36  
37      private static final String EXTERNAL_WILDCARD = "external:*";
38  
39      private static final String EXTERNAL_HTTP_WILDCARD = "external:http:*";
40  
41      private final List<MirrorDef> mirrors = new ArrayList<>();
42  
43      /**
44       * Adds the specified mirror to this selector.
45       *
46       * @param id The identifier of the mirror, must not be {@code null}.
47       * @param url The URL of the mirror, must not be {@code null}.
48       * @param type The content type of the mirror, must not be {@code null}.
49       * @param repositoryManager A flag whether the mirror is a repository manager or a simple server.
50       * @param blocked A flag whether the mirror is blocked from performing any download requests.
51       * @param mirrorOfIds The identifier(s) of remote repositories to mirror, must not be {@code null}. Multiple
52       *            identifiers can be separated by comma and additionally the wildcards "*", "external:http:*" and
53       *            "external:*" can be used to match all (external) repositories, prefixing a repo id with an
54       *            exclamation mark allows to express an exclusion. For example "external:*,!central".
55       * @param mirrorOfTypes The content type(s) of remote repositories to mirror, may be {@code null} or empty to match
56       *            any content type. Similar to the repo id specification, multiple types can be comma-separated, the
57       *            wildcard "*" and the "!" negation syntax are supported. For example "*,!p2".
58       * @return This selector for chaining, never {@code null}.
59       */
60      public DefaultMirrorSelector add(
61              String id,
62              String url,
63              String type,
64              boolean repositoryManager,
65              boolean blocked,
66              String mirrorOfIds,
67              String mirrorOfTypes) {
68          mirrors.add(new MirrorDef(id, url, type, repositoryManager, blocked, mirrorOfIds, mirrorOfTypes));
69  
70          return this;
71      }
72  
73      public RemoteRepository getMirror(RemoteRepository repository) {
74          requireNonNull(repository, "repository cannot be null");
75          MirrorDef mirror = findMirror(repository);
76  
77          if (mirror == null) {
78              return null;
79          }
80  
81          RemoteRepository.Builder builder =
82                  new RemoteRepository.Builder(mirror.id, repository.getContentType(), mirror.url);
83  
84          builder.setRepositoryManager(mirror.repositoryManager);
85  
86          builder.setBlocked(mirror.blocked);
87  
88          if (mirror.type != null && !mirror.type.isEmpty()) {
89              builder.setContentType(mirror.type);
90          }
91  
92          builder.setSnapshotPolicy(repository.getPolicy(true));
93          builder.setReleasePolicy(repository.getPolicy(false));
94  
95          builder.setMirroredRepositories(Collections.singletonList(repository));
96  
97          return builder.build();
98      }
99  
100     private MirrorDef findMirror(RemoteRepository repository) {
101         String repoId = repository.getId();
102 
103         if (repoId != null && !mirrors.isEmpty()) {
104             for (MirrorDef mirror : mirrors) {
105                 if (repoId.equals(mirror.mirrorOfIds)
106                         && matchesType(repository.getContentType(), mirror.mirrorOfTypes)) {
107                     return mirror;
108                 }
109             }
110 
111             for (MirrorDef mirror : mirrors) {
112                 if (matchPattern(repository, mirror.mirrorOfIds)
113                         && matchesType(repository.getContentType(), mirror.mirrorOfTypes)) {
114                     return mirror;
115                 }
116             }
117         }
118 
119         return null;
120     }
121 
122     /**
123      * This method checks if the pattern matches the originalRepository. Valid patterns:
124      * <ul>
125      * <li>{@code *} = everything,</li>
126      * <li>{@code external:*} = everything not on the localhost and not file based,</li>
127      * <li>{@code external:http:*} = any repository not on the localhost using HTTP,</li>
128      * <li>{@code repo,repo1} = {@code repo} or {@code repo1},</li>
129      * <li>{@code *,!repo1} = everything except {@code repo1}.</li>
130      * </ul>
131      *
132      * @param repository to compare for a match.
133      * @param pattern used for match.
134      * @return true if the repository is a match to this pattern.
135      */
136     static boolean matchPattern(RemoteRepository repository, String pattern) {
137         boolean result = false;
138         String originalId = repository.getId();
139 
140         // simple checks first to short circuit processing below.
141         if (WILDCARD.equals(pattern) || pattern.equals(originalId)) {
142             result = true;
143         } else {
144             // process the list
145             String[] repos = pattern.split(",");
146             for (String repo : repos) {
147                 // see if this is a negative match
148                 if (repo.length() > 1 && repo.startsWith("!")) {
149                     if (repo.substring(1).equals(originalId)) {
150                         // explicitly exclude. Set result and stop processing.
151                         result = false;
152                         break;
153                     }
154                 }
155                 // check for exact match
156                 else if (repo.equals(originalId)) {
157                     result = true;
158                     break;
159                 }
160                 // check for external:*
161                 else if (EXTERNAL_WILDCARD.equals(repo) && isExternalRepo(repository)) {
162                     result = true;
163                     // don't stop processing in case a future segment explicitly excludes this repo
164                 }
165                 // check for external:http:*
166                 else if (EXTERNAL_HTTP_WILDCARD.equals(repo) && isExternalHttpRepo(repository)) {
167                     result = true;
168                     // don't stop processing in case a future segment explicitly excludes this repo
169                 } else if (WILDCARD.equals(repo)) {
170                     result = true;
171                     // don't stop processing in case a future segment explicitly excludes this repo
172                 }
173             }
174         }
175         return result;
176     }
177 
178     /**
179      * Checks the URL to see if this repository refers to an external repository.
180      *
181      * @param repository The repository to check, must not be {@code null}.
182      * @return {@code true} if external, {@code false} otherwise.
183      */
184     static boolean isExternalRepo(RemoteRepository repository) {
185         boolean local = isLocal(repository.getHost()) || "file".equalsIgnoreCase(repository.getProtocol());
186         return !local;
187     }
188 
189     private static boolean isLocal(String host) {
190         return "localhost".equals(host) || "127.0.0.1".equals(host);
191     }
192 
193     /**
194      * Checks the URL to see if this repository refers to a non-localhost repository using HTTP.
195      *
196      * @param repository The repository to check, must not be {@code null}.
197      * @return {@code true} if external, {@code false} otherwise.
198      */
199     static boolean isExternalHttpRepo(RemoteRepository repository) {
200         return ("http".equalsIgnoreCase(repository.getProtocol())
201                         || "dav".equalsIgnoreCase(repository.getProtocol())
202                         || "dav:http".equalsIgnoreCase(repository.getProtocol())
203                         || "dav+http".equalsIgnoreCase(repository.getProtocol()))
204                 && !isLocal(repository.getHost());
205     }
206 
207     /**
208      * Checks whether the types configured for a mirror match with the type of the repository.
209      *
210      * @param repoType The type of the repository, may be {@code null}.
211      * @param mirrorType The types supported by the mirror, may be {@code null}.
212      * @return {@code true} if the types associated with the mirror match the type of the original repository,
213      *         {@code false} otherwise.
214      */
215     static boolean matchesType(String repoType, String mirrorType) {
216         boolean result = false;
217 
218         // simple checks first to short circuit processing below.
219         if (mirrorType == null || mirrorType.isEmpty() || WILDCARD.equals(mirrorType)) {
220             result = true;
221         } else if (mirrorType.equals(repoType)) {
222             result = true;
223         } else {
224             // process the list
225             String[] layouts = mirrorType.split(",");
226             for (String layout : layouts) {
227                 // see if this is a negative match
228                 if (layout.length() > 1 && layout.startsWith("!")) {
229                     if (layout.substring(1).equals(repoType)) {
230                         // explicitly exclude. Set result and stop processing.
231                         result = false;
232                         break;
233                     }
234                 }
235                 // check for exact match
236                 else if (layout.equals(repoType)) {
237                     result = true;
238                     break;
239                 } else if (WILDCARD.equals(layout)) {
240                     result = true;
241                     // don't stop processing in case a future segment explicitly excludes this repo
242                 }
243             }
244         }
245 
246         return result;
247     }
248 
249     static class MirrorDef {
250 
251         final String id;
252 
253         final String url;
254 
255         final String type;
256 
257         final boolean repositoryManager;
258 
259         final boolean blocked;
260 
261         final String mirrorOfIds;
262 
263         final String mirrorOfTypes;
264 
265         MirrorDef(
266                 String id,
267                 String url,
268                 String type,
269                 boolean repositoryManager,
270                 boolean blocked,
271                 String mirrorOfIds,
272                 String mirrorOfTypes) {
273             this.id = id;
274             this.url = url;
275             this.type = type;
276             this.repositoryManager = repositoryManager;
277             this.blocked = blocked;
278             this.mirrorOfIds = mirrorOfIds;
279             this.mirrorOfTypes = mirrorOfTypes;
280         }
281     }
282 }