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}