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