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.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 }