shaded.org.eclipse.aether.internal.impl.filter.GroupIdRemoteRepositoryFilterSource Maven / Gradle / Ivy
Show all versions of spring-cloud-contract-shade Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package shaded.shaded.org.eclipse.aether.internal.impl.filter;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.aether.MultiRuntimeException;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import shaded.shaded.org.eclipse.aether.impl.RepositorySystemLifecycle;
import org.eclipse.aether.metadata.Metadata;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactResult;
import shaded.shaded.org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
import shaded.shaded.org.eclipse.aether.spi.resolution.ArtifactResolverPostProcessor;
import org.eclipse.aether.util.ConfigUtils;
import org.eclipse.aether.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.util.Objects.requireNonNull;
/**
* Remote repository filter source filtering on G coordinate. It is backed by a file that lists all allowed groupIds
* and groupId not present in this file are filtered out.
*
* The file can be authored manually: format is one groupId per line, comments starting with "#" (hash) amd empty lines
* for structuring are supported. The file can also be pre-populated by "record" functionality of this filter.
* When "recording", this filter will not filter out anything, but will instead populate the file with all encountered
* groupIds.
*
* The groupId file is expected on path "${basedir}/groupId-${repository.id}.txt".
*
* The groupId file once loaded are cached in component, so in-flight groupId file change during component existence
* are NOT noticed.
*
* @since 1.9.0
*/
@Singleton
@Named(GroupIdRemoteRepositoryFilterSource.NAME)
public final class GroupIdRemoteRepositoryFilterSource extends RemoteRepositoryFilterSourceSupport
implements ArtifactResolverPostProcessor {
public static final String NAME = "groupId";
private static final String CONF_NAME_RECORD = "record";
static final String GROUP_ID_FILE_PREFIX = "groupId-";
static final String GROUP_ID_FILE_SUFFIX = ".txt";
private static final Logger LOGGER = LoggerFactory.getLogger(GroupIdRemoteRepositoryFilterSource.class);
private final RepositorySystemLifecycle repositorySystemLifecycle;
private final ConcurrentHashMap> rules;
private final ConcurrentHashMap changedRules;
private final AtomicBoolean onShutdownHandlerRegistered;
@Inject
public GroupIdRemoteRepositoryFilterSource(RepositorySystemLifecycle repositorySystemLifecycle) {
super(NAME);
this.repositorySystemLifecycle = requireNonNull(repositorySystemLifecycle);
this.rules = new ConcurrentHashMap<>();
this.changedRules = new ConcurrentHashMap<>();
this.onShutdownHandlerRegistered = new AtomicBoolean(false);
}
@Override
public RemoteRepositoryFilter getRemoteRepositoryFilter(RepositorySystemSession session) {
if (isEnabled(session) && !isRecord(session)) {
return new GroupIdFilter(session);
}
return null;
}
@Override
public void postProcess(RepositorySystemSession session, List artifactResults) {
if (isEnabled(session) && isRecord(session)) {
if (onShutdownHandlerRegistered.compareAndSet(false, true)) {
repositorySystemLifecycle.addOnSystemEndedHandler(this::saveRecordedLines);
}
for (ArtifactResult artifactResult : artifactResults) {
if (artifactResult.isResolved() && artifactResult.getRepository() instanceof RemoteRepository) {
Path filePath = filePath(
getBasedir(session, false),
artifactResult.getRepository().getId());
boolean newGroupId = rules.computeIfAbsent(
filePath, f -> Collections.synchronizedSet(new TreeSet<>()))
.add(artifactResult.getArtifact().getGroupId());
if (newGroupId) {
changedRules.put(filePath, Boolean.TRUE);
}
}
}
}
}
/**
* Returns the groupId path. The file and parents may not exist, this method merely calculate the path.
*/
private Path filePath(Path basedir, String remoteRepositoryId) {
return basedir.resolve(GROUP_ID_FILE_PREFIX + remoteRepositoryId + GROUP_ID_FILE_SUFFIX);
}
private Set cacheRules(RepositorySystemSession session, RemoteRepository remoteRepository) {
Path filePath = filePath(getBasedir(session, false), remoteRepository.getId());
return rules.computeIfAbsent(filePath, r -> {
Set rules = loadRepositoryRules(filePath);
if (rules != NOT_PRESENT) {
LOGGER.info("Loaded {} groupId for remote repository {}", rules.size(), remoteRepository.getId());
}
return rules;
});
}
private Set loadRepositoryRules(Path filePath) {
if (Files.isReadable(filePath)) {
try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) {
TreeSet result = new TreeSet<>();
String groupId;
while ((groupId = reader.readLine()) != null) {
if (!groupId.startsWith("#") && !groupId.trim().isEmpty()) {
result.add(groupId);
}
}
return Collections.unmodifiableSet(result);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
return NOT_PRESENT;
}
private static final TreeSet NOT_PRESENT = new TreeSet<>();
private class GroupIdFilter implements RemoteRepositoryFilter {
private final RepositorySystemSession session;
private GroupIdFilter(RepositorySystemSession session) {
this.session = session;
}
@Override
public Result acceptArtifact(RemoteRepository remoteRepository, Artifact artifact) {
return acceptGroupId(remoteRepository, artifact.getGroupId());
}
@Override
public Result acceptMetadata(RemoteRepository remoteRepository, Metadata metadata) {
return acceptGroupId(remoteRepository, metadata.getGroupId());
}
private Result acceptGroupId(RemoteRepository remoteRepository, String groupId) {
Set groupIds = cacheRules(session, remoteRepository);
if (NOT_PRESENT == groupIds) {
return NOT_PRESENT_RESULT;
}
if (groupIds.contains(groupId)) {
return new SimpleResult(true, "G:" + groupId + " allowed from " + remoteRepository);
} else {
return new SimpleResult(false, "G:" + groupId + " NOT allowed from " + remoteRepository);
}
}
}
private static final RemoteRepositoryFilter.Result NOT_PRESENT_RESULT =
new SimpleResult(true, "GroupId file not present");
/**
* Returns {@code true} if given session is recording.
*/
private boolean isRecord(RepositorySystemSession session) {
return ConfigUtils.getBoolean(session, false, configPropKey(CONF_NAME_RECORD));
}
/**
* On-close handler that saves recorded rules, if any.
*/
private void saveRecordedLines() {
if (changedRules.isEmpty()) {
return;
}
ArrayList exceptions = new ArrayList<>();
for (Map.Entry> entry : rules.entrySet()) {
Path filePath = entry.getKey();
if (changedRules.get(filePath) != Boolean.TRUE) {
continue;
}
Set recordedLines = entry.getValue();
if (!recordedLines.isEmpty()) {
try {
TreeSet result = new TreeSet<>();
result.addAll(loadRepositoryRules(filePath));
result.addAll(recordedLines);
LOGGER.info("Saving {} groupIds to '{}'", result.size(), filePath);
FileUtils.writeFileWithBackup(filePath, p -> Files.write(p, result));
} catch (IOException e) {
exceptions.add(e);
}
}
}
MultiRuntimeException.mayThrow("session save groupIds failure", exceptions);
}
}