1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.jclouds.blobstore;
20
21 import static com.google.common.base.Preconditions.checkArgument;
22 import static com.google.common.base.Preconditions.checkNotNull;
23 import static com.google.common.base.Preconditions.checkState;
24 import static com.google.common.base.Throwables.getCausalChain;
25 import static com.google.common.base.Throwables.propagate;
26 import static com.google.common.collect.Iterables.filter;
27 import static com.google.common.collect.Iterables.find;
28 import static com.google.common.collect.Iterables.size;
29 import static com.google.common.collect.Iterables.transform;
30 import static com.google.common.collect.Lists.newArrayList;
31 import static com.google.common.collect.Lists.partition;
32 import static com.google.common.collect.Maps.newHashMap;
33 import static com.google.common.collect.Sets.filter;
34 import static com.google.common.collect.Sets.newTreeSet;
35 import static com.google.common.io.ByteStreams.toByteArray;
36 import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
37 import static com.google.common.util.concurrent.Futures.immediateFuture;
38
39 import java.io.ByteArrayInputStream;
40 import java.io.ByteArrayOutputStream;
41 import java.io.IOException;
42 import java.io.ObjectInput;
43 import java.io.ObjectInputStream;
44 import java.io.ObjectOutput;
45 import java.io.ObjectOutputStream;
46 import java.net.URI;
47 import java.util.Collection;
48 import java.util.Collections;
49 import java.util.Date;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.Set;
53 import java.util.SortedSet;
54 import java.util.TreeSet;
55 import java.util.Map.Entry;
56 import java.util.concurrent.ConcurrentHashMap;
57 import java.util.concurrent.ConcurrentMap;
58 import java.util.concurrent.ExecutorService;
59
60 import javax.annotation.Nullable;
61 import javax.inject.Inject;
62 import javax.inject.Named;
63 import javax.inject.Provider;
64 import javax.ws.rs.core.HttpHeaders;
65 import javax.ws.rs.core.UriBuilder;
66
67 import org.jclouds.Constants;
68 import org.jclouds.blobstore.domain.Blob;
69 import org.jclouds.blobstore.domain.BlobMetadata;
70 import org.jclouds.blobstore.domain.MutableBlobMetadata;
71 import org.jclouds.blobstore.domain.MutableStorageMetadata;
72 import org.jclouds.blobstore.domain.PageSet;
73 import org.jclouds.blobstore.domain.StorageMetadata;
74 import org.jclouds.blobstore.domain.StorageType;
75 import org.jclouds.blobstore.domain.Blob.Factory;
76 import org.jclouds.blobstore.domain.internal.MutableStorageMetadataImpl;
77 import org.jclouds.blobstore.domain.internal.PageSetImpl;
78 import org.jclouds.blobstore.functions.HttpGetOptionsListToGetOptions;
79 import org.jclouds.blobstore.internal.BaseAsyncBlobStore;
80 import org.jclouds.blobstore.options.CreateContainerOptions;
81 import org.jclouds.blobstore.options.GetOptions;
82 import org.jclouds.blobstore.options.ListContainerOptions;
83 import org.jclouds.blobstore.options.PutOptions;
84 import org.jclouds.blobstore.strategy.IfDirectoryReturnNameStrategy;
85 import org.jclouds.blobstore.util.BlobUtils;
86 import org.jclouds.collect.Memoized;
87 import org.jclouds.crypto.Crypto;
88 import org.jclouds.crypto.CryptoStreams;
89 import org.jclouds.date.DateService;
90 import org.jclouds.domain.Location;
91 import org.jclouds.http.HttpCommand;
92 import org.jclouds.http.HttpRequest;
93 import org.jclouds.http.HttpResponse;
94 import org.jclouds.http.HttpResponseException;
95 import org.jclouds.http.HttpUtils;
96 import org.jclouds.http.options.HttpRequestOptions;
97 import org.jclouds.io.ContentMetadata;
98 import org.jclouds.io.MutableContentMetadata;
99 import org.jclouds.io.Payload;
100 import org.jclouds.io.Payloads;
101 import org.jclouds.io.payloads.ByteArrayPayload;
102 import org.jclouds.io.payloads.DelegatingPayload;
103
104 import com.google.common.base.Function;
105 import com.google.common.base.Predicate;
106 import com.google.common.base.Supplier;
107 import com.google.common.base.Throwables;
108 import com.google.common.collect.Iterables;
109 import com.google.common.collect.Multimaps;
110 import com.google.common.util.concurrent.Futures;
111 import com.google.common.util.concurrent.ListenableFuture;
112
113
114
115
116
117
118
119 public class TransientAsyncBlobStore extends BaseAsyncBlobStore {
120
121 protected final DateService dateService;
122 protected final Crypto crypto;
123 protected final ConcurrentMap<String, ConcurrentMap<String, Blob>> containerToBlobs;
124 protected final Provider<UriBuilder> uriBuilders;
125 protected final ConcurrentMap<String, Location> containerToLocation;
126 protected final HttpGetOptionsListToGetOptions httpGetOptionsConverter;
127 protected final IfDirectoryReturnNameStrategy ifDirectoryReturnName;
128 protected final Factory blobFactory;
129
130 @Inject
131 protected TransientAsyncBlobStore(BlobStoreContext context, DateService dateService, Crypto crypto,
132 ConcurrentMap<String, ConcurrentMap<String, Blob>> containerToBlobs, Provider<UriBuilder> uriBuilders,
133 ConcurrentMap<String, Location> containerToLocation,
134 HttpGetOptionsListToGetOptions httpGetOptionsConverter,
135 IfDirectoryReturnNameStrategy ifDirectoryReturnName, Factory blobFactory, BlobUtils blobUtils,
136 @Named(Constants.PROPERTY_USER_THREADS) ExecutorService service, Supplier<Location> defaultLocation,
137 @Memoized Supplier<Set<? extends Location>> locations) {
138 super(context, blobUtils, service, defaultLocation, locations);
139 this.blobFactory = blobFactory;
140 this.dateService = dateService;
141 this.crypto = crypto;
142 this.containerToBlobs = containerToBlobs;
143 this.uriBuilders = uriBuilders;
144 this.containerToLocation = containerToLocation;
145 this.httpGetOptionsConverter = httpGetOptionsConverter;
146 this.ifDirectoryReturnName = ifDirectoryReturnName;
147 getContainerToLocation().put("stub", defaultLocation.get());
148 getContainerToBlobs().put("stub", new ConcurrentHashMap<String, Blob>());
149 }
150
151
152
153
154 @Override
155 public ListenableFuture<PageSet<? extends StorageMetadata>> list(final String container, ListContainerOptions options) {
156 final Map<String, Blob> realContents = getContainerToBlobs().get(container);
157
158 if (realContents == null)
159 return immediateFailedFuture(cnfe(container));
160
161 SortedSet<StorageMetadata> contents = newTreeSet(transform(realContents.keySet(),
162 new Function<String, StorageMetadata>() {
163 public StorageMetadata apply(String key) {
164 Blob oldBlob = realContents.get(key);
165 checkState(oldBlob != null, "blob " + key + " is not present although it was in the list of "
166 + container);
167 checkState(oldBlob.getMetadata() != null, "blob " + container + "/" + key + " has no metadata");
168 MutableBlobMetadata md = copy(oldBlob.getMetadata());
169 String directoryName = ifDirectoryReturnName.execute(md);
170 if (directoryName != null) {
171 md.setName(directoryName);
172 md.setType(StorageType.RELATIVE_PATH);
173 }
174 return md;
175 }
176 }));
177
178 if (options.getMarker() != null) {
179 final String finalMarker = options.getMarker();
180 StorageMetadata lastMarkerMetadata = find(contents, new Predicate<StorageMetadata>() {
181 public boolean apply(StorageMetadata metadata) {
182 return metadata.getName().equals(finalMarker);
183 }
184 });
185 contents = contents.tailSet(lastMarkerMetadata);
186 contents.remove(lastMarkerMetadata);
187 }
188
189 final String prefix = options.getDir();
190 if (prefix != null) {
191 contents = newTreeSet(filter(contents, new Predicate<StorageMetadata>() {
192 public boolean apply(StorageMetadata o) {
193 return (o != null && o.getName().startsWith(prefix) && !o.getName().equals(prefix));
194 }
195 }));
196 }
197
198 String marker = null;
199 Integer maxResults = options.getMaxResults() != null ? options.getMaxResults() : 1000;
200 if (contents.size() > 0) {
201 SortedSet<StorageMetadata> contentsSlice = firstSliceOfSize(contents, maxResults);
202 if (!contentsSlice.contains(contents.last())) {
203
204 marker = contentsSlice.last().getName();
205 } else {
206 marker = null;
207 }
208 contents = contentsSlice;
209 }
210
211 final String delimiter = options.isRecursive() ? null : "/";
212 if (delimiter != null) {
213 SortedSet<String> commonPrefixes = null;
214 Iterable<String> iterable = transform(contents, new CommonPrefixes(prefix != null ? prefix : null, delimiter));
215 commonPrefixes = iterable != null ? newTreeSet(iterable) : new TreeSet<String>();
216 commonPrefixes.remove(CommonPrefixes.NO_PREFIX);
217
218 contents = newTreeSet(filter(contents, new DelimiterFilter(prefix != null ? prefix : null, delimiter)));
219
220 Iterables.<StorageMetadata> addAll(contents, transform(commonPrefixes,
221 new Function<String, StorageMetadata>() {
222 public StorageMetadata apply(String o) {
223 MutableStorageMetadata md = new MutableStorageMetadataImpl();
224 md.setType(StorageType.RELATIVE_PATH);
225 md.setName(o);
226 return md;
227 }
228 }));
229 }
230
231
232 if (!options.isDetailed()) {
233 for (StorageMetadata md : contents) {
234 md.getUserMetadata().clear();
235 }
236 }
237
238 return Futures.<PageSet<? extends StorageMetadata>> immediateFuture(new PageSetImpl<StorageMetadata>(contents,
239 marker));
240
241 }
242
243 private ContainerNotFoundException cnfe(final String name) {
244 return new ContainerNotFoundException(name, String.format("container %s not in %s", name, getContainerToBlobs()
245 .keySet()));
246 }
247
248 public static MutableBlobMetadata copy(MutableBlobMetadata in) {
249 ByteArrayOutputStream bout = new ByteArrayOutputStream();
250 ObjectOutput os;
251 try {
252 os = new ObjectOutputStream(bout);
253 os.writeObject(in);
254 ObjectInput is = new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray()));
255 MutableBlobMetadata out = (MutableBlobMetadata) is.readObject();
256 convertUserMetadataKeysToLowercase(out);
257 HttpUtils.copy(in.getContentMetadata(), out.getContentMetadata());
258 return out;
259 } catch (Exception e) {
260 propagate(e);
261 assert false : "exception should have propagated: " + e;
262 return null;
263 }
264 }
265
266 private static void convertUserMetadataKeysToLowercase(MutableBlobMetadata metadata) {
267 Map<String, String> lowerCaseUserMetadata = newHashMap();
268 for (Entry<String, String> entry : metadata.getUserMetadata().entrySet()) {
269 lowerCaseUserMetadata.put(entry.getKey().toLowerCase(), entry.getValue());
270 }
271 metadata.setUserMetadata(lowerCaseUserMetadata);
272 }
273
274 public static MutableBlobMetadata copy(MutableBlobMetadata in, String newKey) {
275 MutableBlobMetadata newMd = copy(in);
276 newMd.setName(newKey);
277 return newMd;
278 }
279
280
281
282
283 @Override
284 public ListenableFuture<Void> removeBlob(final String container, final String key) {
285 if (getContainerToBlobs().containsKey(container)) {
286 getContainerToBlobs().get(container).remove(key);
287 }
288 return immediateFuture(null);
289 }
290
291 public ListenableFuture<Blob> removeBlobAndReturnOld(String container, String key) {
292 if (getContainerToBlobs().containsKey(container)) {
293 return immediateFuture(getContainerToBlobs().get(container).remove(key));
294 }
295 return immediateFuture(null);
296 }
297
298
299
300
301 @Override
302 public ListenableFuture<Void> clearContainer(final String container) {
303 getContainerToBlobs().get(container).clear();
304 return immediateFuture(null);
305 }
306
307
308
309
310 @Override
311 public ListenableFuture<Void> deleteContainer(final String container) {
312 if (getContainerToBlobs().containsKey(container)) {
313 getContainerToBlobs().remove(container);
314 }
315 return immediateFuture(null);
316 }
317
318 public ListenableFuture<Boolean> deleteContainerImpl(final String container) {
319 Boolean returnVal = true;
320 if (getContainerToBlobs().containsKey(container)) {
321 if (getContainerToBlobs().get(container).size() == 0)
322 getContainerToBlobs().remove(container);
323 else
324 returnVal = false;
325 }
326 return immediateFuture(returnVal);
327 }
328
329
330
331
332 @Override
333 public ListenableFuture<Boolean> containerExists(final String container) {
334 return immediateFuture(getContainerToBlobs().containsKey(container));
335 }
336
337
338
339
340 @Override
341 public ListenableFuture<PageSet<? extends StorageMetadata>> list() {
342 return Futures.<PageSet<? extends StorageMetadata>> immediateFuture(new PageSetImpl<StorageMetadata>(transform(
343 getContainerToBlobs().keySet(), new Function<String, StorageMetadata>() {
344 public StorageMetadata apply(String name) {
345 MutableStorageMetadata cmd = create();
346 cmd.setName(name);
347 cmd.setType(StorageType.CONTAINER);
348 cmd.setLocation(getContainerToLocation().get(name));
349 return cmd;
350 }
351 }), null));
352 }
353
354 protected MutableStorageMetadata create() {
355 return new MutableStorageMetadataImpl();
356 }
357
358
359
360
361 @Override
362 public ListenableFuture<Boolean> createContainerInLocation(final Location location, final String name) {
363 if (!getContainerToBlobs().containsKey(name)) {
364 getContainerToBlobs().put(name, new ConcurrentHashMap<String, Blob>());
365 getContainerToLocation().put(name, location != null ? location : defaultLocation.get());
366 }
367 return immediateFuture(getContainerToBlobs().containsKey(name));
368 }
369
370
371
372
373 public ListenableFuture<Void> createContainerInLocationIfAbsent(final Location location, final String name) {
374 ConcurrentMap<String, Blob> container = getContainerToBlobs().putIfAbsent(name,
375 new ConcurrentHashMap<String, Blob>());
376 if (container == null) {
377 getContainerToLocation().put(name, location != null ? location : defaultLocation.get());
378 return immediateFuture((Void) null);
379 } else {
380 return Futures.immediateFailedFuture(new IllegalStateException("container " + name + " already exists"));
381 }
382 }
383
384 public String getFirstQueryOrNull(String string, @Nullable HttpRequestOptions options) {
385 if (options == null)
386 return null;
387 Collection<String> values = options.buildQueryParameters().get(string);
388 return (values != null && values.size() >= 1) ? values.iterator().next() : null;
389 }
390
391 protected static class DelimiterFilter implements Predicate<StorageMetadata> {
392 private final String prefix;
393 private final String delimiter;
394
395 public DelimiterFilter(String prefix, String delimiter) {
396 this.prefix = prefix;
397 this.delimiter = delimiter;
398 }
399
400 public boolean apply(StorageMetadata metadata) {
401 if (prefix == null)
402 return metadata.getName().indexOf(delimiter) == -1;
403
404 String toMatch = prefix.endsWith("/") ? prefix : prefix + delimiter;
405 if (metadata.getName().startsWith(toMatch)) {
406 String unprefixedName = metadata.getName().replaceFirst(toMatch, "");
407 if (unprefixedName.equals("")) {
408
409 return false;
410 }
411 return unprefixedName.indexOf(delimiter) == -1;
412 }
413 return false;
414 }
415 }
416
417 protected static class CommonPrefixes implements Function<StorageMetadata, String> {
418 private final String prefix;
419 private final String delimiter;
420 public static final String NO_PREFIX = "NO_PREFIX";
421
422 public CommonPrefixes(String prefix, String delimiter) {
423 this.prefix = prefix;
424 this.delimiter = delimiter;
425 }
426
427 public String apply(StorageMetadata metadata) {
428 String working = metadata.getName();
429 if (prefix != null) {
430
431 String toMatch = prefix.endsWith("/") ? prefix : prefix + delimiter;
432 if (working.startsWith(toMatch)) {
433 working = working.replaceFirst(toMatch, "");
434 }
435 }
436 if (working.contains(delimiter)) {
437 return working.substring(0, working.indexOf(delimiter));
438 }
439 return NO_PREFIX;
440 }
441 }
442
443 public static <T extends Comparable<?>> SortedSet<T> firstSliceOfSize(Iterable<T> elements, int size) {
444 List<List<T>> slices = partition(newArrayList(elements), size);
445 return newTreeSet(slices.get(0));
446 }
447
448 public static HttpResponseException returnResponseException(int code) {
449 HttpResponse response = null;
450 response = new HttpResponse(code, null, null);
451 return new HttpResponseException(new HttpCommand() {
452
453 public int getRedirectCount() {
454 return 0;
455 }
456
457 public int incrementRedirectCount() {
458 return 0;
459 }
460
461 public boolean isReplayable() {
462 return false;
463 }
464
465 public Exception getException() {
466 return null;
467 }
468
469 public int getFailureCount() {
470 return 0;
471 }
472
473 public HttpRequest getCurrentRequest() {
474 return new HttpRequest("GET", URI.create("http://stub"));
475 }
476
477 public int incrementFailureCount() {
478 return 0;
479 }
480
481 public void setException(Exception exception) {
482
483 }
484
485 @Override
486 public void setCurrentRequest(HttpRequest request) {
487
488 }
489
490 }, response);
491 }
492
493
494
495
496 @Override
497 public ListenableFuture<String> putBlob(String containerName, Blob in) {
498 checkArgument(containerName != null, "containerName must be set");
499 checkArgument(in != null, "blob must be set");
500 ConcurrentMap<String, Blob> container = getContainerToBlobs().get(containerName);
501 if (container == null) {
502 new IllegalStateException("containerName not found: " + containerName);
503 }
504
505 Blob blob = createUpdatedCopyOfBlobInContainer(containerName, in);
506
507 container.put(blob.getMetadata().getName(), blob);
508
509 return immediateFuture(Iterables.getOnlyElement(blob.getAllHeaders().get(HttpHeaders.ETAG)));
510 }
511
512 public ListenableFuture<Blob> putBlobAndReturnOld(String containerName, Blob in) {
513 ConcurrentMap<String, Blob> container = getContainerToBlobs().get(containerName);
514 if (container == null) {
515 new IllegalStateException("containerName not found: " + containerName);
516 }
517
518 Blob blob = createUpdatedCopyOfBlobInContainer(containerName, in);
519
520 Blob old = container.put(blob.getMetadata().getName(), blob);
521
522 return immediateFuture(old);
523 }
524
525 protected Blob createUpdatedCopyOfBlobInContainer(String containerName, Blob in) {
526 checkNotNull(in, "blob");
527 checkNotNull(in.getPayload(), "blob.payload");
528 ByteArrayPayload payload = (in.getPayload() instanceof ByteArrayPayload) ? ByteArrayPayload.class.cast(in
529 .getPayload()) : null;
530 if (payload == null)
531 payload = (in.getPayload() instanceof DelegatingPayload) ? (DelegatingPayload.class.cast(in.getPayload())
532 .getDelegate() instanceof ByteArrayPayload) ? ByteArrayPayload.class.cast(DelegatingPayload.class
533 .cast(in.getPayload()).getDelegate()) : null : null;
534 try {
535 if (payload == null || !(payload instanceof ByteArrayPayload)) {
536 MutableContentMetadata oldMd = in.getPayload().getContentMetadata();
537 ByteArrayOutputStream out = new ByteArrayOutputStream();
538 in.getPayload().writeTo(out);
539 payload = (ByteArrayPayload) Payloads.calculateMD5(Payloads.newPayload(out.toByteArray()));
540 HttpUtils.copy(oldMd, payload.getContentMetadata());
541 } else {
542 if (payload.getContentMetadata().getContentMD5() == null)
543 Payloads.calculateMD5(in, crypto.md5());
544 }
545 } catch (IOException e) {
546 Throwables.propagate(e);
547 }
548 Blob blob = blobFactory.create(copy(in.getMetadata()));
549 blob.setPayload(payload);
550 blob.getMetadata().setContainer(containerName);
551 blob.getMetadata().setUri(
552 uriBuilders.get().scheme("mem").host(containerName).path(in.getMetadata().getName()).build());
553 blob.getMetadata().setLastModified(new Date());
554 String eTag = CryptoStreams.hex(payload.getContentMetadata().getContentMD5());
555 blob.getMetadata().setETag(eTag);
556
557 blob.getAllHeaders().replaceValues(HttpHeaders.LAST_MODIFIED,
558 Collections.singleton(dateService.rfc822DateFormat(blob.getMetadata().getLastModified())));
559 blob.getAllHeaders().replaceValues(HttpHeaders.ETAG, Collections.singleton(eTag));
560 copyPayloadHeadersToBlob(payload, blob);
561 blob.getAllHeaders().putAll(Multimaps.forMap(blob.getMetadata().getUserMetadata()));
562 return blob;
563 }
564
565 private void copyPayloadHeadersToBlob(Payload payload, Blob blob) {
566 blob.getAllHeaders().putAll(HttpUtils.getContentHeadersFromMetadata(payload.getContentMetadata()));
567 }
568
569
570
571
572 @Override
573 public ListenableFuture<Boolean> blobExists(final String containerName, final String key) {
574 if (!getContainerToBlobs().containsKey(containerName))
575 return immediateFailedFuture(cnfe(containerName));
576 Map<String, Blob> realContents = getContainerToBlobs().get(containerName);
577 return immediateFuture(realContents.containsKey(key));
578 }
579
580
581
582
583 @Override
584 public ListenableFuture<Blob> getBlob(final String containerName, final String key, GetOptions options) {
585 if (!getContainerToBlobs().containsKey(containerName))
586 return immediateFailedFuture(cnfe(containerName));
587 Map<String, Blob> realContents = getContainerToBlobs().get(containerName);
588 if (!realContents.containsKey(key))
589 return immediateFuture(null);
590
591 Blob object = realContents.get(key);
592
593 if (options.getIfMatch() != null) {
594 if (!object.getMetadata().getETag().equals(options.getIfMatch()))
595 return immediateFailedFuture(returnResponseException(412));
596 }
597 if (options.getIfNoneMatch() != null) {
598 if (object.getMetadata().getETag().equals(options.getIfNoneMatch()))
599 return immediateFailedFuture(returnResponseException(304));
600 }
601 if (options.getIfModifiedSince() != null) {
602 Date modifiedSince = options.getIfModifiedSince();
603 if (object.getMetadata().getLastModified().before(modifiedSince)) {
604 HttpResponse response = new HttpResponse(304, null, null);
605 return immediateFailedFuture(new HttpResponseException(String.format("%1$s is before %2$s", object
606 .getMetadata().getLastModified(), modifiedSince), null, response));
607 }
608
609 }
610 if (options.getIfUnmodifiedSince() != null) {
611 Date unmodifiedSince = options.getIfUnmodifiedSince();
612 if (object.getMetadata().getLastModified().after(unmodifiedSince)) {
613 HttpResponse response = new HttpResponse(412, null, null);
614 return immediateFailedFuture(new HttpResponseException(String.format("%1$s is after %2$s", object
615 .getMetadata().getLastModified(), unmodifiedSince), null, response));
616 }
617 }
618 Blob returnVal = copyBlob(object);
619
620 if (options.getRanges() != null && options.getRanges().size() > 0) {
621 byte[] data;
622 try {
623 data = toByteArray(returnVal.getPayload().getInput());
624 } catch (IOException e) {
625 return immediateFailedFuture(new RuntimeException(e));
626 }
627 ByteArrayOutputStream out = new ByteArrayOutputStream();
628 for (String s : options.getRanges()) {
629 if (s.startsWith("-")) {
630 int length = Integer.parseInt(s.substring(1));
631 out.write(data, data.length - length, length);
632 } else if (s.endsWith("-")) {
633 int offset = Integer.parseInt(s.substring(0, s.length() - 1));
634 out.write(data, offset, data.length - offset);
635 } else if (s.contains("-")) {
636 String[] firstLast = s.split("\\-");
637 int offset = Integer.parseInt(firstLast[0]);
638 int last = Integer.parseInt(firstLast[1]);
639 int length = (last < data.length) ? last + 1 : data.length - offset;
640 out.write(data, offset, length);
641 } else {
642 return immediateFailedFuture(new IllegalArgumentException("first and last were null!"));
643 }
644
645 }
646 ContentMetadata cmd = returnVal.getPayload().getContentMetadata();
647 returnVal.setPayload(out.toByteArray());
648 HttpUtils.copy(cmd, returnVal.getPayload().getContentMetadata());
649 returnVal.getPayload().getContentMetadata().setContentLength(new Long(out.toByteArray().length));
650 }
651 checkNotNull(returnVal.getPayload(), "payload " + returnVal);
652 return immediateFuture(returnVal);
653 }
654
655
656
657
658 @Override
659 public ListenableFuture<BlobMetadata> blobMetadata(final String container, final String key) {
660 try {
661 Blob blob = getBlob(container, key).get();
662 return immediateFuture(blob != null ? (BlobMetadata) copy(blob.getMetadata()) : null);
663 } catch (Exception e) {
664 if (size(filter(getCausalChain(e), KeyNotFoundException.class)) >= 1)
665 return immediateFuture(null);
666 return immediateFailedFuture(e);
667 }
668 }
669
670 private Blob copyBlob(Blob blob) {
671 Blob returnVal = blobFactory.create(copy(blob.getMetadata()));
672 returnVal.setPayload(blob.getPayload());
673 copyPayloadHeadersToBlob(blob.getPayload(), returnVal);
674 return returnVal;
675 }
676
677 public ConcurrentMap<String, ConcurrentMap<String, Blob>> getContainerToBlobs() {
678 return containerToBlobs;
679 }
680
681 @Override
682 protected boolean deleteAndVerifyContainerGone(String container) {
683 getContainerToBlobs().remove(container);
684 return getContainerToBlobs().containsKey(container);
685 }
686
687 public ConcurrentMap<String, Location> getContainerToLocation() {
688 return containerToLocation;
689 }
690
691 @Override
692 public ListenableFuture<String> putBlob(String container, Blob blob, PutOptions options) {
693
694 return putBlob(container, blob);
695 }
696
697 @Override
698 public ListenableFuture<Boolean> createContainerInLocation(Location location, String container,
699 CreateContainerOptions options) {
700 if (options.isPublicRead())
701 throw new UnsupportedOperationException("publicRead");
702 return createContainerInLocation(location, container);
703 }
704
705 }