aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-home
diff options
context:
space:
mode:
authorDuarte Meneses <duarte.meneses@sonarsource.com>2015-09-18 11:43:24 +0200
committerDuarte Meneses <duarte.meneses@sonarsource.com>2015-09-30 16:27:12 +0200
commit0847774db59344316629a7171c3943dbfaa3f52d (patch)
treec0ccebef9f3a29c97cb07b3fb1d1b2c667770e9e /sonar-home
parent1dae583a5cca52b34f6a11ed6cf515998ca06d4b (diff)
downloadsonarqube-0847774db59344316629a7171c3943dbfaa3f52d.tar.gz
sonarqube-0847774db59344316629a7171c3943dbfaa3f52d.zip
SONAR-6777 Project cache sync
Diffstat (limited to 'sonar-home')
-rw-r--r--sonar-home/src/main/java/org/sonar/home/cache/DeleteFileOnCloseInputStream.java84
-rw-r--r--sonar-home/src/main/java/org/sonar/home/cache/DirectoryLock.java106
-rw-r--r--sonar-home/src/main/java/org/sonar/home/cache/PersistentCache.java138
-rw-r--r--sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheBuilder.java64
-rw-r--r--sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheLoader.java26
-rw-r--r--sonar-home/src/main/java/org/sonar/home/cache/UnlockOnCloseInputStream.java82
-rw-r--r--sonar-home/src/test/java/org/sonar/home/cache/DirectoryLockTest.java94
-rw-r--r--sonar-home/src/test/java/org/sonar/home/cache/PersistentCacheBuilderTest.java37
-rw-r--r--sonar-home/src/test/java/org/sonar/home/cache/PersistentCacheTest.java107
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>
+ * &lt;sonar_home&gt;/ws_cache/&lt;server_url&gt;-&lt;version&gt;/projects/&lt;project&gt;/
+ * &lt;sonar_home&gt;/ws_cache/&lt;server_url&gt;-&lt;version&gt;/global/
+ * &lt;sonar_home&gt;/ws_cache/&lt;server_url&gt;-&lt;version&gt;/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();
}
}