001/* 002 * SonarQube 003 * Copyright (C) 2009-2017 SonarSource SA 004 * mailto:info AT sonarsource DOT com 005 * 006 * This program is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public License 017 * along with this program; if not, write to the Free Software Foundation, 018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 019 */ 020package org.sonar.api.server.ws; 021 022import com.google.common.annotations.Beta; 023import com.google.common.base.Splitter; 024import java.io.InputStream; 025import java.util.ArrayList; 026import java.util.Date; 027import java.util.List; 028import java.util.function.BiFunction; 029import java.util.function.Consumer; 030import java.util.function.Supplier; 031import javax.annotation.CheckForNull; 032import javax.annotation.Nullable; 033import org.apache.commons.lang.StringUtils; 034import org.sonar.api.utils.DateUtils; 035import org.sonar.api.utils.SonarException; 036 037import static com.google.common.base.Preconditions.checkArgument; 038import static java.lang.String.format; 039import static java.util.Objects.requireNonNull; 040import static org.sonar.api.utils.DateUtils.parseDateQuietly; 041import static org.sonar.api.utils.DateUtils.parseDateTimeQuietly; 042 043/** 044 * @since 4.2 045 */ 046public abstract class Request { 047 048 private static final String MSG_PARAMETER_MISSING = "The '%s' parameter is missing"; 049 050 /** 051 * Returns the name of the HTTP method with which this request was made. Possible 052 * values are GET and POST. Others are not supported. 053 */ 054 public abstract String method(); 055 056 /** 057 * Returns the requested MIME type, or {@code "application/octet-stream"} if not specified. 058 */ 059 public abstract String getMediaType(); 060 061 /** 062 * Return true of the parameter is set in the request. 063 * Does NOT take into account the deprecated key of a parameter. 064 */ 065 public abstract boolean hasParam(String key); 066 067 /** 068 * Returns a non-null value. To be used when parameter is required or has a default value. 069 * 070 * @throws java.lang.IllegalArgumentException is value is null or blank 071 */ 072 public String mandatoryParam(String key) { 073 String value = param(key); 074 checkArgument(value != null, format(MSG_PARAMETER_MISSING, key)); 075 return value; 076 } 077 078 /** 079 * Returns a boolean value. To be used when parameter is required or has a default value. 080 * 081 * @throws java.lang.IllegalArgumentException is value is null or blank 082 */ 083 public boolean mandatoryParamAsBoolean(String key) { 084 String s = mandatoryParam(key); 085 return parseBoolean(key, s); 086 } 087 088 /** 089 * Returns an int value. To be used when parameter is required or has a default value. 090 * 091 * @throws java.lang.IllegalArgumentException is value is null or blank 092 */ 093 public int mandatoryParamAsInt(String key) { 094 String s = mandatoryParam(key); 095 return parseInt(key, s); 096 } 097 098 /** 099 * Returns a long value. To be used when parameter is required or has a default value. 100 * 101 * @throws java.lang.IllegalArgumentException is value is null or blank 102 */ 103 public long mandatoryParamAsLong(String key) { 104 String s = mandatoryParam(key); 105 return parseLong(key, s); 106 } 107 108 public <E extends Enum<E>> E mandatoryParamAsEnum(String key, Class<E> enumClass) { 109 return Enum.valueOf(enumClass, mandatoryParam(key)); 110 } 111 112 public List<String> mandatoryParamAsStrings(String key) { 113 List<String> values = paramAsStrings(key); 114 checkArgument(values != null, format(MSG_PARAMETER_MISSING, key)); 115 return values; 116 } 117 118 public List<String> mandatoryMultiParam(String key) { 119 List<String> values = multiParam(key); 120 checkArgument(!values.isEmpty(), MSG_PARAMETER_MISSING, key); 121 return values; 122 } 123 124 @CheckForNull 125 public abstract List<String> paramAsStrings(String key); 126 127 @CheckForNull 128 public abstract String param(String key); 129 130 public abstract List<String> multiParam(String key); 131 132 @CheckForNull 133 public abstract InputStream paramAsInputStream(String key); 134 135 @CheckForNull 136 public abstract Part paramAsPart(String key); 137 138 public Part mandatoryParamAsPart(String key) { 139 Part part = paramAsPart(key); 140 checkArgument(part != null, MSG_PARAMETER_MISSING, key); 141 return part; 142 } 143 144 /** 145 * @deprecated to be dropped in 4.4. Default values are declared in ws metadata 146 */ 147 @CheckForNull 148 @Deprecated 149 public String param(String key, @CheckForNull String defaultValue) { 150 return StringUtils.defaultString(param(key), defaultValue); 151 } 152 153 /** 154 * @deprecated to be dropped in 4.4. Default values must be declared in {@link org.sonar.api.server.ws.WebService} then 155 * this method can be replaced by {@link #mandatoryParamAsBoolean(String)}. 156 */ 157 @Deprecated 158 public boolean paramAsBoolean(String key, boolean defaultValue) { 159 String value = param(key); 160 return value == null ? defaultValue : parseBoolean(key, value); 161 } 162 163 /** 164 * @deprecated to be dropped in 4.4. Default values must be declared in {@link org.sonar.api.server.ws.WebService} then 165 * this method can be replaced by {@link #mandatoryParamAsInt(String)}. 166 */ 167 @Deprecated 168 public int paramAsInt(String key, int defaultValue) { 169 String s = param(key); 170 return s == null ? defaultValue : parseInt(key, s); 171 } 172 173 /** 174 * @deprecated to be dropped in 4.4. Default values must be declared in {@link org.sonar.api.server.ws.WebService} then 175 * this method can be replaced by {@link #mandatoryParamAsLong(String)}. 176 */ 177 @Deprecated 178 public long paramAsLong(String key, long defaultValue) { 179 String s = param(key); 180 return s == null ? defaultValue : parseLong(key, s); 181 } 182 183 @CheckForNull 184 public Boolean paramAsBoolean(String key) { 185 String value = param(key); 186 return value == null ? null : parseBoolean(key, value); 187 } 188 189 @CheckForNull 190 public Integer paramAsInt(String key) { 191 String s = param(key); 192 return s == null ? null : parseInt(key, s); 193 } 194 195 @CheckForNull 196 public Long paramAsLong(String key) { 197 String s = param(key); 198 return s == null ? null : parseLong(key, s); 199 } 200 201 @CheckForNull 202 public <E extends Enum<E>> E paramAsEnum(String key, Class<E> enumClass) { 203 String s = param(key); 204 return s == null ? null : Enum.valueOf(enumClass, s); 205 } 206 207 @CheckForNull 208 public <E extends Enum<E>> List<E> paramAsEnums(String key, Class<E> enumClass) { 209 String value = param(key); 210 if (value == null) { 211 return null; 212 } 213 Iterable<String> values = Splitter.on(',').omitEmptyStrings().trimResults().split(value); 214 List<E> result = new ArrayList<>(); 215 for (String s : values) { 216 result.add(Enum.valueOf(enumClass, s)); 217 } 218 219 return result; 220 } 221 222 @CheckForNull 223 public Date paramAsDateTime(String key) { 224 String stringDate = param(key); 225 if (stringDate == null) { 226 return null; 227 } 228 229 Date date = parseDateTimeQuietly(stringDate); 230 if (date != null) { 231 return date; 232 } 233 234 date = parseDateQuietly(stringDate); 235 checkArgument(date != null, "'%s' cannot be parsed as either a date or date+time", stringDate); 236 237 return date; 238 } 239 240 @CheckForNull 241 public Date paramAsDate(String key) { 242 String s = param(key); 243 if (s == null) { 244 return null; 245 } 246 247 try { 248 return DateUtils.parseDate(s); 249 } catch (SonarException notDateException) { 250 throw new IllegalArgumentException(notDateException); 251 } 252 } 253 254 private static boolean parseBoolean(String key, String value) { 255 if ("true".equals(value) || "yes".equals(value)) { 256 return true; 257 } 258 if ("false".equals(value) || "no".equals(value)) { 259 return false; 260 } 261 throw new IllegalArgumentException(format("Property %s is not a boolean value: %s", key, value)); 262 } 263 264 private static int parseInt(String key, String value) { 265 try { 266 return Integer.parseInt(value); 267 } catch (NumberFormatException expection) { 268 throw new IllegalArgumentException(format("The '%s' parameter cannot be parsed as an integer value: %s", key, value)); 269 } 270 } 271 272 private static long parseLong(String key, String value) { 273 try { 274 return Long.parseLong(value); 275 } catch (NumberFormatException exception) { 276 throw new IllegalArgumentException(format("The '%s' parameter cannot be parsed as a long value: %s", key, value)); 277 } 278 } 279 280 @Beta 281 public <T> Param<T> getParam(String key, BiFunction<Request, String, T> retrieveAndValidate) { 282 String param = this.param(key); 283 if (param != null) { 284 return GenericParam.present(retrieveAndValidate.apply(this, key)); 285 } 286 return AbsentParam.absent(); 287 } 288 289 @Beta 290 public StringParam getParam(String key, Consumer<String> validate) { 291 String value = this.param(key); 292 if (value != null) { 293 validate.accept(value); 294 return StringParamImpl.present(value); 295 } 296 return AbsentStringParam.absent(); 297 } 298 299 @Beta 300 public StringParam getParam(String key) { 301 String value = this.param(key); 302 if (value != null) { 303 return StringParamImpl.present(value); 304 } 305 return AbsentStringParam.absent(); 306 } 307 308 /** 309 * Allows a web service to call another web service. 310 * @see LocalConnector 311 * @since 5.5 312 */ 313 @Beta 314 public abstract LocalConnector localConnector(); 315 316 /** 317 * Return path of the request 318 * @since 6.0 319 */ 320 public abstract String getPath(); 321 322 /** 323 * @since 6.0 324 */ 325 public interface Part { 326 InputStream getInputStream(); 327 328 String getFileName(); 329 } 330 331 /** 332 * Represents a Request parameter, provides information whether is was specified or not (check {@link #isPresent()}) 333 * and utility method to nicely handles cases where the parameter is not present. 334 */ 335 @Beta 336 public interface Param<T> { 337 boolean isPresent(); 338 339 /** 340 * @return the value of the parameter 341 * 342 * @throws IllegalStateException if param is not present. 343 */ 344 @CheckForNull 345 T getValue(); 346 347 @CheckForNull 348 T or(Supplier<T> defaultValueSupplier); 349 } 350 351 /** 352 * Implementation of {@link Param} where the param is not present. 353 */ 354 private enum AbsentParam implements Param<Object> { 355 INSTANCE; 356 357 @SuppressWarnings("unchecked") 358 protected static <T> Param<T> absent() { 359 return (Param<T>) INSTANCE; 360 } 361 362 /** 363 * Always returns true. 364 */ 365 @Override 366 public boolean isPresent() { 367 return false; 368 } 369 370 /** 371 * Always throws a {@link IllegalStateException}. 372 */ 373 @Override 374 public Object getValue() { 375 throw createGetValueISE(); 376 } 377 378 /** 379 * Always returns the value supplied by {@code defaultValueSupplier}. 380 */ 381 @Override 382 @CheckForNull 383 public Object or(Supplier<Object> defaultValueSupplier) { 384 return checkDefaultValueSupplier(defaultValueSupplier).get(); 385 } 386 } 387 388 /** 389 * Implementation of {@link Param} where the param is present. 390 */ 391 private static final class GenericParam<T> implements Param<T> { 392 private final T value; 393 394 private GenericParam(T value) { 395 this.value = value; 396 } 397 398 static <T> Param<T> present(T value) { 399 return new GenericParam<>(value); 400 } 401 402 /** 403 * Always returns true. 404 */ 405 @Override 406 public boolean isPresent() { 407 return true; 408 } 409 410 /** 411 * @return the value of the parameter 412 * 413 * @throws IllegalStateException if param is not present. 414 */ 415 @Override 416 @CheckForNull 417 public T getValue() { 418 return value; 419 } 420 421 /** 422 * Always returns value of the parameter. 423 * 424 * @throws NullPointerException As per the inherited contract, {@code defaultValueSupplier} can't be null 425 */ 426 @Override 427 @CheckForNull 428 public T or(Supplier<T> defaultValueSupplier) { 429 checkDefaultValueSupplier(defaultValueSupplier); 430 return value; 431 } 432 } 433 434 /** 435 * Extends {@link Param} with convenience methods specific to the type {@link String}. 436 */ 437 public interface StringParam extends Param<String> { 438 /** 439 * Returns a {@link StringParam} object which methods {@link #getValue()} and {@link #or(Supplier)} will 440 * return {@code null} rather than an empty String when the param is present and its value is an empty String. 441 */ 442 StringParam emptyAsNull(); 443 } 444 445 /** 446 * Implementation of {@link StringParam} where the param is not present. 447 */ 448 private enum AbsentStringParam implements StringParam { 449 INSTANCE; 450 451 protected static StringParam absent() { 452 return INSTANCE; 453 } 454 455 /** 456 * Always returns false. 457 */ 458 @Override 459 public boolean isPresent() { 460 return false; 461 } 462 463 /** 464 * Always throws a {@link IllegalStateException}. 465 */ 466 @Override 467 public String getValue() { 468 throw createGetValueISE(); 469 } 470 471 /** 472 * Always returns the value supplied by {@code defaultValueSupplier}. 473 */ 474 @Override 475 public String or(Supplier<String> defaultValueSupplier) { 476 return checkDefaultValueSupplier(defaultValueSupplier).get(); 477 } 478 479 /** 480 * Returns itself. 481 */ 482 @Override 483 public StringParam emptyAsNull() { 484 return this; 485 } 486 } 487 488 /** 489 * Implementation of {@link StringParam} where the param is present. 490 */ 491 private static final class StringParamImpl implements StringParam { 492 @CheckForNull 493 private final String value; 494 private final boolean emptyAsNull; 495 496 private StringParamImpl(@Nullable String value, boolean emptyAsNull) { 497 this.value = value; 498 this.emptyAsNull = emptyAsNull; 499 } 500 501 static StringParam present(String value) { 502 return new StringParamImpl(value, false); 503 } 504 505 @Override 506 public boolean isPresent() { 507 return true; 508 } 509 510 @Override 511 public String getValue() { 512 if (emptyAsNull && value != null && value.isEmpty()) { 513 return null; 514 } 515 return value; 516 } 517 518 @Override 519 @CheckForNull 520 public String or(Supplier<String> defaultValueSupplier) { 521 checkDefaultValueSupplier(defaultValueSupplier); 522 if (emptyAsNull && value != null && value.isEmpty()) { 523 return null; 524 } 525 return value; 526 } 527 528 @Override 529 public StringParam emptyAsNull() { 530 if (emptyAsNull || (value != null && !value.isEmpty())) { 531 return this; 532 } 533 return new StringParamImpl(value, true); 534 } 535 } 536 537 private static <T> Supplier<T> checkDefaultValueSupplier(Supplier<T> defaultValueSupplier) { 538 return requireNonNull(defaultValueSupplier, "default value supplier can't be null"); 539 } 540 541 private static IllegalStateException createGetValueISE() { 542 return new IllegalStateException("Param has no value. Use isPresent() before calling getValue()"); 543 } 544}