Merge branch 'master' of git@github.com:Qi4j/qi4j-sandbox
diff --git a/extensions/entitystore-swift/LICENSE b/extensions/entitystore-cassandra/LICENSE
similarity index 100%
rename from extensions/entitystore-swift/LICENSE
rename to extensions/entitystore-cassandra/LICENSE
diff --git a/extensions/entitystore-swift/NOTICE b/extensions/entitystore-cassandra/NOTICE
similarity index 82%
rename from extensions/entitystore-swift/NOTICE
rename to extensions/entitystore-cassandra/NOTICE
index a2a8626..42bca47 100644
--- a/extensions/entitystore-swift/NOTICE
+++ b/extensions/entitystore-cassandra/NOTICE
@@ -1,5 +1,5 @@
-Qi4j Quick Persistence Extension
-Copyright 2007-2008, The Qi4j Development Team of individuals.
+Qi4j Cassandra Entity Store
+Copyright 2007-2010, The Qi4j Development Team of individuals.
 
 See http://www.qi4j.org/contributors.html for list of of individuals.
 Also see each file for additional information of Copyright claims.
@@ -11,6 +11,8 @@
 Below follows a list of binary dependencies and their licenses;
 ----------------------------------------------------------------
 
+<TODO>
+
 <NONE>
 
 
diff --git a/extensions/entitystore-cassandra/dev-status.xml b/extensions/entitystore-cassandra/dev-status.xml
new file mode 100644
index 0000000..5b74162
--- /dev/null
+++ b/extensions/entitystore-cassandra/dev-status.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<module xmlns="http://www.qi4j.org/schemas/2008/dev-status/1">
+    <status>
+        <!--none,early,beta,stable,mature-->
+        <codebase>early</codebase>
+
+        <!-- none, brief, good, complete -->
+        <documentation>none</documentation>
+
+        <!-- none, some, good, complete -->
+        <unittests>some</unittests>
+    </status>
+    <licenses>
+        <license>ALv2</license>
+    </licenses>
+</module>
\ No newline at end of file
diff --git a/extensions/entitystore-cassandra/pom.xml b/extensions/entitystore-cassandra/pom.xml
new file mode 100644
index 0000000..3da1054
--- /dev/null
+++ b/extensions/entitystore-cassandra/pom.xml
@@ -0,0 +1,73 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <groupId>org.qi4j</groupId>
+        <artifactId>qi4j-extensions</artifactId>
+        <version>1.3-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.qi4j.extension</groupId>
+    <artifactId>qi4j-entitystore-cassandra</artifactId>
+    <name>Qi4j Extension - Entity Store - Cassandra</name>
+    <packaging>bundle</packaging>
+
+    <repositories>
+        <repository>
+            <id>riptano</id>
+            <name>riptano</name>
+            <url>http://mvn.riptano.com/content/repositories/public/</url>
+        </repository>
+    </repositories>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>org.qi4j.core</groupId>
+            <artifactId>qi4j-core-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.qi4j.core</groupId>
+            <artifactId>qi4j-core-bootstrap</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.qi4j.library</groupId>
+            <artifactId>qi4j-lib-locking</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.qi4j.core</groupId>
+            <artifactId>qi4j-core-runtime</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.qi4j.core</groupId>
+            <artifactId>qi4j-core-testsupport</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>libthrift</groupId>
+            <artifactId>libthrift</artifactId>
+            <version>917130</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cassandra</groupId>
+            <artifactId>apache-cassandra</artifactId>
+            <version>0.6.6</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-compress</artifactId>
+            <version>1.1</version>
+        </dependency>
+        <!-- <dependency>
+            <groupId>me.prettyprint</groupId>
+            <artifactId>hector</artifactId>
+            <version>0.7.0-18</version>
+             <exclusions>
+                 <exclusion>
+                     <artifactId>properties-maven-plugin</artifactId>
+                     <groupId>org.codehaus.mojo</groupId>
+                 </exclusion>
+             </exclusions>
+         </dependency>-->
+    </dependencies>
+</project>
diff --git a/extensions/entitystore-cassandra/src/main/java/org/qi4j/entitystore/cassandra/CassandraConfiguration.java b/extensions/entitystore-cassandra/src/main/java/org/qi4j/entitystore/cassandra/CassandraConfiguration.java
new file mode 100644
index 0000000..8a6c45a
--- /dev/null
+++ b/extensions/entitystore-cassandra/src/main/java/org/qi4j/entitystore/cassandra/CassandraConfiguration.java
@@ -0,0 +1,21 @@
+package org.qi4j.entitystore.cassandra;
+
+
+public interface CassandraConfiguration {
+    boolean gzipCompress();
+
+    boolean checkAbsentBeforeCreate();
+
+    boolean checkPresentBeforeDelete();
+
+    boolean checkPresentBeforeUpdate();
+
+    boolean readOnly();
+
+
+    String getHost();
+
+    String getLogin();
+
+    String getPassword();
+}
diff --git a/extensions/entitystore-cassandra/src/main/java/org/qi4j/entitystore/cassandra/CassandraEntityStoreService.java b/extensions/entitystore-cassandra/src/main/java/org/qi4j/entitystore/cassandra/CassandraEntityStoreService.java
new file mode 100644
index 0000000..3e55e16
--- /dev/null
+++ b/extensions/entitystore-cassandra/src/main/java/org/qi4j/entitystore/cassandra/CassandraEntityStoreService.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2007, Rickard Öberg. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.qi4j.entitystore.cassandra;
+
+import org.qi4j.api.concern.Concerns;
+import org.qi4j.api.mixin.Mixins;
+import org.qi4j.api.service.ServiceComposite;
+import org.qi4j.entitystore.map.MapEntityStoreMixin;
+import org.qi4j.spi.entitystore.ConcurrentModificationCheckConcern;
+import org.qi4j.spi.entitystore.EntityStateVersions;
+import org.qi4j.spi.entitystore.EntityStore;
+import org.qi4j.spi.entitystore.StateChangeNotificationConcern;
+
+@Concerns({StateChangeNotificationConcern.class,
+        ConcurrentModificationCheckConcern.class})
+@Mixins({MapEntityStoreMixin.class, CassandraMapEntityStoreMixin.class})
+public interface CassandraEntityStoreService extends EntityStateVersions,
+        EntityStore, ServiceComposite {
+
+}
diff --git a/extensions/entitystore-cassandra/src/main/java/org/qi4j/entitystore/cassandra/CassandraMapEntityStoreMixin.java b/extensions/entitystore-cassandra/src/main/java/org/qi4j/entitystore/cassandra/CassandraMapEntityStoreMixin.java
new file mode 100644
index 0000000..27c7e67
--- /dev/null
+++ b/extensions/entitystore-cassandra/src/main/java/org/qi4j/entitystore/cassandra/CassandraMapEntityStoreMixin.java
@@ -0,0 +1,211 @@
+package org.qi4j.entitystore.cassandra;
+
+import org.apache.cassandra.thrift.*;
+import org.apache.thrift.protocol.TBinaryProtocol;
+import org.apache.thrift.transport.TSocket;
+import org.apache.thrift.transport.TTransport;
+import org.qi4j.api.entity.EntityReference;
+import org.qi4j.api.injection.scope.Service;
+import org.qi4j.api.io.Input;
+import org.qi4j.api.service.Activatable;
+import org.qi4j.entitystore.map.MapEntityStore;
+import org.qi4j.spi.entity.EntityType;
+import org.qi4j.spi.entitystore.EntityAlreadyExistsException;
+import org.qi4j.spi.entitystore.EntityNotFoundException;
+import org.qi4j.spi.entitystore.EntityStoreException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * // TODO: Document this
+ *
+ * @author pvdyck
+ * @since 4.0
+ */
+public class CassandraMapEntityStoreMixin implements MapEntityStore, Activatable {
+    private final Logger logger = LoggerFactory.getLogger(CassandraMapEntityStoreMixin.class);
+
+    private static final String keySpace = "Qi4j";
+    static final String entryColumnFamily = "Qi4jEntries";
+
+    private ColumnPath entryColumnPath;
+
+
+    static final int BYTE_ARRAY_BUFFER_INITIAL_SIZE = 512;
+
+    private TTransport tr = new TSocket("localhost", 9160);
+    private Cassandra.Client client = new Cassandra.Client(new TBinaryProtocol(tr));
+
+    @Service
+    private
+    CassandraConfiguration conf;
+
+    public void activate() throws Exception {
+        logger.info("starting cassandra store");
+        tr = new TSocket(conf.getHost(), 9160);
+        if(conf.getLogin()!=null){
+
+        }
+        client = new Cassandra.Client(new TBinaryProtocol(tr));
+        tr.open();
+        entryColumnPath = new ColumnPath(entryColumnFamily).setColumn("entry".getBytes("UTF-8"));
+        logger.info("started cassandra store");
+    }
+
+
+    public void passivate() throws Exception {
+        logger.info("shutting down cassandra");
+        synchronized (client) {
+            tr.close();
+        }
+    }
+
+
+    public void applyChanges(final MapChanges changes) throws IOException {
+        if (conf.readOnly()) {
+            throw new EntityStoreException("Read-only Entity Store");
+        }
+
+        try {
+            final MapUpdater changer = new MapUpdater();
+            changes.visitMap(changer);
+            synchronized (client) {
+                client.batch_mutate(keySpace, changer.mutationMap, ConsistencyLevel.ONE);
+            }
+        } catch (Throwable e) {
+            throw new EntityStoreException("Exception during cassandra batch "
+                    + " - ", e);
+        }
+    }
+
+    boolean contains(EntityReference ref) throws EntityStoreException {
+        try {
+            return get(ref) != null;
+        } catch (final EntityNotFoundException e1) {
+            return false;
+        } catch (final Exception e1) {
+            throw new EntityStoreException(e1);
+        }
+    }
+
+    public Reader get(final EntityReference ref) {
+        String hashKey = ref.toString();
+
+        synchronized (client) {
+            try {
+                ColumnOrSuperColumn column = client.get(keySpace, hashKey,
+                        entryColumnPath, ConsistencyLevel.ONE);
+
+                return createReader(new ByteArrayInputStream(column.getColumn().getValue()));
+            } catch (NotFoundException nfe) {
+                throw new EntityNotFoundException(ref);
+            } catch (Exception e) {
+                throw new EntityStoreException(e);
+            }
+        }
+    }
+
+    public Input<Reader, IOException> entityStates() {
+          throw new UnsupportedOperationException("Not implemented yet");
+    }
+
+
+    Writer createWriter(OutputStream out) throws IOException {
+        if (conf.gzipCompress())
+            return new OutputStreamWriter(new GZIPOutputStream(out));
+        return new OutputStreamWriter(out);
+    }
+
+    private Reader createReader(InputStream in) throws IOException {
+        if (conf.gzipCompress())
+            return new InputStreamReader(new GZIPInputStream(in));
+        return new InputStreamReader(in);
+    }
+
+    void checkAbsentBeforeCreate(EntityReference ref) {
+        if (!conf.checkAbsentBeforeCreate())
+            return;
+        if (contains(ref))
+            throw new EntityAlreadyExistsException(ref);
+    }
+
+    void checkPresentBeforeDelete(EntityReference ref) {
+        if (!conf.checkPresentBeforeDelete())
+            return;
+        if (!contains(ref))
+            throw new EntityNotFoundException(ref);
+    }
+
+    void checkPresentBeforeUpdate(EntityReference ref) {
+        if (!conf.checkPresentBeforeUpdate())
+            return;
+        if (!contains(ref))
+            throw new EntityNotFoundException(ref);
+    }
+
+    public ColumnPath getEntryColumnPath() {
+        return entryColumnPath;
+    }
+
+    class MapUpdater implements MapEntityStore.MapChanger {
+
+        private final Map<String, Map<String, List<Mutation>>> mutationMap = new HashMap<String, Map<String, List<Mutation>>>();
+
+        public Writer newEntity(final EntityReference ref, EntityType entityType) {
+            checkAbsentBeforeCreate(ref);
+            return getWriter(ref);
+        }
+
+        public Writer updateEntity(final EntityReference ref, EntityType entityType)
+                throws IOException {
+            checkPresentBeforeUpdate(ref);
+            return getWriter(ref);
+        }
+
+        public void removeEntity(EntityReference ref, EntityType entityType)
+                throws EntityNotFoundException {
+            checkPresentBeforeDelete(ref);
+            createMutationHolder(ref.identity()).add(new Mutation().setDeletion(new Deletion(System.currentTimeMillis())));
+        }
+
+        private Writer getWriter(final EntityReference ref) {
+            try {
+                return createWriter(new ByteArrayOutputStream(CassandraMapEntityStoreMixin.BYTE_ARRAY_BUFFER_INITIAL_SIZE) {
+                    @Override
+                    public void close() throws IOException {
+                        super.close();
+                        ColumnOrSuperColumn cosc = new ColumnOrSuperColumn();
+                        cosc.setColumn(new Column(getEntryColumnPath().getColumn(), toByteArray(), System.currentTimeMillis()));
+                        createMutationHolder(ref.identity()).add(new Mutation().setColumn_or_supercolumn(cosc));
+                    }
+                });
+            } catch (final Exception e) {
+                throw new EntityStoreException(e);
+            }
+        }
+
+        private List<Mutation> createMutationHolder(String key) {
+            Map<String, List<Mutation>> keyMutations = mutationMap.get(key);
+
+            if (keyMutations == null) {
+                keyMutations = new HashMap<String, List<Mutation>>();
+                mutationMap.put(key, keyMutations);
+            }
+
+            List<Mutation> columnFamilyMutations = keyMutations.get(CassandraMapEntityStoreMixin.entryColumnFamily);
+            if (columnFamilyMutations == null) {
+                columnFamilyMutations = new ArrayList<Mutation>();
+                keyMutations.put(CassandraMapEntityStoreMixin.entryColumnFamily, columnFamilyMutations);
+            }
+            return columnFamilyMutations;
+        }
+    }
+}
\ No newline at end of file
diff --git a/extensions/entitystore-cassandra/src/test/java/org/qi4j/entitystore/cassandra/CassandraConfigurationService.java b/extensions/entitystore-cassandra/src/test/java/org/qi4j/entitystore/cassandra/CassandraConfigurationService.java
new file mode 100644
index 0000000..36cee3f
--- /dev/null
+++ b/extensions/entitystore-cassandra/src/test/java/org/qi4j/entitystore/cassandra/CassandraConfigurationService.java
@@ -0,0 +1,53 @@
+package org.qi4j.entitystore.cassandra;
+
+import org.qi4j.api.mixin.Mixins;
+import org.qi4j.api.service.ServiceComposite;
+
+@Mixins(CassandraConfigurationService.CassandraConfigurationMixin.class)
+public interface CassandraConfigurationService extends ServiceComposite,
+        CassandraConfiguration {
+    public class CassandraConfigurationMixin implements CassandraConfiguration {
+        private final boolean gzipCompress = true;
+        private final boolean checkAbsentBeforeCreate = false;
+        private final boolean checkPresentBeforeDelete = false;
+        private final boolean checkPresentBeforeUpdate = false;
+
+        private final String NULL = null;
+        private final String LOCALHOST = "localhost";
+
+        public boolean checkAbsentBeforeCreate() {
+            return checkAbsentBeforeCreate;
+        }
+
+        public boolean checkPresentBeforeDelete() {
+            return checkPresentBeforeDelete;
+        }
+
+        public boolean checkPresentBeforeUpdate() {
+            return checkPresentBeforeUpdate;
+        }
+
+        public boolean gzipCompress() {
+            return gzipCompress;
+        }
+
+
+        public boolean readOnly() {
+            return false;
+        }
+
+        public String getHost() {
+            return LOCALHOST;
+        }
+
+        public String getLogin() {
+            return NULL;
+        }
+
+        public String getPassword() {
+            return NULL;
+        }
+
+
+    }
+}
diff --git a/extensions/entitystore-cassandra/src/test/java/org/qi4j/entitystore/cassandra/CassandraEntityStoreTest.java b/extensions/entitystore-cassandra/src/test/java/org/qi4j/entitystore/cassandra/CassandraEntityStoreTest.java
new file mode 100644
index 0000000..610adf5
--- /dev/null
+++ b/extensions/entitystore-cassandra/src/test/java/org/qi4j/entitystore/cassandra/CassandraEntityStoreTest.java
@@ -0,0 +1,15 @@
+package org.qi4j.entitystore.cassandra;
+
+import org.qi4j.bootstrap.AssemblyException;
+import org.qi4j.bootstrap.ModuleAssembly;
+import org.qi4j.test.entity.AbstractEntityStoreTest;
+
+public class CassandraEntityStoreTest extends AbstractEntityStoreTest {
+
+    @Override
+    public void assemble(ModuleAssembly module) throws AssemblyException {
+        super.assemble(module);
+        module.addServices(CassandraEntityStoreService.class,
+                CassandraConfigurationService.class);
+    }
+}
diff --git a/extensions/pom.xml b/extensions/pom.xml
index 3ad34bd..c808f39 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -19,5 +19,7 @@
     <module>entitystore-rmi</module>
     <module>entitystore-s3</module>
     <module>entitystore-swift</module>
+    <module>entitystore-cassandra</module>
+
   </modules>
 </project>