From: Simon Brandhof Date: Thu, 10 May 2018 19:54:03 +0000 (+0200) Subject: SONAR-10591 drop module sonar-home X-Git-Tag: 7.5~1206 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=a6f8921a959e5bc0a51d33b93eecd7c2667f1148;p=sonarqube.git SONAR-10591 drop module sonar-home --- diff --git a/settings.gradle b/settings.gradle index bc05b4e8080..98b327c9111 100644 --- a/settings.gradle +++ b/settings.gradle @@ -19,7 +19,6 @@ include 'sonar-application' include 'sonar-check-api' include 'sonar-core' include 'sonar-duplications' -include 'sonar-home' include 'sonar-markdown' include 'sonar-plugin-api' include 'sonar-plugin-api-deps' diff --git a/sonar-home/src/main/java/org/sonar/home/cache/DirectoryLock.java b/sonar-home/src/main/java/org/sonar/home/cache/DirectoryLock.java deleted file mode 100644 index db61bac9bcd..00000000000 --- a/sonar-home/src/main/java/org/sonar/home/cache/DirectoryLock.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.home.cache; - -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.nio.file.Path; - -public class DirectoryLock { - public static final String LOCK_FILE_NAME = ".sonar_lock"; - private final Path lockFilePath; - private final Logger logger; - - private RandomAccessFile lockRandomAccessFile; - private FileChannel lockChannel; - private FileLock lockFile; - - public DirectoryLock(Path directory, Logger logger) { - this.logger = logger; - this.lockFilePath = directory.resolve(LOCK_FILE_NAME).toAbsolutePath(); - } - - public boolean tryLock() { - try { - lockRandomAccessFile = new RandomAccessFile(lockFilePath.toFile(), "rw"); - lockChannel = lockRandomAccessFile.getChannel(); - lockFile = lockChannel.tryLock(0, 1024, false); - - return lockFile != null; - } catch (IOException e) { - throw new IllegalStateException("Failed to create lock in " + lockFilePath.toString(), e); - } - } - - public void unlock() { - if (lockFile != null) { - try { - lockFile.release(); - lockFile = null; - } catch (IOException e) { - logger.error("Error releasing lock", e); - } - } - if (lockChannel != null) { - try { - lockChannel.close(); - lockChannel = null; - } catch (IOException e) { - logger.error("Error closing file channel", e); - } - } - if (lockRandomAccessFile != null) { - try { - lockRandomAccessFile.close(); - lockRandomAccessFile = null; - } catch (IOException e) { - logger.error("Error closing file", e); - } - } - } -} 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 deleted file mode 100644 index bc42ed0335c..00000000000 --- a/sonar-home/src/main/java/org/sonar/home/cache/FileCache.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.home.cache; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.jar.JarOutputStream; -import java.util.jar.Pack200; -import java.util.zip.GZIPInputStream; - -/** - * 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 { - - /** Maximum loop count when creating temp directories. */ - private static final int TEMP_DIR_ATTEMPTS = 10_000; - - private final File cacheDir; - private final File tmpDir; - private final FileHashes hashes; - private final Logger logger; - - FileCache(File dir, FileHashes fileHashes, Logger logger) { - this.hashes = fileHashes; - this.logger = logger; - this.cacheDir = createDir(dir, "user cache: "); - logger.info(String.format("User cache: %s", dir.getAbsolutePath())); - this.tmpDir = createDir(new File(dir, "_tmp"), "temp dir"); - } - - public static FileCache create(File dir, Logger logger) { - return new FileCache(dir, new FileHashes(), logger); - } - - public File getDir() { - return cacheDir; - } - - public interface Downloader { - void download(String filename, File toFile) throws IOException; - } - - /** - * Look for a file in the cache by its filename and md5 checksum. If the file is not - * present then try to download it. In case of error or if the file is not found - * an exception is thrown. {@code null} is never returned. - */ - 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()) { - cacheMiss(targetFile, hash, downloader); - } - return targetFile; - } - - private void cacheMiss(File targetFile, String expectedHash, Downloader downloader) { - File tempFile = newTempFile(); - download(downloader, targetFile.getName(), tempFile); - String downloadedHash = hashes.of(tempFile); - if (!expectedHash.equals(downloadedHash)) { - throw new IllegalStateException("INVALID HASH: File " + tempFile.getAbsolutePath() + " was expected to have hash " + expectedHash - + " but was downloaded with hash " + downloadedHash); - } - mkdirQuietly(targetFile.getParentFile()); - renameQuietly(tempFile, targetFile); - } - - public File getCompressed(String filename, String hash, Downloader downloader) { - File hashDir = hashDir(hash); - File compressedFile = new File(hashDir, filename); - File jarFile = new File(compressedFile.getParentFile(), getUnpackedFileName(compressedFile.getName())); - - if (!jarFile.exists()) { - if (!compressedFile.exists()) { - cacheMiss(compressedFile, hash, downloader); - } - File tempFile = newTempFile(); - unpack200(compressedFile.toPath(), tempFile.toPath()); - renameQuietly(tempFile, jarFile); - } - return jarFile; - } - - private static String getUnpackedFileName(String packedName) { - return packedName.substring(0, packedName.length() - 7) + "jar"; - } - - private void unpack200(Path compressedFile, Path jarFile) { - logger.debug("Unpacking plugin " + compressedFile); - Pack200.Unpacker unpacker = Pack200.newUnpacker(); - try { - try (JarOutputStream jarStream = new JarOutputStream(new BufferedOutputStream(Files.newOutputStream(jarFile))); - InputStream in = new GZIPInputStream(new BufferedInputStream(Files.newInputStream(compressedFile)))) { - unpacker.unpack(in, jarStream); - } - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - - private static 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 void renameQuietly(File sourceFile, File targetFile) { - boolean rename = sourceFile.renameTo(targetFile); - // Check if the file was cached by another process during download - if (!rename && !targetFile.exists()) { - logger.warn(String.format("Unable to rename %s to %s", sourceFile.getAbsolutePath(), targetFile.getAbsolutePath())); - logger.warn("A copy/delete will be tempted but with no guarantee of atomicity"); - try { - Files.move(sourceFile.toPath(), targetFile.toPath()); - } catch (IOException e) { - throw new IllegalStateException("Fail to move " + sourceFile.getAbsolutePath() + " to " + targetFile, e); - } - } - } - - private File hashDir(String hash) { - return new File(cacheDir, hash); - } - - private static void mkdirQuietly(File hashDir) { - try { - Files.createDirectories(hashDir.toPath()); - } catch (IOException e) { - throw new IllegalStateException("Fail to create cache directory: " + hashDir, e); - } - } - - private File newTempFile() { - try { - return File.createTempFile("fileCache", null, tmpDir); - } catch (IOException e) { - throw new IllegalStateException("Fail to create temp file in " + tmpDir, e); - } - } - - public File createTempDir() { - String baseName = System.currentTimeMillis() + "-"; - - for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) { - File tempDir = new File(tmpDir, baseName + counter); - if (tempDir.mkdir()) { - return tempDir; - } - } - throw new IllegalStateException("Failed to create directory in " + tmpDir); - } - - private File createDir(File dir, String debugTitle) { - if (!dir.isDirectory() || !dir.exists()) { - logger.debug("Create : " + dir.getAbsolutePath()); - try { - Files.createDirectories(dir.toPath()); - } catch (IOException e) { - throw new IllegalStateException("Unable to create " + debugTitle + dir.getAbsolutePath(), e); - } - } - return dir; - } -} 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 deleted file mode 100644 index 0bb78aada30..00000000000 --- a/sonar-home/src/main/java/org/sonar/home/cache/FileCacheBuilder.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.home.cache; - -import java.io.File; - -import javax.annotation.Nullable; - -public class FileCacheBuilder { - private final Logger logger; - private File userHome; - - public FileCacheBuilder(Logger logger) { - this.logger = logger; - } - - public FileCacheBuilder setUserHome(@Nullable File dir) { - this.userHome = dir; - return this; - } - - public FileCache build() { - if (userHome == null) { - userHome = findHome(); - } - File cacheDir = new File(userHome, "cache"); - return FileCache.create(cacheDir, logger); - } - - private static File findHome() { - String path = System.getenv("SONAR_USER_HOME"); - if (path == null) { - // Default - path = System.getProperty("user.home") + File.separator + ".sonar"; - } - return new File(path); - } -} 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 deleted file mode 100644 index a76f17c7340..00000000000 --- a/sonar-home/src/main/java/org/sonar/home/cache/FileHashes.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.home.cache; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.math.BigInteger; -import java.security.MessageDigest; - -/** - * Hashes used to store files in the cache directory. - * - * @since 3.5 - */ -public class FileHashes { - - private static final int STREAM_BUFFER_LENGTH = 1024; - - 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) { - try(InputStream is = input) { - MessageDigest digest = MessageDigest.getInstance("MD5"); - byte[] hash = digest(is, digest); - return toHex(hash); - } catch (Exception e) { - throw new IllegalStateException("Fail to compute hash", e); - } - } - - private static byte[] digest(InputStream input, MessageDigest digest) throws IOException { - final byte[] buffer = new byte[STREAM_BUFFER_LENGTH]; - int read = input.read(buffer, 0, STREAM_BUFFER_LENGTH); - while (read > -1) { - digest.update(buffer, 0, read); - read = input.read(buffer, 0, STREAM_BUFFER_LENGTH); - } - return digest.digest(); - } - - static String toHex(byte[] bytes) { - BigInteger bi = new BigInteger(1, bytes); - return String.format("%0" + (bytes.length << 1) + "x", bi); - } -} diff --git a/sonar-home/src/test/java/org/sonar/home/cache/DirectoryLockTest.java b/sonar-home/src/test/java/org/sonar/home/cache/DirectoryLockTest.java deleted file mode 100644 index 36815b3c2b1..00000000000 --- a/sonar-home/src/test/java/org/sonar/home/cache/DirectoryLockTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.home.cache; - -import java.nio.file.Paths; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.rules.TemporaryFolder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -public class DirectoryLockTest { - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - @Rule - public ExpectedException expectedException = ExpectedException.none(); - private DirectoryLock lock; - - @Before - public void setUp() { - lock = new DirectoryLock(temp.getRoot().toPath(), mock(Logger.class)); - } - - @Test - public void tryLock() { - assertThat(temp.getRoot().list()).isEmpty(); - lock.tryLock(); - assertThat(temp.getRoot().toPath().resolve(".sonar_lock")).exists(); - lock.unlock(); - } - - @Test - public void unlockWithoutLock() { - lock.unlock(); - } - - @Test - public void errorTryLock() { - lock = new DirectoryLock(Paths.get("non", "existing", "path"), mock(Logger.class)); - - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("Failed to create lock"); - - lock.tryLock(); - } -} 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 deleted file mode 100644 index 8e144c9ed14..00000000000 --- a/sonar-home/src/test/java/org/sonar/home/cache/FileCacheBuilderTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.home.cache; - -import java.io.File; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -public class FileCacheBuilderTest { - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - - @Test - public void setUserHome() throws Exception { - File userHome = temp.newFolder(); - FileCache cache = new FileCacheBuilder(mock(Logger.class)).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() { - FileCache cache = new FileCacheBuilder(mock(Logger.class)).setUserHome(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() { - FileCache cache = new FileCacheBuilder(mock(Logger.class)).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 deleted file mode 100644 index 63d4d85458f..00000000000 --- a/sonar-home/src/test/java/org/sonar/home/cache/FileCacheTest.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.home.cache; - -import java.io.File; -import java.io.IOException; -import org.apache.commons.io.FileUtils; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.rules.TemporaryFolder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -public class FileCacheTest { - - @Rule - public TemporaryFolder tempFolder = new TemporaryFolder(); - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - private FileHashes fileHashes = mock(FileHashes.class); - private FileCache cache; - - @Before - public void setUp() { - cache = new FileCache(tempFolder.getRoot(), fileHashes, mock(Logger.class)); - } - - @Test - public void fail_to_download() { - when(fileHashes.of(any(File.class))).thenReturn("ABCDE"); - - FileCache.Downloader downloader = (filename, toFile) -> { - throw new IOException("fail"); - }; - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("Fail to download"); - cache.get("sonar-foo-plugin-1.5.jar", "ABCDE", downloader); - } - - @Test - public void fail_create_hash_dir() throws IOException { - File file = tempFolder.newFile(); - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("Unable to create user cache"); - cache = new FileCache(file, fileHashes, mock(Logger.class)); - } - - @Test - public void fail_to_create_hash_dir() throws IOException { - when(fileHashes.of(any(File.class))).thenReturn("ABCDE"); - - File hashDir = new File(cache.getDir(), "ABCDE"); - hashDir.createNewFile(); - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("Fail to create cache directory"); - cache.get("sonar-foo-plugin-1.5.jar", "ABCDE", mock(FileCache.Downloader.class)); - } - - @Test - public void download_and_add_to_cache() throws IOException { - when(fileHashes.of(any(File.class))).thenReturn("ABCDE"); - - FileCache.Downloader downloader = (filename, toFile) -> 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_and_add_to_cache_compressed_file() { - when(fileHashes.of(any(File.class))).thenReturn("ABCDE"); - - FileCache.Downloader downloader = (filename, toFile) -> FileUtils.copyFile(compressedFile(), toFile); - File cachedFile = cache.getCompressed("sonar-foo-plugin-1.5.pack.gz", "ABCDE", downloader); - assertThat(cachedFile).isNotNull().exists().isFile(); - - assertThat(cachedFile.getName()).isEqualTo("sonar-foo-plugin-1.5.jar"); - assertThat(cachedFile.getParentFile().list()).containsOnly("sonar-foo-plugin-1.5.jar", "sonar-foo-plugin-1.5.pack.gz"); - assertThat(cachedFile.getParentFile().getParentFile()).isEqualTo(cache.getDir()); - } - - @Test - public void dont_download_compressed_file_if_jar_exists() throws IOException { - when(fileHashes.of(any(File.class))).thenReturn("ABCDE"); - FileCache.Downloader downloader = mock(FileCache.Downloader.class); - - File hashDir = new File(cache.getDir(), "ABCDE"); - hashDir.mkdirs(); - File jar = new File(new File(cache.getDir(), "ABCDE"), "sonar-foo-plugin-1.5.jar"); - jar.createNewFile(); - File cachedFile = cache.getCompressed("sonar-foo-plugin-1.5.pack.gz", "ABCDE", downloader); - assertThat(cachedFile).isNotNull().exists().isFile(); - - assertThat(cachedFile.getName()).isEqualTo("sonar-foo-plugin-1.5.jar"); - assertThat(cachedFile.getParentFile().list()).containsOnly("sonar-foo-plugin-1.5.jar"); - assertThat(cachedFile.getParentFile().getParentFile()).isEqualTo(cache.getDir()); - - verifyZeroInteractions(downloader); - } - - @Test - public void dont_download_compressed_file_if_it_exists() throws IOException { - when(fileHashes.of(any(File.class))).thenReturn("ABCDE"); - FileCache.Downloader downloader = mock(FileCache.Downloader.class); - - File hashDir = new File(cache.getDir(), "ABCDE"); - hashDir.mkdirs(); - FileUtils.copyFile(compressedFile(), new File(hashDir, "sonar-foo-plugin-1.5.pack.gz")); - File cachedFile = cache.getCompressed("sonar-foo-plugin-1.5.pack.gz", "ABCDE", downloader); - assertThat(cachedFile).isNotNull().exists().isFile(); - - assertThat(cachedFile.getName()).isEqualTo("sonar-foo-plugin-1.5.jar"); - assertThat(cachedFile.getParentFile().list()).containsOnly("sonar-foo-plugin-1.5.jar", "sonar-foo-plugin-1.5.pack.gz"); - assertThat(cachedFile.getParentFile().getParentFile()).isEqualTo(cache.getDir()); - - verifyZeroInteractions(downloader); - } - - @Test - public void download_corrupted_file() { - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("INVALID HASH"); - - when(fileHashes.of(any(File.class))).thenReturn("VWXYZ"); - - FileCache.Downloader downloader = (filename, toFile) -> FileUtils.write(toFile, "corrupted body"); - cache.get("sonar-foo-plugin-1.5.jar", "ABCDE", downloader); - } - - @Test - public void concurrent_download() throws IOException { - when(fileHashes.of(any(File.class))).thenReturn("ABCDE"); - - FileCache.Downloader downloader = (filename, toFile) -> { - // 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"); - } - - private File compressedFile() { - return new File("src/test/resources/test.pack.gz"); - } -} 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 deleted file mode 100644 index 35bb977675d..00000000000 --- a/sonar-home/src/test/java/org/sonar/home/cache/FileHashesTest.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.home.cache; - -import org.apache.commons.codec.binary.Hex; -import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.rules.TemporaryFolder; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.security.SecureRandom; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class FileHashesTest { - - private SecureRandom secureRandom = new SecureRandom(); - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - - @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).toLowerCase() - ); - } - } - - @Test - public void test_hash_file() throws IOException { - File f = temp.newFile(); - Files.write(f.toPath(), "sonar".getBytes(StandardCharsets.UTF_8)); - assertThat(hashFile(f)).isEqualTo("d85e336d61f5344395c42126fac239bc"); - } - - @Test - public void test_toHex() { - // lower-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()).toLowerCase() - ); - } - } - - @Test - public void fail_if_file_does_not_exist() throws IOException { - File file = temp.newFile("does_not_exist"); - FileUtils.forceDelete(file); - - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("Fail to compute hash of: " + file.getAbsolutePath()); - - new FileHashes().of(file); - } - - @Test - public void fail_if_stream_is_closed() throws Exception { - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("Fail to compute hash"); - - InputStream input = mock(InputStream.class); - when(input.read(any(byte[].class), anyInt(), anyInt())).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); - } - } - - private String hashFile(File f) { - return new FileHashes().of(f); - } -} diff --git a/sonar-scanner-engine/build.gradle b/sonar-scanner-engine/build.gradle index b58fbf87ce6..6186e58573c 100644 --- a/sonar-scanner-engine/build.gradle +++ b/sonar-scanner-engine/build.gradle @@ -28,7 +28,6 @@ dependencies { compile 'org.slf4j:slf4j-api' compile 'org.sonarsource:sonar-persistit' compile project(':sonar-core') - compile project(':sonar-home') compile project(':sonar-scanner-protocol') compile project(':sonar-ws') runtime project(path: ':sonar-plugin-api', configuration: 'shadow') diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/DirectoryLock.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/DirectoryLock.java new file mode 100644 index 00000000000..187793fece0 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/DirectoryLock.java @@ -0,0 +1,82 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.scanner.scan; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.file.Path; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DirectoryLock { + private static final Logger LOGGER = LoggerFactory.getLogger(DirectoryLock.class); + public static final String LOCK_FILE_NAME = ".sonar_lock"; + + private final Path lockFilePath; + + private RandomAccessFile lockRandomAccessFile; + private FileChannel lockChannel; + private FileLock lockFile; + + public DirectoryLock(Path directory) { + this.lockFilePath = directory.resolve(LOCK_FILE_NAME).toAbsolutePath(); + } + + public boolean tryLock() { + try { + lockRandomAccessFile = new RandomAccessFile(lockFilePath.toFile(), "rw"); + lockChannel = lockRandomAccessFile.getChannel(); + lockFile = lockChannel.tryLock(0, 1024, false); + + return lockFile != null; + } catch (IOException e) { + throw new IllegalStateException("Failed to create lock in " + lockFilePath.toString(), e); + } + } + + public void unlock() { + if (lockFile != null) { + try { + lockFile.release(); + lockFile = null; + } catch (IOException e) { + LOGGER.error("Error releasing lock", e); + } + } + if (lockChannel != null) { + try { + lockChannel.close(); + lockChannel = null; + } catch (IOException e) { + LOGGER.error("Error closing file channel", e); + } + } + if (lockRandomAccessFile != null) { + try { + lockRandomAccessFile.close(); + lockRandomAccessFile = null; + } catch (IOException e) { + LOGGER.error("Error closing file", e); + } + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectLock.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectLock.java index 6221ad42c8b..ec0efe6b063 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectLock.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectLock.java @@ -25,8 +25,6 @@ import java.nio.file.Files; import java.nio.file.Path; import org.picocontainer.Startable; import org.sonar.api.batch.fs.internal.InputModuleHierarchy; -import org.sonar.home.cache.DirectoryLock; -import org.sonar.scanner.bootstrap.Slf4jLogger; public class ProjectLock implements Startable { private final DirectoryLock lock; @@ -40,7 +38,7 @@ public class ProjectLock implements Startable { } catch (IOException e) { throw new IllegalStateException("Failed to create work directory", e); } - this.lock = new DirectoryLock(directory.toAbsolutePath(), new Slf4jLogger()); + this.lock = new DirectoryLock(directory.toAbsolutePath()); } public void tryLock() { diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/WorkDirectoriesInitializer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/WorkDirectoriesInitializer.java index 27fad933fe7..aa1fa5d813a 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/WorkDirectoriesInitializer.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/WorkDirectoriesInitializer.java @@ -27,7 +27,6 @@ import java.util.Iterator; import org.sonar.api.batch.fs.internal.DefaultInputModule; import org.sonar.api.batch.fs.internal.InputModuleHierarchy; import org.sonar.core.util.FileUtils; -import org.sonar.home.cache.DirectoryLock; /** * Clean and create working directories of each module. diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/DirectoryLockTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/DirectoryLockTest.java new file mode 100644 index 00000000000..dc3c7eb1e79 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/DirectoryLockTest.java @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.scanner.scan; + +import java.nio.file.Paths; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DirectoryLockTest { + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + private DirectoryLock lock; + + @Before + public void setUp() { + lock = new DirectoryLock(temp.getRoot().toPath()); + } + + @Test + public void tryLock() { + assertThat(temp.getRoot().list()).isEmpty(); + lock.tryLock(); + assertThat(temp.getRoot().toPath().resolve(".sonar_lock")).exists(); + lock.unlock(); + } + + @Test + public void unlockWithoutLock() { + lock.unlock(); + } + + @Test + public void errorTryLock() { + lock = new DirectoryLock(Paths.get("non", "existing", "path")); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Failed to create lock"); + + lock.tryLock(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectLockTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectLockTest.java index e6f3794ed0c..6905dece613 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectLockTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectLockTest.java @@ -29,7 +29,6 @@ import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.sonar.api.batch.fs.internal.DefaultInputModule; import org.sonar.api.batch.fs.internal.InputModuleHierarchy; -import org.sonar.home.cache.DirectoryLock; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/WorkDirectoriesInitializerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/WorkDirectoriesInitializerTest.java index 25350fed269..8a86ddfb511 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/WorkDirectoriesInitializerTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/WorkDirectoriesInitializerTest.java @@ -28,7 +28,6 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.sonar.api.batch.fs.internal.DefaultInputModule; import org.sonar.api.batch.fs.internal.InputModuleHierarchy; -import org.sonar.home.cache.DirectoryLock; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock;