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 software.amazon.awssdk.utils.CollectionUtils.deepCopyMap;
19 import static software.amazon.awssdk.utils.CollectionUtils.deepUnmodifiableMap;
20
21 import java.io.Serializable;
22 import java.util.ArrayList;
23 import java.util.LinkedHashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Optional;
27 import java.util.TreeMap;
28 import software.amazon.awssdk.annotations.Immutable;
29 import software.amazon.awssdk.annotations.SdkInternalApi;
30 import software.amazon.awssdk.utils.CollectionUtils;
31 import software.amazon.awssdk.utils.Validate;
32
33 /**
34  * Internal implementation of {@link SdkHttpFullResponse}, buildable via {@link SdkHttpFullResponse#builder()}. Returned by HTTP
35  * implementation to represent a service response.
36  */

37 @SdkInternalApi
38 @Immutable
39 class DefaultSdkHttpFullResponse implements SdkHttpFullResponse, Serializable {
40
41     private static final long serialVersionUID = 1L;
42
43     private final String statusText;
44     private final int statusCode;
45     private final Map<String, List<String>> headers;
46     private final transient AbortableInputStream content;
47
48     private DefaultSdkHttpFullResponse(Builder builder) {
49         this.statusCode = Validate.isNotNegative(builder.statusCode, "Status code must not be negative.");
50         this.statusText = builder.statusText;
51         this.headers = builder.headersAreFromToBuilder
52                        ? builder.headers
53                        : deepUnmodifiableMap(builder.headers, () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER));
54         this.content = builder.content;
55     }
56
57     @Override
58     public Map<String, List<String>> headers() {
59         return headers;
60     }
61
62     @Override
63     public Optional<AbortableInputStream> content() {
64         return Optional.ofNullable(content);
65     }
66
67     @Override
68     public Optional<String> statusText() {
69         return Optional.ofNullable(statusText);
70     }
71
72     @Override
73     public int statusCode() {
74         return statusCode;
75     }
76
77     @Override
78     public SdkHttpFullResponse.Builder toBuilder() {
79         return new Builder(this);
80     }
81
82     /**
83      * Builder for a {@link DefaultSdkHttpFullResponse}.
84      */

85     static final class Builder implements SdkHttpFullResponse.Builder {
86         private String statusText;
87         private int statusCode;
88         private AbortableInputStream content;
89
90         private boolean headersAreFromToBuilder;
91         private Map<String, List<String>> headers;
92
93         Builder() {
94             headersAreFromToBuilder = false;
95             headers = new LinkedHashMap<>();
96         }
97
98         private Builder(DefaultSdkHttpFullResponse defaultSdkHttpFullResponse) {
99             statusText = defaultSdkHttpFullResponse.statusText;
100             statusCode = defaultSdkHttpFullResponse.statusCode;
101             content = defaultSdkHttpFullResponse.content;
102             headersAreFromToBuilder = true;
103             headers = defaultSdkHttpFullResponse.headers;
104         }
105
106         @Override
107         public String statusText() {
108             return statusText;
109         }
110
111         @Override
112         public Builder statusText(String statusText) {
113             this.statusText = statusText;
114             return this;
115         }
116
117         @Override
118         public int statusCode() {
119             return statusCode;
120         }
121
122         @Override
123         public Builder statusCode(int statusCode) {
124             this.statusCode = statusCode;
125             return this;
126         }
127
128         @Override
129         public AbortableInputStream content() {
130             return content;
131         }
132
133         @Override
134         public Builder content(AbortableInputStream content) {
135             this.content = content;
136             return this;
137         }
138
139         @Override
140         public Builder putHeader(String headerName, List<String> headerValues) {
141             Validate.paramNotNull(headerName, "headerName");
142             Validate.paramNotNull(headerValues, "headerValues");
143             copyHeadersIfNeeded();
144             this.headers.put(headerName, new ArrayList<>(headerValues));
145             return this;
146         }
147
148         @Override
149         public SdkHttpFullResponse.Builder appendHeader(String headerName, String headerValue) {
150             Validate.paramNotNull(headerName, "headerName");
151             Validate.paramNotNull(headerValue, "headerValue");
152             copyHeadersIfNeeded();
153             this.headers.computeIfAbsent(headerName, k -> new ArrayList<>()).add(headerValue);
154             return this;
155         }
156
157         @Override
158         public Builder headers(Map<String, List<String>> headers) {
159             Validate.paramNotNull(headers, "headers");
160             this.headers = CollectionUtils.deepCopyMap(headers);
161             headersAreFromToBuilder = false;
162             return this;
163         }
164
165         @Override
166         public Builder removeHeader(String headerName) {
167             copyHeadersIfNeeded();
168             this.headers.remove(headerName);
169             return this;
170         }
171
172         @Override
173         public Builder clearHeaders() {
174             this.headers = new LinkedHashMap<>();
175             headersAreFromToBuilder = false;
176             return this;
177         }
178
179         @Override
180         public Map<String, List<String>> headers() {
181             return CollectionUtils.unmodifiableMapOfLists(this.headers);
182         }
183
184         private void copyHeadersIfNeeded() {
185             if (headersAreFromToBuilder) {
186                 headersAreFromToBuilder = false;
187                 this.headers = deepCopyMap(headers);
188             }
189         }
190
191         /**
192          * @return An immutable {@link DefaultSdkHttpFullResponse} object.
193          */

194         public SdkHttpFullResponse build() {
195             return new DefaultSdkHttpFullResponse(this);
196         }
197     }
198 }
199