mirror of
https://github.com/apache/maven-mvnd.git
synced 2025-09-28 08:47:29 +00:00
The caches are not all cleaned when deleting the local repository, fixes #312
This commit is contained in:
@@ -70,7 +70,11 @@ import org.apache.maven.extension.internal.CoreExports;
|
||||
import org.apache.maven.extension.internal.CoreExtensionEntry;
|
||||
import org.apache.maven.lifecycle.LifecycleExecutionException;
|
||||
import org.apache.maven.model.building.ModelProcessor;
|
||||
import org.apache.maven.plugin.ExtensionRealmCache;
|
||||
import org.apache.maven.plugin.PluginArtifactsCache;
|
||||
import org.apache.maven.plugin.PluginRealmCache;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
import org.apache.maven.project.artifact.ProjectArtifactsCache;
|
||||
import org.apache.maven.properties.internal.EnvironmentUtils;
|
||||
import org.apache.maven.properties.internal.SystemProperties;
|
||||
import org.apache.maven.session.scope.internal.SessionScopeModule;
|
||||
@@ -89,6 +93,10 @@ import org.codehaus.plexus.classworlds.realm.ClassRealm;
|
||||
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
|
||||
import org.codehaus.plexus.util.StringUtils;
|
||||
import org.eclipse.aether.transfer.TransferListener;
|
||||
import org.mvndaemon.mvnd.cache.impl.CliExtensionRealmCache;
|
||||
import org.mvndaemon.mvnd.cache.impl.CliPluginArtifactsCache;
|
||||
import org.mvndaemon.mvnd.cache.impl.CliPluginRealmCache;
|
||||
import org.mvndaemon.mvnd.cache.impl.CliProjectArtifactsCache;
|
||||
import org.mvndaemon.mvnd.common.Environment;
|
||||
import org.mvndaemon.mvnd.logging.internal.Slf4jLoggerManager;
|
||||
import org.mvndaemon.mvnd.logging.smart.BuildEventListener;
|
||||
@@ -493,6 +501,10 @@ public class DaemonMavenCli {
|
||||
protected void configure() {
|
||||
bind(ILoggerFactory.class).toInstance(slf4jLoggerFactory);
|
||||
bind(CoreExports.class).toInstance(exports);
|
||||
bind(ExtensionRealmCache.class).to(CliExtensionRealmCache.class);
|
||||
bind(PluginArtifactsCache.class).to(CliPluginArtifactsCache.class);
|
||||
bind(PluginRealmCache.class).to(CliPluginRealmCache.class);
|
||||
bind(ProjectArtifactsCache.class).to(CliProjectArtifactsCache.class);
|
||||
}
|
||||
});
|
||||
|
||||
|
56
daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/Cache.java
vendored
Normal file
56
daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/Cache.java
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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 org.mvndaemon.mvnd.cache.factory;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
/**
|
||||
* Cache containing records that can be invalidated.
|
||||
*
|
||||
* Whenever the paths associated to a given {@link CacheRecord} have been modified,
|
||||
* the record will be invalidated using {@link CacheRecord#invalidate()}.
|
||||
*
|
||||
* @param <K>
|
||||
* @param <V>
|
||||
*/
|
||||
public interface Cache<K, V extends CacheRecord> {
|
||||
|
||||
/**
|
||||
* Check if the cache contains the given key
|
||||
*/
|
||||
boolean contains(K key);
|
||||
|
||||
/**
|
||||
* Get the cached record for the key
|
||||
*/
|
||||
V get(K key);
|
||||
|
||||
/**
|
||||
* Put a record in the cache
|
||||
*/
|
||||
void put(K key, V value);
|
||||
|
||||
/**
|
||||
* Remove all cached records
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* Remove cached records according to the predicate
|
||||
*/
|
||||
void removeIf(BiPredicate<K, V> predicate);
|
||||
|
||||
}
|
25
daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/CacheFactory.java
vendored
Normal file
25
daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/CacheFactory.java
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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 org.mvndaemon.mvnd.cache.factory;
|
||||
|
||||
/**
|
||||
* A factory for cache objects
|
||||
*/
|
||||
public interface CacheFactory {
|
||||
|
||||
<K, V extends CacheRecord> Cache<K, V> newCache();
|
||||
|
||||
}
|
38
daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/CacheRecord.java
vendored
Normal file
38
daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/CacheRecord.java
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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 org.mvndaemon.mvnd.cache.factory;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Data stored in a {@link Cache} which depends on the state
|
||||
* of a few {@link Path}s.
|
||||
*/
|
||||
public interface CacheRecord {
|
||||
|
||||
/**
|
||||
* A list of Path that will invalidate this record if modified.
|
||||
*/
|
||||
Stream<Path> getDependentPaths();
|
||||
|
||||
/**
|
||||
* Callback called by the cache when this record
|
||||
* is removed from the cache.
|
||||
*/
|
||||
void invalidate();
|
||||
|
||||
}
|
40
daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/DefaultCacheFactory.java
vendored
Normal file
40
daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/DefaultCacheFactory.java
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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 org.mvndaemon.mvnd.cache.factory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.eclipse.sisu.Priority;
|
||||
import org.mvndaemon.mvnd.common.Os;
|
||||
|
||||
@Named
|
||||
@Singleton
|
||||
@Priority(10)
|
||||
public class DefaultCacheFactory implements CacheFactory {
|
||||
|
||||
@Inject
|
||||
WatchServiceCacheFactory watchServiceCacheFactory;
|
||||
@Inject
|
||||
TimestampCacheFactory timestampCacheFactory;
|
||||
|
||||
@Override
|
||||
public <K, V extends CacheRecord> Cache<K, V> newCache() {
|
||||
CacheFactory factory = Os.current() == Os.WINDOWS ? watchServiceCacheFactory : timestampCacheFactory;
|
||||
return factory.newCache();
|
||||
}
|
||||
|
||||
}
|
149
daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/TimestampCacheFactory.java
vendored
Normal file
149
daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/TimestampCacheFactory.java
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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 org.mvndaemon.mvnd.cache.factory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.codehaus.plexus.logging.AbstractLogEnabled;
|
||||
|
||||
/**
|
||||
* A factory for {@link Cache} objects.
|
||||
*/
|
||||
@Named
|
||||
@Singleton
|
||||
public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFactory {
|
||||
|
||||
public TimestampCacheFactory() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, V extends CacheRecord> Cache<K, V> newCache() {
|
||||
return new DefaultCache<>();
|
||||
}
|
||||
|
||||
static class ArtifactTimestamp {
|
||||
final Path path;
|
||||
final FileTime lastModifiedTime;
|
||||
final Object fileKey;
|
||||
|
||||
ArtifactTimestamp(Path path) {
|
||||
this.path = path;
|
||||
try {
|
||||
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
|
||||
this.lastModifiedTime = attrs.lastModifiedTime();
|
||||
this.fileKey = attrs.fileKey();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
ArtifactTimestamp that = (ArtifactTimestamp) o;
|
||||
return path.equals(that.path) &&
|
||||
Objects.equals(lastModifiedTime, that.lastModifiedTime) &&
|
||||
Objects.equals(fileKey, that.fileKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(path, lastModifiedTime, fileKey);
|
||||
}
|
||||
}
|
||||
|
||||
static class Record<V extends CacheRecord> {
|
||||
final V record;
|
||||
final Set<ArtifactTimestamp> timestamp;
|
||||
|
||||
public Record(V record) {
|
||||
this.record = record;
|
||||
this.timestamp = current();
|
||||
}
|
||||
|
||||
private Set<ArtifactTimestamp> current() {
|
||||
return record.getDependentPaths()
|
||||
.map(ArtifactTimestamp::new)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
static class DefaultCache<K, V extends CacheRecord> implements Cache<K, V> {
|
||||
|
||||
private final ConcurrentHashMap<K, Record<V>> map = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public boolean contains(K key) {
|
||||
return map.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(K key) {
|
||||
Record<V> record = map.compute(key, (k, v) -> {
|
||||
if (v != null) {
|
||||
try {
|
||||
if (Objects.equals(v.timestamp, v.current())) {
|
||||
return v;
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
// ignore and invalidate the record
|
||||
}
|
||||
v.record.invalidate();
|
||||
v = null;
|
||||
}
|
||||
return v;
|
||||
});
|
||||
return record != null ? record.record : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(K key, V value) {
|
||||
map.put(key, new Record<>(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
removeIf((k, v) -> true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeIf(BiPredicate<K, V> predicate) {
|
||||
for (Iterator<Map.Entry<K, Record<V>>> iterator = map.entrySet().iterator(); iterator.hasNext();) {
|
||||
Map.Entry<K, Record<V>> entry = iterator.next();
|
||||
if (predicate.test(entry.getKey(), entry.getValue().record)) {
|
||||
entry.getValue().record.invalidate();
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
236
daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/WatchServiceCacheFactory.java
vendored
Normal file
236
daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/WatchServiceCacheFactory.java
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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 org.mvndaemon.mvnd.cache.factory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardWatchEventKinds;
|
||||
import java.nio.file.WatchEvent;
|
||||
import java.nio.file.WatchKey;
|
||||
import java.nio.file.WatchService;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiPredicate;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.codehaus.plexus.logging.AbstractLogEnabled;
|
||||
|
||||
/**
|
||||
* A factory for {@link Cache} objects.
|
||||
*/
|
||||
@Named
|
||||
@Singleton
|
||||
public class WatchServiceCacheFactory extends AbstractLogEnabled implements CacheFactory {
|
||||
|
||||
private final WatchService watchService;
|
||||
|
||||
/**
|
||||
* Records that have no been invalidated so far. From watched JAR paths to records (because one JAR can be
|
||||
* present in multiple records)
|
||||
*/
|
||||
private final Map<Path, List<CacheRecord>> recordsByPath = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* {@link WatchService} can watch only directories but we actually want to watch files. So here we store
|
||||
* for the given parent directory the count of its child files we watch.
|
||||
*/
|
||||
private final Map<Path, Registration> registrationsByDir = new ConcurrentHashMap<>();
|
||||
|
||||
public WatchServiceCacheFactory() {
|
||||
try {
|
||||
this.watchService = FileSystems.getDefault().newWatchService();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch the JARs associated with the given {@code record} for deletions and modifications.
|
||||
*
|
||||
* @param record the {@link CacheRecord} to watch
|
||||
*/
|
||||
public void add(CacheRecord record) {
|
||||
record.getDependentPaths().forEach(p -> {
|
||||
final List<CacheRecord> records = recordsByPath.computeIfAbsent(p, k -> new ArrayList<>());
|
||||
synchronized (records) {
|
||||
records.add(record);
|
||||
registrationsByDir.compute(p.getParent(), this::register);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, V extends CacheRecord> Cache<K, V> newCache() {
|
||||
return new DefaultCache<>();
|
||||
}
|
||||
|
||||
private Registration register(Path key, Registration value) {
|
||||
if (value == null) {
|
||||
if (getLogger().isDebugEnabled()) {
|
||||
getLogger().debug("Starting to watch path " + key);
|
||||
}
|
||||
try {
|
||||
final WatchKey watchKey = key.register(watchService,
|
||||
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
|
||||
return new Registration(watchKey);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
int cnt = value.count.incrementAndGet();
|
||||
if (getLogger().isDebugEnabled()) {
|
||||
getLogger().debug("Already " + cnt + " watchers for path " + key);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll for events and process them.
|
||||
*/
|
||||
public void validateRecords() {
|
||||
for (Map.Entry<Path, Registration> entry : registrationsByDir.entrySet()) {
|
||||
final WatchKey watchKey = entry.getValue().watchKey;
|
||||
for (WatchEvent<?> event : watchKey.pollEvents()) {
|
||||
final Path dir = entry.getKey();
|
||||
WatchEvent.Kind<?> kind = event.kind();
|
||||
if (getLogger().isDebugEnabled()) {
|
||||
getLogger().debug("Got watcher event " + kind.name());
|
||||
}
|
||||
if (kind == StandardWatchEventKinds.ENTRY_DELETE || kind == StandardWatchEventKinds.ENTRY_MODIFY) {
|
||||
final Path path = dir.resolve((Path) event.context());
|
||||
final List<CacheRecord> records = recordsByPath.remove(path);
|
||||
if (getLogger().isDebugEnabled()) {
|
||||
getLogger().debug("Records for path " + path + ": " + records);
|
||||
}
|
||||
if (records != null) {
|
||||
if (getLogger().isDebugEnabled()) {
|
||||
getLogger().debug("Invalidating recorder of path " + path);
|
||||
}
|
||||
remove(records);
|
||||
}
|
||||
} else if (kind == StandardWatchEventKinds.OVERFLOW) {
|
||||
/* Invalidate all records under the given dir */
|
||||
Iterator<Map.Entry<Path, List<CacheRecord>>> it = recordsByPath.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<Path, List<CacheRecord>> en = it.next();
|
||||
if (en.getKey().getParent().equals(dir)) {
|
||||
it.remove();
|
||||
if (getLogger().isDebugEnabled()) {
|
||||
getLogger().debug("Invalidating recorder of path " + en.getKey());
|
||||
}
|
||||
remove(en.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop watching the JARs associated with the given {@code record} for deletions and modifications.
|
||||
*
|
||||
* @param records the {@link CacheRecord}s to stop watching
|
||||
*/
|
||||
void remove(List<CacheRecord> records) {
|
||||
for (CacheRecord record : records) {
|
||||
record.invalidate();
|
||||
record.getDependentPaths()
|
||||
.map(Path::getParent)
|
||||
.forEach(dir -> registrationsByDir.compute(dir, this::unregister));
|
||||
}
|
||||
}
|
||||
|
||||
private Registration unregister(Path key, Registration value) {
|
||||
if (value == null) {
|
||||
if (getLogger().isDebugEnabled()) {
|
||||
getLogger().debug("Already unwatchers for path " + key);
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
final int cnt = value.count.decrementAndGet();
|
||||
if (cnt <= 0) {
|
||||
if (getLogger().isDebugEnabled()) {
|
||||
getLogger().debug("Unwatching path " + key);
|
||||
}
|
||||
value.watchKey.cancel();
|
||||
return null;
|
||||
} else {
|
||||
if (getLogger().isDebugEnabled()) {
|
||||
getLogger().debug("Still " + cnt + " watchers for path " + key);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A watcher registration for a directory storing the {@link WatchKey} and the count of watchers to be able to
|
||||
* tell when the {@link #watchKey} should be cancelled.
|
||||
*/
|
||||
static class Registration {
|
||||
final AtomicInteger count = new AtomicInteger(1);
|
||||
final WatchKey watchKey;
|
||||
|
||||
Registration(WatchKey watchKey) {
|
||||
this.watchKey = watchKey;
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultCache<K, V extends CacheRecord> implements Cache<K, V> {
|
||||
|
||||
private final ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public boolean contains(K key) {
|
||||
return map.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(K key) {
|
||||
validateRecords();
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(K key, V value) {
|
||||
add(value);
|
||||
map.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
removeIf((k, v) -> true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeIf(BiPredicate<K, V> predicate) {
|
||||
for (Iterator<Map.Entry<K, V>> iterator = map.entrySet().iterator(); iterator.hasNext();) {
|
||||
Map.Entry<K, V> entry = iterator.next();
|
||||
if (predicate.test(entry.getKey(), entry.getValue())) {
|
||||
entry.getValue().invalidate();
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
92
daemon/src/main/java/org/mvndaemon/mvnd/cache/impl/CliExtensionRealmCache.java
vendored
Normal file
92
daemon/src/main/java/org/mvndaemon/mvnd/cache/impl/CliExtensionRealmCache.java
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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 org.mvndaemon.mvnd.cache.impl;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.apache.maven.artifact.Artifact;
|
||||
import org.apache.maven.plugin.DefaultExtensionRealmCache;
|
||||
import org.apache.maven.project.ExtensionDescriptor;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
import org.codehaus.plexus.classworlds.realm.ClassRealm;
|
||||
import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
|
||||
import org.mvndaemon.mvnd.cache.factory.Cache;
|
||||
import org.mvndaemon.mvnd.cache.factory.CacheFactory;
|
||||
|
||||
@Singleton
|
||||
@Named
|
||||
public class CliExtensionRealmCache extends DefaultExtensionRealmCache {
|
||||
|
||||
protected static class Record implements org.mvndaemon.mvnd.cache.factory.CacheRecord {
|
||||
|
||||
private final CacheRecord record;
|
||||
|
||||
public Record(CacheRecord record) {
|
||||
this.record = record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Path> getDependentPaths() {
|
||||
return record.getArtifacts().stream().map(artifact -> artifact.getFile().toPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
ClassRealm realm = record.getRealm();
|
||||
try {
|
||||
realm.getWorld().disposeRealm(realm.getId());
|
||||
} catch (NoSuchRealmException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final Cache<Key, Record> cache;
|
||||
|
||||
@Inject
|
||||
public CliExtensionRealmCache(CacheFactory cacheFactory) {
|
||||
cache = cacheFactory.newCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheRecord get(Key key) {
|
||||
Record r = cache.get(key);
|
||||
return r != null ? r.record : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheRecord put(Key key, ClassRealm extensionRealm, ExtensionDescriptor extensionDescriptor,
|
||||
List<Artifact> artifacts) {
|
||||
CacheRecord record = super.put(key, extensionRealm, extensionDescriptor, artifacts);
|
||||
super.cache.remove(key);
|
||||
cache.put(key, new Record(record));
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(MavenProject project, Key key, CacheRecord record) {
|
||||
}
|
||||
|
||||
}
|
92
daemon/src/main/java/org/mvndaemon/mvnd/cache/impl/CliPluginArtifactsCache.java
vendored
Normal file
92
daemon/src/main/java/org/mvndaemon/mvnd/cache/impl/CliPluginArtifactsCache.java
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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 org.mvndaemon.mvnd.cache.impl;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.apache.maven.artifact.Artifact;
|
||||
import org.apache.maven.plugin.DefaultPluginArtifactsCache;
|
||||
import org.apache.maven.plugin.PluginResolutionException;
|
||||
import org.mvndaemon.mvnd.cache.factory.Cache;
|
||||
import org.mvndaemon.mvnd.cache.factory.CacheFactory;
|
||||
|
||||
@Singleton
|
||||
@Named
|
||||
public class CliPluginArtifactsCache extends DefaultPluginArtifactsCache {
|
||||
|
||||
protected static class Record implements org.mvndaemon.mvnd.cache.factory.CacheRecord {
|
||||
|
||||
private final CacheRecord record;
|
||||
|
||||
public Record(CacheRecord record) {
|
||||
this.record = record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Path> getDependentPaths() {
|
||||
return record.getArtifacts().stream().map(artifact -> artifact.getFile().toPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
}
|
||||
}
|
||||
|
||||
final Cache<Key, Record> cache;
|
||||
|
||||
public CliPluginArtifactsCache(CacheFactory cacheFactory) {
|
||||
this.cache = cacheFactory.newCache();
|
||||
}
|
||||
|
||||
public CacheRecord get(Key key) throws PluginResolutionException {
|
||||
Record r = cache.get(key);
|
||||
if (r != null) {
|
||||
if (r.record.getException() != null) {
|
||||
throw r.record.getException();
|
||||
}
|
||||
return r.record;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public CacheRecord put(Key key, List<Artifact> pluginArtifacts) {
|
||||
CacheRecord record = super.put(key, pluginArtifacts);
|
||||
super.cache.remove(key);
|
||||
cache.put(key, new Record(record));
|
||||
return record;
|
||||
}
|
||||
|
||||
public CacheRecord put(Key key, PluginResolutionException exception) {
|
||||
CacheRecord record = super.put(key, exception);
|
||||
super.cache.remove(key);
|
||||
cache.put(key, new Record(record));
|
||||
return record;
|
||||
}
|
||||
|
||||
protected void assertUniqueKey(Key key) {
|
||||
if (cache.get(key) != null) {
|
||||
throw new IllegalStateException("Duplicate artifact resolution result for plugin " + key);
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
}
|
93
daemon/src/main/java/org/mvndaemon/mvnd/cache/impl/CliPluginDescriptorCache.java
vendored
Normal file
93
daemon/src/main/java/org/mvndaemon/mvnd/cache/impl/CliPluginDescriptorCache.java
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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 org.mvndaemon.mvnd.cache.impl;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.apache.maven.model.Plugin;
|
||||
import org.apache.maven.plugin.DefaultPluginDescriptorCache;
|
||||
import org.apache.maven.plugin.descriptor.PluginDescriptor;
|
||||
import org.codehaus.plexus.classworlds.realm.ClassRealm;
|
||||
import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
|
||||
import org.eclipse.aether.RepositorySystemSession;
|
||||
import org.eclipse.aether.repository.RemoteRepository;
|
||||
import org.mvndaemon.mvnd.cache.factory.Cache;
|
||||
import org.mvndaemon.mvnd.cache.factory.CacheFactory;
|
||||
import org.mvndaemon.mvnd.cache.factory.CacheRecord;
|
||||
|
||||
@Singleton
|
||||
@Named
|
||||
public class CliPluginDescriptorCache extends DefaultPluginDescriptorCache {
|
||||
|
||||
protected static class Record implements CacheRecord {
|
||||
|
||||
private final PluginDescriptor descriptor;
|
||||
|
||||
public Record(PluginDescriptor descriptor) {
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Path> getDependentPaths() {
|
||||
return Optional.ofNullable(descriptor.getArtifacts()).orElse(Collections.emptyList())
|
||||
.stream().map(artifact -> artifact.getFile().toPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
ClassRealm realm = descriptor.getClassRealm();
|
||||
try {
|
||||
realm.getWorld().disposeRealm(realm.getId());
|
||||
} catch (NoSuchRealmException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Cache<Key, Record> cache;
|
||||
|
||||
@Inject
|
||||
public CliPluginDescriptorCache(CacheFactory cacheFactory) {
|
||||
this.cache = cacheFactory.newCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key createKey(Plugin plugin, List<RemoteRepository> repositories, RepositorySystemSession session) {
|
||||
return super.createKey(plugin, repositories, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginDescriptor get(Key key) {
|
||||
Record r = cache.get(key);
|
||||
return r != null ? clone(r.descriptor) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(Key key, PluginDescriptor descriptor) {
|
||||
cache.put(key, new Record(clone(descriptor)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
cache.clear();
|
||||
}
|
||||
}
|
95
daemon/src/main/java/org/mvndaemon/mvnd/cache/impl/CliPluginRealmCache.java
vendored
Normal file
95
daemon/src/main/java/org/mvndaemon/mvnd/cache/impl/CliPluginRealmCache.java
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed 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 org.mvndaemon.mvnd.cache.impl;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.apache.maven.artifact.Artifact;
|
||||
import org.apache.maven.plugin.DefaultPluginRealmCache;
|
||||
import org.apache.maven.plugin.PluginRealmCache;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
import org.codehaus.plexus.classworlds.realm.ClassRealm;
|
||||
import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
|
||||
import org.eclipse.sisu.Priority;
|
||||
import org.eclipse.sisu.Typed;
|
||||
import org.mvndaemon.mvnd.cache.factory.Cache;
|
||||
import org.mvndaemon.mvnd.cache.factory.CacheFactory;
|
||||
|
||||
@Singleton
|
||||
@Named
|
||||
@Priority(10)
|
||||
@Typed(PluginRealmCache.class)
|
||||
public class CliPluginRealmCache extends DefaultPluginRealmCache {
|
||||
|
||||
protected static class Record implements org.mvndaemon.mvnd.cache.factory.CacheRecord {
|
||||
|
||||
final CacheRecord record;
|
||||
|
||||
public Record(CacheRecord record) {
|
||||
this.record = record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Path> getDependentPaths() {
|
||||
return record.getArtifacts().stream().map(artifact -> artifact.getFile().toPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
ClassRealm realm = record.getRealm();
|
||||
try {
|
||||
realm.getWorld().disposeRealm(realm.getId());
|
||||
} catch (NoSuchRealmException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Cache<Key, Record> cache;
|
||||
|
||||
@Inject
|
||||
public CliPluginRealmCache(CacheFactory cacheFactory) {
|
||||
cache = cacheFactory.newCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheRecord get(Key key) {
|
||||
Record r = cache.get(key);
|
||||
return r != null ? r.record : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheRecord put(Key key, ClassRealm pluginRealm, List<Artifact> pluginArtifacts) {
|
||||
CacheRecord record = super.put(key, pluginRealm, pluginArtifacts);
|
||||
super.cache.remove(key);
|
||||
cache.put(key, new Record(record));
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(MavenProject project, Key key, CacheRecord record) {
|
||||
}
|
||||
|
||||
}
|
@@ -13,13 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.mvndaemon.mvnd.plugin;
|
||||
package org.mvndaemon.mvnd.cache.impl;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
@@ -27,7 +26,6 @@ import org.apache.maven.eventspy.AbstractEventSpy;
|
||||
import org.apache.maven.eventspy.EventSpy;
|
||||
import org.apache.maven.execution.MavenExecutionRequest;
|
||||
import org.apache.maven.execution.MavenExecutionResult;
|
||||
import org.apache.maven.plugin.PluginRealmCache;
|
||||
import org.eclipse.sisu.Typed;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -54,29 +52,30 @@ public class CliPluginRealmCacheEventSpy extends AbstractEventSpy {
|
||||
/* Store the multiModuleProjectDirectory path */
|
||||
multiModuleProjectDirectory = ((MavenExecutionRequest) event).getMultiModuleProjectDirectory().toPath();
|
||||
} else if (event instanceof MavenExecutionResult) {
|
||||
/* Evict the entries refering to jars under multiModuleProjectDirectory */
|
||||
final Iterator<Map.Entry<PluginRealmCache.Key, CliPluginRealmCache.ValidableCacheRecord>> i = cache.cache
|
||||
.entrySet().iterator();
|
||||
while (i.hasNext()) {
|
||||
final Map.Entry<PluginRealmCache.Key, CliPluginRealmCache.ValidableCacheRecord> entry = i.next();
|
||||
final CliPluginRealmCache.ValidableCacheRecord record = entry.getValue();
|
||||
for (URL url : record.getRealm().getURLs()) {
|
||||
if (url.getProtocol().equals("file")) {
|
||||
final Path path = Paths.get(url.toURI());
|
||||
if (path.startsWith(multiModuleProjectDirectory)) {
|
||||
LOG.debug(
|
||||
"Removing PluginRealmCache entry {} because it refers to an artifact in the build tree {}",
|
||||
entry.getKey(), path);
|
||||
record.dispose();
|
||||
i.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Evict the entries referring to jars under multiModuleProjectDirectory */
|
||||
cache.cache.removeIf(this::shouldEvict);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Could not notify CliPluginRealmCache", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldEvict(CliPluginRealmCache.Key k, CliPluginRealmCache.Record v) {
|
||||
try {
|
||||
for (URL url : v.record.getRealm().getURLs()) {
|
||||
if (url.getProtocol().equals("file")) {
|
||||
final Path path = Paths.get(url.toURI());
|
||||
if (path.startsWith(multiModuleProjectDirectory)) {
|
||||
LOG.debug(
|
||||
"Removing PluginRealmCache entry {} because it refers to an artifact in the build tree {}",
|
||||
k, path);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (URISyntaxException e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
100
daemon/src/main/java/org/mvndaemon/mvnd/cache/impl/CliProjectArtifactsCache.java
vendored
Normal file
100
daemon/src/main/java/org/mvndaemon/mvnd/cache/impl/CliProjectArtifactsCache.java
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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 org.mvndaemon.mvnd.cache.impl;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.apache.maven.artifact.Artifact;
|
||||
import org.apache.maven.lifecycle.LifecycleExecutionException;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
import org.apache.maven.project.artifact.DefaultProjectArtifactsCache;
|
||||
import org.mvndaemon.mvnd.cache.factory.Cache;
|
||||
import org.mvndaemon.mvnd.cache.factory.CacheFactory;
|
||||
|
||||
@Singleton
|
||||
@Named
|
||||
public class CliProjectArtifactsCache extends DefaultProjectArtifactsCache {
|
||||
|
||||
static class Record implements org.mvndaemon.mvnd.cache.factory.CacheRecord {
|
||||
private final CacheRecord record;
|
||||
|
||||
public Record(CacheRecord record) {
|
||||
this.record = record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Path> getDependentPaths() {
|
||||
return record.getArtifacts().stream()
|
||||
.map(Artifact::getFile)
|
||||
.filter(Objects::nonNull)
|
||||
.map(File::toPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
}
|
||||
}
|
||||
|
||||
final Cache<Key, Record> cache;
|
||||
|
||||
@Inject
|
||||
public CliProjectArtifactsCache(CacheFactory cacheFactory) {
|
||||
this.cache = cacheFactory.newCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheRecord get(Key key) throws LifecycleExecutionException {
|
||||
Record r = cache.get(key);
|
||||
if (r != null) {
|
||||
if (r.record.getException() != null) {
|
||||
throw r.record.getException();
|
||||
}
|
||||
return r.record;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheRecord put(Key key, Set<Artifact> pluginArtifacts) {
|
||||
CacheRecord record = super.put(key, pluginArtifacts);
|
||||
super.cache.remove(key);
|
||||
cache.put(key, new Record(record));
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheRecord put(Key key, LifecycleExecutionException e) {
|
||||
CacheRecord record = super.put(key, e);
|
||||
super.cache.remove(key);
|
||||
cache.put(key, new Record(record));
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(MavenProject project, Key cacheKey, CacheRecord record) {
|
||||
}
|
||||
}
|
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* 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 org.mvndaemon.mvnd.plugin;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.apache.maven.model.Dependency;
|
||||
import org.apache.maven.model.Exclusion;
|
||||
import org.apache.maven.model.Plugin;
|
||||
|
||||
/**
|
||||
* File origin:
|
||||
* https://github.com/apache/maven/blob/maven-3.6.1/maven-core/src/main/java/org/apache/maven/plugin/CacheUtils.java
|
||||
*
|
||||
* @author Benjamin Bentmann
|
||||
*/
|
||||
class CliCacheUtils {
|
||||
|
||||
public static int pluginHashCode(Plugin plugin) {
|
||||
int hash = 17;
|
||||
|
||||
hash = hash * 31 + Objects.hashCode(plugin.getGroupId());
|
||||
hash = hash * 31 + Objects.hashCode(plugin.getArtifactId());
|
||||
hash = hash * 31 + Objects.hashCode(plugin.getVersion());
|
||||
|
||||
hash = hash * 31 + (plugin.isExtensions() ? 1 : 0);
|
||||
|
||||
for (Dependency dependency : plugin.getDependencies()) {
|
||||
hash = hash * 31 + Objects.hashCode(dependency.getGroupId());
|
||||
hash = hash * 31 + Objects.hashCode(dependency.getArtifactId());
|
||||
hash = hash * 31 + Objects.hashCode(dependency.getVersion());
|
||||
hash = hash * 31 + Objects.hashCode(dependency.getType());
|
||||
hash = hash * 31 + Objects.hashCode(dependency.getClassifier());
|
||||
hash = hash * 31 + Objects.hashCode(dependency.getScope());
|
||||
|
||||
for (Exclusion exclusion : dependency.getExclusions()) {
|
||||
hash = hash * 31 + Objects.hashCode(exclusion.getGroupId());
|
||||
hash = hash * 31 + Objects.hashCode(exclusion.getArtifactId());
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public static boolean pluginEquals(Plugin a, Plugin b) {
|
||||
return Objects.equals(a.getArtifactId(), b.getArtifactId()) //
|
||||
&& Objects.equals(a.getGroupId(), b.getGroupId()) //
|
||||
&& Objects.equals(a.getVersion(), b.getVersion()) //
|
||||
&& a.isExtensions() == b.isExtensions() //
|
||||
&& dependenciesEquals(a.getDependencies(), b.getDependencies());
|
||||
}
|
||||
|
||||
private static boolean dependenciesEquals(List<Dependency> a, List<Dependency> b) {
|
||||
if (a.size() != b.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Iterator<Dependency> aI = a.iterator();
|
||||
Iterator<Dependency> bI = b.iterator();
|
||||
|
||||
while (aI.hasNext()) {
|
||||
Dependency aD = aI.next();
|
||||
Dependency bD = bI.next();
|
||||
|
||||
boolean r = Objects.equals(aD.getGroupId(), bD.getGroupId()) //
|
||||
&& Objects.equals(aD.getArtifactId(), bD.getArtifactId()) //
|
||||
&& Objects.equals(aD.getVersion(), bD.getVersion()) //
|
||||
&& Objects.equals(aD.getType(), bD.getType()) //
|
||||
&& Objects.equals(aD.getClassifier(), bD.getClassifier()) //
|
||||
&& Objects.equals(aD.getScope(), bD.getScope());
|
||||
|
||||
r &= exclusionsEquals(aD.getExclusions(), bD.getExclusions());
|
||||
|
||||
if (!r) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean exclusionsEquals(List<Exclusion> a, List<Exclusion> b) {
|
||||
if (a.size() != b.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Iterator<Exclusion> aI = a.iterator();
|
||||
Iterator<Exclusion> bI = b.iterator();
|
||||
|
||||
while (aI.hasNext()) {
|
||||
Exclusion aD = aI.next();
|
||||
Exclusion bD = bI.next();
|
||||
|
||||
boolean r = Objects.equals(aD.getGroupId(), bD.getGroupId()) //
|
||||
&& Objects.equals(aD.getArtifactId(), bD.getArtifactId());
|
||||
|
||||
if (!r) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@@ -1,383 +0,0 @@
|
||||
/*
|
||||
* 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 org.mvndaemon.mvnd.plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardWatchEventKinds;
|
||||
import java.nio.file.WatchEvent;
|
||||
import java.nio.file.WatchEvent.Kind;
|
||||
import java.nio.file.WatchKey;
|
||||
import java.nio.file.WatchService;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.apache.maven.RepositoryUtils;
|
||||
import org.apache.maven.artifact.Artifact;
|
||||
import org.apache.maven.model.Plugin;
|
||||
import org.apache.maven.plugin.PluginRealmCache;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
import org.codehaus.plexus.classworlds.realm.ClassRealm;
|
||||
import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
|
||||
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Disposable;
|
||||
import org.eclipse.aether.RepositorySystemSession;
|
||||
import org.eclipse.aether.graph.DependencyFilter;
|
||||
import org.eclipse.aether.repository.LocalRepository;
|
||||
import org.eclipse.aether.repository.RemoteRepository;
|
||||
import org.eclipse.aether.repository.WorkspaceRepository;
|
||||
import org.eclipse.sisu.Priority;
|
||||
import org.eclipse.sisu.Typed;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Default PluginCache implementation. Assumes cached data does not change.
|
||||
*
|
||||
* File origin:
|
||||
* https://github.com/apache/maven/blob/maven-3.6.2/maven-core/src/main/java/org/apache/maven/plugin/DefaultPluginRealmCache.java
|
||||
*/
|
||||
@Singleton
|
||||
@Named
|
||||
@Priority(10)
|
||||
@Typed(PluginRealmCache.class)
|
||||
public class CliPluginRealmCache
|
||||
implements PluginRealmCache, Disposable {
|
||||
/**
|
||||
* CacheKey
|
||||
*/
|
||||
protected static class CacheKey
|
||||
implements Key {
|
||||
|
||||
private final Plugin plugin;
|
||||
|
||||
private final WorkspaceRepository workspace;
|
||||
|
||||
private final LocalRepository localRepo;
|
||||
|
||||
private final List<RemoteRepository> repositories;
|
||||
|
||||
private final ClassLoader parentRealm;
|
||||
|
||||
private final Map<String, ClassLoader> foreignImports;
|
||||
|
||||
private final DependencyFilter filter;
|
||||
|
||||
private final int hashCode;
|
||||
|
||||
public CacheKey(Plugin plugin, ClassLoader parentRealm, Map<String, ClassLoader> foreignImports,
|
||||
DependencyFilter dependencyFilter, List<RemoteRepository> repositories,
|
||||
RepositorySystemSession session) {
|
||||
this.plugin = plugin.clone();
|
||||
this.workspace = RepositoryUtils.getWorkspace(session);
|
||||
this.localRepo = session.getLocalRepository();
|
||||
this.repositories = new ArrayList<>(repositories.size());
|
||||
for (RemoteRepository repository : repositories) {
|
||||
if (repository.isRepositoryManager()) {
|
||||
this.repositories.addAll(repository.getMirroredRepositories());
|
||||
} else {
|
||||
this.repositories.add(repository);
|
||||
}
|
||||
}
|
||||
this.parentRealm = parentRealm;
|
||||
this.foreignImports = (foreignImports != null) ? foreignImports : Collections.emptyMap();
|
||||
this.filter = dependencyFilter;
|
||||
|
||||
int hash = 17;
|
||||
hash = hash * 31 + CliCacheUtils.pluginHashCode(plugin);
|
||||
hash = hash * 31 + Objects.hashCode(workspace);
|
||||
hash = hash * 31 + Objects.hashCode(localRepo);
|
||||
hash = hash * 31 + RepositoryUtils.repositoriesHashCode(repositories);
|
||||
hash = hash * 31 + Objects.hashCode(parentRealm);
|
||||
hash = hash * 31 + this.foreignImports.hashCode();
|
||||
hash = hash * 31 + Objects.hashCode(dependencyFilter);
|
||||
this.hashCode = hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return plugin.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(o instanceof CacheKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CacheKey that = (CacheKey) o;
|
||||
|
||||
return parentRealm == that.parentRealm
|
||||
&& CliCacheUtils.pluginEquals(plugin, that.plugin)
|
||||
&& Objects.equals(workspace, that.workspace)
|
||||
&& Objects.equals(localRepo, that.localRepo)
|
||||
&& RepositoryUtils.repositoriesEquals(this.repositories, that.repositories)
|
||||
&& Objects.equals(filter, that.filter)
|
||||
&& Objects.equals(foreignImports, that.foreignImports);
|
||||
}
|
||||
}
|
||||
|
||||
static class ValidableCacheRecord extends CacheRecord {
|
||||
|
||||
private volatile boolean valid = true;
|
||||
|
||||
public ValidableCacheRecord(ClassRealm realm, List<Artifact> artifacts) {
|
||||
super(realm, artifacts);
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
ClassRealm realm = getRealm();
|
||||
try {
|
||||
realm.getWorld().disposeRealm(realm.getId());
|
||||
} catch (NoSuchRealmException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link RecordValidator} with some methods to watch JARs associated with {@link ValidableCacheRecord}.
|
||||
*/
|
||||
static class RecordValidator {
|
||||
private final WatchService watchService;
|
||||
|
||||
/**
|
||||
* Records that have no been invalidated so far. From watched JAR paths to records (because one JAR can be
|
||||
* present in multiple records)
|
||||
*/
|
||||
private final Map<Path, List<ValidableCacheRecord>> validRecordsByPath = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* {@link WatchService} can watch only directories but we actually want to watch files. So here we store
|
||||
* for the given parent directory the count of its child files we watch.
|
||||
*/
|
||||
private final Map<Path, Registration> registrationsByDir = new ConcurrentHashMap<>();
|
||||
|
||||
public RecordValidator() {
|
||||
try {
|
||||
this.watchService = FileSystems.getDefault().newWatchService();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch the JARs associated with the given {@code record} for deletions and modifications.
|
||||
*
|
||||
* @param record the {@link ValidableCacheRecord} to watch
|
||||
*/
|
||||
void add(ValidableCacheRecord record) {
|
||||
record.getArtifacts().stream()
|
||||
.map(Artifact::getFile)
|
||||
.map(File::toPath)
|
||||
.forEach(p -> {
|
||||
validRecordsByPath.compute(p, (key, value) -> {
|
||||
if (value == null) {
|
||||
value = new ArrayList<>();
|
||||
}
|
||||
value.add(record);
|
||||
return value;
|
||||
});
|
||||
final Path dir = p.getParent();
|
||||
registrationsByDir.compute(dir, (key, value) -> {
|
||||
if (value == null) {
|
||||
LOG.debug("Starting to watch path {}", key);
|
||||
try {
|
||||
final WatchKey watchKey = dir.register(watchService, StandardWatchEventKinds.ENTRY_DELETE,
|
||||
StandardWatchEventKinds.ENTRY_MODIFY);
|
||||
return new Registration(watchKey);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
int cnt = value.count.incrementAndGet();
|
||||
LOG.debug("Already {} watchers for path {}", cnt, key);
|
||||
return value;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stopn watching the JARs associated with the given {@code record} for deletions and modifications.
|
||||
*
|
||||
* @param record the {@link ValidableCacheRecord} to stop watching
|
||||
*/
|
||||
void remove(ValidableCacheRecord record) {
|
||||
record.getArtifacts().stream()
|
||||
.map(Artifact::getFile)
|
||||
.map(File::toPath)
|
||||
.forEach(p -> {
|
||||
final Path dir = p.getParent();
|
||||
registrationsByDir.compute(dir, (key, value) -> {
|
||||
if (value == null) {
|
||||
LOG.debug("Already unwatchers for path {}", key);
|
||||
return null;
|
||||
} else {
|
||||
final int cnt = value.count.decrementAndGet();
|
||||
if (cnt <= 0) {
|
||||
LOG.debug("Unwatching path {}", key);
|
||||
value.watchKey.cancel();
|
||||
return null;
|
||||
} else {
|
||||
LOG.debug("Still {} watchers for path {}", cnt, key);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll for events and process them.
|
||||
*/
|
||||
public void validateRecords() {
|
||||
for (Entry<Path, Registration> entry : registrationsByDir.entrySet()) {
|
||||
final Path dir = entry.getKey();
|
||||
final WatchKey watchKey = entry.getValue().watchKey;
|
||||
for (WatchEvent<?> event : watchKey.pollEvents()) {
|
||||
Kind<?> kind = event.kind();
|
||||
LOG.debug("Got watcher event {}", kind.name());
|
||||
if (kind == StandardWatchEventKinds.ENTRY_DELETE || kind == StandardWatchEventKinds.ENTRY_MODIFY) {
|
||||
final Path path = dir.resolve((Path) event.context());
|
||||
final List<ValidableCacheRecord> records = validRecordsByPath.get(path);
|
||||
LOG.debug("Records for path {}: {}", path, records);
|
||||
if (records != null) {
|
||||
synchronized (records) {
|
||||
for (ValidableCacheRecord record : records) {
|
||||
LOG.debug("Invalidating recorder of path {}", path);
|
||||
record.valid = false;
|
||||
remove(record);
|
||||
}
|
||||
records.clear();
|
||||
}
|
||||
}
|
||||
} else if (kind == StandardWatchEventKinds.OVERFLOW) {
|
||||
/* Invalidate all records under the given dir */
|
||||
for (Entry<Path, List<ValidableCacheRecord>> en : validRecordsByPath.entrySet()) {
|
||||
final Path entryParent = en.getKey().getParent();
|
||||
if (entryParent.equals(dir)) {
|
||||
final List<ValidableCacheRecord> records = en.getValue();
|
||||
if (records != null) {
|
||||
synchronized (records) {
|
||||
for (ValidableCacheRecord record : records) {
|
||||
record.valid = false;
|
||||
remove(record);
|
||||
}
|
||||
records.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A watcher registration for a directory storing the {@link WatchKey} and the count of watchers to be able to
|
||||
* tell when the {@link #watchKey} should be cancelled.
|
||||
*/
|
||||
static class Registration {
|
||||
final AtomicInteger count = new AtomicInteger(1);
|
||||
final WatchKey watchKey;
|
||||
|
||||
public Registration(WatchKey watchKey) {
|
||||
this.watchKey = watchKey;
|
||||
}
|
||||
}
|
||||
|
||||
public ValidableCacheRecord newRecord(ClassRealm pluginRealm, List<Artifact> pluginArtifacts) {
|
||||
final ValidableCacheRecord result = new ValidableCacheRecord(pluginRealm, pluginArtifacts);
|
||||
add(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CliPluginRealmCache.class);
|
||||
|
||||
protected final Map<Key, ValidableCacheRecord> cache = new ConcurrentHashMap<>();
|
||||
private final RecordValidator watcher;
|
||||
|
||||
public CliPluginRealmCache() {
|
||||
this.watcher = new RecordValidator();
|
||||
}
|
||||
|
||||
public Key createKey(Plugin plugin, ClassLoader parentRealm, Map<String, ClassLoader> foreignImports,
|
||||
DependencyFilter dependencyFilter, List<RemoteRepository> repositories,
|
||||
RepositorySystemSession session) {
|
||||
return new CacheKey(plugin, parentRealm, foreignImports, dependencyFilter, repositories, session);
|
||||
}
|
||||
|
||||
public CacheRecord get(Key key) {
|
||||
watcher.validateRecords();
|
||||
return cache.computeIfPresent(key, (k, r) -> {
|
||||
if (!r.isValid()) {
|
||||
r.dispose();
|
||||
return null;
|
||||
} else {
|
||||
return r;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public CacheRecord put(Key key, ClassRealm pluginRealm, List<Artifact> pluginArtifacts) {
|
||||
Objects.requireNonNull(pluginRealm, "pluginRealm cannot be null");
|
||||
Objects.requireNonNull(pluginArtifacts, "pluginArtifacts cannot be null");
|
||||
return cache.computeIfAbsent(key, k -> watcher.newRecord(pluginRealm, pluginArtifacts));
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
for (ValidableCacheRecord record : cache.values()) {
|
||||
record.dispose();
|
||||
}
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
public void register(MavenProject project, Key key, CacheRecord record) {
|
||||
// default cache does not track plugin usage
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
flush();
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user