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