Skip to content

Commit 27223f2

Browse files
authored
[MNG-8540] Improve cache extensibility (#2109)
1 parent cfb7ce8 commit 27223f2

File tree

5 files changed

+319
-229
lines changed

5 files changed

+319
-229
lines changed

impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ protected void createTerminal(C context) {
311311
InputStream in = context.invokerRequest.stdIn().orElse(InputStream.nullInputStream());
312312
OutputStream out = context.invokerRequest.stdOut().orElse(OutputStream.nullOutputStream());
313313
builder.streams(in, out);
314-
builder.provider("exec");
314+
builder.provider(TerminalBuilder.PROP_PROVIDER_EXEC);
315315
context.coloredOutput = context.coloredOutput != null ? context.coloredOutput : false;
316316
context.closeables.add(out::flush);
317317
} else {
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.impl.cache;
20+
21+
import java.util.ArrayList;
22+
import java.util.HashMap;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.function.Function;
26+
27+
import org.apache.maven.api.cache.BatchRequestException;
28+
import org.apache.maven.api.cache.MavenExecutionException;
29+
import org.apache.maven.api.cache.RequestCache;
30+
import org.apache.maven.api.cache.RequestResult;
31+
import org.apache.maven.api.services.Request;
32+
import org.apache.maven.api.services.Result;
33+
34+
/**
35+
* Abstract implementation of the {@link RequestCache} interface, providing common caching mechanisms
36+
* for executing and caching request results in Maven.
37+
* <p>
38+
* This class implements caching strategies for individual and batch requests, ensuring that results
39+
* are stored and reused where appropriate to optimize performance.
40+
* </p>
41+
*
42+
* @since 4.0.0
43+
*/
44+
public abstract class AbstractRequestCache implements RequestCache {
45+
46+
/**
47+
* Executes and optionally caches a single request.
48+
* <p>
49+
* The caching behavior is determined by the specific implementation of {@link #doCache(Request, Function)}.
50+
* If caching is enabled, the result is retrieved from the cache or computed using the supplier function.
51+
* </p>
52+
*
53+
* @param <REQ> The request type
54+
* @param <REP> The response type
55+
* @param req The request object used as the cache key
56+
* @param supplier The function that provides the response if not cached
57+
* @return The cached or computed response
58+
*/
59+
@Override
60+
@SuppressWarnings("all")
61+
public <REQ extends Request<?>, REP extends Result<REQ>> REP request(REQ req, Function<REQ, REP> supplier) {
62+
CachingSupplier<REQ, REP> cs = doCache(req, supplier);
63+
return cs.apply(req);
64+
}
65+
66+
/**
67+
* Executes and optionally caches a batch of requests.
68+
* <p>
69+
* This method processes a list of requests, utilizing caching where applicable and executing
70+
* only the non-cached requests using the provided supplier function.
71+
* </p>
72+
* <p>
73+
* If any request in the batch fails, a {@link BatchRequestException} is thrown, containing
74+
* details of all failed requests.
75+
* </p>
76+
*
77+
* @param <REQ> The request type
78+
* @param <REP> The response type
79+
* @param reqs List of requests to process
80+
* @param supplier Function to execute the batch of requests
81+
* @return List of results corresponding to the input requests
82+
* @throws BatchRequestException if any request in the batch fails
83+
*/
84+
@Override
85+
@SuppressWarnings("unchecked")
86+
public <REQ extends Request<?>, REP extends Result<REQ>> List<REP> requests(
87+
List<REQ> reqs, Function<List<REQ>, List<REP>> supplier) {
88+
final Map<REQ, Object> nonCachedResults = new HashMap<>();
89+
List<RequestResult<REQ, REP>> allResults = new ArrayList<>(reqs.size());
90+
91+
Function<REQ, REP> individualSupplier = req -> {
92+
synchronized (nonCachedResults) {
93+
while (!nonCachedResults.containsKey(req)) {
94+
try {
95+
nonCachedResults.wait();
96+
} catch (InterruptedException e) {
97+
Thread.currentThread().interrupt();
98+
throw new RuntimeException(e);
99+
}
100+
}
101+
Object val = nonCachedResults.get(req);
102+
if (val instanceof CachingSupplier.AltRes altRes) {
103+
uncheckedThrow(altRes.throwable);
104+
}
105+
return (REP) val;
106+
}
107+
};
108+
109+
List<CachingSupplier<REQ, REP>> suppliers = new ArrayList<>(reqs.size());
110+
List<REQ> nonCached = new ArrayList<>();
111+
for (REQ req : reqs) {
112+
CachingSupplier<REQ, REP> cs = doCache(req, individualSupplier);
113+
suppliers.add(cs);
114+
if (cs.getValue() == null) {
115+
nonCached.add(req);
116+
}
117+
}
118+
119+
if (!nonCached.isEmpty()) {
120+
synchronized (nonCachedResults) {
121+
try {
122+
List<REP> reps = supplier.apply(nonCached);
123+
for (int i = 0; i < reps.size(); i++) {
124+
nonCachedResults.put(nonCached.get(i), reps.get(i));
125+
}
126+
} catch (MavenExecutionException e) {
127+
// If batch request fails, mark all non-cached requests as failed
128+
for (REQ req : nonCached) {
129+
nonCachedResults.put(
130+
req, new CachingSupplier.AltRes(e.getCause())); // Mark as processed but failed
131+
}
132+
} finally {
133+
nonCachedResults.notifyAll();
134+
}
135+
}
136+
}
137+
138+
// Collect results in original order
139+
boolean hasFailures = false;
140+
for (int i = 0; i < reqs.size(); i++) {
141+
REQ req = reqs.get(i);
142+
CachingSupplier<REQ, REP> cs = suppliers.get(i);
143+
try {
144+
REP value = cs.apply(req);
145+
allResults.add(new RequestResult<>(req, value, null));
146+
} catch (Throwable t) {
147+
hasFailures = true;
148+
allResults.add(new RequestResult<>(req, null, t));
149+
}
150+
}
151+
152+
if (hasFailures) {
153+
throw new BatchRequestException("One or more requests failed", allResults);
154+
}
155+
156+
return allResults.stream().map(RequestResult::result).toList();
157+
}
158+
159+
/**
160+
* Abstract method to be implemented by subclasses to handle caching logic.
161+
* <p>
162+
* This method is responsible for determining whether a request result should be cached,
163+
* retrieving it from cache if available, or executing the supplier function if necessary.
164+
* </p>
165+
*
166+
* @param <REQ> The request type
167+
* @param <REP> The response type
168+
* @param req The request object
169+
* @param supplier The function that provides the response
170+
* @return A caching supplier that handles caching logic for the request
171+
*/
172+
protected abstract <REQ extends Request<?>, REP extends Result<REQ>> CachingSupplier<REQ, REP> doCache(
173+
REQ req, Function<REQ, REP> supplier);
174+
175+
@SuppressWarnings("unchecked")
176+
protected static <T extends Throwable> void uncheckedThrow(Throwable t) throws T {
177+
throw (T) t; // rely on vacuous cast
178+
}
179+
}

impl/maven-impl/src/main/java/org/apache/maven/impl/cache/CachingSupplier.java

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@
2727
* @param <REQ> The request type
2828
* @param <REP> The response type
2929
*/
30-
class CachingSupplier<REQ, REP> implements Function<REQ, REP> {
31-
final Function<REQ, REP> supplier;
32-
volatile Object value;
30+
public class CachingSupplier<REQ, REP> implements Function<REQ, REP> {
31+
protected final Function<REQ, REP> supplier;
32+
protected volatile Object value;
3333

34-
CachingSupplier(Function<REQ, REP> supplier) {
34+
public CachingSupplier(Function<REQ, REP> supplier) {
3535
this.supplier = supplier;
3636
}
3737

38-
Object getValue() {
38+
public Object getValue() {
3939
return value;
4040
}
4141

@@ -55,7 +55,7 @@ public REP apply(REQ req) {
5555
}
5656
}
5757
if (v instanceof AltRes altRes) {
58-
DefaultRequestCache.uncheckedThrow(altRes.t);
58+
DefaultRequestCache.uncheckedThrow(altRes.throwable);
5959
}
6060
return (REP) v;
6161
}
@@ -64,16 +64,20 @@ public REP apply(REQ req) {
6464
* Special holder class for exceptions that occur during supplier execution.
6565
* Allows caching and re-throwing of exceptions on subsequent calls.
6666
*/
67-
static class AltRes {
68-
final Throwable t;
67+
public static class AltRes {
68+
protected final Throwable throwable;
6969

7070
/**
7171
* Creates a new AltRes with the given throwable.
7272
*
73-
* @param t The throwable to store
73+
* @param throwable The throwable to store
7474
*/
75-
AltRes(Throwable t) {
76-
this.t = t;
75+
public AltRes(Throwable throwable) {
76+
this.throwable = throwable;
77+
}
78+
79+
public Throwable getThrowable() {
80+
return throwable;
7781
}
7882
}
7983
}

impl/maven-impl/src/main/java/org/apache/maven/impl/cache/DefaultRequestCache.java

Lines changed: 6 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@
1818
*/
1919
package org.apache.maven.impl.cache;
2020

21-
import java.util.ArrayList;
22-
import java.util.HashMap;
23-
import java.util.List;
2421
import java.util.Map;
2522
import java.util.Objects;
2623
import java.util.concurrent.ConcurrentHashMap;
@@ -29,107 +26,22 @@
2926

3027
import org.apache.maven.api.Session;
3128
import org.apache.maven.api.SessionData;
32-
import org.apache.maven.api.cache.BatchRequestException;
3329
import org.apache.maven.api.cache.CacheMetadata;
3430
import org.apache.maven.api.cache.CacheRetention;
35-
import org.apache.maven.api.cache.MavenExecutionException;
36-
import org.apache.maven.api.cache.RequestCache;
37-
import org.apache.maven.api.cache.RequestResult;
3831
import org.apache.maven.api.services.Request;
3932
import org.apache.maven.api.services.RequestTrace;
4033
import org.apache.maven.api.services.Result;
4134

42-
public class DefaultRequestCache implements RequestCache {
35+
public class DefaultRequestCache extends AbstractRequestCache {
4336

44-
private static final SessionData.Key<ConcurrentMap> KEY = SessionData.key(ConcurrentMap.class, CacheMetadata.class);
45-
private static final Object ROOT = new Object();
37+
protected static final SessionData.Key<ConcurrentMap> KEY =
38+
SessionData.key(ConcurrentMap.class, CacheMetadata.class);
39+
protected static final Object ROOT = new Object();
4640

47-
private final Map<Object, CachingSupplier<?, ?>> forever = new ConcurrentHashMap<>();
48-
49-
@Override
50-
@SuppressWarnings("all")
51-
public <REQ extends Request<?>, REP extends Result<REQ>> REP request(REQ req, Function<REQ, REP> supplier) {
52-
CachingSupplier<REQ, REP> cs = doCache(req, supplier);
53-
return cs.apply(req);
54-
}
55-
56-
@Override
57-
@SuppressWarnings("unchecked")
58-
public <REQ extends Request<?>, REP extends Result<REQ>> List<REP> requests(
59-
List<REQ> reqs, Function<List<REQ>, List<REP>> supplier) {
60-
final Map<REQ, Object> nonCachedResults = new HashMap<>();
61-
List<RequestResult<REQ, REP>> allResults = new ArrayList<>(reqs.size());
62-
63-
Function<REQ, REP> individualSupplier = req -> {
64-
synchronized (nonCachedResults) {
65-
while (!nonCachedResults.containsKey(req)) {
66-
try {
67-
nonCachedResults.wait();
68-
} catch (InterruptedException e) {
69-
Thread.currentThread().interrupt();
70-
throw new RuntimeException(e);
71-
}
72-
}
73-
Object val = nonCachedResults.get(req);
74-
if (val instanceof CachingSupplier.AltRes altRes) {
75-
uncheckedThrow(altRes.t);
76-
}
77-
return (REP) val;
78-
}
79-
};
80-
81-
List<CachingSupplier<REQ, REP>> suppliers = new ArrayList<>(reqs.size());
82-
List<REQ> nonCached = new ArrayList<>();
83-
for (REQ req : reqs) {
84-
CachingSupplier<REQ, REP> cs = doCache(req, individualSupplier);
85-
suppliers.add(cs);
86-
if (cs.getValue() == null) {
87-
nonCached.add(req);
88-
}
89-
}
90-
91-
if (!nonCached.isEmpty()) {
92-
synchronized (nonCachedResults) {
93-
try {
94-
List<REP> reps = supplier.apply(nonCached);
95-
for (int i = 0; i < reps.size(); i++) {
96-
nonCachedResults.put(nonCached.get(i), reps.get(i));
97-
}
98-
} catch (MavenExecutionException e) {
99-
// If batch request fails, mark all non-cached requests as failed
100-
for (REQ req : nonCached) {
101-
nonCachedResults.put(
102-
req, new CachingSupplier.AltRes(e.getCause())); // Mark as processed but failed
103-
}
104-
} finally {
105-
nonCachedResults.notifyAll();
106-
}
107-
}
108-
}
109-
110-
// Collect results in original order
111-
boolean hasFailures = false;
112-
for (int i = 0; i < reqs.size(); i++) {
113-
REQ req = reqs.get(i);
114-
CachingSupplier<REQ, REP> cs = suppliers.get(i);
115-
try {
116-
REP value = cs.apply(req);
117-
allResults.add(new RequestResult<>(req, value, null));
118-
} catch (Throwable t) {
119-
hasFailures = true;
120-
allResults.add(new RequestResult<>(req, null, t));
121-
}
122-
}
123-
124-
if (hasFailures) {
125-
throw new BatchRequestException("One or more requests failed", allResults);
126-
}
127-
128-
return allResults.stream().map(RequestResult::result).toList();
129-
}
41+
protected final Map<Object, CachingSupplier<?, ?>> forever = new ConcurrentHashMap<>();
13042

13143
@SuppressWarnings("unchecked")
132-
private <REQ extends Request<?>, REP extends Result<REQ>> CachingSupplier<REQ, REP> doCache(
44+
protected <REQ extends Request<?>, REP extends Result<REQ>> CachingSupplier<REQ, REP> doCache(
13345
REQ req, Function<REQ, REP> supplier) {
13446
CacheRetention retention = Objects.requireNonNullElse(
13547
req instanceof CacheMetadata metadata ? metadata.getCacheRetention() : null,
@@ -159,9 +71,4 @@ private <REQ extends Request<?>> Object doGetOuterRequest(REQ req) {
15971
}
16072
return trace != null && trace.data() != null ? trace.data() : req;
16173
}
162-
163-
@SuppressWarnings("unchecked")
164-
static <T extends Throwable> void uncheckedThrow(Throwable t) throws T {
165-
throw (T) t; // rely on vacuous cast
166-
}
16774
}

0 commit comments

Comments
 (0)