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.Comparator;
23 import java.util.Locale;
24 import java.util.SortedSet;
25 import java.util.TreeSet;
26
27 import org.eclipse.aether.repository.ArtifactRepository;
28 import org.eclipse.aether.repository.RemoteRepository;
29 import org.eclipse.aether.repository.RepositoryKeyFunction;
30 import org.eclipse.aether.util.PathUtils;
31 import org.eclipse.aether.util.StringDigestUtil;
32
33 /**
34 * Helper class for {@link ArtifactRepository#getId()} handling. This class provides helper methods
35 * to get id of repository as it was originally envisioned: as path safe, unique, etc. While POMs are validated by Maven,
36 * there are POMs out there that somehow define repositories with unsafe characters in their id. The problem affects mostly
37 * {@link RemoteRepository} instances, as all other implementations have fixed ids that are path safe.
38 * <p>
39 * <em>Important:</em> multiple of these provided methods are not trivial processing-wise, and some sort of
40 * caching is warmly recommended.
41 *
42 * @see PathUtils
43 * @since 2.0.11
44 */
45 public final class RepositoryIdHelper {
46 private RepositoryIdHelper() {}
47
48 /**
49 * Supported {@code repositoryKey} types.
50 *
51 * @since 2.0.14
52 */
53 public enum RepositoryKeyType {
54 /**
55 * The "simple" repository key, was default in Maven 3.
56 */
57 SIMPLE,
58 /**
59 * Crafts repository key using normalized {@link RemoteRepository#getId()}.
60 */
61 NID,
62 /**
63 * Crafts repository key using hashed {@link RemoteRepository#getUrl()}.
64 */
65 HURL,
66 /**
67 * Crafts unique repository key using normalized {@link RemoteRepository#getId()} and hashed {@link RemoteRepository#getUrl()}.
68 */
69 NID_HURL,
70 /**
71 * Crafts normalized unique repository key using {@link RemoteRepository#getId()} and all the remaining properties of
72 * {@link RemoteRepository} ignoring actual list of mirrors, if any (but mirrors are split).
73 */
74 NGURK,
75 /**
76 * Crafts unique repository key using {@link RemoteRepository#getId()} and all the remaining properties of
77 * {@link RemoteRepository}.
78 */
79 GURK
80 }
81
82 /**
83 * Selector method for {@link RepositoryKeyFunction} based on string representation of {@link RepositoryKeyType}
84 * enum.
85 */
86 public static RepositoryKeyFunction getRepositoryKeyFunction(String keyTypeString) {
87 RepositoryKeyType keyType = RepositoryKeyType.valueOf(keyTypeString.toUpperCase(Locale.ENGLISH));
88 switch (keyType) {
89 case SIMPLE:
90 return RepositoryIdHelper::simpleRepositoryKey;
91 case NID:
92 return RepositoryIdHelper::nidRepositoryKey;
93 case HURL:
94 return RepositoryIdHelper::hurlRepositoryKey;
95 case NID_HURL:
96 return RepositoryIdHelper::nidAndHurlRepositoryKey;
97 case NGURK:
98 return RepositoryIdHelper::normalizedGloballyUniqueRepositoryKey;
99 case GURK:
100 return RepositoryIdHelper::globallyUniqueRepositoryKey;
101 default:
102 throw new IllegalArgumentException("Unknown repository key type: " + keyType.name());
103 }
104 }
105
106 /**
107 * Simple {@code repositoryKey} function (classic). Returns {@link RemoteRepository#getId()}, unless
108 * {@link RemoteRepository#isRepositoryManager()} returns {@code true}, in which case this method creates
109 * unique identifier based on ID and current configuration of the remote repository and context.
110 * <p>
111 * This was the default {@code repositoryKey} method in Maven 3. Is exposed (others key methods are private) as
112 * it is directly used by "simple" LRM.
113 *
114 * @since 2.0.14
115 **/
116 public static String simpleRepositoryKey(RemoteRepository repository, String context) {
117 if (repository.isRepositoryManager()) {
118 StringBuilder buffer = new StringBuilder(128);
119 buffer.append(idToPathSegment(repository));
120 buffer.append('-');
121 SortedSet<String> subKeys = new TreeSet<>();
122 for (RemoteRepository mirroredRepo : repository.getMirroredRepositories()) {
123 subKeys.add(mirroredRepo.getId());
124 }
125 StringDigestUtil sha1 = StringDigestUtil.sha1();
126 sha1.update(context);
127 for (String subKey : subKeys) {
128 sha1.update(subKey);
129 }
130 buffer.append(sha1.digest());
131 return buffer.toString();
132 } else {
133 return idToPathSegment(repository);
134 }
135 }
136
137 /**
138 * The ID {@code repositoryKey} function that uses only the {@link RemoteRepository#getId()} value to derive a key.
139 *
140 * @since 2.0.14
141 **/
142 private static String nidRepositoryKey(RemoteRepository repository, String context) {
143 String seed = null;
144 if (repository.isRepositoryManager() && context != null && !context.isEmpty()) {
145 seed += context;
146 }
147 return idToPathSegment(repository) + (seed == null ? "" : "-" + StringDigestUtil.sha1(seed));
148 }
149
150 /**
151 * The URL {@code repositoryKey} function that uses only the {@link RemoteRepository#getUrl()} hash to derive a key.
152 *
153 * @since 2.0.14
154 **/
155 private static String hurlRepositoryKey(RemoteRepository repository, String context) {
156 String seed = null;
157 if (repository.isRepositoryManager() && context != null && !context.isEmpty()) {
158 seed += context;
159 }
160 return StringDigestUtil.sha1(repository.getUrl()) + (seed == null ? "" : "-" + StringDigestUtil.sha1(seed));
161 }
162
163 /**
164 * The ID and URL {@code repositoryKey} function. This method creates unique identifier based on ID and URL
165 * of the remote repository.
166 *
167 * @since 2.0.14
168 **/
169 private static String nidAndHurlRepositoryKey(RemoteRepository repository, String context) {
170 String seed = repository.getUrl();
171 if (repository.isRepositoryManager() && context != null && !context.isEmpty()) {
172 seed += context;
173 }
174 return idToPathSegment(repository) + "-" + StringDigestUtil.sha1(seed);
175 }
176
177 /**
178 * Normalized globally unique {@code repositoryKey} function. This method creates unique identifier based on ID and current
179 * configuration of the remote repository ignoring mirrors (it records the fact repository is a mirror, but ignores
180 * mirrored repositories). If {@link RemoteRepository#isRepositoryManager()} returns {@code true}, the passed in
181 * {@code context} string is factored in as well.
182 *
183 * @since 2.0.14
184 **/
185 private static String normalizedGloballyUniqueRepositoryKey(RemoteRepository repository, String context) {
186 String seed = remoteRepositoryDescription(repository, false);
187 if (repository.isRepositoryManager() && context != null && !context.isEmpty()) {
188 seed += context;
189 }
190 return idToPathSegment(repository) + "-" + StringDigestUtil.sha1(seed);
191 }
192
193 /**
194 * Globally unique {@code repositoryKey} function. This method creates unique identifier based on ID and current
195 * configuration of the remote repository. If {@link RemoteRepository#isRepositoryManager()} returns {@code true},
196 * the passed in {@code context} string is factored in as well.
197 * <p>
198 * <em>Important:</em> this repository key can be considered "stable" for normal remote repositories (where only
199 * ID and URL matters). But, for mirror repositories, the key will change if mirror members change.
200 *
201 * @since 2.0.14
202 **/
203 private static String globallyUniqueRepositoryKey(RemoteRepository repository, String context) {
204 String seed = remoteRepositoryDescription(repository, true);
205 if (repository.isRepositoryManager() && context != null && !context.isEmpty()) {
206 seed += context;
207 }
208 return idToPathSegment(repository) + "-" + StringDigestUtil.sha1(seed);
209 }
210
211 /**
212 * This method returns the passed in {@link ArtifactRepository#getId()} value, modifying it if needed, making sure that
213 * returned repository ID is "path segment" safe. Ideally, this method should never modify repository ID, as
214 * Maven validation prevents use of illegal FS characters in them, but we found in Maven Central several POMs that
215 * define remote repositories with illegal FS characters in their ID.
216 */
217 private static String idToPathSegment(ArtifactRepository repository) {
218 if (repository instanceof RemoteRepository) {
219 return PathUtils.stringToPathSegment(repository.getId());
220 } else {
221 return repository.getId();
222 }
223 }
224
225 /**
226 * Creates unique string for given {@link RemoteRepository}. Ignores following properties:
227 * <ul>
228 * <li>{@link RemoteRepository#getAuthentication()}</li>
229 * <li>{@link RemoteRepository#getProxy()}</li>
230 * <li>{@link RemoteRepository#getIntent()}</li>
231 * </ul>
232 */
233 private static String remoteRepositoryDescription(RemoteRepository repository, boolean mirrorDetails) {
234 StringBuilder buffer = new StringBuilder(256);
235 buffer.append(repository.getId());
236 buffer.append(" (").append(repository.getUrl());
237 buffer.append(", ").append(repository.getContentType());
238 boolean r = repository.getPolicy(false).isEnabled(),
239 s = repository.getPolicy(true).isEnabled();
240 if (r && s) {
241 buffer.append(", releases+snapshots");
242 } else if (r) {
243 buffer.append(", releases");
244 } else if (s) {
245 buffer.append(", snapshots");
246 } else {
247 buffer.append(", disabled");
248 }
249 if (repository.isRepositoryManager()) {
250 buffer.append(", managed");
251 }
252 if (!repository.getMirroredRepositories().isEmpty()) {
253 if (mirrorDetails) {
254 // sort them to make it stable ordering
255 ArrayList<RemoteRepository> mirroredRepositories =
256 new ArrayList<>(repository.getMirroredRepositories());
257 mirroredRepositories.sort(Comparator.comparing(RemoteRepository::getId));
258 buffer.append(", mirrorOf(");
259 for (RemoteRepository mirroredRepo : mirroredRepositories) {
260 buffer.append(remoteRepositoryDescription(mirroredRepo, true));
261 }
262 buffer.append(")");
263 } else {
264 buffer.append(", isMirror");
265 }
266 }
267 if (repository.isBlocked()) {
268 buffer.append(", blocked");
269 }
270 buffer.append(")");
271 return buffer.toString();
272 }
273 }