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}