aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-home
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@gmail.com>2013-02-05 12:43:45 +0100
committerSimon Brandhof <simon.brandhof@gmail.com>2013-02-05 12:43:45 +0100
commit7bf52372a4f63be30ae6513bae70d2f8b775e29b (patch)
tree459e8af8de426d1b4c320270b367962176390fc4 /sonar-home
parent5c6c22c2820f420428cb308a22c1a5262d0295e9 (diff)
downloadsonarqube-7bf52372a4f63be30ae6513bae70d2f8b775e29b.tar.gz
sonarqube-7bf52372a4f63be30ae6513bae70d2f8b775e29b.zip
SONAR-2291 move management of file cache to the new module sonar-home
Diffstat (limited to 'sonar-home')
-rw-r--r--sonar-home/pom.xml62
-rw-r--r--sonar-home/src/main/java/org/sonar/home/cache/FileCache.java143
-rw-r--r--sonar-home/src/main/java/org/sonar/home/cache/FileCacheBuilder.java61
-rw-r--r--sonar-home/src/main/java/org/sonar/home/cache/FileHashes.java73
-rw-r--r--sonar-home/src/main/java/org/sonar/home/cache/package-info.java25
-rw-r--r--sonar-home/src/main/java/org/sonar/home/log/Log.java31
-rw-r--r--sonar-home/src/main/java/org/sonar/home/log/Slf4jLog.java58
-rw-r--r--sonar-home/src/main/java/org/sonar/home/log/StandardLog.java34
-rw-r--r--sonar-home/src/main/java/org/sonar/home/log/package-info.java25
-rw-r--r--sonar-home/src/test/java/org/sonar/home/cache/FileCacheBuilderTest.java60
-rw-r--r--sonar-home/src/test/java/org/sonar/home/cache/FileCacheTest.java121
-rw-r--r--sonar-home/src/test/java/org/sonar/home/cache/FileHashesTest.java103
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);
+ }
+ }
+}