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 org.jclouds.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 return immediateFuture(Boolean.FALSE);
365 }
366 getContainerToBlobs().put(name, new ConcurrentHashMap<String, Blob>());
367 getContainerToLocation().put(name, location != null ? location : defaultLocation.get());
368 return immediateFuture(Boolean.TRUE);
369 }
370
371
372
373
374 public ListenableFuture<Void> createContainerInLocationIfAbsent(final Location location, final String name) {
375 ConcurrentMap<String, Blob> container = getContainerToBlobs().putIfAbsent(name,
376 new ConcurrentHashMap<String, Blob>());
377 if (container == null) {
378 getContainerToLocation().put(name, location != null ? location : defaultLocation.get());
379 return immediateFuture((Void) null);
380 } else {
381 return Futures.immediateFailedFuture(new IllegalStateException("container " + name + " already exists"));
382 }
383 }
384
385 public String getFirstQueryOrNull(String string, @Nullable HttpRequestOptions options) {
386 if (options == null)
387 return null;
388 Collection<String> values = options.buildQueryParameters().get(string);
389 return (values != null && values.size() >= 1) ? values.iterator().next() : null;
390 }
391
392 protected static class DelimiterFilter implements Predicate<StorageMetadata> {
393 private final String prefix;
394 private final String delimiter;
395
396 public DelimiterFilter(String prefix, String delimiter) {
397 this.prefix = prefix;
398 this.delimiter = delimiter;
399 }
400
401 public boolean apply(StorageMetadata metadata) {
402 if (prefix == null)
403 return metadata.getName().indexOf(delimiter) == -1;
404
405 String toMatch = prefix.endsWith("/") ? prefix : prefix + delimiter;
406 if (metadata.getName().startsWith(toMatch)) {
407 String unprefixedName = metadata.getName().replaceFirst(toMatch, "");
408 if (unprefixedName.equals("")) {
409
410 return false;
411 }
412 return unprefixedName.indexOf(delimiter) == -1;
413 }
414 return false;
415 }
416 }
417
418 protected static class CommonPrefixes implements Function<StorageMetadata, String> {
419 private final String prefix;
420 private final String delimiter;
421 public static final String NO_PREFIX = "NO_PREFIX";
422
423 public CommonPrefixes(String prefix, String delimiter) {
424 this.prefix = prefix;
425 this.delimiter = delimiter;
426 }
427
428 public String apply(StorageMetadata metadata) {
429 String working = metadata.getName();
430 if (prefix != null) {
431
432 String toMatch = prefix.endsWith("/") ? prefix : prefix + delimiter;
433 if (working.startsWith(toMatch)) {
434 working = working.replaceFirst(toMatch, "");
435 }
436 }
437 if (working.contains(delimiter)) {
438 return working.substring(0, working.indexOf(delimiter));
439 }
440 return NO_PREFIX;
441 }
442 }
443
444 public static <T extends Comparable<?>> SortedSet<T> firstSliceOfSize(Iterable<T> elements, int size) {
445 List<List<T>> slices = partition(newArrayList(elements), size);
446 return newTreeSet(slices.get(0));
447 }
448
449 public static HttpResponseException returnResponseException(int code) {
450 HttpResponse response = null;
451 response = new HttpResponse(code, null, null);
452 return new HttpResponseException(new HttpCommand() {
453
454 public int getRedirectCount() {
455 return 0;
456 }
457
458 public int incrementRedirectCount() {
459 return 0;
460 }
461
462 public boolean isReplayable() {
463 return false;
464 }
465
466 public Exception getException() {
467 return null;
468 }
469
470 public int getFailureCount() {
471 return 0;
472 }
473
474 public HttpRequest getCurrentRequest() {
475 return new HttpRequest("GET", URI.create("http://stub"));
476 }
477
478 public int incrementFailureCount() {
479 return 0;
480 }
481
482 public void setException(Exception exception) {
483
484 }
485
486 @Override
487 public void setCurrentRequest(HttpRequest request) {
488
489 }
490
491 }, response);
492 }
493
494
495
496
497 @Override
498 public ListenableFuture<String> putBlob(String containerName, Blob in) {
499 checkArgument(containerName != null, "containerName must be set");
500 checkArgument(in != null, "blob must be set");
501 ConcurrentMap<String, Blob> container = getContainerToBlobs().get(containerName);
502 if (container == null) {
503 new IllegalStateException("containerName not found: " + containerName);
504 }
505
506 Blob blob = createUpdatedCopyOfBlobInContainer(containerName, in);
507
508 container.put(blob.getMetadata().getName(), blob);
509
510 return immediateFuture(Iterables.getOnlyElement(blob.getAllHeaders().get(HttpHeaders.ETAG)));
511 }
512
513 public ListenableFuture<Blob> putBlobAndReturnOld(String containerName, Blob in) {
514 ConcurrentMap<String, Blob> container = getContainerToBlobs().get(containerName);
515 if (container == null) {
516 new IllegalStateException("containerName not found: " + containerName);
517 }
518
519 Blob blob = createUpdatedCopyOfBlobInContainer(containerName, in);
520
521 Blob old = container.put(blob.getMetadata().getName(), blob);
522
523 return immediateFuture(old);
524 }
525
526 protected Blob createUpdatedCopyOfBlobInContainer(String containerName, Blob in) {
527 checkNotNull(in, "blob");
528 checkNotNull(in.getPayload(), "blob.payload");
529 ByteArrayPayload payload = (in.getPayload() instanceof ByteArrayPayload) ? ByteArrayPayload.class.cast(in
530 .getPayload()) : null;
531 if (payload == null)
532 payload = (in.getPayload() instanceof DelegatingPayload) ? (DelegatingPayload.class.cast(in.getPayload())
533 .getDelegate() instanceof ByteArrayPayload) ? ByteArrayPayload.class.cast(DelegatingPayload.class
534 .cast(in.getPayload()).getDelegate()) : null : null;
535 try {
536 if (payload == null || !(payload instanceof ByteArrayPayload)) {
537 MutableContentMetadata oldMd = in.getPayload().getContentMetadata();
538 ByteArrayOutputStream out = new ByteArrayOutputStream();
539 in.getPayload().writeTo(out);
540 payload = (ByteArrayPayload) Payloads.calculateMD5(Payloads.newPayload(out.toByteArray()));
541 HttpUtils.copy(oldMd, payload.getContentMetadata());
542 } else {
543 if (payload.getContentMetadata().getContentMD5() == null)
544 Payloads.calculateMD5(in, crypto.md5());
545 }
546 } catch (IOException e) {
547 Throwables.propagate(e);
548 }
549 Blob blob = blobFactory.create(copy(in.getMetadata()));
550 blob.setPayload(payload);
551 blob.getMetadata().setContainer(containerName);
552 blob.getMetadata().setUri(
553 uriBuilders.get().scheme("mem").host(containerName).path(in.getMetadata().getName()).build());
554 blob.getMetadata().setLastModified(new Date());
555 String eTag = CryptoStreams.hex(payload.getContentMetadata().getContentMD5());
556 blob.getMetadata().setETag(eTag);
557
558 blob.getAllHeaders().replaceValues(HttpHeaders.LAST_MODIFIED,
559 Collections.singleton(dateService.rfc822DateFormat(blob.getMetadata().getLastModified())));
560 blob.getAllHeaders().replaceValues(HttpHeaders.ETAG, Collections.singleton(eTag));
561 copyPayloadHeadersToBlob(payload, blob);
562 blob.getAllHeaders().putAll(Multimaps.forMap(blob.getMetadata().getUserMetadata()));
563 return blob;
564 }
565
566 private void copyPayloadHeadersToBlob(Payload payload, Blob blob) {
567 blob.getAllHeaders().putAll(HttpUtils.getContentHeadersFromMetadata(payload.getContentMetadata()));
568 }
569
570
571
572
573 @Override
574 public ListenableFuture<Boolean> blobExists(final String containerName, final String key) {
575 if (!getContainerToBlobs().containsKey(containerName))
576 return immediateFailedFuture(cnfe(containerName));
577 Map<String, Blob> realContents = getContainerToBlobs().get(containerName);
578 return immediateFuture(realContents.containsKey(key));
579 }
580
581
582
583
584 @Override
585 public ListenableFuture<Blob> getBlob(final String containerName, final String key, GetOptions options) {
586 if (!getContainerToBlobs().containsKey(containerName))
587 return immediateFailedFuture(cnfe(containerName));
588 Map<String, Blob> realContents = getContainerToBlobs().get(containerName);
589 if (!realContents.containsKey(key))
590 return immediateFuture(null);
591
592 Blob object = realContents.get(key);
593
594 if (options.getIfMatch() != null) {
595 if (!object.getMetadata().getETag().equals(options.getIfMatch()))
596 return immediateFailedFuture(returnResponseException(412));
597 }
598 if (options.getIfNoneMatch() != null) {
599 if (object.getMetadata().getETag().equals(options.getIfNoneMatch()))
600 return immediateFailedFuture(returnResponseException(304));
601 }
602 if (options.getIfModifiedSince() != null) {
603 Date modifiedSince = options.getIfModifiedSince();
604 if (object.getMetadata().getLastModified().before(modifiedSince)) {
605 HttpResponse response = new HttpResponse(304, null, null);
606 return immediateFailedFuture(new HttpResponseException(String.format("%1$s is before %2$s", object
607 .getMetadata().getLastModified(), modifiedSince), null, response));
608 }
609
610 }
611 if (options.getIfUnmodifiedSince() != null) {
612 Date unmodifiedSince = options.getIfUnmodifiedSince();
613 if (object.getMetadata().getLastModified().after(unmodifiedSince)) {
614 HttpResponse response = new HttpResponse(412, null, null);
615 return immediateFailedFuture(new HttpResponseException(String.format("%1$s is after %2$s", object
616 .getMetadata().getLastModified(), unmodifiedSince), null, response));
617 }
618 }
619 Blob returnVal = copyBlob(object);
620
621 if (options.getRanges() != null && options.getRanges().size() > 0) {
622 byte[] data;
623 try {
624 data = toByteArray(returnVal.getPayload().getInput());
625 } catch (IOException e) {
626 return immediateFailedFuture(new RuntimeException(e));
627 }
628 ByteArrayOutputStream out = new ByteArrayOutputStream();
629 for (String s : options.getRanges()) {
630 if (s.startsWith("-")) {
631 int length = Integer.parseInt(s.substring(1));
632 out.write(data, data.length - length, length);
633 } else if (s.endsWith("-")) {
634 int offset = Integer.parseInt(s.substring(0, s.length() - 1));
635 out.write(data, offset, data.length - offset);
636 } else if (s.contains("-")) {
637 String[] firstLast = s.split("\\-");
638 int offset = Integer.parseInt(firstLast[0]);
639 int last = Integer.parseInt(firstLast[1]);
640 int length = (last < data.length) ? last + 1 : data.length - offset;
641 out.write(data, offset, length);
642 } else {
643 return immediateFailedFuture(new IllegalArgumentException("first and last were null!"));
644 }
645
646 }
647 ContentMetadata cmd = returnVal.getPayload().getContentMetadata();
648 returnVal.setPayload(out.toByteArray());
649 HttpUtils.copy(cmd, returnVal.getPayload().getContentMetadata());
650 returnVal.getPayload().getContentMetadata().setContentLength(new Long(out.toByteArray().length));
651 }
652 checkNotNull(returnVal.getPayload(), "payload " + returnVal);
653 return immediateFuture(returnVal);
654 }
655
656
657
658
659 @Override
660 public ListenableFuture<BlobMetadata> blobMetadata(final String container, final String key) {
661 try {
662 Blob blob = getBlob(container, key).get();
663 return immediateFuture(blob != null ? (BlobMetadata) copy(blob.getMetadata()) : null);
664 } catch (Exception e) {
665 if (size(filter(getCausalChain(e), KeyNotFoundException.class)) >= 1)
666 return immediateFuture(null);
667 return immediateFailedFuture(e);
668 }
669 }
670
671 private Blob copyBlob(Blob blob) {
672 Blob returnVal = blobFactory.create(copy(blob.getMetadata()));
673 returnVal.setPayload(blob.getPayload());
674 copyPayloadHeadersToBlob(blob.getPayload(), returnVal);
675 return returnVal;
676 }
677
678 public ConcurrentMap<String, ConcurrentMap<String, Blob>> getContainerToBlobs() {
679 return containerToBlobs;
680 }
681
682 @Override
683 protected boolean deleteAndVerifyContainerGone(String container) {
684 getContainerToBlobs().remove(container);
685 return getContainerToBlobs().containsKey(container);
686 }
687
688 public ConcurrentMap<String, Location> getContainerToLocation() {
689 return containerToLocation;
690 }
691
692 @Override
693 public ListenableFuture<String> putBlob(String container, Blob blob, PutOptions options) {
694
695 return putBlob(container, blob);
696 }
697
698 @Override
699 public ListenableFuture<Boolean> createContainerInLocation(Location location, String container,
700 CreateContainerOptions options) {
701 if (options.isPublicRead())
702 throw new UnsupportedOperationException("publicRead");
703 return createContainerInLocation(location, container);
704 }
705
706 }