Package org.infinispan.loaders.cloud

Source Code of org.infinispan.loaders.cloud.CloudCacheStore

/*
* JBoss, Home of Professional Open Source
* Copyright 2009 Red Hat Inc. and/or its affiliates and other
* contributors as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a full listing of
* individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.infinispan.loaders.cloud;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
import org.infinispan.Cache;
import org.infinispan.config.ConfigurationException;
import org.infinispan.container.entries.InternalCacheEntry;
import org.infinispan.loaders.CacheLoaderConfig;
import org.infinispan.loaders.CacheLoaderException;
import org.infinispan.loaders.CacheLoaderMetadata;
import org.infinispan.loaders.CacheStoreConfig;
import org.infinispan.loaders.bucket.Bucket;
import org.infinispan.loaders.bucket.BucketBasedCacheStore;
import org.infinispan.loaders.cloud.logging.Log;
import org.infinispan.loaders.modifications.Modification;
import org.infinispan.marshall.StreamingMarshaller;
import org.infinispan.util.logging.LogFactory;
import org.infinispan.util.stream.Streams;
import org.jclouds.blobstore.AsyncBlobStore;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.BlobStoreContextFactory;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.BlobBuilder;
import org.jclouds.blobstore.domain.PageSet;
import org.jclouds.blobstore.domain.StorageMetadata;
import org.jclouds.domain.Location;
import org.jclouds.enterprise.config.EnterpriseConfigurationModule;
import org.jclouds.logging.log4j.config.Log4JLoggingModule;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;

/**
* The CloudCacheStore implementation that utilizes <a href="http://code.google.com/p/jclouds">JClouds</a> to
* communicate with cloud storage providers such as <a href="http://aws.amazon.com/s3/">Amazon's S3<a>, <a
* href="http://www.rackspacecloud.com/cloud_hosting_products/files">Rackspace's Cloudfiles</a>, or any other such
* provider supported by JClouds.
* <p/>
* This file store stores stuff in the following format: <tt>http://{cloud-storage-provider}/{bucket}/{bucket_number}</tt>
* <p/>
*
* @author Manik Surtani
* @author Adrian Cole
* @since 4.0
*/
@CacheLoaderMetadata(configurationClass = CloudCacheStoreConfig.class)
public class CloudCacheStore extends BucketBasedCacheStore {
   static final Log log = LogFactory.getLog(CloudCacheStore.class, Log.class);
   final ThreadLocal<List<Future<?>>> asyncCommandFutures = new ThreadLocal<List<Future<?>>>();
   CloudCacheStoreConfig cfg;
   String containerName;
   BlobStoreContext ctx;
   BlobStore blobStore;
   AsyncBlobStore asyncBlobStore;
   boolean pollFutures = false;
   boolean constructInternalBlobstores = true;
   protected static final String EARLIEST_EXPIRY_TIME = "metadata_eet";
   private MessageDigest md5;

   public CloudCacheStore() {
      try {
         md5 = MessageDigest.getInstance("MD5");
      } catch (NoSuchAlgorithmException ignore) {
         md5 = null;
      }
   }

   @Override
   public Class<? extends CacheStoreConfig> getConfigurationClass() {
      return CloudCacheStoreConfig.class;
   }

   private String getThisContainerName() {
      return cfg.getBucketPrefix() + "-"
            + cache.getName().toLowerCase().replace("_", "").replace(".", "");
   }

   @Override
   protected boolean supportsMultiThreadedPurge() {
      return true;
   }

   @Override
   public void init(CacheLoaderConfig cfg, Cache<?, ?> cache, StreamingMarshaller m)
         throws CacheLoaderException {
      this.cfg = (CloudCacheStoreConfig) cfg;
      init(cfg, cache, m, null, null, null, true);
   }

   public void init(CacheLoaderConfig cfg, Cache<?, ?> cache, StreamingMarshaller m, BlobStoreContext ctx,
                    BlobStore blobStore, AsyncBlobStore asyncBlobStore, boolean constructInternalBlobstores)
         throws CacheLoaderException {
      super.init(cfg, cache, m);
      this.cfg = (CloudCacheStoreConfig) cfg;
      marshaller = m;
      this.ctx = ctx;
      this.blobStore = blobStore;
      this.asyncBlobStore = asyncBlobStore;
      this.constructInternalBlobstores = constructInternalBlobstores;
   }

   @Override
   public void start() throws CacheLoaderException {
      super.start();
      if (constructInternalBlobstores) {
         if (cfg.getCloudService() == null) {
            throw new ConfigurationException("CloudService must be set!");
        }
         if (cfg.getIdentity() == null) {
            throw new ConfigurationException("Identity must be set");
        }
         if (cfg.getPassword() == null) {
            throw new ConfigurationException("Password must be set");
        }
      }
      if (cfg.getBucketPrefix() == null) {
        throw new ConfigurationException("CloudBucket must be set");
    }
      containerName = getThisContainerName();
      try {
         if (constructInternalBlobstores) {
            // add an executor as a constructor param to
            // EnterpriseConfigurationModule, pass
            // property overrides instead of Properties()
            ctx = new BlobStoreContextFactory().createContext(cfg.getCloudService(), cfg
                  .getIdentity(), cfg.getPassword(), ImmutableSet.of(
                  new EnterpriseConfigurationModule(), new Log4JLoggingModule()),
                                                              new Properties());
            blobStore = ctx.getBlobStore();
            asyncBlobStore = ctx.getAsyncBlobStore();
         }

         if (!blobStore.containerExists(containerName)) {
            Location chosenLoc = null;
            if (cfg.getCloudServiceLocation() != null && cfg.getCloudServiceLocation().trim().length() > 0) {
               Map<String, ? extends Location> idToLocation = Maps.uniqueIndex(blobStore.listAssignableLocations(), new Function<Location, String>() {
                  @Override
                  public String apply(Location input) {
                     return input.getId();
                  }
               });
               String loc = cfg.getCloudServiceLocation().trim().toLowerCase();
               chosenLoc = idToLocation.get(loc);
               if (chosenLoc == null) {
                  log.unableToConfigureCloudService(loc, cfg.getCloudService(), idToLocation.keySet());
               }
            }
            blobStore.createContainerInLocation(chosenLoc, containerName);
         }
         pollFutures = !cfg.getAsyncStoreConfig().isEnabled();
      } catch (RuntimeException ioe) {
         throw new CacheLoaderException("Unable to create context", ioe);
      }
   }


   @Override
   protected void loopOverBuckets(BucketHandler handler) throws CacheLoaderException {
      for (Map.Entry<String, Blob> entry : ctx.createBlobMap(containerName).entrySet()) {
         Bucket bucket = readFromBlob(entry.getValue(), entry.getKey());
         if (bucket != null) {
            if (bucket.removeExpiredEntries()) {
               upgradeLock(bucket.getBucketId());
               try {
                  updateBucket(bucket);
               } finally {
                  downgradeLock(bucket.getBucketId());
               }
            }
            if (handler.handle(bucket)) {
               break;
            }
         } else {
            throw new CacheLoaderException("Blob not found: " + entry.getKey());
         }
      }
   }

   @Override
   protected void fromStreamLockSafe(ObjectInput objectInput) throws CacheLoaderException {
      String source;
      try {
         source = (String) objectInput.readObject();
      } catch (Exception e) {
         throw convertToCacheLoaderException("Error while reading from stream", e);
      }
      if (containerName.equals(source)) {
         log.attemptToLoadSameBucketIgnored(source);
      } else {
         // TODO implement stream handling. What's the JClouds API to "copy" one bucket to another?
      }
   }

   @Override
   protected void toStreamLockSafe(ObjectOutput objectOutput) throws CacheLoaderException {
      try {
         objectOutput.writeObject(containerName);
      } catch (Exception e) {
         throw convertToCacheLoaderException("Error while writing to stream", e);
      }
   }

   @Override
   protected void clearLockSafe() {
      List<Future<?>> futures = asyncCommandFutures.get();
      if (futures == null) {
         // is a sync call
         blobStore.clearContainer(containerName);
      } else {
         // is an async call - invoke clear() on the container asynchronously
         // and store the future
         // in the 'futures' collection
         futures.add(asyncBlobStore.clearContainer(containerName));
      }
   }

   private CacheLoaderException convertToCacheLoaderException(String m, Throwable c) {
      if (c instanceof CacheLoaderException) {
         return (CacheLoaderException) c;
      } else {
         return new CacheLoaderException(m, c);
      }
   }

   @Override
   protected Bucket loadBucket(Integer hash) throws CacheLoaderException {
      if (hash == null) {
         throw new NullPointerException("hash");
      }
      String bucketName = hash.toString();
      return readFromBlob(blobStore.getBlob(
                  containerName, encodeBucketName(bucketName)), bucketName);
   }

   void purge() {
      long currentTime = System.currentTimeMillis();
      PageSet<? extends StorageMetadata> ps = blobStore.list(containerName);

      // TODO do we need to scroll through the PageSet?
      for (StorageMetadata sm : ps) {
         long lastExpirableEntry = readLastExpirableEntryFromMetadata(sm.getUserMetadata());
         if (lastExpirableEntry < currentTime) {
            scanBlobForExpiredEntries(sm.getName());
        }
      }
   }

   private void scanBlobForExpiredEntries(String blobName) {
      Blob blob = blobStore.getBlob(containerName, blobName);
      try {
         Bucket bucket = readFromBlob(blob, blobName);
         if (bucket != null) {
            if (bucket.removeExpiredEntries()) {
               upgradeLock(bucket.getBucketId());
               try {
                  updateBucket(bucket);
               } finally {
                  downgradeLock(bucket.getBucketId());
               }
           }
         } else {
            throw new CacheLoaderException("Blob not found: " + blobName);
         }
      } catch (CacheLoaderException e) {
         log.unableToReadBlob(blobName, e);
      }
   }

   private long readLastExpirableEntryFromMetadata(Map<String, String> metadata) {
      String eet = metadata.get(EARLIEST_EXPIRY_TIME);
      long eetLong = -1;
      if (eet != null) {
        eetLong = Long.parseLong(eet);
    }
      return eetLong;
   }

   @Override
   protected void purgeInternal() throws CacheLoaderException {
      if (!cfg.isLazyPurgingOnly()) {
         boolean success = acquireGlobalLock(false);
         try {
            if (multiThreadedPurge) {
               purgerService.execute(new Runnable() {
                  @Override
                  public void run() {
                     try {
                        purge();
                     } catch (Exception e) {
                        log.problemsPurging(e);
                     }
                  }
               });
            } else {
               purge();
            }
         } finally {
            if(success){
               releaseGlobalLock(false);
            }
         }
      }
   }

   @Override
   protected void updateBucket(Bucket bucket) throws CacheLoaderException {
      BlobBuilder builder = blobStore.blobBuilder(encodeBucketName(bucket.getBucketIdAsString()));
      Blob blob = builder.build();
      writeToBlob(blob, bucket);

      List<Future<?>> futures = asyncCommandFutures.get();
      if (futures == null) {
         // is a sync call
         blobStore.putBlob(containerName, blob);
      } else {
         // is an async call - invoke clear() on the container asynchronously
         // and store the future
         // in the 'futures' collection
         futures.add(asyncBlobStore.putBlob(containerName, blob));
      }
   }

   @Override
   public void applyModifications(List<? extends Modification> modifications)
         throws CacheLoaderException {
      List<Future<?>> futures = new LinkedList<Future<?>>();
      asyncCommandFutures.set(futures);

      try {
         super.applyModifications(modifications);
         if (pollFutures) {
            CacheLoaderException exception = null;
            try {
               futures = asyncCommandFutures.get();
               if (log.isTraceEnabled()) {
                  log.tracef("Futures, in order: %s", futures);
               }
               for (Future<?> f : futures) {
                  Object o = f.get();
                  if (log.isTraceEnabled()) {
                     log.tracef("Future %s returned %s", f, o);
                  }
               }
            } catch (InterruptedException ie) {
               Thread.currentThread().interrupt();
            } catch (ExecutionException ee) {
               exception = convertToCacheLoaderException("Caught exception in async process", ee
                     .getCause());
            }
            if (exception != null) {
               throw exception;
            }
         }
      } finally {
         asyncCommandFutures.remove();
      }
   }

   private void writeToBlob(Blob blob, Bucket bucket) throws CacheLoaderException {
      long earliestExpiryTime = -1;
      for (InternalCacheEntry e : bucket.getEntries().values()) {
         long t = e.getExpiryTime();
         if (t != -1) {
            if (earliestExpiryTime == -1) {
               earliestExpiryTime = t;
            } else {
               earliestExpiryTime = Math.min(earliestExpiryTime, t);
            }
         }
      }

      try {
         final byte[] payloadBuffer = marshaller.objectToByteBuffer(bucket);
         if (cfg.isCompress()) {
            final byte[] compress = compress(payloadBuffer, blob);
            blob.setPayload(compress);
         } else {
            blob.setPayload(payloadBuffer);
        }
         if (earliestExpiryTime > -1) {
            Map<String, String> md = Collections.singletonMap(EARLIEST_EXPIRY_TIME, String
                  .valueOf(earliestExpiryTime));
            blob.getMetadata().setUserMetadata(md);
         }
      } catch (IOException e) {
         throw new CacheLoaderException(e);
      } catch (InterruptedException ie) {
         if (log.isTraceEnabled()) {
            log.trace("Interrupted while writing blob");
        }
         Thread.currentThread().interrupt();
      }
   }

   private Bucket readFromBlob(Blob blob, String bucketName) throws CacheLoaderException {
      if (blob == null) {
        return null;
      }
      try {
         Bucket bucket;
         final InputStream content = blob.getPayload().getInput();
         if (cfg.isCompress()) {
            bucket = uncompress(blob, bucketName, content);
         } else {
            bucket = (Bucket) marshaller.objectFromInputStream(content);
         }

         if (bucket != null) {
            bucket.setBucketId(bucketName);
         }
         return bucket;
      } catch (ClassNotFoundException e) {
         throw convertToCacheLoaderException("Unable to read blob", e);
      } catch (IOException e) {
         throw convertToCacheLoaderException("Class loading issue", e);
      }
   }

   private Bucket uncompress(Blob blob, String bucketName, InputStream content) throws IOException, CacheLoaderException, ClassNotFoundException {
      //TODO go back to fully streamed version and get rid of the byte buffers
      BZip2CompressorInputStream is;
      Bucket bucket;
      ByteArrayOutputStream bos = new ByteArrayOutputStream();

      Streams.copy(content, bos);
      final byte[] compressedByteArray = bos.toByteArray();

      ByteArrayInputStream bis = new ByteArrayInputStream(compressedByteArray);

      is = new BZip2CompressorInputStream(bis);
      ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
      Streams.copy(is, bos2);
      final byte[] uncompressedByteArray = bos2.toByteArray();

      byte[] md5FromStoredBlob = blob.getMetadata().getContentMetadata().getContentMD5();

      // not all blobstores support md5 on GET request
      if (md5FromStoredBlob != null){
         byte[] hash = getMd5Digest(compressedByteArray);
         if (!Arrays.equals(hash, md5FromStoredBlob)) {
            throw new CacheLoaderException("MD5 hash failed when reading (transfer error) for entry " + bucketName);
         }
      }

      is.close();
      bis.close();
      bos.close();
      bos2.close();

      bucket = (Bucket) marshaller
            .objectFromInputStream(new ByteArrayInputStream(uncompressedByteArray));
      return bucket;
   }

   private byte[] compress(final byte[] uncompressedByteArray, Blob blob) throws IOException {
      //TODO go back to fully streamed version and get rid of the byte buffers
      final ByteArrayOutputStream baos = new ByteArrayOutputStream();

      InputStream input = new ByteArrayInputStream(uncompressedByteArray);
      BZip2CompressorOutputStream output = new BZip2CompressorOutputStream(baos);

      Streams.copy(input, output);

      output.close();
      input.close();

      final byte[] compressedByteArray = baos.toByteArray();

      blob.getMetadata().getContentMetadata().setContentMD5(getMd5Digest(compressedByteArray));

      baos.close();

      return compressedByteArray;
   }

   private String encodeBucketName(String bucketId) {
      final String name = bucketId.startsWith("-") ? bucketId.replace('-', 'A')
                                                   : bucketId;
      if (cfg.isCompress()) {
         return name + ".bz2";
      }
      return name;
   }

   private synchronized byte[] getMd5Digest(byte[] toDigest) {
      md5.reset();
      return md5.digest(toDigest);
   }
}
TOP

Related Classes of org.infinispan.loaders.cloud.CloudCacheStore

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.