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 }