1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"). 5 * You may not use this file except in compliance with the License. 6 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.awssdk.http; 17 18 import static java.util.Collections.emptyList; 19 import static java.util.Collections.unmodifiableList; 20 import static software.amazon.awssdk.utils.CollectionUtils.unmodifiableMapOfLists; 21 22 import java.io.IOException; 23 import java.io.ObjectInputStream; 24 import java.util.ArrayList; 25 import java.util.Collection; 26 import java.util.List; 27 import java.util.Map; 28 import java.util.Objects; 29 import java.util.Optional; 30 import java.util.function.BiConsumer; 31 import software.amazon.awssdk.annotations.Immutable; 32 import software.amazon.awssdk.annotations.SdkInternalApi; 33 import software.amazon.awssdk.internal.http.LowCopyListMap; 34 import software.amazon.awssdk.utils.StringUtils; 35 import software.amazon.awssdk.utils.Validate; 36 37 /** 38 * Internal implementation of {@link SdkHttpFullResponse}, buildable via {@link SdkHttpFullResponse#builder()}. Returned by HTTP 39 * implementation to represent a service response. 40 */ 41 @SdkInternalApi 42 @Immutable 43 class DefaultSdkHttpFullResponse implements SdkHttpFullResponse { 44 private static final long serialVersionUID = 1; 45 private final String statusText; 46 private final int statusCode; 47 private final transient AbortableInputStream content; 48 private transient LowCopyListMap.ForBuildable headers; 49 DefaultSdkHttpFullResponse(Builder builder)50 private DefaultSdkHttpFullResponse(Builder builder) { 51 this.statusCode = Validate.isNotNegative(builder.statusCode, "Status code must not be negative."); 52 this.statusText = builder.statusText; 53 this.content = builder.content; 54 this.headers = builder.headers.forBuildable(); 55 } 56 57 @Override headers()58 public Map<String, List<String>> headers() { 59 return headers.forExternalRead(); 60 } 61 62 @Override matchingHeaders(String header)63 public List<String> matchingHeaders(String header) { 64 return unmodifiableList(headers.forInternalRead().getOrDefault(header, emptyList())); 65 } 66 67 @Override firstMatchingHeader(String headerName)68 public Optional<String> firstMatchingHeader(String headerName) { 69 List<String> headers = this.headers.forInternalRead().get(headerName); 70 if (headers == null || headers.isEmpty()) { 71 return Optional.empty(); 72 } 73 74 String header = headers.get(0); 75 if (StringUtils.isEmpty(header)) { 76 return Optional.empty(); 77 } 78 79 return Optional.of(header); 80 } 81 82 @Override firstMatchingHeader(Collection<String> headersToFind)83 public Optional<String> firstMatchingHeader(Collection<String> headersToFind) { 84 for (String headerName : headersToFind) { 85 Optional<String> header = firstMatchingHeader(headerName); 86 if (header.isPresent()) { 87 return header; 88 } 89 } 90 91 return Optional.empty(); 92 } 93 94 @Override forEachHeader(BiConsumer<? super String, ? super List<String>> consumer)95 public void forEachHeader(BiConsumer<? super String, ? super List<String>> consumer) { 96 this.headers.forInternalRead().forEach((k, v) -> consumer.accept(k, unmodifiableList(v))); 97 } 98 99 @Override numHeaders()100 public int numHeaders() { 101 return this.headers.forInternalRead().size(); 102 } 103 104 @Override content()105 public Optional<AbortableInputStream> content() { 106 return Optional.ofNullable(content); 107 } 108 109 @Override statusText()110 public Optional<String> statusText() { 111 return Optional.ofNullable(statusText); 112 } 113 114 @Override statusCode()115 public int statusCode() { 116 return statusCode; 117 } 118 119 @Override toBuilder()120 public SdkHttpFullResponse.Builder toBuilder() { 121 return new Builder(this); 122 } 123 readObject(ObjectInputStream ois)124 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { 125 ois.defaultReadObject(); 126 headers = LowCopyListMap.emptyHeaders().forBuildable(); 127 } 128 129 @Override equals(Object o)130 public boolean equals(Object o) { 131 if (this == o) { 132 return true; 133 } 134 if (o == null || getClass() != o.getClass()) { 135 return false; 136 } 137 DefaultSdkHttpFullResponse that = (DefaultSdkHttpFullResponse) o; 138 return (statusCode == that.statusCode) && 139 Objects.equals(statusText, that.statusText) && 140 Objects.equals(headers, that.headers); 141 } 142 143 @Override hashCode()144 public int hashCode() { 145 int result = statusText != null ? statusText.hashCode() : 0; 146 result = 31 * result + statusCode; 147 result = 31 * result + Objects.hashCode(headers); 148 return result; 149 } 150 151 /** 152 * Builder for a {@link DefaultSdkHttpFullResponse}. 153 */ 154 static final class Builder implements SdkHttpFullResponse.Builder { 155 private String statusText; 156 private int statusCode; 157 private AbortableInputStream content; 158 private LowCopyListMap.ForBuilder headers; 159 Builder()160 Builder() { 161 headers = LowCopyListMap.emptyHeaders(); 162 } 163 Builder(DefaultSdkHttpFullResponse defaultSdkHttpFullResponse)164 private Builder(DefaultSdkHttpFullResponse defaultSdkHttpFullResponse) { 165 statusText = defaultSdkHttpFullResponse.statusText; 166 statusCode = defaultSdkHttpFullResponse.statusCode; 167 content = defaultSdkHttpFullResponse.content; 168 headers = defaultSdkHttpFullResponse.headers.forBuilder(); 169 } 170 171 @Override statusText()172 public String statusText() { 173 return statusText; 174 } 175 176 @Override statusText(String statusText)177 public Builder statusText(String statusText) { 178 this.statusText = statusText; 179 return this; 180 } 181 182 @Override statusCode()183 public int statusCode() { 184 return statusCode; 185 } 186 187 @Override statusCode(int statusCode)188 public Builder statusCode(int statusCode) { 189 this.statusCode = statusCode; 190 return this; 191 } 192 193 @Override content()194 public AbortableInputStream content() { 195 return content; 196 } 197 198 @Override content(AbortableInputStream content)199 public Builder content(AbortableInputStream content) { 200 this.content = content; 201 return this; 202 } 203 204 @Override putHeader(String headerName, List<String> headerValues)205 public Builder putHeader(String headerName, List<String> headerValues) { 206 Validate.paramNotNull(headerName, "headerName"); 207 Validate.paramNotNull(headerValues, "headerValues"); 208 this.headers.forInternalWrite().put(headerName, new ArrayList<>(headerValues)); 209 return this; 210 } 211 212 @Override appendHeader(String headerName, String headerValue)213 public SdkHttpFullResponse.Builder appendHeader(String headerName, String headerValue) { 214 Validate.paramNotNull(headerName, "headerName"); 215 Validate.paramNotNull(headerValue, "headerValue"); 216 this.headers.forInternalWrite().computeIfAbsent(headerName, k -> new ArrayList<>()).add(headerValue); 217 return this; 218 } 219 220 @Override headers(Map<String, List<String>> headers)221 public Builder headers(Map<String, List<String>> headers) { 222 Validate.paramNotNull(headers, "headers"); 223 this.headers.setFromExternal(headers); 224 return this; 225 } 226 227 @Override removeHeader(String headerName)228 public Builder removeHeader(String headerName) { 229 this.headers.forInternalWrite().remove(headerName); 230 return this; 231 } 232 233 @Override clearHeaders()234 public Builder clearHeaders() { 235 this.headers.clear(); 236 return this; 237 } 238 239 @Override headers()240 public Map<String, List<String>> headers() { 241 return unmodifiableMapOfLists(this.headers.forInternalRead()); 242 } 243 244 @Override matchingHeaders(String header)245 public List<String> matchingHeaders(String header) { 246 return unmodifiableList(headers.forInternalRead().getOrDefault(header, emptyList())); 247 } 248 249 @Override firstMatchingHeader(String headerName)250 public Optional<String> firstMatchingHeader(String headerName) { 251 List<String> headers = this.headers.forInternalRead().get(headerName); 252 if (headers == null || headers.isEmpty()) { 253 return Optional.empty(); 254 } 255 256 String header = headers.get(0); 257 if (StringUtils.isEmpty(header)) { 258 return Optional.empty(); 259 } 260 261 return Optional.of(header); 262 } 263 264 @Override firstMatchingHeader(Collection<String> headersToFind)265 public Optional<String> firstMatchingHeader(Collection<String> headersToFind) { 266 for (String headerName : headersToFind) { 267 Optional<String> header = firstMatchingHeader(headerName); 268 if (header.isPresent()) { 269 return header; 270 } 271 } 272 273 return Optional.empty(); 274 } 275 276 @Override forEachHeader(BiConsumer<? super String, ? super List<String>> consumer)277 public void forEachHeader(BiConsumer<? super String, ? super List<String>> consumer) { 278 headers.forInternalRead().forEach((k, v) -> consumer.accept(k, unmodifiableList(v))); 279 } 280 281 @Override numHeaders()282 public int numHeaders() { 283 return headers.forInternalRead().size(); 284 } 285 286 /** 287 * @return An immutable {@link DefaultSdkHttpFullResponse} object. 288 */ 289 @Override build()290 public SdkHttpFullResponse build() { 291 return new DefaultSdkHttpFullResponse(this); 292 } 293 } 294 } 295