aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--it/it-tests/src/test/java/batch/IssuesModeTest.java2
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java6
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java21
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/cache/DefaultProjectCacheStatus.java52
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/cache/GlobalPersistentCacheProvider.java (renamed from sonar-batch/src/main/java/org/sonar/batch/cache/PersistentCacheProvider.java)42
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/cache/NonAssociatedCacheSynchronizer.java13
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/cache/ProjectCacheStatus.java8
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/cache/ProjectCacheSynchronizer.java12
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/cache/ProjectKeySupplier.java36
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/cache/ProjectPersistentCacheProvider.java67
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/cache/ProjectSyncContainer.java14
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/cache/WSLoader.java130
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/repository/DefaultServerIssuesLoader.java19
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/repository/user/UserRepositoryLoader.java32
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/rule/DefaultRulesLoader.java14
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/ProjectLock.java59
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java4
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/util/BatchUtils.java24
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java2
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java2
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/cache/DefaultProjectCacheStatusTest.java73
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/cache/GlobalPersistentCacheProviderTest.java (renamed from sonar-batch/src/test/java/org/sonar/batch/cache/PersistentCacheProviderTest.java)53
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/cache/NonAssociatedCacheSynchronizerTest.java8
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/cache/ProjectCacheSynchronizerTest.java41
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/cache/ProjectPersistentCacheProviderTest.java82
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java2
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderTest.java77
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/mediumtest/BatchMediumTester.java22
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/repository/DefaultServerIssuesLoaderTest.java15
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/repository/user/UserRepositoryLoaderTest.java32
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/rule/DefaultRulesLoaderTest.java19
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/scan/ProjectLockTest.java2
-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/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
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectKey.java (renamed from sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheLoader.java)8
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectReactor.java10
42 files changed, 1108 insertions, 607 deletions
diff --git a/it/it-tests/src/test/java/batch/IssuesModeTest.java b/it/it-tests/src/test/java/batch/IssuesModeTest.java
index 0935af6c35f..7ff66112e3c 100644
--- a/it/it-tests/src/test/java/batch/IssuesModeTest.java
+++ b/it/it-tests/src/test/java/batch/IssuesModeTest.java
@@ -71,7 +71,7 @@ public class IssuesModeTest {
restoreProfile("one-issue-per-line.xml");
orchestrator.getServer().provisionProject("sample", "xoo-sample");
orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line");
- SonarRunner runner = configureRunnerIssues("shared/xoo-sample");
+ SonarRunner runner = configureRunnerIssues("shared/xoo-sample", "sonar.verbose", "true");
BuildResult result = orchestrator.executeBuild(runner);
assertThat(ItUtils.countIssuesInJsonReport(result, true)).isEqualTo(17);
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java
index 2a802f6be1b..30b54799632 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java
@@ -28,7 +28,7 @@ import org.sonar.api.utils.System2;
import org.sonar.api.utils.UriReader;
import org.sonar.batch.analysis.AnalysisProperties;
import org.sonar.batch.analysis.DefaultAnalysisMode;
-import org.sonar.batch.cache.PersistentCacheProvider;
+import org.sonar.batch.cache.GlobalPersistentCacheProvider;
import org.sonar.batch.cache.ProjectSyncContainer;
import org.sonar.batch.cache.StrategyWSLoaderProvider;
import org.sonar.batch.cache.WSLoader.LoadStrategy;
@@ -85,7 +85,7 @@ public class GlobalContainer extends ComponentContainer {
BatchPluginPredicate.class,
ExtensionInstaller.class,
- CachesManager.class,
+ CachesManager.class,
GlobalMode.class,
GlobalSettings.class,
new RulesProvider(),
@@ -95,7 +95,7 @@ public class GlobalContainer extends ComponentContainer {
DefaultHttpDownloader.class,
UriReader.class,
new FileCacheProvider(),
- new PersistentCacheProvider(),
+ new GlobalPersistentCacheProvider(),
System2.INSTANCE,
new GlobalRepositoriesProvider(),
UuidFactoryImpl.INSTANCE);
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java
index 901e2d2cac2..79a6257a504 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java
@@ -19,9 +19,6 @@
*/
package org.sonar.batch.bootstrap;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
@@ -30,13 +27,10 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
-import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
import java.net.URI;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
@@ -62,7 +56,6 @@ import org.sonar.core.util.DefaultHttpDownloader;
@BatchSide
public class ServerClient {
private static final String GET = "GET";
- private static final Logger LOG = LoggerFactory.getLogger(ServerClient.class);
private GlobalProperties props;
private DefaultHttpDownloader.BaseHttpDownloader downloader;
@@ -74,20 +67,6 @@ public class ServerClient {
public String getURL() {
return StringUtils.removeEnd(StringUtils.defaultIfBlank(props.property("sonar.host.url"), "http://localhost:9000"), "/");
}
-
- public String getServerVersion() {
- InputStream is = this.getClass().getClassLoader().getResourceAsStream("sq-version.txt");
- if (is == null) {
- LOG.warn("Failed to get SQ version");
- return null;
- }
- try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
- return br.readLine();
- } catch (IOException e) {
- LOG.warn("Failed to get SQ version", e);
- return null;
- }
- }
public URI getURI(String pathStartingWithSlash) {
Preconditions.checkArgument(pathStartingWithSlash.startsWith("/"), "Path must start with slash /: " + pathStartingWithSlash);
diff --git a/sonar-batch/src/main/java/org/sonar/batch/cache/DefaultProjectCacheStatus.java b/sonar-batch/src/main/java/org/sonar/batch/cache/DefaultProjectCacheStatus.java
index 248d2b8b350..3ecf425d11a 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/cache/DefaultProjectCacheStatus.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/cache/DefaultProjectCacheStatus.java
@@ -19,73 +19,67 @@
*/
package org.sonar.batch.cache;
-import javax.annotation.Nullable;
+import org.apache.commons.io.FileUtils;
-import org.sonar.batch.bootstrap.ServerClient;
import org.sonar.home.cache.PersistentCache;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.Date;
public class DefaultProjectCacheStatus implements ProjectCacheStatus {
- private static final String STATUS_PREFIX = "cache-sync-status-";
+ private static final String STATUS_FILENAME = "cache-sync-status";
private PersistentCache cache;
- private ServerClient client;
- public DefaultProjectCacheStatus(PersistentCache cache, ServerClient client) {
+ public DefaultProjectCacheStatus(PersistentCache cache) {
this.cache = cache;
- this.client = client;
}
@Override
- public void save(@Nullable String projectKey) {
+ public void save() {
Date now = new Date();
try {
- ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
- try (ObjectOutputStream objOutput = new ObjectOutputStream(byteOutput)) {
+ FileOutputStream fos = new FileOutputStream(getStatusFilePath().toFile());
+ try (ObjectOutputStream objOutput = new ObjectOutputStream(fos)) {
objOutput.writeObject(now);
}
- cache.put(getKey(projectKey), byteOutput.toByteArray());
+
} catch (IOException e) {
throw new IllegalStateException("Failed to write cache sync status", e);
}
}
@Override
- public void delete(@Nullable String projectKey) {
- try {
- cache.put(getKey(projectKey), new byte[0]);
- } catch (IOException e) {
- throw new IllegalStateException("Failed to delete cache sync status", e);
- }
+ public void delete() {
+ cache.clear();
+ FileUtils.deleteQuietly(getStatusFilePath().toFile());
}
@Override
- public Date getSyncStatus(@Nullable String projectKey) {
+ public Date getSyncStatus() {
+ Path p = getStatusFilePath();
try {
- byte[] status = cache.get(getKey(projectKey), null);
- if (status == null || status.length == 0) {
+ if (!Files.isRegularFile(p)) {
return null;
}
- ByteArrayInputStream byteInput = new ByteArrayInputStream(status);
- try (ObjectInputStream objInput = new ObjectInputStream(byteInput)) {
+ InputStream is = new FileInputStream(p.toFile());
+ try (ObjectInputStream objInput = new ObjectInputStream(is)) {
return (Date) objInput.readObject();
}
} catch (IOException | ClassNotFoundException e) {
+ FileUtils.deleteQuietly(p.toFile());
throw new IllegalStateException("Failed to read cache sync status", e);
}
}
- private String getKey(@Nullable String projectKey) {
- if (projectKey != null) {
- return STATUS_PREFIX + client.getURL() + "-" + client.getServerVersion() + "-" + projectKey;
- } else {
- return STATUS_PREFIX + client.getURL() + "-" + client.getServerVersion();
- }
+ private Path getStatusFilePath() {
+ return cache.getDirectory().resolve(STATUS_FILENAME);
}
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/cache/PersistentCacheProvider.java b/sonar-batch/src/main/java/org/sonar/batch/cache/GlobalPersistentCacheProvider.java
index 99040ba9927..29e2f42ea38 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/cache/PersistentCacheProvider.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/cache/GlobalPersistentCacheProvider.java
@@ -19,54 +19,38 @@
*/
package org.sonar.batch.cache;
+import org.apache.commons.lang.StringUtils;
import org.sonar.batch.bootstrap.Slf4jLogger;
-import org.sonar.batch.bootstrap.UserProperties;
-
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
+import org.sonar.batch.util.BatchUtils;
+import org.sonar.home.cache.PersistentCacheBuilder;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
-import org.picocontainer.injectors.ProviderAdapter;
+import org.sonar.batch.bootstrap.GlobalProperties;
import org.sonar.home.cache.PersistentCache;
-import org.sonar.home.cache.PersistentCacheBuilder;
+import org.picocontainer.injectors.ProviderAdapter;
-public class PersistentCacheProvider extends ProviderAdapter {
- private static final Logger LOG = Loggers.get(PersistentCacheProvider.class);
+public class GlobalPersistentCacheProvider extends ProviderAdapter {
private PersistentCache cache;
- public PersistentCache provide(UserProperties props) {
+ public PersistentCache provide(GlobalProperties props) {
if (cache == null) {
PersistentCacheBuilder builder = new PersistentCacheBuilder(new Slf4jLogger());
-
String home = props.property("sonar.userHome");
+ String serverUrl = getServerUrl(props);
+
if (home != null) {
builder.setSonarHome(Paths.get(home));
}
-
- builder.setVersion(getServerVersion());
+
+ builder.setAreaForGlobal(serverUrl, BatchUtils.getServerVersion());
cache = builder.build();
}
return cache;
}
- private String getServerVersion() {
- InputStream is = this.getClass().getClassLoader().getResourceAsStream("sq-version.txt");
- if (is == null) {
- LOG.warn("Failed to get SQ version");
- return null;
- }
- try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
- return br.readLine();
- } catch (IOException e) {
- LOG.warn("Failed to get SQ version", e);
- return null;
- }
+ private String getServerUrl(GlobalProperties props) {
+ return StringUtils.removeEnd(StringUtils.defaultIfBlank(props.property("sonar.host.url"), "http://localhost:9000"), "/");
}
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/cache/NonAssociatedCacheSynchronizer.java b/sonar-batch/src/main/java/org/sonar/batch/cache/NonAssociatedCacheSynchronizer.java
index e21d1420694..44743f5c120 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/cache/NonAssociatedCacheSynchronizer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/cache/NonAssociatedCacheSynchronizer.java
@@ -46,7 +46,7 @@ public class NonAssociatedCacheSynchronizer {
}
public void execute(boolean force) {
- Date lastSync = cacheStatus.getSyncStatus(null);
+ Date lastSync = cacheStatus.getSyncStatus();
if (lastSync != null) {
if (!force) {
@@ -55,13 +55,15 @@ public class NonAssociatedCacheSynchronizer {
} else {
LOG.info("-- Found cache [{}], synchronizing data..", lastSync);
}
- cacheStatus.delete(null);
+ cacheStatus.delete();
} else {
LOG.info("-- Cache not found, synchronizing data..");
}
loadData();
- saveStatus();
+
+ cacheStatus.save();
+ LOG.info("-- Succesfully synchronized cache");
}
private static Collection<String> getKeys(Collection<QProfile> qProfiles) {
@@ -73,11 +75,6 @@ public class NonAssociatedCacheSynchronizer {
return list;
}
- private void saveStatus() {
- cacheStatus.save(null);
- LOG.info("-- Succesfully synchronized cache");
- }
-
private void loadData() {
Profiler profiler = Profiler.create(Loggers.get(ProjectCacheSynchronizer.class));
diff --git a/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectCacheStatus.java b/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectCacheStatus.java
index a5f97349a4b..39947c64e3b 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectCacheStatus.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectCacheStatus.java
@@ -19,14 +19,12 @@
*/
package org.sonar.batch.cache;
-import javax.annotation.Nullable;
-
import java.util.Date;
public interface ProjectCacheStatus {
- void save(@Nullable String projectKey);
+ void save();
- void delete(@Nullable String projectKey);
+ void delete();
- Date getSyncStatus(@Nullable String projectKey);
+ Date getSyncStatus();
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectCacheSynchronizer.java b/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectCacheSynchronizer.java
index d2d6cbcf2db..20d16fbaaad 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectCacheSynchronizer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectCacheSynchronizer.java
@@ -63,7 +63,7 @@ public class ProjectCacheSynchronizer {
}
public void load(String projectKey, boolean force) {
- Date lastSync = cacheStatus.getSyncStatus(projectKey);
+ Date lastSync = cacheStatus.getSyncStatus();
if (lastSync != null) {
if (!force) {
@@ -72,17 +72,17 @@ public class ProjectCacheSynchronizer {
} else {
LOG.info("-- Found project [{}] cache [{}], synchronizing data..", projectKey, lastSync);
}
- cacheStatus.delete(projectKey);
+ cacheStatus.delete();
} else {
LOG.info("-- Cache for project [{}] not found, synchronizing data..", projectKey);
}
loadData(projectKey);
- saveStatus(projectKey);
+ saveStatus();
}
- private void saveStatus(String projectKey) {
- cacheStatus.save(projectKey);
+ private void saveStatus() {
+ cacheStatus.save();
LOG.info("-- Succesfully synchronized project cache");
}
@@ -103,7 +103,7 @@ public class ProjectCacheSynchronizer {
profiler.stopInfo();
Collection<String> profileKeys = getKeys(qProfiles);
-
+
profiler.startInfo("Load project active rules");
activeRulesLoader.load(profileKeys, projectKey);
profiler.stopInfo();
diff --git a/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectKeySupplier.java b/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectKeySupplier.java
new file mode 100644
index 00000000000..78afcee1ac3
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectKeySupplier.java
@@ -0,0 +1,36 @@
+/*
+ * 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.batch.cache;
+
+import org.sonar.api.batch.bootstrap.ProjectKey;
+
+public class ProjectKeySupplier implements ProjectKey {
+ private final String key;
+
+ ProjectKeySupplier(String key) {
+ this.key = key;
+ }
+
+ @Override
+ public String get() {
+ return key;
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectPersistentCacheProvider.java b/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectPersistentCacheProvider.java
new file mode 100644
index 00000000000..49684faff18
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectPersistentCacheProvider.java
@@ -0,0 +1,67 @@
+/*
+ * 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.batch.cache;
+
+import org.sonar.api.batch.bootstrap.ProjectKey;
+
+import org.sonar.batch.util.BatchUtils;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.batch.bootstrap.GlobalProperties;
+import com.google.common.base.Preconditions;
+import org.sonar.batch.analysis.DefaultAnalysisMode;
+import org.sonar.batch.bootstrap.Slf4jLogger;
+
+import java.nio.file.Paths;
+
+import org.picocontainer.injectors.ProviderAdapter;
+import org.sonar.home.cache.PersistentCache;
+import org.sonar.home.cache.PersistentCacheBuilder;
+
+public class ProjectPersistentCacheProvider extends ProviderAdapter {
+ private PersistentCache cache;
+
+ public PersistentCache provide(GlobalProperties props, DefaultAnalysisMode mode, ProjectKey key) {
+ if (cache == null) {
+ PersistentCacheBuilder builder = new PersistentCacheBuilder(new Slf4jLogger());
+ String projectKey = key.get();
+ String home = props.property("sonar.userHome");
+ String serverUrl = getServerUrl(props);
+
+ if (home != null) {
+ builder.setSonarHome(Paths.get(home));
+ }
+
+ if (mode.isNotAssociated()) {
+ builder.setAreaForLocalProject(serverUrl, BatchUtils.getServerVersion());
+ } else {
+ Preconditions.checkNotNull(projectKey);
+ builder.setAreaForProject(serverUrl, BatchUtils.getServerVersion(), projectKey);
+ }
+
+ cache = builder.build();
+ }
+
+ return cache;
+ }
+
+ private String getServerUrl(GlobalProperties props) {
+ return StringUtils.removeEnd(StringUtils.defaultIfBlank(props.property("sonar.host.url"), "http://localhost:9000"), "/");
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectSyncContainer.java b/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectSyncContainer.java
index 600a6791c3a..79be9383f10 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectSyncContainer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectSyncContainer.java
@@ -24,8 +24,8 @@ import javax.annotation.Nullable;
import org.sonar.batch.repository.ProjectRepositoriesFactoryProvider;
import org.sonar.batch.analysis.DefaultAnalysisMode;
import org.sonar.api.CoreProperties;
-import com.google.common.collect.ImmutableMap;
+import java.util.HashMap;
import java.util.Map;
import org.sonar.batch.analysis.AnalysisProperties;
@@ -68,8 +68,12 @@ public class ProjectSyncContainer extends ComponentContainer {
}
}
- private static DefaultAnalysisMode createIssuesAnalysisMode() {
- Map<String, String> props = ImmutableMap.of(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES);
+ private static DefaultAnalysisMode createIssuesAnalysisMode(@Nullable String projectKey) {
+ Map<String, String> props = new HashMap<>();
+ props.put(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES);
+ if (projectKey != null) {
+ props.put(CoreProperties.PROJECT_KEY_PROPERTY, projectKey);
+ }
GlobalProperties globalProps = new GlobalProperties(props);
AnalysisProperties analysisProps = new AnalysisProperties(props);
return new DefaultAnalysisMode(globalProps, analysisProps);
@@ -77,10 +81,12 @@ public class ProjectSyncContainer extends ComponentContainer {
private void addComponents() {
add(new StrategyWSLoaderProvider(LoadStrategy.SERVER_ONLY),
+ new ProjectKeySupplier(projectKey),
projectKey != null ? ProjectCacheSynchronizer.class : NonAssociatedCacheSynchronizer.class,
UserRepositoryLoader.class,
new ProjectRepositoriesFactoryProvider(projectKey),
- createIssuesAnalysisMode());
+ new ProjectPersistentCacheProvider(),
+ createIssuesAnalysisMode(projectKey));
addIfMissing(DefaultProjectCacheStatus.class, ProjectCacheStatus.class);
addIfMissing(DefaultProjectRepositoriesLoader.class, ProjectRepositoriesLoader.class);
diff --git a/sonar-batch/src/main/java/org/sonar/batch/cache/WSLoader.java b/sonar-batch/src/main/java/org/sonar/batch/cache/WSLoader.java
index 06b38f7b607..96a83811d08 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/cache/WSLoader.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/cache/WSLoader.java
@@ -19,18 +19,19 @@
*/
package org.sonar.batch.cache;
-import com.google.common.io.ByteSource;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
import org.apache.commons.io.IOUtils;
import org.sonar.api.utils.HttpDownloader;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.batch.bootstrap.ServerClient;
import org.sonar.home.cache.PersistentCache;
-
import static org.sonar.batch.cache.WSLoader.ServerStatus.ACCESSIBLE;
import static org.sonar.batch.cache.WSLoader.ServerStatus.NOT_ACCESSIBLE;
import static org.sonar.batch.cache.WSLoader.ServerStatus.UNKNOWN;
@@ -63,9 +64,8 @@ public class WSLoader {
}
@Nonnull
- public WSLoaderResult<ByteSource> loadSource(String id) {
- WSLoaderResult<byte[]> byteResult = load(id, defautLoadStrategy);
- return new WSLoaderResult<ByteSource>(ByteSource.wrap(byteResult.get()), byteResult.isFromCache());
+ public WSLoaderResult<InputStream> loadStream(String id) {
+ return load(id, defautLoadStrategy, createStreamLoaderServer(), createStreamLoaderCache());
}
@Nonnull
@@ -75,31 +75,25 @@ public class WSLoader {
@Nonnull
public WSLoaderResult<String> loadString(String id, WSLoader.LoadStrategy strategy) {
- WSLoaderResult<byte[]> byteResult = load(id, strategy);
- return new WSLoaderResult<String>(new String(byteResult.get(), StandardCharsets.UTF_8), byteResult.isFromCache());
- }
-
- @Nonnull
- public WSLoaderResult<byte[]> load(String id) {
- return load(id, defautLoadStrategy);
+ return load(id, strategy, createStringLoaderServer(), createStringLoaderCache());
}
@Nonnull
- public WSLoaderResult<byte[]> load(String id, WSLoader.LoadStrategy strategy) {
+ private <T> WSLoaderResult<T> load(String id, WSLoader.LoadStrategy strategy, DataLoader<T> serverLoader, DataLoader<T> cacheLoader) {
switch (strategy) {
case CACHE_FIRST:
- return loadFromCacheFirst(id, true);
+ return loadFromCacheFirst(id, cacheLoader, serverLoader);
case CACHE_ONLY:
- return loadFromCacheFirst(id, false);
+ return loadFromCacheFirst(id, cacheLoader, null);
case SERVER_FIRST:
- return loadFromServerFirst(id, true);
+ return loadFromServerFirst(id, serverLoader, cacheLoader);
case SERVER_ONLY:
default:
- return loadFromServerFirst(id, false);
+ return loadFromServerFirst(id, serverLoader, null);
}
}
- public LoadStrategy getStrategy() {
+ public LoadStrategy getDefaultStrategy() {
return this.defautLoadStrategy;
}
@@ -116,22 +110,14 @@ public class WSLoader {
return serverStatus == NOT_ACCESSIBLE;
}
- private void updateCache(String id, byte[] value) {
- try {
- cache.put(client.getURI(id).toString(), value);
- } catch (IOException e) {
- throw new IllegalStateException("Error saving to WS cache", e);
- }
- }
-
@Nonnull
- private WSLoaderResult<byte[]> loadFromCacheFirst(String id, boolean fallback) {
+ private <T> WSLoaderResult<T> loadFromCacheFirst(String id, DataLoader<T> cacheLoader, @Nullable DataLoader<T> serverLoader) {
try {
- return loadFromCache(id);
+ return loadFromCache(id, cacheLoader);
} catch (NotAvailableException cacheNotAvailable) {
- if (fallback) {
+ if (serverLoader != null) {
try {
- return loadFromServer(id);
+ return loadFromServer(id, serverLoader);
} catch (NotAvailableException serverNotAvailable) {
throw new IllegalStateException(FAIL_MSG, serverNotAvailable.getCause());
}
@@ -141,13 +127,13 @@ public class WSLoader {
}
@Nonnull
- private WSLoaderResult<byte[]> loadFromServerFirst(String id, boolean fallback) {
+ private <T> WSLoaderResult<T> loadFromServerFirst(String id, DataLoader<T> serverLoader, @Nullable DataLoader<T> cacheLoader) {
try {
- return loadFromServer(id);
+ return loadFromServer(id, serverLoader);
} catch (NotAvailableException serverNotAvailable) {
- if (fallback) {
+ if (cacheLoader != null) {
try {
- return loadFromCache(id);
+ return loadFromCache(id, cacheLoader);
} catch (NotAvailableException cacheNotAvailable) {
throw new IllegalStateException(FAIL_MSG, serverNotAvailable.getCause());
}
@@ -156,31 +142,35 @@ public class WSLoader {
}
}
+ interface DataLoader<T> {
+ T load(String id) throws IOException;
+ }
+
@Nonnull
- private WSLoaderResult<byte[]> loadFromCache(String id) throws NotAvailableException {
+ private <T> WSLoaderResult<T> loadFromCache(String id, DataLoader<T> loader) throws NotAvailableException {
+ T result = null;
+
try {
- byte[] result = cache.get(client.getURI(id).toString(), null);
- if (result == null) {
- throw new NotAvailableException("resource not cached");
- }
- return new WSLoaderResult<byte[]>(result, true);
+ result = loader.load(id);
} catch (IOException e) {
// any exception on the cache should fail fast
throw new IllegalStateException(e);
}
+ if (result == null) {
+ throw new NotAvailableException("resource not cached");
+ }
+ return new WSLoaderResult<T>(result, true);
}
@Nonnull
- private WSLoaderResult<byte[]> loadFromServer(String id) throws NotAvailableException {
+ private <T> WSLoaderResult<T> loadFromServer(String id, DataLoader<T> loader) throws NotAvailableException {
if (isOffline()) {
throw new NotAvailableException("Server not available");
}
try {
- InputStream is = client.load(id, REQUEST_METHOD, true, CONNECT_TIMEOUT, READ_TIMEOUT);
+ T t = loader.load(id);
switchToOnline();
- byte[] value = IOUtils.toByteArray(is);
- updateCache(id, value);
- return new WSLoaderResult<byte[]>(value, false);
+ return new WSLoaderResult<T>(t, false);
} catch (IllegalStateException e) {
if (e.getCause() instanceof HttpDownloader.HttpException) {
// fail fast if it could connect but there was a application-level error
@@ -194,6 +184,56 @@ public class WSLoader {
}
}
+ private DataLoader<String> createStringLoaderServer() {
+ return new DataLoader<String>() {
+ @Override
+ public String load(String id) throws IOException {
+ InputStream is = client.load(id, REQUEST_METHOD, true, CONNECT_TIMEOUT, READ_TIMEOUT);
+ String str = IOUtils.toString(is, StandardCharsets.UTF_8);
+ try {
+ cache.put(id, str.getBytes(StandardCharsets.UTF_8));
+ } catch (IOException e) {
+ throw new IllegalStateException("Error saving to WS cache", e);
+ }
+ return str;
+ }
+ };
+ }
+
+ private DataLoader<String> createStringLoaderCache() {
+ return new DataLoader<String>() {
+ @Override
+ public String load(String id) throws IOException {
+ return cache.getString(id);
+ }
+ };
+ }
+
+ private DataLoader<InputStream> createStreamLoaderServer() {
+ return new DataLoader<InputStream>() {
+ @Override
+ public InputStream load(String id) throws IOException {
+ InputStream is = client.load(id, REQUEST_METHOD, true, CONNECT_TIMEOUT, READ_TIMEOUT);
+ try {
+ cache.put(id, is);
+ } catch (IOException e) {
+ throw new IllegalStateException("Error saving to WS cache", e);
+ }
+ is.close();
+ return cache.getStream(id);
+ }
+ };
+ }
+
+ private DataLoader<InputStream> createStreamLoaderCache() {
+ return new DataLoader<InputStream>() {
+ @Override
+ public InputStream load(String id) throws IOException {
+ return cache.getStream(id);
+ }
+ };
+ }
+
private class NotAvailableException extends Exception {
private static final long serialVersionUID = 1L;
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultServerIssuesLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultServerIssuesLoader.java
index aefe0569275..0989cd5e616 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultServerIssuesLoader.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultServerIssuesLoader.java
@@ -19,16 +19,15 @@
*/
package org.sonar.batch.repository;
-import org.sonar.batch.cache.WSLoaderResult;
-
-import org.sonar.batch.cache.WSLoader;
-import org.sonar.batch.util.BatchUtils;
-import com.google.common.io.ByteSource;
import com.google.common.base.Function;
-import org.sonar.batch.protocol.input.BatchInput.ServerIssue;
-
+import com.google.common.io.ByteSource;
import java.io.IOException;
import java.io.InputStream;
+import org.apache.commons.io.IOUtils;
+import org.sonar.batch.cache.WSLoader;
+import org.sonar.batch.cache.WSLoaderResult;
+import org.sonar.batch.protocol.input.BatchInput.ServerIssue;
+import org.sonar.batch.util.BatchUtils;
public class DefaultServerIssuesLoader implements ServerIssuesLoader {
@@ -45,8 +44,8 @@ public class DefaultServerIssuesLoader implements ServerIssuesLoader {
return result.isFromCache();
}
- private static void parseIssues(ByteSource input, Function<ServerIssue, Void> consumer) {
- try (InputStream is = input.openBufferedStream()) {
+ private static void parseIssues(InputStream is, Function<ServerIssue, Void> consumer) {
+ try {
ServerIssue previousIssue = ServerIssue.parseDelimitedFrom(is);
while (previousIssue != null) {
consumer.apply(previousIssue);
@@ -54,6 +53,8 @@ public class DefaultServerIssuesLoader implements ServerIssuesLoader {
}
} catch (IOException e) {
throw new IllegalStateException("Unable to get previous issues", e);
+ } finally {
+ IOUtils.closeQuietly(is);
}
}
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/user/UserRepositoryLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/user/UserRepositoryLoader.java
index d40f918f87b..a7b9dc71d4a 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/repository/user/UserRepositoryLoader.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/repository/user/UserRepositoryLoader.java
@@ -19,8 +19,9 @@
*/
package org.sonar.batch.repository.user;
-import org.sonar.batch.cache.WSLoaderResult;
+import org.apache.commons.io.IOUtils;
+import org.sonar.batch.cache.WSLoaderResult;
import org.sonar.batch.cache.WSLoader;
import javax.annotation.Nullable;
@@ -29,7 +30,6 @@ import org.apache.commons.lang.mutable.MutableBoolean;
import com.google.common.collect.Lists;
import com.google.common.base.Joiner;
import org.sonar.batch.util.BatchUtils;
-import com.google.common.io.ByteSource;
import com.google.common.base.Function;
import org.sonar.batch.protocol.input.BatchInput;
@@ -50,16 +50,16 @@ public class UserRepositoryLoader {
public BatchInput.User load(String userLogin) {
return load(userLogin, null);
}
-
+
public BatchInput.User load(String userLogin, @Nullable MutableBoolean fromCache) {
- ByteSource byteSource = loadQuery(new UserEncodingFunction().apply(userLogin), fromCache);
- return parseUser(byteSource);
+ InputStream is = loadQuery(new UserEncodingFunction().apply(userLogin), fromCache);
+ return parseUser(is);
}
public Collection<BatchInput.User> load(List<String> userLogins) {
return load(userLogins, null);
}
-
+
/**
* Not cache friendly. Should not be used if a cache hit is expected.
*/
@@ -67,13 +67,13 @@ public class UserRepositoryLoader {
if (userLogins.isEmpty()) {
return Collections.emptyList();
}
- ByteSource byteSource = loadQuery(Joiner.on(',').join(Lists.transform(userLogins, new UserEncodingFunction())), fromCache);
+ InputStream is = loadQuery(Joiner.on(',').join(Lists.transform(userLogins, new UserEncodingFunction())), fromCache);
- return parseUsers(byteSource);
+ return parseUsers(is);
}
- private ByteSource loadQuery(String loginsQuery, @Nullable MutableBoolean fromCache) {
- WSLoaderResult<ByteSource> result = wsLoader.loadSource("/scanner/users?logins=" + loginsQuery);
+ private InputStream loadQuery(String loginsQuery, @Nullable MutableBoolean fromCache) {
+ WSLoaderResult<InputStream> result = wsLoader.loadStream("/scanner/users?logins=" + loginsQuery);
if (fromCache != null) {
fromCache.setValue(result.isFromCache());
}
@@ -87,18 +87,20 @@ public class UserRepositoryLoader {
}
}
- private static BatchInput.User parseUser(ByteSource input) {
- try (InputStream is = input.openStream()) {
+ private static BatchInput.User parseUser(InputStream is) {
+ try {
return BatchInput.User.parseDelimitedFrom(is);
} catch (IOException e) {
throw new IllegalStateException("Unable to get user details from server", e);
+ } finally {
+ IOUtils.closeQuietly(is);
}
}
- private static Collection<BatchInput.User> parseUsers(ByteSource input) {
+ private static Collection<BatchInput.User> parseUsers(InputStream is) {
List<BatchInput.User> users = new ArrayList<>();
- try (InputStream is = input.openStream()) {
+ try {
BatchInput.User user = BatchInput.User.parseDelimitedFrom(is);
while (user != null) {
users.add(user);
@@ -106,6 +108,8 @@ public class UserRepositoryLoader {
}
} catch (IOException e) {
throw new IllegalStateException("Unable to get user details from server", e);
+ } finally {
+ IOUtils.closeQuietly(is);
}
return users;
diff --git a/sonar-batch/src/main/java/org/sonar/batch/rule/DefaultRulesLoader.java b/sonar-batch/src/main/java/org/sonar/batch/rule/DefaultRulesLoader.java
index b427cfed871..315106a6179 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/rule/DefaultRulesLoader.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/rule/DefaultRulesLoader.java
@@ -19,15 +19,15 @@
*/
package org.sonar.batch.rule;
-import org.sonar.batch.cache.WSLoaderResult;
+import org.apache.commons.io.IOUtils;
+import org.sonar.batch.cache.WSLoaderResult;
import org.sonar.batch.cache.WSLoader;
import javax.annotation.Nullable;
import org.apache.commons.lang.mutable.MutableBoolean;
import org.sonarqube.ws.Rules.ListResponse.Rule;
-import com.google.common.io.ByteSource;
import org.sonarqube.ws.Rules.ListResponse;
import java.io.IOException;
@@ -45,19 +45,21 @@ public class DefaultRulesLoader implements RulesLoader {
@Override
public List<Rule> load(@Nullable MutableBoolean fromCache) {
- WSLoaderResult<ByteSource> result = wsLoader.loadSource(RULES_SEARCH_URL);
- ListResponse list = loadFromSource(result.get());
+ WSLoaderResult<InputStream> result = wsLoader.loadStream(RULES_SEARCH_URL);
+ ListResponse list = loadFromStream(result.get());
if (fromCache != null) {
fromCache.setValue(result.isFromCache());
}
return list.getRulesList();
}
- private static ListResponse loadFromSource(ByteSource input) {
- try (InputStream is = input.openStream()) {
+ private static ListResponse loadFromStream(InputStream is) {
+ try {
return ListResponse.parseFrom(is);
} catch (IOException e) {
throw new IllegalStateException("Unable to get rules", e);
+ } finally {
+ IOUtils.closeQuietly(is);
}
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectLock.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectLock.java
index 6266bf44e6c..ff42abf956d 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectLock.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectLock.java
@@ -19,46 +19,32 @@
*/
package org.sonar.batch.scan;
+import org.sonar.batch.bootstrap.Slf4jLogger;
+
+import org.sonar.home.cache.DirectoryLock;
import org.picocontainer.Startable;
import org.sonar.api.batch.bootstrap.ProjectReactor;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
-import java.nio.file.Files;
import java.nio.file.Path;
public class ProjectLock implements Startable {
- private static final Logger LOG = LoggerFactory.getLogger(ProjectLock.class);
static final String LOCK_FILE_NAME = ".sonar_lock";
- private final Path lockFilePath;
- private RandomAccessFile lockRandomAccessFile;
- private FileChannel lockChannel;
- private FileLock lockFile;
+ private DirectoryLock lock;
public ProjectLock(ProjectReactor projectReactor) {
Path directory = projectReactor.getRoot().getBaseDir().toPath();
- this.lockFilePath = directory.resolve(LOCK_FILE_NAME).toAbsolutePath();
+ this.lock = new DirectoryLock(directory.toAbsolutePath(), new Slf4jLogger());
}
public void tryLock() {
try {
- lockRandomAccessFile = new RandomAccessFile(lockFilePath.toFile(), "rw");
- lockChannel = lockRandomAccessFile.getChannel();
- lockFile = lockChannel.tryLock(0, 1024, false);
-
- if (lockFile == null) {
+ if (!lock.tryLock()) {
failAlreadyInProgress(null);
}
} catch (OverlappingFileLockException e) {
failAlreadyInProgress(e);
- } catch (IOException e) {
- throw new IllegalStateException("Failed to create project lock in " + lockFilePath.toString(), e);
}
}
@@ -68,42 +54,11 @@ public class ProjectLock implements Startable {
@Override
public void stop() {
- if (lockFile != null) {
- try {
- lockFile.release();
- lockFile = null;
- } catch (IOException e) {
- LOG.error("Error releasing lock", e);
- }
- }
- if (lockChannel != null) {
- try {
- lockChannel.close();
- lockChannel = null;
- } catch (IOException e) {
- LOG.error("Error closing file channel", e);
- }
- }
- if (lockRandomAccessFile != null) {
- try {
- lockRandomAccessFile.close();
- lockRandomAccessFile = null;
- } catch (IOException e) {
- LOG.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
- LOG.debug("Couldn't delete lock file: " + lockFilePath.toString(), e);
- }
+ lock.unlock();
}
@Override
public void start() {
// nothing to do
}
-
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
index 431b916ebff..5c7bbb9e026 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
@@ -19,8 +19,9 @@
*/
package org.sonar.batch.scan;
-import org.sonar.batch.issue.tracking.LocalIssueTracking;
+import org.sonar.batch.cache.ProjectPersistentCacheProvider;
+import org.sonar.batch.issue.tracking.LocalIssueTracking;
import org.sonar.batch.issue.tracking.IssueTransition;
import org.sonar.batch.repository.DefaultProjectRepositoriesFactory;
import org.sonar.batch.repository.QualityProfileProvider;
@@ -151,6 +152,7 @@ public class ProjectScanContainer extends ComponentContainer {
BatchComponentCache.class,
DefaultIssueCallback.class,
new ProjectSettingsProvider(),
+ new ProjectPersistentCacheProvider(),
// temp
new AnalysisTempFolderProvider(),
diff --git a/sonar-batch/src/main/java/org/sonar/batch/util/BatchUtils.java b/sonar-batch/src/main/java/org/sonar/batch/util/BatchUtils.java
index 42d53af38cc..aa5fbc35ae0 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/util/BatchUtils.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/util/BatchUtils.java
@@ -20,12 +20,20 @@
package org.sonar.batch.util;
import com.google.common.base.Strings;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class BatchUtils {
+ private static final Logger LOG = LoggerFactory.getLogger(BatchUtils.class);
private BatchUtils() {
}
@@ -38,7 +46,7 @@ public class BatchUtils {
String cleanKey = StringUtils.deleteWhitespace(projectKey);
return StringUtils.replace(cleanKey, ":", "_");
}
-
+
public static String encodeForUrl(@Nullable String url) {
try {
return URLEncoder.encode(Strings.nullToEmpty(url), "UTF-8");
@@ -59,4 +67,18 @@ public class BatchUtils {
return o.getClass().getName();
}
+
+ public static String getServerVersion() {
+ InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("sq-version.txt");
+ if (is == null) {
+ LOG.warn("Failed to get SQ version");
+ return null;
+ }
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
+ return br.readLine();
+ } catch (IOException e) {
+ LOG.warn("Failed to get SQ version", e);
+ return null;
+ }
+ }
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java
index 77cd2311311..9e80507094b 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java
@@ -63,6 +63,6 @@ public class AnalysisWSLoaderProviderTest {
props = new AnalysisProperties(propMap, null);
WSLoader loader = loaderProvider.provide(props, mode, cache, client);
- assertThat(loader.getStrategy()).isEqualTo(LoadStrategy.SERVER_ONLY);
+ assertThat(loader.getDefaultStrategy()).isEqualTo(LoadStrategy.SERVER_ONLY);
}
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java
index c663dbb3ef4..654616811d5 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java
@@ -81,7 +81,7 @@ public class BatchPluginInstallerTest {
thrown.expect(IllegalStateException.class);
WSLoader wsLoader = mock(WSLoader.class);
- doThrow(new IllegalStateException()).when(wsLoader).load("/deploy/plugins/index.txt");
+ doThrow(new IllegalStateException()).when(wsLoader).loadString("/deploy/plugins/index.txt");
new BatchPluginInstaller(wsLoader, serverClient, fileCache, pluginPredicate).installRemotes();
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/cache/DefaultProjectCacheStatusTest.java b/sonar-batch/src/test/java/org/sonar/batch/cache/DefaultProjectCacheStatusTest.java
index 4239ca1c024..8d86733a01d 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/cache/DefaultProjectCacheStatusTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/cache/DefaultProjectCacheStatusTest.java
@@ -22,19 +22,15 @@ package org.sonar.batch.cache;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.assertj.core.api.Assertions.assertThat;
-
-import org.sonar.home.cache.PersistentCacheLoader;
-
+import com.google.common.io.Files;
import org.junit.rules.ExpectedException;
+import java.io.File;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.Date;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.any;
import org.junit.Test;
-import org.sonar.home.cache.Logger;
import org.junit.rules.TemporaryFolder;
import org.junit.Rule;
import org.junit.Before;
@@ -42,10 +38,9 @@ import org.sonar.batch.bootstrap.ServerClient;
import org.sonar.home.cache.PersistentCache;
public class DefaultProjectCacheStatusTest {
- private static final String PROJ_KEY = "project1";
@Rule
public TemporaryFolder tmp = new TemporaryFolder();
-
+
@Rule
public ExpectedException exception = ExpectedException.none();
@@ -55,70 +50,44 @@ public class DefaultProjectCacheStatusTest {
@Before
public void setUp() {
- cache = new PersistentCache(tmp.getRoot().toPath(), Long.MAX_VALUE, mock(Logger.class), null);
client = mock(ServerClient.class);
- when(client.getServerVersion()).thenReturn("5.2");
- when(client.getURL()).thenReturn("localhost");
- cacheStatus = new DefaultProjectCacheStatus(cache, client);
- }
-
- @Test
- public void errorDelete() throws IOException {
cache = mock(PersistentCache.class);
- doThrow(IOException.class).when(cache).put(anyString(), any(byte[].class));
- cacheStatus = new DefaultProjectCacheStatus(cache, client);
-
- exception.expect(IllegalStateException.class);
- exception.expectMessage("Failed to delete cache sync status");
- cacheStatus.delete(PROJ_KEY);
+ when(cache.getDirectory()).thenReturn(tmp.getRoot().toPath());
+ cacheStatus = new DefaultProjectCacheStatus(cache);
}
-
+
@Test
public void errorSave() throws IOException {
- cache = mock(PersistentCache.class);
- doThrow(IOException.class).when(cache).put(anyString(), any(byte[].class));
- cacheStatus = new DefaultProjectCacheStatus(cache, client);
+ when(cache.getDirectory()).thenReturn(tmp.getRoot().toPath().resolve("unexistent_folder"));
+ cacheStatus = new DefaultProjectCacheStatus(cache);
exception.expect(IllegalStateException.class);
exception.expectMessage("Failed to write cache sync status");
- cacheStatus.save(PROJ_KEY);
- }
-
- @Test
- public void useServerVersionAsKey() {
- cacheStatus.save(PROJ_KEY);
- assertThat(cacheStatus.getSyncStatus(PROJ_KEY)).isNotNull();
- assertThat(age(cacheStatus.getSyncStatus(PROJ_KEY))).isLessThan(2000);
-
- when(client.getServerVersion()).thenReturn("5.1");
-
- assertThat(cacheStatus.getSyncStatus(PROJ_KEY)).isNull();
+ cacheStatus.save();
}
-
+
@Test
public void errorStatus() throws IOException {
- cache = mock(PersistentCache.class);
- doThrow(IOException.class).when(cache).get(anyString(), any(PersistentCacheLoader.class));
- cacheStatus = new DefaultProjectCacheStatus(cache, client);
+ Files.write("trash".getBytes(StandardCharsets.UTF_8), new File(tmp.getRoot(), "cache-sync-status"));
+ cacheStatus = new DefaultProjectCacheStatus(cache);
exception.expect(IllegalStateException.class);
exception.expectMessage("Failed to read cache sync status");
- cacheStatus.getSyncStatus(PROJ_KEY);
+ cacheStatus.getSyncStatus();
}
-
+
@Test
public void testSave() {
- cacheStatus.save(PROJ_KEY);
- assertThat(cacheStatus.getSyncStatus(PROJ_KEY)).isNotNull();
- assertThat(age(cacheStatus.getSyncStatus(PROJ_KEY))).isLessThan(2000);
- assertThat(cacheStatus.getSyncStatus(PROJ_KEY + "1")).isNull();
+ cacheStatus.save();
+ assertThat(cacheStatus.getSyncStatus()).isNotNull();
+ assertThat(age(cacheStatus.getSyncStatus())).isLessThan(2000);
}
@Test
public void testDelete() {
- cacheStatus.save(PROJ_KEY);
- cacheStatus.delete(PROJ_KEY);
- assertThat(cacheStatus.getSyncStatus(PROJ_KEY)).isNull();
+ cacheStatus.save();
+ cacheStatus.delete();
+ assertThat(cacheStatus.getSyncStatus()).isNull();
}
private long age(Date date) {
diff --git a/sonar-batch/src/test/java/org/sonar/batch/cache/PersistentCacheProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/cache/GlobalPersistentCacheProviderTest.java
index 3f9c4af0e77..fe0513bfc5a 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/cache/PersistentCacheProviderTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/cache/GlobalPersistentCacheProviderTest.java
@@ -19,45 +19,40 @@
*/
package org.sonar.batch.cache;
-import org.junit.Rule;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.batch.bootstrap.GlobalProperties;
-import org.sonar.batch.cache.PersistentCacheProvider;
+import org.sonar.batch.util.BatchUtils;
-import java.io.File;
-import java.util.Collections;
+import org.sonar.home.cache.PersistentCache;
+
+import java.util.HashMap;
-import org.junit.Before;
import static org.assertj.core.api.Assertions.assertThat;
+import org.sonar.batch.bootstrap.GlobalProperties;
+import org.junit.Before;
import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
-public class PersistentCacheProviderTest {
+public class GlobalPersistentCacheProviderTest {
@Rule
public TemporaryFolder temp = new TemporaryFolder();
- private PersistentCacheProvider provider = null;
- private GlobalProperties props = null;
-
+ private GlobalPersistentCacheProvider provider;
+ private GlobalProperties globalProperties;
+
@Before
- public void prepare() {
- props = new GlobalProperties(Collections.<String, String>emptyMap());
- provider = new PersistentCacheProvider();
+ public void setUp() {
+ HashMap<String, String> map = new HashMap<String, String>();
+ map.put("sonar.userHome", temp.getRoot().getAbsolutePath());
+ globalProperties = new GlobalProperties(map);
+ provider = new GlobalPersistentCacheProvider();
}
-
- @Test
- public void test_singleton() {
- assertThat(provider.provide(props)).isEqualTo(provider.provide(props));
- }
-
- @Test
- public void test_cache_dir() {
- assertThat(provider.provide(props).getBaseDirectory().toFile()).exists().isDirectory();
- }
-
+
@Test
- public void test_home() {
- File f = temp.getRoot();
- props.properties().put("sonar.userHome", f.getAbsolutePath());
- assertThat(provider.provide(props).getBaseDirectory()).isEqualTo(f.toPath().resolve("ws_cache"));
+ public void test_path() {
+ PersistentCache cache = provider.provide(globalProperties);
+ assertThat(cache.getDirectory()).isEqualTo(temp.getRoot().toPath()
+ .resolve("ws_cache")
+ .resolve("http%3A%2F%2Flocalhost%3A9000-" + BatchUtils.getServerVersion())
+ .resolve("global"));
}
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/cache/NonAssociatedCacheSynchronizerTest.java b/sonar-batch/src/test/java/org/sonar/batch/cache/NonAssociatedCacheSynchronizerTest.java
index d7b3bbd6710..3d58f3d3daa 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/cache/NonAssociatedCacheSynchronizerTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/cache/NonAssociatedCacheSynchronizerTest.java
@@ -62,14 +62,14 @@ public class NonAssociatedCacheSynchronizerTest {
@Test
public void dont_sync_if_exists() {
- when(cacheStatus.getSyncStatus(null)).thenReturn(new Date());
+ when(cacheStatus.getSyncStatus()).thenReturn(new Date());
synchronizer.execute(false);
verifyNoMoreInteractions(qualityProfileLoader, activeRulesLoader);
}
@Test
public void always_sync_if_force() {
- when(cacheStatus.getSyncStatus(null)).thenReturn(new Date());
+ when(cacheStatus.getSyncStatus()).thenReturn(new Date());
synchronizer.execute(true);
checkSync();
}
@@ -81,8 +81,8 @@ public class NonAssociatedCacheSynchronizerTest {
}
private void checkSync() {
- verify(cacheStatus).getSyncStatus(null);
- verify(cacheStatus).save(null);
+ verify(cacheStatus).getSyncStatus();
+ verify(cacheStatus).save();
verify(qualityProfileLoader).load(null, null);
verify(activeRulesLoader).load(ImmutableList.of("profile"), null);
diff --git a/sonar-batch/src/test/java/org/sonar/batch/cache/ProjectCacheSynchronizerTest.java b/sonar-batch/src/test/java/org/sonar/batch/cache/ProjectCacheSynchronizerTest.java
index a4dade54f2d..d91f154ee79 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/cache/ProjectCacheSynchronizerTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/cache/ProjectCacheSynchronizerTest.java
@@ -19,11 +19,32 @@
*/
package org.sonar.batch.cache;
+import static org.mockito.Mockito.when;
+import org.sonar.batch.repository.ProjectRepositoriesFactory;
+import org.sonar.batch.repository.DefaultProjectRepositoriesFactory;
+import org.junit.Rule;
+import org.junit.rules.ExpectedException;
+import org.sonar.batch.repository.ProjectSettingsRepo;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
-import com.google.common.io.ByteSource;
-import com.google.common.io.Resources;
+import org.sonar.batch.protocol.input.ActiveRule;
+import org.sonar.batch.protocol.input.QProfile;
+import org.apache.commons.lang.mutable.MutableBoolean;
+import org.sonar.batch.repository.DefaultProjectSettingsLoader;
+import org.sonar.batch.rule.DefaultActiveRulesLoader;
+import org.sonar.batch.repository.DefaultQualityProfileLoader;
+import org.sonar.batch.repository.ProjectSettingsLoader;
+import org.sonar.batch.rule.ActiveRulesLoader;
+import org.sonar.batch.repository.QualityProfileLoader;
+import org.sonar.batch.analysis.DefaultAnalysisMode;
+import org.sonar.batch.analysis.AnalysisProperties;
+import org.sonar.batch.protocol.input.ProjectRepositories;
+import org.sonar.batch.repository.DefaultServerIssuesLoader;
+import org.sonar.batch.repository.DefaultProjectRepositoriesLoader;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+
import java.io.IOException;
+import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@@ -98,10 +119,10 @@ public class ProjectCacheSynchronizerTest {
MockitoAnnotations.initMocks(this);
String batchProject = getResourceAsString("batch_project.json");
- ByteSource issues = getResourceAsByteSource("batch_issues.protobuf");
+ InputStream issues = getResourceAsInputStream("batch_issues.protobuf");
when(ws.loadString(BATCH_PROJECT)).thenReturn(new WSLoaderResult<>(batchProject, false));
- when(ws.loadSource(ISSUES)).thenReturn(new WSLoaderResult<>(issues, false));
+ when(ws.loadStream(ISSUES)).thenReturn(new WSLoaderResult<>(issues, false));
when(analysisMode.isIssues()).thenReturn(true);
when(properties.properties()).thenReturn(new HashMap<String, String>());
@@ -157,10 +178,10 @@ public class ProjectCacheSynchronizerTest {
sync.load(PROJECT_KEY, false);
verify(ws).loadString(BATCH_PROJECT);
- verify(ws).loadSource(ISSUES);
+ verify(ws).loadStream(ISSUES);
verifyNoMoreInteractions(ws);
- verify(cacheStatus).save(anyString());
+ verify(cacheStatus).save();
}
@Test
@@ -193,12 +214,12 @@ public class ProjectCacheSynchronizerTest {
ProjectCacheSynchronizer sync = create(mockedProjectRepositories);
sync.load(PROJECT_KEY, true);
- verify(cacheStatus).save(PROJECT_KEY);
+ verify(cacheStatus).save();
}
@Test
public void testDontSyncIfNotForce() {
- when(cacheStatus.getSyncStatus(PROJECT_KEY)).thenReturn(new Date());
+ when(cacheStatus.getSyncStatus()).thenReturn(new Date());
ProjectCacheSynchronizer sync = create(null);
sync.load(PROJECT_KEY, false);
@@ -210,8 +231,8 @@ public class ProjectCacheSynchronizerTest {
return Resources.toString(resource, StandardCharsets.UTF_8);
}
- private ByteSource getResourceAsByteSource(String name) throws IOException {
+ private InputStream getResourceAsInputStream(String name) throws IOException {
URL resource = this.getClass().getResource(getClass().getSimpleName() + "/" + name);
- return Resources.asByteSource(resource);
+ return Resources.asByteSource(resource).openBufferedStream();
}
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/cache/ProjectPersistentCacheProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/cache/ProjectPersistentCacheProviderTest.java
new file mode 100644
index 00000000000..75e5b65e633
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/cache/ProjectPersistentCacheProviderTest.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.batch.cache;
+
+import org.sonar.api.batch.bootstrap.ProjectKey;
+
+import org.sonar.batch.util.BatchUtils;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.batch.analysis.DefaultAnalysisMode;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.batch.bootstrap.GlobalProperties;
+import org.sonar.batch.cache.ProjectPersistentCacheProvider;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.Collections;
+
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.mock;
+import org.junit.Before;
+import static org.assertj.core.api.Assertions.assertThat;
+import org.junit.Test;
+
+public class ProjectPersistentCacheProviderTest {
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ private ProjectPersistentCacheProvider provider = null;
+ private GlobalProperties props = null;
+ private DefaultAnalysisMode mode = null;
+ private ProjectKey key = null;
+
+ @Before
+ public void prepare() {
+ key = new ProjectKeySupplier("proj");
+ props = new GlobalProperties(Collections.<String, String>emptyMap());
+ mode = mock(DefaultAnalysisMode.class);
+ provider = new ProjectPersistentCacheProvider();
+ }
+
+ @Test
+ public void test_singleton() {
+ assertThat(provider.provide(props, mode, key)).isEqualTo(provider.provide(props, mode, key));
+ }
+
+ @Test
+ public void test_cache_dir() {
+ assertThat(provider.provide(props, mode, key).getDirectory().toFile()).exists().isDirectory();
+ }
+
+ @Test
+ public void test_home() {
+ File f = temp.getRoot();
+ props.properties().put("sonar.userHome", f.getAbsolutePath());
+ Path expected = f.toPath()
+ .resolve("ws_cache")
+ .resolve("http%3A%2F%2Flocalhost%3A9000-" + BatchUtils.getServerVersion())
+ .resolve("projects")
+ .resolve("proj");
+
+ assertThat(provider.provide(props, mode, key).getDirectory()).isEqualTo(expected);
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java
index 6ad5591a6d4..a7c613056bf 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java
@@ -47,7 +47,7 @@ public class StrategyWSLoaderProviderTest {
StrategyWSLoaderProvider provider = new StrategyWSLoaderProvider(LoadStrategy.CACHE_FIRST);
WSLoader wsLoader = provider.provide(cache, client);
- assertThat(wsLoader.getStrategy()).isEqualTo(LoadStrategy.CACHE_FIRST);
+ assertThat(wsLoader.getDefaultStrategy()).isEqualTo(LoadStrategy.CACHE_FIRST);
}
@Test
diff --git a/sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderTest.java
index fa812fb4988..db9fe132fa9 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderTest.java
@@ -33,6 +33,7 @@ import org.mockito.Mockito;
import org.mockito.InOrder;
import java.io.IOException;
+import java.io.InputStream;
import java.net.URI;
import static org.junit.Assert.*;
@@ -53,7 +54,7 @@ import org.sonar.home.cache.PersistentCache;
import org.mockito.Mock;
public class WSLoaderTest {
- private final static String ID = "/dummy";
+ private final static String ID = "dummy";
private final static String cacheValue = "cache";
private final static String serverValue = "server";
@@ -68,7 +69,7 @@ public class WSLoaderTest {
public void setUp() throws IOException {
MockitoAnnotations.initMocks(this);
when(client.load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt())).thenReturn(IOUtils.toInputStream(serverValue));
- when(cache.get(ID, null)).thenReturn(cacheValue.getBytes());
+ when(cache.getString(ID)).thenReturn(cacheValue);
when(client.getURI(anyString())).thenAnswer(new Answer<URI>() {
@Override
public URI answer(InvocationOnMock invocation) throws Throwable {
@@ -90,14 +91,45 @@ public class WSLoaderTest {
}
@Test
+ public void get_stream_from_cache() throws IOException {
+ InputStream is = mock(InputStream.class);
+ when(cache.getStream(ID)).thenReturn(is);
+ WSLoader loader = new WSLoader(LoadStrategy.CACHE_FIRST, cache, client);
+ WSLoaderResult<InputStream> result = loader.loadStream(ID);
+ assertThat(result.get()).isEqualTo(is);
+ verify(cache).getStream(ID);
+
+ verifyNoMoreInteractions(cache, client);
+ }
+
+ @Test
+ public void put_stream_in_cache() throws IOException {
+ InputStream is1 = mock(InputStream.class);
+ InputStream is2 = mock(InputStream.class);
+
+ when(client.load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt())).thenReturn(is1);
+ when(cache.getStream(ID)).thenReturn(is2);
+
+ WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client);
+ WSLoaderResult<InputStream> result = loader.loadStream(ID);
+ assertThat(result.get()).isEqualTo(is2);
+
+ verify(client).load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt());
+ verify(cache).put(ID, is1);
+ verify(cache).getStream(ID);
+
+ verifyNoMoreInteractions(cache, client);
+ }
+
+ @Test
public void test_cache_strategy_fallback() throws IOException {
turnCacheEmpty();
WSLoader loader = new WSLoader(LoadStrategy.CACHE_FIRST, cache, client);
- assertResult(loader.load(ID), serverValue.getBytes(), false);
+ assertResult(loader.loadString(ID), serverValue, false);
InOrder inOrder = Mockito.inOrder(client, cache);
- inOrder.verify(cache).get(ID, null);
+ inOrder.verify(cache).getString(ID);
inOrder.verify(client).load(eq(ID), anyString(), anyBoolean(), anyInt(), anyInt());
}
@@ -110,13 +142,13 @@ public class WSLoaderTest {
InOrder inOrder = Mockito.inOrder(client, cache);
inOrder.verify(client).load(eq(ID), anyString(), anyBoolean(), anyInt(), anyInt());
- inOrder.verify(cache).get(ID, null);
+ inOrder.verify(cache).getString(ID);
}
@Test
public void test_put_cache() throws IOException {
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client);
- loader.load(ID);
+ loader.loadString(ID);
verify(cache).put(ID, serverValue.getBytes());
}
@@ -124,11 +156,11 @@ public class WSLoaderTest {
public void test_throw_cache_exception_fallback() throws IOException {
turnServerOffline();
- when(cache.get(ID, null)).thenThrow(new NullPointerException());
+ when(cache.getString(ID)).thenThrow(new NullPointerException());
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client);
try {
- loader.load(ID);
+ loader.loadString(ID);
fail("NPE expected");
} catch (NullPointerException e) {
assertUsedServer(1);
@@ -138,12 +170,12 @@ public class WSLoaderTest {
@Test
public void test_throw_cache_exception() throws IOException {
- when(cache.get(ID, null)).thenThrow(new IllegalStateException());
+ when(cache.getString(ID)).thenThrow(new IllegalStateException());
WSLoader loader = new WSLoader(LoadStrategy.CACHE_FIRST, cache, client);
try {
- loader.load(ID);
+ loader.loadString(ID);
fail("IllegalStateException expected");
} catch (IllegalStateException e) {
assertUsedServer(0);
@@ -161,7 +193,7 @@ public class WSLoaderTest {
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client);
try {
- loader.load(ID);
+ loader.loadString(ID);
fail("IllegalStateException expected");
} catch (IllegalStateException e) {
// cache should not be used
@@ -177,7 +209,7 @@ public class WSLoaderTest {
exception.expectMessage(Matchers.is("Server is not available"));
WSLoader loader = new WSLoader(LoadStrategy.SERVER_ONLY, cache, client);
- loader.load(ID);
+ loader.loadString(ID);
}
@Test
@@ -189,7 +221,7 @@ public class WSLoaderTest {
exception.expectMessage(Matchers.is("Server is not accessible and data is not cached"));
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client);
- loader.load(ID);
+ loader.loadString(ID);
}
@Test
@@ -200,13 +232,13 @@ public class WSLoaderTest {
exception.expectMessage(Matchers.is("Data is not cached"));
WSLoader loader = new WSLoader(LoadStrategy.CACHE_ONLY, cache, client);
- loader.load(ID);
+ loader.loadString(ID);
}
@Test
public void test_server_strategy() throws IOException {
WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client);
- assertResult(loader.load(ID), serverValue.getBytes(), false);
+ assertResult(loader.loadString(ID), serverValue, false);
// should not fetch from cache
verify(cache).put(ID, serverValue.getBytes());
@@ -217,7 +249,7 @@ public class WSLoaderTest {
public void test_server_only() throws IOException {
turnServerOffline();
WSLoader loader = new WSLoader(LoadStrategy.SERVER_ONLY, cache, client);
- loader.load(ID);
+ loader.loadString(ID);
}
@Test
@@ -227,14 +259,21 @@ public class WSLoaderTest {
}
private void assertUsedCache(int times) throws IOException {
- verify(cache, times(times)).get(ID, null);
+ verify(cache, times(times)).getString(ID);
}
private void assertUsedServer(int times) {
verify(client, times(times)).load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt());
}
- private <T> void assertResult(WSLoaderResult<T> result, T expected, boolean fromCache) {
+ private void assertResult(WSLoaderResult<InputStream> result, byte[] expected, boolean fromCache) throws IOException {
+ byte[] content = IOUtils.toByteArray(result.get());
+ assertThat(result).isNotNull();
+ assertThat(content).isEqualTo(expected);
+ assertThat(result.isFromCache()).isEqualTo(fromCache);
+ }
+
+ private void assertResult(WSLoaderResult<String> result, String expected, boolean fromCache) {
assertThat(result).isNotNull();
assertThat(result.get()).isEqualTo(expected);
assertThat(result.isFromCache()).isEqualTo(fromCache);
@@ -245,6 +284,6 @@ public class WSLoaderTest {
}
private void turnCacheEmpty() throws IOException {
- when(cache.get(ID, null)).thenReturn(null);
+ when(cache.getString(ID)).thenReturn(null);
}
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/BatchMediumTester.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/BatchMediumTester.java
index 9f3e1c4f9c2..9016442b345 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/BatchMediumTester.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/BatchMediumTester.java
@@ -76,25 +76,25 @@ public class BatchMediumTester {
private Batch batch;
private static Path workingDir = null;
private static Path globalWorkingDir = null;
-
+
private static void createWorkingDirs() throws IOException {
destroyWorkingDirs();
-
+
workingDir = java.nio.file.Files.createTempDirectory("mediumtest-working-dir");
globalWorkingDir = java.nio.file.Files.createTempDirectory("mediumtest-global-working-dir");
}
-
+
private static void destroyWorkingDirs() throws IOException {
- if(workingDir != null) {
+ if (workingDir != null) {
FileUtils.deleteDirectory(workingDir.toFile());
workingDir = null;
}
-
- if(globalWorkingDir != null) {
+
+ if (globalWorkingDir != null) {
FileUtils.deleteDirectory(globalWorkingDir.toFile());
globalWorkingDir = null;
}
-
+
}
public static BatchMediumTesterBuilder builder() {
@@ -103,7 +103,7 @@ public class BatchMediumTester {
} catch (IOException e) {
e.printStackTrace();
}
-
+
BatchMediumTesterBuilder builder = new BatchMediumTesterBuilder().registerCoreMetrics();
builder.bootstrapProperties.put(MEDIUM_TEST_ENABLED, "true");
builder.bootstrapProperties.put(ReportPublisher.KEEP_REPORT_PROP_KEY, "true");
@@ -432,15 +432,15 @@ public class BatchMediumTester {
private static class FakeProjectCacheStatus implements ProjectCacheStatus {
@Override
- public void save(String projectKey) {
+ public void save() {
}
@Override
- public void delete(String projectKey) {
+ public void delete() {
}
@Override
- public Date getSyncStatus(String projectKey) {
+ public Date getSyncStatus() {
return new Date();
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultServerIssuesLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultServerIssuesLoaderTest.java
index 07dab6b0ab5..0bcde52323b 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultServerIssuesLoaderTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultServerIssuesLoaderTest.java
@@ -20,9 +20,7 @@
package org.sonar.batch.repository;
import org.sonar.batch.cache.WSLoaderResult;
-
import org.sonar.batch.cache.WSLoader;
-import com.google.common.io.ByteSource;
import com.google.common.base.Function;
import org.junit.Before;
import org.junit.Test;
@@ -32,6 +30,7 @@ import org.sonar.batch.protocol.input.BatchInput.ServerIssue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
@@ -51,9 +50,6 @@ public class DefaultServerIssuesLoaderTest {
@Test
public void loadFromWs() throws Exception {
- ByteSource bs = mock(ByteSource.class);
- when(wsLoader.loadSource("/scanner/issues?key=foo")).thenReturn(new WSLoaderResult<>(bs, true));
-
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ServerIssue.newBuilder().setKey("ab1").build()
@@ -61,7 +57,8 @@ public class DefaultServerIssuesLoaderTest {
ServerIssue.newBuilder().setKey("ab2").build()
.writeDelimitedTo(bos);
- when(bs.openBufferedStream()).thenReturn(new ByteArrayInputStream(bos.toByteArray()));
+ InputStream is = new ByteArrayInputStream(bos.toByteArray());
+ when(wsLoader.loadStream("/scanner/issues?key=foo")).thenReturn(new WSLoaderResult<>(is, true));
final List<ServerIssue> result = new ArrayList<>();
loader.load("foo", new Function<BatchInput.ServerIssue, Void>() {
@@ -78,9 +75,15 @@ public class DefaultServerIssuesLoaderTest {
@Test(expected = IllegalStateException.class)
public void testError() throws IOException {
+<<<<<<< HEAD
ByteSource source = mock(ByteSource.class);
when(source.openBufferedStream()).thenThrow(IOException.class);
when(wsLoader.loadSource("/scanner/issues?key=foo")).thenReturn(new WSLoaderResult<ByteSource>(source, true));
+=======
+ InputStream is = mock(InputStream.class);
+ when(is.read()).thenThrow(IOException.class);
+ when(wsLoader.loadStream("/batch/issues?key=foo")).thenReturn(new WSLoaderResult<InputStream>(is, true));
+>>>>>>> SONAR-6777 Project cache sync
loader.load("foo", mock(Function.class));
}
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/repository/user/UserRepositoryLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/repository/user/UserRepositoryLoaderTest.java
index a449f57837d..16ee8a426f2 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/repository/user/UserRepositoryLoaderTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/repository/user/UserRepositoryLoaderTest.java
@@ -30,7 +30,6 @@ import com.google.common.collect.ImmutableMap;
import org.junit.rules.ExpectedException;
import org.junit.Rule;
import org.mockito.Mockito;
-import com.google.common.io.ByteSource;
import org.junit.Test;
import org.sonar.batch.protocol.input.BatchInput;
@@ -64,20 +63,20 @@ public class UserRepositoryLoaderTest {
public void testLoadEmptyList() {
assertThat(userRepo.load(Lists.<String>emptyList())).isEmpty();
}
-
+
@Test
public void testLoad() throws IOException {
Map<String, String> userMap = ImmutableMap.of("fmallet", "Freddy Mallet", "sbrandhof", "Simon");
- WSLoaderResult<ByteSource> res = new WSLoaderResult<>(createUsersMock(userMap), true);
- when(wsLoader.loadSource("/scanner/users?logins=fmallet,sbrandhof")).thenReturn(res);
+ WSLoaderResult<InputStream> res = new WSLoaderResult<>(createUsersMock(userMap), true);
+ when(wsLoader.loadStream("/scanner/users?logins=fmallet,sbrandhof")).thenReturn(res);
assertThat(userRepo.load(Arrays.asList("fmallet", "sbrandhof"))).extracting("login", "name").containsOnly(tuple("fmallet", "Freddy Mallet"), tuple("sbrandhof", "Simon"));
}
@Test
public void testFromCache() throws IOException {
- WSLoaderResult<ByteSource> res = new WSLoaderResult<>(createUsersMock(ImmutableMap.of("fmallet", "Freddy Mallet")), true);
- when(wsLoader.loadSource(anyString())).thenReturn(res);
+ WSLoaderResult<InputStream> res = new WSLoaderResult<>(createUsersMock(ImmutableMap.of("fmallet", "Freddy Mallet")), true);
+ when(wsLoader.loadStream(anyString())).thenReturn(res);
MutableBoolean fromCache = new MutableBoolean();
userRepo.load("", fromCache);
assertThat(fromCache.booleanValue()).isTrue();
@@ -89,35 +88,42 @@ public class UserRepositoryLoaderTest {
@Test
public void testLoadSingleUser() throws IOException {
+<<<<<<< HEAD
WSLoaderResult<ByteSource> res = new WSLoaderResult<>(createUsersMock(ImmutableMap.of("fmallet", "Freddy Mallet")), true);
when(wsLoader.loadSource("/scanner/users?logins=fmallet")).thenReturn(res);
+=======
+ WSLoaderResult<InputStream> res = new WSLoaderResult<>(createUsersMock(ImmutableMap.of("fmallet", "Freddy Mallet")), true);
+ when(wsLoader.loadStream("/batch/users?logins=fmallet")).thenReturn(res);
+>>>>>>> SONAR-6777 Project cache sync
assertThat(userRepo.load("fmallet").getName()).isEqualTo("Freddy Mallet");
}
- private ByteSource createUsersMock(Map<String, String> users) throws IOException {
+ private InputStream createUsersMock(Map<String, String> users) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (Map.Entry<String, String> user : users.entrySet()) {
BatchInput.User.Builder builder = BatchInput.User.newBuilder();
builder.setLogin(user.getKey()).setName(user.getValue()).build().writeDelimitedTo(out);
}
- ByteSource source = mock(ByteSource.class);
- when(source.openStream()).thenReturn(new ByteArrayInputStream(out.toByteArray()));
- return source;
+ return new ByteArrayInputStream(out.toByteArray());
}
@Test
public void testInputStreamError() throws IOException {
+<<<<<<< HEAD
ByteSource source = mock(ByteSource.class);
WSLoaderResult<ByteSource> res = new WSLoaderResult<>(source, true);
when(wsLoader.loadSource("/scanner/users?logins=fmallet,sbrandhof")).thenReturn(res);
+=======
+ InputStream is = mock(InputStream.class);
+ Mockito.doThrow(IOException.class).when(is).read();
+ WSLoaderResult<InputStream> res = new WSLoaderResult<>(is, true);
+>>>>>>> SONAR-6777 Project cache sync
- InputStream errorInputStream = mock(InputStream.class);
- Mockito.doThrow(IOException.class).when(errorInputStream).read();
- when(source.openStream()).thenReturn(errorInputStream);
+ when(wsLoader.loadStream("/batch/users?logins=fmallet,sbrandhof")).thenReturn(res);
exception.expect(IllegalStateException.class);
exception.expectMessage("Unable to get user details from server");
diff --git a/sonar-batch/src/test/java/org/sonar/batch/rule/DefaultRulesLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/rule/DefaultRulesLoaderTest.java
index 1c593a51361..8c4989509d2 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/rule/DefaultRulesLoaderTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/rule/DefaultRulesLoaderTest.java
@@ -21,9 +21,7 @@ package org.sonar.batch.rule;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-
import org.junit.rules.ExpectedException;
-
import org.sonar.batch.cache.WSLoaderResult;
import org.sonar.batch.cache.WSLoader;
import org.apache.commons.lang.mutable.MutableBoolean;
@@ -32,6 +30,7 @@ import com.google.common.io.ByteSource;
import com.google.common.io.Resources;
import java.io.IOException;
+import java.io.InputStream;
import java.util.List;
import static org.mockito.Matchers.anyString;
@@ -45,18 +44,18 @@ public class DefaultRulesLoaderTest {
@Test
public void testParseServerResponse() throws IOException {
WSLoader wsLoader = mock(WSLoader.class);
- ByteSource source = Resources.asByteSource(this.getClass().getResource("DefaultRulesLoader/response.protobuf"));
- when(wsLoader.loadSource(anyString())).thenReturn(new WSLoaderResult<>(source, true));
+ InputStream is = Resources.asByteSource(this.getClass().getResource("DefaultRulesLoader/response.protobuf")).openBufferedStream();
+ when(wsLoader.loadStream(anyString())).thenReturn(new WSLoaderResult<>(is, true));
DefaultRulesLoader loader = new DefaultRulesLoader(wsLoader);
List<Rule> ruleList = loader.load(null);
assertThat(ruleList).hasSize(318);
}
@Test
- public void testLoadedFromCache() {
+ public void testLoadedFromCache() throws IOException {
WSLoader wsLoader = mock(WSLoader.class);
- ByteSource source = Resources.asByteSource(this.getClass().getResource("DefaultRulesLoader/response.protobuf"));
- when(wsLoader.loadSource(anyString())).thenReturn(new WSLoaderResult<>(source, true));
+ InputStream is = Resources.asByteSource(this.getClass().getResource("DefaultRulesLoader/response.protobuf")).openBufferedStream();
+ when(wsLoader.loadStream(anyString())).thenReturn(new WSLoaderResult<>(is, true));
DefaultRulesLoader loader = new DefaultRulesLoader(wsLoader);
MutableBoolean fromCache = new MutableBoolean();
loader.load(fromCache);
@@ -65,10 +64,10 @@ public class DefaultRulesLoaderTest {
}
@Test
- public void testError() {
+ public void testError() throws IOException {
WSLoader wsLoader = mock(WSLoader.class);
- ByteSource source = ByteSource.wrap(new String("trash").getBytes());
- when(wsLoader.loadSource(anyString())).thenReturn(new WSLoaderResult<>(source, true));
+ InputStream is = ByteSource.wrap(new String("trash").getBytes()).openBufferedStream();
+ when(wsLoader.loadStream(anyString())).thenReturn(new WSLoaderResult<>(is, true));
DefaultRulesLoader loader = new DefaultRulesLoader(wsLoader);
exception.expect(IllegalStateException.class);
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectLockTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectLockTest.java
index 91bcb4162b5..bf432556235 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectLockTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectLockTest.java
@@ -89,7 +89,7 @@ public class ProjectLockTest {
public void errorLock() {
lock = setUpTest(Paths.get("path", "that", "wont", "exist", "ever").toFile());
exception.expect(IllegalStateException.class);
- exception.expectMessage("Failed to create project lock in");
+ exception.expectMessage("Failed to create lock in");
lock.tryLock();
}
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/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();
}
}
diff --git a/sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheLoader.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectKey.java
index ee906b4b67d..8238f33837f 100644
--- a/sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheLoader.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectKey.java
@@ -17,10 +17,8 @@
* 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;
+package org.sonar.api.batch.bootstrap;
-import java.io.IOException;
-
-public interface PersistentCacheLoader<T> {
- T get() throws IOException;
+public interface ProjectKey {
+ String get();
}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectReactor.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectReactor.java
index 81e2dbe3267..ddb09d932ff 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectReactor.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectReactor.java
@@ -29,7 +29,7 @@ import java.util.List;
* @since 2.9
*/
@BatchSide
-public class ProjectReactor {
+public class ProjectReactor implements ProjectKey {
private ProjectDefinition root;
@@ -67,4 +67,12 @@ public class ProjectReactor {
}
return null;
}
+
+ @Override
+ public String get() {
+ if (root != null) {
+ return root.getKeyWithBranch();
+ }
+ return null;
+ }
}