001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.util.repository; 020 021import java.util.ArrayList; 022import java.util.Comparator; 023import java.util.Locale; 024import java.util.SortedSet; 025import java.util.TreeSet; 026 027import org.eclipse.aether.repository.ArtifactRepository; 028import org.eclipse.aether.repository.RemoteRepository; 029import org.eclipse.aether.repository.RepositoryKeyFunction; 030import org.eclipse.aether.util.PathUtils; 031import org.eclipse.aether.util.StringDigestUtil; 032 033/** 034 * Helper class for {@link ArtifactRepository#getId()} handling. This class provides helper methods 035 * to get id of repository as it was originally envisioned: as path safe, unique, etc. While POMs are validated by Maven, 036 * there are POMs out there that somehow define repositories with unsafe characters in their id. The problem affects mostly 037 * {@link RemoteRepository} instances, as all other implementations have fixed ids that are path safe. 038 * <p> 039 * <em>Important:</em> multiple of these provided methods are not trivial processing-wise, and some sort of 040 * caching is warmly recommended. 041 * 042 * @see PathUtils 043 * @since 2.0.11 044 */ 045public final class RepositoryIdHelper { 046 private RepositoryIdHelper() {} 047 048 /** 049 * Supported {@code repositoryKey} types. 050 * 051 * @since 2.0.14 052 */ 053 public enum RepositoryKeyType { 054 /** 055 * The "simple" repository key, was default in Maven 3. 056 */ 057 SIMPLE, 058 /** 059 * Crafts repository key using normalized {@link RemoteRepository#getId()}. 060 */ 061 NID, 062 /** 063 * Crafts repository key using hashed {@link RemoteRepository#getUrl()}. 064 */ 065 HURL, 066 /** 067 * Crafts unique repository key using normalized {@link RemoteRepository#getId()} and hashed {@link RemoteRepository#getUrl()}. 068 */ 069 NID_HURL, 070 /** 071 * Crafts normalized unique repository key using {@link RemoteRepository#getId()} and all the remaining properties of 072 * {@link RemoteRepository} ignoring actual list of mirrors, if any (but mirrors are split). 073 */ 074 NGURK, 075 /** 076 * Crafts unique repository key using {@link RemoteRepository#getId()} and all the remaining properties of 077 * {@link RemoteRepository}. 078 */ 079 GURK 080 } 081 082 /** 083 * Selector method for {@link RepositoryKeyFunction} based on string representation of {@link RepositoryKeyType} 084 * enum. 085 */ 086 public static RepositoryKeyFunction getRepositoryKeyFunction(String keyTypeString) { 087 RepositoryKeyType keyType = RepositoryKeyType.valueOf(keyTypeString.toUpperCase(Locale.ENGLISH)); 088 switch (keyType) { 089 case SIMPLE: 090 return RepositoryIdHelper::simpleRepositoryKey; 091 case NID: 092 return RepositoryIdHelper::nidRepositoryKey; 093 case HURL: 094 return RepositoryIdHelper::hurlRepositoryKey; 095 case NID_HURL: 096 return RepositoryIdHelper::nidAndHurlRepositoryKey; 097 case NGURK: 098 return RepositoryIdHelper::normalizedGloballyUniqueRepositoryKey; 099 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}