001/*
002 * SonarQube
003 * Copyright (C) 2009-2018 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.internal;
021
022import com.google.common.base.CharMatcher;
023import com.google.common.base.Splitter;
024import java.io.InputStream;
025import java.util.List;
026import java.util.Set;
027import java.util.stream.Collectors;
028import javax.annotation.CheckForNull;
029import javax.annotation.Nullable;
030import org.sonar.api.server.ws.LocalConnector;
031import org.sonar.api.server.ws.Request;
032import org.sonar.api.server.ws.WebService;
033
034import static com.google.common.base.Preconditions.checkArgument;
035import static java.lang.String.format;
036import static java.util.Collections.emptyList;
037import static java.util.Collections.singletonList;
038import static java.util.Objects.requireNonNull;
039import static org.apache.commons.lang.StringUtils.defaultString;
040
041/**
042 * @since 4.2
043 */
044public abstract class ValidatingRequest extends Request {
045
046  private static final Splitter COMMA_SPLITTER = Splitter.on(',').omitEmptyStrings().trimResults();
047  private WebService.Action action;
048  private LocalConnector localConnector;
049
050  public void setAction(WebService.Action action) {
051    this.action = action;
052  }
053
054  public WebService.Action action() {
055    return action;
056  }
057
058  @Override
059  public LocalConnector localConnector() {
060    requireNonNull(localConnector, "Local connector has not been set");
061    return localConnector;
062  }
063
064  public void setLocalConnector(LocalConnector lc) {
065    this.localConnector = lc;
066  }
067
068  @Override
069  @CheckForNull
070  public String param(String key) {
071    WebService.Param definition = action.param(key);
072
073    String rawValue = readParam(key, definition);
074    String rawValueOrDefault = defaultString(rawValue, definition.defaultValue());
075    String value = rawValueOrDefault == null ? null : CharMatcher.WHITESPACE.trimFrom(rawValueOrDefault);
076    validateRequiredValue(key, definition, rawValue);
077    if (value == null) {
078      return null;
079    }
080    validatePossibleValues(key, value, definition);
081    validateMaximumLength(key, definition, rawValueOrDefault);
082    validateMinimumLength(key, definition, rawValueOrDefault);
083    validateMaximumValue(key, definition, value);
084    return value;
085  }
086
087  @Override
088  public List<String> multiParam(String key) {
089    WebService.Param definition = action.param(key);
090    List<String> values = readMultiParamOrDefaultValue(key, definition);
091    return validateValues(values, definition);
092  }
093
094  @Override
095  @CheckForNull
096  public InputStream paramAsInputStream(String key) {
097    return readInputStreamParam(key);
098  }
099
100  @Override
101  @CheckForNull
102  public Part paramAsPart(String key) {
103    return readPart(key);
104  }
105
106  @CheckForNull
107  @Override
108  public List<String> paramAsStrings(String key) {
109    WebService.Param definition = action.param(key);
110    String value = defaultString(readParam(key, definition), definition.defaultValue());
111    if (value == null) {
112      return null;
113    }
114    List<String> values = COMMA_SPLITTER.splitToList(value);
115    return validateValues(values, definition);
116  }
117
118  @CheckForNull
119  @Override
120  public <E extends Enum<E>> List<E> paramAsEnums(String key, Class<E> enumClass) {
121    List<String> values = paramAsStrings(key);
122    if (values == null) {
123      return null;
124    }
125    return values.stream()
126      .map(value -> Enum.valueOf(enumClass, value))
127      .collect(Collectors.toList());
128  }
129
130  @CheckForNull
131  private String readParam(String key, @Nullable WebService.Param definition) {
132    checkArgument(definition != null, "BUG - parameter '%s' is undefined for action '%s'", key, action.key());
133    String deprecatedKey = definition.deprecatedKey();
134    return deprecatedKey != null ? defaultString(readParam(deprecatedKey), readParam(key)) : readParam(key);
135  }
136
137  private List<String> readMultiParamOrDefaultValue(String key, @Nullable WebService.Param definition) {
138    checkArgument(definition != null, "BUG - parameter '%s' is undefined for action '%s'", key, action.key());
139
140    List<String> keyValues = readMultiParam(key);
141    if (!keyValues.isEmpty()) {
142      return keyValues;
143    }
144
145    String deprecatedKey = definition.deprecatedKey();
146    List<String> deprecatedKeyValues = deprecatedKey == null ? emptyList() : readMultiParam(deprecatedKey);
147    if (!deprecatedKeyValues.isEmpty()) {
148      return deprecatedKeyValues;
149    }
150
151    String defaultValue = definition.defaultValue();
152    return defaultValue == null ? emptyList() : singletonList(defaultValue);
153  }
154
155  @CheckForNull
156  protected abstract String readParam(String key);
157
158  protected abstract List<String> readMultiParam(String key);
159
160  @CheckForNull
161  protected abstract InputStream readInputStreamParam(String key);
162
163  @CheckForNull
164  protected abstract Part readPart(String key);
165
166  private static List<String> validateValues(List<String> values, WebService.Param definition) {
167    Integer maximumValues = definition.maxValuesAllowed();
168    checkArgument(maximumValues == null || values.size() <= maximumValues, "'%s' can contains only %s values, got %s", definition.key(), maximumValues, values.size());
169    values.forEach(value -> validatePossibleValues(definition.key(), value, definition));
170    return values;
171  }
172
173  private static void validatePossibleValues(String key, String value, WebService.Param definition) {
174    Set<String> possibleValues = definition.possibleValues();
175    if (possibleValues == null) {
176      return;
177    }
178    checkArgument(possibleValues.contains(value), "Value of parameter '%s' (%s) must be one of: %s", key, value, possibleValues);
179  }
180
181  private static void validateMaximumLength(String key, WebService.Param definition, String valueOrDefault) {
182    Integer maximumLength = definition.maximumLength();
183    if (maximumLength == null) {
184      return;
185    }
186    int valueLength = valueOrDefault.length();
187    checkArgument(valueLength <= maximumLength, "'%s' length (%s) is longer than the maximum authorized (%s)", key, valueLength, maximumLength);
188  }
189
190  private static void validateMinimumLength(String key, WebService.Param definition, String valueOrDefault) {
191    Integer minimumLength = definition.minimumLength();
192    if (minimumLength == null) {
193      return;
194    }
195    int valueLength = valueOrDefault.length();
196    checkArgument(valueLength >= minimumLength, "'%s' length (%s) is shorter than the minimum authorized (%s)", key, valueLength, minimumLength);
197  }
198
199  private static void validateMaximumValue(String key, WebService.Param definition, String value) {
200    Integer maximumValue = definition.maximumValue();
201    if (maximumValue == null) {
202      return;
203    }
204    int valueAsInt = validateAsNumeric(key, value);
205    checkArgument(valueAsInt <= maximumValue, "'%s' value (%s) must be less than %s", key, valueAsInt, maximumValue);
206  }
207
208  private static void validateRequiredValue(String key, WebService.Param definition, String value) {
209    boolean required = definition.isRequired();
210    if (required) {
211      checkArgument(value != null, format(MSG_PARAMETER_MISSING, key));
212    }
213  }
214
215  private static int validateAsNumeric(String key, String value) {
216    try {
217      return Integer.parseInt(value);
218    } catch (NumberFormatException exception) {
219      throw new IllegalStateException(format("'%s' value '%s' cannot be parsed as an integer", key, value), exception);
220    }
221  }
222
223}