import java.util.Map;
/**
- * Defines a web service implemented in Java (no Ruby on Rails at all).
+ * Defines a web service. Note that contrary to the deprecated {@link org.sonar.api.web.Webservice}
+ * the ws is fully implemented in Java and does not require any Ruby on Rails code.
+ *
+ * <p/>
+ * The classes implementing this extension point must be declared in {@link org.sonar.api.SonarPlugin#getExtensions()}.
+ *
+ * <h2>How to use</h2>
+ * <pre>
+ * public class HelloWs implements WebService {
+ * @Override
+ * public void define(Context context) {
+ * NewController controller = context.createController("api/hello");
+ * controller.setDescription("Web service example");
+ *
+ * // create the URL /api/hello/show
+ * controller.createAction("show")
+ * .setDescription("Entry point")
+ * .setHandler(new RequestHandler() {
+ * @Override
+ * public void handle(Request request, Response response) {
+ * // read request parameters and generates response output
+ * response.newJsonWriter()
+ * .prop("hello", request.mandatoryParam("key"))
+ * .close();
+ * }
+ * })
+ * .createParam("key", "Example key");
+ *
+ * // important to apply changes
+ * controller.done();
+ * }
+ * }
+ * </pre>
+ * <h2>How to unit test</h2>
+ * <pre>
+ * public class HelloWsTest {
+ * WebService ws = new HelloWs();
+ *
+ * @Test
+ * public void should_define_ws() throws Exception {
+ * // WsTester is available in the Maven artifact org.codehaus.sonar:sonar-plugin-api
+ * // with type "test-jar"
+ * WsTester tester = new WsTester(ws);
+ * WebService.Controller controller = tester.controller("api/hello");
+ * assertThat(controller).isNotNull();
+ * assertThat(controller.path()).isEqualTo("api/hello");
+ * assertThat(controller.description()).isNotEmpty();
+ * assertThat(controller.actions()).hasSize(1);
+ *
+ * WebService.Action show = controller.action("show");
+ * assertThat(show).isNotNull();
+ * assertThat(show.key()).isEqualTo("show");
+ * assertThat(index.handler()).isNotNull();
+ * }
+ * }
+ * </pre>
*
* @since 4.2
*/
class Context {
private final Map<String, Controller> controllers = Maps.newHashMap();
- public NewController newController(String path) {
+ /**
+ * Create a new controller.
+ * <p/>
+ * Structure of request URL is <code>http://<server>/<>controller path>/<action path>?<parameters></code>.
+ *
+ * @param path the controller path must not start or end with "/". It is recommended to start with "api/"
+ */
+ public NewController createController(String path) {
return new NewController(this, path);
}
private NewController(Context context, String path) {
if (StringUtils.isBlank(path)) {
- throw new IllegalArgumentException("Web service path can't be empty");
+ throw new IllegalArgumentException("WS controller path must not be empty");
+ }
+ if (StringUtils.startsWith(path, "/") || StringUtils.endsWith(path, "/")) {
+ throw new IllegalArgumentException("WS controller path must not start or end with slash: " + path);
}
this.context = context;
this.path = path;
}
+ /**
+ * Important - this method must be called in order to apply changes and make the
+ * controller available in {@link org.sonar.api.server.ws.WebService.Context#controllers()}
+ */
public void done() {
context.register(this);
}
+ /**
+ * Optional plain-text description
+ */
public NewController setDescription(@Nullable String s) {
this.description = s;
return this;
}
+ /**
+ * Optional version when the controller was created
+ */
public NewController setSince(@Nullable String s) {
this.since = s;
return this;
}
- public NewAction newAction(String actionKey) {
+ public NewAction createAction(String actionKey) {
if (actions.containsKey(actionKey)) {
throw new IllegalStateException(
String.format("The action '%s' is defined multiple times in the web service '%s'", actionKey, path)
return this;
}
- public NewParam newParam(String paramKey) {
+ public NewParam createParam(String paramKey) {
if (newParams.containsKey(paramKey)) {
throw new IllegalStateException(
String.format("The parameter '%s' is defined multiple times in the action '%s'", paramKey, key)
return newParam;
}
- public NewAction newParam(String paramKey, @Nullable String description) {
- newParam(paramKey).setDescription(description);
+ public NewAction createParam(String paramKey, @Nullable String description) {
+ createParam(paramKey).setDescription(description);
return this;
}
}
@Override
public void define(Context context) {
- NewController newController = context.newController("api/metric")
+ NewController newController = context.createController("api/metric")
.setDescription("Metrics")
.setSince("3.2");
- newController.newAction("show")
+ newController.createAction("show")
.setDescription("Show metric")
.setHandler(new RequestHandler() {
@Override
}
});
- newController.newAction("create")
+ newController.createAction("create")
.setDescription("Create metric")
.setSince("4.1")
.setPost(true)
new WebService() {
@Override
public void define(Context context) {
- NewController newController = context.newController("api/metric");
- newController.newAction("delete");
+ NewController newController = context.createController("api/metric");
+ newController.createAction("delete");
newController.done();
}
}.define(context);
new WebService() {
@Override
public void define(Context context) {
- NewController controller = context.newController("rule");
- controller.newAction("show");
+ NewController controller = context.createController("rule");
+ controller.createAction("show");
controller.done();
}
}.define(context);
new WebService() {
@Override
public void define(Context context) {
- NewController newController = context.newController("rule");
- newController.newAction("create");
- newController.newAction("delete");
- newController.newAction("delete");
+ NewController newController = context.createController("rule");
+ newController.createAction("create");
+ newController.createAction("delete");
+ newController.createAction("delete");
newController.done();
}
}.define(context);
new WebService() {
@Override
public void define(Context context) {
- context.newController("rule").done();
+ context.createController("rule").done();
}
}.define(context);
fail();
}
@Test
- public void fail_if_no_ws_path() {
+ public void fail_if_no_controller_path() {
try {
new WebService() {
@Override
public void define(Context context) {
- context.newController(null).done();
+ context.createController(null).done();
}
}.define(context);
fail();
} catch (IllegalArgumentException e) {
- assertThat(e).hasMessage("Web service path can't be empty");
+ assertThat(e).hasMessage("WS controller path must not be empty");
+ }
+ }
+
+ @Test
+ public void controller_path_must_not_start_with_slash() {
+ try {
+ new WebService() {
+ @Override
+ public void define(Context context) {
+ context.createController("/hello").done();
+ }
+ }.define(context);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessage("WS controller path must not start or end with slash: /hello");
+ }
+ }
+
+ @Test
+ public void controller_path_must_not_end_with_slash() {
+ try {
+ new WebService() {
+ @Override
+ public void define(Context context) {
+ context.createController("hello/").done();
+ }
+ }.define(context);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessage("WS controller path must not start or end with slash: hello/");
}
}
new WebService() {
@Override
public void define(Context context) {
- NewController newController = context.newController("api/rule");
- NewAction create = newController.newAction("create").setHandler(mock(RequestHandler.class));
- create.newParam("key").setDescription("Key of the new rule");
- create.newParam("severity");
+ NewController newController = context.createController("api/rule");
+ NewAction create = newController.createAction("create").setHandler(mock(RequestHandler.class));
+ create.createParam("key").setDescription("Key of the new rule");
+ create.createParam("severity");
newController.done();
}
}.define(context);
new WebService() {
@Override
public void define(Context context) {
- NewController controller = context.newController("api/rule");
- NewAction action = controller.newAction("create").setHandler(mock(RequestHandler.class));
- action.newParam("key");
- action.newParam("key");
+ NewController controller = context.createController("api/rule");
+ NewAction action = controller.createAction("create").setHandler(mock(RequestHandler.class));
+ action.createParam("key");
+ action.createParam("key");
controller.done();
}
}.define(context);
new WebService() {
@Override
public void define(Context context) {
- NewController newController = context.newController("api/rule");
- newController.newAction("create").setInternal(true).setHandler(mock(RequestHandler.class));
- newController.newAction("update").setInternal(true).setHandler(mock(RequestHandler.class));
+ NewController newController = context.createController("api/rule");
+ newController.createAction("create").setInternal(true).setHandler(mock(RequestHandler.class));
+ newController.createAction("update").setInternal(true).setHandler(mock(RequestHandler.class));
newController.done();
}
}.define(context);
@Override
public void define(Context context) {
- NewController controller = context.newController("api/issue_filters")
+ NewController controller = context.createController("api/issue_filters")
.setSince("4.2")
.setDescription("Issue Filters");
- NewAction app = controller.newAction("app");
+ NewAction app = controller.createAction("app");
app
.setDescription("Data required for rendering the page 'Issues'")
.setInternal(true)
}
});
- NewAction show = controller.newAction("show");
+ NewAction show = controller.createAction("show");
show
.setDescription("Get detail of issue filter")
.setSince("4.2")
show(request, response);
}
})
- .newParam("id");
+ .createParam("id");
- NewAction fav = controller.newAction("favorites");
+ NewAction fav = controller.createAction("favorites");
fav
.setDescription("The issue filters marked as favorite by request user")
.setSince("4.2")
favorites(request, response);
}
})
- .newParam("id");
+ .createParam("id");
controller.done();
}
@Override
public void define(Context context) {
- NewController controller = context.newController("api/issues");
+ NewController controller = context.createController("api/issues");
controller.setDescription("Coding rule issues");
- controller.newAction("show")
+ controller.createAction("show")
.setDescription("Detail of issue")
.setSince("4.2")
.setInternal(true)
.setHandler(showHandler)
- .newParam("key", "Issue key");
+ .createParam("key", "Issue key");
controller.done();
}
@Override
public void define(Context context) {
- NewController controller = context.newController("api/qualitygates")
+ NewController controller = context.createController("api/qualitygates")
.setSince("4.3")
.setDescription("This service can be used to manage quality gates, including requirements and project association.");
defineConditionActions(controller);
- controller.newAction("app")
+ controller.createAction("app")
.setInternal(true)
.setDescription("Get initialization items for the admin UI")
.setSince("4.3")
}
private void defineConditionActions(NewController controller) {
- NewAction createCondition = controller.newAction("create_condition")
+ NewAction createCondition = controller.createAction("create_condition")
.setDescription("Add a new condition to a quality gate.")
.setPost(true)
.setHandler(new RequestHandler() {
createCondition(request, response);
}
});
- createCondition.newParam(PARAM_GATE_ID).setDescription("The numerical ID of the quality gate for which the condition will be created.");
+ createCondition.createParam(PARAM_GATE_ID).setDescription("The numerical ID of the quality gate for which the condition will be created.");
addConditionParams(createCondition);
- NewAction updateCondition = controller.newAction("update_condition")
+ NewAction updateCondition = controller.createAction("update_condition")
.setDescription("Update a condition attached to a quality gate.")
.setPost(true)
.setHandler(new RequestHandler() {
updateCondition(request, response);
}
});
- updateCondition.newParam(PARAM_ID).setDescription("The numerical ID of the condition to update.");
+ updateCondition.createParam(PARAM_ID).setDescription("The numerical ID of the condition to update.");
addConditionParams(updateCondition);
- controller.newAction("delete_condition")
+ controller.createAction("delete_condition")
.setDescription("Remove a condition from a quality gate.")
.setPost(true)
.setHandler(new RequestHandler() {
public void handle(Request request, Response response) {
deleteCondition(request, response);
}
- }).newParam(PARAM_ID).setDescription("The numerical ID of the condition to delete.");
+ }).createParam(PARAM_ID).setDescription("The numerical ID of the condition to delete.");
}
private void addConditionParams(NewAction createCondition) {
- createCondition.newParam(PARAM_METRIC).setDescription("The key for the metric tested by this condition.");
- createCondition.newParam(PARAM_OPERATOR).setDescription("The operator used for the test, one of 'EQ', 'NE', 'LT', 'GT'.");
- createCondition.newParam(PARAM_PERIOD).setDescription("The optional period to use (for differential measures).");
- createCondition.newParam(PARAM_WARNING).setDescription("An optional value for the warning threshold.");
- createCondition.newParam(PARAM_ERROR).setDescription("An optional value for the error threshold.");
+ createCondition.createParam(PARAM_METRIC).setDescription("The key for the metric tested by this condition.");
+ createCondition.createParam(PARAM_OPERATOR).setDescription("The operator used for the test, one of 'EQ', 'NE', 'LT', 'GT'.");
+ createCondition.createParam(PARAM_PERIOD).setDescription("The optional period to use (for differential measures).");
+ createCondition.createParam(PARAM_WARNING).setDescription("An optional value for the warning threshold.");
+ createCondition.createParam(PARAM_ERROR).setDescription("An optional value for the error threshold.");
}
private void defineQualityGateActions(NewController controller) {
- controller.newAction("create")
+ controller.createAction("create")
.setDescription("Create a quality gate, given its name.")
.setPost(true)
.setHandler(new RequestHandler() {
public void handle(Request request, Response response) {
create(request, response);
}
- }).newParam(PARAM_NAME).setDescription("The name of the quality gate to create.");
+ }).createParam(PARAM_NAME).setDescription("The name of the quality gate to create.");
- NewAction copy = controller.newAction("copy")
+ NewAction copy = controller.createAction("copy")
.setDescription("Copy a quality gate, given its ID and the name for the new quality gate.")
.setPost(true)
.setHandler(new RequestHandler() {
copy(request, response);
}
});
- copy.newParam(PARAM_ID).setDescription("The ID of the source quality gate.");
- copy.newParam(PARAM_NAME).setDescription("The name of the destination quality gate.");
+ copy.createParam(PARAM_ID).setDescription("The ID of the source quality gate.");
+ copy.createParam(PARAM_NAME).setDescription("The name of the destination quality gate.");
- controller.newAction("set_as_default")
+ controller.createAction("set_as_default")
.setDescription("Select the default quality gate.")
.setPost(true)
.setHandler(new RequestHandler() {
public void handle(Request request, Response response) {
setDefault(request, response);
}
- }).newParam(PARAM_ID).setDescription("The ID of the quality gate to use as default.");
+ }).createParam(PARAM_ID).setDescription("The ID of the quality gate to use as default.");
- controller.newAction("unset_default")
+ controller.createAction("unset_default")
.setDescription("Unselect the default quality gate.")
.setPost(true)
.setHandler(new RequestHandler() {
}
});
- NewAction rename = controller.newAction("rename")
+ NewAction rename = controller.createAction("rename")
.setDescription("Rename a quality gate, given its id and new name.")
.setPost(true)
.setHandler(new RequestHandler() {
rename(request, response);
}
});
- rename.newParam(PARAM_ID).setDescription("The ID of the quality gate to rename.");
- rename.newParam(PARAM_NAME).setDescription("The new name for the quality gate.");
+ rename.createParam(PARAM_ID).setDescription("The ID of the quality gate to rename.");
+ rename.createParam(PARAM_NAME).setDescription("The new name for the quality gate.");
- controller.newAction("list")
+ controller.createAction("list")
.setDescription("List all quality gates.")
.setHandler(new RequestHandler() {
@Override
}
});
- controller.newAction("show")
+ controller.createAction("show")
.setDescription("Show a quality gate in details, with associated conditions.")
.setHandler(new RequestHandler() {
@Override
public void handle(Request request, Response response) {
show(request, response);
}
- }).newParam(PARAM_ID, "The ID of the quality gate; either this or name must be provided.")
- .newParam(PARAM_NAME, "The name of the quality gate; either this or id must be provided.");
+ }).createParam(PARAM_ID, "The ID of the quality gate; either this or name must be provided.")
+ .createParam(PARAM_NAME, "The name of the quality gate; either this or id must be provided.");
- controller.newAction("destroy")
+ controller.createAction("destroy")
.setDescription("Destroy a quality gate, given its id.")
.setPost(true)
.setHandler(new RequestHandler() {
public void handle(Request request, Response response) {
destroy(request, response);
}
- }).newParam(PARAM_ID).setDescription("The numerical ID of the quality gate to destroy.");
+ }).createParam(PARAM_ID).setDescription("The numerical ID of the quality gate to destroy.");
- NewAction search = controller.newAction("search")
+ NewAction search = controller.createAction("search")
.setDescription("Search projects associated (or not) with a quality gate.")
.setHandler(new RequestHandler() {
@Override
search(request, response);
}
});
- search.newParam(PARAM_GATE_ID).setDescription("The numerical ID of the quality gate considered for association.");
- search.newParam(PARAM_SELECTED).setDescription("Optionally, to search for projects associated (selected=selected) or not (selected=deselected).");
- search.newParam(PARAM_QUERY).setDescription("Optionally, part of the name of the projects to search for.");
- search.newParam(PARAM_PAGE);
- search.newParam(PARAM_PAGE_SIZE);
+ search.createParam(PARAM_GATE_ID).setDescription("The numerical ID of the quality gate considered for association.");
+ search.createParam(PARAM_SELECTED).setDescription("Optionally, to search for projects associated (selected=selected) or not (selected=deselected).");
+ search.createParam(PARAM_QUERY).setDescription("Optionally, part of the name of the projects to search for.");
+ search.createParam(PARAM_PAGE);
+ search.createParam(PARAM_PAGE_SIZE);
- NewAction select = controller.newAction("select")
+ NewAction select = controller.createAction("select")
.setPost(true)
.setHandler(new RequestHandler() {
@Override
select(request, response);
}
});
- select.newParam(PARAM_GATE_ID);
- select.newParam(PARAM_PROJECT_ID);
+ select.createParam(PARAM_GATE_ID);
+ select.createParam(PARAM_PROJECT_ID);
- NewAction deselect = controller.newAction("deselect")
+ NewAction deselect = controller.createAction("deselect")
.setPost(true)
.setHandler(new RequestHandler() {
@Override
deselect(request, response);
}
});
- deselect.newParam(PARAM_GATE_ID);
- deselect.newParam(PARAM_PROJECT_ID);
+ deselect.createParam(PARAM_GATE_ID);
+ deselect.createParam(PARAM_PROJECT_ID);
}
protected void copy(Request request, Response response) {
@Override
public void define(Context context) {
- NewController controller = context.newController("api/rule_tags")
+ NewController controller = context.createController("api/rule_tags")
.setDescription("Rule tags");
- controller.newAction("list")
+ controller.createAction("list")
.setDescription("List all available rule tags")
.setSince("4.2")
.setHandler(new RequestHandler() {
}
});
- controller.newAction("create")
+ controller.createAction("create")
.setPost(true)
.setDescription("Create a new rule tag")
.setSince("4.2")
create(request, response);
}
})
- .newParam("tag").setDescription("Value of the new rule tag");
+ .createParam("tag").setDescription("Value of the new rule tag");
controller.done();
}
@Override
public void define(Context context) {
- NewController controller = context.newController("api/rules")
+ NewController controller = context.createController("api/rules")
.setDescription("Coding rules");
- controller.newAction("list")
+ controller.createAction("list")
.setDescription("List rules that match the given criteria")
.setSince("4.3")
.setHandler(searchHandler)
- .newParam("s", "An optional query that will be matched against rule titles.")
- .newParam("k", "An optional query that will be matched exactly agains rule keys.")
- .newParam("ps", "Optional page size (default is 25).")
- .newParam("p", "Optional page number (default is 0).");
+ .createParam("s", "An optional query that will be matched against rule titles.")
+ .createParam("k", "An optional query that will be matched exactly agains rule keys.")
+ .createParam("ps", "Optional page size (default is 25).")
+ .createParam("p", "Optional page number (default is 0).");
- controller.newAction("show")
+ controller.createAction("show")
.setDescription("Detail of rule")
.setSince("4.2")
.setHandler(showHandler)
- .newParam("key", "Mandatory key of rule");
+ .createParam("key", "Mandatory key of rule");
- addTagParams(controller.newAction("add_tags")
+ addTagParams(controller.createAction("add_tags")
.setDescription("Add tags to a rule")
.setSince("4.2")
.setPost(true)
.setHandler(addTagsWsHandler));
- addTagParams(controller.newAction("remove_tags")
+ addTagParams(controller.createAction("remove_tags")
.setDescription("Remove tags from a rule")
.setSince("4.2")
.setPost(true)
}
private void addTagParams(final NewAction action) {
- action.newParam("key", "Full key of the rule");
- action.newParam("tags", "Comma separated list of tags");
+ action.createParam("key", "Full key of the rule");
+ action.createParam("tags", "Comma separated list of tags");
}
}
@Override
public void define(Context context) {
- NewController controller = context.newController("api/sources");
+ NewController controller = context.createController("api/sources");
- controller.newAction("show")
+ controller.createAction("show")
.setDescription("Show source of a component")
.setSince("4.2")
.setInternal(true)
.setHandler(showHandler)
- .newParam("key", "Component key");
+ .createParam("key", "Component key");
controller.done();
}
@Override
public void define(final Context context) {
- NewController controller = context.newController("api/webservices")
+ NewController controller = context.createController("api/webservices")
.setDescription("List web services")
.setSince("4.2");
- controller.newAction("list")
+ controller.createAction("list")
.setHandler(new RequestHandler() {
@Override
public void handle(Request request, Response response) {
static class MetricWebService implements WebService {
@Override
public void define(Context context) {
- NewController newController = context.newController("api/metric")
+ NewController newController = context.createController("api/metric")
.setDescription("Metrics")
.setSince("3.2");
// action with default values
- newController.newAction("show")
+ newController.createAction("show")
.setHandler(new RequestHandler() {
@Override
public void handle(Request request, Response response) {
// action with a lot of overridden values
- NewAction create = newController.newAction("create")
+ NewAction create = newController.createAction("create")
.setDescription("Create metric")
.setSince("4.1")
.setPost(true)
public void handle(Request request, Response response) {
}
});
- create.newParam("key").setDescription("Key of new metric");
- create.newParam("name");
+ create.createParam("key").setDescription("Key of new metric");
+ create.createParam("name");
newController.done();
}
}
static class SystemWebService implements WebService {
@Override
public void define(Context context) {
- NewController newController = context.newController("api/system");
- newController.newAction("health")
+ NewController newController = context.createController("api/system");
+ newController.createAction("health")
.setHandler(new RequestHandler() {
@Override
public void handle(Request request, Response response) {
}
}
});
- newController.newAction("ping")
+ newController.createAction("ping")
.setPost(true)
.setHandler(new RequestHandler() {
@Override
}
}
});
- newController.newAction("fail")
+ newController.createAction("fail")
.setHandler(new RequestHandler() {
@Override
public void handle(Request request, Response response) {
throw new IllegalStateException("Unexpected");
}
});
- newController.newAction("alive")
+ newController.createAction("alive")
.setHandler(new RequestHandler() {
@Override
public void handle(Request request, Response response) {
});
// parameter "message" is required but not "author"
- newController.newAction("print")
- .newParam("message", "required message")
- .newParam("author", "optional author")
+ newController.createAction("print")
+ .createParam("message", "required message")
+ .createParam("author", "optional author")
.setHandler(new RequestHandler() {
@Override
public void handle(Request request, Response response) {