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>