1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.internal.impl.filter;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.io.BufferedReader;
26 import java.io.IOException;
27 import java.io.UncheckedIOException;
28 import java.nio.charset.StandardCharsets;
29 import java.nio.file.Files;
30 import java.nio.file.Path;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36 import java.util.TreeSet;
37 import java.util.concurrent.ConcurrentHashMap;
38 import java.util.concurrent.atomic.AtomicBoolean;
39
40 import org.eclipse.aether.MultiRuntimeException;
41 import org.eclipse.aether.RepositorySystemSession;
42 import org.eclipse.aether.artifact.Artifact;
43 import org.eclipse.aether.impl.RepositorySystemLifecycle;
44 import org.eclipse.aether.metadata.Metadata;
45 import org.eclipse.aether.repository.RemoteRepository;
46 import org.eclipse.aether.resolution.ArtifactResult;
47 import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
48 import org.eclipse.aether.spi.resolution.ArtifactResolverPostProcessor;
49 import org.eclipse.aether.util.ConfigUtils;
50 import org.eclipse.aether.util.FileUtils;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 import static java.util.Objects.requireNonNull;
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 @Singleton
73 @Named(GroupIdRemoteRepositoryFilterSource.NAME)
74 public final class GroupIdRemoteRepositoryFilterSource extends RemoteRepositoryFilterSourceSupport
75 implements ArtifactResolverPostProcessor {
76 public static final String NAME = "groupId";
77
78 private static final String CONFIG_PROPS_PREFIX =
79 RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME + ".";
80
81
82
83
84
85
86
87
88 public static final String CONFIG_PROP_ENABLED = RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME;
89
90
91
92
93
94
95
96
97 public static final String CONFIG_PROP_BASEDIR = CONFIG_PROPS_PREFIX + "basedir";
98
99 public static final String LOCAL_REPO_PREFIX_DIR = ".remoteRepositoryFilters";
100
101
102
103
104
105
106
107
108 public static final String CONFIG_PROP_RECORD = CONFIG_PROPS_PREFIX + "record";
109
110 static final String GROUP_ID_FILE_PREFIX = "groupId-";
111
112 static final String GROUP_ID_FILE_SUFFIX = ".txt";
113
114 private static final Logger LOGGER = LoggerFactory.getLogger(GroupIdRemoteRepositoryFilterSource.class);
115
116 private final RepositorySystemLifecycle repositorySystemLifecycle;
117
118 private final ConcurrentHashMap<Path, Set<String>> rules;
119
120 private final ConcurrentHashMap<Path, Boolean> changedRules;
121
122 private final AtomicBoolean onShutdownHandlerRegistered;
123
124 @Inject
125 public GroupIdRemoteRepositoryFilterSource(RepositorySystemLifecycle repositorySystemLifecycle) {
126 this.repositorySystemLifecycle = requireNonNull(repositorySystemLifecycle);
127 this.rules = new ConcurrentHashMap<>();
128 this.changedRules = new ConcurrentHashMap<>();
129 this.onShutdownHandlerRegistered = new AtomicBoolean(false);
130 }
131
132 @Override
133 protected boolean isEnabled(RepositorySystemSession session) {
134 return ConfigUtils.getBoolean(session, false, CONFIG_PROP_ENABLED);
135 }
136
137 @Override
138 public RemoteRepositoryFilter getRemoteRepositoryFilter(RepositorySystemSession session) {
139 if (isEnabled(session) && !isRecord(session)) {
140 return new GroupIdFilter(session);
141 }
142 return null;
143 }
144
145 @Override
146 public void postProcess(RepositorySystemSession session, List<ArtifactResult> artifactResults) {
147 if (isEnabled(session) && isRecord(session)) {
148 if (onShutdownHandlerRegistered.compareAndSet(false, true)) {
149 repositorySystemLifecycle.addOnSystemEndedHandler(this::saveRecordedLines);
150 }
151 for (ArtifactResult artifactResult : artifactResults) {
152 if (artifactResult.isResolved() && artifactResult.getRepository() instanceof RemoteRepository) {
153 Path filePath = filePath(
154 getBasedir(session, LOCAL_REPO_PREFIX_DIR, CONFIG_PROP_BASEDIR, false),
155 artifactResult.getRepository().getId());
156 boolean newGroupId = rules.computeIfAbsent(
157 filePath, f -> Collections.synchronizedSet(new TreeSet<>()))
158 .add(artifactResult.getArtifact().getGroupId());
159 if (newGroupId) {
160 changedRules.put(filePath, Boolean.TRUE);
161 }
162 }
163 }
164 }
165 }
166
167
168
169
170 private Path filePath(Path basedir, String remoteRepositoryId) {
171 return basedir.resolve(GROUP_ID_FILE_PREFIX + remoteRepositoryId + GROUP_ID_FILE_SUFFIX);
172 }
173
174 private Set<String> cacheRules(RepositorySystemSession session, RemoteRepository remoteRepository) {
175 Path filePath = filePath(
176 getBasedir(session, LOCAL_REPO_PREFIX_DIR, CONFIG_PROP_BASEDIR, false), remoteRepository.getId());
177 return rules.computeIfAbsent(filePath, r -> {
178 Set<String> rules = loadRepositoryRules(filePath);
179 if (rules != NOT_PRESENT) {
180 LOGGER.info("Loaded {} groupId for remote repository {}", rules.size(), remoteRepository.getId());
181 }
182 return rules;
183 });
184 }
185
186 private Set<String> loadRepositoryRules(Path filePath) {
187 if (Files.isReadable(filePath)) {
188 try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) {
189 TreeSet<String> result = new TreeSet<>();
190 String groupId;
191 while ((groupId = reader.readLine()) != null) {
192 if (!groupId.startsWith("#") && !groupId.trim().isEmpty()) {
193 result.add(groupId);
194 }
195 }
196 return Collections.unmodifiableSet(result);
197 } catch (IOException e) {
198 throw new UncheckedIOException(e);
199 }
200 }
201 return NOT_PRESENT;
202 }
203
204 private static final TreeSet<String> NOT_PRESENT = new TreeSet<>();
205
206 private class GroupIdFilter implements RemoteRepositoryFilter {
207 private final RepositorySystemSession session;
208
209 private GroupIdFilter(RepositorySystemSession session) {
210 this.session = session;
211 }
212
213 @Override
214 public Result acceptArtifact(RemoteRepository remoteRepository, Artifact artifact) {
215 return acceptGroupId(remoteRepository, artifact.getGroupId());
216 }
217
218 @Override
219 public Result acceptMetadata(RemoteRepository remoteRepository, Metadata metadata) {
220 return acceptGroupId(remoteRepository, metadata.getGroupId());
221 }
222
223 private Result acceptGroupId(RemoteRepository remoteRepository, String groupId) {
224 Set<String> groupIds = cacheRules(session, remoteRepository);
225 if (NOT_PRESENT == groupIds) {
226 return NOT_PRESENT_RESULT;
227 }
228
229 if (groupIds.contains(groupId)) {
230 return new SimpleResult(true, "G:" + groupId + " allowed from " + remoteRepository);
231 } else {
232 return new SimpleResult(false, "G:" + groupId + " NOT allowed from " + remoteRepository);
233 }
234 }
235 }
236
237 private static final RemoteRepositoryFilter.Result NOT_PRESENT_RESULT =
238 new SimpleResult(true, "GroupId file not present");
239
240
241
242
243 private boolean isRecord(RepositorySystemSession session) {
244 return ConfigUtils.getBoolean(session, false, CONFIG_PROP_RECORD);
245 }
246
247
248
249
250 private void saveRecordedLines() {
251 if (changedRules.isEmpty()) {
252 return;
253 }
254
255 ArrayList<Exception> exceptions = new ArrayList<>();
256 for (Map.Entry<Path, Set<String>> entry : rules.entrySet()) {
257 Path filePath = entry.getKey();
258 if (changedRules.get(filePath) != Boolean.TRUE) {
259 continue;
260 }
261 Set<String> recordedLines = entry.getValue();
262 if (!recordedLines.isEmpty()) {
263 try {
264 TreeSet<String> result = new TreeSet<>();
265 result.addAll(loadRepositoryRules(filePath));
266 result.addAll(recordedLines);
267
268 LOGGER.info("Saving {} groupIds to '{}'", result.size(), filePath);
269 FileUtils.writeFileWithBackup(filePath, p -> Files.write(p, result));
270 } catch (IOException e) {
271 exceptions.add(e);
272 }
273 }
274 }
275 MultiRuntimeException.mayThrow("session save groupIds failure", exceptions);
276 }
277 }