diff options
author | Simon Brandhof <simon.brandhof@gmail.com> | 2013-02-05 12:43:45 +0100 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@gmail.com> | 2013-02-05 12:43:45 +0100 |
commit | 7bf52372a4f63be30ae6513bae70d2f8b775e29b (patch) | |
tree | 459e8af8de426d1b4c320270b367962176390fc4 /sonar-home | |
parent | 5c6c22c2820f420428cb308a22c1a5262d0295e9 (diff) | |
download | sonarqube-7bf52372a4f63be30ae6513bae70d2f8b775e29b.tar.gz sonarqube-7bf52372a4f63be30ae6513bae70d2f8b775e29b.zip |
SONAR-2291 move management of file cache to the new module sonar-home
Diffstat (limited to 'sonar-home')
12 files changed, 796 insertions, 0 deletions
diff --git a/sonar-home/pom.xml b/sonar-home/pom.xml new file mode 100644 index 00000000000..d1e9b1b80fa --- /dev/null +++ b/sonar-home/pom.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar</artifactId> + <version>3.5-SNAPSHOT</version> + </parent> + + <artifactId>sonar-home</artifactId> + + <name>Sonar :: Home</name> + <description>Access the user home directory that contains cache of files</description> + + <dependencies> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <optional>true</optional> + </dependency> + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.easytesting</groupId> + <artifactId>fest-assert</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <!-- used to compare results --> + <groupId>commons-codec</groupId> + <artifactId>commons-codec</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-all</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + <scope>test</scope> + </dependency> + + </dependencies> + +</project> diff --git a/sonar-home/src/main/java/org/sonar/home/cache/FileCache.java b/sonar-home/src/main/java/org/sonar/home/cache/FileCache.java new file mode 100644 index 00000000000..5e57d82a4a0 --- /dev/null +++ b/sonar-home/src/main/java/org/sonar/home/cache/FileCache.java @@ -0,0 +1,143 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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 3 of the License, or (at your option) any later version. + * + * Sonar 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.home.cache; + +import org.apache.commons.io.FileUtils; +import org.sonar.home.log.Log; + +import javax.annotation.CheckForNull; + +import java.io.File; +import java.io.IOException; + +/** + * This class is responsible for managing Sonar batch file cache. You can put file into cache and + * later try to retrieve them. MD5 is used to differentiate files (name is not secure as files may come + * from different Sonar servers and have same name but be actually different, and same for SNAPSHOTs). + */ +public class FileCache { + + private final File dir; + private final FileHashes hashes; + private final Log log; + + FileCache(File dir, Log log, FileHashes fileHashes) { + this.dir = dir; + this.hashes = fileHashes; + this.log = log; + if (!dir.exists()) { + log.debug(String.format("Create cache directory: %s", dir.getAbsolutePath())); + try { + FileUtils.forceMkdir(dir); + } catch (IOException e) { + throw new IllegalStateException("Unable to create cache directory " + dir.getAbsolutePath(), e); + } + } + log.info(String.format("User cache: %s", dir.getAbsolutePath())); + } + + public static FileCache create(File dir, Log log) { + return new FileCache(dir, log, new FileHashes()); + } + + public File getDir() { + return dir; + } + + /** + * Look for a file in the cache by its filename and md5 checksum. If the file is not + * present then return null. + */ + @CheckForNull + public File get(String filename, String hash) { + File cachedFile = new File(new File(dir, hash), filename); + if (cachedFile.exists()) { + return cachedFile; + } + log.debug(String.format("No file found in the cache with name %s and hash %s", filename, hash)); + return null; + } + + public static interface Downloader { + void download(String filename, File toFile) throws IOException; + } + + public File get(String filename, String hash, Downloader downloader) { + // Does not fail if another process tries to create the directory at the same time. + File hashDir = hashDir(hash); + File targetFile = new File(hashDir, filename); + if (!targetFile.exists()) { + File tempFile = newTempFile(filename); + download(downloader, filename, tempFile); + String downloadedHash = hashes.of(tempFile); + if (!hash.equals(downloadedHash)) { + throw new IllegalStateException("INVALID HASH: File " + tempFile.getAbsolutePath() + " was expected to have hash " + hash + + " but was downloaded with hash " + downloadedHash); + } + mkdirQuietly(hashDir); + renameQuietly(tempFile, targetFile); + } + return targetFile; + } + + private void download(Downloader downloader, String filename, File tempFile) { + try { + downloader.download(filename, tempFile); + } catch (IOException e) { + throw new IllegalStateException("Fail to download " + filename + " to " + tempFile, e); + } + } + + private File newTempFile(String filename) { + try { + return File.createTempFile(filename, ".tmp"); + } catch (IOException e) { + throw new IllegalStateException("Fail to create temp file", e); + } + } + + private void renameQuietly(File sourceFile, File targetFile) { + boolean rename = sourceFile.renameTo(targetFile); + if (!rename) { + // Check if the file was cached by another process during download + if (!targetFile.exists()) { + log.warn(String.format("Unable to rename %s to %s", sourceFile.getAbsolutePath(), targetFile.getAbsolutePath())); + log.warn(String.format("A copy/delete will be tempted but with no garantee of atomicity")); + try { + FileUtils.moveFile(sourceFile, targetFile); + } catch (IOException e) { + throw new IllegalStateException("Fail to move " + sourceFile.getAbsolutePath() + " to " + targetFile, e); + } + } + } + } + + private File hashDir(String hash) { + return new File(dir, hash); + } + + private void mkdirQuietly(File hashDir) { + try { + FileUtils.forceMkdir(hashDir); + } catch (IOException e) { + throw new IllegalStateException("Fail to create cache directory: " + hashDir, e); + } + } +} diff --git a/sonar-home/src/main/java/org/sonar/home/cache/FileCacheBuilder.java b/sonar-home/src/main/java/org/sonar/home/cache/FileCacheBuilder.java new file mode 100644 index 00000000000..79c88b4ce68 --- /dev/null +++ b/sonar-home/src/main/java/org/sonar/home/cache/FileCacheBuilder.java @@ -0,0 +1,61 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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 3 of the License, or (at your option) any later version. + * + * Sonar 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.home.cache; + +import org.sonar.home.log.Log; +import org.sonar.home.log.StandardLog; + +import javax.annotation.Nullable; + +import java.io.File; + +public class FileCacheBuilder { + + private File userHome; + private Log log = new StandardLog(); + + public FileCacheBuilder setUserHome(File d) { + this.userHome = d; + return this; + } + + public FileCacheBuilder setLog(Log log) { + this.log = log; + return this; + } + + public FileCacheBuilder setUserHome(@Nullable String path) { + this.userHome = (path == null ? null : new File(path)); + return this; + } + + public FileCache build() { + if (userHome == null) { + String path = System.getenv("SONAR_USER_HOME"); + if (path == null) { + // Default + path = System.getProperty("user.home") + File.separator + ".sonar"; + } + userHome = new File(path); + } + File cacheDir = new File(userHome, "cache"); + return FileCache.create(cacheDir, log); + } +} diff --git a/sonar-home/src/main/java/org/sonar/home/cache/FileHashes.java b/sonar-home/src/main/java/org/sonar/home/cache/FileHashes.java new file mode 100644 index 00000000000..89a973fd2f4 --- /dev/null +++ b/sonar-home/src/main/java/org/sonar/home/cache/FileHashes.java @@ -0,0 +1,73 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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 3 of the License, or (at your option) any later version. + * + * Sonar 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.home.cache; + +import org.apache.commons.io.IOUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.security.DigestInputStream; +import java.security.MessageDigest; + +/** + * Hashes used to store files in the cache directory. + * + * @since 3.5 + */ +public class FileHashes { + + public String of(File file) { + try { + return of(new FileInputStream(file)); + } catch (IOException e) { + throw new IllegalStateException("Fail to compute hash of: " + file.getAbsolutePath(), e); + } + } + + /** + * Computes the hash of given stream. The stream is closed by this method. + */ + public String of(InputStream input) { + DigestInputStream digestInputStream = null; + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + digestInputStream = new DigestInputStream(input, digest); + while (digestInputStream.read() != -1) { + } + byte[] hash = digest.digest(); + return toHex(hash); + + } catch (Exception e) { + throw new IllegalStateException("Fail to compute hash", e); + + } finally { + IOUtils.closeQuietly(digestInputStream); + IOUtils.closeQuietly(input); + } + } + + static String toHex(byte[] bytes) { + BigInteger bi = new BigInteger(1, bytes); + return String.format("%0" + (bytes.length << 1) + "X", bi); + } +}
\ No newline at end of file diff --git a/sonar-home/src/main/java/org/sonar/home/cache/package-info.java b/sonar-home/src/main/java/org/sonar/home/cache/package-info.java new file mode 100644 index 00000000000..ca0cf6417d7 --- /dev/null +++ b/sonar-home/src/main/java/org/sonar/home/cache/package-info.java @@ -0,0 +1,25 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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 3 of the License, or (at your option) any later version. + * + * Sonar 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ + +@ParametersAreNonnullByDefault +package org.sonar.home.cache; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-home/src/main/java/org/sonar/home/log/Log.java b/sonar-home/src/main/java/org/sonar/home/log/Log.java new file mode 100644 index 00000000000..21c6a0eb459 --- /dev/null +++ b/sonar-home/src/main/java/org/sonar/home/log/Log.java @@ -0,0 +1,31 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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 3 of the License, or (at your option) any later version. + * + * Sonar 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.home.log; + +public interface Log { + void debug(String s); + + void info(String s); + + void warn(String s); + + void error(String s, Throwable throwable); + +} diff --git a/sonar-home/src/main/java/org/sonar/home/log/Slf4jLog.java b/sonar-home/src/main/java/org/sonar/home/log/Slf4jLog.java new file mode 100644 index 00000000000..736e624dac2 --- /dev/null +++ b/sonar-home/src/main/java/org/sonar/home/log/Slf4jLog.java @@ -0,0 +1,58 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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 3 of the License, or (at your option) any later version. + * + * Sonar 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.home.log; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Slf4jLog implements Log { + + private final Logger logger; + + public Slf4jLog(Logger logger) { + this.logger = logger; + } + + public Slf4jLog(Class loggerClass) { + this.logger = LoggerFactory.getLogger(loggerClass); + } + + public boolean isDebugEnabled() { + return logger.isDebugEnabled(); + } + + public void debug(String s) { + logger.debug(s); + } + + public void info(String s) { + logger.info(s); + } + + public void warn(String s) { + logger.warn(s); + } + + public void error(String s, Throwable throwable) { + logger.error(s, throwable); + } + + +} diff --git a/sonar-home/src/main/java/org/sonar/home/log/StandardLog.java b/sonar-home/src/main/java/org/sonar/home/log/StandardLog.java new file mode 100644 index 00000000000..264d5dfec60 --- /dev/null +++ b/sonar-home/src/main/java/org/sonar/home/log/StandardLog.java @@ -0,0 +1,34 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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 3 of the License, or (at your option) any later version. + * + * Sonar 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.home.log; + +public class StandardLog implements Log { + public void debug(String s) { + } + + public void info(String s) { + } + + public void warn(String s) { + } + + public void error(String s, Throwable throwable) { + } +} diff --git a/sonar-home/src/main/java/org/sonar/home/log/package-info.java b/sonar-home/src/main/java/org/sonar/home/log/package-info.java new file mode 100644 index 00000000000..fee20fa530c --- /dev/null +++ b/sonar-home/src/main/java/org/sonar/home/log/package-info.java @@ -0,0 +1,25 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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 3 of the License, or (at your option) any later version. + * + * Sonar 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ + +@ParametersAreNonnullByDefault +package org.sonar.home.log; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-home/src/test/java/org/sonar/home/cache/FileCacheBuilderTest.java b/sonar-home/src/test/java/org/sonar/home/cache/FileCacheBuilderTest.java new file mode 100644 index 00000000000..658180c0873 --- /dev/null +++ b/sonar-home/src/test/java/org/sonar/home/cache/FileCacheBuilderTest.java @@ -0,0 +1,60 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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 3 of the License, or (at your option) any later version. + * + * Sonar 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.home.cache; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; + +import static org.fest.assertions.Assertions.assertThat; + +public class FileCacheBuilderTest { + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void setUserHome() throws Exception { + File userHome = temp.newFolder(); + FileCache cache = new FileCacheBuilder().setUserHome(userHome).build(); + + assertThat(cache.getDir()).isDirectory().exists(); + assertThat(cache.getDir().getName()).isEqualTo("cache"); + assertThat(cache.getDir().getParentFile()).isEqualTo(userHome); + } + + @Test + public void user_home_property_can_be_null() throws Exception { + FileCache cache = new FileCacheBuilder().setUserHome((String) null).build(); + + // does not fail. It uses default path or env variable + assertThat(cache.getDir()).isDirectory().exists(); + assertThat(cache.getDir().getName()).isEqualTo("cache"); + } + + @Test + public void use_default_path_or_env_variable() throws Exception { + FileCache cache = new FileCacheBuilder().build(); + + assertThat(cache.getDir()).isDirectory().exists(); + assertThat(cache.getDir().getName()).isEqualTo("cache"); + } +} diff --git a/sonar-home/src/test/java/org/sonar/home/cache/FileCacheTest.java b/sonar-home/src/test/java/org/sonar/home/cache/FileCacheTest.java new file mode 100644 index 00000000000..86382f67307 --- /dev/null +++ b/sonar-home/src/test/java/org/sonar/home/cache/FileCacheTest.java @@ -0,0 +1,121 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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 3 of the License, or (at your option) any later version. + * + * Sonar 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.home.cache; + +import org.apache.commons.io.FileUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.home.log.Slf4jLog; + +import java.io.File; +import java.io.IOException; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class FileCacheTest { + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Slf4jLog log = new Slf4jLog(FileCacheTest.class); + + @Test + public void not_in_cache() throws IOException { + FileCache cache = FileCache.create(tempFolder.newFolder(), log); + assertThat(cache.get("sonar-foo-plugin-1.5.jar", "ABCDE")).isNull(); + } + + @Test + public void found_in_cache() throws IOException { + FileCache cache = FileCache.create(tempFolder.newFolder(), log); + + // populate the cache. Assume that hash is correct. + File cachedFile = new File(new File(cache.getDir(), "ABCDE"), "sonar-foo-plugin-1.5.jar"); + FileUtils.write(cachedFile, "body"); + + assertThat(cache.get("sonar-foo-plugin-1.5.jar", "ABCDE")).isNotNull().exists().isEqualTo(cachedFile); + } + + @Test + public void download_and_add_to_cache() throws IOException { + FileHashes hashes = mock(FileHashes.class); + FileCache cache = new FileCache(tempFolder.newFolder(), log, hashes); + when(hashes.of(any(File.class))).thenReturn("ABCDE"); + + FileCache.Downloader downloader = new FileCache.Downloader() { + public void download(String filename, File toFile) throws IOException { + FileUtils.write(toFile, "body"); + } + }; + File cachedFile = cache.get("sonar-foo-plugin-1.5.jar", "ABCDE", downloader); + assertThat(cachedFile).isNotNull().exists().isFile(); + assertThat(cachedFile.getName()).isEqualTo("sonar-foo-plugin-1.5.jar"); + assertThat(cachedFile.getParentFile().getParentFile()).isEqualTo(cache.getDir()); + assertThat(FileUtils.readFileToString(cachedFile)).isEqualTo("body"); + } + + @Test + public void download_corrupted_file() throws IOException { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("INVALID HASH"); + + FileHashes hashes = mock(FileHashes.class); + FileCache cache = new FileCache(tempFolder.newFolder(), log, hashes); + when(hashes.of(any(File.class))).thenReturn("VWXYZ"); + + FileCache.Downloader downloader = new FileCache.Downloader() { + public void download(String filename, File toFile) throws IOException { + FileUtils.write(toFile, "corrupted body"); + } + }; + cache.get("sonar-foo-plugin-1.5.jar", "ABCDE", downloader); + } + + @Test + public void concurrent_download() throws IOException { + FileHashes hashes = mock(FileHashes.class); + when(hashes.of(any(File.class))).thenReturn("ABCDE"); + final FileCache cache = new FileCache(tempFolder.newFolder(), log, hashes); + + FileCache.Downloader downloader = new FileCache.Downloader() { + public void download(String filename, File toFile) throws IOException { + // Emulate a concurrent download that adds file to cache before + File cachedFile = new File(new File(cache.getDir(), "ABCDE"), "sonar-foo-plugin-1.5.jar"); + FileUtils.write(cachedFile, "downloaded by other"); + + FileUtils.write(toFile, "downloaded by me"); + } + }; + + // do not fail + File cachedFile = cache.get("sonar-foo-plugin-1.5.jar", "ABCDE", downloader); + assertThat(cachedFile).isNotNull().exists().isFile(); + assertThat(cachedFile.getName()).isEqualTo("sonar-foo-plugin-1.5.jar"); + assertThat(cachedFile.getParentFile().getParentFile()).isEqualTo(cache.getDir()); + assertThat(FileUtils.readFileToString(cachedFile)).contains("downloaded by"); + } +} diff --git a/sonar-home/src/test/java/org/sonar/home/cache/FileHashesTest.java b/sonar-home/src/test/java/org/sonar/home/cache/FileHashesTest.java new file mode 100644 index 00000000000..08f7227f49c --- /dev/null +++ b/sonar-home/src/test/java/org/sonar/home/cache/FileHashesTest.java @@ -0,0 +1,103 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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 3 of the License, or (at your option) any later version. + * + * Sonar 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.home.cache; + +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.IOUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.math.BigInteger; +import java.security.SecureRandom; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class FileHashesTest { + + SecureRandom secureRandom = new SecureRandom(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void test_md5_hash() { + assertThat(hash("sonar")).isEqualTo("D85E336D61F5344395C42126FAC239BC"); + + // compare results with commons-codec + for (int index = 0; index < 100; index++) { + String random = randomString(); + assertThat(hash(random)).as(random).isEqualTo( + DigestUtils.md5Hex(random).toUpperCase() + ); + } + } + + @Test + public void test_toHex() { + // upper-case + assertThat(FileHashes.toHex("aloa_bi_bop_a_loula".getBytes())).isEqualTo("616C6F615F62695F626F705F615F6C6F756C61"); + + // compare results with commons-codec + for (int index = 0; index < 100; index++) { + String random = randomString(); + assertThat(FileHashes.toHex(random.getBytes())).as(random).isEqualTo( + Hex.encodeHexString(random.getBytes()).toUpperCase() + ); + } + } + + @Test + public void fail_if_file_does_not_exist() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Fail to compute hash of: /does/not/exist"); + + new FileHashes().of(new File("/does/not/exist")); + } + + @Test + public void fail_if_stream_is_closed() throws Exception { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Fail to compute hash"); + + InputStream input = mock(InputStream.class); + when(input.read()).thenThrow(new IllegalThreadStateException()); + new FileHashes().of(input); + } + + private String randomString() { + return new BigInteger(130, secureRandom).toString(32); + } + + private String hash(String s) { + InputStream in = new ByteArrayInputStream(s.getBytes()); + try { + return new FileHashes().of(in); + } finally { + IOUtils.closeQuietly(in); + } + } +} |