You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

WebService.java 29KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.api.server.ws;
  21. import java.io.IOException;
  22. import java.net.URL;
  23. import java.nio.charset.StandardCharsets;
  24. import java.util.ArrayList;
  25. import java.util.Collection;
  26. import java.util.Collections;
  27. import java.util.HashMap;
  28. import java.util.LinkedHashSet;
  29. import java.util.List;
  30. import java.util.Map;
  31. import java.util.Objects;
  32. import java.util.Set;
  33. import java.util.stream.Collectors;
  34. import javax.annotation.CheckForNull;
  35. import javax.annotation.Nullable;
  36. import javax.annotation.concurrent.Immutable;
  37. import org.apache.commons.io.FilenameUtils;
  38. import org.apache.commons.io.IOUtils;
  39. import org.apache.commons.lang.StringUtils;
  40. import org.sonar.api.ExtensionPoint;
  41. import org.sonar.api.server.ServerSide;
  42. import org.sonar.api.utils.log.Logger;
  43. import org.sonar.api.utils.log.Loggers;
  44. import static java.lang.String.format;
  45. import static java.util.Arrays.asList;
  46. import static java.util.Arrays.stream;
  47. import static java.util.Objects.requireNonNull;
  48. import static org.sonar.api.utils.Preconditions.checkArgument;
  49. import static org.sonar.api.utils.Preconditions.checkState;
  50. /**
  51. * Defines a web service.
  52. * <br>
  53. * <br>
  54. * The classes implementing this extension point must be declared by {@link org.sonar.api.Plugin}.
  55. * <br>
  56. * <h3>How to use</h3>
  57. * <pre>
  58. * public class HelloWs implements WebService {
  59. * {@literal @}Override
  60. * public void define(Context context) {
  61. * NewController controller = context.createController("api/hello");
  62. * controller.setDescription("Web service example");
  63. * // create the URL /api/hello/show
  64. * controller.createAction("show")
  65. * .setDescription("Entry point")
  66. * .setHandler(new RequestHandler() {
  67. * {@literal @}Override
  68. * public void handle(Request request, Response response) {
  69. * // read request parameters and generate response output
  70. * response.newJsonWriter()
  71. * .beginObject()
  72. * .prop("hello", request.mandatoryParam("key"))
  73. * .endObject()
  74. * .close();
  75. * }
  76. * })
  77. * .createParam("key").setDescription("Example key").setRequired(true);
  78. * // important to apply changes
  79. * controller.done();
  80. * }
  81. * }
  82. * </pre>
  83. * <p>
  84. * Since version 5.5, a web service can call another web service to get some data. See {@link Request#localConnector()}
  85. * provided by {@link RequestHandler#handle(Request, Response)}.
  86. *
  87. * @since 4.2
  88. */
  89. @ServerSide
  90. @ExtensionPoint
  91. public interface WebService extends Definable<WebService.Context> {
  92. class Context {
  93. private final Map<String, Controller> controllers = new HashMap<>();
  94. /**
  95. * Create a new controller.
  96. * <br>
  97. * Structure of request URL is <code>http://&lt;server&gt;/&lt;controller path&gt;/&lt;action path&gt;?&lt;parameters&gt;</code>.
  98. *
  99. * @param path the controller path must not start or end with "/". It is recommended to start with "api/"
  100. * and to use lower-case format with underscores, for example "api/coding_rules". Usual actions
  101. * are "search", "list", "show", "create" and "delete".
  102. * the plural form is recommended - ex: api/projects
  103. */
  104. public NewController createController(String path) {
  105. return new NewController(this, path);
  106. }
  107. private void register(NewController newController) {
  108. if (controllers.containsKey(newController.path)) {
  109. throw new IllegalStateException(
  110. format("The web service '%s' is defined multiple times", newController.path));
  111. }
  112. controllers.put(newController.path, new Controller(newController));
  113. }
  114. @CheckForNull
  115. public Controller controller(String key) {
  116. return controllers.get(key);
  117. }
  118. public List<Controller> controllers() {
  119. return Collections.unmodifiableList(new ArrayList<>(controllers.values()));
  120. }
  121. }
  122. class NewController {
  123. private final Context context;
  124. private final String path;
  125. private String description;
  126. private String since;
  127. private final Map<String, NewAction> actions = new HashMap<>();
  128. private NewController(Context context, String path) {
  129. if (StringUtils.isBlank(path)) {
  130. throw new IllegalArgumentException("WS controller path must not be empty");
  131. }
  132. if (StringUtils.startsWith(path, "/") || StringUtils.endsWith(path, "/")) {
  133. throw new IllegalArgumentException("WS controller path must not start or end with slash: " + path);
  134. }
  135. this.context = context;
  136. this.path = path;
  137. }
  138. /**
  139. * Important - this method must be called in order to apply changes and make the
  140. * controller available in {@link org.sonar.api.server.ws.WebService.Context#controllers()}
  141. */
  142. public void done() {
  143. context.register(this);
  144. }
  145. /**
  146. * Optional description (accept HTML)
  147. */
  148. public NewController setDescription(@Nullable String s) {
  149. this.description = s;
  150. return this;
  151. }
  152. /**
  153. * Optional version when the controller was created
  154. */
  155. public NewController setSince(@Nullable String s) {
  156. this.since = s;
  157. return this;
  158. }
  159. public NewAction createAction(String actionKey) {
  160. if (actions.containsKey(actionKey)) {
  161. throw new IllegalStateException(
  162. format("The action '%s' is defined multiple times in the web service '%s'", actionKey, path));
  163. }
  164. NewAction action = new NewAction(actionKey);
  165. actions.put(actionKey, action);
  166. return action;
  167. }
  168. }
  169. @Immutable
  170. class Controller {
  171. private final String path;
  172. private final String description;
  173. private final String since;
  174. private final Map<String, Action> actions;
  175. private Controller(NewController newController) {
  176. checkState(!newController.actions.isEmpty(), "At least one action must be declared in the web service '%s'", newController.path);
  177. this.path = newController.path;
  178. this.description = newController.description;
  179. this.since = newController.since;
  180. Map<String, Action> mapBuilder = new HashMap<>();
  181. for (NewAction newAction : newController.actions.values()) {
  182. mapBuilder.put(newAction.key, new Action(this, newAction));
  183. }
  184. this.actions = Collections.unmodifiableMap(mapBuilder);
  185. }
  186. public String path() {
  187. return path;
  188. }
  189. @CheckForNull
  190. public String description() {
  191. return description;
  192. }
  193. @CheckForNull
  194. public String since() {
  195. return since;
  196. }
  197. @CheckForNull
  198. public Action action(String actionKey) {
  199. return actions.get(actionKey);
  200. }
  201. public Collection<Action> actions() {
  202. return actions.values();
  203. }
  204. /**
  205. * Returns true if all the actions are for internal use
  206. *
  207. * @see org.sonar.api.server.ws.WebService.Action#isInternal()
  208. * @since 4.3
  209. */
  210. public boolean isInternal() {
  211. for (Action action : actions()) {
  212. if (!action.isInternal()) {
  213. return false;
  214. }
  215. }
  216. return true;
  217. }
  218. }
  219. class NewAction {
  220. private final String key;
  221. private static final String PAGE_PARAM_DESCRIPTION = "1-based page number";
  222. private String deprecatedKey;
  223. private String description;
  224. private String since;
  225. private String deprecatedSince;
  226. private boolean post = false;
  227. private boolean isInternal = false;
  228. private RequestHandler handler;
  229. private Map<String, NewParam> newParams = new HashMap<>();
  230. private URL responseExample = null;
  231. private List<Change> changelog = new ArrayList<>();
  232. private NewAction(String key) {
  233. this.key = key;
  234. }
  235. public NewAction setDeprecatedKey(@Nullable String s) {
  236. this.deprecatedKey = s;
  237. return this;
  238. }
  239. /**
  240. * Used in Orchestrator
  241. */
  242. public NewAction setDescription(@Nullable String description) {
  243. this.description = description;
  244. return this;
  245. }
  246. /**
  247. * @since 5.6
  248. */
  249. public NewAction setDescription(@Nullable String description, Object... descriptionArgument) {
  250. this.description = description == null ? null : String.format(description, descriptionArgument);
  251. return this;
  252. }
  253. public NewAction setSince(@Nullable String s) {
  254. this.since = s;
  255. return this;
  256. }
  257. /**
  258. * @since 5.3
  259. */
  260. public NewAction setDeprecatedSince(@Nullable String deprecatedSince) {
  261. this.deprecatedSince = deprecatedSince;
  262. return this;
  263. }
  264. public NewAction setPost(boolean b) {
  265. this.post = b;
  266. return this;
  267. }
  268. /**
  269. * Internal actions are not displayed by default in the web api documentation. They are
  270. * displayed only when the check-box "Show Internal API" is selected. By default
  271. * an action is not internal.
  272. */
  273. public NewAction setInternal(boolean b) {
  274. this.isInternal = b;
  275. return this;
  276. }
  277. public NewAction setHandler(RequestHandler h) {
  278. this.handler = h;
  279. return this;
  280. }
  281. /**
  282. * Link to the document containing an example of response. Content must be UTF-8 encoded.
  283. * <br>
  284. * Example:
  285. * <pre>
  286. * newAction.setResponseExample(getClass().getResource("/org/sonar/my-ws-response-example.json"));
  287. * </pre>
  288. *
  289. * @since 4.4
  290. */
  291. public NewAction setResponseExample(@Nullable URL url) {
  292. this.responseExample = url;
  293. return this;
  294. }
  295. /**
  296. * List of changes made to the contract or valuable insight. Example: changes to the response format.
  297. *
  298. * @since 6.4
  299. */
  300. public NewAction setChangelog(Change... changes) {
  301. this.changelog = stream(requireNonNull(changes))
  302. .filter(Objects::nonNull)
  303. .collect(Collectors.toList());
  304. return this;
  305. }
  306. public NewParam createParam(String paramKey) {
  307. checkState(!newParams.containsKey(paramKey), "The parameter '%s' is defined multiple times in the action '%s'", paramKey, key);
  308. NewParam newParam = new NewParam(paramKey);
  309. newParams.put(paramKey, newParam);
  310. return newParam;
  311. }
  312. /**
  313. * Add predefined parameters related to pagination of results.
  314. */
  315. public NewAction addPagingParams(int defaultPageSize) {
  316. createParam(Param.PAGE)
  317. .setDescription(PAGE_PARAM_DESCRIPTION)
  318. .setExampleValue("42")
  319. .setDefaultValue("1");
  320. createParam(Param.PAGE_SIZE)
  321. .setDescription("Page size. Must be greater than 0.")
  322. .setExampleValue("20")
  323. .setDefaultValue(String.valueOf(defaultPageSize));
  324. return this;
  325. }
  326. /**
  327. * Add predefined parameters related to pagination of results with a maximum page size.
  328. * Note the maximum is a documentation only feature. It does not check anything.
  329. */
  330. public NewAction addPagingParams(int defaultPageSize, int maxPageSize) {
  331. createPageParam();
  332. createPageSize(defaultPageSize, maxPageSize);
  333. return this;
  334. }
  335. public NewParam createPageParam() {
  336. return createParam(Param.PAGE)
  337. .setDescription(PAGE_PARAM_DESCRIPTION)
  338. .setExampleValue("42")
  339. .setDefaultValue("1");
  340. }
  341. public NewParam createPageSize(int defaultPageSize, int maxPageSize) {
  342. return createParam(Param.PAGE_SIZE)
  343. .setDefaultValue(String.valueOf(defaultPageSize))
  344. .setMaximumValue(maxPageSize)
  345. .setDescription("Page size. Must be greater than 0 and less or equal than " + maxPageSize)
  346. .setExampleValue("20");
  347. }
  348. /**
  349. * Add predefined parameters related to pagination of results with a maximum page size.
  350. *
  351. * @since 7.1
  352. */
  353. public NewAction addPagingParamsSince(int defaultPageSize, int maxPageSize, String version) {
  354. createParam(Param.PAGE)
  355. .setDescription(PAGE_PARAM_DESCRIPTION)
  356. .setExampleValue("42")
  357. .setDefaultValue("1")
  358. .setSince(version);
  359. createParam(Param.PAGE_SIZE)
  360. .setDescription("Page size. Must be greater than 0 and less than " + maxPageSize)
  361. .setDefaultValue(String.valueOf(defaultPageSize))
  362. .setMaximumValue(maxPageSize)
  363. .setExampleValue("20")
  364. .setSince(version);
  365. return this;
  366. }
  367. /**
  368. * Creates the parameter {@link org.sonar.api.server.ws.WebService.Param#FIELDS}, which is
  369. * used to restrict the number of fields returned in JSON response.
  370. */
  371. public NewAction addFieldsParam(Collection<?> possibleValues) {
  372. createFieldsParam(possibleValues);
  373. return this;
  374. }
  375. public NewParam createFieldsParam(Collection<?> possibleValues) {
  376. return createParam(Param.FIELDS)
  377. .setDescription("Comma-separated list of the fields to be returned in response. All the fields are returned by default.")
  378. .setPossibleValues(possibleValues);
  379. }
  380. /**
  381. * Creates the parameter {@link org.sonar.api.server.ws.WebService.Param#TEXT_QUERY}, which is
  382. * used to search for a subset of fields containing the supplied string.
  383. * <p>
  384. * The fields must be in the <strong>plural</strong> form (ex: "names", "keys").
  385. * </p>
  386. */
  387. public NewAction addSearchQuery(String exampleValue, String... pluralFields) {
  388. createSearchQuery(exampleValue, pluralFields);
  389. return this;
  390. }
  391. /**
  392. * Creates the parameter {@link org.sonar.api.server.ws.WebService.Param#TEXT_QUERY}, which is
  393. * used to search for a subset of fields containing the supplied string.
  394. * <p>
  395. * The fields must be in the <strong>plural</strong> form (ex: "names", "keys").
  396. * </p>
  397. */
  398. public NewParam createSearchQuery(String exampleValue, String... pluralFields) {
  399. String actionDescription = format("Limit search to %s that contain the supplied string.", String.join(" or ", pluralFields));
  400. return createParam(Param.TEXT_QUERY)
  401. .setDescription(actionDescription)
  402. .setExampleValue(exampleValue);
  403. }
  404. /**
  405. * Add predefined parameters related to sorting of results.
  406. */
  407. public <V> NewAction addSortParams(Collection<V> possibleValues, @Nullable V defaultValue, boolean defaultAscending) {
  408. createSortParams(possibleValues, defaultValue, defaultAscending);
  409. return this;
  410. }
  411. /**
  412. * Add predefined parameters related to sorting of results.
  413. */
  414. public <V> NewParam createSortParams(Collection<V> possibleValues, @Nullable V defaultValue, boolean defaultAscending) {
  415. createParam(Param.ASCENDING)
  416. .setDescription("Ascending sort")
  417. .setBooleanPossibleValues()
  418. .setDefaultValue(defaultAscending);
  419. return createParam(Param.SORT)
  420. .setDescription("Sort field")
  421. .setDefaultValue(defaultValue)
  422. .setPossibleValues(possibleValues);
  423. }
  424. /**
  425. * Add 'selected=(selected|deselected|all)' for select-list oriented WS.
  426. */
  427. public NewAction addSelectionModeParam() {
  428. createParam(Param.SELECTED)
  429. .setDescription("Depending on the value, show only selected items (selected=selected), deselected items (selected=deselected), " +
  430. "or all items with their selection status (selected=all).")
  431. .setDefaultValue(SelectionMode.SELECTED.value())
  432. .setPossibleValues(SelectionMode.possibleValues());
  433. return this;
  434. }
  435. }
  436. @Immutable
  437. class Action {
  438. private static final Logger LOGGER = Loggers.get(Action.class);
  439. private final String key;
  440. private final String deprecatedKey;
  441. private final String path;
  442. private final String description;
  443. private final String since;
  444. private final String deprecatedSince;
  445. private final boolean post;
  446. private final boolean isInternal;
  447. private final RequestHandler handler;
  448. private final Map<String, Param> params;
  449. private final URL responseExample;
  450. private final List<Change> changelog;
  451. private Action(Controller controller, NewAction newAction) {
  452. this.key = newAction.key;
  453. this.deprecatedKey = newAction.deprecatedKey;
  454. this.path = format("%s/%s", controller.path(), key);
  455. this.description = newAction.description;
  456. this.since = newAction.since;
  457. this.deprecatedSince = newAction.deprecatedSince;
  458. this.post = newAction.post;
  459. this.isInternal = newAction.isInternal;
  460. this.responseExample = newAction.responseExample;
  461. this.handler = newAction.handler;
  462. this.changelog = newAction.changelog;
  463. checkState(this.handler != null, "RequestHandler is not set on action %s", path);
  464. logWarningIf(this.description == null || this.description.isEmpty(), "Description is not set on action " + path);
  465. logWarningIf(this.since == null || this.since.isEmpty(), "Since is not set on action " + path);
  466. logWarningIf(!this.post && this.responseExample == null, "The response example is not set on action " + path);
  467. Map<String, Param> paramsBuilder = new HashMap<>();
  468. for (NewParam newParam : newAction.newParams.values()) {
  469. paramsBuilder.put(newParam.key, new Param(this, newParam));
  470. }
  471. this.params = Collections.unmodifiableMap(paramsBuilder);
  472. }
  473. private static void logWarningIf(boolean condition, String message) {
  474. if (condition) {
  475. LOGGER.warn(message);
  476. }
  477. }
  478. public String key() {
  479. return key;
  480. }
  481. public String deprecatedKey() {
  482. return deprecatedKey;
  483. }
  484. public String path() {
  485. return path;
  486. }
  487. @CheckForNull
  488. public String description() {
  489. return description;
  490. }
  491. /**
  492. * Set if different than controller.
  493. */
  494. @CheckForNull
  495. public String since() {
  496. return since;
  497. }
  498. @CheckForNull
  499. public String deprecatedSince() {
  500. return deprecatedSince;
  501. }
  502. public boolean isPost() {
  503. return post;
  504. }
  505. /**
  506. * @see NewAction#setChangelog(Change...)
  507. * @since 6.4
  508. */
  509. public List<Change> changelog() {
  510. return changelog;
  511. }
  512. /**
  513. * @see NewAction#setInternal(boolean)
  514. */
  515. public boolean isInternal() {
  516. return isInternal;
  517. }
  518. public RequestHandler handler() {
  519. return handler;
  520. }
  521. /**
  522. * @see org.sonar.api.server.ws.WebService.NewAction#setResponseExample(java.net.URL)
  523. */
  524. @CheckForNull
  525. public URL responseExample() {
  526. return responseExample;
  527. }
  528. /**
  529. * @see org.sonar.api.server.ws.WebService.NewAction#setResponseExample(java.net.URL)
  530. */
  531. @CheckForNull
  532. public String responseExampleAsString() {
  533. try {
  534. if (responseExample != null) {
  535. return StringUtils.trim(IOUtils.toString(responseExample, StandardCharsets.UTF_8));
  536. }
  537. return null;
  538. } catch (IOException e) {
  539. throw new IllegalStateException("Fail to load " + responseExample, e);
  540. }
  541. }
  542. /**
  543. * @see org.sonar.api.server.ws.WebService.NewAction#setResponseExample(java.net.URL)
  544. */
  545. @CheckForNull
  546. public String responseExampleFormat() {
  547. if (responseExample != null) {
  548. return StringUtils.lowerCase(FilenameUtils.getExtension(responseExample.getFile()));
  549. }
  550. return null;
  551. }
  552. @CheckForNull
  553. public Param param(String key) {
  554. return params.get(key);
  555. }
  556. public Collection<Param> params() {
  557. return params.values();
  558. }
  559. @Override
  560. public String toString() {
  561. return path;
  562. }
  563. }
  564. class NewParam {
  565. private String key;
  566. private String since;
  567. private String deprecatedSince;
  568. private String deprecatedKey;
  569. private String deprecatedKeySince;
  570. private String description;
  571. private String exampleValue;
  572. private String defaultValue;
  573. private boolean required = false;
  574. private boolean internal = false;
  575. private Set<String> possibleValues = null;
  576. private Integer maxValuesAllowed;
  577. private Integer maximumLength;
  578. private Integer minimumLength;
  579. private Integer maximumValue;
  580. private NewParam(String key) {
  581. this.key = key;
  582. }
  583. /**
  584. * @see Param#since()
  585. * @since 5.3
  586. */
  587. public NewParam setSince(@Nullable String since) {
  588. this.since = since;
  589. return this;
  590. }
  591. /**
  592. * @since 5.3
  593. */
  594. public NewParam setDeprecatedSince(@Nullable String deprecatedSince) {
  595. this.deprecatedSince = deprecatedSince;
  596. return this;
  597. }
  598. /**
  599. * @param deprecatedSince Version when the old key was replaced/deprecated. Ex: 5.6
  600. * @see Param#deprecatedKey()
  601. * @since 6.4
  602. */
  603. public NewParam setDeprecatedKey(@Nullable String key, @Nullable String deprecatedSince) {
  604. this.deprecatedKey = key;
  605. this.deprecatedKeySince = deprecatedSince;
  606. return this;
  607. }
  608. public NewParam setDescription(@Nullable String description) {
  609. this.description = description;
  610. return this;
  611. }
  612. /**
  613. * @see Param#description()
  614. * @since 5.6
  615. */
  616. public NewParam setDescription(@Nullable String description, Object... descriptionArgument) {
  617. this.description = description == null ? null : String.format(description, descriptionArgument);
  618. return this;
  619. }
  620. /**
  621. * Is the parameter required or optional ? Default value is false (optional).
  622. *
  623. * @see Param#isRequired()
  624. * @since 4.4
  625. */
  626. public NewParam setRequired(boolean b) {
  627. this.required = b;
  628. return this;
  629. }
  630. /**
  631. * Internal parameters are not displayed by default in the web api documentation. They are
  632. * displayed only when the check-box "Show Internal API" is selected. By default
  633. * a parameter is not internal.
  634. *
  635. * @see Param#isInternal()
  636. * @since 6.2
  637. */
  638. public NewParam setInternal(boolean b) {
  639. this.internal = b;
  640. return this;
  641. }
  642. /**
  643. * @see Param#exampleValue()
  644. * @since 4.4
  645. */
  646. public NewParam setExampleValue(@Nullable Object s) {
  647. this.exampleValue = (s != null) ? s.toString() : null;
  648. return this;
  649. }
  650. /**
  651. * Exhaustive list of possible values when it makes sense, for example
  652. * list of severities.
  653. *
  654. * @see Param#possibleValues()
  655. * @since 4.4
  656. */
  657. public <T> NewParam setPossibleValues(@Nullable T... values) {
  658. return setPossibleValues(values == null ? Collections.emptyList() : asList(values));
  659. }
  660. /**
  661. * Shortcut for {@code setPossibleValues("true", "false", "yes", "no")}
  662. *
  663. * @since 4.4
  664. */
  665. public NewParam setBooleanPossibleValues() {
  666. return setPossibleValues("true", "false", "yes", "no");
  667. }
  668. /**
  669. * Exhaustive list of possible values when it makes sense, for example
  670. * list of severities.
  671. *
  672. * @see Param#possibleValues()
  673. * @since 4.4
  674. */
  675. public <T> NewParam setPossibleValues(@Nullable Collection<T> values) {
  676. if (values == null || values.isEmpty()) {
  677. this.possibleValues = null;
  678. } else {
  679. this.possibleValues = new LinkedHashSet<>();
  680. for (Object value : values) {
  681. this.possibleValues.add(value.toString());
  682. }
  683. }
  684. return this;
  685. }
  686. /**
  687. * @see Param#defaultValue()
  688. * @since 4.4
  689. */
  690. public NewParam setDefaultValue(@Nullable Object o) {
  691. this.defaultValue = (o != null) ? o.toString() : null;
  692. return this;
  693. }
  694. /**
  695. * @see Param#maxValuesAllowed()
  696. * @since 6.4
  697. */
  698. public NewParam setMaxValuesAllowed(@Nullable Integer maxValuesAllowed) {
  699. this.maxValuesAllowed = maxValuesAllowed;
  700. return this;
  701. }
  702. /**
  703. * @see Param#maximumLength()
  704. * @since 7.0
  705. */
  706. public NewParam setMaximumLength(@Nullable Integer maximumLength) {
  707. this.maximumLength = maximumLength;
  708. return this;
  709. }
  710. /**
  711. * @see Param#minimumLength()
  712. * @since 7.0
  713. */
  714. public NewParam setMinimumLength(@Nullable Integer minimumLength) {
  715. this.minimumLength = minimumLength;
  716. return this;
  717. }
  718. /**
  719. * @see Param#maximumValue()
  720. * @since 7.0
  721. */
  722. public NewParam setMaximumValue(@Nullable Integer maximumValue) {
  723. this.maximumValue = maximumValue;
  724. return this;
  725. }
  726. @Override
  727. public String toString() {
  728. return key;
  729. }
  730. }
  731. enum SelectionMode {
  732. SELECTED("selected"), DESELECTED("deselected"), ALL("all");
  733. private final String paramValue;
  734. private static final Map<String, SelectionMode> BY_VALUE = stream(values())
  735. .collect(Collectors.toMap(v -> v.paramValue, v -> v));
  736. SelectionMode(String paramValue) {
  737. this.paramValue = paramValue;
  738. }
  739. public String value() {
  740. return paramValue;
  741. }
  742. public static SelectionMode fromParam(String paramValue) {
  743. checkArgument(BY_VALUE.containsKey(paramValue));
  744. return BY_VALUE.get(paramValue);
  745. }
  746. public static Collection<String> possibleValues() {
  747. return BY_VALUE.keySet();
  748. }
  749. }
  750. @Immutable
  751. class Param {
  752. public static final String TEXT_QUERY = "q";
  753. public static final String PAGE = "p";
  754. public static final String PAGE_SIZE = "ps";
  755. public static final String FIELDS = "f";
  756. public static final String SORT = "s";
  757. public static final String ASCENDING = "asc";
  758. public static final String FACETS = "facets";
  759. public static final String SELECTED = "selected";
  760. private final String key;
  761. private final String since;
  762. private final String deprecatedSince;
  763. private final String deprecatedKey;
  764. private final String deprecatedKeySince;
  765. private final String description;
  766. private final String exampleValue;
  767. private final String defaultValue;
  768. private final boolean required;
  769. private final boolean internal;
  770. private final Set<String> possibleValues;
  771. private final Integer maximumLength;
  772. private final Integer minimumLength;
  773. private final Integer maximumValue;
  774. private final Integer maxValuesAllowed;
  775. protected Param(Action action, NewParam newParam) {
  776. this.key = newParam.key;
  777. this.since = newParam.since;
  778. this.deprecatedSince = newParam.deprecatedSince;
  779. this.deprecatedKey = newParam.deprecatedKey;
  780. this.deprecatedKeySince = newParam.deprecatedKeySince;
  781. this.description = newParam.description;
  782. this.exampleValue = newParam.exampleValue;
  783. this.defaultValue = newParam.defaultValue;
  784. this.required = newParam.required;
  785. this.internal = newParam.internal;
  786. this.possibleValues = newParam.possibleValues;
  787. this.maxValuesAllowed = newParam.maxValuesAllowed;
  788. this.maximumLength = newParam.maximumLength;
  789. this.minimumLength = newParam.minimumLength;
  790. this.maximumValue = newParam.maximumValue;
  791. checkArgument(!required || defaultValue == null, "Default value must not be set on parameter '%s?%s' as it's marked as required", action, key);
  792. }
  793. public String key() {
  794. return key;
  795. }
  796. /**
  797. * @since 5.3
  798. */
  799. @CheckForNull
  800. public String since() {
  801. return since;
  802. }
  803. /**
  804. * @since 5.3
  805. */
  806. @CheckForNull
  807. public String deprecatedSince() {
  808. return deprecatedSince;
  809. }
  810. /**
  811. * @since 5.0
  812. */
  813. @CheckForNull
  814. public String deprecatedKey() {
  815. return deprecatedKey;
  816. }
  817. /**
  818. * @since 6.4
  819. */
  820. @CheckForNull
  821. public String deprecatedKeySince() {
  822. return deprecatedKeySince;
  823. }
  824. @CheckForNull
  825. public String description() {
  826. return description;
  827. }
  828. /**
  829. * @since 4.4
  830. */
  831. @CheckForNull
  832. public String exampleValue() {
  833. return exampleValue;
  834. }
  835. /**
  836. * Is the parameter required or optional ?
  837. *
  838. * @since 4.4
  839. */
  840. public boolean isRequired() {
  841. return required;
  842. }
  843. /**
  844. * Is the parameter internal ?
  845. *
  846. * @see NewParam#setInternal(boolean)
  847. * @since 6.2
  848. */
  849. public boolean isInternal() {
  850. return internal;
  851. }
  852. /**
  853. * @since 4.4
  854. */
  855. @CheckForNull
  856. public Set<String> possibleValues() {
  857. return possibleValues;
  858. }
  859. /**
  860. * @since 4.4
  861. */
  862. @CheckForNull
  863. public String defaultValue() {
  864. return defaultValue;
  865. }
  866. /**
  867. * Specify the maximum number of values allowed when using {@link Request#multiParam(String)}
  868. *
  869. * @since 6.4
  870. */
  871. public Integer maxValuesAllowed() {
  872. return maxValuesAllowed;
  873. }
  874. /**
  875. * Specify the maximum length of the value used in this parameter
  876. *
  877. * @since 7.0
  878. */
  879. @CheckForNull
  880. public Integer maximumLength() {
  881. return maximumLength;
  882. }
  883. /**
  884. * Specify the minimum length of the value used in this parameter
  885. *
  886. * @since 7.0
  887. */
  888. @CheckForNull
  889. public Integer minimumLength() {
  890. return minimumLength;
  891. }
  892. /**
  893. * Specify the maximum value of the numeric variable used in this parameter
  894. *
  895. * @since 7.0
  896. */
  897. @CheckForNull
  898. public Integer maximumValue() {
  899. return maximumValue;
  900. }
  901. @Override
  902. public String toString() {
  903. return key;
  904. }
  905. }
  906. /**
  907. * Executed once at server startup.
  908. */
  909. @Override
  910. void define(Context context);
  911. }