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