]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6777 Project cache sync
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Fri, 18 Sep 2015 09:43:24 +0000 (11:43 +0200)
committerDuarte Meneses <duarte.meneses@sonarsource.com>
Wed, 30 Sep 2015 14:27:12 +0000 (16:27 +0200)
45 files changed:
it/it-tests/src/test/java/batch/IssuesModeTest.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java
sonar-batch/src/main/java/org/sonar/batch/cache/DefaultProjectCacheStatus.java
sonar-batch/src/main/java/org/sonar/batch/cache/GlobalPersistentCacheProvider.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/cache/NonAssociatedCacheSynchronizer.java
sonar-batch/src/main/java/org/sonar/batch/cache/PersistentCacheProvider.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/cache/ProjectCacheStatus.java
sonar-batch/src/main/java/org/sonar/batch/cache/ProjectCacheSynchronizer.java
sonar-batch/src/main/java/org/sonar/batch/cache/ProjectKeySupplier.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/cache/ProjectPersistentCacheProvider.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/cache/ProjectSyncContainer.java
sonar-batch/src/main/java/org/sonar/batch/cache/WSLoader.java
sonar-batch/src/main/java/org/sonar/batch/repository/DefaultServerIssuesLoader.java
sonar-batch/src/main/java/org/sonar/batch/repository/user/UserRepositoryLoader.java
sonar-batch/src/main/java/org/sonar/batch/rule/DefaultRulesLoader.java
sonar-batch/src/main/java/org/sonar/batch/scan/ProjectLock.java
sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
sonar-batch/src/main/java/org/sonar/batch/util/BatchUtils.java
sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java
sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java
sonar-batch/src/test/java/org/sonar/batch/cache/DefaultProjectCacheStatusTest.java
sonar-batch/src/test/java/org/sonar/batch/cache/GlobalPersistentCacheProviderTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/cache/NonAssociatedCacheSynchronizerTest.java
sonar-batch/src/test/java/org/sonar/batch/cache/PersistentCacheProviderTest.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/cache/ProjectCacheSynchronizerTest.java
sonar-batch/src/test/java/org/sonar/batch/cache/ProjectPersistentCacheProviderTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java
sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderTest.java
sonar-batch/src/test/java/org/sonar/batch/mediumtest/BatchMediumTester.java
sonar-batch/src/test/java/org/sonar/batch/repository/DefaultServerIssuesLoaderTest.java
sonar-batch/src/test/java/org/sonar/batch/repository/user/UserRepositoryLoaderTest.java
sonar-batch/src/test/java/org/sonar/batch/rule/DefaultRulesLoaderTest.java
sonar-batch/src/test/java/org/sonar/batch/scan/ProjectLockTest.java
sonar-home/src/main/java/org/sonar/home/cache/DeleteFileOnCloseInputStream.java [new file with mode: 0644]
sonar-home/src/main/java/org/sonar/home/cache/DirectoryLock.java [new file with mode: 0644]
sonar-home/src/main/java/org/sonar/home/cache/PersistentCache.java
sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheBuilder.java
sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheLoader.java [deleted file]
sonar-home/src/main/java/org/sonar/home/cache/UnlockOnCloseInputStream.java [new file with mode: 0644]
sonar-home/src/test/java/org/sonar/home/cache/DirectoryLockTest.java [new file with mode: 0644]
sonar-home/src/test/java/org/sonar/home/cache/PersistentCacheBuilderTest.java
sonar-home/src/test/java/org/sonar/home/cache/PersistentCacheTest.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectKey.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectReactor.java

index 0935af6c35f0a3ff0a2845f61b09443699042baf..7ff66112e3cb21d0b06b245123581a5081e6f7fb 100644 (file)
@@ -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);
   }
index 2a802f6be1be10421aeadf6fbbb6fd6f757d5d36..30b5479963233e2cb97858371b5ca555a63bd3f8 100644 (file)
@@ -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);
index 901e2d2cac22cb628fce3c73397b33db63bed9e9..79a6257a504572944ab08569532c123d2b59ca87 100644 (file)
@@ -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);
index 248d2b8b35065710290ee1685da9f62df8be43fa..3ecf425d11a6c13a1294fb3213aa2d9184879ddb 100644 (file)
  */
 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/GlobalPersistentCacheProvider.java b/sonar-batch/src/main/java/org/sonar/batch/cache/GlobalPersistentCacheProvider.java
new file mode 100644 (file)
index 0000000..29e2f42
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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.apache.commons.lang.StringUtils;
+import org.sonar.batch.bootstrap.Slf4jLogger;
+import org.sonar.batch.util.BatchUtils;
+import org.sonar.home.cache.PersistentCacheBuilder;
+
+import java.nio.file.Paths;
+
+import org.sonar.batch.bootstrap.GlobalProperties;
+import org.sonar.home.cache.PersistentCache;
+import org.picocontainer.injectors.ProviderAdapter;
+
+public class GlobalPersistentCacheProvider extends ProviderAdapter {
+  private PersistentCache cache;
+
+  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.setAreaForGlobal(serverUrl, BatchUtils.getServerVersion());
+      cache = builder.build();
+    }
+
+    return cache;
+  }
+
+  private String getServerUrl(GlobalProperties props) {
+    return StringUtils.removeEnd(StringUtils.defaultIfBlank(props.property("sonar.host.url"), "http://localhost:9000"), "/");
+  }
+}
index e21d14206947413462c124d5dcf1c5734c140350..44743f5c12088fb6b8e5f199565d9293652ff131 100644 (file)
@@ -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/PersistentCacheProvider.java b/sonar-batch/src/main/java/org/sonar/batch/cache/PersistentCacheProvider.java
deleted file mode 100644 (file)
index 99040ba..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.batch.cache;
-
-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 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.home.cache.PersistentCache;
-import org.sonar.home.cache.PersistentCacheBuilder;
-
-public class PersistentCacheProvider extends ProviderAdapter {
-  private static final Logger LOG = Loggers.get(PersistentCacheProvider.class);
-  private PersistentCache cache;
-
-  public PersistentCache provide(UserProperties props) {
-    if (cache == null) {
-      PersistentCacheBuilder builder = new PersistentCacheBuilder(new Slf4jLogger());
-
-      String home = props.property("sonar.userHome");
-      if (home != null) {
-        builder.setSonarHome(Paths.get(home));
-      }
-
-      builder.setVersion(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;
-    }
-  }
-}
index a5f97349a4b0903e15f42a5a41fc3bc8f9c31e51..39947c64e3b2a917cc47755c10b09129832de1c3 100644 (file)
  */
 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();
 }
index d2d6cbcf2dbc6c2c43e7a0d697428eb9b074876d..20d16fbaaad8b2927ee0cb742e64ceefd884f600 100644 (file)
@@ -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 (file)
index 0000000..78afcee
--- /dev/null
@@ -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 (file)
index 0000000..49684fa
--- /dev/null
@@ -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"), "/");
+  }
+}
index 600a6791c3adf6c0484ade7c16123f484dc3ee15..79be9383f107c914c7b8667dbe95bd4ed35b9b1a 100644 (file)
@@ -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);
index 06b38f7b607917afaa373eb451d236d44ea15d8a..96a83811d080f8f38e8ab0e663e3401a0bf20fae 100644 (file)
  */
 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;
 
index aefe0569275a7672ce77e674cc6857b41e86c11f..0989cd5e6161ea79475335fb4ba2f85f4d99f8e5 100644 (file)
  */
 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);
     }
   }
 }
index d40f918f87b501550e2274ac071fbd6a70f3ed6b..a7b9dc71d4ae3d065d2e4b5a52e1e93e032901e3 100644 (file)
@@ -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;
index b427cfed871940bace679020858ab4b781dd711d..315106a6179dae1c9c2b99767233bf0b3855e3ac 100644 (file)
  */
 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);
     }
   }
 
index 6266bf44e6c6a9bf0b691d48cfc73cdb8f1d3bd0..ff42abf956d55e868986b1e2e95a481a6f92bb79 100644 (file)
  */
 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
   }
-
 }
index 431b916ebff3b595937a501e5f069b2cfc5fe4bb..5c7bbb9e0265f72e72db7ce156014c52c2a38f4b 100644 (file)
@@ -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(),
index 42d53af38cc6d1aa9fe8f8f1d6c0209718c525de..aa5fbc35ae0ea0876deb4d1a44d78fac65660743 100644 (file)
 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;
+    }
+  }
 }
index 77cd2311311d8654ae576d7a26c08f43dc446385..9e80507094bfbb7eeed165cc7bad1f81c2c266a7 100644 (file)
@@ -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);
   }
 }
index c663dbb3ef40f61b57394b8bfd95cf33d9475e38..654616811d594b4831ce9d6e1a138630384bdfa0 100644 (file)
@@ -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();
   }
index 4239ca1c024edad6a0a084df3e55c98e07feb175..8d86733a01d81a16fd9b04defa0065931497fe3a 100644 (file)
@@ -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/GlobalPersistentCacheProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/cache/GlobalPersistentCacheProviderTest.java
new file mode 100644 (file)
index 0000000..fe0513b
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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.batch.util.BatchUtils;
+
+import org.sonar.home.cache.PersistentCache;
+
+import java.util.HashMap;
+
+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 GlobalPersistentCacheProviderTest {
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+  
+  private GlobalPersistentCacheProvider provider;
+  private GlobalProperties globalProperties;
+  
+  @Before
+  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_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"));
+  }
+}
index d7b3bbd6710acd8ea53b692a9f210bad96409e37..3d58f3d3daa4a39f1049d7318df5ab8364c30e95 100644 (file)
@@ -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/PersistentCacheProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/cache/PersistentCacheProviderTest.java
deleted file mode 100644 (file)
index 3f9c4af..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.batch.cache;
-
-import org.junit.Rule;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.batch.bootstrap.GlobalProperties;
-import org.sonar.batch.cache.PersistentCacheProvider;
-
-import java.io.File;
-import java.util.Collections;
-
-import org.junit.Before;
-import static org.assertj.core.api.Assertions.assertThat;
-import org.junit.Test;
-
-public class PersistentCacheProviderTest {
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-  
-  private PersistentCacheProvider provider = null;
-  private GlobalProperties props = null;
-
-  @Before
-  public void prepare() {
-    props = new GlobalProperties(Collections.<String, String>emptyMap());
-    provider = new PersistentCacheProvider();
-  }
-
-  @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"));
-  }
-}
index a4dade54f2d8d3db1378c6b8f7947cbdf6858c21..d91f154ee79de814a2fea42b9f9a5a0773398d98 100644 (file)
  */
 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 (file)
index 0000000..75e5b65
--- /dev/null
@@ -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);
+  }
+}
index 6ad5591a6d477ec915ee9a3cfc66a9f2996989d5..a7c613056bf515521b22ce7769a8701fda57cd41 100644 (file)
@@ -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
index fa812fb4988e594cfcf5ebb9c2065bbc2f9cce82..db9fe132fa9651a4427196ec0302d1881707bdc0 100644 (file)
@@ -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 {
@@ -89,15 +90,46 @@ public class WSLoaderTest {
     assertUsedCache(2);
   }
 
+  @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);
   }
 }
index 9f3e1c4f9c236e306be9914c0fdd7a4d4abc0cad..9016442b3458f6a3970d8c19dd487d345563d747 100644 (file)
@@ -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();
     }
 
index 07dab6b0ab5ffb9672c7b0c38ad1bbd080c78b34..0bcde52323b60550c601266edc58b940d7741881 100644 (file)
@@ -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));
   }
 }
index a449f57837dcb446543c3d55ca6dd58e008f166f..16ee8a426f2191e20df93cc09c23c0b324498a16 100644 (file)
@@ -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");
index 1c593a51361126c752e8f0fa03ae38930c422492..8c4989509d2aec2e551890bade68e228930743c0 100644 (file)
@@ -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);
index 91bcb4162b5bb9b54c21d2c6152f0029beb85b7d..bf432556235285f6ad3950065ef8ea7baec1a221 100644 (file)
@@ -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 (file)
index 0000000..7f4a307
--- /dev/null
@@ -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 (file)
index 0000000..4f14d21
--- /dev/null
@@ -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());
+    }
+  }
+}
index 3122d9a6fd00150ee4c49def33e9047e67679874..35dfa4abfcc1086dd3dcecfa5d423b2362128655 100644 (file)
@@ -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) {
index 055d2615ed6960077d80a8365e6396926645c60b..2097832853b902872306c97a53f3544f99a6e265 100644 (file)
  */
 package org.sonar.home.cache;
 
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.concurrent.TimeUnit;
+
 import javax.annotation.Nullable;
 
+/**
+ * Cache files will be placed in 3 areas:
+ * <pre>
+ *   &lt;sonar_home&gt;/ws_cache/&lt;server_url&gt;-&lt;version&gt;/projects/&lt;project&gt;/
+ *   &lt;sonar_home&gt;/ws_cache/&lt;server_url&gt;-&lt;version&gt;/global/
+ *   &lt;sonar_home&gt;/ws_cache/&lt;server_url&gt;-&lt;version&gt;/local/
+ * </pre>
+ */
 public class PersistentCacheBuilder {
   private static final long DEFAULT_EXPIRE_DURATION = TimeUnit.MILLISECONDS.convert(1L, TimeUnit.DAYS);
   private static final String DIR_NAME = "ws_cache";
 
-  private Path cachePath;
+  private Path cacheBasePath;
+  private Path relativePath;
   private final Logger logger;
-  private String version;
 
   public PersistentCacheBuilder(Logger logger) {
     this.logger = logger;
   }
 
-  public PersistentCache build() {
-    if (cachePath == null) {
-      setSonarHome(findHome());
-    }
-
-    return new PersistentCache(cachePath, DEFAULT_EXPIRE_DURATION, logger, version);
+  public PersistentCacheBuilder setAreaForProject(String serverUrl, String serverVersion, String projectKey) {
+    relativePath = Paths.get(sanitizeFilename(serverUrl + "-" + serverVersion))
+      .resolve("projects")
+      .resolve(sanitizeFilename(projectKey));
+    return this;
   }
-
-  public PersistentCacheBuilder setVersion(String version) {
-    this.version = version;
+  
+  public PersistentCacheBuilder setAreaForGlobal(String serverUrl, String serverVersion) {
+    relativePath = Paths.get(sanitizeFilename(serverUrl + "-" + serverVersion))
+      .resolve("global");
     return this;
   }
-
+  
+  public PersistentCacheBuilder setAreaForLocalProject(String serverUrl, String serverVersion) {
+    relativePath = Paths.get(sanitizeFilename(serverUrl + "-" + serverVersion))
+      .resolve("local");
+    return this;
+  }
+  
   public PersistentCacheBuilder setSonarHome(@Nullable Path p) {
     if (p != null) {
-      this.cachePath = p.resolve(DIR_NAME);
+      this.cacheBasePath = p.resolve(DIR_NAME);
     }
     return this;
   }
 
+  public PersistentCache build() {
+    if(relativePath == null) {
+      throw new IllegalStateException("area must be set before building");
+    }
+    if (cacheBasePath == null) {
+      setSonarHome(findHome());
+    }
+    Path cachePath = cacheBasePath.resolve(relativePath);
+    DirectoryLock lock = new DirectoryLock(cacheBasePath, logger);
+    return new PersistentCache(cachePath, DEFAULT_EXPIRE_DURATION, logger, lock);
+  }
+
   private static Path findHome() {
     String home = System.getenv("SONAR_USER_HOME");
 
@@ -66,4 +96,12 @@ public class PersistentCacheBuilder {
     home = System.getProperty("user.home");
     return Paths.get(home, ".sonar");
   }
+
+  private String sanitizeFilename(String name) {
+    try {
+      return URLEncoder.encode(name, StandardCharsets.UTF_8.name());
+    } catch (UnsupportedEncodingException e) {
+      throw new IllegalStateException("Couldn't sanitize filename: " + name, e);
+    }
+  }
 }
diff --git a/sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheLoader.java b/sonar-home/src/main/java/org/sonar/home/cache/PersistentCacheLoader.java
deleted file mode 100644 (file)
index ee906b4..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.home.cache;
-
-import java.io.IOException;
-
-public interface PersistentCacheLoader<T> {
-  T get() throws IOException;
-}
diff --git a/sonar-home/src/main/java/org/sonar/home/cache/UnlockOnCloseInputStream.java b/sonar-home/src/main/java/org/sonar/home/cache/UnlockOnCloseInputStream.java
new file mode 100644 (file)
index 0000000..fdfac82
--- /dev/null
@@ -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 (file)
index 0000000..fc1d994
--- /dev/null
@@ -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();
+  }
+}
index 78efe6966950a8fc5cd0d2e4864f1e22f6d53887..7a57e98d68ca5aebfe6c325af4f1c910e47ec061 100644 (file)
 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"));
   }
 }
index 9760b0966769c6273e546f287c0d4251073a80da..77ee30908e105e035fef324f388df350860302b3 100644 (file)
 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
@@ -49,60 +56,44 @@ public class PersistentCacheTest {
     assertCacheHit(false);
   }
 
-  @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-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectKey.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectKey.java
new file mode 100644 (file)
index 0000000..8238f33
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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.api.batch.bootstrap;
+
+public interface ProjectKey {
+  String get();
+}
index 81e2dbe326723ea652795055a1d9b037af5d8601..ddb09d932ff35c655ee1937137459e05d91b8e93 100644 (file)
@@ -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;
+  }
 }