diff options
Diffstat (limited to 'sonar-home')
9 files changed, 503 insertions, 235 deletions
diff --git a/sonar-home/src/main/java/org/sonar/home/cache/DeleteFileOnCloseInputStream.java b/sonar-home/src/main/java/org/sonar/home/cache/DeleteFileOnCloseInputStream.java new file mode 100644 index 00000000000..7f4a3071bed --- /dev/null +++ b/sonar-home/src/main/java/org/sonar/home/cache/DeleteFileOnCloseInputStream.java @@ -0,0 +1,84 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +public class DeleteFileOnCloseInputStream extends InputStream { + private final InputStream is; + private final Path p; + + public DeleteFileOnCloseInputStream(InputStream stream, Path p) { + this.is = stream; + this.p = p; + } + + @Override + public int read() throws IOException { + return is.read(); + } + + @Override + public int read(byte[] b) throws IOException { + return is.read(b); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return is.read(b, off, len); + } + + @Override + public long skip(long n) throws IOException { + return is.skip(n); + } + + @Override + public synchronized void mark(int readlimit) { + is.mark(readlimit); + } + + @Override + public synchronized void reset() throws IOException { + is.reset(); + } + + @Override + public int available() throws IOException { + return is.available(); + } + + @Override + public boolean markSupported() { + return is.markSupported(); + } + + @Override + public void close() throws IOException { + try { + super.close(); + } finally { + Files.delete(p); + } + } +} 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 new file mode 100644 index 00000000000..4f14d215f1c --- /dev/null +++ b/sonar-home/src/main/java/org/sonar/home/cache/DirectoryLock.java @@ -0,0 +1,106 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.PrintWriter; +import java.io.RandomAccessFile; +import java.io.StringWriter; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.file.Files; +import java.nio.file.Path; + +public class DirectoryLock { + 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 String getFileLockName() { + return LOCK_FILE_NAME; + } + + public void lock() { + try { + lockRandomAccessFile = new RandomAccessFile(lockFilePath.toFile(), "rw"); + lockChannel = lockRandomAccessFile.getChannel(); + lockFile = lockChannel.lock(0, 1024, false); + } catch (IOException e) { + throw new IllegalStateException("Failed to create lock in " + lockFilePath.toString(), e); + } + } + + 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); + } + } + + try { + Files.delete(lockFilePath); + } catch (IOException e) { + // ignore, as an error happens if another process just started to acquire the same lock + StringWriter errors = new StringWriter(); + e.printStackTrace(new PrintWriter(errors)); + logger.debug("Couldn't delete lock file: " + lockFilePath.toString() + " " + errors.toString()); + } + } +} diff --git a/sonar-home/src/main/java/org/sonar/home/cache/PersistentCache.java b/sonar-home/src/main/java/org/sonar/home/cache/PersistentCache.java index 3122d9a6fd0..35dfa4abfcc 100644 --- a/sonar-home/src/main/java/org/sonar/home/cache/PersistentCache.java +++ b/sonar-home/src/main/java/org/sonar/home/cache/PersistentCache.java @@ -22,9 +22,6 @@ package org.sonar.home.cache; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.RandomAccessFile; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; @@ -37,7 +34,6 @@ import java.security.NoSuchAlgorithmException; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; @@ -47,41 +43,38 @@ public class PersistentCache { private static final char[] hexArray = "0123456789ABCDEF".toCharArray(); private static final Charset ENCODING = StandardCharsets.UTF_8; private static final String DIGEST_ALGO = "MD5"; - private static final String LOCK_FNAME = ".lock"; // eviction strategy is to expire entries after modification once a time duration has elapsed private final long defaultDurationToExpireMs; private final Logger logger; - private final String version; - private final Path baseDir; + private final Path dir; + private DirectoryLock lock; - public PersistentCache(Path baseDir, long defaultDurationToExpireMs, Logger logger, String version) { - this.baseDir = baseDir; + public PersistentCache(Path dir, long defaultDurationToExpireMs, Logger logger, DirectoryLock lock) { + this.dir = dir; this.defaultDurationToExpireMs = defaultDurationToExpireMs; this.logger = logger; - this.version = version; + this.lock = lock; reconfigure(); - logger.debug("cache: " + baseDir + ", default expiration time (ms): " + defaultDurationToExpireMs); + logger.debug("cache: " + dir + ", default expiration time (ms): " + defaultDurationToExpireMs); } public synchronized void reconfigure() { try { - Files.createDirectories(baseDir); + Files.createDirectories(dir); } catch (IOException e) { throw new IllegalStateException("failed to create cache dir", e); } } - public Path getBaseDirectory() { - return baseDir; + public Path getDirectory() { + return dir; } @CheckForNull - public synchronized String getString(@Nonnull String obj, @Nullable final PersistentCacheLoader<String> valueLoader) throws IOException { - ValueLoaderDecoder decoder = valueLoader != null ? new ValueLoaderDecoder(valueLoader) : null; - - byte[] cached = get(obj, decoder); + public synchronized String getString(@Nonnull String obj) throws IOException { + byte[] cached = get(obj); if (cached == null) { return null; @@ -97,7 +90,7 @@ public class PersistentCache { try { lock(); Path path = getCacheCopy(key); - return new DeleteOnCloseInputStream(new FileInputStream(path.toFile()), path); + return new DeleteFileOnCloseInputStream(new FileInputStream(path.toFile()), path); } finally { unlock(); @@ -105,7 +98,7 @@ public class PersistentCache { } @CheckForNull - public synchronized byte[] get(@Nonnull String obj, @Nullable PersistentCacheLoader<byte[]> valueLoader) throws IOException { + public synchronized byte[] get(@Nonnull String obj) throws IOException { String key = getKey(obj); try { @@ -119,14 +112,6 @@ public class PersistentCache { } logger.debug("cache miss for " + obj + " -> " + key); - - if (valueLoader != null) { - byte[] value = valueLoader.get(); - if (value != null) { - putCache(key, value); - } - return value; - } } finally { unlock(); } @@ -161,7 +146,7 @@ public class PersistentCache { logger.info("cache: clearing"); try { lock(); - deleteCacheEntries(new DirectoryClearFilter()); + deleteCacheEntries(new DirectoryClearFilter(lock.getFileLockName())); } catch (IOException e) { logger.error("Error clearing cache", e); } finally { @@ -176,7 +161,7 @@ public class PersistentCache { logger.info("cache: cleaning"); try { lock(); - deleteCacheEntries(new DirectoryCleanFilter(defaultDurationToExpireMs)); + deleteCacheEntries(new DirectoryCleanFilter(defaultDurationToExpireMs, lock.getFileLockName())); } catch (IOException e) { logger.error("Error cleaning cache", e); } finally { @@ -185,49 +170,16 @@ public class PersistentCache { } private void lock() throws IOException { - lockRandomAccessFile = new RandomAccessFile(getLockPath().toFile(), "rw"); - lockChannel = lockRandomAccessFile.getChannel(); - lockFile = lockChannel.lock(); + lock.lock(); } - private RandomAccessFile lockRandomAccessFile; - private FileChannel lockChannel; - private FileLock lockFile; - private void unlock() { - if (lockFile != null) { - try { - lockFile.release(); - } catch (IOException e) { - logger.error("Error releasing lock", e); - } - } - if (lockChannel != null) { - try { - lockChannel.close(); - } catch (IOException e) { - logger.error("Error closing file channel", e); - } - } - if (lockRandomAccessFile != null) { - try { - lockRandomAccessFile.close(); - } catch (IOException e) { - logger.error("Error closing file", e); - } - } - - lockFile = null; - lockRandomAccessFile = null; - lockChannel = null; + lock.unlock(); } private String getKey(String uri) { try { String key = uri; - if (version != null) { - key += version; - } MessageDigest digest = MessageDigest.getInstance(DIGEST_ALGO); digest.update(key.getBytes(StandardCharsets.UTF_8)); return byteArrayToHex(digest.digest()); @@ -237,7 +189,7 @@ public class PersistentCache { } private void deleteCacheEntries(DirectoryStream.Filter<Path> filter) throws IOException { - try (DirectoryStream<Path> stream = Files.newDirectoryStream(baseDir, filter)) { + try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, filter)) { for (Path p : stream) { try { Files.delete(p); @@ -248,40 +200,31 @@ public class PersistentCache { } } - private static class ValueLoaderDecoder implements PersistentCacheLoader<byte[]> { - PersistentCacheLoader<String> valueLoader; + private static class DirectoryClearFilter implements DirectoryStream.Filter<Path> { + private String lockFileName; - ValueLoaderDecoder(PersistentCacheLoader<String> valueLoader) { - this.valueLoader = valueLoader; + DirectoryClearFilter(String lockFileName) { + this.lockFileName = lockFileName; } @Override - public byte[] get() throws IOException { - String s = valueLoader.get(); - if (s != null) { - return s.getBytes(ENCODING); - } - return null; - } - } - - private static class DirectoryClearFilter implements DirectoryStream.Filter<Path> { - @Override public boolean accept(Path entry) throws IOException { - return !LOCK_FNAME.equals(entry.getFileName().toString()); + return !lockFileName.equals(entry.getFileName().toString()); } } private static class DirectoryCleanFilter implements DirectoryStream.Filter<Path> { private long defaultDurationToExpireMs; + private String lockFileName; - DirectoryCleanFilter(long defaultDurationToExpireMs) { + DirectoryCleanFilter(long defaultDurationToExpireMs, String lockFileName) { this.defaultDurationToExpireMs = defaultDurationToExpireMs; + this.lockFileName = lockFileName; } @Override public boolean accept(Path entry) throws IOException { - if (LOCK_FNAME.equals(entry.getFileName().toString())) { + if (lockFileName.equals(entry.getFileName().toString())) { return false; } @@ -321,27 +264,6 @@ public class PersistentCache { return temp; } - private static class DeleteOnCloseInputStream extends InputStream { - private final InputStream stream; - private final Path p; - - private DeleteOnCloseInputStream(InputStream stream, Path p) { - this.stream = stream; - this.p = p; - } - - @Override - public int read() throws IOException { - return stream.read(); - } - - @Override - public void close() throws IOException { - stream.close(); - Files.delete(p); - } - } - private boolean validateCacheEntry(Path cacheEntryPath, long durationToExpireMs) throws IOException { if (!Files.exists(cacheEntryPath)) { return false; @@ -369,12 +291,8 @@ public class PersistentCache { return false; } - private Path getLockPath() { - return baseDir.resolve(LOCK_FNAME); - } - private Path getCacheEntryPath(String key) { - return baseDir.resolve(key); + return dir.resolve(key); } public static String byteArrayToHex(byte[] bytes) { diff --git a/sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheBuilder.java b/sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheBuilder.java index 055d2615ed6..2097832853b 100644 --- a/sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheBuilder.java +++ b/sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheBuilder.java @@ -19,43 +19,73 @@ */ package org.sonar.home.cache; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.util.concurrent.TimeUnit; + import javax.annotation.Nullable; +/** + * Cache files will be placed in 3 areas: + * <pre> + * <sonar_home>/ws_cache/<server_url>-<version>/projects/<project>/ + * <sonar_home>/ws_cache/<server_url>-<version>/global/ + * <sonar_home>/ws_cache/<server_url>-<version>/local/ + * </pre> + */ public class PersistentCacheBuilder { private static final long DEFAULT_EXPIRE_DURATION = TimeUnit.MILLISECONDS.convert(1L, TimeUnit.DAYS); private static final String DIR_NAME = "ws_cache"; - private Path cachePath; + private Path cacheBasePath; + private Path relativePath; private final Logger logger; - private String version; public PersistentCacheBuilder(Logger logger) { this.logger = logger; } - public PersistentCache build() { - if (cachePath == null) { - setSonarHome(findHome()); - } - - return new PersistentCache(cachePath, DEFAULT_EXPIRE_DURATION, logger, version); + public PersistentCacheBuilder setAreaForProject(String serverUrl, String serverVersion, String projectKey) { + relativePath = Paths.get(sanitizeFilename(serverUrl + "-" + serverVersion)) + .resolve("projects") + .resolve(sanitizeFilename(projectKey)); + return this; } - - public PersistentCacheBuilder setVersion(String version) { - this.version = version; + + public PersistentCacheBuilder setAreaForGlobal(String serverUrl, String serverVersion) { + relativePath = Paths.get(sanitizeFilename(serverUrl + "-" + serverVersion)) + .resolve("global"); return this; } - + + public PersistentCacheBuilder setAreaForLocalProject(String serverUrl, String serverVersion) { + relativePath = Paths.get(sanitizeFilename(serverUrl + "-" + serverVersion)) + .resolve("local"); + return this; + } + public PersistentCacheBuilder setSonarHome(@Nullable Path p) { if (p != null) { - this.cachePath = p.resolve(DIR_NAME); + this.cacheBasePath = p.resolve(DIR_NAME); } return this; } + public PersistentCache build() { + if(relativePath == null) { + throw new IllegalStateException("area must be set before building"); + } + if (cacheBasePath == null) { + setSonarHome(findHome()); + } + Path cachePath = cacheBasePath.resolve(relativePath); + DirectoryLock lock = new DirectoryLock(cacheBasePath, logger); + return new PersistentCache(cachePath, DEFAULT_EXPIRE_DURATION, logger, lock); + } + private static Path findHome() { String home = System.getenv("SONAR_USER_HOME"); @@ -66,4 +96,12 @@ public class PersistentCacheBuilder { home = System.getProperty("user.home"); return Paths.get(home, ".sonar"); } + + private String sanitizeFilename(String name) { + try { + return URLEncoder.encode(name, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException("Couldn't sanitize filename: " + name, e); + } + } } diff --git a/sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheLoader.java b/sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheLoader.java deleted file mode 100644 index ee906b4b67d..00000000000 --- a/sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheLoader.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube 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. - * - * SonarQube 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; - -public interface PersistentCacheLoader<T> { - T get() throws IOException; -} diff --git a/sonar-home/src/main/java/org/sonar/home/cache/UnlockOnCloseInputStream.java b/sonar-home/src/main/java/org/sonar/home/cache/UnlockOnCloseInputStream.java new file mode 100644 index 00000000000..fdfac82cff1 --- /dev/null +++ b/sonar-home/src/main/java/org/sonar/home/cache/UnlockOnCloseInputStream.java @@ -0,0 +1,82 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.InputStream; + +public class UnlockOnCloseInputStream extends InputStream { + private final DirectoryLock lock; + private final InputStream is; + + public UnlockOnCloseInputStream(InputStream stream, DirectoryLock lock) { + this.is = stream; + this.lock = lock; + } + + @Override + public int read() throws IOException { + return is.read(); + } + + @Override + public int read(byte[] b) throws IOException { + return is.read(b); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return is.read(b, off, len); + } + + @Override + public long skip(long n) throws IOException { + return is.skip(n); + } + + @Override + public synchronized void mark(int readlimit) { + is.mark(readlimit); + } + + @Override + public synchronized void reset() throws IOException { + is.reset(); + } + + @Override + public int available() throws IOException { + return is.available(); + } + + @Override + public boolean markSupported() { + return is.markSupported(); + } + + @Override + public void close() throws IOException { + try { + super.close(); + } finally { + lock.unlock(); + } + } +} 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 new file mode 100644 index 00000000000..fc1d994bd6f --- /dev/null +++ b/sonar-home/src/test/java/org/sonar/home/cache/DirectoryLockTest.java @@ -0,0 +1,94 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 static org.mockito.Mockito.mock; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.rules.ExpectedException; + +import java.nio.channels.OverlappingFileLockException; +import java.nio.file.Paths; + +import org.junit.Test; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; + +public class DirectoryLockTest { + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + @Rule + public ExpectedException exception = ExpectedException.none(); + private DirectoryLock lock; + + @Before + public void setUp() { + lock = new DirectoryLock(temp.getRoot().toPath(), mock(Logger.class)); + } + + @Test + public void lock() { + assertThat(temp.getRoot().list()).isEmpty(); + lock.lock(); + assertThat(temp.getRoot().toPath().resolve(".sonar_lock")).exists(); + lock.unlock(); + assertThat(temp.getRoot().list()).isEmpty(); + } + + @Test + public void tryLock() { + assertThat(temp.getRoot().list()).isEmpty(); + lock.tryLock(); + assertThat(temp.getRoot().toPath().resolve(".sonar_lock")).exists(); + lock.unlock(); + assertThat(temp.getRoot().list()).isEmpty(); + } + + @Test(expected = OverlappingFileLockException.class) + public void error_2locks() { + assertThat(temp.getRoot().list()).isEmpty(); + lock.lock(); + lock.lock(); + } + + @Test + public void unlockWithoutLock() { + lock.unlock(); + } + + @Test + public void errorCreatingLock() { + lock = new DirectoryLock(Paths.get("non", "existing", "path"), mock(Logger.class)); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Failed to create lock"); + lock.lock(); + } + + @Test + public void errorTryLock() { + lock = new DirectoryLock(Paths.get("non", "existing", "path"), mock(Logger.class)); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Failed to create lock"); + lock.tryLock(); + } +} diff --git a/sonar-home/src/test/java/org/sonar/home/cache/PersistentCacheBuilderTest.java b/sonar-home/src/test/java/org/sonar/home/cache/PersistentCacheBuilderTest.java index 78efe696695..7a57e98d68c 100644 --- a/sonar-home/src/test/java/org/sonar/home/cache/PersistentCacheBuilderTest.java +++ b/sonar-home/src/test/java/org/sonar/home/cache/PersistentCacheBuilderTest.java @@ -20,10 +20,11 @@ package org.sonar.home.cache; import java.nio.file.Files; +import java.nio.file.Paths; + import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; - import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; @@ -35,17 +36,18 @@ public class PersistentCacheBuilderTest { @Test public void user_home_property_can_be_null() { - PersistentCache cache = new PersistentCacheBuilder(mock(Logger.class)).setSonarHome(null).build(); - assertTrue(Files.isDirectory(cache.getBaseDirectory())); - assertThat(cache.getBaseDirectory().getFileName().toString()).isEqualTo("ws_cache"); + PersistentCache cache = new PersistentCacheBuilder(mock(Logger.class)).setSonarHome(null).setAreaForGlobal("url", "0").build(); + assertTrue(Files.isDirectory(cache.getDirectory())); + assertThat(cache.getDirectory()).endsWith(Paths.get("url-0", "global")); } @Test public void set_user_home() { - PersistentCache cache = new PersistentCacheBuilder(mock(Logger.class)).setSonarHome(temp.getRoot().toPath()).build(); + PersistentCache cache = new PersistentCacheBuilder(mock(Logger.class)).setSonarHome(temp.getRoot().toPath()).setAreaForGlobal("url", "0").build(); - assertThat(cache.getBaseDirectory().getParent().toString()).isEqualTo(temp.getRoot().toPath().toString()); - assertTrue(Files.isDirectory(cache.getBaseDirectory())); + assertThat(cache.getDirectory()).isDirectory(); + assertThat(cache.getDirectory()).startsWith(temp.getRoot().toPath()); + assertTrue(Files.isDirectory(cache.getDirectory())); } @Test @@ -54,11 +56,22 @@ public class PersistentCacheBuilderTest { System.setProperty("user.home", temp.getRoot().getAbsolutePath()); - PersistentCache cache = new PersistentCacheBuilder(mock(Logger.class)).build(); - assertTrue(Files.isDirectory(cache.getBaseDirectory())); - assertThat(cache.getBaseDirectory().getFileName().toString()).isEqualTo("ws_cache"); + PersistentCache cache = new PersistentCacheBuilder(mock(Logger.class)).setAreaForGlobal("url", "0").build(); + assertTrue(Files.isDirectory(cache.getDirectory())); + assertThat(cache.getDirectory()).startsWith(temp.getRoot().toPath()); + } + + @Test + public void directories() { + System.setProperty("user.home", temp.getRoot().getAbsolutePath()); + + PersistentCache cache = new PersistentCacheBuilder(mock(Logger.class)).setAreaForProject("url", "0", "proj").build(); + assertThat(cache.getDirectory()).endsWith(Paths.get(".sonar", "ws_cache", "url-0", "projects", "proj")); + + cache = new PersistentCacheBuilder(mock(Logger.class)).setAreaForLocalProject("url", "0").build(); + assertThat(cache.getDirectory()).endsWith(Paths.get(".sonar", "ws_cache", "url-0", "local")); - String expectedSonarHome = temp.getRoot().toPath().resolve(".sonar").toString(); - assertThat(cache.getBaseDirectory().getParent().toString()).isEqualTo(expectedSonarHome); + cache = new PersistentCacheBuilder(mock(Logger.class)).setAreaForGlobal("url", "0").build(); + assertThat(cache.getDirectory()).endsWith(Paths.get(".sonar", "ws_cache", "url-0", "global")); } } diff --git a/sonar-home/src/test/java/org/sonar/home/cache/PersistentCacheTest.java b/sonar-home/src/test/java/org/sonar/home/cache/PersistentCacheTest.java index 9760b096676..77ee30908e1 100644 --- a/sonar-home/src/test/java/org/sonar/home/cache/PersistentCacheTest.java +++ b/sonar-home/src/test/java/org/sonar/home/cache/PersistentCacheTest.java @@ -20,7 +20,13 @@ package org.sonar.home.cache; import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.verify; import org.apache.commons.io.FileUtils; import org.junit.Before; import org.junit.Rule; @@ -28,20 +34,21 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; public class PersistentCacheTest { private final static String URI = "key1"; private final static String VALUE = "cache content"; private PersistentCache cache = null; + private DirectoryLock lock = null; @Rule public TemporaryFolder tmp = new TemporaryFolder(); @Before public void setUp() { - cache = new PersistentCache(tmp.getRoot().toPath(), Long.MAX_VALUE, mock(Logger.class), null); + lock = mock(DirectoryLock.class); + when(lock.getFileLockName()).thenReturn("lock"); + cache = new PersistentCache(tmp.getRoot().toPath(), Long.MAX_VALUE, mock(Logger.class), lock); } @Test @@ -50,59 +57,43 @@ public class PersistentCacheTest { } @Test - public void testNullLoader() throws Exception { - assertThat(cache.get(URI, null)).isNull(); - assertCacheHit(false); - } - - @Test - public void testNullLoaderString() throws Exception { - assertThat(cache.getString(URI, null)).isNull(); - assertCacheHit(false); - } - - @Test - public void testNullValue() throws Exception { - // mocks have their methods returning null by default - PersistentCacheLoader<byte[]> c = mock(PersistentCacheLoader.class); - assertThat(cache.get(URI, c)).isNull(); - verify(c).get(); - assertCacheHit(false); - } - - @Test public void testClean() throws Exception { + Path lockFile = cache.getDirectory().resolve("lock"); // puts entry - assertCacheHit(false); + cache.put(URI, VALUE.getBytes(StandardCharsets.UTF_8)); + Files.write(lockFile, "test".getBytes(StandardCharsets.UTF_8)); + assertCacheHit(true); // negative time to make sure it is expired - cache = new PersistentCache(tmp.getRoot().toPath(), -100, mock(Logger.class), null); + cache = new PersistentCache(tmp.getRoot().toPath(), -100, mock(Logger.class), lock); cache.clean(); assertCacheHit(false); + // lock file should not get deleted + assertThat(new String(Files.readAllBytes(lockFile), StandardCharsets.UTF_8)).isEqualTo("test"); } @Test public void testClear() throws Exception { - assertCacheHit(false); + Path lockFile = cache.getDirectory().resolve("lock"); + cache.put(URI, VALUE.getBytes(StandardCharsets.UTF_8)); + Files.write(lockFile, "test".getBytes(StandardCharsets.UTF_8)); + assertCacheHit(true); cache.clear(); assertCacheHit(false); + // lock file should not get deleted + assertThat(new String(Files.readAllBytes(lockFile), StandardCharsets.UTF_8)).isEqualTo("test"); } @Test public void testCacheHit() throws Exception { - assertCacheHit(false); - assertCacheHit(true); - } - - @Test - public void testPut() throws Exception { - cache.put(URI, VALUE.getBytes()); + cache.put(URI, VALUE.getBytes(StandardCharsets.UTF_8)); assertCacheHit(true); } @Test public void testReconfigure() throws Exception { - cache = new PersistentCache(tmp.getRoot().toPath(), Long.MAX_VALUE, mock(Logger.class), null); + cache = new PersistentCache(tmp.getRoot().toPath(), Long.MAX_VALUE, mock(Logger.class), lock); assertCacheHit(false); + cache.put(URI, VALUE.getBytes(StandardCharsets.UTF_8)); assertCacheHit(true); File root = tmp.getRoot(); @@ -113,26 +104,16 @@ public class PersistentCacheTest { assertThat(root).exists(); assertCacheHit(false); + cache.put(URI, VALUE.getBytes(StandardCharsets.UTF_8)); assertCacheHit(true); } @Test public void testExpiration() throws Exception { - // negative time to make sure it is expired on the second call - cache = new PersistentCache(tmp.getRoot().toPath(), -100, mock(Logger.class), null); - assertCacheHit(false); - assertCacheHit(false); - } - - @Test - public void testDifferentServerVersions() throws Exception { + // negative time to make sure it is expired + cache = new PersistentCache(tmp.getRoot().toPath(), -100, mock(Logger.class), lock); + cache.put(URI, VALUE.getBytes(StandardCharsets.UTF_8)); assertCacheHit(false); - assertCacheHit(true); - - PersistentCache cache2 = new PersistentCache(tmp.getRoot().toPath(), Long.MAX_VALUE, mock(Logger.class), "5.2"); - assertCacheHit(cache2, false); - assertCacheHit(cache2, true); - } private void assertCacheHit(boolean hit) throws Exception { @@ -140,31 +121,9 @@ public class PersistentCacheTest { } private void assertCacheHit(PersistentCache pCache, boolean hit) throws Exception { - CacheFillerString c = new CacheFillerString(); - assertThat(pCache.getString(URI, c)).isEqualTo(VALUE); - assertThat(c.wasCalled).isEqualTo(!hit); - } - - private class CacheFillerString implements PersistentCacheLoader<String> { - public boolean wasCalled = false; - - @Override - public String get() { - wasCalled = true; - return VALUE; - } - } - - /** - * WSCache should be transparent regarding exceptions: if an exception is thrown by the value loader, it should pass through - * the cache to the original caller using the cache. - * @throws Exception - */ - @Test(expected = ArithmeticException.class) - public void testExceptions() throws Exception { - PersistentCacheLoader<byte[]> c = mock(PersistentCacheLoader.class); - when(c.get()).thenThrow(ArithmeticException.class); - cache.get(URI, c); + String expected = hit ? VALUE : null; + assertThat(pCache.getString(URI)).isEqualTo(expected); + verify(lock, atLeast(1)).unlock(); } } |