aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pom.xml11
-rw-r--r--sonar-batch/pom.xml8
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchSonarCache.java58
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java2
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/bootstrap/FileCacheProvider.java38
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcDriverHolder.java53
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginDownloader.java50
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/cache/SonarCache.java209
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/bootstrap/FileCacheProviderTest.java47
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/bootstrap/JdbcDriverHolderTest.java44
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/bootstrap/PluginDownloaderTest.java82
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/cache/SonarCacheTest.java94
-rw-r--r--sonar-core/pom.xml4
-rw-r--r--sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java29
-rw-r--r--sonar-core/src/main/java/org/sonar/core/plugins/RemotePluginFile.java10
-rw-r--r--sonar-core/src/test/java/org/sonar/core/plugins/RemotePluginTest.java2
-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
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java6
-rw-r--r--sonar-server/pom.xml7
-rw-r--r--sonar-server/src/main/java/org/sonar/server/startup/GenerateBootstrapIndex.java17
-rw-r--r--sonar-server/src/main/java/org/sonar/server/startup/JdbcDriverDeployer.java21
-rw-r--r--sonar-server/src/test/java/org/sonar/server/startup/JdbcDriverDeployerTest.java9
33 files changed, 996 insertions, 601 deletions
diff --git a/pom.xml b/pom.xml
index 5386fca70ea..44946a65751 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,6 +23,7 @@
<module>sonar-deprecated</module>
<module>sonar-duplications</module>
<module>sonar-graph</module>
+ <module>sonar-home</module>
<module>sonar-java-api</module>
<module>sonar-markdown</module>
<module>sonar-maven-plugin</module>
@@ -558,6 +559,11 @@
</dependency>
<dependency>
<groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-home</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-graph</artifactId>
<version>${project.version}</version>
</dependency>
@@ -657,6 +663,11 @@
<version>10.0.1</version>
</dependency>
<dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <version>1.3.9</version>
+ </dependency>
+ <dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.0.1</version>
diff --git a/sonar-batch/pom.xml b/sonar-batch/pom.xml
index 8fcf5b43847..9e95782e6d3 100644
--- a/sonar-batch/pom.xml
+++ b/sonar-batch/pom.xml
@@ -1,5 +1,6 @@
<?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">
+<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>
@@ -9,7 +10,6 @@
<groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-batch</artifactId>
- <packaging>jar</packaging>
<name>Sonar :: Batch</name>
<dependencies>
@@ -23,6 +23,10 @@
</dependency>
<dependency>
<groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-home</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-java-api</artifactId>
</dependency>
<dependency>
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchSonarCache.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchSonarCache.java
deleted file mode 100644
index d656c21c447..00000000000
--- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchSonarCache.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.batch.bootstrap;
-
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.config.Settings;
-import org.sonar.api.task.TaskExtension;
-import org.sonar.batch.cache.SonarCache;
-
-import java.io.File;
-
-public class BatchSonarCache implements TaskExtension {
-
- private Settings settings;
- private SonarCache cache;
-
- public BatchSonarCache(Settings settings) {
- this.settings = settings;
- }
-
- public void start() {
- // Try to get Sonar user home from property
- String sonarUserHome = settings.getString(CoreProperties.SONAR_USER_HOME_PROPERTY);
- if (StringUtils.isBlank(sonarUserHome)) {
- // Try to get Sonar user home from environment variable
- sonarUserHome = settings.getString(CoreProperties.SONAR_USER_HOME);
- }
- if (StringUtils.isBlank(sonarUserHome)) {
- // Default
- sonarUserHome = System.getProperty("user.home") + File.separator + ".sonar";
- }
-
- SonarCache.Builder builder = SonarCache.create(new File(sonarUserHome));
- this.cache = builder.build();
- }
-
- public SonarCache getCache() {
- return cache;
- }
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java
index 39614208ea1..9e4d54b201f 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java
@@ -74,7 +74,7 @@ public class BootstrapModule extends Module {
container.addSingleton(HttpDownloader.class);
container.addSingleton(UriReader.class);
container.addSingleton(PluginDownloader.class);
- container.addSingleton(BatchSonarCache.class);
+ container.addPicoAdapter(new FileCacheProvider());
for (Object component : boostrapperComponents) {
if (component != null) {
container.addSingleton(component);
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/FileCacheProvider.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/FileCacheProvider.java
new file mode 100644
index 00000000000..610083d98cd
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/FileCacheProvider.java
@@ -0,0 +1,38 @@
+/*
+ * 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.batch.bootstrap;
+
+import org.picocontainer.injectors.ProviderAdapter;
+import org.sonar.api.config.Settings;
+import org.sonar.home.cache.FileCache;
+import org.sonar.home.cache.FileCacheBuilder;
+import org.sonar.home.log.Slf4jLog;
+
+public class FileCacheProvider extends ProviderAdapter {
+ private FileCache cache;
+
+ public FileCache provide(Settings settings) {
+ if (cache == null) {
+ String home = settings.getString("sonar.userHome");
+ cache = new FileCacheBuilder().setLog(new Slf4jLog(FileCache.class)).setUserHome(home).build();
+ }
+ return cache;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcDriverHolder.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcDriverHolder.java
index 06b36e2b9c6..4db53d7db31 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcDriverHolder.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcDriverHolder.java
@@ -25,7 +25,7 @@ import org.slf4j.LoggerFactory;
import org.sonar.api.CoreProperties;
import org.sonar.api.config.Settings;
import org.sonar.api.utils.SonarException;
-import org.sonar.batch.cache.SonarCache;
+import org.sonar.home.cache.FileCache;
import java.io.File;
import java.io.IOException;
@@ -44,43 +44,38 @@ public class JdbcDriverHolder {
private ServerClient serverClient;
private Settings settings;
- private BatchSonarCache batchCache;
+ private FileCache fileCache;
// initialized in start()
private JdbcDriverClassLoader classLoader = null;
- public JdbcDriverHolder(BatchSonarCache batchCache, Settings settings, ServerClient serverClient) {
+ public JdbcDriverHolder(FileCache fileCache, Settings settings, ServerClient serverClient) {
this.serverClient = serverClient;
this.settings = settings;
- this.batchCache = batchCache;
+ this.fileCache = fileCache;
}
public void start() {
if (!settings.getBoolean(CoreProperties.DRY_RUN)) {
try {
LOG.info("Install JDBC driver");
- String[] nameAndMd5 = downloadJdbcDriverIndex();
- String filename = nameAndMd5[0];
- String remoteMd5 = nameAndMd5[1];
- File driverInCache = getSonarCache().getFileFromCache(filename, remoteMd5);
- if (driverInCache == null) {
- File tmpDownloadFile = getSonarCache().getTemporaryFile();
- String url = "/deploy/" + filename;
- if (LOG.isDebugEnabled()) {
- LOG.debug("Downloading {} to {}", url, tmpDownloadFile.getAbsolutePath());
+ String[] nameAndHash = downloadJdbcDriverIndex();
+ String filename = nameAndHash[0];
+ String hash = nameAndHash[1];
+
+ File jdbcDriver = fileCache.get(filename, hash, new FileCache.Downloader() {
+ public void download(String filename, File toFile) throws IOException {
+ String url = "/deploy/" + filename;
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Download {} to {}", url, toFile.getAbsolutePath());
+ } else {
+ LOG.info("Download {}", filename);
+ }
+ serverClient.download(url, toFile);
}
- else {
- LOG.info("Downloading {}", filename);
- }
- serverClient.download(url, tmpDownloadFile);
- String md5 = getSonarCache().cacheFile(tmpDownloadFile, filename);
- driverInCache = getSonarCache().getFileFromCache(filename, md5);
- if (!md5.equals(remoteMd5)) {
- throw new SonarException("INVALID CHECKSUM: File " + driverInCache.getAbsolutePath() + " was expected to have checksum " + remoteMd5
- + " but was downloaded with checksum " + md5);
- }
- }
- classLoader = initClassloader(driverInCache);
+ });
+
+ classLoader = initClassloader(jdbcDriver);
} catch (SonarException e) {
throw e;
} catch (Exception e) {
@@ -89,10 +84,6 @@ public class JdbcDriverHolder {
}
}
- private SonarCache getSonarCache() {
- return batchCache.getCache();
- }
-
@VisibleForTesting
JdbcDriverClassLoader getClassLoader() {
return classLoader;
@@ -148,7 +139,7 @@ public class JdbcDriverHolder {
private String[] downloadJdbcDriverIndex() {
String url = "/deploy/jdbc-driver.txt";
try {
- LOG.debug("Downloading index of jdbc-driver");
+ LOG.debug("Download index of jdbc-driver");
String indexContent = serverClient.request(url);
return indexContent.split("\\|");
} catch (Exception e) {
@@ -159,7 +150,7 @@ public class JdbcDriverHolder {
static class JdbcDriverClassLoader extends URLClassLoader {
public JdbcDriverClassLoader(URL jdbcDriver, ClassLoader parent) {
- super(new URL[] {jdbcDriver}, parent);
+ super(new URL[]{jdbcDriver}, parent);
}
public void clearReferencesJdbc() {
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginDownloader.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginDownloader.java
index dadabefd1d8..f037413a278 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginDownloader.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginDownloader.java
@@ -26,11 +26,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.BatchComponent;
import org.sonar.api.utils.SonarException;
-import org.sonar.batch.cache.SonarCache;
import org.sonar.core.plugins.RemotePlugin;
import org.sonar.core.plugins.RemotePluginFile;
+import org.sonar.home.cache.FileCache;
import java.io.File;
+import java.io.IOException;
import java.util.List;
public class PluginDownloader implements BatchComponent {
@@ -38,45 +39,32 @@ public class PluginDownloader implements BatchComponent {
private static final Logger LOG = LoggerFactory.getLogger(PluginDownloader.class);
private ServerClient server;
- private BatchSonarCache batchCache;
+ private FileCache fileCache;
- public PluginDownloader(BatchSonarCache batchCache, ServerClient server) {
+ public PluginDownloader(FileCache fileCache, ServerClient server) {
this.server = server;
- this.batchCache = batchCache;
+ this.fileCache = fileCache;
}
- private SonarCache getSonarCache() {
- return batchCache.getCache();
- }
-
- public List<File> downloadPlugin(RemotePlugin remote) {
+ public List<File> downloadPlugin(final RemotePlugin remote) {
try {
List<File> files = Lists.newArrayList();
- for (RemotePluginFile file : remote.getFiles()) {
- File fileInCache = getSonarCache().getFileFromCache(file.getFilename(), file.getMd5());
- if (fileInCache == null) {
- File tmpDownloadFile = getSonarCache().getTemporaryFile();
- String url = "/deploy/plugins/" + remote.getKey() + "/" + file.getFilename();
- if (LOG.isDebugEnabled()) {
- LOG.debug("Downloading {} to {}", url, tmpDownloadFile.getAbsolutePath());
- }
- else {
- LOG.info("Downloading {}", file.getFilename());
- }
- server.download(url, tmpDownloadFile);
- String md5 = getSonarCache().cacheFile(tmpDownloadFile, file.getFilename());
- fileInCache = getSonarCache().getFileFromCache(file.getFilename(), md5);
- if (!md5.equals(file.getMd5())) {
- throw new SonarException("INVALID CHECKSUM: File " + fileInCache.getAbsolutePath() + " was expected to have checksum " + file.getMd5()
- + " but was downloaded with checksum " + md5);
+ for (final RemotePluginFile file : remote.getFiles()) {
+ File cachedFile = fileCache.get(file.getFilename(), file.getHash(), new FileCache.Downloader() {
+ public void download(String filename, File toFile) throws IOException {
+ String url = "/deploy/plugins/" + remote.getKey() + "/" + file.getFilename();
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Download {} to {}", url, toFile.getAbsolutePath());
+ } else {
+ LOG.info("Download {}", file.getFilename());
+ }
+ server.download(url, toFile);
}
- }
- files.add(fileInCache);
+ });
+ files.add(cachedFile);
}
return files;
- } catch (SonarException e) {
- throw e;
} catch (Exception e) {
throw new SonarException("Fail to download plugin: " + remote.getKey(), e);
}
@@ -85,7 +73,7 @@ public class PluginDownloader implements BatchComponent {
public List<RemotePlugin> downloadPluginIndex() {
String url = "/deploy/plugins/index.txt";
try {
- LOG.debug("Downloading index of plugins");
+ LOG.debug("Download index of plugins");
String indexContent = server.request(url);
String[] rows = StringUtils.split(indexContent, CharUtils.LF);
List<RemotePlugin> remoteLocations = Lists.newArrayList();
diff --git a/sonar-batch/src/main/java/org/sonar/batch/cache/SonarCache.java b/sonar-batch/src/main/java/org/sonar/batch/cache/SonarCache.java
deleted file mode 100644
index 77499426727..00000000000
--- a/sonar-batch/src/main/java/org/sonar/batch/cache/SonarCache.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * 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.batch.cache;
-
-import com.google.common.io.Closeables;
-import com.google.common.io.Files;
-import org.apache.commons.codec.digest.DigestUtils;
-import org.apache.commons.io.FileUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.utils.SonarException;
-
-import java.io.File;
-import java.io.FileInputStream;
-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).
- * Default location of cache is
- * @author Julien HENRY
- *
- */
-public class SonarCache {
-
- private static final Logger LOG = LoggerFactory.getLogger(SonarCache.class);
-
- private static final int TEMP_FILE_ATTEMPTS = 10000;
-
- private File cacheLocation;
- /**
- * Temporary directory where files should be stored before be inserted in the cache.
- * Having a temporary close to the final location (read on same FS) will assure
- * the move will be atomic.
- */
- private File tmpDir;
-
- private SonarCache(File cacheLocation) {
- this.cacheLocation = cacheLocation;
- tmpDir = new File(cacheLocation, "tmp");
- if (!cacheLocation.exists()) {
- LOG.debug("Creating cache directory: {}", cacheLocation.getAbsolutePath());
- try {
- FileUtils.forceMkdir(cacheLocation);
- } catch (IOException e) {
- throw new RuntimeException("Unable to create cache directory " + cacheLocation.getAbsolutePath(), e);
- }
- }
- }
-
- public static class Builder {
-
- private File sonarUserHomeLocation;
- private File cacheLocation;
-
- public Builder(File sonarUserHomeLocation) {
- this.sonarUserHomeLocation = sonarUserHomeLocation;
- }
-
- public Builder setCacheLocation(File cacheLocation) {
- this.cacheLocation = cacheLocation;
- return this;
- }
-
- public SonarCache build() {
- if (cacheLocation == null) {
- return new SonarCache(new File(sonarUserHomeLocation, "cache"));
- }
- else {
- return new SonarCache(cacheLocation);
- }
- }
-
- }
-
- public static Builder create(File sonarUserHomeLocation) {
- if (sonarUserHomeLocation == null) {
- throw new SonarException("Sonar user home directory should not be null");
- }
- return new Builder(sonarUserHomeLocation);
- }
-
- /**
- * Move the given file inside the cache. Return the MD5 of the cached file.
- * @param sourceFile
- * @throws IOException
- */
- public String cacheFile(File sourceFile, String filename) throws IOException {
- LOG.debug("Trying to cache file {} with filename {}", sourceFile.getAbsolutePath(), filename);
- File tmpFileName = null;
- try {
- if (!sourceFile.getParentFile().equals(getTmpDir())) {
- // Provided file is not close to the cache so we will move it first in a temporary file (could be non atomic)
- tmpFileName = getTemporaryFile();
- Files.move(sourceFile, tmpFileName);
- }
- else {
- tmpFileName = sourceFile;
- }
- // Now compute the md5 to find the final destination
- String md5;
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(tmpFileName);
- md5 = DigestUtils.md5Hex(fis);
- } finally {
- Closeables.closeQuietly(fis);
- }
- File finalDir = new File(cacheLocation, md5);
- File finalFileName = new File(finalDir, filename);
- // Try to create final destination folder
- FileUtils.forceMkdir(finalDir);
- // Now try to move the file from temporary folder to final location
- boolean rename = tmpFileName.renameTo(finalFileName);
- if (!rename) {
- // Check if the file was already in cache
- if (!finalFileName.exists()) {
- LOG.warn("Unable to rename {} to {}", tmpFileName.getAbsolutePath(), finalFileName.getAbsolutePath());
- LOG.warn("A copy/delete will be tempted but with no garantee of atomicity");
- FileUtils.moveFile(tmpFileName, finalFileName);
- }
- }
- LOG.debug("File cached at {}", finalFileName.getAbsolutePath());
- return md5;
- } finally {
- FileUtils.deleteQuietly(tmpFileName);
- }
-
- }
-
- /**
- * Look for a file in the cache by its filename and md5 checksum. If the file is not
- * present then return null.
- */
- public File getFileFromCache(String filename, String md5) {
- File location = new File(new File(cacheLocation, md5), filename);
- LOG.debug("Looking for {}", location.getAbsolutePath());
- if (location.exists()) {
- return location;
- }
- LOG.debug("No file found in the cache with name {} and checksum {}", filename, md5);
- return null;
- }
-
- /**
- * Return a temporary file that caller can use to store file content before
- * asking for caching it with {@link #cacheFile(File)}.
- * This is to avoid extra copy.
- * @return
- * @throws IOException
- */
- public File getTemporaryFile() throws IOException {
- return createTempFile(getTmpDir());
- }
-
- /**
- * Create a temporary file in the given directory.
- * @param baseDir
- * @return
- * @throws IOException
- */
- private static File createTempFile(File baseDir) throws IOException {
- String baseName = System.currentTimeMillis() + "-";
-
- for (int counter = 0; counter < TEMP_FILE_ATTEMPTS; counter++) {
- File tempFile = new File(baseDir, baseName + counter);
- if (tempFile.createNewFile()) {
- return tempFile;
- }
- }
- throw new IOException("Failed to create temporary file in " + baseDir.getAbsolutePath() + " within "
- + TEMP_FILE_ATTEMPTS + " attempts (tried "
- + baseName + "0 to " + baseName + (TEMP_FILE_ATTEMPTS - 1) + ')');
- }
-
- public File getTmpDir() {
- if (!tmpDir.exists()) {
- LOG.debug("Creating temporary cache directory: {}", tmpDir.getAbsolutePath());
- try {
- FileUtils.forceMkdir(tmpDir);
- } catch (IOException e) {
- throw new RuntimeException("Unable to create temporary cache directory " + tmpDir.getAbsolutePath(), e);
- }
- }
- return tmpDir;
- }
-
- public File getCacheLocation() {
- return cacheLocation;
- }
-}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/FileCacheProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/FileCacheProviderTest.java
new file mode 100644
index 00000000000..305e6c9c146
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/FileCacheProviderTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.batch.bootstrap;
+
+import org.junit.Test;
+import org.sonar.api.config.Settings;
+import org.sonar.home.cache.FileCache;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class FileCacheProviderTest {
+ @Test
+ public void provide() {
+ FileCacheProvider provider = new FileCacheProvider();
+ FileCache cache = provider.provide(new Settings());
+
+ assertThat(cache).isNotNull();
+ assertThat(cache.getDir()).isNotNull().exists();
+ }
+
+ @Test
+ public void keep_singleton_instance() {
+ FileCacheProvider provider = new FileCacheProvider();
+ Settings settings = new Settings();
+ FileCache cache1 = provider.provide(settings);
+ FileCache cache2 = provider.provide(settings);
+
+ assertThat(cache1).isSameAs(cache2);
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/JdbcDriverHolderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/JdbcDriverHolderTest.java
index 0047141fb2d..df091fab76b 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/JdbcDriverHolderTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/JdbcDriverHolderTest.java
@@ -25,15 +25,15 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
-import org.mockito.Mockito;
import org.sonar.api.CoreProperties;
import org.sonar.api.config.Settings;
-import org.sonar.api.utils.SonarException;
-import org.sonar.batch.cache.SonarCache;
+import org.sonar.home.cache.FileCache;
import java.io.File;
import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@@ -60,15 +60,10 @@ public class JdbcDriverHolderTest {
@Test
public void should_extend_classloader_with_jdbc_driver() throws Exception {
- SonarCache cache = mock(SonarCache.class);
- BatchSonarCache batchCache = mock(BatchSonarCache.class);
- when(batchCache.getCache()).thenReturn(cache);
+ FileCache cache = mock(FileCache.class);
File fakeDriver = new File(getClass().getResource("/org/sonar/batch/bootstrap/JdbcDriverHolderTest/jdbc-driver.jar").toURI());
- when(cache.cacheFile(Mockito.any(File.class), Mockito.anyString())).thenReturn("fakemd5");
- when(cache.getFileFromCache(Mockito.anyString(), Mockito.anyString()))
- .thenReturn(null)
- .thenReturn(fakeDriver);
+ when(cache.get(eq("ojdbc14.jar"), eq("fakemd5"), any(FileCache.Downloader.class))).thenReturn(fakeDriver);
/* jdbc-driver.jar has just one file /foo/foo.txt */
assertThat(Thread.currentThread().getContextClassLoader().getResource("foo/foo.txt")).isNull();
@@ -77,7 +72,7 @@ public class JdbcDriverHolderTest {
when(server.request("/deploy/jdbc-driver.txt")).thenReturn("ojdbc14.jar|fakemd5");
when(server.request("/deploy/ojdbc14.jar")).thenReturn("fakecontent");
- JdbcDriverHolder holder = new JdbcDriverHolder(batchCache, new Settings(), server);
+ JdbcDriverHolder holder = new JdbcDriverHolder(cache, new Settings(), server);
holder.start();
assertThat(holder.getClassLoader().getResource("foo/foo.txt")).isNotNull();
@@ -90,34 +85,11 @@ public class JdbcDriverHolderTest {
}
@Test
- public void should_fail_if_checksum_mismatch() throws Exception {
- SonarCache cache = mock(SonarCache.class);
- BatchSonarCache batchCache = mock(BatchSonarCache.class);
- when(batchCache.getCache()).thenReturn(cache);
-
- File fakeDriver = new File(getClass().getResource("/org/sonar/batch/bootstrap/JdbcDriverHolderTest/jdbc-driver.jar").toURI());
- when(cache.cacheFile(Mockito.any(File.class), Mockito.anyString())).thenReturn("anotherfakemd5");
- when(cache.getFileFromCache(Mockito.anyString(), Mockito.anyString()))
- .thenReturn(null)
- .thenReturn(fakeDriver);
-
- ServerClient server = mock(ServerClient.class);
- when(server.request("/deploy/jdbc-driver.txt")).thenReturn("ojdbc14.jar|fakemd5");
- when(server.request("/deploy/ojdbc14.jar")).thenReturn("fakecontent");
-
- JdbcDriverHolder holder = new JdbcDriverHolder(batchCache, new Settings(), server);
-
- thrown.expect(SonarException.class);
- thrown.expectMessage("INVALID CHECKSUM");
-
- holder.start();
- }
-
- @Test
public void should_be_disabled_if_dry_run() {
+ FileCache cache = mock(FileCache.class);
Settings settings = new Settings().setProperty(CoreProperties.DRY_RUN, true);
ServerClient server = mock(ServerClient.class);
- JdbcDriverHolder holder = new JdbcDriverHolder(new BatchSonarCache(new Settings()), settings, server);
+ JdbcDriverHolder holder = new JdbcDriverHolder(cache, settings, server);
holder.start();
diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/PluginDownloaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/PluginDownloaderTest.java
index 86fca9177d4..d1b21b0fde9 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/PluginDownloaderTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/PluginDownloaderTest.java
@@ -23,20 +23,18 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
-import org.mockito.Mockito;
-import org.sonar.api.config.Settings;
import org.sonar.api.utils.SonarException;
-import org.sonar.batch.cache.SonarCache;
import org.sonar.core.plugins.RemotePlugin;
+import org.sonar.home.cache.FileCache;
import java.io.File;
import java.util.List;
import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class PluginDownloaderTest {
@@ -49,9 +47,10 @@ public class PluginDownloaderTest {
@Test
public void should_request_list_of_plugins() {
+ FileCache cache = mock(FileCache.class);
ServerClient server = mock(ServerClient.class);
when(server.request("/deploy/plugins/index.txt")).thenReturn("checkstyle,true\nsqale,false");
- PluginDownloader downloader = new PluginDownloader(new BatchSonarCache(new Settings()), server);
+ PluginDownloader downloader = new PluginDownloader(cache, server);
List<RemotePlugin> plugins = downloader.downloadPluginIndex();
assertThat(plugins).hasSize(2);
@@ -62,52 +61,23 @@ public class PluginDownloaderTest {
}
@Test
- public void should_download_plugin_if_not_cached() throws Exception {
- SonarCache cache = mock(SonarCache.class);
- BatchSonarCache batchCache = mock(BatchSonarCache.class);
- when(batchCache.getCache()).thenReturn(cache);
-
- File fileInCache = temp.newFile();
- when(cache.cacheFile(Mockito.any(File.class), Mockito.anyString())).thenReturn("fakemd51").thenReturn("fakemd52");
- when(cache.getFileFromCache(Mockito.anyString(), Mockito.anyString()))
- .thenReturn(null)
- .thenReturn(fileInCache)
- .thenReturn(null)
- .thenReturn(fileInCache);
- ServerClient server = mock(ServerClient.class);
- PluginDownloader downloader = new PluginDownloader(batchCache, server);
-
- RemotePlugin plugin = new RemotePlugin("checkstyle", true)
- .addFile("checkstyle-plugin.jar", "fakemd51")
- .addFile("checkstyle-extensions.jar", "fakemd52");
- List<File> files = downloader.downloadPlugin(plugin);
+ public void should_download_plugin() throws Exception {
+ FileCache cache = mock(FileCache.class);
- assertThat(files).hasSize(2);
- verify(server).download(Mockito.eq("/deploy/plugins/checkstyle/checkstyle-plugin.jar"), Mockito.any(File.class));
- verify(server).download(Mockito.eq("/deploy/plugins/checkstyle/checkstyle-extensions.jar"), Mockito.any(File.class));
- }
+ File pluginJar = temp.newFile();
+ File extensionJar = temp.newFile();
+ when(cache.get(eq("checkstyle-plugin.jar"), eq("fakemd5_1"), any(FileCache.Downloader.class))).thenReturn(pluginJar);
+ when(cache.get(eq("checkstyle-extensions.jar"), eq("fakemd5_2"), any(FileCache.Downloader.class))).thenReturn(extensionJar);
- @Test
- public void should_not_download_plugin_if_cached() throws Exception {
- SonarCache cache = mock(SonarCache.class);
- BatchSonarCache batchCache = mock(BatchSonarCache.class);
- when(batchCache.getCache()).thenReturn(cache);
-
- File fileInCache = temp.newFile();
- when(cache.getFileFromCache(Mockito.anyString(), Mockito.anyString()))
- .thenReturn(fileInCache)
- .thenReturn(fileInCache);
ServerClient server = mock(ServerClient.class);
- PluginDownloader downloader = new PluginDownloader(batchCache, server);
+ PluginDownloader downloader = new PluginDownloader(cache, server);
RemotePlugin plugin = new RemotePlugin("checkstyle", true)
- .addFile("checkstyle-plugin.jar", "fakemd51")
- .addFile("checkstyle-extensions.jar", "fakemd52");
+ .addFile("checkstyle-plugin.jar", "fakemd5_1")
+ .addFile("checkstyle-extensions.jar", "fakemd5_2");
List<File> files = downloader.downloadPlugin(plugin);
assertThat(files).hasSize(2);
- verify(server, never()).download(Mockito.anyString(), Mockito.any(File.class));
- verify(cache, never()).cacheFile(Mockito.any(File.class), Mockito.anyString());
}
@Test
@@ -117,28 +87,6 @@ public class PluginDownloaderTest {
ServerClient server = mock(ServerClient.class);
doThrow(new SonarException()).when(server).request("/deploy/plugins/index.txt");
- new PluginDownloader(new BatchSonarCache(new Settings()), server).downloadPluginIndex();
- }
-
- @Test
- public void should_fail_if_invalid_checksum() throws Exception {
- thrown.expect(SonarException.class);
- thrown.expectMessage("INVALID CHECKSUM");
-
- SonarCache cache = mock(SonarCache.class);
- BatchSonarCache batchCache = mock(BatchSonarCache.class);
- when(batchCache.getCache()).thenReturn(cache);
-
- File fileInCache = temp.newFile();
- when(cache.cacheFile(Mockito.any(File.class), Mockito.anyString())).thenReturn("fakemd51diff");
- when(cache.getFileFromCache(Mockito.anyString(), Mockito.anyString()))
- .thenReturn(null)
- .thenReturn(fileInCache);
- ServerClient server = mock(ServerClient.class);
- PluginDownloader downloader = new PluginDownloader(batchCache, server);
-
- RemotePlugin plugin = new RemotePlugin("checkstyle", true)
- .addFile("checkstyle-plugin.jar", "fakemd51");
- downloader.downloadPlugin(plugin);
+ new PluginDownloader(mock(FileCache.class), server).downloadPluginIndex();
}
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/cache/SonarCacheTest.java b/sonar-batch/src/test/java/org/sonar/batch/cache/SonarCacheTest.java
deleted file mode 100644
index dce64ba75bb..00000000000
--- a/sonar-batch/src/test/java/org/sonar/batch/cache/SonarCacheTest.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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.batch.cache;
-
-import org.apache.commons.io.FileUtils;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-import java.io.File;
-import java.io.IOException;
-
-import static org.fest.assertions.Assertions.assertThat;
-
-public class SonarCacheTest {
-
- @Rule
- public TemporaryFolder tempFolder = new TemporaryFolder();
-
- private SonarCache cache;
-
- @Before
- public void prepare() throws IOException {
- cache = SonarCache.create(tempFolder.newFolder()).build();
- }
-
- @Test
- public void testCacheExternalFile() throws IOException {
- // Create a file outside the cache
- File fileToCache = tempFolder.newFile();
- FileUtils.write(fileToCache, "Sample data");
- // Put it in the cache
- String md5 = cache.cacheFile(fileToCache, "foo.txt");
- // Verify the temporary location was created to do the copy in the cache in 2 stages
- File tmpCache = new File(cache.getCacheLocation(), "tmp");
- assertThat(tmpCache).exists();
- // The tmp location should be empty as the file was moved inside the cache
- assertThat(tmpCache.list()).isEmpty();
- // Verify it is present in the cache folder
- File fileInCache = new File(new File(cache.getCacheLocation(), md5), "foo.txt");
- assertThat(fileInCache).exists();
- String content = FileUtils.readFileToString(fileInCache);
- assertThat(content).isEqualTo("Sample data");
- // Now retrieve from cache API
- File fileFromCache = cache.getFileFromCache("foo.txt", md5);
- assertThat(fileFromCache.getCanonicalPath()).isEqualTo(fileInCache.getCanonicalPath());
- }
-
- @Test
- public void testCacheInternalFile() throws IOException {
- // Create a file in the cache temp location
- File fileToCache = cache.getTemporaryFile();
- // Verify the temporary location was created
- File tmpCache = new File(cache.getCacheLocation(), "tmp");
- assertThat(tmpCache).exists();
- assertThat(tmpCache.list().length).isEqualTo(1);
-
- FileUtils.write(fileToCache, "Sample data");
- String md5 = cache.cacheFile(fileToCache, "foo.txt");
- // Verify it is present in the cache folder
- File fileInCache = new File(new File(cache.getCacheLocation(), md5), "foo.txt");
- assertThat(fileInCache).exists();
- String content = FileUtils.readFileToString(fileInCache);
- assertThat(content).isEqualTo("Sample data");
- // Now retrieve from cache API
- File fileFromCache = cache.getFileFromCache("foo.txt", md5);
- assertThat(fileFromCache.getCanonicalPath()).isEqualTo(fileInCache.getCanonicalPath());
- }
-
- @Test
- public void testGetFileNotInCache() throws IOException {
- File fileFromCache = cache.getFileFromCache("foo.txt", "mockmd5");
- assertThat(fileFromCache).isNull();
- }
-
-}
diff --git a/sonar-core/pom.xml b/sonar-core/pom.xml
index 1ec0ff42482..249bf40f460 100644
--- a/sonar-core/pom.xml
+++ b/sonar-core/pom.xml
@@ -34,6 +34,10 @@
<artifactId>sonar-update-center-common</artifactId>
</dependency>
<dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-home</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java b/sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java
index 75be9c70a8a..dc53d64f0ea 100644
--- a/sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java
+++ b/sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java
@@ -20,12 +20,10 @@
package org.sonar.core.plugins;
import com.google.common.collect.Lists;
-import com.google.common.io.Closeables;
-import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
+import org.sonar.home.cache.FileHashes;
import java.io.File;
-import java.io.FileInputStream;
import java.util.List;
public class RemotePlugin {
@@ -52,8 +50,8 @@ public class RemotePlugin {
RemotePlugin result = new RemotePlugin(fields[0], Boolean.parseBoolean(fields[1]));
if (fields.length > 2) {
for (int index = 2; index < fields.length; index++) {
- String[] nameAndMd5 = StringUtils.split(fields[index], "|");
- result.addFile(nameAndMd5[0], nameAndMd5.length > 1 ? nameAndMd5[1] : null);
+ String[] nameAndHash = StringUtils.split(fields[index], "|");
+ result.addFile(nameAndHash[0], nameAndHash.length > 1 ? nameAndHash[1] : null);
}
}
return result;
@@ -64,10 +62,7 @@ public class RemotePlugin {
sb.append(pluginKey).append(",");
sb.append(String.valueOf(core));
for (RemotePluginFile file : files) {
- sb.append(",").append(file.getFilename());
- if (StringUtils.isNotBlank(file.getMd5())) {
- sb.append("|").append(file.getMd5());
- }
+ sb.append(",").append(file.getFilename()).append("|").append(file.getHash());
}
return sb.toString();
}
@@ -80,23 +75,13 @@ public class RemotePlugin {
return core;
}
- public RemotePlugin addFile(String filename, String md5) {
- files.add(new RemotePluginFile(filename, md5));
+ public RemotePlugin addFile(String filename, String hash) {
+ files.add(new RemotePluginFile(filename, hash));
return this;
}
public RemotePlugin addFile(File f) {
- String md5;
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(f);
- md5 = DigestUtils.md5Hex(fis);
- } catch (Exception e) {
- md5 = null;
- } finally {
- Closeables.closeQuietly(fis);
- }
- return this.addFile(f.getName(), md5);
+ return this.addFile(f.getName(), f.exists() ? new FileHashes().of(f) : null);
}
public List<RemotePluginFile> getFiles() {
diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/RemotePluginFile.java b/sonar-core/src/main/java/org/sonar/core/plugins/RemotePluginFile.java
index 88d85c3485b..87c9495cacb 100644
--- a/sonar-core/src/main/java/org/sonar/core/plugins/RemotePluginFile.java
+++ b/sonar-core/src/main/java/org/sonar/core/plugins/RemotePluginFile.java
@@ -23,18 +23,18 @@ package org.sonar.core.plugins;
public class RemotePluginFile {
private String filename;
- private String md5;
+ private String hash;
- public RemotePluginFile(String filename, String md5) {
+ public RemotePluginFile(String filename, String hash) {
this.filename = filename;
- this.md5 = md5;
+ this.hash = hash;
}
public String getFilename() {
return filename;
}
- public String getMd5() {
- return md5;
+ public String getHash() {
+ return hash;
}
}
diff --git a/sonar-core/src/test/java/org/sonar/core/plugins/RemotePluginTest.java b/sonar-core/src/test/java/org/sonar/core/plugins/RemotePluginTest.java
index b62dc1d3ad4..d6b44ab6f75 100644
--- a/sonar-core/src/test/java/org/sonar/core/plugins/RemotePluginTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/plugins/RemotePluginTest.java
@@ -60,7 +60,7 @@ public class RemotePluginTest {
assertThat(clirr.isCore(), is(false));
assertThat(clirr.getFiles().size(), is(1));
assertThat(clirr.getFiles().get(0).getFilename(), is("clirr-1.1.jar"));
- assertThat(clirr.getFiles().get(0).getMd5(), is("fakemd5"));
+ assertThat(clirr.getFiles().get(0).getHash(), is("fakemd5"));
}
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);
+ }
+ }
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
index 57c69dd389a..3186a6ca6d7 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
@@ -377,12 +377,6 @@ public interface CoreProperties {
String TASK = "sonar.task";
/**
- * @since 3.5
- */
- String SONAR_USER_HOME = "SONAR_USER_HOME";
- String SONAR_USER_HOME_PROPERTY = "sonar.userHome";
-
- /**
* @deprecated replaced in v3.4 by properties specific to languages, for example sonar.java.coveragePlugin
* See http://jira.codehaus.org/browse/SONARJAVA-39 for more details.
*/
diff --git a/sonar-server/pom.xml b/sonar-server/pom.xml
index 4417542fa20..da24a99a4ea 100644
--- a/sonar-server/pom.xml
+++ b/sonar-server/pom.xml
@@ -1,5 +1,6 @@
<?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">
+<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>
@@ -39,6 +40,10 @@
</dependency>
<dependency>
<groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-home</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-java-api</artifactId>
</dependency>
<dependency>
diff --git a/sonar-server/src/main/java/org/sonar/server/startup/GenerateBootstrapIndex.java b/sonar-server/src/main/java/org/sonar/server/startup/GenerateBootstrapIndex.java
index e449ad76500..1c04c970baf 100644
--- a/sonar-server/src/main/java/org/sonar/server/startup/GenerateBootstrapIndex.java
+++ b/sonar-server/src/main/java/org/sonar/server/startup/GenerateBootstrapIndex.java
@@ -20,14 +20,11 @@
package org.sonar.server.startup;
import com.google.common.collect.Lists;
-import com.google.common.io.Closeables;
-import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.CharUtils;
import org.apache.commons.lang.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.sonar.home.cache.FileHashes;
import org.sonar.server.platform.DefaultServerFileSystem;
import javax.servlet.ServletContext;
@@ -44,8 +41,6 @@ import java.util.Set;
*/
public final class GenerateBootstrapIndex {
- private static final Logger LOG = LoggerFactory.getLogger(GenerateBootstrapIndex.class);
-
private final ServletContext servletContext;
private final DefaultServerFileSystem fileSystem;
@@ -64,16 +59,8 @@ public final class GenerateBootstrapIndex {
try {
for (String path : getLibs(servletContext)) {
writer.append(path);
- // Compute MD5
InputStream is = servletContext.getResourceAsStream("/WEB-INF/lib/" + path);
- try {
- String md5 = DigestUtils.md5Hex(is);
- writer.append("|").append(md5);
- } catch (IOException e) {
- LOG.warn("Unable to compute checksum of {}", path, e);
- } finally {
- Closeables.closeQuietly(is);
- }
+ writer.append("|").append(new FileHashes().of(is));
writer.append(CharUtils.LF);
}
writer.flush();
diff --git a/sonar-server/src/main/java/org/sonar/server/startup/JdbcDriverDeployer.java b/sonar-server/src/main/java/org/sonar/server/startup/JdbcDriverDeployer.java
index 1e1a8a8b52b..4bf99bcff4e 100644
--- a/sonar-server/src/main/java/org/sonar/server/startup/JdbcDriverDeployer.java
+++ b/sonar-server/src/main/java/org/sonar/server/startup/JdbcDriverDeployer.java
@@ -19,21 +19,15 @@
*/
package org.sonar.server.startup;
-import com.google.common.io.Closeables;
-import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.sonar.home.cache.FileHashes;
import org.sonar.server.platform.DefaultServerFileSystem;
import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
public class JdbcDriverDeployer {
- private static final Logger LOG = LoggerFactory.getLogger(JdbcDriverDeployer.class);
-
private final DefaultServerFileSystem fileSystem;
public JdbcDriverDeployer(DefaultServerFileSystem fileSystem) {
@@ -43,7 +37,7 @@ public class JdbcDriverDeployer {
public void start() {
File driver = fileSystem.getJdbcDriver();
File deployedDriver = new File(fileSystem.getDeployDir(), driver.getName());
- if (deployedDriver == null || !deployedDriver.exists() || deployedDriver.length() != driver.length()) {
+ if (!deployedDriver.exists() || deployedDriver.length() != driver.length()) {
try {
FileUtils.copyFile(driver, deployedDriver);
@@ -52,16 +46,11 @@ public class JdbcDriverDeployer {
}
}
File deployedDriverIndex = fileSystem.getDeployedJdbcDriverIndex();
- // Compute MD5
- InputStream is = null;
try {
- is = FileUtils.openInputStream(deployedDriver);
- String md5 = DigestUtils.md5Hex(is);
- FileUtils.writeStringToFile(deployedDriverIndex, deployedDriver.getName() + "|" + md5);
+ String hash = new FileHashes().of(deployedDriver);
+ FileUtils.writeStringToFile(deployedDriverIndex, deployedDriver.getName() + "|" + hash);
} catch (IOException e) {
- throw new RuntimeException("Can not generate index of JDBC driver", e);
- } finally {
- Closeables.closeQuietly(is);
+ throw new IllegalStateException("Can not generate index of JDBC driver", e);
}
}
}
diff --git a/sonar-server/src/test/java/org/sonar/server/startup/JdbcDriverDeployerTest.java b/sonar-server/src/test/java/org/sonar/server/startup/JdbcDriverDeployerTest.java
index 4cac7112870..eeb5a76ef2e 100644
--- a/sonar-server/src/test/java/org/sonar/server/startup/JdbcDriverDeployerTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/startup/JdbcDriverDeployerTest.java
@@ -25,18 +25,15 @@ import org.sonar.server.platform.DefaultServerFileSystem;
import org.sonar.test.TestUtils;
import java.io.File;
-import java.io.IOException;
import static org.fest.assertions.Assertions.assertThat;
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class JdbcDriverDeployerTest {
@Test
- public void testDeploy() throws IOException {
+ public void test_deploy() throws Exception {
DefaultServerFileSystem fs = mock(DefaultServerFileSystem.class);
File initialDriver = TestUtils.getResource(getClass(), "deploy/my-driver.jar");
when(fs.getJdbcDriver()).thenReturn(initialDriver);
@@ -54,7 +51,7 @@ public class JdbcDriverDeployerTest {
assertThat(deployedIndex).exists();
assertThat(deployedFile).exists();
- assertThat(deployedFile.length(), is(initialDriver.length()));
- assertThat(FileUtils.readFileToString(deployedIndex)).isEqualTo("my-driver.jar|02b97f7bc37b2b68fc847fcc3fc1c156");
+ assertThat(deployedFile).hasSize(initialDriver.length());
+ assertThat(FileUtils.readFileToString(deployedIndex)).isEqualTo("my-driver.jar|02B97F7BC37B2B68FC847FCC3FC1C156");
}
}