1 | |
|
2 | |
|
3 | |
|
4 | |
|
5 | |
|
6 | |
|
7 | |
|
8 | |
|
9 | |
|
10 | |
|
11 | |
|
12 | |
|
13 | |
|
14 | |
|
15 | |
|
16 | |
|
17 | |
|
18 | |
|
19 | |
|
20 | |
|
21 | |
|
22 | |
|
23 | |
|
24 | |
|
25 | |
|
26 | |
|
27 | |
|
28 | |
|
29 | |
|
30 | |
package com.jcabi.http.request; |
31 | |
|
32 | |
import com.jcabi.aspects.Immutable; |
33 | |
import com.jcabi.aspects.Loggable; |
34 | |
import com.jcabi.http.Request; |
35 | |
import com.jcabi.http.RequestBody; |
36 | |
import com.jcabi.http.Response; |
37 | |
import com.jcabi.immutable.Array; |
38 | |
import com.jcabi.log.Logger; |
39 | |
import java.lang.reflect.InvocationTargetException; |
40 | |
import java.nio.charset.Charset; |
41 | |
import java.util.LinkedList; |
42 | |
import java.util.List; |
43 | |
import java.util.Map; |
44 | |
import java.util.concurrent.ConcurrentHashMap; |
45 | |
import java.util.concurrent.ConcurrentMap; |
46 | |
import lombok.EqualsAndHashCode; |
47 | |
|
48 | |
|
49 | |
|
50 | |
|
51 | |
|
52 | |
|
53 | |
|
54 | |
@Immutable |
55 | 0 | @EqualsAndHashCode(of = { "req", "code", "phrase", "hdrs", "content" }) |
56 | |
@Loggable(Loggable.DEBUG) |
57 | |
public final class DefaultResponse implements Response { |
58 | |
|
59 | |
|
60 | |
|
61 | |
|
62 | 1 | private static final Charset CHARSET = Charset.forName("UTF-8"); |
63 | |
|
64 | |
|
65 | |
|
66 | |
|
67 | |
private static final String ERR = "\uFFFD"; |
68 | |
|
69 | |
|
70 | |
|
71 | |
|
72 | |
private final transient Request req; |
73 | |
|
74 | |
|
75 | |
|
76 | |
|
77 | |
private final transient int code; |
78 | |
|
79 | |
|
80 | |
|
81 | |
|
82 | |
private final transient String phrase; |
83 | |
|
84 | |
|
85 | |
|
86 | |
|
87 | |
private final transient Array<Map.Entry<String, String>> hdrs; |
88 | |
|
89 | |
|
90 | |
|
91 | |
|
92 | |
@Immutable.Array |
93 | |
private final transient byte[] content; |
94 | |
|
95 | |
|
96 | |
|
97 | |
|
98 | |
|
99 | |
|
100 | |
|
101 | |
|
102 | |
|
103 | |
|
104 | |
public DefaultResponse(final Request request, final int status, |
105 | |
final String reason, final Array<Map.Entry<String, String>> headers, |
106 | 151 | final byte[] body) { |
107 | 151 | this.req = request; |
108 | 151 | this.code = status; |
109 | 151 | this.phrase = reason; |
110 | 151 | this.hdrs = headers; |
111 | 151 | this.content = body.clone(); |
112 | 151 | } |
113 | |
|
114 | |
@Override |
115 | |
public Request back() { |
116 | 215 | return this.req; |
117 | |
} |
118 | |
|
119 | |
@Override |
120 | |
public int status() { |
121 | 308 | return this.code; |
122 | |
} |
123 | |
|
124 | |
@Override |
125 | |
public String reason() { |
126 | 157 | return this.phrase; |
127 | |
} |
128 | |
|
129 | |
@Override |
130 | |
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") |
131 | |
public Map<String, List<String>> headers() { |
132 | 60 | final ConcurrentMap<String, List<String>> map = |
133 | |
new ConcurrentHashMap<>(0); |
134 | 60 | for (final Map.Entry<String, String> header : this.hdrs) { |
135 | 250 | map.putIfAbsent(header.getKey(), new LinkedList<String>()); |
136 | 250 | map.get(header.getKey()).add(header.getValue()); |
137 | 250 | } |
138 | 60 | return map; |
139 | |
} |
140 | |
|
141 | |
@Override |
142 | |
public String body() { |
143 | 64 | final String body = new String(this.content, DefaultResponse.CHARSET); |
144 | 64 | if (body.contains(DefaultResponse.ERR)) { |
145 | 1 | throw new IllegalStateException( |
146 | |
Logger.format( |
147 | |
"broken Unicode text at line #%d in '%[text]s' (%d bytes)", |
148 | |
body.length() - body.replace("\n", "").length(), |
149 | |
body, |
150 | |
this.content.length |
151 | |
) |
152 | |
); |
153 | |
} |
154 | 63 | return body; |
155 | |
} |
156 | |
|
157 | |
@Override |
158 | |
public byte[] binary() { |
159 | 18 | return this.content.clone(); |
160 | |
} |
161 | |
|
162 | |
@Override |
163 | |
@SuppressWarnings("PMD.ShortMethodName") |
164 | |
public <T extends Response> T as(final Class<T> type) { |
165 | |
try { |
166 | 136 | return type.getDeclaredConstructor(Response.class) |
167 | |
.newInstance(this); |
168 | 0 | } catch (final InstantiationException |
169 | |
| IllegalAccessException | NoSuchMethodException |
170 | |
| InvocationTargetException ex) { |
171 | 0 | throw new IllegalStateException(ex); |
172 | |
} |
173 | |
} |
174 | |
|
175 | |
@Override |
176 | |
@SuppressWarnings("PMD.ConsecutiveLiteralAppends") |
177 | |
public String toString() { |
178 | 203 | final StringBuilder text = new StringBuilder(0) |
179 | |
.append(this.code).append(' ') |
180 | |
.append(this.phrase) |
181 | |
.append(" [") |
182 | |
.append(this.back().uri().get()) |
183 | |
.append("]\n"); |
184 | 203 | for (final Map.Entry<String, String> header : this.hdrs) { |
185 | 831 | text.append( |
186 | |
Logger.format( |
187 | |
"%s: %s\n", |
188 | |
header.getKey(), |
189 | |
header.getValue() |
190 | |
) |
191 | |
); |
192 | 830 | } |
193 | 202 | return text.append('\n') |
194 | |
.append(new RequestBody.Printable(this.content)) |
195 | |
.toString(); |
196 | |
} |
197 | |
|
198 | |
} |