1 | /** |
2 | * Licensed to jclouds, Inc. (jclouds) under one or more |
3 | * contributor license agreements. See the NOTICE file |
4 | * distributed with this work for additional information |
5 | * regarding copyright ownership. jclouds licenses this file |
6 | * to you under the Apache License, Version 2.0 (the |
7 | * "License"); you may not use this file except in compliance |
8 | * with the License. You may obtain a copy of the License at |
9 | * |
10 | * http://www.apache.org/licenses/LICENSE-2.0 |
11 | * |
12 | * Unless required by applicable law or agreed to in writing, |
13 | * software distributed under the License is distributed on an |
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
15 | * KIND, either express or implied. See the License for the |
16 | * specific language governing permissions and limitations |
17 | * under the License. |
18 | */ |
19 | package org.jclouds.http.options; |
20 | |
21 | import static com.google.common.base.Preconditions.checkArgument; |
22 | import static com.google.common.base.Preconditions.checkNotNull; |
23 | |
24 | import java.io.UnsupportedEncodingException; |
25 | import java.util.ArrayList; |
26 | import java.util.Date; |
27 | import java.util.List; |
28 | |
29 | import javax.ws.rs.core.HttpHeaders; |
30 | |
31 | import org.jclouds.date.DateService; |
32 | import org.jclouds.date.internal.SimpleDateFormatDateService; |
33 | |
34 | import com.google.common.base.Joiner; |
35 | import com.google.common.collect.Multimap; |
36 | |
37 | /** |
38 | * Contains options supported for HTTP GET operations. <h2> |
39 | * Usage</h2> The recommended way to instantiate a GetObjectOptions object is to statically import |
40 | * GetObjectOptions.Builder.* and invoke a static creation method followed by an instance mutator |
41 | * (if needed): |
42 | * <p/> |
43 | * <code> |
44 | * import static org.jclouds.http.options.GetOptions.Builder.* |
45 | * |
46 | * |
47 | * // this will get the first megabyte of an object. |
48 | * blob = client.get("objectName",range(0,1024)); |
49 | * <code> |
50 | * |
51 | * @author Adrian Cole |
52 | * |
53 | */ |
54 | public class GetOptions extends BaseHttpRequestOptions { |
55 | private final static DateService dateService = new SimpleDateFormatDateService(); |
56 | public static final GetOptions NONE = new GetOptions(); |
57 | private final List<String> ranges = new ArrayList<String>(); |
58 | |
59 | @Override |
60 | public Multimap<String, String> buildRequestHeaders() { |
61 | Multimap<String, String> headers = super.buildRequestHeaders(); |
62 | String range = getRange(); |
63 | if (range != null) |
64 | headers.put("Range", this.getRange()); |
65 | return headers; |
66 | } |
67 | |
68 | /** |
69 | * download the specified range of the object. |
70 | */ |
71 | public GetOptions range(long start, long end) { |
72 | checkArgument(start >= 0, "start must be >= 0"); |
73 | checkArgument(end >= 0, "end must be >= 0"); |
74 | ranges.add(String.format("%d-%d", start, end)); |
75 | return this; |
76 | } |
77 | |
78 | /** |
79 | * download the object offset at <code>start</code> |
80 | */ |
81 | public GetOptions startAt(long start) { |
82 | checkArgument(start >= 0, "start must be >= 0"); |
83 | ranges.add(String.format("%d-", start)); |
84 | return this; |
85 | } |
86 | |
87 | /** |
88 | * download the last <code>count</code> bytes of the object |
89 | */ |
90 | public GetOptions tail(long count) { |
91 | checkArgument(count > 0, "count must be > 0"); |
92 | ranges.add(String.format("-%d", count)); |
93 | return this; |
94 | } |
95 | |
96 | /** |
97 | * For use in the header Range |
98 | * <p /> |
99 | * |
100 | * @see GetOptions#range(long, long) |
101 | */ |
102 | public String getRange() { |
103 | return (ranges.size() > 0) ? String.format("bytes=%s", Joiner.on(",").join(ranges)) : null; |
104 | } |
105 | |
106 | /** |
107 | * Only return the object if it has changed since this time. |
108 | * <p /> |
109 | * Not compatible with {@link #ifETagMatches(String)} or {@link #ifUnmodifiedSince(Date)} |
110 | */ |
111 | public GetOptions ifModifiedSince(Date ifModifiedSince) { |
112 | checkArgument(getIfMatch() == null, "ifETagMatches() is not compatible with ifModifiedSince()"); |
113 | checkArgument(getIfUnmodifiedSince() == null, "ifUnmodifiedSince() is not compatible with ifModifiedSince()"); |
114 | this.headers.put(HttpHeaders.IF_MODIFIED_SINCE, |
115 | dateService.rfc822DateFormat(checkNotNull(ifModifiedSince, "ifModifiedSince"))); |
116 | return this; |
117 | } |
118 | |
119 | /** |
120 | * For use in the header If-Modified-Since |
121 | * <p /> |
122 | * Return the object only if it has been modified since the specified time, otherwise return a |
123 | * 304 (not modified). |
124 | * |
125 | * @see #ifModifiedSince(Date) |
126 | */ |
127 | public String getIfModifiedSince() { |
128 | return this.getFirstHeaderOrNull(HttpHeaders.IF_MODIFIED_SINCE); |
129 | } |
130 | |
131 | /** |
132 | * Only return the object if it hasn't changed since this time. |
133 | * <p /> |
134 | * Not compatible with {@link #ifETagDoesntMatch(String)} or {@link #ifModifiedSince(Date)} |
135 | */ |
136 | public GetOptions ifUnmodifiedSince(Date ifUnmodifiedSince) { |
137 | checkArgument(getIfNoneMatch() == null, "ifETagDoesntMatch() is not compatible with ifUnmodifiedSince()"); |
138 | checkArgument(getIfModifiedSince() == null, "ifModifiedSince() is not compatible with ifUnmodifiedSince()"); |
139 | this.headers.put(HttpHeaders.IF_UNMODIFIED_SINCE, |
140 | dateService.rfc822DateFormat(checkNotNull(ifUnmodifiedSince, "ifUnmodifiedSince"))); |
141 | return this; |
142 | } |
143 | |
144 | /** |
145 | * For use in the header If-Unmodified-Since |
146 | * <p /> |
147 | * Return the object only if it has not been modified since the specified time, otherwise return |
148 | * a 412 (precondition failed). |
149 | * |
150 | * @see #ifUnmodifiedSince(Date) |
151 | */ |
152 | public String getIfUnmodifiedSince() { |
153 | return this.getFirstHeaderOrNull(HttpHeaders.IF_UNMODIFIED_SINCE); |
154 | } |
155 | |
156 | /** |
157 | * The object's eTag hash should match the parameter <code>eTag</code>. |
158 | * |
159 | * <p /> |
160 | * Not compatible with {@link #ifETagDoesntMatch(byte[])} or {@link #ifModifiedSince(Date)} |
161 | * |
162 | * @param eTag |
163 | * hash representing the payload |
164 | * @throws UnsupportedEncodingException |
165 | * if there was a problem converting this into an S3 eTag string |
166 | */ |
167 | public GetOptions ifETagMatches(String eTag) { |
168 | checkArgument(getIfNoneMatch() == null, "ifETagDoesntMatch() is not compatible with ifETagMatches()"); |
169 | checkArgument(getIfModifiedSince() == null, "ifModifiedSince() is not compatible with ifETagMatches()"); |
170 | this.headers.put(HttpHeaders.IF_MATCH, String.format("\"%1$s\"", checkNotNull(eTag, "eTag"))); |
171 | return this; |
172 | } |
173 | |
174 | /** |
175 | * For use in the request header: If-Match |
176 | * <p /> |
177 | * Return the object only if its payload tag (ETag) is the same as the eTag specified, otherwise |
178 | * return a 412 (precondition failed). |
179 | * |
180 | * @see #ifETagMatches(String) |
181 | */ |
182 | public String getIfMatch() { |
183 | return this.getFirstHeaderOrNull(HttpHeaders.IF_MATCH); |
184 | } |
185 | |
186 | /** |
187 | * The object should not have a eTag hash corresponding with the parameter <code>eTag</code>. |
188 | * <p /> |
189 | * Not compatible with {@link #ifETagMatches(String)} or {@link #ifUnmodifiedSince(Date)} |
190 | * |
191 | * @param eTag |
192 | * hash representing the payload |
193 | * @throws UnsupportedEncodingException |
194 | * if there was a problem converting this into an S3 eTag string |
195 | */ |
196 | public GetOptions ifETagDoesntMatch(String eTag) { |
197 | checkArgument(getIfMatch() == null, "ifETagMatches() is not compatible with ifETagDoesntMatch()"); |
198 | checkArgument(getIfUnmodifiedSince() == null, "ifUnmodifiedSince() is not compatible with ifETagDoesntMatch()"); |
199 | this.headers.put(HttpHeaders.IF_NONE_MATCH, String.format("\"%1$s\"", checkNotNull(eTag, "ifETagDoesntMatch"))); |
200 | return this; |
201 | } |
202 | |
203 | /** |
204 | * For use in the request header: If-None-Match |
205 | * <p /> |
206 | * Return the object only if its payload tag (ETag) is different from the one specified, |
207 | * otherwise return a 304 (not modified). |
208 | * |
209 | * @see #ifETagDoesntMatch(String) |
210 | */ |
211 | public String getIfNoneMatch() { |
212 | return this.getFirstHeaderOrNull(HttpHeaders.IF_NONE_MATCH); |
213 | } |
214 | |
215 | public List<String> getRanges() { |
216 | return ranges; |
217 | } |
218 | |
219 | public static class Builder { |
220 | |
221 | /** |
222 | * @see GetOptions#range(long, long) |
223 | */ |
224 | public static GetOptions range(long start, long end) { |
225 | GetOptions options = new GetOptions(); |
226 | return options.range(start, end); |
227 | } |
228 | |
229 | /** |
230 | * @see GetOptions#startAt(long) |
231 | */ |
232 | public static GetOptions startAt(long start) { |
233 | GetOptions options = new GetOptions(); |
234 | return options.startAt(start); |
235 | } |
236 | |
237 | /** |
238 | * @see GetOptions#tail(long) |
239 | */ |
240 | public static GetOptions tail(long count) { |
241 | GetOptions options = new GetOptions(); |
242 | return options.tail(count); |
243 | } |
244 | |
245 | /** |
246 | * @see GetOptions#getIfModifiedSince() |
247 | */ |
248 | public static GetOptions ifModifiedSince(Date ifModifiedSince) { |
249 | GetOptions options = new GetOptions(); |
250 | return options.ifModifiedSince(ifModifiedSince); |
251 | } |
252 | |
253 | /** |
254 | * @see GetOptions#ifUnmodifiedSince(Date) |
255 | */ |
256 | public static GetOptions ifUnmodifiedSince(Date ifUnmodifiedSince) { |
257 | GetOptions options = new GetOptions(); |
258 | return options.ifUnmodifiedSince(ifUnmodifiedSince); |
259 | } |
260 | |
261 | /** |
262 | * @see GetOptions#ifETagMatches(String) |
263 | */ |
264 | public static GetOptions ifETagMatches(String eTag) { |
265 | GetOptions options = new GetOptions(); |
266 | return options.ifETagMatches(eTag); |
267 | } |
268 | |
269 | /** |
270 | * @see GetOptions#ifETagDoesntMatch(String) |
271 | */ |
272 | public static GetOptions ifETagDoesntMatch(String eTag) { |
273 | GetOptions options = new GetOptions(); |
274 | return options.ifETagDoesntMatch(eTag); |
275 | } |
276 | |
277 | } |
278 | |
279 | @Override |
280 | public int hashCode() { |
281 | final int prime = 31; |
282 | int result = super.hashCode(); |
283 | result = prime * result + ((ranges == null) ? 0 : ranges.hashCode()); |
284 | return result; |
285 | } |
286 | |
287 | @Override |
288 | public boolean equals(Object obj) { |
289 | if (this == obj) |
290 | return true; |
291 | if (!super.equals(obj)) |
292 | return false; |
293 | if (getClass() != obj.getClass()) |
294 | return false; |
295 | GetOptions other = (GetOptions) obj; |
296 | if (ranges == null) { |
297 | if (other.ranges != null) |
298 | return false; |
299 | } else if (!ranges.equals(other.ranges)) |
300 | return false; |
301 | return true; |
302 | } |
303 | |
304 | @Override |
305 | public String toString() { |
306 | return "[matrixParameters=" + matrixParameters + ", formParameters=" + formParameters + ", queryParameters=" |
307 | + queryParameters + ", headers=" + headers + ", payload=" + payload + ", pathSuffix=" + pathSuffix |
308 | + ", ranges=" + ranges + "]"; |
309 | } |
310 | |
311 | } |