aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@gmail.com>2014-01-11 14:32:39 +0100
committerSimon Brandhof <simon.brandhof@gmail.com>2014-01-11 14:32:39 +0100
commit2d511c42c97c6507b4cee94966c26e266a4c43f6 (patch)
treedfe600796de68ca02f2eac3c9904fa20ae88c7c3
parentcd090b8b0fcc32d186eef621d3a639da8daa82ff (diff)
downloadsonarqube-2d511c42c97c6507b4cee94966c26e266a4c43f6.tar.gz
sonarqube-2d511c42c97c6507b4cee94966c26e266a4c43f6.zip
SONAR-5010 internal extension point to implement Java web services
Not available yet in web routing.
-rw-r--r--sonar-server/src/main/java/org/sonar/server/ws/Request.java51
-rw-r--r--sonar-server/src/main/java/org/sonar/server/ws/RequestHandler.java31
-rw-r--r--sonar-server/src/main/java/org/sonar/server/ws/Response.java84
-rw-r--r--sonar-server/src/main/java/org/sonar/server/ws/WebService.java244
-rw-r--r--sonar-server/src/main/java/org/sonar/server/ws/package-info.java23
-rw-r--r--sonar-server/src/test/java/org/sonar/server/ws/RequestTest.java44
-rw-r--r--sonar-server/src/test/java/org/sonar/server/ws/WebServiceTest.java171
7 files changed, 648 insertions, 0 deletions
diff --git a/sonar-server/src/main/java/org/sonar/server/ws/Request.java b/sonar-server/src/main/java/org/sonar/server/ws/Request.java
new file mode 100644
index 00000000000..483f0ff7f4a
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/ws/Request.java
@@ -0,0 +1,51 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.ws;
+
+import javax.annotation.CheckForNull;
+import java.util.Map;
+
+/**
+ * @since 4.2
+ */
+public class Request {
+
+ private final Map<String,String> params;
+
+ public Request(Map<String, String> params) {
+ this.params = params;
+ }
+
+ @CheckForNull
+ public String param(String key) {
+ return params.get(key);
+ }
+
+ @CheckForNull
+ public Integer intParam(String key) {
+ String s = params.get(key);
+ return s == null ? null : Integer.parseInt(s);
+ }
+
+ public int intParam(String key, int defaultValue) {
+ String s = params.get(key);
+ return s == null ? defaultValue : Integer.parseInt(s);
+ }
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/ws/RequestHandler.java b/sonar-server/src/main/java/org/sonar/server/ws/RequestHandler.java
new file mode 100644
index 00000000000..9d94e4312e3
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/ws/RequestHandler.java
@@ -0,0 +1,31 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.ws;
+
+import org.sonar.api.ServerExtension;
+
+/**
+ * @since 4.2
+ */
+public interface RequestHandler extends ServerExtension {
+
+ void handle(Request request, Response response);
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/ws/Response.java b/sonar-server/src/main/java/org/sonar/server/ws/Response.java
new file mode 100644
index 00000000000..93e37377619
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/ws/Response.java
@@ -0,0 +1,84 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.ws;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+/**
+ * HTTP response
+ *
+ * @since 4.2
+ */
+public class Response {
+
+ /**
+ * HTTP Status-Code 200: OK.
+ */
+ public static final int HTTP_OK = 200;
+
+ /**
+ * HTTP Status-Code 400: Bad Request.
+ */
+ public static final int HTTP_BAD_REQUEST = 400;
+
+ /**
+ * HTTP Status-Code 401: Unauthorized.
+ */
+ public static final int HTTP_UNAUTHORIZED = 401;
+
+ /**
+ * HTTP Status-Code 403: Forbidden.
+ */
+ public static final int HTTP_FORBIDDEN = 403;
+
+ /**
+ * HTTP Status-Code 404: Not Found.
+ */
+ public static final int HTTP_NOT_FOUND = 404;
+
+ /**
+ * HTTP Status-Code 500: Internal Server Error.
+ */
+ public static final int HTTP_INTERNAL_ERROR = 500;
+
+
+ private int httpStatus = HTTP_OK;
+ private String body;
+
+ @CheckForNull
+ public String body() {
+ return body;
+ }
+
+ public Response setBody(@Nullable String body) {
+ this.body = body;
+ return this;
+ }
+
+ public int status() {
+ return httpStatus;
+ }
+
+ public Response setStatus(int httpStatus) {
+ this.httpStatus = httpStatus;
+ return this;
+ }
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/ws/WebService.java b/sonar-server/src/main/java/org/sonar/server/ws/WebService.java
new file mode 100644
index 00000000000..46432041680
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/ws/WebService.java
@@ -0,0 +1,244 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.ws;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import org.sonar.api.ServerExtension;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @since 4.2
+ */
+public interface WebService extends ServerExtension {
+
+ static class Context {
+ private final Map<String, Controller> controllers = Maps.newHashMap();
+
+ public NewController newController(String key) {
+ return new NewController(this, key);
+ }
+
+ private void register(NewController newController) {
+ if (controllers.containsKey(newController.key)) {
+ throw new IllegalStateException(
+ String.format("The web service '%s' is defined multiple times", newController.key)
+ );
+ }
+ controllers.put(newController.key, new Controller(newController));
+ }
+
+ @CheckForNull
+ public Controller controller(String key) {
+ return controllers.get(key);
+ }
+
+ // TODO sort by keys
+ public List<Controller> controllers() {
+ return ImmutableList.copyOf(controllers.values());
+ }
+ }
+
+ static class NewController {
+ private final Context context;
+ private final String key;
+ private String description, since;
+ private boolean api = false;
+ private final Map<String, NewAction> actions = Maps.newHashMap();
+
+ private NewController(Context context, String key) {
+ this.context = context;
+ this.key = key;
+ }
+
+ public void done() {
+ context.register(this);
+ }
+
+ public NewController setDescription(@Nullable String s) {
+ this.description = s;
+ return this;
+ }
+
+ public NewController setApi(boolean b) {
+ this.api = b;
+ return this;
+ }
+
+ public NewController setSince(@Nullable String s) {
+ this.since = s;
+ return this;
+ }
+
+ public NewAction newAction(String actionKey) {
+ if (actions.containsKey(actionKey)) {
+ throw new IllegalStateException(
+ String.format("The action '%s' is defined multiple times in the web service '%s'", actionKey, key)
+ );
+ }
+ NewAction action = new NewAction(actionKey);
+ actions.put(actionKey, action);
+ return action;
+ }
+ }
+
+ static class Controller {
+ private final String key, description, since;
+ private final boolean api;
+ private final Map<String, Action> actions;
+
+ private Controller(NewController newController) {
+ if (newController.actions.isEmpty()) {
+ throw new IllegalStateException(
+ String.format("At least one action must be declared in the web service '%s'", newController.key)
+ );
+ }
+ this.key = newController.key;
+ this.description = newController.description;
+ this.since = newController.since;
+ this.api = newController.api;
+ ImmutableMap.Builder<String, Action> mapBuilder = ImmutableMap.builder();
+ for (NewAction newAction : newController.actions.values()) {
+ mapBuilder.put(newAction.key, new Action(this, newAction));
+ }
+ this.actions = mapBuilder.build();
+ }
+
+ public String key() {
+ return key;
+ }
+
+ public String path() {
+ return String.format("%s/%s", WebServiceEngine.BASE_PATH, key);
+ }
+
+ @CheckForNull
+ public String description() {
+ return description;
+ }
+
+ public boolean isApi() {
+ return api;
+ }
+
+ @CheckForNull
+ public String since() {
+ return since;
+ }
+
+ @CheckForNull
+ public Action action(String actionKey) {
+ return actions.get(actionKey);
+ }
+
+ public Collection<Action> actions() {
+ return actions.values();
+ }
+ }
+
+ static class NewAction {
+ private final String key;
+ private String description, since;
+ private boolean post = false;
+ private RequestHandler handler;
+
+ private NewAction(String key) {
+ this.key = key;
+ }
+
+ public NewAction setDescription(@Nullable String s) {
+ this.description = s;
+ return this;
+ }
+
+ public NewAction setSince(@Nullable String s) {
+ this.since = s;
+ return this;
+ }
+
+ public NewAction setPost(boolean b) {
+ this.post = b;
+ return this;
+ }
+
+ public NewAction setHandler(RequestHandler h) {
+ this.handler = h;
+ return this;
+ }
+ }
+
+ static class Action {
+ private final Controller controller;
+ private final String key, description, since;
+ private final boolean post;
+ private final RequestHandler handler;
+
+ private Action(Controller controller, NewAction newAction) {
+ this.controller = controller;
+ this.key = newAction.key;
+ this.description = newAction.description;
+ this.since = newAction.since;
+ this.post = newAction.post;
+ this.handler = newAction.handler;
+ }
+
+ public String key() {
+ return key;
+ }
+
+ public String path() {
+ return String.format("%s/%s", controller.path(), key);
+ }
+
+ @CheckForNull
+ public String description() {
+ return description;
+ }
+
+ /**
+ * Set if different than controller.
+ */
+ @CheckForNull
+ public String since() {
+ return since;
+ }
+
+ public boolean isPost() {
+ return post;
+ }
+
+ @CheckForNull
+ public RequestHandler handler() {
+ return handler;
+ }
+ }
+
+ /**
+ * Executed at server startup.
+ */
+ void define(Context context);
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/ws/package-info.java b/sonar-server/src/main/java/org/sonar/server/ws/package-info.java
new file mode 100644
index 00000000000..6b4332971cf
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/ws/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.ws;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/sonar-server/src/test/java/org/sonar/server/ws/RequestTest.java b/sonar-server/src/test/java/org/sonar/server/ws/RequestTest.java
new file mode 100644
index 00000000000..dbd196d16ac
--- /dev/null
+++ b/sonar-server/src/test/java/org/sonar/server/ws/RequestTest.java
@@ -0,0 +1,44 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.ws;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Test;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class RequestTest {
+ @Test
+ public void string_params() {
+ Request request = new Request(ImmutableMap.of("foo", "bar"));
+ assertThat(request.param("none")).isNull();
+ assertThat(request.param("foo")).isEqualTo("bar");
+ }
+
+ @Test
+ public void int_params() {
+ Request request = new Request(ImmutableMap.of("foo", "123"));
+ assertThat(request.intParam("none")).isNull();
+ assertThat(request.intParam("foo")).isEqualTo(123);
+
+ assertThat(request.intParam("none", 456)).isEqualTo(456);
+ assertThat(request.intParam("foo", 456)).isEqualTo(123);
+ }
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/ws/WebServiceTest.java b/sonar-server/src/test/java/org/sonar/server/ws/WebServiceTest.java
new file mode 100644
index 00000000000..705c7060720
--- /dev/null
+++ b/sonar-server/src/test/java/org/sonar/server/ws/WebServiceTest.java
@@ -0,0 +1,171 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.ws;
+
+import org.junit.Test;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+import static org.mockito.Mockito.mock;
+
+public class WebServiceTest {
+
+ static class MetricWebService implements WebService {
+ boolean showCalled = false, createCalled = false;
+ @Override
+ public void define(Context context) {
+ NewController newController = context.newController("metric")
+ .setApi(true)
+ .setDescription("Metrics")
+ .setSince("3.2");
+ newController.newAction("show")
+ .setDescription("Show metric")
+ .setSince("4.1")
+ .setHandler(new RequestHandler() {
+ @Override
+ public void handle(Request request, Response response) {
+ show(request, response);
+ }
+ });
+ newController.newAction("create")
+ .setDescription("Create metric")
+ .setPost(true)
+ .setHandler(new RequestHandler() {
+ @Override
+ public void handle(Request request, Response response) {
+ create(request, response);
+ }
+ });
+ newController.done();
+ }
+
+ void show(Request request, Response response) {
+ showCalled = true;
+ }
+
+ void create(Request request, Response response) {
+ createCalled = true;
+ }
+ }
+
+
+ WebService.Context context = new WebService.Context();
+
+ @Test
+ public void no_web_services_by_default() {
+ assertThat(context.controllers()).isEmpty();
+ assertThat(context.controller("metric")).isNull();
+ }
+
+ @Test
+ public void define_web_service() {
+ MetricWebService metricWs = new MetricWebService();
+
+ metricWs.define(context);
+
+ WebService.Controller controller = context.controller("metric");
+ assertThat(controller).isNotNull();
+ assertThat(controller.key()).isEqualTo("metric");
+ assertThat(controller.description()).isEqualTo("Metrics");
+ assertThat(controller.since()).isEqualTo("3.2");
+ assertThat(controller.isApi()).isTrue();
+ assertThat(controller.path()).isEqualTo("ws/metric");
+ assertThat(controller.actions()).hasSize(2);
+ WebService.Action showAction = controller.action("show");
+ assertThat(showAction).isNotNull();
+ assertThat(showAction.key()).isEqualTo("show");
+ assertThat(showAction.description()).isEqualTo("Show metric");
+ assertThat(showAction.handler()).isNotNull();
+ assertThat(showAction.since()).isEqualTo("4.1");
+ assertThat(showAction.isPost()).isFalse();
+ assertThat(showAction.path()).isEqualTo("ws/metric/show");
+ WebService.Action createAction = controller.action("create");
+ assertThat(createAction).isNotNull();
+ assertThat(createAction.key()).isEqualTo("create");
+ assertThat(createAction.isPost()).isTrue();
+ }
+
+ @Test
+ public void fail_if_duplicated_ws_keys() {
+ MetricWebService metricWs = new MetricWebService();
+ metricWs.define(context);
+ try {
+ new WebService() {
+ @Override
+ public void define(Context context) {
+ NewController newController = context.newController("metric");
+ newController.newAction("delete");
+ newController.done();
+ }
+ }.define(context);
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessage("The web service 'metric' is defined multiple times");
+ }
+ }
+
+ @Test
+ public void fail_if_duplicated_action_keys() {
+ try {
+ new WebService() {
+ @Override
+ public void define(Context context) {
+ NewController newController = context.newController("rule");
+ newController.newAction("create");
+ newController.newAction("delete");
+ newController.newAction("delete");
+ newController.done();
+ }
+ }.define(context);
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessage("The action 'delete' is defined multiple times in the web service 'rule'");
+ }
+ }
+
+ @Test
+ public void fail_if_no_actions() {
+ try {
+ new WebService() {
+ @Override
+ public void define(Context context) {
+ context.newController("rule").done();
+ }
+ }.define(context);
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessage("At least one action must be declared in the web service 'rule'");
+ }
+ }
+
+ @Test
+ public void handle_request() {
+ MetricWebService metricWs = new MetricWebService();
+ metricWs.define(context);
+
+ assertThat(metricWs.showCalled).isFalse();
+ assertThat(metricWs.createCalled).isFalse();
+ context.controller("metric").action("show").handler().handle(mock(Request.class), mock(Response.class));
+ assertThat(metricWs.showCalled).isTrue();
+ assertThat(metricWs.createCalled).isFalse();
+ context.controller("metric").action("create").handler().handle(mock(Request.class), mock(Response.class));
+ assertThat(metricWs.createCalled).isTrue();
+ }
+}