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}