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 CONF_NAME_RECORD = "record";
79
80 static final String GROUP_ID_FILE_PREFIX = "groupId-";
81
82 static final String GROUP_ID_FILE_SUFFIX = ".txt";
83
84 private static final Logger LOGGER = LoggerFactory.getLogger(GroupIdRemoteRepositoryFilterSource.class);
85
86 private final RepositorySystemLifecycle repositorySystemLifecycle;
87
88 private final ConcurrentHashMap<Path, Set<String>> rules;
89
90 private final ConcurrentHashMap<Path, Boolean> changedRules;
91
92 private final AtomicBoolean onShutdownHandlerRegistered;
93
94 @Inject
95 public GroupIdRemoteRepositoryFilterSource(RepositorySystemLifecycle repositorySystemLifecycle) {
96 super(NAME);
97 this.repositorySystemLifecycle = requireNonNull(repositorySystemLifecycle);
98 this.rules = new ConcurrentHashMap<>();
99 this.changedRules = new ConcurrentHashMap<>();
100 this.onShutdownHandlerRegistered = new AtomicBoolean(false);
101 }
102
103 @Override
104 public RemoteRepositoryFilter getRemoteRepositoryFilter(RepositorySystemSession session) {
105 if (isEnabled(session) && !isRecord(session)) {
106 return new GroupIdFilter(session);
107 }
108 return null;
109 }
110
111 @Override
112 public void postProcess(RepositorySystemSession session, List<ArtifactResult> artifactResults) {
113 if (isEnabled(session) && isRecord(session)) {
114 if (onShutdownHandlerRegistered.compareAndSet(false, true)) {
115 repositorySystemLifecycle.addOnSystemEndedHandler(this::saveRecordedLines);
116 }
117 for (ArtifactResult artifactResult : artifactResults) {
118 if (artifactResult.isResolved() && artifactResult.getRepository() instanceof RemoteRepository) {
119 Path filePath = filePath(
120 getBasedir(session, false),
121 artifactResult.getRepository().getId());
122 boolean newGroupId = rules.computeIfAbsent(
123 filePath, f -> Collections.synchronizedSet(new TreeSet<>()))
124 .add(artifactResult.getArtifact().getGroupId());
125 if (newGroupId) {
126 changedRules.put(filePath, Boolean.TRUE);
127 }
128 }
129 }
130 }
131 }
132
133
134
135
136 private Path filePath(Path basedir, String remoteRepositoryId) {
137 return basedir.resolve(GROUP_ID_FILE_PREFIX + remoteRepositoryId + GROUP_ID_FILE_SUFFIX);
138 }
139
140 private Set<String> cacheRules(RepositorySystemSession session, RemoteRepository remoteRepository) {
141 Path filePath = filePath(getBasedir(session, false), remoteRepository.getId());
142 return rules.computeIfAbsent(filePath, r -> {
143 Set<String> rules = loadRepositoryRules(filePath);
144 if (rules != NOT_PRESENT) {
145 LOGGER.info("Loaded {} groupId for remote repository {}", rules.size(), remoteRepository.getId());
146 }
147 return rules;
148 });
149 }
150
151 private Set<String> loadRepositoryRules(Path filePath) {
152 if (Files.isReadable(filePath)) {
153 try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) {
154 TreeSet<String> result = new TreeSet<>();
155 String groupId;
156 while ((groupId = reader.readLine()) != null) {
157 if (!groupId.startsWith("#") && !groupId.trim().isEmpty()) {
158 result.add(groupId);
159 }
160 }
161 return Collections.unmodifiableSet(result);
162 } catch (IOException e) {
163 throw new UncheckedIOException(e);
164 }
165 }
166 return NOT_PRESENT;
167 }
168
169 private static final TreeSet<String> NOT_PRESENT = new TreeSet<>();
170
171 private class GroupIdFilter implements RemoteRepositoryFilter {
172 private final RepositorySystemSession session;
173
174 private GroupIdFilter(RepositorySystemSession session) {
175 this.session = session;
176 }
177
178 @Override
179 public Result acceptArtifact(RemoteRepository remoteRepository, Artifact artifact) {
180 return acceptGroupId(remoteRepository, artifact.getGroupId());
181 }
182
183 @Override
184 public Result acceptMetadata(RemoteRepository remoteRepository, Metadata metadata) {
185 return acceptGroupId(remoteRepository, metadata.getGroupId());
186 }
187
188 private Result acceptGroupId(RemoteRepository remoteRepository, String groupId) {
189 Set<String> groupIds = cacheRules(session, remoteRepository);
190 if (NOT_PRESENT == groupIds) {
191 return NOT_PRESENT_RESULT;
192 }
193
194 if (groupIds.contains(groupId)) {
195 return new SimpleResult(true, "G:" + groupId + " allowed from " + remoteRepository);
196 } else {
197 return new SimpleResult(false, "G:" + groupId + " NOT allowed from " + remoteRepository);
198 }
199 }
200 }
201
202 private static final RemoteRepositoryFilter.Result NOT_PRESENT_RESULT =
203 new SimpleResult(true, "GroupId file not present");
204
205
206
207
208 private boolean isRecord(RepositorySystemSession session) {
209 return ConfigUtils.getBoolean(session, false, configPropKey(CONF_NAME_RECORD));
210 }
211
212
213
214
215 private void saveRecordedLines() {
216 if (changedRules.isEmpty()) {
217 return;
218 }
219
220 ArrayList<Exception> exceptions = new ArrayList<>();
221 for (Map.Entry<Path, Set<String>> entry : rules.entrySet()) {
222 Path filePath = entry.getKey();
223 if (changedRules.get(filePath) != Boolean.TRUE) {
224 continue;
225 }
226 Set<String> recordedLines = entry.getValue();
227 if (!recordedLines.isEmpty()) {
228 try {
229 TreeSet<String> result = new TreeSet<>();
230 result.addAll(loadRepositoryRules(filePath));
231 result.addAll(recordedLines);
232
233 LOGGER.info("Saving {} groupIds to '{}'", result.size(), filePath);
234 FileUtils.writeFileWithBackup(filePath, p -> Files.write(p, result));
235 } catch (IOException e) {
236 exceptions.add(e);
237 }
238 }
239 }
240 MultiRuntimeException.mayThrow("session save groupIds failure", exceptions);
241 }
242 }