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