View Javadoc

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 }