[jbosscache-commits] JBoss Cache SVN: r5454 - in core/trunk: src/main/docbook/userguide/en/modules and 3 other directories.

jbosscache-commits at lists.jboss.org jbosscache-commits at lists.jboss.org
Thu Mar 20 04:22:11 EDT 2008


Author: genman
Date: 2008-03-20 04:22:10 -0400 (Thu, 20 Mar 2008)
New Revision: 5454

Added:
   core/trunk/src/main/java/org/jboss/cache/loader/s3/
   core/trunk/src/main/java/org/jboss/cache/loader/s3/S3CacheLoader.java
   core/trunk/src/main/java/org/jboss/cache/loader/s3/S3Exception.java
   core/trunk/src/main/java/org/jboss/cache/loader/s3/S3LoaderConfig.java
   core/trunk/src/test/java/org/jboss/cache/loader/S3CacheLoaderTest.java
Modified:
   core/trunk/
   core/trunk/pom.xml
   core/trunk/src/main/docbook/userguide/en/modules/cache_loaders.xml
Log:
JBCACHE-1286 CacheLoader suppport for Amazon-S3
Test by default use the emulator; Set appropriate system properties to use the real service


Property changes on: core/trunk
___________________________________________________________________
Name: svn:ignore
   - output
build.log
junit*
0000*
je.lck
*.iml
*.ipr
*.iws
bin
jbossdb
dist
derby.log
target
build
.classpath
.project
.settings
eclipse-output

   + output
build.log
junit*
0000*
je.lck
*.iml
*.ipr
*.iws
bin
jbossdb
dist
derby.log
target
build
.classpath
.project
.settings
eclipse-output
test-output


Modified: core/trunk/pom.xml
===================================================================
--- core/trunk/pom.xml	2008-03-20 00:25:56 UTC (rev 5453)
+++ core/trunk/pom.xml	2008-03-20 08:22:10 UTC (rev 5454)
@@ -79,6 +79,24 @@
          <version>1.0</version>
          <optional>true</optional>
       </dependency>
+      <dependency>
+         <groupId>net.noderunner</groupId>
+         <artifactId>amazon-s3</artifactId>
+         <version>1.0.0.0</version>
+         <optional>true</optional>
+      </dependency>
+      <dependency>
+         <groupId>net.noderunner</groupId>
+         <artifactId>http</artifactId>
+         <version>1.0</version>
+         <scope>test</scope>
+       </dependency>
+       <dependency>
+         <groupId>javax.servlet</groupId>
+         <artifactId>servlet-api</artifactId>
+         <version>2.3</version>
+         <scope>test</scope>
+       </dependency>
 
       <!-- test dependencies -->
       <dependency>
@@ -327,6 +345,11 @@
          <id>snapshots.jboss.org</id>
          <url>http://snapshots.jboss.org/maven2</url>
       </repository>
+      <!-- For Amazon S3 artifacts -->
+      <repository>
+         <id>e-xml.sourceforge.net</id>
+         <url>http://e-xml.sourceforge.net/maven2/repository</url>
+      </repository>
    </repositories>
 
    <profiles>
@@ -368,5 +391,5 @@
          </dependencies>
       </profile>
    </profiles>
-
+   
 </project>

Modified: core/trunk/src/main/docbook/userguide/en/modules/cache_loaders.xml
===================================================================
--- core/trunk/src/main/docbook/userguide/en/modules/cache_loaders.xml	2008-03-20 00:25:56 UTC (rev 5453)
+++ core/trunk/src/main/docbook/userguide/en/modules/cache_loaders.xml	2008-03-20 08:22:10 UTC (rev 5454)
@@ -899,6 +899,163 @@
          </section>
       </section>
 
+      <section id="cl.s3">
+         <title>S3CacheLoader</title>
+
+         <para>The <literal>S3CacheLoader</literal> uses the 
+                  <ulink url="http://aws.amazon.com/">Amazon S3</ulink> (Simple Storage Solution)
+			for storing cache data.  
+            Since Amazon S3 is remote network storage and has fairly high latency,
+			it is really best for caches that store large pieces of data, such as media
+			or files.
+            But consider this cache loader over the JDBC or
+			file system based cache loaders if you want remotely managed, highly reliable
+			storage. Or, use it for applications running on Amazon's Elastic Compute Cloud.
+         </para>
+
+		 <para>
+			If you're planning to use Amazon S3 for storage, consider using it with JBoss Cache.
+			JBoss Cache itself provides in-memory caching for your data to minimize the amount of 
+			remote access calls, thus reducing the latency and cost of fetching your Amazon S3 data.
+			With cache replication, you are also able to load data from your local cluster 
+			without having to remotely access it every time.
+         </para>
+
+         <para>
+			Note that Amazon S3 does not support transactions.  If transactions
+			are used in your application then there is some possibility of state
+			inconsistency when using this cache loader.  However, writes are atomic, in
+			that if a write fails nothing is considered written and data is never
+			corrupted.
+         </para>
+
+         <para>
+			Data is stored in keys based on the FQN of the Node and Node data is 
+			serialized as a java.util.Map using the <literal>CacheSPI.getMarshaller()</literal>
+			instance.
+			Read the JavaDoc on how data is structured and stored.
+            Be aware this means data is not readily accessible over HTTP to
+            non-JBossCache clients. Your feedback and help would be appreciated
+            to extend this cache loader for that purpose.
+         </para>
+
+         <para>
+			With this cache loader, single-key operations such as
+			<literal>Node.remove(Object)</literal> and <literal>Node.put(Object,
+			Object)</literal> are the slowest as data is stored in a single Map instance.
+			Use bulk operations such as <literal>Node.replaceAll(Map)</literal>
+			and <literal>Node.clearData()</literal> for more efficiency.
+			Try the <literal>cache.s3.optimize</literal> option as well.
+         </para>
+         
+         <section>
+            <title>Amazon S3 Library</title>
+			  <para>The S3 cache loader is provided with the default
+              distribution but requires a library to access the service
+              at runtime. This runtime library may be obtained through the JBoss Maven
+              Repository. Include the following sections in your pom.xml file:
+              </para>
+              <programlisting><![CDATA[
+      <repository>
+         <id>e-xml.sourceforge.net</id>
+         <url>http://e-xml.sourceforge.net/maven2/repository</url>
+      </repository>
+        ...
+      <dependency>
+         <groupId>net.noderunner</groupId>
+         <artifactId>amazon-s3</artifactId>
+         <version>1.0.0.0</version>
+      </dependency>
+              ]]>
+              </programlisting>
+              If you do not use Maven, you can still download the
+              amazon-s3 library through the repository URL.
+         </section>
+
+         <section>
+            <title>Configuration</title>
+			  <para>At a minimum, you must configure your Amazon S3 access key and 
+				secret access key. The other configuration keys are listed in general
+				order of utility.
+              </para>
+
+               <para>
+                  <itemizedlist>
+                     <listitem>
+                        <literal>cache.s3.accessKeyId</literal> -
+						Amazon S3 Access Key, available from your account profile.
+                     </listitem>
+
+                     <listitem>
+                        <literal>cache.s3.secretAccessKey</literal> -
+						Amazon S3 Secret Access Key, available from your account profile.
+						As this is a password, be careful not to distribute it or include
+						this secret key in built software.
+                     </listitem>
+
+                     <listitem>
+                        <literal>cache.s3.secure</literal> -
+						The default is <literal>false</literal>:
+						Traffic is sent unencrypted over the public Internet. 
+						Set to <literal>true</literal> to use HTTPS.
+						Note that unencrypted is obviously faster.
+                     </listitem>
+
+                     <listitem>
+                        <literal>cache.s3.bucket</literal> -
+						Name of the bucket to store data.
+						For different caches using the same access key, use a different bucket name.
+						Read the S3 documentation on the definition of a bucket.
+						The default is <literal>jboss-cache</literal>.
+                     </listitem>
+
+                     <listitem>
+                        <literal>cache.s3.callingFormat</literal> -
+						One of <literal>PATH</literal>, <literal>SUBDOMAIN</literal>, or
+						<literal>VANITY</literal>.
+						Read the S3 documentation on the use of calling domains.
+						The default is <literal>SUBDOMAIN</literal>.
+                     </listitem>
+
+                     <listitem>
+                        <literal>cache.s3.optimize</literal> -
+						The default is <literal>false</literal>.
+						If true, <literal>put(Map)</literal> operations
+						replace the data stored at an FQN rather than attempt
+                        to fetch and merge. (This option is fairly experimental
+                        at the moment.)
+                     </listitem>
+
+                     <listitem>
+                        <literal>cache.s3.parentCache</literal> -
+						The default is <literal>true</literal>.
+						Set this value to <literal>false</literal> if you are using multiple caches
+						sharing the same S3 bucket, that remove parent nodes of nodes being created
+						in other caches. (This is not a common use case.)
+                        <para>
+						JBoss Cache stores nodes in a tree format and automatically
+						creates intermediate parent nodes as necessary.
+						The S3 cache loader must also create these parent nodes as well
+						to allow for operations such as <literal>getChildrenNames</literal> to work
+						properly. Checking if all parent nodes exists for every <literal>put</literal>
+						operation is fairly expensive, so by default the cache loader caches
+						the existance of these parent nodes.
+                        </para>
+                     </listitem>
+
+                     <listitem>
+                        <literal>cache.s3.location</literal> -
+                        This choses a primary storage location for your data
+                        to reduce loading and storage latency closest to you.
+                        Set to <literal>EU</literal> to store data locale to Europe.
+                        The default is <literal>null</literal>, or to store in the United States.  
+                     </listitem>
+                  </itemizedlist>
+               </para>
+            </section>
+            
+      </section>
+	
       <section id="cl.tcp">
          <title>TcpDelegatingCacheLoader</title>
 

Added: core/trunk/src/main/java/org/jboss/cache/loader/s3/S3CacheLoader.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/loader/s3/S3CacheLoader.java	                        (rev 0)
+++ core/trunk/src/main/java/org/jboss/cache/loader/s3/S3CacheLoader.java	2008-03-20 08:22:10 UTC (rev 5454)
@@ -0,0 +1,459 @@
+package org.jboss.cache.loader.s3;
+
+import java.io.BufferedInputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.jcip.annotations.ThreadSafe;
+import net.noderunner.amazon.s3.Bucket;
+import net.noderunner.amazon.s3.Connection;
+import net.noderunner.amazon.s3.Entry;
+import net.noderunner.amazon.s3.GetStreamResponse;
+import net.noderunner.amazon.s3.ListResponse;
+import net.noderunner.amazon.s3.Response;
+import net.noderunner.amazon.s3.S3Object;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.config.CacheLoaderConfig.IndividualCacheLoaderConfig;
+import org.jboss.cache.loader.AbstractCacheLoader;
+
+
+/**
+ * Uses the Amazon S3 service for storage.
+ * See http://aws.amazon.com/ for information.
+ * Does not support transaction isolation.
+ * <p/>
+ * Data is stored in a single bucket location.
+ * The FQN comprises the key of the storage, the data the data itself.
+ * <p/>
+ * Internal structure:
+ * <pre>
+ * A/
+ * B/_rootchild
+ * C/_rootchild/_child1
+ * C/_rootchild/_child2
+ * C/_rootchild/_child3
+ * B/_root2
+ * </pre>
+ * The FQN component type is either prefixed with a _ for String, or a primitive type prefix.
+ * <p/>
+ * All put and many remove operations require fetching and merging data before storing data,
+ * which increases latency. This fetching can be turned off. See {@link S3LoaderConfig#getOptimize()}.
+ * <p/>
+ * Parent nodes are added to the store as needed.
+ * For example, when doing a put("/a/b/c"), the nodes "/a/b" and "/a" are created
+ * if they do not exist. To prevent unnecessary checks of the store,
+ * a local cache is kept of these "parent nodes". With multiple sites removing
+ * parent nodes, this can potentially need to inconsistencies. To disable caching,
+ * set {@link S3LoaderConfig#setParentCache} to false.
+ * <p/>
+ * 
+ *
+ * @author Elias Ross
+ * @version $Id: JdbmCacheLoader.java 4298 2007-08-15 18:30:00Z genman $
+ */
+ at ThreadSafe
+ at SuppressWarnings("unchecked")
+public class S3CacheLoader extends AbstractCacheLoader
+{
+   private static final Log log = LogFactory.getLog(S3CacheLoader.class);
+
+   /**
+    * Max number of dummy parent nodes to cache.
+    */
+   private static final int PARENT_CACHE_SIZE = 100;
+
+   /**
+    * Configuration.
+    */
+   private S3LoaderConfig config;
+
+   /**
+    * Unit separator.
+    */
+   private final char SEP = Fqn.SEPARATOR.charAt(0);
+
+   /**
+    * Zero depth prefix.
+    */
+   private final char DEPTH_0 = 'A';
+   
+   /**
+    * Stateless connection; thread safe.
+    */
+   private Connection connection;
+
+   /**
+    * Map classes to characters.
+    */
+   private static Map<Class<?>, Character> prefix = new HashMap<Class<?>, Character>();
+   static
+   {
+      prefix.put(Byte.class, 'B');
+      prefix.put(Character.class, 'C');
+      prefix.put(Double.class, 'D');
+      prefix.put(Float.class, 'F');
+      prefix.put(Integer.class, 'I');
+      prefix.put(Long.class, 'J');
+      prefix.put(Short.class, 'S');
+      prefix.put(Boolean.class, 'Z');
+   }
+
+   /**
+    * Empty parent nodes whose existence is cached.
+    * Empty parents are required to ensure {@link #getChildrenNames(Fqn)}
+    * and recursive {@link #remove(Fqn)} work correctly.
+    */
+   private Set<Fqn> parents = Collections.synchronizedSet(new HashSet<Fqn>());
+
+   /**
+    * Empty HashMap, serialized, lazy created.
+    */
+   private S3Object dummyObj;
+
+   /**
+    * This cache loader is stateless, but as part of initialization access the service.
+    * Creates a new bucket, if necessary.
+    */
+   @Override
+   public void start() throws Exception
+   {
+      log.debug("Starting");
+      try
+      {
+         this.connection = config.getConnection();
+         Response create = connection.create(getBucket(), config.getLocation());
+         if (!create.isOk())
+            throw new S3Exception("Unable to create bucket: " + create);
+         log.info("S3 accessed successfully. Bucket created: " + create);
+      }
+      catch (Exception e)
+      {
+         destroy();
+         throw e;
+      }
+   }
+
+   /**
+    * Closes the connection; shuts down the HTTP connection pool.
+    */
+   @Override
+   public void stop()
+   {
+      log.debug("stop");
+      connection.shutdown();
+   }
+
+   /**
+    * Sets the configuration string for this cache loader.
+    */
+   public void setConfig(IndividualCacheLoaderConfig base)
+   {
+      if (base instanceof S3LoaderConfig)
+      {
+         this.config = (S3LoaderConfig) base;
+      }
+      else
+      {
+         config = new S3LoaderConfig(base);
+      }
+
+      if (log.isTraceEnabled())
+         log.trace("config=" + config);
+   }
+
+   public IndividualCacheLoaderConfig getConfig()
+   {
+      return config;
+   }
+
+   private String key(Fqn fqn)
+   {
+      return key(fqn.size(), fqn).toString();
+   }
+
+   private String children(Fqn fqn)
+   {
+      return key(fqn.size() + 1, fqn).append(SEP).toString();
+   }
+
+   private StringBuilder key(int depth, Fqn fqn)
+   {
+      StringBuilder sb = new StringBuilder();
+      List l = fqn.peekElements();
+      sb.append((char) (DEPTH_0 + depth));
+      for (Object o : l)
+      {
+         sb.append(SEP);
+         if (o == null)
+            sb.append("_null");
+         else if (o instanceof String)
+            sb.append("_").append(o);
+         else
+         {
+            // TODO
+            Character c = prefix.get(o.getClass());
+            if (c == null)
+               throw new IllegalArgumentException("not supported " + o.getClass());
+            sb.append(c.charValue()).append(o);
+         }
+      }
+      return sb;
+   }
+
+   /**
+    * Returns an unmodifiable set of relative children names, or
+    * returns null if the parent node is not found or if no children are found.
+    */
+   public Set<String> getChildrenNames(Fqn name) throws Exception
+   {
+      String children = children(name);
+      ListResponse response = connection.list(getBucket(), children);
+      if (log.isTraceEnabled())
+      {
+         log.trace("getChildrenNames " + name + " response=" + response);
+      }
+      if (response.isNotFound())
+         return null;
+      if (!response.isOk())
+         throw new Exception("List failed " + response);
+
+      Set<String> set = new HashSet<String>();
+      for (Entry e : response.getEntries())
+      {
+         // TODO decode prefix
+         set.add(e.getKey().substring(children.length() + 1));
+      }
+
+      if (set.isEmpty())
+      {
+         return null;
+      }
+
+      return Collections.unmodifiableSet(set);
+   }
+
+   /**
+    * Returns a map containing all key-value pairs for the given FQN, or null
+    * if the node is not present.
+    */
+   public Map get(Fqn name) throws Exception
+   {
+      GetStreamResponse response = connection.getStream(getBucket(), key(name));
+      try
+      {
+         if (log.isTraceEnabled())
+         {
+            log.trace("get " + name + " response=" + response);
+         }
+
+         if (response.isNotFound())
+            return null;
+         if (!response.isOk())
+            throw new S3Exception("get failed " + response);
+
+         BufferedInputStream is = new BufferedInputStream(response.getInputStream());
+         Map map = (Map) getMarshaller().objectFromStream(is);
+         response.release();
+         return map;
+      }
+      finally
+      {
+         response.release();
+      }
+   }
+
+   private Bucket getBucket()
+   {
+      return config.getBucket();
+   }
+
+   /**
+    * Returns whether the given node exists.
+    */
+   public boolean exists(Fqn name) throws Exception
+   {
+      Response response = connection.head(getBucket(), key(name));
+      if (log.isTraceEnabled())
+      {
+         log.trace("exists " + name + " response=" + response);
+      }
+      return response.isOk();
+   }
+
+   private S3Object wrap(Map map) throws Exception
+   {
+      byte[] b = getMarshaller().objectToByteBuffer(map);
+      return new S3Object(b);
+   }
+
+   /**
+    * Stores a single FQN-key-value record.
+    * This is slow, so avoid this method.
+    */
+   public Object put(Fqn name, Object key, Object value) throws Exception
+   {
+      Map map = mayGet(name);
+      Object oldValue;
+      if (map != null)
+      {
+         oldValue = map.put(key, value);
+      }
+      else
+      {
+         map = new HashMap(Collections.singletonMap(key, value));
+         oldValue = null;
+      }
+      put0(name, map);
+      return oldValue;
+   }
+
+   /**
+    * Puts by replacing all the contents of the node.
+    */
+   private void put0(Fqn name, Map map) throws Exception
+   {
+      put0(name, wrap(map));
+   }
+
+   private void put0(Fqn name, S3Object obj) throws Exception
+   {
+      Response response = connection.put(getBucket(), key(name), obj);
+      if (log.isTraceEnabled())
+      {
+         log.trace("put " + name + " obj=" + obj + " response=" + response);
+      }
+      ensureParent(name);
+      if (!response.isOk())
+         throw new S3Exception("Put failed " + response);
+   }
+
+   private S3Object getDummy() throws Exception
+   {
+      if (dummyObj != null)
+         return dummyObj;
+      return dummyObj = wrap(new HashMap(0));
+   }
+
+   /**
+    * Ensures a parent node exists.
+    * Calls recursively to initialize parents as necessary.
+    */
+   private void ensureParent(Fqn name) throws Exception
+   {
+      if (name.size() <= 1)
+         return;
+      Fqn parent = name.getParent();
+      boolean cache = config.getParentCache();
+      if (cache && parents.contains(parent))
+         return;
+      // potential race condition between exists and put
+      if (!exists(parent))
+         put0(parent, getDummy());
+      if (cache)
+      {
+         parents.add(parent);
+         if (parents.size() > PARENT_CACHE_SIZE)
+         {
+            parents.clear();
+         }
+      }
+      ensureParent(parent);
+   }
+
+   /**
+    * Returns null if optimized; else fetches.
+    */
+   private Map mayGet(Fqn name) throws Exception
+   {
+      if (config.getOptimize())
+         return null;
+      else
+         return get(name);
+   }
+
+   /**
+    * Removes a key from an FQN.
+    * Not very fast.
+    */
+   public Object remove(Fqn name, Object key) throws Exception
+   {
+      Map map = get(name);
+      Object oldValue;
+      if (map != null)
+      {
+         oldValue = map.remove(key);
+      }
+      else
+      {
+         oldValue = null;
+      }
+      put0(name, map);
+      return oldValue;
+   }
+
+   /**
+    * Stores a map of key-values for a given FQN, but does not delete existing
+    * key-value pairs (that is, it does not erase).
+    */
+   public void put(Fqn name, Map<Object, Object> values) throws Exception
+   {
+      Map map = mayGet(name);
+      if (values == null)
+         values = Collections.emptyMap();
+      if (map != null)
+         map.putAll(values);
+      else
+         map = new HashMap(values);
+      put0(name, map);
+   }
+
+   /**
+    * Deletes the node for a given FQN and all its descendant nodes.
+    */
+   public void remove(Fqn name) throws Exception
+   {
+      /*
+      if (name.isRoot())
+      {
+        log.trace("optimized delete");
+       connection.delete(getBucket()).assertOk();
+       connection.create(getBucket()).assertOk();
+        log.trace("done");
+       return;
+      }
+      */
+      Set<String> children = getChildrenNames(name);
+      if (children != null)
+      {
+         log.trace("remove children: " + children);
+         for (String child : children)
+         {
+            remove(new Fqn(name, child));
+         }
+      }
+      Response response = connection.delete(getBucket(), key(name));
+      if (log.isTraceEnabled())
+      {
+         log.trace("delete " + name + " response=" + response);
+      }
+      if (!response.isOk() && !response.isNotFound())
+         throw new S3Exception("delete failed " + response);
+      parents.remove(name);
+   }
+
+   /**
+    * Clears the map for the given node, but does not remove the node.
+    */
+   public void removeData(Fqn name) throws Exception
+   {
+      put0(name, getDummy());
+   }
+
+}
\ No newline at end of file

Added: core/trunk/src/main/java/org/jboss/cache/loader/s3/S3Exception.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/loader/s3/S3Exception.java	                        (rev 0)
+++ core/trunk/src/main/java/org/jboss/cache/loader/s3/S3Exception.java	2008-03-20 08:22:10 UTC (rev 5454)
@@ -0,0 +1,44 @@
+package org.jboss.cache.loader.s3;
+
+import org.jboss.cache.CacheException;
+
+/**
+ * Basic exception class.
+ */
+public class S3Exception extends CacheException
+{
+
+   private static final long serialVersionUID = -5961236335942313217L;
+
+   /**
+    * Constructs a new S3Exception.
+    */
+   public S3Exception()
+   {
+   }
+
+   /**
+    * Constructs a new S3Exception.
+    */
+   public S3Exception(String arg0)
+   {
+      super(arg0);
+   }
+
+   /**
+    * Constructs a new S3Exception.
+    */
+   public S3Exception(Throwable arg0)
+   {
+      super(arg0);
+   }
+
+   /**
+    * Constructs a new S3Exception.
+    */
+   public S3Exception(String arg0, Throwable arg1)
+   {
+      super(arg0, arg1);
+   }
+
+}

Added: core/trunk/src/main/java/org/jboss/cache/loader/s3/S3LoaderConfig.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/loader/s3/S3LoaderConfig.java	                        (rev 0)
+++ core/trunk/src/main/java/org/jboss/cache/loader/s3/S3LoaderConfig.java	2008-03-20 08:22:10 UTC (rev 5454)
@@ -0,0 +1,338 @@
+package org.jboss.cache.loader.s3;
+
+import java.lang.reflect.Field;
+import java.util.Properties;
+
+import net.noderunner.amazon.s3.Bucket;
+import net.noderunner.amazon.s3.CallingFormat;
+import net.noderunner.amazon.s3.Connection;
+
+import org.jboss.cache.CacheException;
+import org.jboss.cache.config.CacheLoaderConfig.IndividualCacheLoaderConfig;
+
+
+/**
+ * Amazon S3 loader configuration.
+ *  
+ * @author Elias Ross
+ */
+public class S3LoaderConfig extends IndividualCacheLoaderConfig
+{
+   private static final long serialVersionUID = 4626734068542420865L;
+
+   private String accessKeyId;
+
+   private String secretAccessKey;
+
+   private boolean secure;
+
+   private String server = Connection.DEFAULT_HOST;
+
+   private int port;
+
+   private Bucket bucket = new Bucket("jboss-cache");
+
+   private CallingFormat callingFormat = CallingFormat.SUBDOMAIN;
+
+   private String location = Connection.LOCATION_DEFAULT;
+
+   private boolean optimize = false;
+
+   private boolean parentCache = true;
+
+   public S3LoaderConfig()
+   {
+      setClassName(S3CacheLoader.class.getName());
+   }
+
+   /**
+    * For use by {@link S3CacheLoader}.
+    *
+    * @param base generic config object created by XML parsing.
+    */
+   S3LoaderConfig(IndividualCacheLoaderConfig base)
+   {
+      setClassName(S3CacheLoader.class.getName());
+      populateFromBaseConfig(base);
+   }
+
+   /**
+    * Returns a new connection.
+    */
+   Connection getConnection()
+   {
+      return new Connection(getAccessKeyId(), getSecretAccessKey(),
+            isSecure(), getServer(), getPort(), getCallingFormat());
+   }
+
+   @Override
+   public void setProperties(Properties props)
+   {
+      super.setProperties(props);
+      if (props == null)
+         return;
+      setAccessKeyId(props.getProperty("cache.s3.accessKeyId"));
+      setSecretAccessKey(props.getProperty("cache.s3.secretAccessKey"));
+      setSecure(props.getProperty("cache.s3.secure"));
+      setServer(props.getProperty("cache.s3.server", Connection.DEFAULT_HOST));
+      setPort(props.getProperty("cache.s3.port"));
+      setBucket(props.getProperty("cache.s3.bucket"));
+      setCallingFormat(props.getProperty("cache.s3.callingFormat"));
+      setOptimize(props.getProperty("cache.s3.optimize"));
+      setParentCache(props.getProperty("cache.s3.parentCache"));
+      setLocation(props.getProperty("cache.s3.location"));
+   }
+
+   private void setLocation(String s)
+   {
+      if (s != null)
+          this.location = s;
+   }
+
+   private void setParentCache(String s)
+   {
+      if (s != null)
+         setParentCache(Boolean.valueOf(s));
+   }
+
+   private void setOptimize(String s)
+   {
+      if (s != null)
+         setOptimize(Boolean.valueOf(s));
+   }
+
+   private void setCallingFormat(String s)
+   {
+      if (s != null)
+      {
+         try
+         {
+            Field field = CallingFormat.class.getDeclaredField(s);
+            setCallingFormat((CallingFormat)field.get(null));
+         }
+         catch (Exception e)
+         {
+            throw new CacheException(e);
+         }
+      }
+   }
+
+   private void setBucket(String s)
+   {
+      if (s != null)
+         setBucket(new Bucket(s));
+   }
+
+   private void setSecure(String s)
+   {
+      if (s != null)
+         setSecure(Boolean.valueOf(s));
+   }
+
+   private void setPort(String s)
+   {
+      if (s != null)
+         setPort(Integer.parseInt(s));
+      else
+         setPort(secure ? Connection.SECURE_PORT : Connection.INSECURE_PORT);
+   }
+
+   @Override
+   public boolean equals(Object obj)
+   {
+      if (obj instanceof S3LoaderConfig && equalsExcludingProperties(obj))
+      {
+         // TODO
+         return this == obj;
+      }
+      return false;
+   }
+
+   @Override
+   public int hashCode()
+   {
+      return 31 * hashCodeExcludingProperties();
+      // TODO
+   }
+
+   @Override
+   public S3LoaderConfig clone() throws CloneNotSupportedException
+   {
+      return (S3LoaderConfig) super.clone();
+   }
+
+   /**
+    * Returns accessKeyId.
+    */
+   public String getAccessKeyId()
+   {
+      return accessKeyId;
+   }
+
+   /**
+    * Sets accessKeyId.
+    */
+   public void setAccessKeyId(String accessKeyId)
+   {
+      this.accessKeyId = accessKeyId.trim();
+   }
+
+   /**
+    * Returns secretAccessKey.
+    */
+   public String getSecretAccessKey()
+   {
+      return secretAccessKey.trim();
+   }
+
+   /**
+    * Sets secretAccessKey.
+    */
+   public void setSecretAccessKey(String secretAccessKey)
+   {
+      this.secretAccessKey = secretAccessKey;
+   }
+
+   /**
+    * Returns secure.
+    */
+   public boolean isSecure()
+   {
+      return secure;
+   }
+
+   /**
+    * Sets secure.
+    */
+   public void setSecure(boolean secure)
+   {
+      this.secure = secure;
+   }
+
+   /**
+    * Returns server.
+    */
+   public String getServer()
+   {
+      return server;
+   }
+
+   /**
+    * Sets server.
+    */
+   public void setServer(String server)
+   {
+      this.server = server;
+   }
+
+   /**
+    * Returns port.
+    */
+   public int getPort()
+   {
+      return port;
+   }
+
+   /**
+    * Sets port.
+    */
+   public void setPort(int port)
+   {
+      this.port = port;
+   }
+
+   /**
+    * Returns bucket.
+    */
+   public Bucket getBucket()
+   {
+      return bucket;
+   }
+
+   /**
+    * Sets bucket.
+    */
+   public void setBucket(Bucket bucket)
+   {
+      this.bucket = bucket;
+   }
+
+   /**
+    * Returns callingFormat.
+    */
+   public CallingFormat getCallingFormat()
+   {
+      return callingFormat;
+   }
+
+   /**
+    * Sets callingFormat.
+    */
+   public void setCallingFormat(CallingFormat callingFormat)
+   {
+      this.callingFormat = callingFormat;
+   }
+
+   /**
+    * Returns a debug string.
+    */
+   @Override
+   public String toString()
+   {
+      return super.toString() + 
+          " keyid=" + accessKeyId +
+          " secret=" + "***" +
+  		  " secure=" + secure +
+  		  " server=" + server +
+  		  " port=" + port +
+  		  " bucket=" + bucket +
+  		  " cf=" + callingFormat +
+  		  " location=" + location;
+   }
+
+   /**
+    * Returns true if certain CRUD operations are optimized to not do extra queries.
+    * If true, then the behavior will be:
+    * <ul>
+    * <li>put(Fqn, Map) will always overwrite
+    * <li>put(Fqn, Object, Object) will always overwrite, return null
+    * <li>remove(Object) will return null
+    * </ul>
+    */
+   public boolean getOptimize()
+   {
+      return optimize;
+   }
+
+   /**
+    * Sets optimize.
+    */
+   public void setOptimize(boolean optimize)
+   {
+      this.optimize = optimize;
+   }
+
+   /**
+    * Returns true if the existence of parent nodes should be cached.
+    */
+   public boolean getParentCache()
+   {
+      return parentCache;
+   }
+
+   /**
+    * Sets parentCache.
+    */
+   public void setParentCache(boolean parentCache)
+   {
+      this.parentCache = parentCache;
+   }
+
+   /**
+    * Returns location.
+    */
+   public String getLocation()
+   {
+      return location;
+   }
+}
\ No newline at end of file

Added: core/trunk/src/test/java/org/jboss/cache/loader/S3CacheLoaderTest.java
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/loader/S3CacheLoaderTest.java	                        (rev 0)
+++ core/trunk/src/test/java/org/jboss/cache/loader/S3CacheLoaderTest.java	2008-03-20 08:22:10 UTC (rev 5454)
@@ -0,0 +1,108 @@
+package org.jboss.cache.loader;
+
+import static org.testng.AssertJUnit.assertNotNull;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import net.noderunner.amazon.s3.emulator.Server;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.config.CacheLoaderConfig;
+import org.testng.annotations.Test;
+
+
+/**
+ * Tests {@link org.jboss.cache.loader.s3.S3CacheLoader}.
+ * 
+ * This requires a S3 account to truly test; uses an emulator otherwise.
+ *
+ * @author Elias Ross
+ * @version $Id: JdbmCacheLoaderTest.java 4561 2007-10-08 14:02:02Z manik.surtani at jboss.com $
+ */
+ at Test(groups =
+{"functional"}, enabled = true)
+public class S3CacheLoaderTest extends CacheLoaderTestsBase
+{
+
+   private static final Log log = LogFactory.getLog(S3CacheLoaderTest.class);
+
+   private Server server;
+
+   @Override
+   protected void configureCache() throws Exception
+   {      
+      String accessKey = System.getProperty("accessKey");
+      String properties;
+      if (accessKey == null)
+      {
+         log.info("Testing using S3CacheLoader using emulator");
+         server = new Server();
+         server.start();
+         properties = 
+               "cache.s3.accessKeyId=dummy\n"  +
+               "cache.s3.secretAccessKey=dummy\n"  +
+               "cache.s3.server=localhost\n"  +
+               "cache.s3.port=" + server.getPort() + "\n"  +
+               "cache.s3.callingFormat=VANITY" + "\n"  +
+               "cache.s3.bucket=localhost" + "\n";
+      }
+      else
+      {
+          properties = 
+             "cache.s3.accessKeyId=" + accessKey + "\n" +
+             "cache.s3.secretAccessKey=" + System.getProperty("secretKey") + "\n" ;
+      }
+      CacheLoaderConfig config = getSingleCacheLoaderConfig("", "org.jboss.cache.loader.s3.S3CacheLoader", properties, false, true, false);
+      // System.out.println(config);
+      Properties p = config.getFirstCacheLoaderConfig().getProperties();
+      // System.out.println(p);
+      assertNotNull(p.get("cache.s3.accessKeyId"));
+      assertNotNull(p.get("cache.s3.secretAccessKey"));
+      cache.getConfiguration().setCacheLoaderConfig(config);
+   }
+   
+   @Override
+   public void cleanup() {
+      if (server != null)
+      {
+         try
+         {
+             server.close();
+         }
+         catch (IOException e) {
+         }
+      }
+   }
+
+   protected void postConfigure()
+   {
+      cache.removeNode(Fqn.root());
+   }
+
+   //@Override
+   public void testCacheLoaderThreadSafety()
+   {
+
+   }
+
+   //@Override
+   public void testPartialLoadAndStore()
+   {
+      // do nothing
+   }
+
+   //@Override
+   public void testBuddyBackupStore()
+   {
+      // do nothing
+   }
+
+   //@Override
+   protected void threadSafetyTest(final boolean singleFqn) throws Exception
+   {
+   }
+   
+}




More information about the jbosscache-commits mailing list