From a9203f4791002a127872f6e09a4f20de44502aa8 Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Fri, 1 Feb 2013 14:34:15 +0100 Subject: [PATCH] SONAR-2291 Implement cache for JDBC driver --- .../batch/bootstrap/JdbcDriverHolder.java | 57 ++++++++++++++++--- .../batch/bootstrap/JdbcDriverHolderTest.java | 57 ++++++++++++++++--- .../platform/DefaultServerFileSystem.java | 4 +- .../server/startup/JdbcDriverDeployer.java | 21 ++++++- .../startup/JdbcDriverDeployerTest.java | 22 ++++--- 5 files changed, 136 insertions(+), 25 deletions(-) diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcDriverHolder.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcDriverHolder.java index 80b4f8e1e43..06b36e2b9c6 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcDriverHolder.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcDriverHolder.java @@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory; import org.sonar.api.CoreProperties; import org.sonar.api.config.Settings; import org.sonar.api.utils.SonarException; +import org.sonar.batch.cache.SonarCache; import java.io.File; import java.io.IOException; @@ -41,28 +42,57 @@ public class JdbcDriverHolder { private static final Logger LOG = LoggerFactory.getLogger(JdbcDriverHolder.class); - private TempDirectories tempDirectories; private ServerClient serverClient; private Settings settings; + private BatchSonarCache batchCache; // initialized in start() private JdbcDriverClassLoader classLoader = null; - public JdbcDriverHolder(Settings settings, TempDirectories tempDirectories, ServerClient serverClient) { - this.tempDirectories = tempDirectories; + public JdbcDriverHolder(BatchSonarCache batchCache, Settings settings, ServerClient serverClient) { this.serverClient = serverClient; this.settings = settings; + this.batchCache = batchCache; } public void start() { if (!settings.getBoolean(CoreProperties.DRY_RUN)) { - LOG.info("Install JDBC driver"); - File jdbcDriver = new File(tempDirectories.getRoot(), "jdbc-driver.jar"); - serverClient.download("/deploy/jdbc-driver.jar", jdbcDriver); - classLoader = initClassloader(jdbcDriver); + try { + LOG.info("Install JDBC driver"); + String[] nameAndMd5 = downloadJdbcDriverIndex(); + String filename = nameAndMd5[0]; + String remoteMd5 = nameAndMd5[1]; + File driverInCache = getSonarCache().getFileFromCache(filename, remoteMd5); + if (driverInCache == null) { + File tmpDownloadFile = getSonarCache().getTemporaryFile(); + String url = "/deploy/" + filename; + if (LOG.isDebugEnabled()) { + LOG.debug("Downloading {} to {}", url, tmpDownloadFile.getAbsolutePath()); + } + else { + LOG.info("Downloading {}", filename); + } + serverClient.download(url, tmpDownloadFile); + String md5 = getSonarCache().cacheFile(tmpDownloadFile, filename); + driverInCache = getSonarCache().getFileFromCache(filename, md5); + if (!md5.equals(remoteMd5)) { + throw new SonarException("INVALID CHECKSUM: File " + driverInCache.getAbsolutePath() + " was expected to have checksum " + remoteMd5 + + " but was downloaded with checksum " + md5); + } + } + classLoader = initClassloader(driverInCache); + } catch (SonarException e) { + throw e; + } catch (Exception e) { + throw new SonarException("Fail to install JDBC driver", e); + } } } + private SonarCache getSonarCache() { + return batchCache.getCache(); + } + @VisibleForTesting JdbcDriverClassLoader getClassLoader() { return classLoader; @@ -115,10 +145,21 @@ public class JdbcDriverHolder { } } + private String[] downloadJdbcDriverIndex() { + String url = "/deploy/jdbc-driver.txt"; + try { + LOG.debug("Downloading index of jdbc-driver"); + String indexContent = serverClient.request(url); + return indexContent.split("\\|"); + } catch (Exception e) { + throw new SonarException("Fail to download jdbc-driver index: " + url, e); + } + } + static class JdbcDriverClassLoader extends URLClassLoader { public JdbcDriverClassLoader(URL jdbcDriver, ClassLoader parent) { - super(new URL[]{jdbcDriver}, parent); + super(new URL[] {jdbcDriver}, parent); } public void clearReferencesJdbc() { diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/JdbcDriverHolderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/JdbcDriverHolderTest.java index 8f70ea1e3bd..0047141fb2d 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/JdbcDriverHolderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/JdbcDriverHolderTest.java @@ -21,20 +21,31 @@ package org.sonar.batch.bootstrap; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.mockito.Mockito; import org.sonar.api.CoreProperties; import org.sonar.api.config.Settings; +import org.sonar.api.utils.SonarException; +import org.sonar.batch.cache.SonarCache; import java.io.File; import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; public class JdbcDriverHolderTest { + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + ClassLoader initialThreadClassloader; @Before @@ -49,18 +60,26 @@ public class JdbcDriverHolderTest { @Test public void should_extend_classloader_with_jdbc_driver() throws Exception { + SonarCache cache = mock(SonarCache.class); + BatchSonarCache batchCache = mock(BatchSonarCache.class); + when(batchCache.getCache()).thenReturn(cache); + + File fakeDriver = new File(getClass().getResource("/org/sonar/batch/bootstrap/JdbcDriverHolderTest/jdbc-driver.jar").toURI()); + when(cache.cacheFile(Mockito.any(File.class), Mockito.anyString())).thenReturn("fakemd5"); + when(cache.getFileFromCache(Mockito.anyString(), Mockito.anyString())) + .thenReturn(null) + .thenReturn(fakeDriver); + /* jdbc-driver.jar has just one file /foo/foo.txt */ assertThat(Thread.currentThread().getContextClassLoader().getResource("foo/foo.txt")).isNull(); - File fakeDriver = new File(getClass().getResource("/org/sonar/batch/bootstrap/JdbcDriverHolderTest/jdbc-driver.jar").toURI()); - TempDirectories tempDirectories = mock(TempDirectories.class); - when(tempDirectories.getRoot()).thenReturn(fakeDriver.getParentFile()); ServerClient server = mock(ServerClient.class); + when(server.request("/deploy/jdbc-driver.txt")).thenReturn("ojdbc14.jar|fakemd5"); + when(server.request("/deploy/ojdbc14.jar")).thenReturn("fakecontent"); - JdbcDriverHolder holder = new JdbcDriverHolder(new Settings(), tempDirectories, server); + JdbcDriverHolder holder = new JdbcDriverHolder(batchCache, new Settings(), server); holder.start(); - verify(server).download("/deploy/jdbc-driver.jar", fakeDriver); assertThat(holder.getClassLoader().getResource("foo/foo.txt")).isNotNull(); assertThat(Thread.currentThread().getContextClassLoader()).isSameAs(holder.getClassLoader()); assertThat(holder.getClassLoader().getParent()).isSameAs(getClass().getClassLoader()); @@ -70,11 +89,35 @@ public class JdbcDriverHolderTest { assertThat(holder.getClassLoader()).isNull(); } + @Test + public void should_fail_if_checksum_mismatch() throws Exception { + SonarCache cache = mock(SonarCache.class); + BatchSonarCache batchCache = mock(BatchSonarCache.class); + when(batchCache.getCache()).thenReturn(cache); + + File fakeDriver = new File(getClass().getResource("/org/sonar/batch/bootstrap/JdbcDriverHolderTest/jdbc-driver.jar").toURI()); + when(cache.cacheFile(Mockito.any(File.class), Mockito.anyString())).thenReturn("anotherfakemd5"); + when(cache.getFileFromCache(Mockito.anyString(), Mockito.anyString())) + .thenReturn(null) + .thenReturn(fakeDriver); + + ServerClient server = mock(ServerClient.class); + when(server.request("/deploy/jdbc-driver.txt")).thenReturn("ojdbc14.jar|fakemd5"); + when(server.request("/deploy/ojdbc14.jar")).thenReturn("fakecontent"); + + JdbcDriverHolder holder = new JdbcDriverHolder(batchCache, new Settings(), server); + + thrown.expect(SonarException.class); + thrown.expectMessage("INVALID CHECKSUM"); + + holder.start(); + } + @Test public void should_be_disabled_if_dry_run() { Settings settings = new Settings().setProperty(CoreProperties.DRY_RUN, true); ServerClient server = mock(ServerClient.class); - JdbcDriverHolder holder = new JdbcDriverHolder(settings, mock(TempDirectories.class), server); + JdbcDriverHolder holder = new JdbcDriverHolder(new BatchSonarCache(new Settings()), settings, server); holder.start(); diff --git a/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java b/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java index faf605a190f..53d17dc6960 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java @@ -111,8 +111,8 @@ public class DefaultServerFileSystem implements ServerFileSystem { return deployDir; } - public File getDeployedJdbcDriver() { - return new File(deployDir, "jdbc-driver.jar"); + public File getDeployedJdbcDriverIndex() { + return new File(deployDir, "jdbc-driver.txt"); } public File getDeployedPluginsDir() { diff --git a/sonar-server/src/main/java/org/sonar/server/startup/JdbcDriverDeployer.java b/sonar-server/src/main/java/org/sonar/server/startup/JdbcDriverDeployer.java index 8bf17a700bb..1e1a8a8b52b 100644 --- a/sonar-server/src/main/java/org/sonar/server/startup/JdbcDriverDeployer.java +++ b/sonar-server/src/main/java/org/sonar/server/startup/JdbcDriverDeployer.java @@ -19,14 +19,21 @@ */ package org.sonar.server.startup; +import com.google.common.io.Closeables; +import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.sonar.server.platform.DefaultServerFileSystem; import java.io.File; import java.io.IOException; +import java.io.InputStream; public class JdbcDriverDeployer { + private static final Logger LOG = LoggerFactory.getLogger(JdbcDriverDeployer.class); + private final DefaultServerFileSystem fileSystem; public JdbcDriverDeployer(DefaultServerFileSystem fileSystem) { @@ -35,7 +42,7 @@ public class JdbcDriverDeployer { public void start() { File driver = fileSystem.getJdbcDriver(); - File deployedDriver = fileSystem.getDeployedJdbcDriver(); + File deployedDriver = new File(fileSystem.getDeployDir(), driver.getName()); if (deployedDriver == null || !deployedDriver.exists() || deployedDriver.length() != driver.length()) { try { FileUtils.copyFile(driver, deployedDriver); @@ -44,5 +51,17 @@ public class JdbcDriverDeployer { throw new RuntimeException("Can not copy the JDBC driver from " + driver + " to " + deployedDriver, e); } } + File deployedDriverIndex = fileSystem.getDeployedJdbcDriverIndex(); + // Compute MD5 + InputStream is = null; + try { + is = FileUtils.openInputStream(deployedDriver); + String md5 = DigestUtils.md5Hex(is); + FileUtils.writeStringToFile(deployedDriverIndex, deployedDriver.getName() + "|" + md5); + } catch (IOException e) { + throw new RuntimeException("Can not generate index of JDBC driver", e); + } finally { + Closeables.closeQuietly(is); + } } } diff --git a/sonar-server/src/test/java/org/sonar/server/startup/JdbcDriverDeployerTest.java b/sonar-server/src/test/java/org/sonar/server/startup/JdbcDriverDeployerTest.java index bdf11002336..4cac7112870 100644 --- a/sonar-server/src/test/java/org/sonar/server/startup/JdbcDriverDeployerTest.java +++ b/sonar-server/src/test/java/org/sonar/server/startup/JdbcDriverDeployerTest.java @@ -19,12 +19,15 @@ */ package org.sonar.server.startup; +import org.apache.commons.io.FileUtils; import org.junit.Test; import org.sonar.server.platform.DefaultServerFileSystem; import org.sonar.test.TestUtils; import java.io.File; +import java.io.IOException; +import static org.fest.assertions.Assertions.assertThat; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; @@ -33,20 +36,25 @@ import static org.mockito.Mockito.when; public class JdbcDriverDeployerTest { @Test - public void testDeploy() { + public void testDeploy() throws IOException { DefaultServerFileSystem fs = mock(DefaultServerFileSystem.class); File initialDriver = TestUtils.getResource(getClass(), "deploy/my-driver.jar"); when(fs.getJdbcDriver()).thenReturn(initialDriver); - - File deployed = new File(TestUtils.getTestTempDir(getClass(), "deploy", true), "copy.jar"); - assertThat(deployed.exists(), is(false)); - when(fs.getDeployedJdbcDriver()).thenReturn(deployed); + File deployDir = TestUtils.getTestTempDir(getClass(), "deploy", true); + when(fs.getDeployDir()).thenReturn(deployDir); + File deployedIndex = new File(deployDir, "jdbc-driver.txt"); + File deployedFile = new File(deployDir, "my-driver.jar"); + assertThat(deployedIndex).doesNotExist(); + assertThat(deployedFile).doesNotExist(); + when(fs.getDeployedJdbcDriverIndex()).thenReturn(deployedIndex); JdbcDriverDeployer deployer = new JdbcDriverDeployer(fs); deployer.start(); - assertThat(deployed.exists(), is(true)); - assertThat(deployed.length(), is(initialDriver.length())); + assertThat(deployedIndex).exists(); + assertThat(deployedFile).exists(); + assertThat(deployedFile.length(), is(initialDriver.length())); + assertThat(FileUtils.readFileToString(deployedIndex)).isEqualTo("my-driver.jar|02b97f7bc37b2b68fc847fcc3fc1c156"); } } -- 2.39.5