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}