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 BatchRequestException exception = new BatchRequestException("One or more requests failed", allResults);
154 // Add all individual exceptions as suppressed exceptions to preserve stack traces
155 for (RequestResult<REQ, REP> result : allResults) {
156 if (result.error() != null) {
157 exception.addSuppressed(result.error());
158 }
159 }
160 throw exception;
161 }
162
163 return allResults.stream().map(RequestResult::result).toList();
164 }
165
166 /**
167 * Abstract method to be implemented by subclasses to handle caching logic.
168 * <p>
169 * This method is responsible for determining whether a request result should be cached,
170 * retrieving it from cache if available, or executing the supplier function if necessary.
171 * </p>
172 *
173 * @param <REQ> The request type
174 * @param <REP> The response type
175 * @param req The request object
176 * @param supplier The function that provides the response
177 * @return A caching supplier that handles caching logic for the request
178 */
179 protected abstract <REQ extends Request<?>, REP extends Result<REQ>> CachingSupplier<REQ, REP> doCache(
180 REQ req, Function<REQ, REP> supplier);
181
182 @SuppressWarnings("unchecked")
183 protected static <T extends Throwable> void uncheckedThrow(Throwable t) throws T {
184 throw (T) t; // rely on vacuous cast
185 }
186 }