Repository: polygene-java Updated Branches: refs/heads/develop 829168ddc -> ba600a555
Adding more flexible configuration and more documentation. Project: http://git-wip-us.apache.org/repos/asf/polygene-java/repo Commit: http://git-wip-us.apache.org/repos/asf/polygene-java/commit/ba600a55 Tree: http://git-wip-us.apache.org/repos/asf/polygene-java/tree/ba600a55 Diff: http://git-wip-us.apache.org/repos/asf/polygene-java/diff/ba600a55 Branch: refs/heads/develop Commit: ba600a55520dee34a78e3e056ae1faf89355aba8 Parents: 829168d Author: niclas <[email protected]> Authored: Sun Feb 19 15:17:38 2017 +0800 Committer: niclas <[email protected]> Committed: Sun Feb 19 15:17:38 2017 +0800 ---------------------------------------------------------------------- .../src/docs/es-cassandra.txt | 116 +++++++++++++++++++ .../entitystore/cassandra/CassandraCluster.java | 38 +++--- .../CassandraEntityStoreConfiguration.java | 7 ++ .../cassandra/CassandraEntityStoreMixin.java | 42 ++++--- .../entitystore/cassandra/ClusterBuilder.java | 105 +++++++++++++++++ .../polygene/entitystore/cassandra/package.html | 48 ++++++-- .../entitystore/cassandra/DocSupport.java | 68 +++++++++++ 7 files changed, 384 insertions(+), 40 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/polygene-java/blob/ba600a55/extensions/entitystore-cassandra/src/docs/es-cassandra.txt ---------------------------------------------------------------------- diff --git a/extensions/entitystore-cassandra/src/docs/es-cassandra.txt b/extensions/entitystore-cassandra/src/docs/es-cassandra.txt new file mode 100644 index 0000000..25da991 --- /dev/null +++ b/extensions/entitystore-cassandra/src/docs/es-cassandra.txt @@ -0,0 +1,116 @@ +/////////////////////////////////////////////////////////////// + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. +/////////////////////////////////////////////////////////////// + +[[extension-es-cassandra,Cassandra EntityStore]] += Cassandra EntityStore = + +[devstatus] +-------------- +source=extensions/entitystore-cassandra/dev-status.xml +-------------- + +EntityStore service backed by a http://cassandra.apache.org/ in which Entity state is stored in single rows of a +single table. + +include::../../build/docs/buildinfo/artifact.txt[] + +== Assembly == + +Assembly is done using the provided Assembler: + +[snippet,java] +---- +source=extensions/entitystore-cassandra/src/test/java/org/apache/polygene/entitystore/cassandra/CassandraMapEntityStoreTest.java +tag=assembly +---- + +=== Custom Cluster Client === +There are many options in Apache Cassandra on how the cluster is set up and how to connect to it. Instead of mapping +all possible features, a ClusterBuilder type, which by default sets it up according to this page. By overriding the +DefaultBuilder mixin, on the CassandraCluster composite, it is possible to provide your configuration. + +[snippet,java] +---- +source=extensions/entitystore-cassandra/src/test/java/org/apache/polygene/entitystore/cassandra/DocSupport.java +tag=assembly +---- + +And we then have a choice to override any of the provided extension points in DefaultBuilder. For instance; + +[snippet,java] +---- +source=extensions/entitystore-cassandra/src/test/java/org/apache/polygene/entitystore/cassandra/DocSupport.java +tag=builder +---- + +Of course, it is possible to simply override the entire Mixin and not subtype it at all. + +== Configuration == + +Here are the configuration properties for the Cassandra EntityStore: + +[snippet,java] +---- +source=extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/CassandraEntityStoreConfiguration.java +tag=config +---- + +== Cassandra Authentication == +It is possible to configure Cassandra with many types of authentication. User/password is provided in this library's +configuration, and for more advanced setups, you need to provide an implementation of com.datastax.driver.core.AuthProvider +as a Polygene service (and visible to the ClusterBuilder as usual). + +== Cassandra Keyspace == +The Cassandra EntityStore can either use an existing Cassandra Keyspace, which is the default, OR create its own +keyspace. + +The CassandraEntityStoreConfiguration#keyspaceName() defines the name of the keyspace to use, or to be created. If not +defined, then the default name is "polygene". + +The CassandraEntityStoreConfiguration#createIfMissing() defines if new missing resources should be created or an +Exception should be thrown if missing. + +If the keyspace is created, then the CassandraEntityStoreConfiguration#replicationFactor() will define the replication +factor, defaults to 3, and the command to create the keyspace is; + +[source] +----------------- + CREATE KEYSPACE <keyspaceName> WITH replication = {'class':'SimpleStrategy', 'replication_factor' : <replicationFactor> }; +----------------- + +== Polygene's Cassandra Table == + +Polygene will store all entities in a single Cassandra TABLE + +[source] +----------------- + CREATE TABLE <tableName> ( + id text, + version text, + appversion text, + storeversion text, + usecase text, + modified timestamp, + properties map<string,string> + assocs map<string,string> + manyassocs map<string,string> + namedassocs map<string,string> + PRIMARY KEY ( id ) + ); +----------------- http://git-wip-us.apache.org/repos/asf/polygene-java/blob/ba600a55/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/CassandraCluster.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/CassandraCluster.java b/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/CassandraCluster.java index a8d3d29..701471c 100644 --- a/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/CassandraCluster.java +++ b/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/CassandraCluster.java @@ -38,14 +38,16 @@ */ package org.apache.polygene.entitystore.cassandra; +import com.datastax.driver.core.AuthProvider; import com.datastax.driver.core.Cluster; import com.datastax.driver.core.KeyspaceMetadata; import com.datastax.driver.core.PreparedStatement; import com.datastax.driver.core.Session; +import org.apache.polygene.api.common.Optional; import org.apache.polygene.api.configuration.Configuration; +import org.apache.polygene.api.injection.scope.Service; import org.apache.polygene.api.injection.scope.This; import org.apache.polygene.api.mixin.Mixins; -import org.apache.polygene.api.service.ServiceActivation; import org.apache.polygene.spi.entitystore.EntityStoreException; @Mixins( CassandraCluster.Mixin.class ) @@ -55,11 +57,11 @@ public interface CassandraCluster String DEFAULT_KEYSPACE_NAME = "polygene"; String DEFAULT_TABLE_NAME = "entitystore"; String IDENTITY_COLUMN = "id"; + String STORE_VERSION_COLUMN = "storeversion"; String VERSION_COLUMN = "version"; + String APP_VERSION_COLUMN = "appversion"; String USECASE_COLUMN = "usecase"; String LASTMODIFIED_COLUMN = "modified"; - String APP_VERSION_COLUMN = "appversion"; - String STORE_VERSION_COLUMN = "storeversion"; String TYPE_COLUMN = "type"; String PROPERTIES_COLUMN = "props"; String ASSOCIATIONS_COLUMN = "assocs"; @@ -78,12 +80,25 @@ public interface CassandraCluster String keyspaceName(); + void activate() + throws Exception; + + void passivate() + throws Exception; + class Mixin - implements ServiceActivation, CassandraCluster + implements CassandraCluster { @This private Configuration<CassandraEntityStoreConfiguration> configuration; + @Service + @Optional + private AuthProvider authProvider; + + @This + private ClusterBuilder clusterBuilder; + private Cluster cluster; private Session session; private String keyspaceName; @@ -132,20 +147,12 @@ public interface CassandraCluster return tableName; } - @Override - public void activateService() + public void activate() throws Exception { configuration.refresh(); CassandraEntityStoreConfiguration config = configuration.get(); - - String[] hostNames = config.hostnames().get().split( "," ); - Cluster.Builder builder = - Cluster.builder() - .withClusterName( "myCluster" ) - .addContactPoints( hostNames ) - .withCredentials( config.username().get(), config.password().get() ); - cluster = builder.build(); + cluster = clusterBuilder.build(config); keyspaceName = config.keySpace().get(); if( keyspaceName == null ) { @@ -233,8 +240,7 @@ public interface CassandraCluster } } - @Override - public void passivateService() + public void passivate() throws Exception { cluster.close(); http://git-wip-us.apache.org/repos/asf/polygene-java/blob/ba600a55/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/CassandraEntityStoreConfiguration.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/CassandraEntityStoreConfiguration.java b/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/CassandraEntityStoreConfiguration.java index f0ab16e..f212d78 100644 --- a/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/CassandraEntityStoreConfiguration.java +++ b/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/CassandraEntityStoreConfiguration.java @@ -43,6 +43,13 @@ public interface CassandraEntityStoreConfiguration @Optional Property<String> hostnames(); + /** The name of the cluster to connect to. + * + * @return The configured cluster name. Default: "polygene-cluster" + */ + @Optional + Property<String> clusterName(); + /** The replication factor to be used, if a KEYSPACE is created. * * @return The replication factor to use in the keyspace if a keyspace is created. Default: 3 http://git-wip-us.apache.org/repos/asf/polygene-java/blob/ba600a55/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/CassandraEntityStoreMixin.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/CassandraEntityStoreMixin.java b/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/CassandraEntityStoreMixin.java index 159bc9c..798b63b 100644 --- a/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/CassandraEntityStoreMixin.java +++ b/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/CassandraEntityStoreMixin.java @@ -22,6 +22,7 @@ package org.apache.polygene.entitystore.cassandra; import com.datastax.driver.core.BoundStatement; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Row; +import com.datastax.driver.core.Session; import com.datastax.driver.core.TypeTokens; import com.google.common.reflect.TypeToken; import java.time.Instant; @@ -83,7 +84,7 @@ import static org.apache.polygene.entitystore.cassandra.CassandraCluster.VERSION * MongoDB implementation of MapEntityStore. */ public class CassandraEntityStoreMixin - implements EntityStore, EntityStoreSPI + implements EntityStore, EntityStoreSPI, ServiceActivation { @This @@ -407,19 +408,34 @@ public class CassandraEntityStoreMixin @Override public Stream<EntityState> entityStates( ModuleDescriptor module ) { - ResultSet resultSet = cluster.session().execute( "SELECT " - + IDENTITY_COLUMN + ", " - + VERSION_COLUMN + ", " - + APP_VERSION_COLUMN + ", " - + STORE_VERSION_COLUMN + ", " - + LASTMODIFIED_COLUMN + ", " - + USECASE_COLUMN + ", " - + PROPERTIES_COLUMN + ", " - + ASSOCIATIONS_COLUMN + ", " - + MANYASSOCIATIONS_COLUMN + ", " - + NAMEDASSOCIATIONS_COLUMN - + " FROM " + cluster.tableName() ); + Session session = cluster.session(); + String tableName = cluster.tableName(); + ResultSet resultSet = session.execute( "SELECT " + + IDENTITY_COLUMN + ", " + + VERSION_COLUMN + ", " + + APP_VERSION_COLUMN + ", " + + STORE_VERSION_COLUMN + ", " + + LASTMODIFIED_COLUMN + ", " + + USECASE_COLUMN + ", " + + PROPERTIES_COLUMN + ", " + + ASSOCIATIONS_COLUMN + ", " + + MANYASSOCIATIONS_COLUMN + ", " + + NAMEDASSOCIATIONS_COLUMN + + " FROM " + tableName ); return stream(resultSet.spliterator(), false).map( row -> deserialize( row, module )); } + @Override + public void activateService() + throws Exception + { + cluster.activate(); + } + + @Override + public void passivateService() + throws Exception + { + cluster.passivate(); + } } http://git-wip-us.apache.org/repos/asf/polygene-java/blob/ba600a55/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/ClusterBuilder.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/ClusterBuilder.java b/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/ClusterBuilder.java new file mode 100644 index 0000000..df01331 --- /dev/null +++ b/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/ClusterBuilder.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.entitystore.cassandra; + +import com.datastax.driver.core.Cluster; +import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Collectors; +import org.apache.polygene.api.mixin.Mixins; + +@Mixins(ClusterBuilder.DefaultBuilder.class) +public interface ClusterBuilder +{ + String DEFAULT_HOST_PORT = "localhost:9042"; + + Cluster build(CassandraEntityStoreConfiguration config); + + class DefaultBuilder + implements ClusterBuilder + { + + protected CassandraEntityStoreConfiguration config; + + @Override + public Cluster build( CassandraEntityStoreConfiguration config ) + { + this.config = config; + String clusterName = clusterName( config ); + Collection<InetSocketAddress> connectionPoints = cassandraConnectionPoints(); + Cluster.Builder builder = + Cluster.builder() + .withClusterName( clusterName ) + .addContactPointsWithPorts(connectionPoints) + .withCredentials( username(), password() ); + builder = customConfiguration(builder); + return builder.build(); + } + + protected String clusterName( CassandraEntityStoreConfiguration config ) + { + String clusterName = config.clusterName().get(); + if( clusterName == null ) + { + clusterName = "polygene-cluster"; + } + return clusterName; + } + + protected String username() + { + return config.username().get(); + } + + protected String password() + { + return config.password().get(); + } + + protected Collection<InetSocketAddress> cassandraConnectionPoints() + { + String hostnames = hostnames(); + return Arrays.stream( hostnames.split( "(,| )" ) ) + .map( text -> + { + String[] strings = text.split( ":" ); + return new InetSocketAddress( strings[ 0 ], Integer.parseInt( strings[ 1 ] ) ); + } + ) + .collect( Collectors.toList() ); + } + + protected String hostnames() + { + String hostnames = config.hostnames().get(); + if( hostnames == null ) + { + hostnames = DEFAULT_HOST_PORT; + } + return hostnames; + } + + protected Cluster.Builder customConfiguration( Cluster.Builder builder ) + { + return builder; + } + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/ba600a55/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/package.html ---------------------------------------------------------------------- diff --git a/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/package.html b/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/package.html index 2a0077e..f5fbec6 100644 --- a/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/package.html +++ b/extensions/entitystore-cassandra/src/main/java/org/apache/polygene/entitystore/cassandra/package.html @@ -20,24 +20,50 @@ <html> <body> <h2>Casssandra EntityStore.</h2> - <p> - The Cassandra EntityStore leverages the CQL 'map' type, which is the "newer" way of doing variable-wdith rows. - </p> +<h3>Polygene's Cassandra Keyspace</h3> <p> - For properties, there is a map called '_props' with string key (the name of the property) and string value, - which is the serialized state of the property. + The Cassandra EntityStore can either use an existing Cassandra Keyspace, which is the default, OR create its own + keyspace. </p> <p> - For associations, there is map called '_assocs' with string key (the name of the association) and string value, - which is the entity reference in toString() format. + The {@code CassandraEntityStoreConfiguration#keyspaceName()} defines the name of the keyspace to use, or to be + created. + If not defined, then the default name is "polygene". </p> <p> - For manyassociations, there is map called '_manyassocs' with string key (the name of the manyassociation) and - list of strings value, which is the list of entity references in toString() format. + The {@code CassandraEntityStoreConfiguration#createIfMissing()} defines if new missing resources should + be created or an Exception should be thrown if missing. </p> <p> - For namedassociations, there is map called '_manyassocs' with string key (the name of the association) and - map of strings value, which is the name as the key and the value is the entity reference in toString() format. + If it is created, then the {@code CassandraEntityStoreConfiguration#replicationFactor()} will define the replication + factor, and the command to create the keyspace is; </p> +<code> + <pre> + CREATE KEYSPACE <keyspaceName> WITH replication = {'class':'SimpleStrategy', 'replication_factor' : <replicationFactor> }; + </pre> +</code> +<h3>Polygene's Cassandra Table</h3> +<p> + Polygene will store all entities in a single Cassandra TABLE +</p> +<code> + <pre> + CREATE TABLE <tableName> ( + id text, + version text, + appversion text, + storeversion text, + usecase text, + modified timestamp, + properties map<string,string> + assocs map<string,string> + manyassocs map<string,string> + namedassocs map<string,string> + PRIMARY KEY ( id ) + ); + </pre> +</code> + </body> </html> http://git-wip-us.apache.org/repos/asf/polygene-java/blob/ba600a55/extensions/entitystore-cassandra/src/test/java/org/apache/polygene/entitystore/cassandra/DocSupport.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-cassandra/src/test/java/org/apache/polygene/entitystore/cassandra/DocSupport.java b/extensions/entitystore-cassandra/src/test/java/org/apache/polygene/entitystore/cassandra/DocSupport.java new file mode 100644 index 0000000..e1f281a --- /dev/null +++ b/extensions/entitystore-cassandra/src/test/java/org/apache/polygene/entitystore/cassandra/DocSupport.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.polygene.entitystore.cassandra; + +import org.apache.polygene.api.injection.scope.Structure; +import org.apache.polygene.api.structure.Application; +import org.apache.polygene.bootstrap.Assembler; +import org.apache.polygene.bootstrap.AssemblyException; +import org.apache.polygene.bootstrap.ModuleAssembly; + + +public class DocSupport + implements Assembler +{ +// START-SNIPPET: assembly + @Override + public void assemble( ModuleAssembly module ) + throws AssemblyException + { +// END-SNIPPET: assembly + module.services( ClusterBuilder.class ).withMixins( MyClusterBuilder.class ); +// START-SNIPPET: assembly + } +// END-SNIPPET: assembly + +// START-SNIPPET: builder + public class MyClusterBuilder extends ClusterBuilder.DefaultBuilder + implements ClusterBuilder + { + @Structure + private Application application; + + @Override + protected String hostnames() + { + switch( application.mode() ) + { + case development: + return "localhost:9042"; + case staging: + return "cassandra.staging:9042"; + case production: + return "cassandra1.prod:9042,cassandra2.prod:9042,cassandra3.prod:9042"; + case test: + default: + return "cassandra.test:9042"; + } + } + } +// END-SNIPPET: builder +}
