diff options
33 files changed, 996 insertions, 601 deletions
@@ -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"); } } |