package org.sonar.api.server.ws;
/**
- * Used to declare web services that are still implemented in rails.
+ * Used to declare web services that are still implemented in Ruby on Rails.
*
* @since 4.4
*/
public class RailsHandler implements RequestHandler {
- public static final RequestHandler INSTANCE = new RailsHandler(){};
+ public static final RequestHandler INSTANCE = new RailsHandler();
private RailsHandler() {
// Nothing
*/
public interface RequestHandler extends ServerExtension {
- void handle(Request request, Response response);
+ void handle(Request request, Response response) throws Exception;
}
*/
package org.sonar.api.server.ws;
+import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.ServerExtension;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
-
+import java.io.IOException;
+import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.Map;
private boolean post = false, isInternal = false;
private RequestHandler handler;
private Map<String, NewParam> newParams = Maps.newHashMap();
+ private URL responseExample = null;
+ private String responseExampleFormat = null;
private NewAction(String key) {
this.key = key;
return this;
}
+ /**
+ * Link to the document containing an example of response. Content must be UTF-8 encoded.
+ * <p/>
+ * Example:
+ * <pre>
+ * newAction.setResponseExample(getClass().getResource("/org/sonar/my-ws-response-example.json"));
+ * </pre>
+ *
+ * @since 4.4
+ */
+ public NewAction setResponseExample(@Nullable URL url) {
+ this.responseExample = url;
+ return this;
+ }
+
+ /**
+ * Used only if {@link #setResponseExample(java.net.URL)} is set. Example of values: "xml", "json", "txt", "csv".
+ *
+ * @since 4.4
+ */
+ public NewAction setResponseExampleFormat(@Nullable String format) {
+ this.responseExampleFormat = format;
+ return this;
+ }
+
public NewParam createParam(String paramKey) {
if (newParams.containsKey(paramKey)) {
throw new IllegalStateException(
private final boolean post, isInternal;
private final RequestHandler handler;
private final Map<String, Param> params;
+ private final URL responseExample;
+ private final String responseExampleFormat;
private Action(Controller controller, NewAction newAction) {
this.key = newAction.key;
this.since = StringUtils.defaultIfBlank(newAction.since, controller.since);
this.post = newAction.post;
this.isInternal = newAction.isInternal;
+ this.responseExample = newAction.responseExample;
+ this.responseExampleFormat = newAction.responseExampleFormat;
if (newAction.handler == null) {
- throw new IllegalStateException("RequestHandler is not set on action " + path);
+ throw new IllegalArgumentException("RequestHandler is not set on action " + path);
}
this.handler = newAction.handler;
return handler;
}
+ /**
+ * @see org.sonar.api.server.ws.WebService.NewAction#setResponseExample(java.net.URL)
+ */
+ @CheckForNull
+ public URL responseExample() {
+ return responseExample;
+ }
+
+ /**
+ * @see org.sonar.api.server.ws.WebService.NewAction#setResponseExample(java.net.URL)
+ */
+ @CheckForNull
+ public String responseExampleAsString() {
+ try {
+ if (responseExample != null) {
+ return IOUtils.toString(responseExample, Charsets.UTF_8);
+ }
+ return null;
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to load " + responseExample, e);
+ }
+ }
+
+ /**
+ * @see org.sonar.api.server.ws.WebService.NewAction#setResponseExampleFormat(String)
+ */
+ @CheckForNull
+ public String responseExampleFormat() {
+ return responseExampleFormat;
+ }
+
@CheckForNull
public Param param(String key) {
return params.get(key);
/**
* Is the parameter required or optional ? Default value is false (optional).
+ *
* @since 4.4
*/
public NewParam setRequired(boolean b) {
/**
* Exhaustive list of possible values when it makes sense, for example
* list of severities.
+ *
* @since 4.4
*/
public NewParam setPossibleValues(@Nullable String... s) {
/**
* Is the parameter required or optional ?
+ *
* @since 4.4
*/
public boolean isRequired() {
*/
package org.sonar.api.server.ws;
+import org.apache.commons.lang.StringUtils;
import org.junit.Test;
+import java.net.MalformedURLException;
+import java.net.URL;
+
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
import static org.mockito.Mockito.mock;
.setSince("4.1")
.setPost(true)
.setInternal(true)
+ .setResponseExampleFormat("txt")
+ .setResponseExample(getClass().getResource("/org/sonar/api/server/ws/WebServiceTest/response-example.txt"))
.setHandler(new RequestHandler() {
@Override
public void handle(Request request, Response response) {
}
}.define(context);
fail();
- } catch (IllegalStateException e) {
+ } catch (IllegalArgumentException e) {
assertThat(e).hasMessage("RequestHandler is not set on action rule/show");
}
}
assertThat(context.controller("api/rule").isInternal()).isTrue();
}
+
+ @Test
+ public void response_example() {
+ MetricWebService metricWs = new MetricWebService();
+ metricWs.define(context);
+ WebService.Action action = context.controller("api/metric").action("create");
+
+ assertThat(action.responseExampleFormat()).isEqualTo("txt");
+ assertThat(action.responseExample()).isNotNull();
+ assertThat(StringUtils.trim(action.responseExampleAsString())).isEqualTo("example of WS response");
+ }
+
+ @Test
+ public void fail_to_open_response_example() {
+ WebService ws = new WebService() {
+ @Override
+ public void define(Context context) {
+ try {
+ NewController controller = context.createController("foo");
+ controller
+ .createAction("bar")
+ .setHandler(mock(RequestHandler.class))
+ .setResponseExample(new URL("file:/does/not/exist"));
+ controller.done();
+
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+ }
+ };
+ ws.define(context);
+
+ WebService.Action action = context.controller("foo").action("bar");
+ try {
+ action.responseExampleAsString();
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessage("Fail to load file:/does/not/exist");
+ }
+ }
}
--- /dev/null
+example of WS response
*/
package org.sonar.server.rule2;
-import org.apache.commons.beanutils.BeanUtils;
-
import org.sonar.api.ServerComponent;
import org.sonar.api.rule.RuleKey;
import org.sonar.core.rule.RuleDao;
import org.sonar.server.search.Hit;
import javax.annotation.CheckForNull;
-
import java.util.Collection;
import java.util.Collections;
private RuleDao dao;
private RuleIndex index;
- public RuleService(RuleDao dao, RuleIndex index){
+ public RuleService(RuleDao dao, RuleIndex index) {
this.dao = dao;
this.index = index;
}
@CheckForNull
public Rule getByKey(RuleKey key) {
Hit hit = index.getByKey(key);
- if(hit != null){
+ if (hit != null) {
return toRule(hit);
} else {
return null;
}
}
- public Collection<Hit> search(RuleQuery query){
+ public Collection<Hit> search(RuleQuery query) {
return Collections.emptyList();
}
- public static Rule toRule(RuleDto ruleDto){
+ public static Rule toRule(RuleDto ruleDto) {
return new RuleImpl();
}
- public static Rule toRule(Hit hit){
+ public static Rule toRule(Hit hit) {
// BeanUtils.setProperty(bean, name, value);
return new RuleImpl();
}
.setDescription("Get source code. Parameter 'output' with value 'raw' is missing before being marked as a public WS.")
.setSince("4.2")
.setInternal(true)
+ .setResponseExampleFormat("json")
+ .setResponseExample(getClass().getResource("/org/sonar/server/source/ws/example-show.json"))
.setHandler(this);
action
*/
package org.sonar.server.ws;
+import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.collect.Ordering;
+import org.apache.commons.io.IOUtils;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.RequestHandler;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.text.JsonWriter;
+import java.io.IOException;
import java.util.List;
/**
@Override
public void define(final Context context) {
- NewController controller = context.createController("api/webservices")
- .setDescription("List web services")
- .setSince("4.2");
- controller.createAction("list")
+ NewController controller = context
+ .createController("api/webservices")
+ .setDescription("List web services");
+ defineList(context, controller);
+ defineResponseExample(context, controller);
+ controller.done();
+ }
+
+ private void defineList(final Context context, NewController controller) {
+ controller
+ .createAction("list")
+ .setSince("4.2")
.setHandler(new RequestHandler() {
@Override
public void handle(Request request, Response response) {
- list(context.controllers(), response);
+ handleList(context.controllers(), response);
}
});
- controller.done();
}
- void list(List<Controller> controllers, Response response) {
+ private void defineResponseExample(final Context context, NewController controller) {
+ NewAction action = controller
+ .createAction("responseExample")
+ .setHandler(new RequestHandler() {
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ Controller controller = context.controller(request.mandatoryParam("controller"));
+ Action action = controller.action(request.mandatoryParam("action"));
+ handleResponseExample(action, response);
+ }
+ });
+ action.createParam("controller").setRequired(true);
+ action.createParam("action").setRequired(true);
+ }
+
+ private void handleResponseExample(Action action, Response response) throws IOException {
+ if (action.responseExample() != null) {
+ response
+ .newJsonWriter()
+ .beginObject()
+ .prop("format", action.responseExampleFormat())
+ .prop("example", IOUtils.toString(action.responseExample(), Charsets.UTF_8))
+ .endObject()
+ .close();
+ } else {
+ response.noContent();
+ }
+ }
+
+ void handleList(List<Controller> controllers, Response response) {
JsonWriter writer = response.newJsonWriter();
writer.beginObject();
writer.name("webServices").beginArray();
}
});
for (Controller controller : ordering.sortedCopy(controllers)) {
- write(writer, controller);
+ writeController(writer, controller);
}
writer.endArray();
writer.endObject();
writer.close();
}
- private void write(JsonWriter writer, Controller controller) {
+ private void writeController(JsonWriter writer, Controller controller) {
writer.beginObject();
writer.prop("path", controller.path());
writer.prop("since", controller.since());
});
writer.name("actions").beginArray();
for (Action action : ordering.sortedCopy(controller.actions())) {
- write(writer, action);
+ writeAction(writer, action);
}
writer.endArray();
writer.endObject();
}
- private void write(JsonWriter writer, Action action) {
+ private void writeAction(JsonWriter writer, Action action) {
writer.beginObject();
writer.prop("key", action.key());
writer.prop("description", action.description());
writer.prop("since", action.since());
writer.prop("internal", action.isInternal());
writer.prop("post", action.isPost());
+ writer.prop("hasResponseExample", action.responseExample()!=null);
if (!action.params().isEmpty()) {
// sort parameters by key
Ordering<Param> ordering = Ordering.natural().onResultOf(new Function<Param, String>() {
});
writer.name("params").beginArray();
for (Param param : ordering.sortedCopy(action.params())) {
- write(writer, param);
+ writeParam(writer, param);
}
writer.endArray();
}
writer.endObject();
}
- private void write(JsonWriter writer, Param param) {
+ private void writeParam(JsonWriter writer, Param param) {
writer.beginObject();
writer.prop("key", param.key());
writer.prop("description", param.description());
--- /dev/null
+{
+ "source": {
+ "20": "<span class=\"k\">package </span>org.sonar.check;",
+ "21": "",
+ "22": "<span class=\"k\">public </span><span class=\"k\">enum </span><span class=\"sym-922 sym\">Priority</span> {",
+ "23": " <span class=\"cppd\">/**</span>",
+ "24": "<span class=\"cppd\"> * WARNING : DO NOT CHANGE THE ENUMERATION ORDER</span>",
+ "25": "<span class=\"cppd\"> * the enum ordinal is used for db persistence</span>"
+ },
+ "scm": {
+ "20": ["simon.brandhof@gmail.com", "2010-09-06"]
+ }
+}
*/
package org.sonar.server.source.ws;
+import org.apache.commons.io.IOUtils;
import org.junit.Before;
import org.junit.Test;
+import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.source.SourceService;
request.execute().assertJson(getClass(), "show_source.json");
}
+ @Test
+ public void response_example_exists() throws Exception {
+ WebService.Action show = tester.controller("api/sources").action("show");
+ assertThat(show.responseExampleFormat()).isEqualTo("json");
+ assertThat(show.responseExampleAsString()).isNotEmpty();
+ }
+
@Test
public void fail_to_show_source_if_no_source_found() throws Exception {
String componentKey = "src/Foo.java";
assertThat(controller).isNotNull();
assertThat(controller.path()).isEqualTo("api/webservices");
assertThat(controller.description()).isNotEmpty();
- assertThat(controller.since()).isEqualTo("4.2");
- assertThat(controller.actions()).hasSize(1);
+ assertThat(controller.actions()).hasSize(2);
WebService.Action index = controller.action("list");
assertThat(index).isNotNull();
assertThat(index.since()).isEqualTo("4.2");
assertThat(index.isPost()).isFalse();
assertThat(index.isInternal()).isFalse();
+
+ assertThat(controller.action("responseExample")).isNotNull();
}
@Test
- public void index() throws Exception {
+ public void list() throws Exception {
WsTester tester = new WsTester(ws, new MetricWebService());
tester.newRequest("api/webservices", "list").execute().assertJson(getClass(), "list.json");
}
+ @Test
+ public void response_example() throws Exception {
+ WsTester tester = new WsTester(ws, new MetricWebService());
+ tester
+ .newRequest("api/webservices", "responseExample")
+ .setParam("controller", "api/metric")
+ .setParam("action", "create")
+ .execute().assertJson(getClass(), "response_example.json");
+ }
+
static class MetricWebService implements WebService {
@Override
public void define(Context context) {
.setSince("4.1")
.setPost(true)
.setInternal(true)
+ .setResponseExample(getClass().getResource("/org/sonar/server/ws/ListingWsTest/metrics_example.json"))
+ .setResponseExampleFormat("json")
.setHandler(new RequestHandler() {
@Override
public void handle(Request request, Response response) {
return params.get(key);
}
- public Result execute() {
+ public Result execute() throws Exception {
TestResponse response = new TestResponse();
action.handler().handle(this, response);
return new Result(response);
* at src/test/resources/org/foo/BarTest/index.json.
*
* @param clazz the test class
- * @param jsonResourceFilename name of the file containing the expected JSON
+ * @param expectedJsonFilename name of the file containing the expected JSON
*/
public Result assertJson(Class clazz, String expectedJsonFilename) throws Exception {
String path = clazz.getSimpleName() + "/" + expectedJsonFilename;
-{
- "webServices": [
- {
- "path": "api/metric",
- "since": "3.2",
- "description": "Metrics",
- "actions": [
- {
- "key": "create",
- "description": "Create metric",
- "since": "4.1",
- "internal": true,
- "post": true,
- "params": [
- {
- "key": "name",
- "required": false
- },
- {
- "key": "severity",
- "description": "Severity",
- "required": true,
- "defaultValue": "BLOCKER",
- "exampleValue": "INFO",
- "possibleValues": ["BLOCKER", "INFO"]
-
- }
- ]
- },
- {
- "key": "show",
- "since": "3.2",
- "internal": false,
- "post": false
- }
- ]
- },
- {
- "path": "api/webservices",
- "since": "4.2",
- "description": "List web services",
- "actions": [
- {
- "key": "list",
- "since": "4.2",
- "internal": false,
- "post": false
- }
- ]
- }
- ]}
+{"webServices": [
+ {
+ "path": "api/metric",
+ "since": "3.2",
+ "description": "Metrics",
+ "actions": [
+ {
+ "key": "create",
+ "description": "Create metric",
+ "since": "4.1",
+ "internal": true,
+ "post": true,
+ "hasResponseExample": true,
+ "params": [
+ {
+ "key": "name",
+ "required": false
+ },
+ {
+ "key": "severity",
+ "description": "Severity",
+ "required": true,
+ "defaultValue": "BLOCKER",
+ "exampleValue": "INFO",
+ "possibleValues": ["BLOCKER", "INFO"]
+ }
+ ]
+ },
+ {
+ "key": "show",
+ "since": "3.2",
+ "internal": false,
+ "post": false,
+ "hasResponseExample": false
+ }
+ ]
+ },
+ {
+ "path": "api/webservices",
+ "description": "List web services",
+ "actions": [
+ {
+ "key": "list",
+ "since": "4.2",
+ "internal": false,
+ "post": false,
+ "hasResponseExample": false
+ },
+ {
+ "key": "responseExample",
+ "internal": false,
+ "post": false,
+ "hasResponseExample": false,
+ "params": [
+ {
+ "key": "action",
+ "required": true
+ },
+ {
+ "key": "controller",
+ "required": true
+ }
+ ]
+ }
+ ]
+ }
+]}
--- /dev/null
+{"metrics":[]}
--- /dev/null
+{"format":"json","example":"{\"metrics\":[]}\n"}