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 com.google.common.collect.Lists; 025import java.io.InputStream; 026import java.util.ArrayList; 027import java.util.Date; 028import java.util.List; 029import java.util.function.BiFunction; 030import java.util.function.Consumer; 031import java.util.function.Supplier; 032import javax.annotation.CheckForNull; 033import javax.annotation.Nullable; 034import org.apache.commons.lang.StringUtils; 035import org.sonar.api.utils.DateUtils; 036import org.sonar.api.utils.SonarException; 037 038import static com.google.common.base.Preconditions.checkArgument; 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, String.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 if (values == null) { 115 throw new IllegalArgumentException(String.format(MSG_PARAMETER_MISSING, key)); 116 } 117 return values; 118 } 119 120 public List<String> mandatoryMultiParam(String key) { 121 List<String> values = multiParam(key); 122 checkArgument(!values.isEmpty(), MSG_PARAMETER_MISSING, key); 123 124 return values; 125 } 126 127 @CheckForNull 128 public List<String> paramAsStrings(String key) { 129 String value = param(key); 130 if (value == null) { 131 return null; 132 } 133 return Lists.newArrayList(Splitter.on(',').omitEmptyStrings().trimResults().split(value)); 134 } 135 136 @CheckForNull 137 public abstract String param(String key); 138 139 public abstract List<String> multiParam(String key); 140 141 @CheckForNull 142 public abstract InputStream paramAsInputStream(String key); 143 144 @CheckForNull 145 public abstract Part paramAsPart(String key); 146 147 public Part mandatoryParamAsPart(String key) { 148 Part part = paramAsPart(key); 149 checkArgument(part != null, MSG_PARAMETER_MISSING, key); 150 return part; 151 } 152 153 /** 154 * @deprecated to be dropped in 4.4. Default values are declared in ws metadata 155 */ 156 @CheckForNull 157 @Deprecated 158 public String param(String key, @CheckForNull String defaultValue) { 159 return StringUtils.defaultString(param(key), defaultValue); 160 } 161 162 /** 163 * @deprecated to be dropped in 4.4. Default values must be declared in {@link org.sonar.api.server.ws.WebService} then 164 * this method can be replaced by {@link #mandatoryParamAsBoolean(String)}. 165 */ 166 @Deprecated 167 public boolean paramAsBoolean(String key, boolean defaultValue) { 168 String value = param(key); 169 return value == null ? defaultValue : parseBoolean(key, value); 170 } 171 172 /** 173 * @deprecated to be dropped in 4.4. Default values must be declared in {@link org.sonar.api.server.ws.WebService} then 174 * this method can be replaced by {@link #mandatoryParamAsInt(String)}. 175 */ 176 @Deprecated 177 public int paramAsInt(String key, int defaultValue) { 178 String s = param(key); 179 return s == null ? defaultValue : parseInt(key, s); 180 } 181 182 /** 183 * @deprecated to be dropped in 4.4. Default values must be declared in {@link org.sonar.api.server.ws.WebService} then 184 * this method can be replaced by {@link #mandatoryParamAsLong(String)}. 185 */ 186 @Deprecated 187 public long paramAsLong(String key, long defaultValue) { 188 String s = param(key); 189 return s == null ? defaultValue : parseLong(key, s); 190 } 191 192 @CheckForNull 193 public Boolean paramAsBoolean(String key) { 194 String value = param(key); 195 return value == null ? null : parseBoolean(key, value); 196 } 197 198 @CheckForNull 199 public Integer paramAsInt(String key) { 200 String s = param(key); 201 return s == null ? null : parseInt(key, s); 202 } 203 204 @CheckForNull 205 public Long paramAsLong(String key) { 206 String s = param(key); 207 return s == null ? null : parseLong(key, s); 208 } 209 210 @CheckForNull 211 public <E extends Enum<E>> E paramAsEnum(String key, Class<E> enumClass) { 212 String s = param(key); 213 return s == null ? null : Enum.valueOf(enumClass, s); 214 } 215 216 @CheckForNull 217 public <E extends Enum<E>> List<E> paramAsEnums(String key, Class<E> enumClass) { 218 String value = param(key); 219 if (value == null) { 220 return null; 221 } 222 Iterable<String> values = Splitter.on(',').omitEmptyStrings().trimResults().split(value); 223 List<E> result = new ArrayList<>(); 224 for (String s : values) { 225 result.add(Enum.valueOf(enumClass, s)); 226 } 227 228 return result; 229 } 230 231 @CheckForNull 232 public Date paramAsDateTime(String key) { 233 String stringDate = param(key); 234 if (stringDate == null) { 235 return null; 236 } 237 238 Date date = parseDateTimeQuietly(stringDate); 239 if (date != null) { 240 return date; 241 } 242 243 date = parseDateQuietly(stringDate); 244 checkArgument(date != null, "'%s' cannot be parsed as either a date or date+time", stringDate); 245 246 return date; 247 } 248 249 @CheckForNull 250 public Date paramAsDate(String key) { 251 String s = param(key); 252 if (s == null) { 253 return null; 254 } 255 256 try { 257 return DateUtils.parseDate(s); 258 } catch (SonarException notDateException) { 259 throw new IllegalArgumentException(notDateException); 260 } 261 } 262 263 private static boolean parseBoolean(String key, String value) { 264 if ("true".equals(value) || "yes".equals(value)) { 265 return true; 266 } 267 if ("false".equals(value) || "no".equals(value)) { 268 return false; 269 } 270 throw new IllegalArgumentException(String.format("Property %s is not a boolean value: %s", key, value)); 271 } 272 273 private static int parseInt(String key, String value) { 274 try { 275 return Integer.parseInt(value); 276 } catch (NumberFormatException expection) { 277 throw new IllegalArgumentException(String.format("The '%s' parameter cannot be parsed as an integer value: %s", key, value)); 278 } 279 } 280 281 private static long parseLong(String key, String value) { 282 try { 283 return Long.parseLong(value); 284 } catch (NumberFormatException exception) { 285 throw new IllegalArgumentException(String.format("The '%s' parameter cannot be parsed as a long value: %s", key, value)); 286 } 287 } 288 289 @Beta 290 public <T> Param<T> getParam(String key, BiFunction<Request, String, T> retrieveAndValidate) { 291 String param = this.param(key); 292 if (param != null) { 293 return GenericParam.present(retrieveAndValidate.apply(this, key)); 294 } 295 return AbsentParam.absent(); 296 } 297 298 @Beta 299 public StringParam getParam(String key, Consumer<String> validate) { 300 String value = this.param(key); 301 if (value != null) { 302 validate.accept(value); 303 return StringParamImpl.present(value); 304 } 305 return AbsentStringParam.absent(); 306 } 307 308 @Beta 309 public StringParam getParam(String key) { 310 String value = this.param(key); 311 if (value != null) { 312 return StringParamImpl.present(value); 313 } 314 return AbsentStringParam.absent(); 315 } 316 317 /** 318 * Allows a web service to call another web service. 319 * @see LocalConnector 320 * @since 5.5 321 */ 322 @Beta 323 public abstract LocalConnector localConnector(); 324 325 /** 326 * Return path of the request 327 * @since 6.0 328 */ 329 public abstract String getPath(); 330 331 /** 332 * @since 6.0 333 */ 334 public interface Part { 335 InputStream getInputStream(); 336 337 String getFileName(); 338 } 339 340 /** 341 * Represents a Request parameter, provides information whether is was specified or not (check {@link #isPresent()}) 342 * and utility method to nicely handles cases where the parameter is not present. 343 */ 344 @Beta 345 public interface Param<T> { 346 boolean isPresent(); 347 348 /** 349 * @return the value of the parameter 350 * 351 * @throws IllegalStateException if param is not present. 352 */ 353 @CheckForNull 354 T getValue(); 355 356 @CheckForNull 357 T or(Supplier<T> defaultValueSupplier); 358 } 359 360 /** 361 * Implementation of {@link Param} where the param is not present. 362 */ 363 private enum AbsentParam implements Param<Object> { 364 INSTANCE; 365 366 @SuppressWarnings("unchecked") 367 protected static <T> Param<T> absent() { 368 return (Param<T>) INSTANCE; 369 } 370 371 /** 372 * Always returns true. 373 */ 374 @Override 375 public boolean isPresent() { 376 return false; 377 } 378 379 /** 380 * Always throws a {@link IllegalStateException}. 381 */ 382 @Override 383 public Object getValue() { 384 throw createGetValueISE(); 385 } 386 387 /** 388 * Always returns the value supplied by {@code defaultValueSupplier}. 389 */ 390 @Override 391 @CheckForNull 392 public Object or(Supplier<Object> defaultValueSupplier) { 393 return checkDefaultValueSupplier(defaultValueSupplier).get(); 394 } 395 } 396 397 /** 398 * Implementation of {@link Param} where the param is present. 399 */ 400 private static final class GenericParam<T> implements Param<T> { 401 private final T value; 402 403 private GenericParam(T value) { 404 this.value = value; 405 } 406 407 static <T> Param<T> present(T value) { 408 return new GenericParam<>(value); 409 } 410 411 /** 412 * Always returns true. 413 */ 414 @Override 415 public boolean isPresent() { 416 return true; 417 } 418 419 /** 420 * @return the value of the parameter 421 * 422 * @throws IllegalStateException if param is not present. 423 */ 424 @Override 425 @CheckForNull 426 public T getValue() { 427 return value; 428 } 429 430 /** 431 * Always returns value of the parameter. 432 * 433 * @throws NullPointerException As per the inherited contract, {@code defaultValueSupplier} can't be null 434 */ 435 @Override 436 @CheckForNull 437 public T or(Supplier<T> defaultValueSupplier) { 438 checkDefaultValueSupplier(defaultValueSupplier); 439 return value; 440 } 441 } 442 443 /** 444 * Extends {@link Param} with convenience methods specific to the type {@link String}. 445 */ 446 public interface StringParam extends Param<String> { 447 /** 448 * Returns a {@link StringParam} object which methods {@link #getValue()} and {@link #or(Supplier)} will 449 * return {@code null} rather than an empty String when the param is present and its value is an empty String. 450 */ 451 StringParam emptyAsNull(); 452 } 453 454 /** 455 * Implementation of {@link StringParam} where the param is not present. 456 */ 457 private enum AbsentStringParam implements StringParam { 458 INSTANCE; 459 460 protected static StringParam absent() { 461 return INSTANCE; 462 } 463 464 /** 465 * Always returns false. 466 */ 467 @Override 468 public boolean isPresent() { 469 return false; 470 } 471 472 /** 473 * Always throws a {@link IllegalStateException}. 474 */ 475 @Override 476 public String getValue() { 477 throw createGetValueISE(); 478 } 479 480 /** 481 * Always returns the value supplied by {@code defaultValueSupplier}. 482 */ 483 @Override 484 public String or(Supplier<String> defaultValueSupplier) { 485 return checkDefaultValueSupplier(defaultValueSupplier).get(); 486 } 487 488 /** 489 * Returns itself. 490 */ 491 @Override 492 public StringParam emptyAsNull() { 493 return this; 494 } 495 } 496 497 /** 498 * Implementation of {@link StringParam} where the param is present. 499 */ 500 private static final class StringParamImpl implements StringParam { 501 @CheckForNull 502 private final String value; 503 private final boolean emptyAsNull; 504 505 private StringParamImpl(@Nullable String value, boolean emptyAsNull) { 506 this.value = value; 507 this.emptyAsNull = emptyAsNull; 508 } 509 510 static StringParam present(String value) { 511 return new StringParamImpl(value, false); 512 } 513 514 @Override 515 public boolean isPresent() { 516 return true; 517 } 518 519 @Override 520 public String getValue() { 521 if (emptyAsNull && value != null && value.isEmpty()) { 522 return null; 523 } 524 return value; 525 } 526 527 @Override 528 @CheckForNull 529 public String or(Supplier<String> defaultValueSupplier) { 530 checkDefaultValueSupplier(defaultValueSupplier); 531 if (emptyAsNull && value != null && value.isEmpty()) { 532 return null; 533 } 534 return value; 535 } 536 537 @Override 538 public StringParam emptyAsNull() { 539 if (emptyAsNull || (value != null && !value.isEmpty())) { 540 return this; 541 } 542 return new StringParamImpl(value, true); 543 } 544 } 545 546 private static <T> Supplier<T> checkDefaultValueSupplier(Supplier<T> defaultValueSupplier) { 547 return requireNonNull(defaultValueSupplier, "default value supplier can't be null"); 548 } 549 550 private static IllegalStateException createGetValueISE() { 551 return new IllegalStateException("Param has no value. Use isPresent() before calling getValue()"); 552 } 553}