View Javadoc
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 }