From 86706f9bae9c427f729484cd98b425cd25ae653e Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Sat, 14 Jul 2012 19:32:45 +0200 Subject: 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 --- .../java/org/sonar/api/utils/HttpDownloader.java | 21 ++- .../main/java/org/sonar/api/utils/UriReader.java | 147 +++++++++++++++++++++ .../org/sonar/api/utils/HttpDownloaderTest.java | 59 ++++++--- .../java/org/sonar/api/utils/UriReaderTest.java | 122 +++++++++++++++++ .../org/sonar/api/utils/UriReaderTest/foo.txt | 1 + 5 files changed, 328 insertions(+), 22 deletions(-) create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/utils/UriReader.java create mode 100644 sonar-plugin-api/src/test/java/org/sonar/api/utils/UriReaderTest.java create mode 100644 sonar-plugin-api/src/test/resources/org/sonar/api/utils/UriReaderTest/foo.txt (limited to 'sonar-plugin-api') 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 -- cgit v1.2.3