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 $
+ */
+@ThreadSafe
+@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(a)jboss.com $
+ */
+@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
+ {
+ }
+
+}