1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.buildcache;
20
21 import javax.annotation.Nonnull;
22 import javax.inject.Inject;
23 import javax.inject.Named;
24
25 import java.io.Closeable;
26 import java.io.File;
27 import java.io.IOException;
28 import java.lang.reflect.Method;
29 import java.net.URI;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.util.List;
33 import java.util.Optional;
34 import java.util.concurrent.atomic.AtomicReference;
35
36 import org.apache.http.HttpStatus;
37 import org.apache.http.client.HttpResponseException;
38 import org.apache.maven.SessionScoped;
39 import org.apache.maven.buildcache.checksum.MavenProjectInput;
40 import org.apache.maven.buildcache.xml.Build;
41 import org.apache.maven.buildcache.xml.CacheConfig;
42 import org.apache.maven.buildcache.xml.CacheSource;
43 import org.apache.maven.buildcache.xml.XmlService;
44 import org.apache.maven.buildcache.xml.build.Artifact;
45 import org.apache.maven.buildcache.xml.report.CacheReport;
46 import org.apache.maven.buildcache.xml.report.ProjectReport;
47 import org.apache.maven.execution.MavenSession;
48 import org.apache.maven.project.MavenProject;
49 import org.apache.maven.wagon.ResourceDoesNotExistException;
50 import org.eclipse.aether.RepositorySystemSession;
51 import org.eclipse.aether.repository.Authentication;
52 import org.eclipse.aether.repository.Proxy;
53 import org.eclipse.aether.repository.RemoteRepository;
54 import org.eclipse.aether.spi.connector.transport.GetTask;
55 import org.eclipse.aether.spi.connector.transport.PutTask;
56 import org.eclipse.aether.spi.connector.transport.Transporter;
57 import org.eclipse.aether.spi.connector.transport.TransporterProvider;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60
61
62
63
64 @SessionScoped
65 @Named("resolver")
66 public class RemoteCacheRepositoryImpl implements RemoteCacheRepository, Closeable {
67
68 private static final Logger LOGGER = LoggerFactory.getLogger(RemoteCacheRepositoryImpl.class);
69
70 private final XmlService xmlService;
71 private final CacheConfig cacheConfig;
72 private final Transporter transporter;
73
74 @Inject
75 public RemoteCacheRepositoryImpl(
76 XmlService xmlService,
77 CacheConfig cacheConfig,
78 MavenSession mavenSession,
79 TransporterProvider transporterProvider)
80 throws Exception {
81 this.xmlService = xmlService;
82 this.cacheConfig = cacheConfig;
83 if (cacheConfig.isRemoteCacheEnabled()) {
84 RepositorySystemSession session = mavenSession.getRepositorySession();
85 RemoteRepository repo =
86 new RemoteRepository.Builder(cacheConfig.getId(), "cache", cacheConfig.getUrl()).build();
87 RemoteRepository mirror = session.getMirrorSelector().getMirror(repo);
88 RemoteRepository repoOrMirror = mirror != null ? mirror : repo;
89 Proxy proxy = session.getProxySelector().getProxy(repoOrMirror);
90 Authentication auth = session.getAuthenticationSelector().getAuthentication(repoOrMirror);
91 RemoteRepository repository = new RemoteRepository.Builder(repoOrMirror)
92 .setProxy(proxy)
93 .setAuthentication(auth)
94 .build();
95 this.transporter = transporterProvider.newTransporter(session, repository);
96 } else {
97 this.transporter = null;
98 }
99 }
100
101 @Override
102 public void close() throws IOException {
103 if (transporter != null) {
104 transporter.close();
105 }
106 }
107
108 @Nonnull
109 @Override
110 public Optional<Build> findBuild(CacheContext context) throws IOException {
111 final String resourceUrl = getResourceUrl(context, BUILDINFO_XML);
112 return getResourceContent(resourceUrl)
113 .map(content -> new Build(xmlService.loadBuild(content), CacheSource.REMOTE));
114 }
115
116 @Override
117 public boolean getArtifactContent(CacheContext context, Artifact artifact, Path target) {
118 return getResourceContent(getResourceUrl(context, artifact.getFileName()), target);
119 }
120
121 @Override
122 public void saveBuildInfo(CacheResult cacheResult, Build build) throws IOException {
123 final String resourceUrl = getResourceUrl(cacheResult.getContext(), BUILDINFO_XML);
124 putToRemoteCache(xmlService.toBytes(build.getDto()), resourceUrl);
125 }
126
127 @Override
128 public void saveCacheReport(String buildId, MavenSession session, CacheReport cacheReport) throws IOException {
129 MavenProject rootProject = session.getTopLevelProject();
130 final String resourceUrl = MavenProjectInput.CACHE_IMPLEMENTATION_VERSION
131 + "/" + rootProject.getGroupId()
132 + "/" + rootProject.getArtifactId()
133 + "/" + buildId
134 + "/" + CACHE_REPORT_XML;
135 putToRemoteCache(xmlService.toBytes(cacheReport), resourceUrl);
136 }
137
138 @Override
139 public void saveArtifactFile(CacheResult cacheResult, org.apache.maven.artifact.Artifact artifact)
140 throws IOException {
141 final String resourceUrl = getResourceUrl(cacheResult.getContext(), CacheUtils.normalizedName(artifact));
142 putToRemoteCache(artifact.getFile(), resourceUrl);
143 }
144
145
146
147
148
149
150 @Nonnull
151 public Optional<byte[]> getResourceContent(String url) {
152 String fullUrl = getFullUrl(url);
153 try {
154 LOGGER.info("Downloading {}", fullUrl);
155 GetTask task = new GetTask(new URI(url));
156 transporter.get(task);
157 return Optional.of(task.getDataBytes());
158 } catch (ResourceDoesNotExistException e) {
159 logNotFound(fullUrl, e);
160 return Optional.empty();
161 } catch (Exception e) {
162
163
164 if ((e instanceof HttpResponseException
165 || e.getClass().getName().equals(HttpResponseException.class.getName()))
166 && getStatusCode(e) == HttpStatus.SC_NOT_FOUND) {
167 logNotFound(fullUrl, e);
168 return Optional.empty();
169 }
170 if (cacheConfig.isFailFast()) {
171 LOGGER.error("Error downloading cache item: {}", fullUrl, e);
172 throw new RuntimeException("Error downloading cache item: " + fullUrl, e);
173 } else {
174 LOGGER.error("Error downloading cache item: {}", fullUrl);
175 return Optional.empty();
176 }
177 }
178 }
179
180 private int getStatusCode(Exception ex) {
181
182
183
184
185
186
187 try {
188 Method method = ex.getClass().getMethod("getStatusCode");
189 return (int) method.invoke(ex);
190 } catch (Throwable t) {
191 LOGGER.debug(t.getMessage(), t);
192 return 0;
193 }
194 }
195
196 private void logNotFound(String fullUrl, Exception e) {
197 if (LOGGER.isDebugEnabled()) {
198 LOGGER.info("Cache item not found: {}", fullUrl, e);
199 } else {
200 LOGGER.info("Cache item not found: {}", fullUrl);
201 }
202 }
203
204 public boolean getResourceContent(String url, Path target) {
205 try {
206 LOGGER.info("Downloading {}", getFullUrl(url));
207 GetTask task = new GetTask(new URI(url)).setDataFile(target.toFile());
208 transporter.get(task);
209 return true;
210 } catch (Exception e) {
211 LOGGER.info("Cannot download {}: {}", getFullUrl(url), e.toString());
212 return false;
213 }
214 }
215
216 @Nonnull
217 @Override
218 public String getResourceUrl(CacheContext context, String filename) {
219 return getResourceUrl(
220 filename,
221 context.getProject().getGroupId(),
222 context.getProject().getArtifactId(),
223 context.getInputInfo().getChecksum());
224 }
225
226 private String getResourceUrl(String filename, String groupId, String artifactId, String checksum) {
227 return MavenProjectInput.CACHE_IMPLEMENTATION_VERSION + "/" + groupId + "/" + artifactId + "/" + checksum + "/"
228 + filename;
229 }
230
231 private void putToRemoteCache(byte[] bytes, String url) throws IOException {
232 Path tmp = Files.createTempFile("mbce-", ".tmp");
233 try {
234 Files.write(tmp, bytes);
235 PutTask put = new PutTask(new URI(url));
236 put.setDataFile(tmp.toFile());
237 transporter.put(put);
238 LOGGER.info("Saved to remote cache {}", getFullUrl(url));
239 } catch (Exception e) {
240 LOGGER.info("Unable to save to remote cache {}", getFullUrl(url), e);
241 } finally {
242 Files.deleteIfExists(tmp);
243 }
244 }
245
246 private void putToRemoteCache(File file, String url) throws IOException {
247 try {
248 PutTask put = new PutTask(new URI(url));
249 put.setDataFile(file);
250 transporter.put(put);
251 LOGGER.info("Saved to remote cache {}", getFullUrl(url));
252 } catch (Exception e) {
253 LOGGER.info("Unable to save to remote cache {}", getFullUrl(url), e);
254 }
255 }
256
257 private final AtomicReference<CacheReport> cacheReportSupplier = new AtomicReference<>();
258
259 @Nonnull
260 @Override
261 public Optional<Build> findBaselineBuild(MavenProject project) {
262 Optional<List<ProjectReport>> cachedProjectsHolder = findCacheInfo().map(CacheReport::getProjects);
263
264 if (!cachedProjectsHolder.isPresent()) {
265 return Optional.empty();
266 }
267
268 final List<ProjectReport> projects = cachedProjectsHolder.get();
269 final Optional<ProjectReport> projectReportHolder = projects.stream()
270 .filter(p -> project.getArtifactId().equals(p.getArtifactId())
271 && project.getGroupId().equals(p.getGroupId()))
272 .findFirst();
273
274 if (!projectReportHolder.isPresent()) {
275 return Optional.empty();
276 }
277
278 final ProjectReport projectReport = projectReportHolder.get();
279
280 String url;
281 if (projectReport.getUrl() != null) {
282 url = projectReport.getUrl();
283 LOGGER.info("Retrieving baseline buildinfo: {}", url);
284 } else {
285 url = getResourceUrl(
286 BUILDINFO_XML, project.getGroupId(), project.getArtifactId(), projectReport.getChecksum());
287 LOGGER.info("Baseline project record doesn't have url, trying default location {}", url);
288 }
289
290 try {
291 return getResourceContent(url).map(content -> new Build(xmlService.loadBuild(content), CacheSource.REMOTE));
292 } catch (Exception e) {
293 LOGGER.warn("Error restoring baseline build at url: {}, skipping diff", url, e);
294 return Optional.empty();
295 }
296 }
297
298 private Optional<CacheReport> findCacheInfo() {
299 Optional<CacheReport> report = Optional.ofNullable(cacheReportSupplier.get());
300 if (!report.isPresent()) {
301 try {
302 LOGGER.info("Downloading baseline cache report from: {}", cacheConfig.getBaselineCacheUrl());
303 report = getResourceContent(cacheConfig.getBaselineCacheUrl()).map(xmlService::loadCacheReport);
304 } catch (Exception e) {
305 LOGGER.error(
306 "Error downloading baseline report from: {}, skipping diff.",
307 cacheConfig.getBaselineCacheUrl(),
308 e);
309 report = Optional.empty();
310 }
311 cacheReportSupplier.compareAndSet(null, report.orElse(null));
312 }
313 return report;
314 }
315
316 private String getFullUrl(String url) {
317 return cacheConfig.getUrl() + "/" + url;
318 }
319 }