/*
*
* Copyright (C) 2010 JFrog Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* /
*/
package org.jfrog.wharf.ivy.util;
import org.apache.ivy.core.cache.CacheMetadataOptions;
import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
import org.apache.ivy.core.resolve.ResolveData;
import org.apache.ivy.core.resolve.ResolvedModuleRevision;
import org.apache.ivy.plugins.repository.Resource;
import org.apache.ivy.plugins.resolver.BasicResolver;
import org.apache.ivy.plugins.resolver.util.ResolvedResource;
import org.apache.ivy.util.ChecksumHelper;
import org.apache.ivy.util.FileUtil;
import org.apache.ivy.util.Message;
import org.apache.ivy.util.url.URLHandler;
import org.apache.ivy.util.url.URLHandlerRegistry;
import org.jfrog.wharf.ivy.cache.WharfCacheManager;
import org.jfrog.wharf.ivy.checksum.ChecksumType;
import org.jfrog.wharf.ivy.handler.WharfUrlHandler;
import org.jfrog.wharf.ivy.model.ArtifactMetadata;
import org.jfrog.wharf.ivy.repository.WharfArtifactResourceResolver;
import org.jfrog.wharf.ivy.repository.WharfURLRepository;
import org.jfrog.wharf.ivy.resolver.WharfResolver;
import org.jfrog.wharf.ivy.resolver.WharfResourceDownloader;
import org.jfrog.wharf.ivy.resource.WharfUrlResource;
import java.io.*;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;
import java.util.Properties;
/**
* @author Tomer Cohen
*/
public class WharfUtils {
private final static String VERSION = "version";
private final static String CORE_PROPERTY_FILE = "/org/jfrog/wharf/wharf-core.properties";
public static final String SHA1_ALGORITHM = "sha1";
public static final String MD5_ALGORITHM = "md5";
public static String getChecksumAlgorithm() {
return SHA1_ALGORITHM;
}
public static String getCoreVersion() {
try {
Properties props = new Properties();
props.load(WharfUtils.class.getResourceAsStream(CORE_PROPERTY_FILE));
return props.getProperty(VERSION);
} catch (IOException e) {
return "Error reading " + CORE_PROPERTY_FILE + ": " + e.getMessage();
}
}
public static void hackIvyBasicResolver(WharfResolver wharfResolver) {
try {
// Override the URLRepository
wharfResolver.setRepository(new WharfURLRepository());
// TODO: The following reflection can be removed once Ivy uses a getDownloader and getArtifactResourceResolver methods
Field downloaderField = BasicResolver.class.getDeclaredField("downloader");
downloaderField.setAccessible(true);
downloaderField.set(wharfResolver, new WharfResourceDownloader(wharfResolver));
Field artifactResourceResolverField = BasicResolver.class.getDeclaredField("artifactResourceResolver");
artifactResourceResolverField.setAccessible(true);
artifactResourceResolverField.set(wharfResolver, new WharfArtifactResourceResolver(wharfResolver));
} catch (Exception e) {
throw new RuntimeException("Could not hack Ivy :(", e);
}
}
public static ResolvedResource convertToWharfResource(WharfResolver wharfResolver,
ArtifactMetadata artifactMetadata,
// TODO: Revision should be in artifact metadata
String revision) {
try {
WharfCacheManager cacheManager = (WharfCacheManager) wharfResolver.getRepositoryCacheManager();
File storageFile = cacheManager.getStorageFile(artifactMetadata.sha1);
WharfUrlResource urlResource = new WharfUrlResource(new URL(artifactMetadata.location));
urlResource.initWith(storageFile, artifactMetadata);
return new ResolvedResource(urlResource, revision);
} catch (MalformedURLException e) {
Message.warn("Artifact metadata " + artifactMetadata.id +
" contains an invalid URL " + artifactMetadata.location + " :" + e.getMessage());
return null;
}
}
public static ResolvedResource convertToWharfResource(ResolvedResource resolvedResource) {
if (resolvedResource == null) {
return null;
}
Resource resource = resolvedResource.getResource();
if (resource == null) {
return resolvedResource;
}
if (resource instanceof WharfUrlResource) {
return resolvedResource;
}
return new ResolvedResource(new WharfUrlResource(resource), resolvedResource.getRevision());
}
public static WharfUrlHandler getWharfUrlHandler() {
// Enforce WharfUrlHandler TODO: Remove ugly static in Ivy
URLHandler urlHandler = URLHandlerRegistry.getDefault();
if (!(urlHandler instanceof WharfUrlHandler)) {
urlHandler = new WharfUrlHandler();
URLHandlerRegistry.setDefault(urlHandler);
}
return (WharfUrlHandler) urlHandler;
}
public static ResolvedModuleRevision findModuleInCache(WharfResolver wharfResolver, DependencyDescriptor dd, ResolveData data) {
WharfCacheManager cacheManager = (WharfCacheManager) wharfResolver.getRepositoryCacheManager();
// If check modified is true, make sure to clean the resource cache
CacheMetadataOptions cacheOptions = wharfResolver.getCacheOptions(data);
if (cacheOptions.isCheckmodified() != null && cacheOptions.isCheckmodified()) {
Message.verbose("don't use cache for " + dd + ": checkModified=true");
// TODO: Check if we can Remove this global flag
WharfURLRepository.setAlwaysCheck(true);
return null;
}
if (cacheManager.isChanging(dd, dd.getDependencyRevisionId(), cacheOptions)) {
Message.verbose("don't use cache for " + dd + ": changing=true");
// TODO: Check if we can Remove this global flag
WharfURLRepository.setAlwaysCheck(true);
return null;
}
return wharfResolver.basicFindModuleInCache(dd, data, false);
}
public static void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
Message.verbose("Closing " + closeable + " throw an exception: " + e.getMessage());
}
}
}
private enum OperatingSystem {
OLD_WINDOWS {
@Override
void linkCacheFileToStorage(File storageFile, File cacheFile) throws IOException {
FileUtil.copy(storageFile, cacheFile, new WharfCopyListener(), true);
}
},
NEW_WINDOWS {
@Override
void linkCacheFileToStorage(File storageFile, File cacheFile) throws IOException {
WindowsUtils.windowsSymlink(storageFile, cacheFile, new WharfCopyListener(), true);
}
},
OS_X, OTHER;
void linkCacheFileToStorage(File storageFile, File cacheFile) throws IOException {
FileUtil.symlink(storageFile, cacheFile, new WharfCopyListener(), true);
}
}
private static final OperatingSystem OS;
static {
String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("windows")) {
if (osName.contains("vista") || osName.contains("7")) {
OS = OperatingSystem.NEW_WINDOWS;
} else {
OS = OperatingSystem.OLD_WINDOWS;
}
} else if (osName.contains("mac os x")) {
OS = OperatingSystem.OS_X;
} else {
OS = OperatingSystem.OTHER;
}
}
public static void linkCacheFileToStorage(File storageFile, File cacheFile) throws IOException {
OS.linkCacheFileToStorage(storageFile, cacheFile);
}
public static String getCleanChecksum(File checksumFile) throws IOException {
if (!checksumFile.canRead()) {
return null;
}
String csFileContent = FileUtil.readEntirely(
new BufferedReader(new FileReader(checksumFile))).trim().toLowerCase(Locale.US);
return getCleanChecksum(csFileContent);
}
public static String getCleanChecksum(String checksum) {
if (checksum == null) {
return null;
}
checksum = checksum.trim();
String cleanChecksum;
if (checksum.indexOf(' ') > -1
&& (checksum.startsWith("md") || checksum.startsWith("sha"))) {
int lastSpaceIndex = checksum.lastIndexOf(' ');
cleanChecksum = checksum.substring(lastSpaceIndex + 1);
} else {
int spaceIndex = checksum.indexOf(' ');
if (spaceIndex != -1) {
cleanChecksum = checksum.substring(0, spaceIndex);
// IVY-1155: support some strange formats like this one:
// http://repo2.maven.org/maven2/org/apache/pdfbox/fontbox/0.8.0-incubator/fontbox-0.8.0-incubator.jar.md5
if (cleanChecksum.endsWith(":")) {
StringBuffer result = new StringBuffer();
char[] chars = checksum.substring(spaceIndex + 1).toCharArray();
for (char aChar : chars) {
if (!Character.isWhitespace(aChar)) {
result.append(aChar);
}
}
cleanChecksum = result.toString();
}
} else {
cleanChecksum = checksum;
}
}
return cleanChecksum;
}
public static String computeUUID(String content) {
String algorithm = MD5_ALGORITHM;
if (!ChecksumHelper.isKnownAlgorithm(algorithm)) {
throw new IllegalArgumentException("unknown algorithm " + algorithm);
}
try {
MessageDigest md = MessageDigest.getInstance(algorithm);
md.reset();
byte[] bytes = content.trim().toLowerCase(Locale.US).getBytes("UTF-8");
md.update(bytes, 0, bytes.length);
byte[] digest = md.digest();
return ChecksumHelper.byteArrayToHexString(digest);
} catch (NoSuchAlgorithmException e) {
// Impossible
throw new IllegalArgumentException("unknown algorithm " + algorithm, e);
} catch (UnsupportedEncodingException e) {
// Impossible except with IBM :)
throw new IllegalArgumentException("unknown charset UTF-8", e);
}
}
public static long getAndCheck(WharfResolver wharfResolver, Resource resource, File dest) throws IOException {
if (!(resource instanceof WharfUrlResource)) {
throw new IllegalArgumentException("The Wharf Resolver manage only WharfUrlResource");
}
WharfUrlResource wharfResource = (WharfUrlResource) resource;
WharfCacheManager cacheManager = (WharfCacheManager) wharfResolver.getRepositoryCacheManager();
// First get the checksum for this resource
String checksumValue = wharfResource.getSha1();
File tempStorageFile = cacheManager.getTempStorageFile();
if (!tempStorageFile.getParentFile().exists()) {
tempStorageFile.getParentFile().mkdirs();
}
try {
WharfURLRepository wharfUrlRepository = wharfResolver.getWharfUrlRepository();
if (checksumValue == null && wharfResolver.supportsWrongSha1()) {
wharfUrlRepository.get(wharfResource, tempStorageFile);
// Check with the actual sha1 now
checksumValue = wharfResource.getActual().get(ChecksumType.sha1);
}
if (checksumValue == null) {
throw new IOException(
"Checksum " + ChecksumType.sha1.alg() + " not found for " + resource.getName());
}
File storageFile = cacheManager.getStorageFile(checksumValue);
if (!storageFile.exists()) {
// Not in storage cache => download to temp if needed
if (!tempStorageFile.exists()) {
wharfUrlRepository.get(wharfResource, tempStorageFile);
}
wharfUrlRepository.checkChecksums(wharfResource);
if (!storageFile.getParentFile().exists()) {
storageFile.getParentFile().mkdirs();
}
tempStorageFile.renameTo(storageFile);
}
// If we get here, then the file was found in cache with the good checksum!
// Just need to link the storage to file to the final cache destination.
WharfUtils.linkCacheFileToStorage(storageFile, dest);
if (!storageFile.setLastModified(resource.getLastModified())) {
throw new IOException("Could not change the timestamp of " + storageFile.getAbsolutePath());
}
return dest.length();
} finally {
if (tempStorageFile.exists()) {
FileUtil.forceDelete(tempStorageFile);
}
}
}
public static boolean isEmptyString(String s) {
return s == null || s.length() == 0 || s.trim().length() == 0;
}
}