From 604570330b3702c3c2df0463bd2610208e64a55b Mon Sep 17 00:00:00 2001 From: Teryk Bellahsene Date: Mon, 21 Sep 2015 16:56:29 +0200 Subject: [PATCH] SONAR-6866 Handle format suffix like .protobuf in WS url --- .../org/sonar/server/util/ObjectUtils.java | 58 ++++++++++ .../org/sonar/server/ws/ServletRequest.java | 25 +++- .../org/sonar/server/ws/WebServiceEngine.java | 17 +-- .../sonar/server/ws/ServletRequestTest.java | 24 ++-- .../sonar/server/ws/WebServiceEngineTest.java | 108 ++++++++++-------- 5 files changed, 163 insertions(+), 69 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/util/ObjectUtils.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/ObjectUtils.java b/server/sonar-server/src/main/java/org/sonar/server/util/ObjectUtils.java new file mode 100644 index 00000000000..09b57e70df2 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/util/ObjectUtils.java @@ -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.server.util; + +public class ObjectUtils { + + /** + * Taken from http://commons.apache.org/proper/commons-lang/javadocs/api-3.1/src-html/org/apache/commons/lang3/ObjectUtils.html#line.119 + *

Returns the first value in the array which is not {@code null}. + * If all the values are {@code null} or the array is {@code null} + * or empty then {@code null} is returned.

+ * + *
+    * ObjectUtils.firstNonNull(null, null)      = null
+    * ObjectUtils.firstNonNull(null, "")        = ""
+    * ObjectUtils.firstNonNull(null, null, "")  = ""
+    * ObjectUtils.firstNonNull(null, "zz")      = "zz"
+    * ObjectUtils.firstNonNull("abc", *)        = "abc"
+    * ObjectUtils.firstNonNull(null, "xyz", *)  = "xyz"
+    * ObjectUtils.firstNonNull(Boolean.TRUE, *) = Boolean.TRUE
+    * ObjectUtils.firstNonNull()                = null
+    * 
+ * + * @param the component type of the array + * @param values the values to test, may be {@code null} or empty + * @return the first value from {@code values} which is not {@code null}, + * or {@code null} if there are no non-null values + * @since 3.0 + */ + public static T firstNonNull(T... values) { + if (values != null) { + for (T val : values) { + if (val != null) { + return val; + } + } + } + return null; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/ws/ServletRequest.java b/server/sonar-server/src/main/java/org/sonar/server/ws/ServletRequest.java index a1aa8d2d39b..6e637c2a6cf 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ws/ServletRequest.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ws/ServletRequest.java @@ -19,19 +19,25 @@ */ package org.sonar.server.ws; -import com.google.common.base.Objects; +import com.google.common.collect.ImmutableMap; +import java.io.InputStream; +import java.util.Map; +import javax.annotation.CheckForNull; +import javax.servlet.http.HttpServletRequest; import org.jruby.RubyFile; import org.sonar.api.server.ws.internal.ValidatingRequest; +import org.sonar.server.plugins.MimeTypes; -import javax.servlet.http.HttpServletRequest; - -import java.io.InputStream; -import java.util.Map; +import static org.sonar.server.util.ObjectUtils.firstNonNull; public class ServletRequest extends ValidatingRequest { private final HttpServletRequest source; private final Map params; + private static final Map SUPPORTED_FORMATS = ImmutableMap.of( + "JSON", MimeTypes.JSON, + "PROTOBUF", MimeTypes.PROTOBUF, + "TEXT", MimeTypes.TXT); public ServletRequest(HttpServletRequest source, Map params) { this.source = source; @@ -45,7 +51,8 @@ public class ServletRequest extends ValidatingRequest { @Override public String getMediaType() { - return Objects.firstNonNull(source.getContentType(), "application/octet-stream"); + String mediaTypeFromUrl = mediaTypeFromUrl(source.getRequestURI()); + return firstNonNull(mediaTypeFromUrl, source.getContentType(), MimeTypes.DEFAULT); } @Override @@ -83,4 +90,10 @@ public class ServletRequest extends ValidatingRequest { } return url.toString(); } + + @CheckForNull + private static String mediaTypeFromUrl(String url) { + String mediaType = url.substring(url.lastIndexOf('.') + 1); + return SUPPORTED_FORMATS.get(mediaType.toUpperCase()); + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/ws/WebServiceEngine.java b/server/sonar-server/src/main/java/org/sonar/server/ws/WebServiceEngine.java index be69cbb783e..038de3a04dd 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ws/WebServiceEngine.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ws/WebServiceEngine.java @@ -19,6 +19,9 @@ */ package org.sonar.server.ws; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.List; import org.picocontainer.Startable; import org.sonar.api.i18n.I18n; import org.sonar.api.server.ServerSide; @@ -33,10 +36,7 @@ import org.sonar.server.exceptions.ServerException; import org.sonar.server.plugins.MimeTypes; import org.sonar.server.user.UserSession; -import java.io.OutputStreamWriter; -import java.nio.charset.StandardCharsets; -import java.util.List; - +import static java.lang.String.format; import static org.sonar.server.ws.RequestVerifier.verifyRequest; /** @@ -98,13 +98,16 @@ public class WebServiceEngine implements Startable { } private WebService.Action getAction(String controllerPath, String actionKey) { + String actionKeyWithoutFormatSuffix = actionKey.contains(".") ? + actionKey.substring(0, actionKey.lastIndexOf('.')) + : actionKey; WebService.Controller controller = context.controller(controllerPath); if (controller == null) { - throw new BadRequestException(String.format("Unknown web service: %s", controllerPath)); + throw new BadRequestException(format("Unknown web service: %s", controllerPath)); } - WebService.Action action = controller.action(actionKey); + WebService.Action action = controller.action(actionKeyWithoutFormatSuffix); if (action == null) { - throw new BadRequestException(String.format("Unknown action: %s/%s", controllerPath, actionKey)); + throw new BadRequestException(format("Unknown action: %s/%s", controllerPath, actionKeyWithoutFormatSuffix)); } return action; } diff --git a/server/sonar-server/src/test/java/org/sonar/server/ws/ServletRequestTest.java b/server/sonar-server/src/test/java/org/sonar/server/ws/ServletRequestTest.java index 93d32b702fe..2df707593ff 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ws/ServletRequestTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ws/ServletRequestTest.java @@ -21,16 +21,16 @@ package org.sonar.server.ws; import com.google.common.collect.ImmutableMap; +import java.util.Collections; +import javax.servlet.http.HttpServletRequest; import org.jruby.RubyFile; import org.junit.Test; - -import javax.servlet.http.HttpServletRequest; - -import java.util.Collections; import org.sonar.server.plugins.MimeTypes; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class ServletRequestTest { @@ -46,6 +46,7 @@ public class ServletRequestTest { @Test public void getMediaType() throws Exception { when(source.getContentType()).thenReturn(MimeTypes.JSON); + when(source.getRequestURI()).thenReturn("/path/to/resource/search"); ServletRequest request = new ServletRequest(source, Collections.emptyMap()); assertThat(request.getMediaType()).isEqualTo(MimeTypes.JSON); } @@ -53,12 +54,21 @@ public class ServletRequestTest { @Test public void default_media_type_is_octet_stream() throws Exception { ServletRequest request = new ServletRequest(source, Collections.emptyMap()); - assertThat(request.getMediaType()).isEqualTo("application/octet-stream"); + when(source.getRequestURI()).thenReturn("/path/to/resource/search"); + assertThat(request.getMediaType()).isEqualTo(MimeTypes.DEFAULT); + } + + @Test + public void media_type_taken_in_url_first() throws Exception { + ServletRequest request = new ServletRequest(source, Collections.emptyMap()); + when(source.getContentType()).thenReturn(MimeTypes.JSON); + when(source.getRequestURI()).thenReturn("/path/to/resource/search.protobuf"); + assertThat(request.getMediaType()).isEqualTo(MimeTypes.PROTOBUF); } @Test public void has_param_from_source() { - when(source.getParameterMap()).thenReturn(ImmutableMap.of("param", new String[]{"value"})); + when(source.getParameterMap()).thenReturn(ImmutableMap.of("param", new String[] {"value"})); ServletRequest request = new ServletRequest(source, Collections.emptyMap()); assertThat(request.hasParam("param")).isTrue(); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/ws/WebServiceEngineTest.java b/server/sonar-server/src/test/java/org/sonar/server/ws/WebServiceEngineTest.java index 1dc9fa46239..801d6859362 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ws/WebServiceEngineTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ws/WebServiceEngineTest.java @@ -48,55 +48,6 @@ import static org.mockito.Mockito.when; public class WebServiceEngineTest { - private static class SimpleRequest extends ValidatingRequest { - private final String method; - private Map params = Maps.newHashMap(); - - private SimpleRequest(String method) { - this.method = method; - } - - @Override - public String method() { - return method; - } - - @Override - public String getMediaType() { - return MimeTypes.JSON; - } - - @Override - public boolean hasParam(String key) { - return params.keySet().contains(key); - } - - @Override - protected String readParam(String key) { - return params.get(key); - } - - @Override - protected InputStream readInputStreamParam(String key) { - String param = readParam(key); - - return param == null ? null : IOUtils.toInputStream(param); - } - - public SimpleRequest setParams(Map m) { - this.params = m; - return this; - } - - public SimpleRequest setParam(String key, @Nullable String value) { - if (value != null) { - params.put(key, value); - } - return this; - } - - } - @Rule public UserSessionRule userSessionRule = UserSessionRule.standalone(); I18n i18n = mock(I18n.class); @@ -127,6 +78,15 @@ public class WebServiceEngineTest { assertThat(response.stream().outputAsString()).isEqualTo("good"); } + @Test + public void execute_request_with_format_type() { + ValidatingRequest request = new SimpleRequest("GET"); + ServletResponse response = new ServletResponse(); + engine.execute(request, response, "api/system", "health.protobuf"); + + assertThat(response.stream().outputAsString()).isEqualTo("good"); + } + @Test public void no_content() { ValidatingRequest request = new SimpleRequest("GET"); @@ -305,6 +265,55 @@ public class WebServiceEngineTest { assertThat(response.getHeader(name)).isEqualTo(value); } + private static class SimpleRequest extends ValidatingRequest { + private final String method; + private Map params = Maps.newHashMap(); + + private SimpleRequest(String method) { + this.method = method; + } + + @Override + public String method() { + return method; + } + + @Override + public String getMediaType() { + return MimeTypes.JSON; + } + + @Override + public boolean hasParam(String key) { + return params.keySet().contains(key); + } + + @Override + protected String readParam(String key) { + return params.get(key); + } + + @Override + protected InputStream readInputStreamParam(String key) { + String param = readParam(key); + + return param == null ? null : IOUtils.toInputStream(param); + } + + public SimpleRequest setParams(Map m) { + this.params = m; + return this; + } + + public SimpleRequest setParam(String key, @Nullable String value) { + if (value != null) { + params.put(key, value); + } + return this; + } + + } + static class SystemWs implements WebService { @Override public void define(Context context) { @@ -405,5 +414,6 @@ public class WebServiceEngineTest { }); newController.done(); } + } } -- 2.39.5