/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package org.gradle.internal.resource.transport.http; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.net.URI; import java.util.Iterator; import java.util.SortedSet; import java.util.TreeSet; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.filefilter.DirectoryFileFilter; import org.apache.commons.io.filefilter.NameFileFilter; import org.apache.commons.io.filefilter.TrueFileFilter; import org.apache.commons.lang.StringUtils; import org.apache.http.HttpHeaders; import org.apache.http.HttpStatus; import org.apache.http.HttpVersion; import org.apache.http.StatusLine; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicHttpResponse; import org.apache.http.message.BasicStatusLine; import org.apache.maven.artifact.repository.metadata.Metadata; import org.apache.maven.artifact.repository.metadata.Versioning; import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Writer; import org.apache.maven.artifact.versioning.ComparableVersion; import org.gradle.internal.hash.HashUtil; import org.gradle.internal.hash.HashValue; import org.gradle.internal.resource.metadata.DefaultExternalResourceMetaData; import org.gradle.internal.resource.metadata.ExternalResourceMetaData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Andrea Di Giorgi */ public class LiferayHttpResourceAccessor extends HttpResourceAccessor { public LiferayHttpResourceAccessor(HttpClientHelper httpClientHelper) { super(httpClientHelper); } @Override public ExternalResourceMetaData getMetaData(URI uri, boolean revalidate) { if (!_isForcedCacheEnabled()) { return super.getMetaData(uri, revalidate); } String location = _getLocation(uri); if (StringUtils.isBlank(location)) { return super.getMetaData(uri, revalidate); } File cachedArtifactFile = _getCachedArtifactFile(location); if (cachedArtifactFile == null) { return super.getMetaData(uri, revalidate); } HashValue hashValue = HashUtil.sha1(cachedArtifactFile); return new DefaultExternalResourceMetaData( uri, cachedArtifactFile.lastModified(), cachedArtifactFile.length(), null, hashValue.asHexString(), hashValue); } @Override public HttpResponseResource openResource(URI uri, boolean revalidate) { if (!_isForcedCacheEnabled()) { return super.openResource(uri, revalidate); } String location = _getLocation(uri); if (StringUtils.isBlank(location)) { return super.openResource(uri, revalidate); } HttpResponseResource httpResponseResource = null; try { if (StringUtils.endsWithIgnoreCase( location, "/maven-metadata.xml")) { httpResponseResource = _getMavenMetadataResponseResource( uri, location); } else if (StringUtils.endsWithIgnoreCase(location, ".sha1")) { httpResponseResource = _getSHA1ResponseResource(uri, location); } } catch (Exception e) { _logger.error(e.getMessage(), e); } if (httpResponseResource == null) { httpResponseResource = super.openResource(uri, revalidate); } return httpResponseResource; } private File _fetchCachedFile(File dir, String fileName) { File cachedFile = null; Iterator<File> iterator = FileUtils.iterateFiles( dir, new NameFileFilter(fileName), TrueFileFilter.TRUE); while (iterator.hasNext()) { File file = iterator.next(); if ((cachedFile == null) || (cachedFile.lastModified() < file.lastModified())) { cachedFile = file; } } return cachedFile; } private File _getCachedArtifactFile(String location) { String[] tokens = StringUtils.split(location, '/'); String fileName = tokens[tokens.length - 1]; String group = StringUtils.join(tokens, '.', 0, tokens.length - 3); String module = tokens[tokens.length - 3]; String version = tokens[tokens.length - 2]; File moduleDir = new File( _getGradleUserHome(), _FILES_CACHE_DIR_NAME + "/" + group + "/" + module); File artifactDir = new File(moduleDir, version); if (!artifactDir.exists()) { if (!StringUtils.endsWithIgnoreCase(version, "-SNAPSHOT") || !StringUtils.startsWithIgnoreCase(fileName, module + "-")) { return null; } // If the name of the artifact directory in the Gradle cache is a // unique snapshot version (e.g., 3.10.200-20150904.172142-1), the // version token of the requested URI is just the snapshot version // (e.g., 3.10.200-SNAPSHOT). int pos = fileName.lastIndexOf('.'); version = fileName.substring(module.length() + 1, pos); artifactDir = new File(moduleDir, version); if (!artifactDir.exists()) { return null; } } File cachedFile = _fetchCachedFile(artifactDir, fileName); if (cachedFile == null) { cachedFile = _fetchCachedFile( artifactDir, module + "-" + version + "." + FilenameUtils.getExtension(fileName)); } return cachedFile; } private File _getGradleUserHome() { String gradleUserHome = System.getProperty("gradle.user.home"); if (StringUtils.isBlank(gradleUserHome)) { gradleUserHome = System.getenv("GRADLE_USER_HOME"); } if (StringUtils.isBlank(gradleUserHome)) { gradleUserHome = System.getProperty("user.home") + "/.gradle"; } return new File(gradleUserHome); } private String _getLocation(URI uri) { String location = uri.toString(); for (String repositoryUrl : _REPOSITORY_URLS) { if (location.startsWith(repositoryUrl)) { return location.substring(repositoryUrl.length()); } } for (String key : _REPOSITORY_URL_PROPERTY_KEYS) { String repositoryUrl = System.getProperty(key); if ((repositoryUrl == null) || repositoryUrl.isEmpty()) { continue; } if (repositoryUrl.charAt(repositoryUrl.length() - 1) != '/') { repositoryUrl += '/'; } if (location.startsWith(repositoryUrl)) { return location.substring(repositoryUrl.length()); } } return null; } private HttpResponseResource _getMavenMetadataResponseResource( URI uri, String location) throws Exception { String[] tokens = StringUtils.split(location, '/'); String group = null; String module = null; String version = tokens[tokens.length - 2]; if (StringUtils.endsWithIgnoreCase(version, "-SNAPSHOT")) { group = StringUtils.join(tokens, '.', 0, tokens.length - 3); module = tokens[tokens.length - 3]; } else { group = StringUtils.join(tokens, '.', 0, tokens.length - 2); module = version; version = null; } File moduleDir = new File( _getGradleUserHome(), _FILES_CACHE_DIR_NAME + "/" + group + "/" + module); if (!moduleDir.exists()) { return null; } if (StringUtils.isNotBlank(version)) { File artifactDir = new File(moduleDir, version); if (!artifactDir.exists()) { return null; } } Metadata metadata = new Metadata(); metadata.setArtifactId(module); metadata.setGroupId(group); Versioning versioning = new Versioning(); if (StringUtils.isNotBlank(version)) { metadata.setVersion(version); } else { SortedSet<ComparableVersion> moduleVersions = _getModuleVersions( moduleDir, false); for (ComparableVersion moduleVersion : moduleVersions) { versioning.addVersion(moduleVersion.toString()); } } versioning.setLatest(_getModuleLatestVersion(moduleDir, false)); versioning.setRelease(_getModuleLatestVersion(moduleDir, true)); metadata.setVersioning(versioning); MetadataXpp3Writer metadataXpp3Writer = new MetadataXpp3Writer(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); metadataXpp3Writer.write(byteArrayOutputStream, metadata); CloseableHttpResponse closeableHttpResponse = new BasicCloseableHttpResponse( new BasicStatusLine( HttpVersion.HTTP_1_1, HttpStatus.SC_OK, null)); closeableHttpResponse.setEntity( new ByteArrayEntity( byteArrayOutputStream.toByteArray(), ContentType.APPLICATION_XML)); closeableHttpResponse.setHeader( HttpHeaders.CONTENT_LENGTH, String.valueOf(byteArrayOutputStream.size())); return new HttpResponseResource( HttpGet.METHOD_NAME, uri, closeableHttpResponse); } private String _getModuleLatestVersion( File moduleDir, boolean excludeSnapshots) { SortedSet<ComparableVersion> moduleVersions = _getModuleVersions( moduleDir, excludeSnapshots); if (moduleVersions.isEmpty()) { return null; } ComparableVersion moduleVersion = moduleVersions.last(); return moduleVersion.toString(); } private SortedSet<ComparableVersion> _getModuleVersions( File moduleDir, boolean excludeSnapshots) { SortedSet<ComparableVersion> moduleVersions = new TreeSet<ComparableVersion>(); String[] versions = moduleDir.list(DirectoryFileFilter.DIRECTORY); for (String version : versions) { if (excludeSnapshots && StringUtils.endsWithIgnoreCase(version, "-SNAPSHOT")) { continue; } moduleVersions.add(new ComparableVersion(version)); } return moduleVersions; } private HttpResponseResource _getSHA1ResponseResource( URI uri, String location) throws Exception { location = location.substring(0, location.length() - 5); File cachedArtifactFile = _getCachedArtifactFile(location); if (cachedArtifactFile == null) { return null; } HashValue hashValue = HashUtil.sha1(cachedArtifactFile); String sha1 = hashValue.asHexString(); CloseableHttpResponse closeableHttpResponse = new BasicCloseableHttpResponse( new BasicStatusLine( HttpVersion.HTTP_1_1, HttpStatus.SC_OK, null)); closeableHttpResponse.setEntity(new StringEntity(sha1)); closeableHttpResponse.setHeader( HttpHeaders.CONTENT_LENGTH, String.valueOf(sha1.length())); closeableHttpResponse.setHeader( HttpHeaders.LAST_MODIFIED, String.valueOf(cachedArtifactFile.lastModified())); return new HttpResponseResource( HttpGet.METHOD_NAME, uri, closeableHttpResponse); } private boolean _isForcedCacheEnabled() { return Boolean.getBoolean("forced.cache.enabled"); } private static final String _FILES_CACHE_DIR_NAME = "caches/modules-2/files-2.1"; private static final String[] _REPOSITORY_URL_PROPERTY_KEYS = { "repository.private.url", "repository.url" }; private static final String[] _REPOSITORY_URLS = { "http://cdn.repository.liferay.com/nexus/content/groups/public/", "http://repository.liferay.com/nexus/content/groups/public/", "https://cdn.lfrs.sl/repository.liferay.com/nexus/content/groups" + "/public/" }; private static final Logger _logger = LoggerFactory.getLogger( LiferayHttpResourceAccessor.class); private static class BasicCloseableHttpResponse extends BasicHttpResponse implements CloseableHttpResponse { public BasicCloseableHttpResponse(StatusLine statusLine) { super(statusLine); } @Override public void close() throws IOException { } } }