Skip to content

Commit edc21c7

Browse files
authored
Merge pull request #7 from maveniverse/autodiscover-prefixes
Make Heimdall auto-discover prefixes, while it still can use locally provided ones.
2 parents 23348c1 + f2f513d commit edc21c7

File tree

6 files changed

+125
-17993
lines changed

6 files changed

+125
-17993
lines changed

core/src/main/java/eu/maveniverse/maven/heimdall/shared/Session.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,9 @@ public interface Session extends Closeable {
1414
* Returns this session configuration.
1515
*/
1616
SessionConfig config();
17+
18+
/**
19+
* Allows one to register a hook called just before this session is closed.
20+
*/
21+
void registerOnCloseHook(Runnable onCloseHook);
1722
}

core/src/main/java/eu/maveniverse/maven/heimdall/shared/impl/DefaultSession.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,42 @@
77
*/
88
package eu.maveniverse.maven.heimdall.shared.impl;
99

10+
import static java.util.Objects.requireNonNull;
11+
1012
import eu.maveniverse.maven.heimdall.shared.Session;
1113
import eu.maveniverse.maven.heimdall.shared.SessionConfig;
1214
import eu.maveniverse.maven.shared.core.component.CloseableConfigSupport;
15+
import java.io.IOException;
16+
import java.util.concurrent.CopyOnWriteArrayList;
1317

1418
public class DefaultSession extends CloseableConfigSupport<SessionConfig> implements Session {
19+
private final CopyOnWriteArrayList<Runnable> hooks;
20+
1521
public DefaultSession(SessionConfig sessionConfig) {
1622
super(sessionConfig);
23+
this.hooks = new CopyOnWriteArrayList<>();
1724
}
1825

1926
@Override
2027
public SessionConfig config() {
2128
return config;
2229
}
30+
31+
@Override
32+
public void registerOnCloseHook(Runnable onCloseHook) {
33+
requireNonNull(onCloseHook);
34+
checkClosed();
35+
this.hooks.add(onCloseHook);
36+
}
37+
38+
@Override
39+
protected void doClose() throws IOException {
40+
for (Runnable hook : hooks) {
41+
try {
42+
hook.run();
43+
} catch (Exception e) {
44+
logger.warn(e.getMessage());
45+
}
46+
}
47+
}
2348
}

core/src/main/java/eu/maveniverse/maven/heimdall/shared/impl/GroupIdRemoteRepositoryFilterSource.java

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@
2626
import java.nio.charset.StandardCharsets;
2727
import java.nio.file.Files;
2828
import java.nio.file.Path;
29-
import java.util.Collections;
3029
import java.util.Optional;
31-
import java.util.Set;
3230
import java.util.TreeSet;
3331
import java.util.concurrent.ConcurrentHashMap;
3432
import javax.inject.Inject;
@@ -39,8 +37,6 @@
3937
import org.eclipse.aether.metadata.Metadata;
4038
import org.eclipse.aether.repository.RemoteRepository;
4139
import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
42-
import org.slf4j.Logger;
43-
import org.slf4j.LoggerFactory;
4440

4541
/**
4642
* Remote repository filter source filtering on G coordinate. It is backed by a file that lists all allowed groupIds
@@ -67,9 +63,7 @@ public final class GroupIdRemoteRepositoryFilterSource extends RemoteRepositoryF
6763

6864
static final String GROUP_ID_FILE_SUFFIX = ".txt";
6965

70-
private static final Logger LOGGER = LoggerFactory.getLogger(GroupIdRemoteRepositoryFilterSource.class);
71-
72-
private final ConcurrentHashMap<Path, Set<String>> rules;
66+
private final ConcurrentHashMap<Path, GroupIds> rules;
7367

7468
@Inject
7569
public GroupIdRemoteRepositoryFilterSource() {
@@ -81,7 +75,7 @@ public GroupIdRemoteRepositoryFilterSource() {
8175
public RemoteRepositoryFilter getRemoteRepositoryFilter(RepositorySystemSession session) {
8276
Optional<Session> so = SessionUtils.mayGetSession(session);
8377
if (so.isPresent() && isEnabled(session)) {
84-
return new GroupIdFilter(session);
78+
return new GroupIdFilter(so.orElseThrow(J8Utils.OET), session);
8579
}
8680
return null;
8781
}
@@ -93,19 +87,21 @@ private Path filePath(Path basedir, String remoteRepositoryId) {
9387
return basedir.resolve(GROUP_ID_FILE_PREFIX + remoteRepositoryId + GROUP_ID_FILE_SUFFIX);
9488
}
9589

96-
private Set<String> cacheRules(RepositorySystemSession session, RemoteRepository remoteRepository) {
90+
private GroupIds cacheRules(RepositorySystemSession session, RemoteRepository remoteRepository) {
9791
Path filePath = filePath(getBasedir(session, false), remoteRepository.getId());
9892
return rules.computeIfAbsent(filePath, r -> {
99-
Set<String> rules = loadRepositoryRules(filePath);
93+
GroupIds rules = loadRepositoryRules(filePath);
10094
if (rules != NOT_PRESENT) {
101-
LOGGER.info(
102-
"Heimdall loaded {} groupId for remote repository {}", rules.size(), remoteRepository.getId());
95+
logger.info(
96+
"Heimdall loaded {} groupId for remote repository {}",
97+
rules.ruleCount(),
98+
remoteRepository.getId());
10399
}
104100
return rules;
105101
});
106102
}
107103

108-
private Set<String> loadRepositoryRules(Path filePath) {
104+
private GroupIds loadRepositoryRules(Path filePath) {
109105
if (Files.isReadable(filePath)) {
110106
try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) {
111107
TreeSet<String> result = new TreeSet<>();
@@ -115,21 +111,23 @@ private Set<String> loadRepositoryRules(Path filePath) {
115111
result.add(groupId);
116112
}
117113
}
118-
return Collections.unmodifiableSet(result);
114+
return new GroupIds(result);
119115
} catch (IOException e) {
120116
throw new UncheckedIOException(e);
121117
}
122118
}
123119
return NOT_PRESENT;
124120
}
125121

126-
private static final TreeSet<String> NOT_PRESENT = new TreeSet<>();
122+
private static final GroupIds NOT_PRESENT = new GroupIds(new TreeSet<>());
127123

128124
private class GroupIdFilter implements RemoteRepositoryFilter {
129-
private final RepositorySystemSession session;
125+
private final Session session;
126+
private final RepositorySystemSession repoSession;
130127

131-
private GroupIdFilter(RepositorySystemSession session) {
128+
private GroupIdFilter(Session session, RepositorySystemSession repoSession) {
132129
this.session = session;
130+
this.repoSession = repoSession;
133131
}
134132

135133
@Override
@@ -143,12 +141,12 @@ public Result acceptMetadata(RemoteRepository remoteRepository, Metadata metadat
143141
}
144142

145143
private Result acceptGroupId(RemoteRepository remoteRepository, String groupId) {
146-
Set<String> groupIds = cacheRules(session, remoteRepository);
144+
GroupIds groupIds = cacheRules(repoSession, remoteRepository);
147145
if (NOT_PRESENT == groupIds) {
148146
return NOT_PRESENT_RESULT;
149147
}
150148

151-
if (groupIds.contains(groupId)) {
149+
if (groupIds.accepted(groupId)) {
152150
return new SimpleResult(true, "G:" + groupId + " allowed from " + remoteRepository);
153151
} else {
154152
return new SimpleResult(false, "G:" + groupId + " NOT allowed from " + remoteRepository);
@@ -158,4 +156,20 @@ private Result acceptGroupId(RemoteRepository remoteRepository, String groupId)
158156

159157
private static final RemoteRepositoryFilter.Result NOT_PRESENT_RESULT =
160158
new SimpleResult(true, "GroupId file not present");
159+
160+
private static class GroupIds {
161+
private final TreeSet<String> groupIds;
162+
163+
public GroupIds(TreeSet<String> groupIds) {
164+
this.groupIds = groupIds;
165+
}
166+
167+
public int ruleCount() {
168+
return groupIds.size();
169+
}
170+
171+
public boolean accepted(String groupId) {
172+
return groupIds.contains(groupId);
173+
}
174+
}
161175
}

core/src/main/java/eu/maveniverse/maven/heimdall/shared/impl/PrefixesRemoteRepositoryFilterSource.java

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,26 @@
3232
import java.nio.file.Path;
3333
import java.util.ArrayList;
3434
import java.util.Arrays;
35+
import java.util.Collections;
3536
import java.util.HashMap;
3637
import java.util.List;
3738
import java.util.Optional;
3839
import java.util.concurrent.ConcurrentHashMap;
3940
import javax.inject.Inject;
4041
import javax.inject.Named;
4142
import javax.inject.Singleton;
43+
import org.eclipse.aether.RepositorySystem;
4244
import org.eclipse.aether.RepositorySystemSession;
4345
import org.eclipse.aether.artifact.Artifact;
46+
import org.eclipse.aether.metadata.DefaultMetadata;
4447
import org.eclipse.aether.metadata.Metadata;
4548
import org.eclipse.aether.repository.RemoteRepository;
49+
import org.eclipse.aether.resolution.MetadataRequest;
50+
import org.eclipse.aether.resolution.MetadataResult;
4651
import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
4752
import org.eclipse.aether.spi.connector.layout.RepositoryLayout;
4853
import org.eclipse.aether.spi.connector.layout.RepositoryLayoutProvider;
4954
import org.eclipse.aether.transfer.NoRepositoryLayoutException;
50-
import org.slf4j.Logger;
51-
import org.slf4j.LoggerFactory;
5255

5356
/**
5457
* Remote repository filter source filtering on path prefixes. It is backed by a file that lists all allowed path
@@ -82,7 +85,7 @@ public final class PrefixesRemoteRepositoryFilterSource extends RemoteRepository
8285

8386
static final String PREFIXES_FILE_SUFFIX = ".txt";
8487

85-
private static final Logger LOGGER = LoggerFactory.getLogger(PrefixesRemoteRepositoryFilterSource.class);
88+
private final RepositorySystem repositorySystem;
8689

8790
private final RepositoryLayoutProvider repositoryLayoutProvider;
8891

@@ -91,8 +94,10 @@ public final class PrefixesRemoteRepositoryFilterSource extends RemoteRepository
9194
private final ConcurrentHashMap<RemoteRepository, RepositoryLayout> layouts;
9295

9396
@Inject
94-
public PrefixesRemoteRepositoryFilterSource(RepositoryLayoutProvider repositoryLayoutProvider) {
97+
public PrefixesRemoteRepositoryFilterSource(
98+
RepositorySystem repositorySystem, RepositoryLayoutProvider repositoryLayoutProvider) {
9599
super(NAME);
100+
this.repositorySystem = requireNonNull(repositorySystem);
96101
this.repositoryLayoutProvider = requireNonNull(repositoryLayoutProvider);
97102
this.prefixes = new ConcurrentHashMap<>();
98103
this.layouts = new ConcurrentHashMap<>();
@@ -102,7 +107,7 @@ public PrefixesRemoteRepositoryFilterSource(RepositoryLayoutProvider repositoryL
102107
public RemoteRepositoryFilter getRemoteRepositoryFilter(RepositorySystemSession session) {
103108
Optional<Session> so = SessionUtils.mayGetSession(session);
104109
if (so.isPresent() && isEnabled(session)) {
105-
return new PrefixesFilter(session, getBasedir(session, false));
110+
return new PrefixesFilter(so.orElseThrow(J8Utils.OET), session, getBasedir(session, false));
106111
}
107112
return null;
108113
}
@@ -122,21 +127,35 @@ private RepositoryLayout cacheLayout(RepositorySystemSession session, RemoteRepo
122127
});
123128
}
124129

130+
private final ConcurrentHashMap<RemoteRepository, Boolean> ongoingUpdates = new ConcurrentHashMap<>();
131+
125132
/**
126133
* Caches prefixes instances for remote repository.
127134
*/
128-
private Node cacheNode(Path basedir, RemoteRepository remoteRepository) {
129-
return prefixes.computeIfAbsent(remoteRepository, r -> loadRepositoryPrefixes(basedir, remoteRepository));
135+
private Node cacheNode(RepositorySystemSession session, Path basedir, RemoteRepository remoteRepository) {
136+
if (!remoteRepository.isBlocked() && null == ongoingUpdates.putIfAbsent(remoteRepository, Boolean.TRUE)) {
137+
try {
138+
return prefixes.computeIfAbsent(
139+
remoteRepository, r -> loadRepositoryPrefixes(session, basedir, remoteRepository));
140+
} finally {
141+
ongoingUpdates.remove(remoteRepository);
142+
}
143+
}
144+
return NOT_PRESENT_NODE;
130145
}
131146

132147
/**
133148
* Loads prefixes file and preprocesses it into {@link Node} instance.
134149
*/
135-
private Node loadRepositoryPrefixes(Path baseDir, RemoteRepository remoteRepository) {
136-
Path filePath = baseDir.resolve(PREFIXES_FILE_PREFIX + remoteRepository.getId() + PREFIXES_FILE_SUFFIX);
150+
private Node loadRepositoryPrefixes(
151+
RepositorySystemSession session, Path baseDir, RemoteRepository remoteRepository) {
152+
Path filePath = resolvePrefixesFromRemoteRepository(session, remoteRepository);
153+
if (filePath == null) {
154+
filePath = baseDir.resolve(PREFIXES_FILE_PREFIX + remoteRepository.getId() + PREFIXES_FILE_SUFFIX);
155+
}
137156
if (Files.isReadable(filePath)) {
138157
try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) {
139-
LOGGER.debug(
158+
logger.debug(
140159
"Loading prefixes for remote repository {} from file '{}'", remoteRepository.getId(), filePath);
141160
Node root = new Node("");
142161
String prefix;
@@ -150,31 +169,49 @@ private Node loadRepositoryPrefixes(Path baseDir, RemoteRepository remoteReposit
150169
}
151170
}
152171
}
153-
LOGGER.info("Heimdall loaded {} prefixes for remote repository {}", lines, remoteRepository.getId());
172+
logger.info("Heimdall loaded {} prefixes for remote repository {}", lines, remoteRepository.getId());
154173
return root;
155174
} catch (FileNotFoundException e) {
156175
// strange: we tested for it above, still, we should not fail
157176
} catch (IOException e) {
158177
throw new UncheckedIOException(e);
159178
}
160179
}
161-
LOGGER.debug("Prefix file for remote repository {} not found at '{}'", remoteRepository, filePath);
180+
logger.debug("Prefix file for remote repository {} not found at '{}'", remoteRepository, filePath);
162181
return NOT_PRESENT_NODE;
163182
}
164183

165-
private class PrefixesFilter implements RemoteRepositoryFilter {
166-
private final RepositorySystemSession session;
184+
private Path resolvePrefixesFromRemoteRepository(
185+
RepositorySystemSession session, RemoteRepository remoteRepository) {
186+
MetadataRequest request =
187+
new MetadataRequest(new DefaultMetadata(".meta/prefixes.txt", Metadata.Nature.RELEASE_OR_SNAPSHOT));
188+
request.setRepository(remoteRepository);
189+
request.setDeleteLocalCopyIfMissing(true);
190+
request.setFavorLocalRepository(true);
191+
MetadataResult result = repositorySystem
192+
.resolveMetadata(session, Collections.singleton(request))
193+
.get(0);
194+
if (result.isResolved()) {
195+
return result.getMetadata().getFile().toPath();
196+
} else {
197+
return null;
198+
}
199+
}
167200

201+
private class PrefixesFilter implements RemoteRepositoryFilter {
202+
private final Session session;
203+
private final RepositorySystemSession repoSession;
168204
private final Path basedir;
169205

170-
private PrefixesFilter(RepositorySystemSession session, Path basedir) {
206+
private PrefixesFilter(Session session, RepositorySystemSession repoSession, Path basedir) {
171207
this.session = session;
208+
this.repoSession = repoSession;
172209
this.basedir = basedir;
173210
}
174211

175212
@Override
176213
public Result acceptArtifact(RemoteRepository remoteRepository, Artifact artifact) {
177-
RepositoryLayout repositoryLayout = cacheLayout(session, remoteRepository);
214+
RepositoryLayout repositoryLayout = cacheLayout(repoSession, remoteRepository);
178215
if (repositoryLayout == null) {
179216
return new SimpleResult(true, "Unsupported layout: " + remoteRepository);
180217
}
@@ -185,7 +222,7 @@ public Result acceptArtifact(RemoteRepository remoteRepository, Artifact artifac
185222

186223
@Override
187224
public Result acceptMetadata(RemoteRepository remoteRepository, Metadata metadata) {
188-
RepositoryLayout repositoryLayout = cacheLayout(session, remoteRepository);
225+
RepositoryLayout repositoryLayout = cacheLayout(repoSession, remoteRepository);
189226
if (repositoryLayout == null) {
190227
return new SimpleResult(true, "Unsupported layout: " + remoteRepository);
191228
}
@@ -195,7 +232,10 @@ public Result acceptMetadata(RemoteRepository remoteRepository, Metadata metadat
195232
}
196233

197234
private Result acceptPrefix(RemoteRepository remoteRepository, String path) {
198-
Node root = cacheNode(basedir, remoteRepository);
235+
if (!isEnabled(repoSession)) {
236+
return NOT_PRESENT_RESULT;
237+
}
238+
Node root = cacheNode(repoSession, basedir, remoteRepository);
199239
if (NOT_PRESENT_NODE == root) {
200240
return NOT_PRESENT_RESULT;
201241
}
@@ -243,12 +283,7 @@ public boolean isLeaf() {
243283
}
244284

245285
public Node addSibling(String name) {
246-
Node sibling = siblings.get(name);
247-
if (sibling == null) {
248-
sibling = new Node(name);
249-
siblings.put(name, sibling);
250-
}
251-
return sibling;
286+
return siblings.computeIfAbsent(name, Node::new);
252287
}
253288

254289
public Node getSibling(String name) {

core/src/main/java/eu/maveniverse/maven/heimdall/shared/impl/RemoteRepositoryFilterSourceSupport.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import static java.util.Objects.requireNonNull;
2222

23+
import eu.maveniverse.maven.shared.core.component.ComponentSupport;
2324
import java.io.IOException;
2425
import java.io.UncheckedIOException;
2526
import java.nio.file.Path;
@@ -46,7 +47,8 @@
4647
*
4748
* @since 1.9.0
4849
*/
49-
public abstract class RemoteRepositoryFilterSourceSupport implements RemoteRepositoryFilterSource {
50+
public abstract class RemoteRepositoryFilterSourceSupport extends ComponentSupport
51+
implements RemoteRepositoryFilterSource {
5052
private static final String CONFIG_PROP_PREFIX = "heimdall.";
5153

5254
private static final String CONF_NAME_BASEDIR = "basedir";

0 commit comments

Comments
 (0)