From: Simon Brandhof Date: Sat, 14 Jul 2012 17:32:45 +0000 (+0200) Subject: Update Center improvements X-Git-Tag: 3.2~83 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=86706f9bae9c427f729484cd98b425cd25ae653e;p=sonarqube.git Update Center improvements * SONAR-3661 API: new component org.sonar.api.utils.UriReader * SONAR-3660 The property sonar.updatecenter.url must support local files * SONAR-3659 Availability of Update Center with non-RELEASE versions of Sonar * SONAR-2008 Enable updates from SNAPSHOT versions for plugins --- diff --git a/pom.xml b/pom.xml index 3ead717e790..af072cd328a 100644 --- a/pom.xml +++ b/pom.xml @@ -596,7 +596,7 @@ org.codehaus.sonar sonar-update-center-common - 1.1 + 1.3-SNAPSHOT org.codehaus.sonar diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java index 11adc8adcd3..528186df9f4 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java @@ -23,6 +23,7 @@ import org.apache.commons.configuration.PropertiesConfiguration; import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.api.config.EmailSettings; import org.sonar.api.utils.HttpDownloader; +import org.sonar.api.utils.UriReader; import org.sonar.batch.FakeMavenPluginExecutor; import org.sonar.batch.MavenPluginExecutor; import org.sonar.batch.RemoteServerMetadata; @@ -64,6 +65,7 @@ public class BootstrapModule extends Module { addCoreSingleton(ServerMetadata.class);// registered here because used by BootstrapClassLoader addCoreSingleton(TempDirectories.class);// registered here because used by BootstrapClassLoader addCoreSingleton(HttpDownloader.class);// registered here because used by BootstrapClassLoader + addCoreSingleton(UriReader.class);// registered here because used by BootstrapClassLoader addCoreSingleton(ArtifactDownloader.class);// registered here because used by BootstrapClassLoader addCoreSingleton(JdbcDriverHolder.class); addCoreSingleton(EmailSettings.class); diff --git a/sonar-core/src/test/java/org/sonar/core/plugins/DefaultPluginMetadataTest.java b/sonar-core/src/test/java/org/sonar/core/plugins/DefaultPluginMetadataTest.java index c2e34ec93b2..5ac3f1dcc0a 100644 --- a/sonar-core/src/test/java/org/sonar/core/plugins/DefaultPluginMetadataTest.java +++ b/sonar-core/src/test/java/org/sonar/core/plugins/DefaultPluginMetadataTest.java @@ -125,7 +125,7 @@ public class DefaultPluginMetadataTest { assertThat(pluginWithVersion("3.1-RC2").isCompatibleWith("3.2-SNAPSHOT")).isTrue(); assertThat(pluginWithVersion("3.1-RC1").isCompatibleWith("3.2-RC2")).isTrue(); assertThat(pluginWithVersion("3.1-RC1").isCompatibleWith("3.1-RC2")).isTrue(); - assertThat(pluginWithVersion("3.1-RC2").isCompatibleWith("3.1-RC1")).isTrue(); + assertThat(pluginWithVersion("3.1-RC2").isCompatibleWith("3.1-RC1")).isFalse(); assertThat(pluginWithVersion(null).isCompatibleWith("0")).isTrue(); assertThat(pluginWithVersion(null).isCompatibleWith("3.1")).isTrue(); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/HttpDownloader.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/HttpDownloader.java index 1f99d62f46a..1f7b1c54613 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/utils/HttpDownloader.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/HttpDownloader.java @@ -34,6 +34,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.*; +import java.nio.charset.Charset; import java.util.List; /** @@ -41,7 +42,7 @@ import java.util.List; * * @since 2.2 */ -public class HttpDownloader implements BatchComponent, ServerComponent { +public class HttpDownloader implements UriReader.SchemeProcessor, BatchComponent, ServerComponent { public static final int TIMEOUT_MILLISECONDS = 20 * 1000; @@ -91,9 +92,13 @@ public class HttpDownloader implements BatchComponent, ServerComponent { return Joiner.on(", ").join(descriptions); } + public String description(URI uri) { + return String.format("%s (%s)", uri.toString(), getProxySynthesis(uri)); + } + private void registerProxyCredentials(Settings settings) { Authenticator.setDefault(new ProxyAuthenticator(settings.getString("http.proxyUser"), settings - .getString("http.proxyPassword"))); + .getString("http.proxyPassword"))); } private boolean requiresProxyAuthentication(Settings settings) { @@ -165,6 +170,18 @@ public class HttpDownloader implements BatchComponent, ServerComponent { } } + public String[] getSupportedSchemes() { + return new String[]{"http", "https"}; + } + + public byte[] readBytes(URI uri) { + return download(uri); + } + + public String readString(URI uri, Charset charset) { + return downloadPlainText(uri, charset.name()); + } + public InputStream openStream(URI uri) { try { HttpURLConnection connection = newHttpConnection(uri); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/UriReader.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/UriReader.java new file mode 100644 index 00000000000..105d984dfd8 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/UriReader.java @@ -0,0 +1,147 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.api.utils; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.base.Throwables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.io.Files; +import org.sonar.api.BatchComponent; +import org.sonar.api.ServerComponent; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * Reads different types of URI. Supported schemes are http and file. + * + * @since 3.2 + */ +public class UriReader implements BatchComponent, ServerComponent { + + private final Map processorsByScheme = Maps.newHashMap(); + + public UriReader(SchemeProcessor[] processors) { + List allProcessors = Lists.asList(new FileProcessor(), processors); + for (SchemeProcessor processor : allProcessors) { + for (String scheme : processor.getSupportedSchemes()) { + processorsByScheme.put(scheme.toLowerCase(Locale.ENGLISH), processor); + } + } + } + + /** + * Reads all bytes from uri. It throws an unchecked exception if an error occurs. + */ + public byte[] readBytes(URI uri) { + return searchForSupportedProcessor(uri).readBytes(uri); + } + + /** + * Reads all characters from uri, using the given character set. + * It throws an unchecked exception if an error occurs. + */ + public String readString(URI uri, Charset charset) { + return searchForSupportedProcessor(uri).readString(uri, charset); + } + + /** + * Opens an input stream over the given uri. + */ + public InputStream openStream(URI uri) { + return searchForSupportedProcessor(uri).openStream(uri); + } + + /** + * Returns a detailed description of the given uri. For example HTTP URIs are completed + * with the configured HTTP proxy. + */ + public String description(URI uri) { + SchemeProcessor reader = searchForSupportedProcessor(uri); + + return reader.description(uri); + } + + @VisibleForTesting + SchemeProcessor searchForSupportedProcessor(URI uri) { + SchemeProcessor processor = processorsByScheme.get(uri.getScheme().toLowerCase(Locale.ENGLISH)); + Preconditions.checkArgument(processor != null, "URI schema is not supported: " + uri.getScheme()); + return processor; + } + + static interface SchemeProcessor extends BatchComponent, ServerComponent { + String[] getSupportedSchemes(); + + byte[] readBytes(URI uri); + + String readString(URI uri, Charset charset); + + InputStream openStream(URI uri); + + String description(URI uri); + } + + + /** + * This implementation is not exposed in API and is kept private. + */ + private static class FileProcessor implements SchemeProcessor { + + public String[] getSupportedSchemes() { + return new String[]{"file"}; + } + + public byte[] readBytes(URI uri) { + try { + return Files.toByteArray(new File(uri)); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + + public String readString(URI uri, Charset charset) { + try { + return Files.toString(new File(uri), charset); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + + public InputStream openStream(URI uri) { + try { + return Files.newInputStreamSupplier(new File(uri)).getInput(); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + + public String description(URI uri) { + return new File(uri).getAbsolutePath(); + } + } +} diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/utils/HttpDownloaderTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/utils/HttpDownloaderTest.java index f72d9b6843f..c8bc80d83e2 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/utils/HttpDownloaderTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/utils/HttpDownloaderTest.java @@ -19,6 +19,7 @@ */ package org.sonar.api.utils; +import com.google.common.base.Charsets; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.SystemUtils; @@ -31,15 +32,14 @@ import org.sonar.api.platform.Server; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.*; import java.util.Arrays; import java.util.Properties; -import static org.hamcrest.Matchers.greaterThan; +import static org.fest.assertions.Assertions.assertThat; import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; import static org.junit.Assume.assumeThat; -import static org.junit.internal.matchers.StringContains.containsString; import static org.mockito.Matchers.anyObject; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -161,21 +161,28 @@ public class HttpDownloaderTest { } @Test - public void downloadBytes() throws URISyntaxException { - byte[] bytes = new HttpDownloader(new Settings()).download(new URI(baseUrl)); - assertThat(bytes.length, greaterThan(10)); + public void downloadBytes() throws Exception { + byte[] bytes = new HttpDownloader(new Settings()).readBytes(new URI(baseUrl)); + assertThat(bytes.length).isGreaterThan(10); } @Test - public void downloadPlainText() throws URISyntaxException { - String text = new HttpDownloader(new Settings()).downloadPlainText(new URI(baseUrl), "UTF-8"); - assertThat(text.length(), greaterThan(10)); + public void readString() throws Exception { + String text = new HttpDownloader(new Settings()).readString(new URI(baseUrl), Charsets.UTF_8); + assertThat(text.length()).isGreaterThan(10); + } + + @Test + public void openStream() throws Exception { + InputStream input = new HttpDownloader(new Settings()).openStream(new URI(baseUrl)); + assertThat(IOUtils.toByteArray(input).length).isGreaterThan(10); + IOUtils.closeQuietly(input); } @Test(expected = SonarException.class) - public void failIfServerDown() throws URISyntaxException { + public void failIfServerDown() throws Exception { // I hope that the port 1 is not used ! - new HttpDownloader(new Settings()).download(new URI("http://localhost:1/unknown")); + new HttpDownloader(new Settings()).readBytes(new URI("http://localhost:1/unknown")); } @Test @@ -186,8 +193,8 @@ public class HttpDownloaderTest { File toFile = new File(toDir, "downloadToFile.txt"); new HttpDownloader(new Settings()).download(new URI(baseUrl), toFile); - assertThat(toFile.exists(), is(true)); - assertThat(toFile.length(), greaterThan(10l)); + assertThat(toFile).exists(); + assertThat(toFile.length()).isGreaterThan(10l); } @Test @@ -201,7 +208,7 @@ public class HttpDownloaderTest { // I hope that the port 1 is not used ! new HttpDownloader(new Settings()).download(new URI("http://localhost:1/unknown"), toFile); } catch (SonarException e) { - assertThat(toFile.exists(), is(false)); + assertThat(toFile).doesNotExist(); } } @@ -210,30 +217,42 @@ public class HttpDownloaderTest { Server server = mock(Server.class); when(server.getVersion()).thenReturn("2.2"); - byte[] bytes = new HttpDownloader(server, new Settings()).download(new URI(baseUrl)); + byte[] bytes = new HttpDownloader(server, new Settings()).readBytes(new URI(baseUrl)); Properties props = new Properties(); props.load(IOUtils.toInputStream(new String(bytes))); - assertThat(props.getProperty("agent"), is("Sonar 2.2")); + assertThat(props.getProperty("agent")).isEqualTo("Sonar 2.2"); } @Test public void followRedirect() throws URISyntaxException { - byte[] bytes = new HttpDownloader(new Settings()).download(new URI(baseUrl + "/redirect/")); - assertThat(new String(bytes), containsString("count")); + byte[] bytes = new HttpDownloader(new Settings()).readBytes(new URI(baseUrl + "/redirect/")); + assertThat(new String(bytes)).contains("count"); } @Test public void shouldGetDirectProxySynthesis() throws URISyntaxException { ProxySelector proxySelector = mock(ProxySelector.class); when(proxySelector.select((URI) anyObject())).thenReturn(Arrays.asList(Proxy.NO_PROXY)); - assertThat(HttpDownloader.getProxySynthesis(new URI("http://an_url"), proxySelector), is("no proxy")); + assertThat(HttpDownloader.getProxySynthesis(new URI("http://an_url"), proxySelector)).isEqualTo("no proxy"); } @Test public void shouldGetProxySynthesis() throws URISyntaxException { ProxySelector proxySelector = mock(ProxySelector.class); when(proxySelector.select((URI) anyObject())).thenReturn(Arrays.asList((Proxy) new FakeProxy())); - assertThat(HttpDownloader.getProxySynthesis(new URI("http://an_url"), proxySelector), is("proxy: http://proxy_url:4040")); + assertThat(HttpDownloader.getProxySynthesis(new URI("http://an_url"), proxySelector)).isEqualTo("proxy: http://proxy_url:4040"); + } + + @Test + public void supported_schemes() { + assertThat(new HttpDownloader(new Settings()).getSupportedSchemes()).contains("http"); + } + + @Test + public void uri_description() throws Exception { + HttpDownloader downloader = new HttpDownloader(new Settings()); + String description = downloader.description(new URI("http://sonarsource.org")); + assertThat(description).matches("http://sonarsource.org \\(.*\\)"); } } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/utils/UriReaderTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/utils/UriReaderTest.java new file mode 100644 index 00000000000..c37a635d568 --- /dev/null +++ b/sonar-plugin-api/src/test/java/org/sonar/api/utils/UriReaderTest.java @@ -0,0 +1,122 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.api.utils; + +import com.google.common.base.Charsets; +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class UriReaderTest { + + private static URI testFile; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void init() throws URISyntaxException { + testFile = UriReaderTest.class.getResource("/org/sonar/api/utils/UriReaderTest/foo.txt").toURI(); + } + + @Test + public void file_processor_is_always_available() throws URISyntaxException { + UriReader uriReader = new UriReader(new UriReader.SchemeProcessor[0]); + + assertThat(uriReader.searchForSupportedProcessor(testFile)).isNotNull(); + } + + @Test + public void file_readString() throws Exception { + UriReader uriReader = new UriReader(new UriReader.SchemeProcessor[0]); + assertThat(uriReader.readString(testFile, Charsets.UTF_8)).isEqualTo("in foo"); + } + + @Test + public void file_readBytes() throws Exception { + UriReader uriReader = new UriReader(new UriReader.SchemeProcessor[0]); + assertThat(new String(uriReader.readBytes(testFile))).isEqualTo("in foo"); + } + + @Test + public void file_openStream() throws Exception { + UriReader uriReader = new UriReader(new UriReader.SchemeProcessor[0]); + InputStream input = uriReader.openStream(testFile); + assertThat(IOUtils.toString(input)).isEqualTo("in foo"); + IOUtils.closeQuietly(input); + } + + @Test + public void file_readString_fails_if_file_not_found() throws Exception { + thrown.expect(RuntimeException.class); + UriReader uriReader = new UriReader(new UriReader.SchemeProcessor[0]); + uriReader.readString(new URI("file:/notfound"), Charsets.UTF_8); + } + + @Test + public void file_readBytes_fails_if_file_not_found() throws Exception { + thrown.expect(RuntimeException.class); + UriReader uriReader = new UriReader(new UriReader.SchemeProcessor[0]); + uriReader.readBytes(new URI("file:/notfound")); + } + + @Test + public void file_openStream_fails_if_file_not_found() throws Exception { + thrown.expect(RuntimeException.class); + UriReader uriReader = new UriReader(new UriReader.SchemeProcessor[0]); + uriReader.openStream(new URI("file:/notfound")); + } + + @Test + public void file_description() { + UriReader uriReader = new UriReader(new UriReader.SchemeProcessor[0]); + + // the prefix file:/ is removed + assertThat(uriReader.description(testFile)).doesNotMatch("file:/.*"); + assertThat(uriReader.description(testFile)).matches(".*foo\\.txt"); + } + + @Test + public void fail_if_unknown_scheme() throws Exception { + thrown.expect(IllegalArgumentException.class); + UriReader uriReader = new UriReader(new UriReader.SchemeProcessor[0]); + uriReader.readBytes(new URI("ftp://sonarsource.org")); + } + + @Test + public void register_processors() throws Exception { + UriReader.SchemeProcessor processor = mock(UriReader.SchemeProcessor.class); + when(processor.getSupportedSchemes()).thenReturn(new String[]{"ftp"}); + UriReader uriReader = new UriReader(new UriReader.SchemeProcessor[]{processor}); + + assertThat(uriReader.searchForSupportedProcessor(new URI("ftp://sonarsource.org"))).isNotNull(); + } +} diff --git a/sonar-plugin-api/src/test/resources/org/sonar/api/utils/UriReaderTest/foo.txt b/sonar-plugin-api/src/test/resources/org/sonar/api/utils/UriReaderTest/foo.txt new file mode 100644 index 00000000000..be36d7b95db --- /dev/null +++ b/sonar-plugin-api/src/test/resources/org/sonar/api/utils/UriReaderTest/foo.txt @@ -0,0 +1 @@ +in foo \ No newline at end of file diff --git a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java index 287f8e2f541..b3295221436 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java @@ -36,6 +36,7 @@ import org.sonar.api.rules.XMLRuleParser; import org.sonar.api.utils.HttpDownloader; import org.sonar.api.utils.IocContainer; import org.sonar.api.utils.TimeProfiler; +import org.sonar.api.utils.UriReader; import org.sonar.api.workflow.internal.DefaultWorkflow; import org.sonar.core.PicoUtils; import org.sonar.core.config.Logback; @@ -193,6 +194,7 @@ public final class Platform { servicesContainer.addSingleton(WorkflowEngine.class); servicesContainer.addSingleton(HttpDownloader.class); + servicesContainer.addSingleton(UriReader.class); servicesContainer.addSingleton(UpdateCenterClient.class); servicesContainer.addSingleton(UpdateCenterMatrixFactory.class); servicesContainer.addSingleton(PluginDownloader.class); diff --git a/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java b/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java index 191c0e88cbd..7d461004756 100644 --- a/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java +++ b/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java @@ -19,6 +19,7 @@ */ package org.sonar.server.plugins; +import com.google.common.annotations.VisibleForTesting; import org.apache.commons.io.IOUtils; import org.slf4j.LoggerFactory; import org.sonar.api.Properties; @@ -26,8 +27,7 @@ import org.sonar.api.Property; import org.sonar.api.PropertyType; import org.sonar.api.ServerComponent; import org.sonar.api.config.Settings; -import org.sonar.api.utils.HttpDownloader; -import org.sonar.api.utils.Logs; +import org.sonar.api.utils.UriReader; import org.sonar.updatecenter.common.UpdateCenter; import org.sonar.updatecenter.common.UpdateCenterDeserializer; @@ -67,19 +67,17 @@ public class UpdateCenterClient implements ServerComponent { private URI uri; private UpdateCenter center = null; private long lastRefreshDate = 0; - private HttpDownloader downloader; + private UriReader uriReader; - /** - * for unit tests - */ - UpdateCenterClient(HttpDownloader downloader, URI uri) { - this.downloader = downloader; + @VisibleForTesting + UpdateCenterClient(UriReader uriReader, URI uri) { + this.uriReader = uriReader; this.uri = uri; - Logs.INFO.info("Update center: " + uri + " (" + downloader.getProxySynthesis(uri) + ")"); + LoggerFactory.getLogger(getClass()).info("Update center: " + uriReader.description(uri)); } - public UpdateCenterClient(HttpDownloader downloader, Settings configuration) throws URISyntaxException { - this(downloader, new URI(configuration.getString(URL_PROPERTY))); + public UpdateCenterClient(UriReader uriReader, Settings settings) throws URISyntaxException { + this(uriReader, new URI(settings.getString(URL_PROPERTY))); } public UpdateCenter getCenter() { @@ -88,7 +86,7 @@ public class UpdateCenterClient implements ServerComponent { public UpdateCenter getCenter(boolean forceRefresh) { if (center == null || forceRefresh || needsRefresh()) { - center = download(); + center = init(); lastRefreshDate = System.currentTimeMillis(); } return center; @@ -102,10 +100,10 @@ public class UpdateCenterClient implements ServerComponent { return lastRefreshDate + PERIOD_IN_MILLISECONDS < System.currentTimeMillis(); } - private UpdateCenter download() { + private UpdateCenter init() { InputStream input = null; try { - input = downloader.openStream(uri); + input = uriReader.openStream(uri); if (input != null) { java.util.Properties properties = new java.util.Properties(); properties.load(input); @@ -113,7 +111,7 @@ public class UpdateCenterClient implements ServerComponent { } } catch (Exception e) { - LoggerFactory.getLogger(getClass()).error("Fail to download data from update center", e); + LoggerFactory.getLogger(getClass()).error("Fail to connect to update center", e); } finally { IOUtils.closeQuietly(input); diff --git a/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterMatrix.java b/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterMatrix.java index e3f6e48decb..b7caf70ca76 100644 --- a/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterMatrix.java +++ b/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterMatrix.java @@ -41,18 +41,6 @@ public final class UpdateCenterMatrix { this.installedSonarVersion = installedSonarVersion; } - public UpdateCenterMatrix(UpdateCenter center, String installedSonarVersion) { - this(center, Version.create(installedSonarVersion)); - } - - public UpdateCenter getCenter() { - return center; - } - - public Version getInstalledSonarVersion() { - return installedSonarVersion; - } - public UpdateCenterMatrix registerInstalledPlugin(String pluginKey, Version pluginVersion) { Plugin plugin = center.getPlugin(pluginKey); if (plugin != null) { @@ -61,21 +49,30 @@ public final class UpdateCenterMatrix { return this; } + /** + * Used by ruby webapp + */ + public UpdateCenter getCenter() { + return center; + } + public UpdateCenterMatrix registerPendingPluginsByFilename(String filename) { pendingPluginFilenames.add(filename); return this; } public List findAvailablePlugins() { + Version adjustedSonarVersion = getAdjustedSonarVersion(); + List availables = Lists.newArrayList(); for (Plugin plugin : center.getPlugins()) { if (!installedPlugins.containsKey(plugin) && !isAlreadyDownloaded(plugin)) { - Release release = plugin.getLastCompatibleRelease(installedSonarVersion); + Release release = plugin.getLastCompatibleRelease(adjustedSonarVersion); if (release != null) { availables.add(PluginUpdate.createWithStatus(release, PluginUpdate.Status.COMPATIBLE)); } else { - release = plugin.getLastCompatibleReleaseIfUpgrade(installedSonarVersion); + release = plugin.getLastCompatibleReleaseIfUpgrade(adjustedSonarVersion); if (release != null) { availables.add(PluginUpdate.createWithStatus(release, PluginUpdate.Status.REQUIRE_SONAR_UPGRADE)); } @@ -96,13 +93,15 @@ public final class UpdateCenterMatrix { } public List findPluginUpdates() { + Version adjustedSonarVersion = getAdjustedSonarVersion(); + List updates = Lists.newArrayList(); for (Map.Entry entry : installedPlugins.entrySet()) { Plugin plugin = entry.getKey(); if (!isAlreadyDownloaded(plugin)) { Version pluginVersion = entry.getValue(); for (Release release : plugin.getReleasesGreaterThan(pluginVersion)) { - updates.add(PluginUpdate.createForPluginRelease(release, installedSonarVersion)); + updates.add(PluginUpdate.createForPluginRelease(release, adjustedSonarVersion)); } } } @@ -156,4 +155,13 @@ public final class UpdateCenterMatrix { this.date = d; return this; } + + /** + * Update center declares RELEASE versions of Sonar, for instance 3.2 but not 3.2-SNAPSHOT. + * We assume that SNAPSHOT, milestones and release candidates of Sonar support the + * same plugins than related RELEASE. + */ + private Version getAdjustedSonarVersion() { + return Version.createRelease(installedSonarVersion.toString()); + } } diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/updatecenter/available.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/updatecenter/available.html.erb index d96b398f17a..0f5defd15af 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/updatecenter/available.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/updatecenter/available.html.erb @@ -1,7 +1,7 @@ - <%= render :partial => 'updatecenter/tabs', :locals => {:tab => 'available'} -%> +

<%= message('update_center.page') -%>

+<%= render :partial => 'updatecenter/tabs', :locals => {:tab => 'available'} -%>
<%= render :partial => 'updatecenter/operations' -%> diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/updatecenter/system_updates.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/updatecenter/system_updates.html.erb index a52a6d89968..a4f707b32f5 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/updatecenter/system_updates.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/updatecenter/system_updates.html.erb @@ -1,102 +1,113 @@ - - - <%= render :partial => 'updatecenter/tabs', :locals => {:tab => 'system_updates'} -%> +

<%= message('update_center.page') -%>

+<%= render :partial => 'updatecenter/tabs', :locals => {:tab => 'system_updates'} -%> -
+
- <%= render :partial => 'updatecenter/operations' -%> + <%= render :partial => 'updatecenter/operations' -%> - <% if @center %> - <% if @sonar_updates.empty? %> - - - - - - - - - - - -
System is up to date
- <% else %> + <% if @center %> + <% if @sonar_updates.empty? %> + + + + + + + + + + + +
System is up to date
+ <% else %> - <% @sonar_updates.to_a.reverse.each do |update| + <% @sonar_updates.to_a.reverse.each do |update| release=update.getRelease() - %> - - + %> +
+ - - + + - -

Sonar <%= release.getVersion() -%>

- + - - + + - +
Date: <%= release_date(release.getDate()) if release.getDate() -%>
<%= link_to_if release.getChangelogUrl(), 'Release Notes', release.getChangelogUrl(), :class => 'external' %>: + <%= link_to_if release.getChangelogUrl(), 'Release Notes', release.getChangelogUrl(), :class => 'external' %> + : <%= release.getDescription() -%>
How to upgrade: <% if update.hasWarnings() %> - Follow those steps to upgrade Sonar from version <%= sonar_version -%> to version <%= release.getVersion() -%> : -
    - <% update.getIncompatiblePlugins().each do |incompatible_plugin| %> -
  1. -
    - <%= image_tag 'warning.png' -%> the plugin <%= incompatible_plugin.getName() -%> which is not compatible with Sonar <%= release.getVersion() -%>. -
    -
  2. - <% end %> - <% update.getPluginsToUpgrade().each do |plugin_to_upgrade| %> -
  3. -
    - - the plugin <%= plugin_to_upgrade.getArtifact().getName() -%> to version <%= plugin_to_upgrade.getVersion() -%> -
    -
  4. - <% end %> -
  5. Stop Sonar
  6. -
  7. <%= link_to 'Download', release.getDownloadUrl(), :class => 'external' -%> and install Sonar <%= release.getVersion() -%> after having carefully read the upgrade guide.
  8. -
  9. Start Sonar
  10. -
+ Follow those steps to upgrade Sonar from version <%= sonar_version -%> to + version <%= release.getVersion() -%> : +
    + <% update.getIncompatiblePlugins().each do |incompatible_plugin| %> +
  1. +
    + <%= image_tag 'warning.png' -%> + + the plugin <%= incompatible_plugin.getName() -%> which is not compatible with + Sonar <%= release.getVersion() -%>. +
    +
  2. + <% end %> + <% update.getPluginsToUpgrade().each do |plugin_to_upgrade| %> +
  3. +
    + + the plugin <%= plugin_to_upgrade.getArtifact().getName() -%> to + version <%= plugin_to_upgrade.getVersion() -%> +
    +
  4. + <% end %> +
  5. Stop Sonar
  6. +
  7. <%= link_to 'Download', release.getDownloadUrl(), :class => 'external' -%> and install + Sonar <%= release.getVersion() -%> after having carefully read the upgrade guide. +
  8. +
  9. Start Sonar
  10. +
<% else %> - <%= link_to 'Download', release.getDownloadUrl(), :class => 'external' -%> and install Sonar <%= release.getVersion() -%> after having carefully read the upgrade guide. + <%= link_to 'Download', release.getDownloadUrl(), :class => 'external' -%> and install + Sonar <%= release.getVersion() -%> after having carefully read the upgrade guide. <% end %>
-
- <% end - end - end %> + + +
+ <% end + end + end %> - <%= render :partial => 'updatecenter/status', :locals => {:action => 'system_updates' } %> -
+ <%= render :partial => 'updatecenter/status', :locals => {:action => 'system_updates'} %> +
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/updatecenter/updates.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/updatecenter/updates.html.erb index 1966fd95241..d313e675353 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/updatecenter/updates.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/updatecenter/updates.html.erb @@ -7,8 +7,8 @@ function upgradePlugin(key) { return false; } - - <%= render :partial => 'updatecenter/tabs', :locals => {:tab => 'updates'} -%> +

<%= message('update_center.page') -%>

+<%= render :partial => 'updatecenter/tabs', :locals => {:tab => 'updates'} -%>
diff --git a/sonar-server/src/test/java/org/sonar/server/plugins/UpdateCenterClientTest.java b/sonar-server/src/test/java/org/sonar/server/plugins/UpdateCenterClientTest.java index d170dbbe935..339659b709a 100644 --- a/sonar-server/src/test/java/org/sonar/server/plugins/UpdateCenterClientTest.java +++ b/sonar-server/src/test/java/org/sonar/server/plugins/UpdateCenterClientTest.java @@ -22,64 +22,61 @@ package org.sonar.server.plugins; import org.apache.commons.io.IOUtils; import org.junit.Before; import org.junit.Test; -import org.sonar.api.utils.HttpDownloader; import org.sonar.api.utils.SonarException; +import org.sonar.api.utils.UriReader; import org.sonar.updatecenter.common.UpdateCenter; import org.sonar.updatecenter.common.Version; import java.net.URI; import java.net.URISyntaxException; -import static junit.framework.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.internal.matchers.IsCollectionContaining.hasItems; -import static org.mockito.Matchers.anyObject; +import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Mockito.*; public class UpdateCenterClientTest { private UpdateCenterClient client; - private HttpDownloader downloader; + private UriReader reader; private static final String BASE_URL = "http://update.sonarsource.org"; @Before public void startServer() throws Exception { - downloader = mock(HttpDownloader.class); - client = new UpdateCenterClient(downloader, new URI(BASE_URL)); + reader = mock(UriReader.class); + client = new UpdateCenterClient(reader, new URI(BASE_URL)); } @Test public void downloadUpdateCenter() throws URISyntaxException { - when(downloader.openStream(new URI(BASE_URL))).thenReturn(IOUtils.toInputStream("sonar.versions=2.2,2.3")); + when(reader.openStream(new URI(BASE_URL))).thenReturn(IOUtils.toInputStream("sonar.versions=2.2,2.3")); UpdateCenter center = client.getCenter(); - verify(downloader, times(1)).openStream(new URI(BASE_URL)); - assertThat(center.getSonar().getVersions(), hasItems(Version.create("2.2"), Version.create("2.3"))); + verify(reader, times(1)).openStream(new URI(BASE_URL)); + assertThat(center.getSonar().getVersions()).containsOnly(Version.create("2.2"), Version.create("2.3")); } @Test - public void ignoreWhenServerIsDown() { - when(downloader.download((URI) anyObject())).thenThrow(new SonarException()); - assertNull(client.getCenter()); + public void ignore_connection_errors() { + when(reader.openStream(any(URI.class))).thenThrow(new SonarException()); + assertThat(client.getCenter()).isNull(); } @Test - public void cacheData() throws URISyntaxException { - when(downloader.openStream(new URI(BASE_URL))).thenReturn(IOUtils.toInputStream("sonar.versions=2.2,2.3")); + public void cache_data() throws Exception { + when(reader.openStream(new URI(BASE_URL))).thenReturn(IOUtils.toInputStream("sonar.versions=2.2,2.3")); client.getCenter(); client.getCenter(); - verify(downloader, times(1)).openStream(new URI(BASE_URL)); + verify(reader, times(1)).openStream(new URI(BASE_URL)); } @Test - public void forceRefresh() throws URISyntaxException { - when(downloader.openStream(new URI(BASE_URL))).thenReturn(IOUtils.toInputStream("sonar.versions=2.2,2.3")); + public void forceRefresh() throws Exception { + when(reader.openStream(new URI(BASE_URL))).thenReturn(IOUtils.toInputStream("sonar.versions=2.2,2.3")); client.getCenter(); client.getCenter(true); - verify(downloader, times(2)).openStream(new URI(BASE_URL)); + verify(reader, times(2)).openStream(new URI(BASE_URL)); } } \ No newline at end of file diff --git a/sonar-server/src/test/java/org/sonar/server/plugins/UpdateCenterMatrixTest.java b/sonar-server/src/test/java/org/sonar/server/plugins/UpdateCenterMatrixTest.java index 3a07a915cca..d6b287b7d62 100644 --- a/sonar-server/src/test/java/org/sonar/server/plugins/UpdateCenterMatrixTest.java +++ b/sonar-server/src/test/java/org/sonar/server/plugins/UpdateCenterMatrixTest.java @@ -28,9 +28,7 @@ import org.sonar.updatecenter.common.Version; import java.util.List; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.fest.assertions.Assertions.assertThat; public class UpdateCenterMatrixTest { private UpdateCenter center; @@ -66,29 +64,29 @@ public class UpdateCenterMatrixTest { @Test public void findPluginUpdates() { - UpdateCenterMatrix matrix = new UpdateCenterMatrix(center, "2.1"); + UpdateCenterMatrix matrix = new UpdateCenterMatrix(center, Version.create("2.1")); matrix.registerInstalledPlugin("foo", Version.create("1.0")); List updates = matrix.findPluginUpdates(); - assertThat(updates.size(), is(2)); + assertThat(updates).hasSize(2); - assertThat(updates.get(0).getRelease(), is(foo11)); - assertThat(updates.get(0).isCompatible(), is(true)); + assertThat(updates.get(0).getRelease()).isEqualTo(foo11); + assertThat(updates.get(0).isCompatible()).isTrue(); - assertThat(updates.get(1).getRelease(), is(foo12)); - assertThat(updates.get(1).isCompatible(), is(false)); - assertThat(updates.get(1).requiresSonarUpgrade(), is(true)); + assertThat(updates.get(1).getRelease()).isEqualTo(foo12); + assertThat(updates.get(1).isCompatible()).isFalse(); + assertThat(updates.get(1).requiresSonarUpgrade()).isTrue(); } @Test public void noPluginUpdatesIfLastReleaseIsInstalled() { - UpdateCenterMatrix matrix = new UpdateCenterMatrix(center, "2.3"); + UpdateCenterMatrix matrix = new UpdateCenterMatrix(center, Version.create("2.3")); matrix.registerInstalledPlugin("foo", Version.create("1.2")); - assertTrue(matrix.findPluginUpdates().isEmpty()); + assertThat(matrix.findPluginUpdates()).isEmpty(); } @Test public void availablePluginsAreOnlyTheBestReleases() { - UpdateCenterMatrix matrix = new UpdateCenterMatrix(center, "2.2"); + UpdateCenterMatrix matrix = new UpdateCenterMatrix(center, Version.create("2.2")); matrix.registerInstalledPlugin("foo", Version.create("1.0")); List availables = matrix.findAvailablePlugins(); @@ -96,14 +94,14 @@ public class UpdateCenterMatrixTest { // bar 1.0 is compatible with the installed sonar // bar 1.1 requires sonar to be upgraded to 2.2.2 or 2.3 // => available plugin to install is bar 1.0 - assertThat(availables.size(), is(1)); - assertThat(availables.get(0).getRelease(), is(bar10)); - assertThat(availables.get(0).isCompatible(), is(true)); + assertThat(availables.size()).isEqualTo(1); + assertThat(availables.get(0).getRelease()).isEqualTo(bar10); + assertThat(availables.get(0).isCompatible()).isTrue(); } @Test public void availablePluginsRequireSonarUpgrade() { - UpdateCenterMatrix matrix = new UpdateCenterMatrix(center, "2.2.1"); + UpdateCenterMatrix matrix = new UpdateCenterMatrix(center, Version.create("2.2.1")); matrix.registerInstalledPlugin("foo", Version.create("1.0")); List availables = matrix.findAvailablePlugins(); @@ -111,9 +109,9 @@ public class UpdateCenterMatrixTest { // bar 1.0 is not compatible with the installed sonar // bar 1.1 requires sonar to be upgraded to 2.2.2 or 2.3 // => available plugin to install is bar 1.1 - assertThat(availables.size(), is(1)); - assertThat(availables.get(0).getRelease(), is(bar11)); - assertThat(availables.get(0).requiresSonarUpgrade(), is(true)); + assertThat(availables.size()).isEqualTo(1); + assertThat(availables.get(0).getRelease()).isEqualTo(bar11); + assertThat(availables.get(0).requiresSonarUpgrade()).isTrue(); } @Test @@ -121,13 +119,13 @@ public class UpdateCenterMatrixTest { center.getSonar().addRelease(Version.create("2.3")); center.getSonar().addRelease(Version.create("2.4")); - UpdateCenterMatrix matrix = new UpdateCenterMatrix(center, "2.2"); + UpdateCenterMatrix matrix = new UpdateCenterMatrix(center, Version.create("2.2")); List updates = matrix.findSonarUpdates(); // no plugins are installed, so both sonar versions are compatible - assertThat(updates.size(), is(2)); - assertThat(updates.get(0).hasWarnings(), is(false)); - assertThat(updates.get(1).hasWarnings(), is(false)); + assertThat(updates).hasSize(2); + assertThat(updates.get(0).hasWarnings()).isFalse(); + assertThat(updates.get(1).hasWarnings()).isFalse(); } @Test @@ -135,40 +133,40 @@ public class UpdateCenterMatrixTest { center.getSonar().addRelease(Version.create("2.3")); center.getSonar().addRelease(Version.create("2.4")); - UpdateCenterMatrix matrix = new UpdateCenterMatrix(center, "2.2"); + UpdateCenterMatrix matrix = new UpdateCenterMatrix(center, Version.create("2.2")); matrix.registerInstalledPlugin("foo", Version.create("1.0")); matrix.registerInstalledPlugin("bar", Version.create("1.0")); List updates = matrix.findSonarUpdates(); - assertThat(updates.size(), is(2)); + assertThat(updates).hasSize(2); // sonar 2.3 supports foo 1.1/1.2 and bar 1.1 // => 2 plugin upgrades are required - assertThat(updates.get(0).hasWarnings(), is(true)); - assertThat(updates.get(0).requiresPluginUpgrades(), is(true)); - assertThat(updates.get(0).getPluginsToUpgrade().size(), is(2)); + assertThat(updates.get(0).hasWarnings()).isTrue(); + assertThat(updates.get(0).requiresPluginUpgrades()).isTrue(); + assertThat(updates.get(0).getPluginsToUpgrade()).hasSize(2); // sonar 2.4 supports no plugins - assertThat(updates.get(1).hasWarnings(), is(true)); - assertThat(updates.get(1).isIncompatible(), is(true)); - assertThat(updates.get(1).getIncompatiblePlugins().size(), is(2)); + assertThat(updates.get(1).hasWarnings()).isTrue(); + assertThat(updates.get(1).isIncompatible()).isTrue(); + assertThat(updates.get(1).getIncompatiblePlugins()).hasSize(2); } @Test public void excludePendingDownloadsFromPluginUpdates() { - UpdateCenterMatrix matrix = new UpdateCenterMatrix(center, "2.1"); + UpdateCenterMatrix matrix = new UpdateCenterMatrix(center, Version.create("2.1")); matrix.registerInstalledPlugin("foo", Version.create("1.0")); matrix.registerPendingPluginsByFilename("foo-1.0.jar"); List updates = matrix.findPluginUpdates(); - assertThat(updates.size(), is(0)); + assertThat(updates.size()).isEqualTo(0); } @Test public void excludePendingDownloadsFromAvailablePlugins() { - UpdateCenterMatrix matrix = new UpdateCenterMatrix(center, "2.1"); + UpdateCenterMatrix matrix = new UpdateCenterMatrix(center, Version.create("2.1")); matrix.registerPendingPluginsByFilename("foo-1.0.jar"); matrix.registerPendingPluginsByFilename("bar-1.1.jar"); List updates = matrix.findAvailablePlugins(); - assertThat(updates.size(), is(0)); + assertThat(updates.size()).isEqualTo(0); } }