EMMA Coverage Report (generated Wed Jun 22 19:47:49 EDT 2011)
[all classes][org.jclouds.filesystem]

COVERAGE SUMMARY FOR SOURCE FILE [FilesystemAsyncBlobStore.java]

nameclass, %method, %block, %line, %
FilesystemAsyncBlobStore.java44%  (4/9)55%  (28/51)48%  (637/1317)49%  (119.7/243)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class FilesystemAsyncBlobStore$20%   (0/1)0%   (0/2)0%   (0/15)0%   (0/2)
FilesystemAsyncBlobStore$2 (FilesystemAsyncBlobStore, String): void 0%   (0/1)0%   (0/9)0%   (0/1)
apply (StorageMetadata): boolean 0%   (0/1)0%   (0/6)0%   (0/1)
     
class FilesystemAsyncBlobStore$40%   (0/1)0%   (0/2)0%   (0/18)0%   (0/5)
FilesystemAsyncBlobStore$4 (FilesystemAsyncBlobStore): void 0%   (0/1)0%   (0/6)0%   (0/1)
apply (String): StorageMetadata 0%   (0/1)0%   (0/12)0%   (0/4)
     
class FilesystemAsyncBlobStore$60%   (0/1)0%   (0/10)0%   (0/24)0%   (0/10)
FilesystemAsyncBlobStore$6 (): void 0%   (0/1)0%   (0/3)0%   (0/1)
getCurrentRequest (): HttpRequest 0%   (0/1)0%   (0/7)0%   (0/1)
getException (): Exception 0%   (0/1)0%   (0/2)0%   (0/1)
getFailureCount (): int 0%   (0/1)0%   (0/2)0%   (0/1)
getRedirectCount (): int 0%   (0/1)0%   (0/2)0%   (0/1)
incrementFailureCount (): int 0%   (0/1)0%   (0/2)0%   (0/1)
incrementRedirectCount (): int 0%   (0/1)0%   (0/2)0%   (0/1)
isReplayable (): boolean 0%   (0/1)0%   (0/2)0%   (0/1)
setCurrentRequest (HttpRequest): void 0%   (0/1)0%   (0/1)0%   (0/1)
setException (Exception): void 0%   (0/1)0%   (0/1)0%   (0/1)
     
class FilesystemAsyncBlobStore$CommonPrefixes0%   (0/1)0%   (0/2)0%   (0/58)0%   (0/12)
FilesystemAsyncBlobStore$CommonPrefixes (String, String): void 0%   (0/1)0%   (0/9)0%   (0/4)
apply (StorageMetadata): String 0%   (0/1)0%   (0/49)0%   (0/8)
     
class FilesystemAsyncBlobStore$DelimiterFilter0%   (0/1)0%   (0/2)0%   (0/71)0%   (0/13)
FilesystemAsyncBlobStore$DelimiterFilter (String, String): void 0%   (0/1)0%   (0/9)0%   (0/4)
apply (StorageMetadata): boolean 0%   (0/1)0%   (0/62)0%   (0/9)
     
class FilesystemAsyncBlobStore100% (1/1)81%  (22/27)52%  (523/1009)57%  (105.8/187)
copy (MutableBlobMetadata, String): MutableBlobMetadata 0%   (0/1)0%   (0/8)0%   (0/3)
createContainerInLocation (Location, String, CreateContainerOptions): Listena... 0%   (0/1)0%   (0/13)0%   (0/3)
getFirstQueryOrNull (String, HttpRequestOptions): String 0%   (0/1)0%   (0/22)0%   (0/4)
putBlob (String, Blob, PutOptions): ListenableFuture 0%   (0/1)0%   (0/5)0%   (0/1)
returnResponseException (int): HttpResponseException 0%   (0/1)0%   (0/17)0%   (0/3)
getBlob (String, String, GetOptions): ListenableFuture 100% (1/1)30%  (97/319)32%  (16/50)
getEtag (Blob): String 100% (1/1)47%  (15/32)57%  (4/7)
blobMetadata (String, String): ListenableFuture 100% (1/1)50%  (15/30)32%  (1.9/6)
convertUserMetadataKeysToLowercase (MutableBlobMetadata): void 100% (1/1)50%  (14/28)68%  (3.4/5)
list (String, ListContainerOptions): ListenableFuture 100% (1/1)52%  (102/197)62%  (23.6/38)
putBlob (String, Blob): ListenableFuture 100% (1/1)63%  (33/52)67%  (6/9)
copy (MutableBlobMetadata): MutableBlobMetadata 100% (1/1)64%  (36/56)67%  (8/12)
<static initializer> 100% (1/1)75%  (6/8)75%  (0.8/1)
loadFileBlob (String, String): Blob 100% (1/1)81%  (71/88)83%  (15/18)
FilesystemAsyncBlobStore (BlobStoreContext, DateService, Crypto, HttpGetOptio... 100% (1/1)100% (32/32)100% (9/9)
access$000 (FilesystemAsyncBlobStore, String, String): Blob 100% (1/1)100% (5/5)100% (1/1)
blobExists (String, String): ListenableFuture 100% (1/1)100% (8/8)100% (1/1)
cnfe (String): ContainerNotFoundException 100% (1/1)100% (13/13)100% (1/1)
containerExists (String): ListenableFuture 100% (1/1)100% (8/8)100% (2/2)
containerExistsSyncImpl (String): boolean 100% (1/1)100% (5/5)100% (1/1)
create (): MutableStorageMetadata 100% (1/1)100% (4/4)100% (1/1)
createContainerInLocation (Location, String): ListenableFuture 100% (1/1)100% (9/9)100% (2/2)
deleteAndVerifyContainerGone (String): boolean 100% (1/1)100% (8/8)100% (2/2)
deleteContainer (String): ListenableFuture 100% (1/1)100% (7/7)100% (2/2)
firstSliceOfSize (Iterable, int): SortedSet 100% (1/1)100% (11/11)100% (2/2)
list (): ListenableFuture 100% (1/1)100% (16/16)100% (2/2)
removeBlob (String, String): ListenableFuture 100% (1/1)100% (8/8)100% (2/2)
     
class FilesystemAsyncBlobStore$1100% (1/1)100% (2/2)90%  (69/77)79%  (7.9/10)
apply (String): StorageMetadata 100% (1/1)88%  (60/68)77%  (6.9/9)
FilesystemAsyncBlobStore$1 (FilesystemAsyncBlobStore, String): void 100% (1/1)100% (9/9)100% (1/1)
     
class FilesystemAsyncBlobStore$3100% (1/1)100% (2/2)100% (27/27)100% (2/2)
FilesystemAsyncBlobStore$3 (FilesystemAsyncBlobStore, String): void 100% (1/1)100% (9/9)100% (1/1)
apply (StorageMetadata): boolean 100% (1/1)100% (18/18)100% (1/1)
     
class FilesystemAsyncBlobStore$5100% (1/1)100% (2/2)100% (18/18)100% (5/5)
FilesystemAsyncBlobStore$5 (FilesystemAsyncBlobStore): void 100% (1/1)100% (6/6)100% (1/1)
apply (String): StorageMetadata 100% (1/1)100% (12/12)100% (4/4)

1/**
2 *
3 * Copyright (C) 2011 Cloud Conscious, LLC. <info@cloudconscious.com>
4 *
5 * ====================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * 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, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 * ====================================================================
18 */
19package org.jclouds.filesystem;
20 
21import static com.google.common.base.Preconditions.checkNotNull;
22import static com.google.common.base.Preconditions.checkState;
23import static com.google.common.base.Throwables.getCausalChain;
24import static com.google.common.base.Throwables.propagate;
25import static com.google.common.collect.Iterables.filter;
26import static com.google.common.collect.Iterables.find;
27import static com.google.common.collect.Iterables.size;
28import static com.google.common.collect.Iterables.transform;
29import static com.google.common.collect.Lists.newArrayList;
30import static com.google.common.collect.Lists.partition;
31import static com.google.common.collect.Maps.newHashMap;
32import static com.google.common.collect.Sets.filter;
33import static com.google.common.collect.Sets.newTreeSet;
34import static com.google.common.io.ByteStreams.toByteArray;
35import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
36import static com.google.common.util.concurrent.Futures.immediateFuture;
37 
38import java.io.ByteArrayInputStream;
39import java.io.ByteArrayOutputStream;
40import java.io.File;
41import java.io.IOException;
42import java.io.ObjectInput;
43import java.io.ObjectInputStream;
44import java.io.ObjectOutput;
45import java.io.ObjectOutputStream;
46import java.net.URI;
47import java.util.Collection;
48import java.util.Date;
49import java.util.List;
50import java.util.Map;
51import java.util.Set;
52import java.util.SortedSet;
53import java.util.TreeSet;
54import java.util.Map.Entry;
55import java.util.concurrent.ExecutorService;
56 
57import javax.annotation.Nullable;
58import javax.annotation.Resource;
59import javax.inject.Inject;
60import javax.inject.Named;
61import javax.ws.rs.Path;
62import javax.ws.rs.PathParam;
63 
64import org.jclouds.Constants;
65import org.jclouds.blobstore.BlobStoreContext;
66import org.jclouds.blobstore.ContainerNotFoundException;
67import org.jclouds.blobstore.KeyNotFoundException;
68import org.jclouds.blobstore.domain.Blob;
69import org.jclouds.blobstore.domain.BlobMetadata;
70import org.jclouds.blobstore.domain.MutableBlobMetadata;
71import org.jclouds.blobstore.domain.MutableStorageMetadata;
72import org.jclouds.blobstore.domain.PageSet;
73import org.jclouds.blobstore.domain.StorageMetadata;
74import org.jclouds.blobstore.domain.StorageType;
75import org.jclouds.blobstore.domain.Blob.Factory;
76import org.jclouds.blobstore.domain.internal.MutableBlobMetadataImpl;
77import org.jclouds.blobstore.domain.internal.MutableStorageMetadataImpl;
78import org.jclouds.blobstore.domain.internal.PageSetImpl;
79import org.jclouds.blobstore.functions.HttpGetOptionsListToGetOptions;
80import org.jclouds.blobstore.internal.BaseAsyncBlobStore;
81import org.jclouds.blobstore.options.CreateContainerOptions;
82import org.jclouds.blobstore.options.GetOptions;
83import org.jclouds.blobstore.options.ListContainerOptions;
84import org.jclouds.blobstore.options.PutOptions;
85import org.jclouds.blobstore.strategy.IfDirectoryReturnNameStrategy;
86import org.jclouds.blobstore.util.BlobUtils;
87import org.jclouds.collect.Memoized;
88import org.jclouds.crypto.Crypto;
89import org.jclouds.crypto.CryptoStreams;
90import org.jclouds.date.DateService;
91import org.jclouds.domain.Location;
92import org.jclouds.filesystem.predicates.validators.FilesystemContainerNameValidator;
93import org.jclouds.filesystem.strategy.FilesystemStorageStrategy;
94import org.jclouds.http.HttpCommand;
95import org.jclouds.http.HttpRequest;
96import org.jclouds.http.HttpResponse;
97import org.jclouds.http.HttpResponseException;
98import org.jclouds.http.options.HttpRequestOptions;
99import org.jclouds.io.Payload;
100import org.jclouds.io.Payloads;
101import org.jclouds.io.payloads.BaseMutableContentMetadata;
102import org.jclouds.io.payloads.FilePayload;
103import org.jclouds.logging.Logger;
104import org.jclouds.rest.annotations.ParamValidators;
105 
106import com.google.common.base.Function;
107import com.google.common.base.Predicate;
108import com.google.common.base.Supplier;
109import com.google.common.base.Throwables;
110import com.google.common.collect.Iterables;
111import com.google.common.util.concurrent.Futures;
112import com.google.common.util.concurrent.ListenableFuture;
113 
114/**
115 * 
116 * Preconditions: Blob name cannot start with / char (or \ under windows)
117 * 
118 * @author Alfredo "Rainbowbreeze" Morresi
119 */
120public class FilesystemAsyncBlobStore extends BaseAsyncBlobStore {
121 
122   @Resource
123   protected Logger logger = Logger.NULL;
124 
125   protected final DateService dateService;
126   protected final Crypto crypto;
127   protected final HttpGetOptionsListToGetOptions httpGetOptionsConverter;
128   protected final IfDirectoryReturnNameStrategy ifDirectoryReturnName;
129   protected final Factory blobFactory;
130   protected final FilesystemStorageStrategy storageStrategy;
131 
132   @Inject
133   protected FilesystemAsyncBlobStore(BlobStoreContext context, DateService dateService, Crypto crypto,
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, FilesystemStorageStrategy storageStrategy) {
138      super(context, blobUtils, service, defaultLocation, locations);
139      // super(context, blobUtils, service, null, null);
140      this.blobFactory = blobFactory;
141      this.dateService = dateService;
142      this.crypto = crypto;
143      this.httpGetOptionsConverter = httpGetOptionsConverter;
144      this.ifDirectoryReturnName = ifDirectoryReturnName;
145      this.storageStrategy = checkNotNull(storageStrategy, "Storage strategy");
146   }
147 
148   /**
149    * default maxResults is 1000
150    */
151   @Override
152   public ListenableFuture<PageSet<? extends StorageMetadata>> list(final String container, ListContainerOptions options) {
153 
154      // Check if the container exists
155      if (!containerExistsSyncImpl(container)) {
156         return immediateFailedFuture(cnfe(container));
157      }
158 
159      // Loading blobs from container
160      Iterable<String> blobBelongingToContainer = null;
161      try {
162         blobBelongingToContainer = storageStrategy.getBlobKeysInsideContainer(container);
163      } catch (IOException e) {
164         logger.error(e, "An error occurred loading blobs contained into container %s", container);
165         Throwables.propagate(e);
166      }
167 
168      SortedSet<StorageMetadata> contents = newTreeSet(transform(blobBelongingToContainer,
169               new Function<String, StorageMetadata>() {
170                  public StorageMetadata apply(String key) {
171                     Blob oldBlob = loadFileBlob(container, key);
172 
173                     checkState(oldBlob != null, "blob " + key + " is not present although it was in the list of "
174                              + container);
175                     checkState(oldBlob.getMetadata() != null, "blob " + container + "/" + key + " has no metadata");
176                     MutableBlobMetadata md = copy(oldBlob.getMetadata());
177                     String directoryName = ifDirectoryReturnName.execute(md);
178                     if (directoryName != null) {
179                        md.setName(directoryName);
180                        md.setType(StorageType.RELATIVE_PATH);
181                     }
182                     return md;
183                  }
184               }));
185 
186      String marker = null;
187      if (options != null) {
188         if (options.getMarker() != null) {
189            final String finalMarker = options.getMarker();
190            StorageMetadata lastMarkerMetadata = find(contents, new Predicate<StorageMetadata>() {
191               public boolean apply(StorageMetadata metadata) {
192                  return metadata.getName().equals(finalMarker);
193               }
194            });
195            contents = contents.tailSet(lastMarkerMetadata);
196            contents.remove(lastMarkerMetadata);
197         }
198 
199         final String prefix = options.getDir();
200         if (prefix != null) {
201            contents = newTreeSet(filter(contents, new Predicate<StorageMetadata>() {
202               public boolean apply(StorageMetadata o) {
203                  return (o != null && o.getName().startsWith(prefix) && !o.getName().equals(prefix));
204               }
205            }));
206         }
207 
208         Integer maxResults = options.getMaxResults() != null ? options.getMaxResults() : 1000;
209         if (contents.size() > 0) {
210            SortedSet<StorageMetadata> contentsSlice = firstSliceOfSize(contents, maxResults);
211            if (!contentsSlice.contains(contents.last())) {
212               // Partial listing
213               marker = contentsSlice.last().getName();
214            } else {
215               marker = null;
216            }
217            contents = contentsSlice;
218         }
219 
220         final String delimiter = options.isRecursive() ? null : File.separator;
221         if (delimiter != null) {
222            SortedSet<String> commonPrefixes = null;
223            Iterable<String> iterable = transform(contents, new CommonPrefixes(prefix != null ? prefix : null,
224                     delimiter));
225            commonPrefixes = iterable != null ? newTreeSet(iterable) : new TreeSet<String>();
226            commonPrefixes.remove(CommonPrefixes.NO_PREFIX);
227 
228            contents = newTreeSet(filter(contents, new DelimiterFilter(prefix != null ? prefix : null, delimiter)));
229 
230            Iterables.<StorageMetadata> addAll(contents, transform(commonPrefixes,
231                     new Function<String, StorageMetadata>() {
232                        public StorageMetadata apply(String o) {
233                           MutableStorageMetadata md = new MutableStorageMetadataImpl();
234                           md.setType(StorageType.RELATIVE_PATH);
235                           md.setName(o);
236                           return md;
237                        }
238                     }));
239         }
240 
241         // trim metadata, if the response isn't supposed to be detailed.
242         if (!options.isDetailed()) {
243            for (StorageMetadata md : contents) {
244               md.getUserMetadata().clear();
245            }
246         }
247      }
248 
249      return Futures.<PageSet<? extends StorageMetadata>> immediateFuture(new PageSetImpl<StorageMetadata>(contents,
250               marker));
251 
252   }
253 
254   private ContainerNotFoundException cnfe(final String name) {
255      return new ContainerNotFoundException(name, String.format("container %s not in filesystem", name));
256   }
257 
258   public static MutableBlobMetadata copy(MutableBlobMetadata in) {
259      ByteArrayOutputStream bout = new ByteArrayOutputStream();
260      ObjectOutput os;
261      try {
262         os = new ObjectOutputStream(bout);
263         os.writeObject(in);
264         ObjectInput is = new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray()));
265         MutableBlobMetadata metadata = (MutableBlobMetadata) is.readObject();
266         convertUserMetadataKeysToLowercase(metadata);
267         metadata.setContentMetadata(BaseMutableContentMetadata.fromContentMetadata(in.getContentMetadata().toBuilder()
268                  .build()));
269         return metadata;
270      } catch (Exception e) {
271         propagate(e);
272         assert false : "exception should have propagated: " + e;
273         return null;
274      }
275   }
276 
277   private static void convertUserMetadataKeysToLowercase(MutableBlobMetadata metadata) {
278      Map<String, String> lowerCaseUserMetadata = newHashMap();
279      for (Entry<String, String> entry : metadata.getUserMetadata().entrySet()) {
280         lowerCaseUserMetadata.put(entry.getKey().toLowerCase(), entry.getValue());
281      }
282      metadata.setUserMetadata(lowerCaseUserMetadata);
283   }
284 
285   public static MutableBlobMetadata copy(MutableBlobMetadata in, String newKey) {
286      MutableBlobMetadata newMd = copy(in);
287      newMd.setName(newKey);
288      return newMd;
289   }
290 
291   /**
292    * {@inheritDoc}
293    */
294   @Override
295   public ListenableFuture<Void> removeBlob(final String container, final String key) {
296      storageStrategy.removeBlob(container, key);
297      return immediateFuture(null);
298   }
299 
300   /**
301    * {@inheritDoc}
302    */
303   @Override
304   public ListenableFuture<Boolean> containerExists(final String containerName) {
305      boolean exists = containerExistsSyncImpl(containerName);
306      return immediateFuture(exists);
307   }
308 
309   /**
310    * {@inheritDoc}
311    */
312   @Override
313   public ListenableFuture<PageSet<? extends StorageMetadata>> list() {
314      Iterable<String> containers = storageStrategy.getAllContainerNames();
315 
316      return Futures.<PageSet<? extends StorageMetadata>> immediateFuture(new PageSetImpl<StorageMetadata>(transform(
317               containers, new Function<String, StorageMetadata>() {
318                  public StorageMetadata apply(String name) {
319                     MutableStorageMetadata cmd = create();
320                     cmd.setName(name);
321                     cmd.setType(StorageType.CONTAINER);
322                     return cmd;
323                  }
324               }), null));
325   }
326 
327   protected MutableStorageMetadata create() {
328      return new MutableStorageMetadataImpl();
329   }
330 
331   /**
332    * {@inheritDoc}
333    */
334   @Path("{container}")
335   @Override
336   public ListenableFuture<Boolean> createContainerInLocation(final Location location,
337            @PathParam("container") @ParamValidators( { FilesystemContainerNameValidator.class }) String name) {
338      boolean result = storageStrategy.createContainer(name);
339      return immediateFuture(result);
340   }
341 
342   public String getFirstQueryOrNull(String string, @Nullable HttpRequestOptions options) {
343      if (options == null)
344         return null;
345      Collection<String> values = options.buildQueryParameters().get(string);
346      return (values != null && values.size() >= 1) ? values.iterator().next() : null;
347   }
348 
349   /**
350    * Load the blob with the given key belonging to the container with the given name. There must
351    * exist a resource on the file system whose complete name is given concatenating the container
352    * name and the key
353    * 
354    * @param container
355    *           it's the name of the container the blob belongs to
356    * @param key
357    *           it's the key of the blob
358    * 
359    * @return the blob belonging to the given container with the given key
360    */
361   private Blob loadFileBlob(final String container, final String key) {
362      logger.debug("Opening blob in container: %s - %s", container, key);
363      File blobPayload = storageStrategy.getFileForBlobKey(container, key);
364 
365      Payload payload = new FilePayload(blobPayload);
366      // Loading object metadata
367      MutableBlobMetadata metadata = new MutableBlobMetadataImpl();
368      metadata.setName(key);
369      metadata.setLastModified(new Date(blobPayload.lastModified()));
370      metadata.getContentMetadata().setContentLength(blobPayload.length());
371      // TODO What about the MD5? are we supposed to calculate it each time we load
372      // the file?
373      try {
374         payload = Payloads.calculateMD5(payload);
375      } catch (IOException e) {
376         logger.error("An error occurred calculating MD5 for blob %s from container ", key, container);
377         Throwables.propagateIfPossible(e);
378      }
379      metadata.getContentMetadata().setContentType("");
380      String eTag = CryptoStreams.hex(payload.getContentMetadata().getContentMD5());
381      metadata.setETag(eTag);
382      // Creating new blob object
383      Blob blob = blobFactory.create(metadata);
384      blob.setPayload(blobPayload);
385      return blob;
386   }
387 
388   protected static class DelimiterFilter implements Predicate<StorageMetadata> {
389      private final String prefix;
390      private final String delimiter;
391 
392      public DelimiterFilter(String prefix, String delimiter) {
393         this.prefix = prefix;
394         this.delimiter = delimiter;
395      }
396 
397      public boolean apply(StorageMetadata metadata) {
398         if (prefix == null)
399            return metadata.getName().indexOf(delimiter) == -1;
400         // ensure we don't accidentally append twice
401         String toMatch = prefix.endsWith("/") ? prefix : prefix + delimiter;
402         if (metadata.getName().startsWith(toMatch)) {
403            String unprefixedName = metadata.getName().replaceFirst(toMatch, "");
404            if (unprefixedName.equals("")) {
405               // we are the prefix in this case, return false
406               return false;
407            }
408            return unprefixedName.indexOf(delimiter) == -1;
409         }
410         return false;
411      }
412   }
413 
414   protected static class CommonPrefixes implements Function<StorageMetadata, String> {
415      private final String prefix;
416      private final String delimiter;
417      public static final String NO_PREFIX = "NO_PREFIX";
418 
419      public CommonPrefixes(String prefix, String delimiter) {
420         this.prefix = prefix;
421         this.delimiter = delimiter;
422      }
423 
424      public String apply(StorageMetadata metadata) {
425         String working = metadata.getName();
426         if (prefix != null) {
427            // ensure we don't accidentally append twice
428            String toMatch = prefix.endsWith("/") ? prefix : prefix + delimiter;
429            if (working.startsWith(toMatch)) {
430               working = working.replaceFirst(toMatch, "");
431            }
432         }
433         if (working.contains(delimiter)) {
434            return working.substring(0, working.indexOf(delimiter));
435         }
436         return NO_PREFIX;
437      }
438   }
439 
440   public static <T extends Comparable<?>> SortedSet<T> firstSliceOfSize(Iterable<T> elements, int size) {
441      List<List<T>> slices = partition(newArrayList(elements), size);
442      return newTreeSet(slices.get(0));
443   }
444 
445   public static HttpResponseException returnResponseException(int code) {
446      HttpResponse response = null;
447      response = new HttpResponse(code, null, null);
448      return new HttpResponseException(new HttpCommand() {
449 
450         public int getRedirectCount() {
451            return 0;
452         }
453 
454         public int incrementRedirectCount() {
455            return 0;
456         }
457 
458         public boolean isReplayable() {
459            return false;
460         }
461 
462         public Exception getException() {
463            return null;
464         }
465 
466         public int getFailureCount() {
467            return 0;
468         }
469 
470         public int incrementFailureCount() {
471            return 0;
472         }
473 
474         public void setException(Exception exception) {
475 
476         }
477 
478         @Override
479         public HttpRequest getCurrentRequest() {
480            return new HttpRequest("GET", URI.create("http://stub"));
481         }
482 
483         @Override
484         public void setCurrentRequest(HttpRequest request) {
485 
486         }
487 
488      }, response);
489   }
490 
491   /**
492    * {@inheritDoc}
493    */
494   @Override
495   public ListenableFuture<String> putBlob(String containerName, Blob object) {
496      String blobKey = object.getMetadata().getName();
497 
498      logger.debug("Put object with key [%s] to container [%s]", blobKey, containerName);
499      String eTag = getEtag(object);
500      try {
501         // TODO
502         // must override existing file?
503 
504         storageStrategy.writePayloadOnFile(containerName, blobKey, object.getPayload());
505      } catch (IOException e) {
506         logger.error(e, "An error occurred storing the new object with name [%s] to container [%s].", blobKey,
507                  containerName);
508         Throwables.propagate(e);
509      }
510      return immediateFuture(eTag);
511   }
512 
513   /**
514    * {@inheritDoc}
515    */
516   @Override
517   public ListenableFuture<Boolean> blobExists(final String containerName, final String key) {
518      return immediateFuture(storageStrategy.blobExists(containerName, key));
519   }
520 
521   /**
522    * {@inheritDoc}
523    */
524   @Override
525   public ListenableFuture<Blob> getBlob(final String containerName, final String key, GetOptions options) {
526      logger.debug("Retrieving blob with key %s from container %s", key, containerName);
527      // If the container doesn't exist, an exception is thrown
528      if (!containerExistsSyncImpl(containerName)) {
529         logger.debug("Container %s does not exist", containerName);
530         return immediateFailedFuture(cnfe(containerName));
531      }
532      // If the blob doesn't exist, a null object is returned
533      if (!storageStrategy.blobExists(containerName, key)) {
534         logger.debug("Item %s does not exist in container %s", key, containerName);
535         return immediateFuture(null);
536      }
537 
538      Blob blob = loadFileBlob(containerName, key);
539 
540      if (options != null) {
541         if (options.getIfMatch() != null) {
542            if (!blob.getMetadata().getETag().equals(options.getIfMatch()))
543               return immediateFailedFuture(returnResponseException(412));
544         }
545         if (options.getIfNoneMatch() != null) {
546            if (blob.getMetadata().getETag().equals(options.getIfNoneMatch()))
547               return immediateFailedFuture(returnResponseException(304));
548         }
549         if (options.getIfModifiedSince() != null) {
550            Date modifiedSince = options.getIfModifiedSince();
551            if (blob.getMetadata().getLastModified().before(modifiedSince)) {
552               HttpResponse response = new HttpResponse(304, null, null);
553               return immediateFailedFuture(new HttpResponseException(String.format("%1$s is before %2$s", blob
554                        .getMetadata().getLastModified(), modifiedSince), null, response));
555            }
556 
557         }
558         if (options.getIfUnmodifiedSince() != null) {
559            Date unmodifiedSince = options.getIfUnmodifiedSince();
560            if (blob.getMetadata().getLastModified().after(unmodifiedSince)) {
561               HttpResponse response = new HttpResponse(412, null, null);
562               return immediateFailedFuture(new HttpResponseException(String.format("%1$s is after %2$s", blob
563                        .getMetadata().getLastModified(), unmodifiedSince), null, response));
564            }
565         }
566 
567         if (options.getRanges() != null && options.getRanges().size() > 0) {
568            byte[] data;
569            try {
570               data = toByteArray(blob.getPayload().getInput());
571            } catch (IOException e) {
572               return immediateFailedFuture(new RuntimeException(e));
573            }
574            ByteArrayOutputStream out = new ByteArrayOutputStream();
575            for (String s : options.getRanges()) {
576               if (s.startsWith("-")) {
577                  int length = Integer.parseInt(s.substring(1));
578                  out.write(data, data.length - length, length);
579               } else if (s.endsWith("-")) {
580                  int offset = Integer.parseInt(s.substring(0, s.length() - 1));
581                  out.write(data, offset, data.length - offset);
582               } else if (s.contains("-")) {
583                  String[] firstLast = s.split("\\-");
584                  int offset = Integer.parseInt(firstLast[0]);
585                  int last = Integer.parseInt(firstLast[1]);
586                  int length = (last < data.length) ? last + 1 : data.length - offset;
587                  out.write(data, offset, length);
588               } else {
589                  return immediateFailedFuture(new IllegalArgumentException("first and last were null!"));
590               }
591 
592            }
593            blob.setPayload(out.toByteArray());
594            blob.getMetadata().getContentMetadata().setContentLength(new Long(data.length));
595         }
596      }
597      checkNotNull(blob.getPayload(), "payload " + blob);
598      return immediateFuture(blob);
599   }
600 
601   /**
602    * {@inheritDoc}
603    */
604   @Override
605   public ListenableFuture<BlobMetadata> blobMetadata(final String container, final String key) {
606      try {
607         Blob blob = getBlob(container, key).get();
608         return immediateFuture(blob != null ? (BlobMetadata) copy(blob.getMetadata()) : null);
609      } catch (Exception e) {
610         if (size(filter(getCausalChain(e), KeyNotFoundException.class)) >= 1)
611            return immediateFuture(null);
612         return immediateFailedFuture(e);
613      }
614   }
615 
616   @Override
617   protected boolean deleteAndVerifyContainerGone(String container) {
618      storageStrategy.deleteContainer(container);
619      return containerExistsSyncImpl(container);
620   }
621 
622   /**
623    * Override parent method because it uses strange futures and listenables that creates problem in
624    * the test if more than one test that deletes the container is executed
625    * 
626    * @param container
627    * @return
628    */
629   @Override
630   public ListenableFuture<Void> deleteContainer(String container) {
631      deleteAndVerifyContainerGone(container);
632      return immediateFuture(null);
633   }
634 
635   /**
636    * Each container is a directory, so in order to check if a container exists the corresponding
637    * directory must exists. Synchronous implementation
638    * 
639    * @param containerName
640    * @return
641    */
642   private boolean containerExistsSyncImpl(String containerName) {
643      return storageStrategy.containerExists(containerName);
644   }
645 
646   /**
647    * 
648    * Calculates the object MD5 and returns it as eTag
649    * 
650    * @param object
651    * @return
652    */
653   private String getEtag(Blob object) {
654      try {
655         Payloads.calculateMD5(object, crypto.md5());
656      } catch (IOException ex) {
657         logger.error(ex, "An error occurred calculating MD5 for object with name %s.", object.getMetadata().getName());
658         Throwables.propagate(ex);
659      }
660 
661      String eTag = CryptoStreams.hex(object.getPayload().getContentMetadata().getContentMD5());
662      return eTag;
663   }
664 
665   @Override
666   public ListenableFuture<String> putBlob(String container, Blob blob, PutOptions options) {
667      // TODO implement options
668      return putBlob(container, blob);
669   }
670 
671   @Override
672   public ListenableFuture<Boolean> createContainerInLocation(Location location, String container,
673            CreateContainerOptions options) {
674      if (options.isPublicRead())
675         throw new UnsupportedOperationException("publicRead");
676      return createContainerInLocation(location, container);
677   }
678}

[all classes][org.jclouds.filesystem]
EMMA 2.0.5312 (C) Vladimir Roubtsov