1 // Copyright 2009 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.api
.blobstore
;
5 import com
.google
.appengine
.api
.blobstore
.BlobstoreServicePb
.BlobstoreServiceError
;
6 import com
.google
.appengine
.api
.blobstore
.BlobstoreServicePb
.CreateEncodedGoogleStorageKeyRequest
;
7 import com
.google
.appengine
.api
.blobstore
.BlobstoreServicePb
.CreateEncodedGoogleStorageKeyResponse
;
8 import com
.google
.appengine
.api
.blobstore
.BlobstoreServicePb
.CreateUploadURLRequest
;
9 import com
.google
.appengine
.api
.blobstore
.BlobstoreServicePb
.CreateUploadURLResponse
;
10 import com
.google
.appengine
.api
.blobstore
.BlobstoreServicePb
.DeleteBlobRequest
;
11 import com
.google
.appengine
.api
.blobstore
.BlobstoreServicePb
.FetchDataRequest
;
12 import com
.google
.appengine
.api
.blobstore
.BlobstoreServicePb
.FetchDataResponse
;
13 import com
.google
.apphosting
.api
.ApiProxy
;
15 import java
.util
.ArrayList
;
16 import java
.util
.Enumeration
;
17 import java
.util
.HashMap
;
18 import java
.util
.List
;
21 import javax
.servlet
.http
.HttpServletRequest
;
22 import javax
.servlet
.http
.HttpServletResponse
;
25 * {@code BlobstoreServiceImpl} is an implementation of {@link
26 * BlobstoreService} that makes API calls to {@link ApiProxy}.
29 class BlobstoreServiceImpl
implements BlobstoreService
{
30 static final String PACKAGE
= "blobstore";
31 static final String SERVE_HEADER
= "X-AppEngine-BlobKey";
32 static final String UPLOADED_BLOBKEY_ATTR
= "com.google.appengine.api.blobstore.upload.blobkeys";
33 static final String BLOB_RANGE_HEADER
= "X-AppEngine-BlobRange";
35 public String
createUploadUrl(String successPath
) {
36 return createUploadUrl(successPath
, UploadOptions
.Builder
.withDefaults());
39 public String
createUploadUrl(String successPath
, UploadOptions uploadOptions
) {
40 if (successPath
== null) {
41 throw new NullPointerException("Success path must not be null.");
44 CreateUploadURLRequest request
= new CreateUploadURLRequest();
45 request
.setSuccessPath(successPath
);
47 if (uploadOptions
.hasMaxUploadSizeBytesPerBlob()) {
48 request
.setMaxUploadSizePerBlobBytes(uploadOptions
.getMaxUploadSizeBytesPerBlob());
51 if (uploadOptions
.hasMaxUploadSizeBytes()) {
52 request
.setMaxUploadSizeBytes(uploadOptions
.getMaxUploadSizeBytes());
55 if (uploadOptions
.hasGoogleStorageBucketName()) {
56 request
.setGsBucketName(uploadOptions
.getGoogleStorageBucketName());
61 responseBytes
= ApiProxy
.makeSyncCall(PACKAGE
, "CreateUploadURL", request
.toByteArray());
62 } catch (ApiProxy
.ApplicationException ex
) {
63 switch (BlobstoreServiceError
.ErrorCode
.valueOf(ex
.getApplicationError())) {
65 throw new IllegalArgumentException("The resulting URL was too long.");
67 throw new BlobstoreFailureException("An internal blobstore error occured.");
69 throw new BlobstoreFailureException("An unexpected error occurred.", ex
);
73 CreateUploadURLResponse response
= new CreateUploadURLResponse();
74 response
.mergeFrom(responseBytes
);
75 return response
.getUrl();
78 public void serve(BlobKey blobKey
, HttpServletResponse response
) {
79 serve(blobKey
, (ByteRange
) null, response
);
82 public void serve(BlobKey blobKey
, String rangeHeader
, HttpServletResponse response
) {
83 serve(blobKey
, ByteRange
.parse(rangeHeader
), response
);
86 public void serve(BlobKey blobKey
, ByteRange byteRange
, HttpServletResponse response
) {
87 if (response
.isCommitted()) {
88 throw new IllegalStateException("Response was already committed.");
91 response
.setStatus(HttpServletResponse
.SC_OK
);
92 response
.setHeader(SERVE_HEADER
, blobKey
.getKeyString());
93 if (byteRange
!= null) {
94 response
.setHeader(BLOB_RANGE_HEADER
, byteRange
.toString());
98 public ByteRange
getByteRange(HttpServletRequest request
) {
99 @SuppressWarnings("unchecked")
100 Enumeration
<String
> rangeHeaders
= request
.getHeaders("range");
101 if (!rangeHeaders
.hasMoreElements()) {
105 String rangeHeader
= rangeHeaders
.nextElement();
106 if (rangeHeaders
.hasMoreElements()) {
107 throw new UnsupportedRangeFormatException("Cannot accept multiple range headers.");
110 return ByteRange
.parse(rangeHeader
);
113 public void delete(BlobKey
... blobKeys
) {
114 DeleteBlobRequest request
= new DeleteBlobRequest();
115 for (BlobKey blobKey
: blobKeys
) {
116 request
.addBlobKey(blobKey
.getKeyString());
119 if (request
.blobKeySize() == 0) {
123 byte[] responseBytes
;
125 responseBytes
= ApiProxy
.makeSyncCall(PACKAGE
, "DeleteBlob", request
.toByteArray());
126 } catch (ApiProxy
.ApplicationException ex
) {
127 switch (BlobstoreServiceError
.ErrorCode
.valueOf(ex
.getApplicationError())) {
129 throw new BlobstoreFailureException("An internal blobstore error occured.");
131 throw new BlobstoreFailureException("An unexpected error occurred.", ex
);
136 @Deprecated public Map
<String
, BlobKey
> getUploadedBlobs(HttpServletRequest request
) {
137 Map
<String
, List
<BlobKey
>> blobKeys
= getUploads(request
);
138 Map
<String
, BlobKey
> result
= new HashMap
<String
, BlobKey
>(blobKeys
.size());
140 for (Map
.Entry
<String
, List
<BlobKey
>> entry
: blobKeys
.entrySet()) {
141 if (!entry
.getValue().isEmpty()) {
142 result
.put(entry
.getKey(), entry
.getValue().get(0));
148 public Map
<String
, List
<BlobKey
>> getUploads(HttpServletRequest request
) {
149 @SuppressWarnings("unchecked")
150 Map
<String
, List
<String
>> attributes
=
151 (Map
<String
, List
<String
>>) request
.getAttribute(UPLOADED_BLOBKEY_ATTR
);
152 if (attributes
== null) {
153 throw new IllegalStateException("Must be called from a blob upload callback request.");
155 Map
<String
, List
<BlobKey
>> blobKeys
= new HashMap
<String
, List
<BlobKey
>>(attributes
.size());
156 for (Map
.Entry
<String
, List
<String
>> attr
: attributes
.entrySet()) {
157 List
<BlobKey
> blobs
= new ArrayList
<BlobKey
>(attr
.getValue().size());
158 for (String key
: attr
.getValue()) {
159 blobs
.add(new BlobKey(key
));
161 blobKeys
.put(attr
.getKey(), blobs
);
166 public byte[] fetchData(BlobKey blobKey
, long startIndex
, long endIndex
) {
167 if (startIndex
< 0) {
168 throw new IllegalArgumentException("Start index must be >= 0.");
171 if (endIndex
< startIndex
) {
172 throw new IllegalArgumentException("End index must be >= startIndex.");
175 long fetchSize
= endIndex
- startIndex
+ 1;
176 if (fetchSize
> MAX_BLOB_FETCH_SIZE
) {
177 throw new IllegalArgumentException("Blob fetch size " + fetchSize
+ " it larger " +
178 "than maximum size " + MAX_BLOB_FETCH_SIZE
+ " bytes.");
181 FetchDataRequest request
= new FetchDataRequest();
182 request
.setBlobKey(blobKey
.getKeyString());
183 request
.setStartIndex(startIndex
);
184 request
.setEndIndex(endIndex
);
186 byte[] responseBytes
;
188 responseBytes
= ApiProxy
.makeSyncCall(PACKAGE
, "FetchData", request
.toByteArray());
189 } catch (ApiProxy
.ApplicationException ex
) {
190 switch (BlobstoreServiceError
.ErrorCode
.valueOf(ex
.getApplicationError())) {
191 case PERMISSION_DENIED
:
192 throw new SecurityException("This application does not have access to that blob.");
194 throw new IllegalArgumentException("Blob not found.");
196 throw new BlobstoreFailureException("An internal blobstore error occured.");
198 throw new BlobstoreFailureException("An unexpected error occurred.", ex
);
202 FetchDataResponse response
= new FetchDataResponse();
203 response
.mergeFrom(responseBytes
);
204 return response
.getDataAsBytes();
207 public BlobKey
createGsBlobKey(String filename
) {
209 if (!filename
.startsWith("/gs/")) {
210 throw new IllegalArgumentException("Google storage filenames must be" +
211 " prefixed with /gs/");
213 CreateEncodedGoogleStorageKeyRequest request
= new CreateEncodedGoogleStorageKeyRequest();
214 request
.setFilename(filename
);
216 byte[] responseBytes
;
218 responseBytes
= ApiProxy
.makeSyncCall(PACKAGE
,
219 "CreateEncodedGoogleStorageKey", request
.toByteArray());
220 } catch (ApiProxy
.ApplicationException ex
) {
221 switch (BlobstoreServiceError
.ErrorCode
.valueOf(ex
.getApplicationError())) {
223 throw new BlobstoreFailureException("An internal blobstore error occured.");
225 throw new BlobstoreFailureException("An unexpected error occurred.", ex
);
229 CreateEncodedGoogleStorageKeyResponse response
= new CreateEncodedGoogleStorageKeyResponse();
230 response
.mergeFrom(responseBytes
);
231 return new BlobKey(response
.getBlobKey());