From af0273b864dd6b3f1ca2ad6bb246533ae7940890 Mon Sep 17 00:00:00 2001 From: Matthew Adams Date: Mon, 25 Nov 2013 18:32:25 -0600 Subject: [PATCH] spring-cassandra compiling --- attic/cassandra/conf/cassandra.yaml | 664 +++++++++ attic/get-and-start-cassandra | 23 + .../core/CachedPreparedStatementCreator.java | 80 ++ .../cassandra/core/CassandraOperations.java | 401 ++++++ .../cassandra/core/CassandraTemplate.java | 563 ++++++++ .../cassandra/core/CqlParameter.java | 139 ++ .../cassandra/core/CqlParameterValue.java | 68 + .../cassandra/core/CqlProvider.java | 26 + .../cassandra/core/HostMapper.java | 13 + .../cassandra/core/KeyType.java | 19 + .../cassandra/core/Keyspace.java | 49 + .../cassandra/core/Ordering.java | 32 + .../core/PreparedStatementBinder.java | 30 + .../core/PreparedStatementCallback.java | 31 + .../core/PreparedStatementCreator.java | 41 + .../core/PreparedStatementCreatorFactory.java | 202 +++ .../core/PreparedStatementCreatorImpl.java | 73 + .../cassandra/core/ResultSetExtractor.java | 11 + .../core/ResultSetFutureExtractor.java | 11 + .../cassandra/core/RingMember.java | 43 + .../cassandra/core/RingMemberHostMapper.java | 54 + .../cassandra/core/RowCallback.java | 29 + .../cassandra/core/RowCallbackHandler.java | 10 + .../cassandra/core/RowMapper.java | 10 + .../cassandra/core/SessionCallback.java | 40 + .../cassandra/core/SessionFactoryBean.java | 87 ++ .../core/SimplePreparedStatementCreator.java | 58 + .../cassandra/core/cql/CqlStringUtils.java | 117 ++ .../cql/generator/AddColumnCqlGenerator.java | 22 + .../generator/AlterColumnCqlGenerator.java | 22 + .../cql/generator/AlterTableCqlGenerator.java | 106 ++ .../generator/ColumnChangeCqlGenerator.java | 35 + .../generator/CreateTableCqlGenerator.java | 173 +++ .../cql/generator/DropColumnCqlGenerator.java | 21 + .../cql/generator/DropTableCqlGenerator.java | 22 + .../core/cql/generator/TableCqlGenerator.java | 65 + .../cql/generator/TableNameCqlGenerator.java | 36 + .../generator/TableOptionsCqlGenerator.java | 65 + .../core/keyspace/AddColumnSpecification.java | 10 + .../keyspace/AlterColumnSpecification.java | 10 + .../keyspace/AlterTableSpecification.java | 51 + .../keyspace/ColumnChangeSpecification.java | 26 + .../core/keyspace/ColumnSpecification.java | 167 +++ .../ColumnTypeChangeSpecification.java | 24 + .../keyspace/CreateTableSpecification.java | 34 + .../core/keyspace/DefaultOption.java | 157 ++ .../core/keyspace/DefaultTableDescriptor.java | 17 + .../keyspace/DropColumnSpecification.java | 8 + .../core/keyspace/DropTableSpecification.java | 24 + .../cassandra/core/keyspace/Option.java | 61 + .../core/keyspace/TableDescriptor.java | 52 + .../core/keyspace/TableNameSpecification.java | 38 + .../core/keyspace/TableOperations.java | 34 + .../cassandra/core/keyspace/TableOption.java | 287 ++++ .../keyspace/TableOptionsSpecification.java | 93 ++ .../core/keyspace/TableSpecification.java | 162 +++ .../cassandra/core/util/MapBuilder.java | 126 ++ .../cassandra/support/CassandraAccessor.java | 76 + .../support/CassandraExceptionTranslator.java | 136 ++ .../CassandraAuthenticationException.java | 42 + .../CassandraConnectionFailureException.java | 45 + ...nsufficientReplicasAvailableException.java | 51 + .../exception/CassandraInternalException.java | 37 + ...aInvalidConfigurationInQueryException.java | 38 + .../CassandraInvalidQueryException.java | 37 + .../CassandraKeyspaceExistsException.java | 35 + .../CassandraQuerySyntaxException.java | 37 + .../CassandraReadTimeoutException.java | 40 + ...CassandraSchemaElementExistsException.java | 50 + .../CassandraTableExistsException.java | 35 + .../CassandraTraceRetrievalException.java | 37 + .../exception/CassandraTruncateException.java | 37 + .../CassandraTypeMismatchException.java | 37 + .../CassandraUnauthorizedException.java | 33 + .../CassandraUncategorizedException.java | 33 + .../CassandraWriteTimeoutException.java | 40 + ...tractEmbeddedCassandraIntegrationTest.java | 71 + .../CqlTableSpecificationAssertions.java | 144 ++ ...eateTableCqlGeneratorIntegrationTests.java | 55 + .../unit/core/cql/CqlStringUtilsTest.java | 19 + .../AlterTableCqlGeneratorTests.java | 65 + .../CreateTableCqlGeneratorTests.java | 100 ++ .../generator/DropTableCqlGeneratorTests.java | 54 + .../TableOperationCqlGeneratorTest.java | 36 + .../test/unit/core/keyspace/OptionTest.java | 104 ++ .../CassandraExceptionTranslatorTest.java | 71 + .../test/resources/cassandra-keyspace.yaml | 3 + .../src/test/resources/cassandra.yaml | 690 +++++++++ .../cassandraOperationsTest-cql-dataload.cql | 3 + .../src/test/resources/cql-dataload.cql | 3 + .../src/test/resources/log4j.properties | 6 + .../CassandraNamespaceTests-context.xml | 55 + .../integration/config/cassandra.properties | 7 + spring-cassandra/template.mf | 29 + .../data/cassandra/Constants.java | 24 + .../AbstractCassandraConfiguration.java | 203 +++ .../data/cassandra/config/BeanNames.java | 31 + .../config/CassandraClusterParser.java | 118 ++ .../config/CassandraKeyspaceParser.java | 127 ++ .../config/CassandraNamespaceHandler.java | 36 + .../config/CassandraSessionParser.java | 69 + .../cassandra/config/CompressionType.java | 25 + .../cassandra/config/KeyspaceAttributes.java | 107 ++ .../config/PoolingOptionsConfig.java | 62 + .../cassandra/config/SocketOptionsConfig.java | 89 ++ .../cassandra/config/TableAttributes.java | 49 + .../convert/AbstractCassandraConverter.java | 67 + .../cassandra/convert/CassandraConverter.java | 30 + .../CassandraPropertyValueProvider.java | 95 ++ .../convert/MappingCassandraConverter.java | 278 ++++ .../convert/RowReaderPropertyAccessor.java | 83 ++ .../core/CassandraAdminOperations.java | 79 + .../core/CassandraAdminTemplate.java | 243 ++++ .../core/CassandraClusterFactoryBean.java | 263 ++++ .../core/CassandraDataOperations.java | 619 ++++++++ .../cassandra/core/CassandraDataTemplate.java | 1269 +++++++++++++++++ .../core/CassandraKeyspaceFactoryBean.java | 341 +++++ .../data/cassandra/core/CassandraValue.java | 45 + .../core/ClassNameToTableNameConverter.java | 6 + .../core/ColumnNameToFieldNameConverter.java | 6 + .../data/cassandra/core/ConsistencyLevel.java | 28 + .../core/ConsistencyLevelResolver.java | 78 + .../core/FieldNameToColumnNameConverter.java | 6 + .../data/cassandra/core/QueryOptions.java | 106 ++ .../data/cassandra/core/ReadRowCallback.java | 47 + .../data/cassandra/core/RetryPolicy.java | 28 + .../cassandra/core/RetryPolicyResolver.java | 67 + .../core/TableNameToClassNameConverter.java | 6 + .../exception/EntityWriterException.java | 48 + .../BasicCassandraPersistentEntity.java | 120 ++ .../BasicCassandraPersistentProperty.java | 188 +++ .../CachingCassandraPersistentProperty.java | 103 ++ .../mapping/CassandraMappingContext.java | 85 ++ .../mapping/CassandraPersistentEntity.java | 34 + .../mapping/CassandraPersistentProperty.java | 57 + .../mapping/CassandraSimpleTypes.java | 98 ++ .../data/cassandra/mapping/Column.java | 23 + .../data/cassandra/mapping/ColumnId.java | 33 + .../cassandra/mapping/CompositeRowId.java | 44 + .../mapping/DataTypeInformation.java | 74 + .../data/cassandra/mapping/Index.java | 35 + .../data/cassandra/mapping/Qualify.java | 37 + .../data/cassandra/mapping/RowId.java | 34 + .../data/cassandra/mapping/Table.java | 39 + .../repository/CassandraRepository.java | 29 + .../data/cassandra/repository/Query.java | 42 + .../cassandra/util/CassandraNamingUtils.java | 36 + .../data/cassandra/util/CqlUtils.java | 467 ++++++ .../main/resources/META-INF/spring.handlers | 1 + .../main/resources/META-INF/spring.schemas | 2 + .../main/resources/META-INF/spring.tooling | 4 + .../cassandra/config/spring-cassandra-1.0.xsd | 454 ++++++ .../cassandra/config/spring-cassandra.gif | Bin 0 -> 581 bytes .../config/CassandraNamespaceTests.java | 58 + .../test/integration/config/DriverTests.java | 50 + .../test/integration/config/TestConfig.java | 80 ++ ...andraPersistentEntityIntegrationTests.java | 126 ++ ...draPersistentPropertyIntegrationTests.java | 117 ++ .../test/integration/table/Book.java | 104 ++ .../test/integration/table/Comment.java | 112 ++ .../test/integration/table/LogEntry.java | 89 ++ .../test/integration/table/Notification.java | 108 ++ .../test/integration/table/Post.java | 122 ++ .../test/integration/table/Timeline.java | 86 ++ .../test/integration/table/User.java | 158 ++ .../test/integration/table/UserAlter.java | 161 +++ .../template/CassandraAdminTest.java | 106 ++ .../template/CassandraDataOperationsTest.java | 940 ++++++++++++ .../template/CassandraOperationsTest.java | 299 ++++ .../test/resources/cassandra-keyspace.yaml | 3 + .../src/test/resources/cassandra.yaml | 690 +++++++++ .../cassandraOperationsTest-cql-dataload.cql | 3 + .../src/test/resources/cql-dataload.cql | 3 + .../src/test/resources/log4j.properties | 6 + .../CassandraNamespaceTests-context.xml | 55 + .../integration/config/cassandra.properties | 7 + spring-data-cassandra/template.mf | 30 + 177 files changed, 17593 insertions(+) create mode 100644 attic/cassandra/conf/cassandra.yaml create mode 100755 attic/get-and-start-cassandra create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/CachedPreparedStatementCreator.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/CassandraOperations.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/CassandraTemplate.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlParameter.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlParameterValue.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlProvider.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/HostMapper.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/KeyType.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/Keyspace.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/Ordering.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementBinder.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementCallback.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementCreator.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementCreatorFactory.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementCreatorImpl.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/ResultSetExtractor.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/ResultSetFutureExtractor.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/RingMember.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/RingMemberHostMapper.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/RowCallback.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/RowCallbackHandler.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/RowMapper.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/SessionCallback.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/SessionFactoryBean.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/SimplePreparedStatementCreator.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/CqlStringUtils.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AddColumnCqlGenerator.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AlterColumnCqlGenerator.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AlterTableCqlGenerator.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/ColumnChangeCqlGenerator.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/CreateTableCqlGenerator.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropColumnCqlGenerator.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropTableCqlGenerator.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/TableCqlGenerator.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/TableNameCqlGenerator.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/TableOptionsCqlGenerator.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/AddColumnSpecification.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/AlterColumnSpecification.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/AlterTableSpecification.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/ColumnChangeSpecification.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/ColumnSpecification.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/ColumnTypeChangeSpecification.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/CreateTableSpecification.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DefaultOption.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DefaultTableDescriptor.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DropColumnSpecification.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DropTableSpecification.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/Option.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableDescriptor.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableNameSpecification.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableOperations.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableOption.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableOptionsSpecification.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableSpecification.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/core/util/MapBuilder.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/CassandraAccessor.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/CassandraExceptionTranslator.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraAuthenticationException.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraConnectionFailureException.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraInsufficientReplicasAvailableException.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraInternalException.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraInvalidConfigurationInQueryException.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraInvalidQueryException.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraKeyspaceExistsException.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraQuerySyntaxException.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraReadTimeoutException.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraSchemaElementExistsException.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraTableExistsException.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraTraceRetrievalException.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraTruncateException.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraTypeMismatchException.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraUnauthorizedException.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraUncategorizedException.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraWriteTimeoutException.java create mode 100644 spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/AbstractEmbeddedCassandraIntegrationTest.java create mode 100644 spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/CqlTableSpecificationAssertions.java create mode 100644 spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/CreateTableCqlGeneratorIntegrationTests.java create mode 100644 spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/CqlStringUtilsTest.java create mode 100644 spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/AlterTableCqlGeneratorTests.java create mode 100644 spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/CreateTableCqlGeneratorTests.java create mode 100644 spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/DropTableCqlGeneratorTests.java create mode 100644 spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/TableOperationCqlGeneratorTest.java create mode 100644 spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/keyspace/OptionTest.java create mode 100644 spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/support/CassandraExceptionTranslatorTest.java create mode 100644 spring-cassandra/src/test/resources/cassandra-keyspace.yaml create mode 100644 spring-cassandra/src/test/resources/cassandra.yaml create mode 100644 spring-cassandra/src/test/resources/cassandraOperationsTest-cql-dataload.cql create mode 100644 spring-cassandra/src/test/resources/cql-dataload.cql create mode 100644 spring-cassandra/src/test/resources/log4j.properties create mode 100644 spring-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/CassandraNamespaceTests-context.xml create mode 100644 spring-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/cassandra.properties create mode 100644 spring-cassandra/template.mf create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/Constants.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/AbstractCassandraConfiguration.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/BeanNames.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraClusterParser.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraKeyspaceParser.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraNamespaceHandler.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraSessionParser.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CompressionType.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/KeyspaceAttributes.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/PoolingOptionsConfig.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/SocketOptionsConfig.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/TableAttributes.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/AbstractCassandraConverter.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraConverter.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraPropertyValueProvider.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/RowReaderPropertyAccessor.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminOperations.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminTemplate.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraClusterFactoryBean.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraDataOperations.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraDataTemplate.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraKeyspaceFactoryBean.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraValue.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ClassNameToTableNameConverter.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ColumnNameToFieldNameConverter.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ConsistencyLevel.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ConsistencyLevelResolver.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/FieldNameToColumnNameConverter.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/QueryOptions.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReadRowCallback.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/RetryPolicy.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/RetryPolicyResolver.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/TableNameToClassNameConverter.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/exception/EntityWriterException.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntity.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentProperty.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CachingCassandraPersistentProperty.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraMappingContext.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentEntity.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentProperty.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraSimpleTypes.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Column.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/ColumnId.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CompositeRowId.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DataTypeInformation.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Index.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Qualify.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/RowId.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Table.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/CassandraRepository.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/Query.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/util/CassandraNamingUtils.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/util/CqlUtils.java create mode 100644 spring-data-cassandra/src/main/resources/META-INF/spring.handlers create mode 100644 spring-data-cassandra/src/main/resources/META-INF/spring.schemas create mode 100644 spring-data-cassandra/src/main/resources/META-INF/spring.tooling create mode 100644 spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra-1.0.xsd create mode 100644 spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra.gif create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/config/CassandraNamespaceTests.java create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/config/DriverTests.java create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/config/TestConfig.java create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/BasicCassandraPersistentEntityIntegrationTests.java create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/BasicCassandraPersistentPropertyIntegrationTests.java create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Book.java create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Comment.java create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/LogEntry.java create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Notification.java create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Post.java create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Timeline.java create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/User.java create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/UserAlter.java create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraAdminTest.java create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraDataOperationsTest.java create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraOperationsTest.java create mode 100644 spring-data-cassandra/src/test/resources/cassandra-keyspace.yaml create mode 100644 spring-data-cassandra/src/test/resources/cassandra.yaml create mode 100644 spring-data-cassandra/src/test/resources/cassandraOperationsTest-cql-dataload.cql create mode 100644 spring-data-cassandra/src/test/resources/cql-dataload.cql create mode 100644 spring-data-cassandra/src/test/resources/log4j.properties create mode 100644 spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/CassandraNamespaceTests-context.xml create mode 100644 spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/cassandra.properties create mode 100644 spring-data-cassandra/template.mf diff --git a/attic/cassandra/conf/cassandra.yaml b/attic/cassandra/conf/cassandra.yaml new file mode 100644 index 000000000..c6c96f715 --- /dev/null +++ b/attic/cassandra/conf/cassandra.yaml @@ -0,0 +1,664 @@ +# Cassandra storage config YAML + +# NOTE: +# See http://wiki.apache.org/cassandra/StorageConfiguration for +# full explanations of configuration directives +# /NOTE + +# The name of the cluster. This is mainly used to prevent machines in +# one logical cluster from joining another. +cluster_name: 'Test Cluster' + +# This defines the number of tokens randomly assigned to this node on the ring +# The more tokens, relative to other nodes, the larger the proportion of data +# that this node will store. You probably want all nodes to have the same number +# of tokens assuming they have equal hardware capability. +# +# If you leave this unspecified, Cassandra will use the default of 1 token for legacy compatibility, +# and will use the initial_token as described below. +# +# Specifying initial_token will override this setting. +# +# If you already have a cluster with 1 token per node, and wish to migrate to +# multiple tokens per node, see http://wiki.apache.org/cassandra/Operations +num_tokens: 256 + +# initial_token allows you to specify tokens manually. While you can use # it with +# vnodes (num_tokens > 1, above) -- in which case you should provide a +# comma-separated list -- it's primarily used when adding nodes # to legacy clusters +# that do not have vnodes enabled. +# initial_token: + +# See http://wiki.apache.org/cassandra/HintedHandoff +hinted_handoff_enabled: true +# this defines the maximum amount of time a dead host will have hints +# generated. After it has been dead this long, new hints for it will not be +# created until it has been seen alive and gone down again. +max_hint_window_in_ms: 10800000 # 3 hours +# Maximum throttle in KBs per second, per delivery thread. This will be +# reduced proportionally to the number of nodes in the cluster. (If there +# are two nodes in the cluster, each delivery thread will use the maximum +# rate; if there are three, each will throttle to half of the maximum, +# since we expect two nodes to be delivering hints simultaneously.) +hinted_handoff_throttle_in_kb: 1024 +# Number of threads with which to deliver hints; +# Consider increasing this number when you have multi-dc deployments, since +# cross-dc handoff tends to be slower +max_hints_delivery_threads: 2 + +# The following setting populates the page cache on memtable flush and compaction +# WARNING: Enable this setting only when the whole node's data fits in memory. +# Defaults to: false +# populate_io_cache_on_flush: false + +# Authentication backend, implementing IAuthenticator; used to identify users +# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthenticator, +# PasswordAuthenticator}. +# +# - AllowAllAuthenticator performs no checks - set it to disable authentication. +# - PasswordAuthenticator relies on username/password pairs to authenticate +# users. It keeps usernames and hashed passwords in system_auth.credentials table. +# Please increase system_auth keyspace replication factor if you use this authenticator. +authenticator: AllowAllAuthenticator + +# Authorization backend, implementing IAuthorizer; used to limit access/provide permissions +# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthorizer, +# CassandraAuthorizer}. +# +# - AllowAllAuthorizer allows any action to any user - set it to disable authorization. +# - CassandraAuthorizer stores permissions in system_auth.permissions table. Please +# increase system_auth keyspace replication factor if you use this authorizer. +authorizer: AllowAllAuthorizer + +# Validity period for permissions cache (fetching permissions can be an +# expensive operation depending on the authorizer, CassandraAuthorizer is +# one example). Defaults to 2000, set to 0 to disable. +# Will be disabled automatically for AllowAllAuthorizer. +permissions_validity_in_ms: 2000 + +# The partitioner is responsible for distributing rows (by key) across +# nodes in the cluster. Any IPartitioner may be used, including your +# own as long as it is on the classpath. Out of the box, Cassandra +# provides org.apache.cassandra.dht.{Murmur3Partitioner, RandomPartitioner +# ByteOrderedPartitioner, OrderPreservingPartitioner (deprecated)}. +# +# - RandomPartitioner distributes rows across the cluster evenly by md5. +# This is the default prior to 1.2 and is retained for compatibility. +# - Murmur3Partitioner is similar to RandomPartioner but uses Murmur3_128 +# Hash Function instead of md5. When in doubt, this is the best option. +# - ByteOrderedPartitioner orders rows lexically by key bytes. BOP allows +# scanning rows in key order, but the ordering can generate hot spots +# for sequential insertion workloads. +# - OrderPreservingPartitioner is an obsolete form of BOP, that stores +# - keys in a less-efficient format and only works with keys that are +# UTF8-encoded Strings. +# - CollatingOPP collates according to EN,US rules rather than lexical byte +# ordering. Use this as an example if you need custom collation. +# +# See http://wiki.apache.org/cassandra/Operations for more on +# partitioners and token selection. +partitioner: org.apache.cassandra.dht.Murmur3Partitioner + +# Directories where Cassandra should store data on disk. Cassandra +# will spread data evenly across them, subject to the granularity of +# the configured compaction strategy. +data_file_directories: + - .cassandra/var/lib/cassandra/data + +# commit log +commitlog_directory: .cassandra/var/lib/cassandra/commitlog + +# policy for data disk failures: +# stop: shut down gossip and Thrift, leaving the node effectively dead, but +# can still be inspected via JMX. +# best_effort: stop using the failed disk and respond to requests based on +# remaining available sstables. This means you WILL see obsolete +# data at CL.ONE! +# ignore: ignore fatal errors and let requests fail, as in pre-1.2 Cassandra +disk_failure_policy: stop + +# Maximum size of the key cache in memory. +# +# Each key cache hit saves 1 seek and each row cache hit saves 2 seeks at the +# minimum, sometimes more. The key cache is fairly tiny for the amount of +# time it saves, so it's worthwhile to use it at large numbers. +# The row cache saves even more time, but must contain the entire row, +# so it is extremely space-intensive. It's best to only use the +# row cache if you have hot rows or static rows. +# +# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup. +# +# Default value is empty to make it "auto" (min(5% of Heap (in MB), 100MB)). Set to 0 to disable key cache. +key_cache_size_in_mb: + +# Duration in seconds after which Cassandra should +# save the key cache. Caches are saved to saved_caches_directory as +# specified in this configuration file. +# +# Saved caches greatly improve cold-start speeds, and is relatively cheap in +# terms of I/O for the key cache. Row cache saving is much more expensive and +# has limited use. +# +# Default is 14400 or 4 hours. +key_cache_save_period: 14400 + +# Number of keys from the key cache to save +# Disabled by default, meaning all keys are going to be saved +# key_cache_keys_to_save: 100 + +# Maximum size of the row cache in memory. +# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup. +# +# Default value is 0, to disable row caching. +row_cache_size_in_mb: 0 + +# Duration in seconds after which Cassandra should +# safe the row cache. Caches are saved to saved_caches_directory as specified +# in this configuration file. +# +# Saved caches greatly improve cold-start speeds, and is relatively cheap in +# terms of I/O for the key cache. Row cache saving is much more expensive and +# has limited use. +# +# Default is 0 to disable saving the row cache. +row_cache_save_period: 0 + +# Number of keys from the row cache to save +# Disabled by default, meaning all keys are going to be saved +# row_cache_keys_to_save: 100 + +# The off-heap memory allocator. Affects storage engine metadata as +# well as caches. Experiments show that JEMAlloc saves some memory +# than the native GCC allocator (i.e., JEMalloc is more +# fragmentation-resistant). +# +# Supported values are: NativeAllocator, JEMallocAllocator +# +# If you intend to use JEMallocAllocator you have to install JEMalloc as library and +# modify cassandra-env.sh as directed in the file. +# +# Defaults to NativeAllocator +# memory_allocator: NativeAllocator + +# saved caches +saved_caches_directory: .cassandra/var/lib/cassandra/saved_caches + +# commitlog_sync may be either "periodic" or "batch." +# When in batch mode, Cassandra won't ack writes until the commit log +# has been fsynced to disk. It will wait up to +# commitlog_sync_batch_window_in_ms milliseconds for other writes, before +# performing the sync. +# +# commitlog_sync: batch +# commitlog_sync_batch_window_in_ms: 50 +# +# the other option is "periodic" where writes may be acked immediately +# and the CommitLog is simply synced every commitlog_sync_period_in_ms +# milliseconds. By default this allows 1024*(CPU cores) pending +# entries on the commitlog queue. If you are writing very large blobs, +# you should reduce that; 16*cores works reasonably well for 1MB blobs. +# It should be at least as large as the concurrent_writes setting. +commitlog_sync: periodic +commitlog_sync_period_in_ms: 10000 +# commitlog_periodic_queue_size: + +# The size of the individual commitlog file segments. A commitlog +# segment may be archived, deleted, or recycled once all the data +# in it (potentially from each columnfamily in the system) has been +# flushed to sstables. +# +# The default size is 32, which is almost always fine, but if you are +# archiving commitlog segments (see commitlog_archiving.properties), +# then you probably want a finer granularity of archiving; 8 or 16 MB +# is reasonable. +commitlog_segment_size_in_mb: 32 + +# any class that implements the SeedProvider interface and has a +# constructor that takes a Map of parameters will do. +seed_provider: + # Addresses of hosts that are deemed contact points. + # Cassandra nodes use this list of hosts to find each other and learn + # the topology of the ring. You must change this if you are running + # multiple nodes! + - class_name: org.apache.cassandra.locator.SimpleSeedProvider + parameters: + # seeds is actually a comma-delimited list of addresses. + # Ex: ",," + - seeds: "127.0.0.1" + +# For workloads with more data than can fit in memory, Cassandra's +# bottleneck will be reads that need to fetch data from +# disk. "concurrent_reads" should be set to (16 * number_of_drives) in +# order to allow the operations to enqueue low enough in the stack +# that the OS and drives can reorder them. +# +# On the other hand, since writes are almost never IO bound, the ideal +# number of "concurrent_writes" is dependent on the number of cores in +# your system; (8 * number_of_cores) is a good rule of thumb. +concurrent_reads: 32 +concurrent_writes: 32 + +# Total memory to use for sstable-reading buffers. Defaults to +# the smaller of 1/4 of heap or 512MB. +# file_cache_size_in_mb: 512 + +# Total memory to use for memtables. Cassandra will flush the largest +# memtable when this much memory is used. +# If omitted, Cassandra will set it to 1/3 of the heap. +# memtable_total_space_in_mb: 2048 + +# Total space to use for commitlogs. Since commitlog segments are +# mmapped, and hence use up address space, the default size is 32 +# on 32-bit JVMs, and 1024 on 64-bit JVMs. +# +# If space gets above this value (it will round up to the next nearest +# segment multiple), Cassandra will flush every dirty CF in the oldest +# segment and remove it. So a small total commitlog space will tend +# to cause more flush activity on less-active columnfamilies. +# commitlog_total_space_in_mb: 4096 + +# This sets the amount of memtable flush writer threads. These will +# be blocked by disk io, and each one will hold a memtable in memory +# while blocked. If you have a large heap and many data directories, +# you can increase this value for better flush performance. +# By default this will be set to the amount of data directories defined. +#memtable_flush_writers: 1 + +# the number of full memtables to allow pending flush, that is, +# waiting for a writer thread. At a minimum, this should be set to +# the maximum number of secondary indexes created on a single CF. +memtable_flush_queue_size: 4 + +# Whether to, when doing sequential writing, fsync() at intervals in +# order to force the operating system to flush the dirty +# buffers. Enable this to avoid sudden dirty buffer flushing from +# impacting read latencies. Almost always a good idea on SSDs; not +# necessarily on platters. +trickle_fsync: false +trickle_fsync_interval_in_kb: 10240 + +# TCP port, for commands and data +storage_port: 7000 + +# SSL port, for encrypted communication. Unused unless enabled in +# encryption_options +ssl_storage_port: 7001 + +# Address to bind to and tell other Cassandra nodes to connect to. You +# _must_ change this if you want multiple nodes to be able to +# communicate! +# +# Leaving it blank leaves it up to InetAddress.getLocalHost(). This +# will always do the Right Thing _if_ the node is properly configured +# (hostname, name resolution, etc), and the Right Thing is to use the +# address associated with the hostname (it might not be). +# +# Setting this to 0.0.0.0 is always wrong. +listen_address: localhost + +# Address to broadcast to other Cassandra nodes +# Leaving this blank will set it to the same value as listen_address +# broadcast_address: 1.2.3.4 + +# Internode authentication backend, implementing IInternodeAuthenticator; +# used to allow/disallow connections from peer nodes. +# internode_authenticator: org.apache.cassandra.auth.AllowAllInternodeAuthenticator + +# Whether to start the native transport server. +# Please note that the address on which the native transport is bound is the +# same as the rpc_address. The port however is different and specified below. +start_native_transport: true +# port for the CQL native transport to listen for clients on +native_transport_port: 9042 +# The maximum threads for handling requests when the native transport is used. +# This is similar to rpc_max_threads though the default differs slightly (and +# there is no native_transport_min_threads, idle threads will always be stopped +# after 30 seconds). +# native_transport_max_threads: 128 + +# Whether to start the thrift rpc server. +start_rpc: true + +# The address to bind the Thrift RPC service and native transport +# server -- clients connect here. +# +# Leaving this blank has the same effect it does for ListenAddress, +# (i.e. it will be based on the configured hostname of the node). +# +# Note that unlike ListenAddress above, it is allowed to specify 0.0.0.0 +# here if you want to listen on all interfaces but is not best practice +# as it is known to confuse the node auto-discovery features of some +# client drivers. +rpc_address: localhost +# port for Thrift to listen for clients on +rpc_port: 9160 + +# enable or disable keepalive on rpc connections +rpc_keepalive: true + +# Cassandra provides three out-of-the-box options for the RPC Server: +# +# sync -> One thread per thrift connection. For a very large number of clients, memory +# will be your limiting factor. On a 64 bit JVM, 180KB is the minimum stack size +# per thread, and that will correspond to your use of virtual memory (but physical memory +# may be limited depending on use of stack space). +# +# hsha -> Stands for "half synchronous, half asynchronous." All thrift clients are handled +# asynchronously using a small number of threads that does not vary with the amount +# of thrift clients (and thus scales well to many clients). The rpc requests are still +# synchronous (one thread per active request). +# +# The default is sync because on Windows hsha is about 30% slower. On Linux, +# sync/hsha performance is about the same, with hsha of course using less memory. +# +# Alternatively, can provide your own RPC server by providing the fully-qualified class name +# of an o.a.c.t.TServerFactory that can create an instance of it. +rpc_server_type: sync + +# Uncomment rpc_min|max_thread to set request pool size limits. +# +# Regardless of your choice of RPC server (see above), the number of maximum requests in the +# RPC thread pool dictates how many concurrent requests are possible (but if you are using the sync +# RPC server, it also dictates the number of clients that can be connected at all). +# +# The default is unlimited and thus provides no protection against clients overwhelming the server. You are +# encouraged to set a maximum that makes sense for you in production, but do keep in mind that +# rpc_max_threads represents the maximum number of client requests this server may execute concurrently. +# +# rpc_min_threads: 16 +# rpc_max_threads: 2048 + +# uncomment to set socket buffer sizes on rpc connections +# rpc_send_buff_size_in_bytes: +# rpc_recv_buff_size_in_bytes: + +# Uncomment to set socket buffer size for internode communication +# Note that when setting this, the buffer size is limited by net.core.wmem_max +# and when not setting it it is defined by net.ipv4.tcp_wmem +# See: +# /proc/sys/net/core/wmem_max +# /proc/sys/net/core/rmem_max +# /proc/sys/net/ipv4/tcp_wmem +# /proc/sys/net/ipv4/tcp_wmem +# and: man tcp +# internode_send_buff_size_in_bytes: +# internode_recv_buff_size_in_bytes: + +# Frame size for thrift (maximum message length). +thrift_framed_transport_size_in_mb: 15 + +# Set to true to have Cassandra create a hard link to each sstable +# flushed or streamed locally in a backups/ subdirectory of the +# keyspace data. Removing these links is the operator's +# responsibility. +incremental_backups: false + +# Whether or not to take a snapshot before each compaction. Be +# careful using this option, since Cassandra won't clean up the +# snapshots for you. Mostly useful if you're paranoid when there +# is a data format change. +snapshot_before_compaction: false + +# Whether or not a snapshot is taken of the data before keyspace truncation +# or dropping of column families. The STRONGLY advised default of true +# should be used to provide data safety. If you set this flag to false, you will +# lose data on truncation or drop. +auto_snapshot: true + +# Add column indexes to a row after its contents reach this size. +# Increase if your column values are large, or if you have a very large +# number of columns. The competing causes are, Cassandra has to +# deserialize this much of the row to read a single column, so you want +# it to be small - at least if you do many partial-row reads - but all +# the index data is read for each access, so you don't want to generate +# that wastefully either. +column_index_size_in_kb: 64 + +# Size limit for rows being compacted in memory. Larger rows will spill +# over to disk and use a slower two-pass compaction process. A message +# will be logged specifying the row key. +in_memory_compaction_limit_in_mb: 64 + +# Number of simultaneous compactions to allow, NOT including +# validation "compactions" for anti-entropy repair. Simultaneous +# compactions can help preserve read performance in a mixed read/write +# workload, by mitigating the tendency of small sstables to accumulate +# during a single long running compactions. The default is usually +# fine and if you experience problems with compaction running too +# slowly or too fast, you should look at +# compaction_throughput_mb_per_sec first. +# +# concurrent_compactors defaults to the number of cores. +# Uncomment to make compaction mono-threaded, the pre-0.8 default. +#concurrent_compactors: 1 + +# Multi-threaded compaction. When enabled, each compaction will use +# up to one thread per core, plus one thread per sstable being merged. +# This is usually only useful for SSD-based hardware: otherwise, +# your concern is usually to get compaction to do LESS i/o (see: +# compaction_throughput_mb_per_sec), not more. +multithreaded_compaction: false + +# Throttles compaction to the given total throughput across the entire +# system. The faster you insert data, the faster you need to compact in +# order to keep the sstable count down, but in general, setting this to +# 16 to 32 times the rate you are inserting data is more than sufficient. +# Setting this to 0 disables throttling. Note that this account for all types +# of compaction, including validation compaction. +compaction_throughput_mb_per_sec: 16 + +# Track cached row keys during compaction, and re-cache their new +# positions in the compacted sstable. Disable if you use really large +# key caches. +compaction_preheat_key_cache: true + +# Throttles all outbound streaming file transfers on this node to the +# given total throughput in Mbps. This is necessary because Cassandra does +# mostly sequential IO when streaming data during bootstrap or repair, which +# can lead to saturating the network connection and degrading rpc performance. +# When unset, the default is 200 Mbps or 25 MB/s. +# stream_throughput_outbound_megabits_per_sec: 200 + +# How long the coordinator should wait for read operations to complete +read_request_timeout_in_ms: 5000 +# How long the coordinator should wait for seq or index scans to complete +range_request_timeout_in_ms: 10000 +# How long the coordinator should wait for writes to complete +write_request_timeout_in_ms: 2000 +# How long a coordinator should continue to retry a CAS operation +# that contends with other proposals for the same row +cas_contention_timeout_in_ms: 1000 +# How long the coordinator should wait for truncates to complete +# (This can be much longer, because unless auto_snapshot is disabled +# we need to flush first so we can snapshot before removing the data.) +truncate_request_timeout_in_ms: 60000 +# The default timeout for other, miscellaneous operations +request_timeout_in_ms: 10000 + +# Enable operation timeout information exchange between nodes to accurately +# measure request timeouts. If disabled, replicas will assume that requests +# were forwarded to them instantly by the coordinator, which means that +# under overload conditions we will waste that much extra time processing +# already-timed-out requests. +# +# Warning: before enabling this property make sure to ntp is installed +# and the times are synchronized between the nodes. +cross_node_timeout: false + +# Enable socket timeout for streaming operation. +# When a timeout occurs during streaming, streaming is retried from the start +# of the current file. This _can_ involve re-streaming an important amount of +# data, so you should avoid setting the value too low. +# Default value is 0, which never timeout streams. +# streaming_socket_timeout_in_ms: 0 + +# phi value that must be reached for a host to be marked down. +# most users should never need to adjust this. +# phi_convict_threshold: 8 + +# endpoint_snitch -- Set this to a class that implements +# IEndpointSnitch. The snitch has two functions: +# - it teaches Cassandra enough about your network topology to route +# requests efficiently +# - it allows Cassandra to spread replicas around your cluster to avoid +# correlated failures. It does this by grouping machines into +# "datacenters" and "racks." Cassandra will do its best not to have +# more than one replica on the same "rack" (which may not actually +# be a physical location) +# +# IF YOU CHANGE THE SNITCH AFTER DATA IS INSERTED INTO THE CLUSTER, +# YOU MUST RUN A FULL REPAIR, SINCE THE SNITCH AFFECTS WHERE REPLICAS +# ARE PLACED. +# +# Out of the box, Cassandra provides +# - SimpleSnitch: +# Treats Strategy order as proximity. This improves cache locality +# when disabling read repair, which can further improve throughput. +# Only appropriate for single-datacenter deployments. +# - PropertyFileSnitch: +# Proximity is determined by rack and data center, which are +# explicitly configured in cassandra-topology.properties. +# - GossipingPropertyFileSnitch +# The rack and datacenter for the local node are defined in +# cassandra-rackdc.properties and propagated to other nodes via gossip. If +# cassandra-topology.properties exists, it is used as a fallback, allowing +# migration from the PropertyFileSnitch. +# - RackInferringSnitch: +# Proximity is determined by rack and data center, which are +# assumed to correspond to the 3rd and 2nd octet of each node's +# IP address, respectively. Unless this happens to match your +# deployment conventions (as it did Facebook's), this is best used +# as an example of writing a custom Snitch class. +# - Ec2Snitch: +# Appropriate for EC2 deployments in a single Region. Loads Region +# and Availability Zone information from the EC2 API. The Region is +# treated as the datacenter, and the Availability Zone as the rack. +# Only private IPs are used, so this will not work across multiple +# Regions. +# - Ec2MultiRegionSnitch: +# Uses public IPs as broadcast_address to allow cross-region +# connectivity. (Thus, you should set seed addresses to the public +# IP as well.) You will need to open the storage_port or +# ssl_storage_port on the public IP firewall. (For intra-Region +# traffic, Cassandra will switch to the private IP after +# establishing a connection.) +# +# You can use a custom Snitch by setting this to the full class name +# of the snitch, which will be assumed to be on your classpath. +endpoint_snitch: SimpleSnitch + +# controls how often to perform the more expensive part of host score +# calculation +dynamic_snitch_update_interval_in_ms: 100 +# controls how often to reset all host scores, allowing a bad host to +# possibly recover +dynamic_snitch_reset_interval_in_ms: 600000 +# if set greater than zero and read_repair_chance is < 1.0, this will allow +# 'pinning' of replicas to hosts in order to increase cache capacity. +# The badness threshold will control how much worse the pinned host has to be +# before the dynamic snitch will prefer other replicas over it. This is +# expressed as a double which represents a percentage. Thus, a value of +# 0.2 means Cassandra would continue to prefer the static snitch values +# until the pinned host was 20% worse than the fastest. +dynamic_snitch_badness_threshold: 0.1 + +# request_scheduler -- Set this to a class that implements +# RequestScheduler, which will schedule incoming client requests +# according to the specific policy. This is useful for multi-tenancy +# with a single Cassandra cluster. +# NOTE: This is specifically for requests from the client and does +# not affect inter node communication. +# org.apache.cassandra.scheduler.NoScheduler - No scheduling takes place +# org.apache.cassandra.scheduler.RoundRobinScheduler - Round robin of +# client requests to a node with a separate queue for each +# request_scheduler_id. The scheduler is further customized by +# request_scheduler_options as described below. +request_scheduler: org.apache.cassandra.scheduler.NoScheduler + +# Scheduler Options vary based on the type of scheduler +# NoScheduler - Has no options +# RoundRobin +# - throttle_limit -- The throttle_limit is the number of in-flight +# requests per client. Requests beyond +# that limit are queued up until +# running requests can complete. +# The value of 80 here is twice the number of +# concurrent_reads + concurrent_writes. +# - default_weight -- default_weight is optional and allows for +# overriding the default which is 1. +# - weights -- Weights are optional and will default to 1 or the +# overridden default_weight. The weight translates into how +# many requests are handled during each turn of the +# RoundRobin, based on the scheduler id. +# +# request_scheduler_options: +# throttle_limit: 80 +# default_weight: 5 +# weights: +# Keyspace1: 1 +# Keyspace2: 5 + +# request_scheduler_id -- An identifier based on which to perform +# the request scheduling. Currently the only valid option is keyspace. +# request_scheduler_id: keyspace + +# Enable or disable inter-node encryption +# Default settings are TLS v1, RSA 1024-bit keys (it is imperative that +# users generate their own keys) TLS_RSA_WITH_AES_128_CBC_SHA as the cipher +# suite for authentication, key exchange and encryption of the actual data transfers. +# NOTE: No custom encryption options are enabled at the moment +# The available internode options are : all, none, dc, rack +# +# If set to dc cassandra will encrypt the traffic between the DCs +# If set to rack cassandra will encrypt the traffic between the racks +# +# The passwords used in these options must match the passwords used when generating +# the keystore and truststore. For instructions on generating these files, see: +# http://download.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#CreateKeystore +# +server_encryption_options: + internode_encryption: none + keystore: conf/.keystore + keystore_password: cassandra + truststore: conf/.truststore + truststore_password: cassandra + # More advanced defaults below: + # protocol: TLS + # algorithm: SunX509 + # store_type: JKS + # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA] + # require_client_auth: false + +# enable or disable client/server encryption. +client_encryption_options: + enabled: false + keystore: conf/.keystore + keystore_password: cassandra + # require_client_auth: false + # Set trustore and truststore_password if require_client_auth is true + # truststore: conf/.truststore + # truststore_password: cassandra + # More advanced defaults below: + # protocol: TLS + # algorithm: SunX509 + # store_type: JKS + # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA] + +# internode_compression controls whether traffic between nodes is +# compressed. +# can be: all - all traffic is compressed +# dc - traffic between different datacenters is compressed +# none - nothing is compressed. +internode_compression: all + +# Enable or disable tcp_nodelay for inter-dc communication. +# Disabling it will result in larger (but fewer) network packets being sent, +# reducing overhead from the TCP protocol itself, at the cost of increasing +# latency if you block for cross-datacenter responses. +inter_dc_tcp_nodelay: false + +# Enable or disable kernel page cache preheating from contents of the key cache after compaction. +# When enabled it would preheat only first "page" (4KB) of each row to optimize +# for sequential access. Note: This could be harmful for fat rows, see CASSANDRA-4937 +# for further details on that topic. +preheat_kernel_page_cache: false diff --git a/attic/get-and-start-cassandra b/attic/get-and-start-cassandra new file mode 100755 index 000000000..2c045e19e --- /dev/null +++ b/attic/get-and-start-cassandra @@ -0,0 +1,23 @@ +CASSANDRA_DIST=.cassandra/dist +mkdir -p $CASSANDRA_DIST + +curl -sL http://downloads.datastax.com/community/dsc.tar.gz > $CASSANDRA_DIST/dist.tgz +tar -xzf $CASSANDRA_DIST/dist.tgz -C $CASSANDRA_DIST + +CASSANDRA_HOME=`find $CASSANDRA_DIST -name 'dsc-cassandra-*' -print` +if [ -z "$CASSANDRA_HOME" ]; then + echo "Couldn't determine CASSANDRA_HOME" + exit 1 +fi +echo "CASSANDRA_HOME is $CASSANDRA_HOME" + +# these directories must match what's in test-support/cassandra/conf/cassandra.yaml +mkdir -p .cassandra/var/lib/cassandra +mkdir -p .cassandra/var/log/cassandra + +mv $CASSANDRA_HOME/conf/cassandra.yaml $CASSANDRA_HOME/conf/cassandra.yaml.original +cp test-support/cassandra/conf/cassandra.yaml $CASSANDRA_HOME/conf/cassandra.yaml + +$CASSANDRA_HOME/bin/cassandra -p $CASSANDRA_HOME/cassandra.pid + +sleep 5 diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/CachedPreparedStatementCreator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/CachedPreparedStatementCreator.java new file mode 100644 index 000000000..90fdba895 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/CachedPreparedStatementCreator.java @@ -0,0 +1,80 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.cassandra.core; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.Assert; + +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.exceptions.DriverException; + +/** + * Created a PreparedStatement and retrieved the PreparedStatement from cache if the statement has been prepared + * previously. In general, this creator should be used over the {@link SimplePreparedStatementCreator} as it provides + * better performance. + * + *

+ * There is overhead in Cassandra when Preparing a Statement. This is negligible on a single data center configuration, + * but when your cluster spans multiple data centers, preparing the same statement over and over again is not necessary + * and causes performance issues in high throughput use cases. + *

+ * + * @author David Webb + * + */ +public class CachedPreparedStatementCreator implements PreparedStatementCreator, CqlProvider { + + private static Logger log = LoggerFactory.getLogger(CachedPreparedStatementCreator.class); + + private final String cql; + + private PreparedStatement cache; + + /** + * Create a CachedPreparedStatementCreator from the provided CQL. + * + * @param cql + */ + public CachedPreparedStatementCreator(String cql) { + Assert.notNull(cql, "CQL is required to create a PreparedStatement"); + this.cql = cql; + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.PreparedStatementCreator#createPreparedStatement(com.datastax.driver.core.Session) + */ + @Override + public PreparedStatement createPreparedStatement(Session session) throws DriverException { + if (cache == null) { + log.debug("PreparedStatement cache is null, preparing new Statement"); + cache = session.prepare(getCql()); + } else { + log.debug("Using cached PreparedStatement"); + } + return cache; + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CqlProvider#getCql() + */ + @Override + public String getCql() { + return this.cql; + } + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/CassandraOperations.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/CassandraOperations.java new file mode 100644 index 000000000..991c9ec95 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/CassandraOperations.java @@ -0,0 +1,401 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.cassandra.core; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.springframework.dao.DataAccessException; + +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.Session; + +/** + * Operations for interacting with Cassandra at the lowest level. This interface provides Exception Translation. + * + * @author David Webb + * @author Matthew Adams + */ +public interface CassandraOperations { + + /** + * Executes the supplied {@link SessionCallback} in the current Template Session. The implementation of + * SessionCallback can decide whether or not to execute() or executeAsync() the operation. + * + * @param sessionCallback + * @return Type defined in the SessionCallback + */ + T execute(SessionCallback sessionCallback) throws DataAccessException; + + /** + * Executes the supplied CQL Query and returns nothing. + * + * @param cql + */ + void execute(final String cql) throws DataAccessException; + + /** + * Executes the supplied CQL Query Asynchronously and returns nothing. + * + * @param cql The CQL Statement to execute + */ + void executeAsynchronously(final String cql) throws DataAccessException; + + /** + * Executes the provided CQL Query, and extracts the results with the ResultSetExtractor. + * + * @param cql The Query + * @param rse The implementation for extracting the ResultSet + * + * @return Type specified in the ResultSetExtractor + * @throws DataAccessException + */ + T query(final String cql, ResultSetExtractor rse) throws DataAccessException; + + /** + * Executes the provided CQL Query asynchronously, and extracts the results with the ResultSetFutureExtractor + * + * @param cql The Query + * @param rse The implementation for extracting the future results + * @return + * @throws DataAccessException + */ + T queryAsynchronously(final String cql, ResultSetFutureExtractor rse) throws DataAccessException; + + /** + * Executes the provided CQL Query, and then processes the results with the RowCallbackHandler. + * + * @param cql The Query + * @param rch The implementation for processing the rows returned. + * @throws DataAccessException + */ + void query(final String cql, RowCallbackHandler rch) throws DataAccessException; + + /** + * Processes the ResultSet through the RowCallbackHandler and return nothing. This is used internal to the Template + * for core operations, but is made available through Operations in the event you have a ResultSet to process. The + * ResultsSet could come from a ResultSetFuture after an asynchronous query. + * + * @param resultSet Results to process + * @param rch RowCallbackHandler with the processing implementation + * @throws DataAccessException + */ + void process(ResultSet resultSet, RowCallbackHandler rch) throws DataAccessException; + + /** + * Executes the provided CQL Query, and maps all Rows returned with the supplied RowMapper. + * + * @param cql The Query + * @param rowMapper The implementation for mapping all rows + * @return List of processed by the RowMapper + * @throws DataAccessException + */ + List query(final String cql, RowMapper rowMapper) throws DataAccessException; + + /** + * Processes the ResultSet through the RowMapper and returns the List of mapped Rows. This is used internal to the + * Template for core operations, but is made available through Operations in the event you have a ResultSet to + * process. The ResultsSet could come from a ResultSetFuture after an asynchronous query. + * + * @param resultSet Results to process + * @param rowMapper RowMapper with the processing implementation + * @return List of generated by the RowMapper + * @throws DataAccessException + */ + List process(ResultSet resultSet, RowMapper rowMapper) throws DataAccessException; + + /** + * Executes the provided CQL Query, and maps ONE Row returned with the supplied RowMapper. + * + *

+ * This expects only ONE row to be returned. More than one Row will cause an Exception to be thrown. + *

+ * + * @param cql The Query + * @param rowMapper The implementation for convert the Row to + * @return Object + * @throws DataAccessException + */ + T queryForObject(final String cql, RowMapper rowMapper) throws DataAccessException; + + /** + * Process a ResultSet through a RowMapper. This is used internal to the Template for core operations, but is made + * available through Operations in the event you have a ResultSet to process. The ResultsSet could come from a + * ResultSetFuture after an asynchronous query. + * + * @param resultSet + * @param rowMapper + * @return + * @throws DataAccessException + */ + T processOne(ResultSet resultSet, RowMapper rowMapper) throws DataAccessException; + + /** + * Executes the provided query and tries to return the first column of the first Row as a Class. + * + * @param cql The Query + * @param requiredType Valid Class that Cassandra Data Types can be converted to. + * @return The Object - item [0,0] in the result table of the query. + * @throws DataAccessException + */ + T queryForObject(final String cql, Class requiredType) throws DataAccessException; + + /** + * Process a ResultSet, trying to convert the first columns of the first Row to Class. This is used internal to the + * Template for core operations, but is made available through Operations in the event you have a ResultSet to + * process. The ResultsSet could come from a ResultSetFuture after an asynchronous query. + * + * @param resultSet + * @param requiredType + * @return + * @throws DataAccessException + */ + T processOne(ResultSet resultSet, Class requiredType) throws DataAccessException; + + /** + * Executes the provided CQL Query and maps ONE Row to a basic Map of Strings and Objects. If more than one Row + * is returned from the Query, an exception will be thrown. + * + * @param cql The Query + * @return Map representing the results of the Query + * @throws DataAccessException + */ + Map queryForMap(final String cql) throws DataAccessException; + + /** + * Process a ResultSet with ONE Row and convert to a Map. This is used internal to the Template for core + * operations, but is made available through Operations in the event you have a ResultSet to process. The ResultsSet + * could come from a ResultSetFuture after an asynchronous query. + * + * @param resultSet + * @return + * @throws DataAccessException + */ + Map processMap(ResultSet resultSet) throws DataAccessException; + + /** + * Executes the provided CQL and returns all values in the first column of the Results as a List of the Type in the + * second argument. + * + * @param cql The Query + * @param elementType Type to cast the data values to + * @return List of elementType + * @throws DataAccessException + */ + List queryForList(final String cql, Class elementType) throws DataAccessException; + + /** + * Process a ResultSet and convert the first column of the results to a List. This is used internal to the Template + * for core operations, but is made available through Operations in the event you have a ResultSet to process. The + * ResultsSet could come from a ResultSetFuture after an asynchronous query. + * + * @param resultSet + * @param elementType + * @return + * @throws DataAccessException + */ + List processList(ResultSet resultSet, Class elementType) throws DataAccessException; + + /** + * Executes the provided CQL and converts the results to a basic List of Maps. Each element in the List represents a + * Row returned from the Query. Each Row's columns are put into the map as column/value. + * + * @param cql The Query + * @return List of Maps with the query results + * @throws DataAccessException + */ + List> queryForListOfMap(final String cql) throws DataAccessException; + + /** + * Process a ResultSet and convert it to a List of Maps with column/value. This is used internal to the Template for + * core operations, but is made available through Operations in the event you have a ResultSet to process. The + * ResultsSet could come from a ResultSetFuture after an asynchronous query. + * + * @param resultSet + * @return + * @throws DataAccessException + */ + List> processListOfMap(ResultSet resultSet) throws DataAccessException; + + /** + * Converts the CQL provided into a {@link SimplePreparedStatementCreator}. This can only be used for CQL + * Statements that do not have data binding. The results of the PreparedStatement are processed with + * PreparedStatementCallback implementation provided by the Application Code. + * + * @param cql The CQL Statement to Execute + * @param action What to do with the results of the PreparedStatement + * @return Type as determined by the supplied Callback. + * @throws DataAccessException + */ + T execute(String cql, PreparedStatementCallback action) throws DataAccessException; + + /** + * Uses the provided PreparedStatementCreator to prepare a new Session call, then executes the statement and processes + * the statement using the provided Callback. This can only be used for CQL Statements that do not have data + * binding. The results of the PreparedStatement are processed with PreparedStatementCallback implementation + * provided by the Application Code. + * + * @param psc The implementation to create the PreparedStatement + * @param action What to do with the results of the PreparedStatement + * @return Type as determined by the supplied Callback. + * @throws DataAccessException + */ + T execute(PreparedStatementCreator psc, PreparedStatementCallback action) throws DataAccessException; + + /** + * Converts the CQL provided into a {@link SimplePreparedStatementCreator}. Then, the PreparedStatementBinder will + * bind its values to the bind variables in the provided CQL String. The results of the PreparedStatement are + * processed with the ResultSetExtractor implementation provided by the Application Code. The can return any object, + * including a List of Objects to support the ResultSet processing. + * + * @param cql The Query to Prepare + * @param psb The Binding implementation + * @param rse The implementation for extracting the results of the query. + * @return Type generated by the ResultSetExtractor + * @throws DataAccessException + */ + T query(final String cql, PreparedStatementBinder psb, ResultSetExtractor rse) throws DataAccessException; + + /** + * Converts the CQL provided into a {@link SimplePreparedStatementCreator}. Then, the PreparedStatementBinder will + * bind its values to the bind variables in the provided CQL String. The results of the PreparedStatement are + * processed with the RowCallbackHandler implementation provided and nothing is returned. + * + * @param cql The Query to Prepare + * @param psb The Binding implementation + * @param rch The RowCallbackHandler for processing the ResultSet + * @throws DataAccessException + */ + void query(final String cql, PreparedStatementBinder psb, RowCallbackHandler rch) throws DataAccessException; + + /** + * Converts the CQL provided into a {@link SimplePreparedStatementCreator}. Then, the PreparedStatementBinder will + * bind its values to the bind variables in the provided CQL String. The results of the PreparedStatement are + * processed with the RowMapper implementation provided and a List is returned with elements of Type for each Row + * returned. + * + * @param cql The Query to Prepare + * @param psb The Binding implementation + * @param rowMapper The implementation for Mapping a Row to Type + * @return List of for each Row returned from the Query. + * @throws DataAccessException + */ + List query(final String cql, PreparedStatementBinder psb, RowMapper rowMapper) throws DataAccessException; + + /** + * Uses the provided PreparedStatementCreator to prepare a new Session call. This can only be used for CQL + * Statements that do not have data binding. The results of the PreparedStatement are processed with + * ResultSetExtractor implementation provided by the Application Code. + * + * @param psc The implementation to create the PreparedStatement + * @param rse Implementation for extracting from the ResultSet + * @return Type which is the output of the ResultSetExtractor + * @throws DataAccessException + */ + T query(PreparedStatementCreator psc, ResultSetExtractor rse) throws DataAccessException; + + /** + * Uses the provided PreparedStatementCreator to prepare a new Session call. This can only be used for CQL + * Statements that do not have data binding. The results of the PreparedStatement are processed with + * RowCallbackHandler and nothing is returned. + * + * @param psc The implementation to create the PreparedStatement + * @param rch The implementation to process Results + * @throws DataAccessException + */ + void query(PreparedStatementCreator psc, RowCallbackHandler rch) throws DataAccessException; + + /** + * Uses the provided PreparedStatementCreator to prepare a new Session call. This can only be used for CQL + * Statements that do not have data binding. The results of the PreparedStatement are processed with RowMapper + * implementation provided and a List is returned with elements of Type for each Row returned. + * + * @param psc The implementation to create the PreparedStatement + * @param rowMapper The implementation for mapping each Row returned. + * @return List of Type mapped from each Row in the Results + * @throws DataAccessException + */ + List query(PreparedStatementCreator psc, RowMapper rowMapper) throws DataAccessException; + + /** + * Uses the provided PreparedStatementCreator to prepare a new Session call. Binds the values from the + * PreparedStatementBinder to the available bind variables. The results of the PreparedStatement are processed with + * ResultSetExtractor implementation provided by the Application Code. + * + * @param psc The implementation to create the PreparedStatement + * @param psb The implementation to bind variables to values + * @param rse Implementation for extracting from the ResultSet + * @return Type which is the output of the ResultSetExtractor + * @throws DataAccessException + */ + T query(PreparedStatementCreator psc, final PreparedStatementBinder psb, final ResultSetExtractor rse) + throws DataAccessException; + + /** + * Uses the provided PreparedStatementCreator to prepare a new Session call. Binds the values from the + * PreparedStatementBinder to the available bind variables. The results of the PreparedStatement are processed with + * RowCallbackHandler and nothing is returned. + * + * @param psc The implementation to create the PreparedStatement + * @param psb The implementation to bind variables to values + * @param rch The implementation to process Results + * @return Type which is the output of the ResultSetExtractor + * @throws DataAccessException + */ + void query(PreparedStatementCreator psc, final PreparedStatementBinder psb, final RowCallbackHandler rch) + throws DataAccessException; + + /** + * Uses the provided PreparedStatementCreator to prepare a new Session call. Binds the values from the + * PreparedStatementBinder to the available bind variables. The results of the PreparedStatement are processed with + * RowMapper implementation provided and a List is returned with elements of Type for each Row returned. + * + * @param psc The implementation to create the PreparedStatement + * @param psb The implementation to bind variables to values + * @param rowMapper The implementation for mapping each Row returned. + * @return Type which is the output of the ResultSetExtractor + * @throws DataAccessException + */ + List query(PreparedStatementCreator psc, final PreparedStatementBinder psb, final RowMapper rowMapper) + throws DataAccessException; + + /** + * Describe the current Ring. This uses the provided {@link RingMemberHostMapper} to provide the basics of the + * Cassandra Ring topology. + * + * @return The list of ring tokens that are active in the cluster + */ + List describeRing() throws DataAccessException; + + /** + * Describe the current Ring. Application code must provide its own {@link HostMapper} implementation to process the + * lists of hosts returned by the Cassandra Cluster Metadata. + * + * @param hostMapper The implementation to use for host mapping. + * @return Collection generated by the provided HostMapper. + * @throws DataAccessException + */ + Collection describeRing(HostMapper hostMapper) throws DataAccessException; + + /** + * Get the current Session used for operations in the implementing class. + * + * @return The DataStax Driver Session Object + */ + Session getSession(); + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/CassandraTemplate.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/CassandraTemplate.java new file mode 100644 index 000000000..7689c083d --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/CassandraTemplate.java @@ -0,0 +1,563 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.cassandra.core; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.cassandra.support.CassandraAccessor; +import org.springframework.dao.DataAccessException; +import org.springframework.util.Assert; + +import com.datastax.driver.core.BoundStatement; +import com.datastax.driver.core.ColumnDefinitions; +import com.datastax.driver.core.ColumnDefinitions.Definition; +import com.datastax.driver.core.DataType; +import com.datastax.driver.core.Host; +import com.datastax.driver.core.Metadata; +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.ResultSetFuture; +import com.datastax.driver.core.Row; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.exceptions.DriverException; + +/** + * This is the Central class in the Cassandra core package. It simplifies the use of Cassandra and helps to avoid + * common errors. It executes the core Cassandra workflow, leaving application code to provide CQL and result + * extraction. This class execute CQL Queries, provides different ways to extract/map results, and provides Exception + * translation to the generic, more informative exception hierarchy defined in the org.springframework.dao + * package. + * + *

+ * For working with POJOs, use the {@link CassandraDataTemplate}. + *

+ * + * @author David Webb + * @author Matthew Adams + */ +public class CassandraTemplate extends CassandraAccessor implements CassandraOperations { + + /** + * Blank constructor. You must wire in the Session before use. + * + */ + public CassandraTemplate() { + } + + /** + * Constructor used for a basic template configuration + * + * @param session must not be {@literal null}. + */ + public CassandraTemplate(Session session) { + setSession(session); + afterPropertiesSet(); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#execute(org.springframework.data.cassandra.core.SessionCallback) + */ + @Override + public T execute(SessionCallback sessionCallback) throws DataAccessException { + return doExecute(sessionCallback); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#execute(java.lang.String) + */ + @Override + public void execute(final String cql) throws DataAccessException { + doExecute(cql); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#query(java.lang.String, org.springframework.cassandra.core.FutureResultSetExtractor) + */ + @Override + public T queryAsynchronously(final String cql, ResultSetFutureExtractor rse) throws DataAccessException { + return rse.extractData(execute(new SessionCallback() { + @Override + public ResultSetFuture doInSession(Session s) throws DataAccessException { + return s.executeAsync(cql); + } + })); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#query(java.lang.String, org.springframework.cassandra.core.ResultSetExtractor) + */ + public T query(String cql, ResultSetExtractor rse) throws DataAccessException { + ResultSet rs = doExecute(cql); + return rse.extractData(rs); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#query(java.lang.String, org.springframework.cassandra.core.RowCallbackHandler) + */ + public void query(String cql, RowCallbackHandler rch) throws DataAccessException { + process(doExecute(cql), rch); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#query(java.lang.String, org.springframework.cassandra.core.RowMapper) + */ + public List query(String cql, RowMapper rowMapper) throws DataAccessException { + return process(doExecute(cql), rowMapper); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#queryForList(java.lang.String) + */ + public List> queryForListOfMap(String cql) throws DataAccessException { + return processListOfMap(doExecute(cql)); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#queryForList(java.lang.String, java.lang.Class) + */ + public List queryForList(String cql, Class elementType) throws DataAccessException { + return processList(doExecute(cql), elementType); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#queryForMap(java.lang.String) + */ + public Map queryForMap(String cql) throws DataAccessException { + return processMap(doExecute(cql)); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#queryForObject(java.lang.String, java.lang.Class) + */ + public T queryForObject(String cql, Class requiredType) throws DataAccessException { + return processOne(doExecute(cql), requiredType); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#queryForObject(java.lang.String, org.springframework.cassandra.core.RowMapper) + */ + public T queryForObject(String cql, RowMapper rowMapper) throws DataAccessException { + return processOne(doExecute(cql), rowMapper); + } + + /** + * Execute a command at the Session Level + * + * @param callback + * @return + */ + protected T doExecute(SessionCallback callback) { + + Assert.notNull(callback); + + try { + + return callback.doInSession(getSession()); + + } catch (DataAccessException e) { + throw throwTranslated(e); + } + } + + /** + * Execute a command at the Session Level + * + * @param callback + * @return + */ + protected ResultSet doExecute(final String cql) { + + return doExecute(new SessionCallback() { + + @Override + public ResultSet doInSession(Session s) throws DataAccessException { + return s.execute(cql); + } + }); + } + + /** + * Execute a command at the Session Level + * + * @param callback + * @return + */ + protected ResultSet doExecute(final BoundStatement bs) { + + return doExecute(new SessionCallback() { + + @Override + public ResultSet doInSession(Session s) throws DataAccessException { + return s.execute(bs); + } + }); + } + + /** + * @param row + * @return + */ + protected Object firstColumnToObject(Row row) { + ColumnDefinitions cols = row.getColumnDefinitions(); + if (cols.size() == 0) { + return null; + } + return cols.getType(0).deserialize(row.getBytesUnsafe(0)); + } + + /** + * @param row + * @return + */ + protected Map toMap(Row row) { + if (row == null) { + return null; + } + + ColumnDefinitions cols = row.getColumnDefinitions(); + Map map = new HashMap(cols.size()); + + for (Definition def : cols.asList()) { + String name = def.getName(); + DataType dataType = def.getType(); + map.put(name, def.getType().deserialize(row.getBytesUnsafe(name))); + } + + return map; + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#describeRing() + */ + @Override + public List describeRing() throws DataAccessException { + return new ArrayList(describeRing(new RingMemberHostMapper())); + } + + /** + * Pulls the list of Hosts for the current Session + * + * @return + */ + private Set getHosts() { + + /* + * Get the cluster metadata for this session + */ + Metadata clusterMetadata = doExecute(new SessionCallback() { + + @Override + public Metadata doInSession(Session s) throws DataAccessException { + return s.getCluster().getMetadata(); + } + + }); + + /* + * Get all hosts in the cluster + */ + Set hosts = clusterMetadata.getAllHosts(); + + return hosts; + + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#describeRing(org.springframework.cassandra.core.HostMapper) + */ + @Override + public Collection describeRing(HostMapper hostMapper) throws DataAccessException { + Set hosts = getHosts(); + return hostMapper.mapHosts(hosts); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#executeAsynchronously(java.lang.String) + */ + @Override + public void executeAsynchronously(final String cql) throws DataAccessException { + execute(new SessionCallback() { + @Override + public Object doInSession(Session s) throws DataAccessException { + return s.executeAsync(cql); + } + }); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#process(com.datastax.driver.core.ResultSet, org.springframework.cassandra.core.RowCallbackHandler) + */ + @Override + public void process(ResultSet resultSet, RowCallbackHandler rch) throws DataAccessException { + try { + for (Row row : resultSet.all()) { + rch.processRow(row); + } + } catch (DriverException dx) { + throwTranslated(dx); + } + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#process(com.datastax.driver.core.ResultSet, org.springframework.cassandra.core.RowMapper) + */ + @Override + public List process(ResultSet resultSet, RowMapper rowMapper) throws DataAccessException { + List mappedRows = new ArrayList(); + try { + int i = 0; + for (Row row : resultSet.all()) { + mappedRows.add(rowMapper.mapRow(row, i++)); + } + } catch (DriverException dx) { + throwTranslated(dx); + } + return mappedRows; + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#processOne(com.datastax.driver.core.ResultSet, org.springframework.cassandra.core.RowMapper) + */ + @Override + public T processOne(ResultSet resultSet, RowMapper rowMapper) throws DataAccessException { + T row = null; + Assert.notNull(resultSet, "ResultSet cannot be null"); + try { + List rows = resultSet.all(); + Assert.notNull(rows, "null row list returned from query"); + Assert.isTrue(rows.size() == 1, "row list has " + rows.size() + " rows instead of one"); + row = rowMapper.mapRow(rows.get(0), 0); + } catch (DriverException dx) { + throwTranslated(dx); + } + return row; + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#processOne(com.datastax.driver.core.ResultSet, java.lang.Class) + */ + @SuppressWarnings("unchecked") + @Override + public T processOne(ResultSet resultSet, Class requiredType) throws DataAccessException { + if (resultSet == null) { + return null; + } + Row row = resultSet.one(); + if (row == null) { + return null; + } + return (T) firstColumnToObject(row); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#processMap(com.datastax.driver.core.ResultSet) + */ + @Override + public Map processMap(ResultSet resultSet) throws DataAccessException { + if (resultSet == null) { + return null; + } + return toMap(resultSet.one()); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#processList(com.datastax.driver.core.ResultSet, java.lang.Class) + */ + @Override + public List processList(ResultSet resultSet, Class elementType) throws DataAccessException { + List rows = resultSet.all(); + List list = new ArrayList(rows.size()); + for (Row row : rows) { + list.add((T) firstColumnToObject(row)); + } + return list; + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#processListOfMap(com.datastax.driver.core.ResultSet) + */ + @Override + public List> processListOfMap(ResultSet resultSet) throws DataAccessException { + List rows = resultSet.all(); + List> list = new ArrayList>(rows.size()); + for (Row row : rows) { + list.add(toMap(row)); + } + return list; + } + + /** + * Attempt to translate a Runtime Exception to a Spring Data Exception + * + * @param ex + * @return + */ + protected RuntimeException throwTranslated(RuntimeException ex) { + RuntimeException resolved = getExceptionTranslator().translateExceptionIfPossible(ex); + return resolved == null ? ex : resolved; + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#execute(org.springframework.cassandra.core.PreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementCallback) + */ + @Override + public T execute(PreparedStatementCreator psc, PreparedStatementCallback action) { + + try { + PreparedStatement ps = psc.createPreparedStatement(getSession()); + return action.doInPreparedStatement(ps); + } catch (DriverException dx) { + throwTranslated(dx); + } + + return null; + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#execute(java.lang.String, org.springframework.cassandra.core.PreparedStatementCallback) + */ + @Override + public T execute(String cql, PreparedStatementCallback action) { + return execute(new SimplePreparedStatementCreator(cql), action); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#query(org.springframework.cassandra.core.PreparedStatementCreator, org.springframework.cassandra.core.ResultSetExtractor) + */ + @Override + public T query(PreparedStatementCreator psc, ResultSetExtractor rse) throws DataAccessException { + return query(psc, null, rse); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#query(org.springframework.cassandra.core.PreparedStatementCreator, org.springframework.cassandra.core.RowCallbackHandler) + */ + @Override + public void query(PreparedStatementCreator psc, RowCallbackHandler rch) throws DataAccessException { + query(psc, null, rch); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#query(org.springframework.cassandra.core.PreparedStatementCreator, org.springframework.cassandra.core.RowMapper) + */ + @Override + public List query(PreparedStatementCreator psc, RowMapper rowMapper) throws DataAccessException { + return query(psc, null, rowMapper); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#query(org.springframework.cassandra.core.PreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementSetter, org.springframework.cassandra.core.ResultSetExtractor) + */ + public T query(PreparedStatementCreator psc, final PreparedStatementBinder psb, final ResultSetExtractor rse) + throws DataAccessException { + + Assert.notNull(rse, "ResultSetExtractor must not be null"); + logger.debug("Executing prepared CQL query"); + + return execute(psc, new PreparedStatementCallback() { + public T doInPreparedStatement(PreparedStatement ps) throws DriverException { + ResultSet rs = null; + BoundStatement bs = null; + if (psb != null) { + bs = psb.bindValues(ps); + } else { + bs = ps.bind(); + } + rs = doExecute(bs); + return rse.extractData(rs); + } + }); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementSetter, org.springframework.cassandra.core.ResultSetExtractor) + */ + @Override + public T query(String cql, PreparedStatementBinder psb, ResultSetExtractor rse) throws DataAccessException { + return query(new SimplePreparedStatementCreator(cql), psb, rse); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementSetter, org.springframework.cassandra.core.RowCallbackHandler) + */ + @Override + public void query(String cql, PreparedStatementBinder psb, RowCallbackHandler rch) throws DataAccessException { + query(new SimplePreparedStatementCreator(cql), psb, rch); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementSetter, org.springframework.cassandra.core.RowMapper) + */ + @Override + public List query(String cql, PreparedStatementBinder psb, RowMapper rowMapper) throws DataAccessException { + return query(new SimplePreparedStatementCreator(cql), psb, rowMapper); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#query(org.springframework.cassandra.core.PreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowCallbackHandler) + */ + @Override + public void query(PreparedStatementCreator psc, final PreparedStatementBinder psb, final RowCallbackHandler rch) + throws DataAccessException { + Assert.notNull(rch, "RowCallbackHandler must not be null"); + logger.debug("Executing prepared CQL query"); + + execute(psc, new PreparedStatementCallback() { + public Object doInPreparedStatement(PreparedStatement ps) throws DriverException { + ResultSet rs = null; + BoundStatement bs = null; + if (psb != null) { + bs = psb.bindValues(ps); + } else { + bs = ps.bind(); + } + rs = doExecute(bs); + process(rs, rch); + return null; + } + }); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CassandraOperations#query(org.springframework.cassandra.core.PreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowMapper) + */ + @Override + public List query(PreparedStatementCreator psc, final PreparedStatementBinder psb, final RowMapper rowMapper) + throws DataAccessException { + Assert.notNull(rowMapper, "RowMapper must not be null"); + logger.debug("Executing prepared CQL query"); + + return execute(psc, new PreparedStatementCallback>() { + public List doInPreparedStatement(PreparedStatement ps) throws DriverException { + ResultSet rs = null; + BoundStatement bs = null; + if (psb != null) { + bs = psb.bindValues(ps); + } else { + bs = ps.bind(); + } + rs = doExecute(bs); + + return process(rs, rowMapper); + } + }); + } +} \ No newline at end of file diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlParameter.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlParameter.java new file mode 100644 index 000000000..bfd5db193 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlParameter.java @@ -0,0 +1,139 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.cassandra.core; + +import java.util.LinkedList; +import java.util.List; + +import org.springframework.util.Assert; + +import com.datastax.driver.core.DataType; + +/** + * @author David Webb + * + */ +public class CqlParameter { + + /** The name of the parameter, if any */ + private String name; + + /** SQL type constant from {@link DataType} */ + private final DataType type; + + /** The scale to apply in case of a NUMERIC or DECIMAL type, if any */ + private Integer scale; + + /** + * Create a new anonymous CqlParameter, supplying the SQL type. + * + * @param type Cassandra Data Type of the parameter according to {@link DataType} + */ + public CqlParameter(DataType type) { + this.type = type; + } + + /** + * Create a new anonymous CqlParameter, supplying the SQL type. + * + * @param type Cassandra Data Type of the parameter according to {@link DataType} + * @param scale the number of digits after the decimal point + */ + public CqlParameter(DataType type, int scale) { + this.type = type; + this.scale = scale; + } + + /** + * Create a new CqlParameter, supplying name and SQL type. + * + * @param name name of the parameter, as used in input and output maps + * @param type Cassandra Data Type of the parameter according to {@link DataType} + */ + public CqlParameter(String name, DataType type) { + this.name = name; + this.type = type; + } + + /** + * Create a new CqlParameter, supplying name and SQL type. + * + * @param name name of the parameter, as used in input and output maps + * @param type Cassandra Data Type of the parameter according to {@link DataType} + * @param scale the number of digits after the decimal point (for DECIMAL and NUMERIC types) + */ + public CqlParameter(String name, DataType type, int scale) { + this.name = name; + this.type = type; + this.scale = scale; + } + + /** + * Copy constructor. + * + * @param otherParam the CqlParameter object to copy from + */ + public CqlParameter(CqlParameter otherParam) { + Assert.notNull(otherParam, "CqlParameter object must not be null"); + this.name = otherParam.name; + this.type = otherParam.type; + this.scale = otherParam.scale; + } + + /** + * Return the name of the parameter. + */ + public String getName() { + return this.name; + } + + /** + * Return the SQL type of the parameter. + */ + public DataType getType() { + return this.type; + } + + /** + * Return the scale of the parameter, if any. + */ + public Integer getScale() { + return this.scale; + } + + /** + * Return whether this parameter holds input values that should be set before execution even if they are {@code null}. + *

+ * This implementation always returns {@code true}. + */ + public boolean isInputValueProvided() { + return true; + } + + /** + * Convert a list of JDBC types, as defined in {@code java.sql.Types}, to a List of CqlParameter objects as used in + * this package. + */ + public static List sqlTypesToAnonymousParameterList(DataType[] types) { + List result = new LinkedList(); + if (types != null) { + for (DataType type : types) { + result.add(new CqlParameter(type)); + } + } + return result; + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlParameterValue.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlParameterValue.java new file mode 100644 index 000000000..c2932815f --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlParameterValue.java @@ -0,0 +1,68 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.cassandra.core; + +import com.datastax.driver.core.DataType; + +/** + * @author David Webb + * + */ +public class CqlParameterValue extends CqlParameter { + + private final Object value; + + /** + * Create a new CqlParameterValue, supplying the Cassandra DataType. + * + * @param type Cassandra Data Type of the parameter according to {@link DataType} + * @param value the value object + */ + public CqlParameterValue(DataType type, Object value) { + super(type); + this.value = value; + } + + /** + * Create a new CqlParameterValue, supplying the Cassandra DataType. + * + * @param type Cassandra Data Type of the parameter according to {@link DataType} + * @param scale the number of digits after the decimal point (for DECIMAL and NUMERIC types) + * @param value the value object + */ + public CqlParameterValue(DataType type, int scale, Object value) { + super(type, scale); + this.value = value; + } + + /** + * Create a new CqlParameterValue based on the given CqlParameter declaration. + * + * @param declaredParam the declared CqlParameter to define a value for + * @param value the value object + */ + public CqlParameterValue(CqlParameter declaredParam, Object value) { + super(declaredParam); + this.value = value; + } + + /** + * Return the value object that this parameter value holds. + */ + public Object getValue() { + return this.value; + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlProvider.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlProvider.java new file mode 100644 index 000000000..7b0ddd59d --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlProvider.java @@ -0,0 +1,26 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.cassandra.core; + +/** + * @author David Webb + * + */ +public interface CqlProvider { + + String getCql(); + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/HostMapper.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/HostMapper.java new file mode 100644 index 000000000..66b81f3da --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/HostMapper.java @@ -0,0 +1,13 @@ +package org.springframework.cassandra.core; + +import java.util.Collection; +import java.util.Set; + +import com.datastax.driver.core.Host; +import com.datastax.driver.core.exceptions.DriverException; + +public interface HostMapper { + + Collection mapHosts(Set host) throws DriverException; + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/KeyType.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/KeyType.java new file mode 100644 index 000000000..692920091 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/KeyType.java @@ -0,0 +1,19 @@ +package org.springframework.cassandra.core; + +/** + * Values representing primary key column types. + * + * @author Matthew T. Adams + */ +public enum KeyType { + + /** + * Used for a column that is a primary key and that also is or is part of the partition key. + */ + PARTITION, + + /** + * Used for a primary key column that is not part of the partition key. + */ + PRIMARY +} \ No newline at end of file diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/Keyspace.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/Keyspace.java new file mode 100644 index 000000000..de9d9d7cd --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/Keyspace.java @@ -0,0 +1,49 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.cassandra.core; + +import com.datastax.driver.core.Session; + +/** + * Simple Cassandra Keyspace object + * + * @author Alex Shvid + */ +public class Keyspace { + + private final String keyspace; + private final Session session; + + /** + * Constructor used for a basic keyspace configuration + * + * @param keyspace, system if {@literal null}. + * @param session must not be {@literal null}. + * @param cassandraConverter must not be {@literal null}. + */ + public Keyspace(String keyspace, Session session) { + this.keyspace = keyspace; + this.session = session; + } + + public String getKeyspace() { + return keyspace; + } + + public Session getSession() { + return session; + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/Ordering.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/Ordering.java new file mode 100644 index 000000000..a9c36d2bb --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/Ordering.java @@ -0,0 +1,32 @@ +package org.springframework.cassandra.core; + +/** + * Enum for Cassandra primary key column ordering. + * + * @author Matthew T. Adams + */ +public enum Ordering { + + /** + * Ascending Cassandra column ordering. + */ + ASCENDING("ASC"), + + /** + * Descending Cassandra column ordering. + */ + DESCENDING("DESC"); + + private String cql; + + private Ordering(String cql) { + this.cql = cql; + } + + /** + * Returns the CQL keyword of this {@link Ordering}. + */ + public String cql() { + return cql; + } +} \ No newline at end of file diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementBinder.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementBinder.java new file mode 100644 index 000000000..f4bd1cca2 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementBinder.java @@ -0,0 +1,30 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.cassandra.core; + +import com.datastax.driver.core.BoundStatement; +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.exceptions.DriverException; + +/** + * @author David Webb + * + */ +public interface PreparedStatementBinder { + + BoundStatement bindValues(PreparedStatement ps) throws DriverException; + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementCallback.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementCallback.java new file mode 100644 index 000000000..1b2ba5fda --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementCallback.java @@ -0,0 +1,31 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.cassandra.core; + +import org.springframework.dao.DataAccessException; + +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.exceptions.DriverException; + +/** + * @author David Webb + * + */ +public interface PreparedStatementCallback { + + T doInPreparedStatement(PreparedStatement ps) throws DriverException, DataAccessException; + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementCreator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementCreator.java new file mode 100644 index 000000000..d95e92862 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementCreator.java @@ -0,0 +1,41 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.cassandra.core; + +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.exceptions.DriverException; + +/** + * Creates a PreparedStatement for the usage with the DataStax Java Driver + * + * @author David Webb + * + */ +public interface PreparedStatementCreator { + + /** + * Create a statement in this session. Allows implementations to use PreparedStatements. The CassandraTemlate will + * attempt to cache the PreparedStatement for future use without the overhead of re-preparing on the entire cluster. + * + * @param session Session to use to create statement + * @return a prepared statement + * @throws DriverException there is no need to catch DriverException that may be thrown in the implementation of this + * method. The CassandraTemlate class will handle them. + */ + PreparedStatement createPreparedStatement(Session session) throws DriverException; + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementCreatorFactory.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementCreatorFactory.java new file mode 100644 index 000000000..974b0436e --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementCreatorFactory.java @@ -0,0 +1,202 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.cassandra.core; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.util.Assert; + +import com.datastax.driver.core.BoundStatement; +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.exceptions.DriverException; + +/** + * @author David Webb + * + */ +public class PreparedStatementCreatorFactory { + + /** + * The CQL, which won't change when the parameters change + */ + private final String cql; + + /** List of CqlParameter objects. May not be {@code null}. */ + private final List declaredParameters; + + /** + * Create a new factory. + */ + public PreparedStatementCreatorFactory(String cql) { + this.cql = cql; + this.declaredParameters = new LinkedList(); + } + + /** + * Create a new factory with the given CQL and parameters. + * + * @param cql CQL + * @param declaredParameters list of {@link CqlParameter} objects + * @see CqlParameter + */ + public PreparedStatementCreatorFactory(String cql, List declaredParameters) { + this.cql = cql; + this.declaredParameters = declaredParameters; + } + + /** + * Return a new PreparedStatementBinder for the given parameters. + * + * @param params list of parameters (may be {@code null}) + */ + public PreparedStatementBinder newPreparedStatementBinder(List params) { + return new PreparedStatementCreatorImpl(params != null ? params : Collections.emptyList()); + } + + /** + * Return a new PreparedStatementBinder for the given parameters. + * + * @param params the parameter array (may be {@code null}) + */ + public PreparedStatementBinder newPreparedStatementBinder(Object[] params) { + return new PreparedStatementCreatorImpl(params != null ? Arrays.asList(params) : Collections.emptyList()); + } + + /** + * Return a new PreparedStatementCreator for the given parameters. + * + * @param params list of parameters (may be {@code null}) + */ + public PreparedStatementCreator newPreparedStatementCreator(List params) { + return new PreparedStatementCreatorImpl(params != null ? params : Collections.emptyList()); + } + + /** + * Return a new PreparedStatementCreator for the given parameters. + * + * @param params the parameter array (may be {@code null}) + */ + public PreparedStatementCreator newPreparedStatementCreator(Object[] params) { + return new PreparedStatementCreatorImpl(params != null ? Arrays.asList(params) : Collections.emptyList()); + } + + /** + * Return a new PreparedStatementCreator for the given parameters. + * + * @param sqlToUse the actual SQL statement to use (if different from the factory's, for example because of named + * parameter expanding) + * @param params the parameter array (may be {@code null}) + */ + public PreparedStatementCreator newPreparedStatementCreator(String sqlToUse, Object[] params) { + return new PreparedStatementCreatorImpl(sqlToUse, params != null ? Arrays.asList(params) : Collections.emptyList()); + } + + /** + * PreparedStatementCreator implementation returned by this class. + */ + private class PreparedStatementCreatorImpl implements PreparedStatementCreator, PreparedStatementBinder, CqlProvider { + + private final String actualCql; + + private final List parameters; + + public PreparedStatementCreatorImpl(List parameters) { + this(cql, parameters); + } + + /** + * @param actualCql + * @param parameters + */ + public PreparedStatementCreatorImpl(String actualCql, List parameters) { + this.actualCql = actualCql; + Assert.notNull(parameters, "Parameters List must not be null"); + this.parameters = parameters; + if (this.parameters.size() != declaredParameters.size()) { + Set names = new HashSet(); + for (int i = 0; i < parameters.size(); i++) { + Object param = parameters.get(i); + if (param instanceof CqlParameterValue) { + names.add(((CqlParameterValue) param).getName()); + } else { + names.add("Parameter #" + i); + } + } + if (names.size() != declaredParameters.size()) { + throw new InvalidDataAccessApiUsageException("CQL [" + cql + "]: given " + names.size() + + " parameters but expected " + declaredParameters.size()); + } + } + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.PreparedStatementCreator#createPreparedStatement(com.datastax.driver.core.Session) + */ + @Override + public PreparedStatement createPreparedStatement(Session session) throws DriverException { + return session.prepare(this.actualCql); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.PreparedStatementBinder#bindValues(com.datastax.driver.core.PreparedStatement) + */ + @Override + public BoundStatement bindValues(PreparedStatement ps) throws DriverException { + if (this.parameters == null || this.parameters.size() == 0) { + return ps.bind(); + } + + // Test the type of the first value + Object v = this.parameters.get(0); + Object[] values; + if (v instanceof CqlParameterValue) { + LinkedList valuesList = new LinkedList(); + for (Object value : this.parameters) { + valuesList.add(((CqlParameterValue) value).getValue()); + } + values = valuesList.toArray(); + } else { + values = this.parameters.toArray(); + } + + return ps.bind(values); + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CqlProvider#getCql() + */ + @Override + public String getCql() { + return cql; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("PreparedStatementCreatorFactory.PreparedStatementCreatorImpl: cql=["); + sb.append(cql).append("]; parameters=").append(this.parameters); + return sb.toString(); + } + + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementCreatorImpl.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementCreatorImpl.java new file mode 100644 index 000000000..2bedb7f67 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/PreparedStatementCreatorImpl.java @@ -0,0 +1,73 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.cassandra.core; + +import java.util.List; + +import com.datastax.driver.core.BoundStatement; +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.exceptions.DriverException; + +/** + * @author David Webb + * + */ +public class PreparedStatementCreatorImpl implements PreparedStatementCreator, CqlProvider, PreparedStatementBinder { + + private final String cql; + private List values; + + public PreparedStatementCreatorImpl(String cql) { + this.cql = cql; + } + + public PreparedStatementCreatorImpl(String cql, List values) { + this.cql = cql; + this.values = values; + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.PreparedStatementSetter#setValues(com.datastax.driver.core.PreparedStatement) + */ + @Override + public BoundStatement bindValues(PreparedStatement ps) throws DriverException { + // Nothing to set if there are no values + if (values == null) { + return null; + } + + return ps.bind(values.toArray()); + + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CqlProvider#getCql() + */ + @Override + public String getCql() { + return this.cql; + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.PreparedStatementCreator#createPreparedStatement(com.datastax.driver.core.Session) + */ + @Override + public PreparedStatement createPreparedStatement(Session session) throws DriverException { + return session.prepare(this.cql); + } + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/ResultSetExtractor.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/ResultSetExtractor.java new file mode 100644 index 000000000..94b03aae9 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/ResultSetExtractor.java @@ -0,0 +1,11 @@ +package org.springframework.cassandra.core; + +import org.springframework.dao.DataAccessException; + +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.exceptions.DriverException; + +public interface ResultSetExtractor { + + T extractData(ResultSet rs) throws DriverException, DataAccessException; +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/ResultSetFutureExtractor.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/ResultSetFutureExtractor.java new file mode 100644 index 000000000..7c52af2eb --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/ResultSetFutureExtractor.java @@ -0,0 +1,11 @@ +package org.springframework.cassandra.core; + +import org.springframework.dao.DataAccessException; + +import com.datastax.driver.core.ResultSetFuture; +import com.datastax.driver.core.exceptions.DriverException; + +public interface ResultSetFutureExtractor { + + T extractData(ResultSetFuture rs) throws DriverException, DataAccessException; +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/RingMember.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/RingMember.java new file mode 100644 index 000000000..705d1b6b7 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/RingMember.java @@ -0,0 +1,43 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.cassandra.core; + +import java.io.Serializable; + +import com.datastax.driver.core.Host; + +/** + * @author David Webb + * + */ +public final class RingMember implements Serializable { + + /* + * Ring attributes + */ + public String hostName; + public String address; + public String DC; + public String rack; + + public RingMember(Host h) { + this.hostName = h.getAddress().getHostName(); + this.address = h.getAddress().getHostAddress(); + this.DC = h.getDatacenter(); + this.rack = h.getRack(); + } + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/RingMemberHostMapper.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/RingMemberHostMapper.java new file mode 100644 index 000000000..d4a0e44ed --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/RingMemberHostMapper.java @@ -0,0 +1,54 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.cassandra.core; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.springframework.util.Assert; + +import com.datastax.driver.core.Host; +import com.datastax.driver.core.exceptions.DriverException; + +/** + * @author David Webb + * @param + * + */ +public class RingMemberHostMapper implements HostMapper { + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.HostMapper#mapHosts(java.util.Set) + */ + @Override + public List mapHosts(Set hosts) throws DriverException { + + List members = new ArrayList(); + + Assert.notNull(hosts); + Assert.notEmpty(hosts); + + RingMember r = null; + for (Host host : hosts) { + r = new RingMember(host); + members.add(r); + } + + return members; + + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/RowCallback.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/RowCallback.java new file mode 100644 index 000000000..57b12493f --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/RowCallback.java @@ -0,0 +1,29 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.cassandra.core; + +import com.datastax.driver.core.Row; + +/** + * Simple internal callback to allow operations on a {@link Row}. + * + * @author Alex Shvid + */ + +public interface RowCallback { + + T doWith(Row object); +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/RowCallbackHandler.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/RowCallbackHandler.java new file mode 100644 index 000000000..8eaf6da8e --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/RowCallbackHandler.java @@ -0,0 +1,10 @@ +package org.springframework.cassandra.core; + +import com.datastax.driver.core.Row; +import com.datastax.driver.core.exceptions.DriverException; + +public interface RowCallbackHandler { + + void processRow(Row row) throws DriverException; + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/RowMapper.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/RowMapper.java new file mode 100644 index 000000000..4de62c128 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/RowMapper.java @@ -0,0 +1,10 @@ +package org.springframework.cassandra.core; + +import com.datastax.driver.core.Row; +import com.datastax.driver.core.exceptions.DriverException; + +public interface RowMapper { + + T mapRow(Row row, int rowNum) throws DriverException; + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/SessionCallback.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/SessionCallback.java new file mode 100644 index 000000000..96a8d8167 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/SessionCallback.java @@ -0,0 +1,40 @@ +/* + * Copyright 2010-2011 the original author or authors. + * + * 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.springframework.cassandra.core; + +import org.springframework.dao.DataAccessException; + +import com.datastax.driver.core.Session; + +/** + * Interface for operations on a Cassandra Session. + * + * @author David Webb + * + * @param + */ +public interface SessionCallback { + + /** + * Perform the operation in the given Session + * + * @param s + * @return + * @throws DataAccessException + */ + T doInSession(Session s) throws DataAccessException; + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/SessionFactoryBean.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/SessionFactoryBean.java new file mode 100644 index 000000000..f753a322f --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/SessionFactoryBean.java @@ -0,0 +1,87 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.cassandra.core; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.cassandra.core.Keyspace; + +import com.datastax.driver.core.Session; + +/** + * @author David Webb + * + */ +public class SessionFactoryBean implements FactoryBean, InitializingBean { + + private Keyspace keyspace; + + public SessionFactoryBean() { + } + + public SessionFactoryBean(Keyspace keyspace) { + setKeyspace(keyspace); + } + + /* (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + @Override + public void afterPropertiesSet() throws Exception { + if (keyspace == null) { + throw new IllegalStateException("Keyspace required."); + } + } + + /** + * @return Returns the keyspace. + */ + public Keyspace getKeyspace() { + return keyspace; + } + + /** + * @param keyspace The keyspace to set. + */ + public void setKeyspace(Keyspace keyspace) { + this.keyspace = keyspace; + } + + /* (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#getObject() + */ + @Override + public Session getObject() { + return keyspace.getSession(); + } + + /* (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + @Override + public Class getObjectType() { + return Session.class; + } + + /* (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#isSingleton() + */ + @Override + public boolean isSingleton() { + return true; + } + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/SimplePreparedStatementCreator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/SimplePreparedStatementCreator.java new file mode 100644 index 000000000..f2ae91a5c --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/SimplePreparedStatementCreator.java @@ -0,0 +1,58 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.cassandra.core; + +import org.springframework.util.Assert; + +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.exceptions.DriverException; + +/** + * @author David Webb + * + */ +public class SimplePreparedStatementCreator implements PreparedStatementCreator, CqlProvider { + + private final String cql; + + /** + * Create a PreparedStatementCreator from the provided CQL. + * + * @param cql + */ + public SimplePreparedStatementCreator(String cql) { + Assert.notNull(cql, "CQL is required to create a PreparedStatement"); + this.cql = cql; + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.CqlProvider#getCql() + */ + @Override + public String getCql() { + return this.cql; + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.PreparedStatementCreator#createPreparedStatement(com.datastax.driver.core.Session) + */ + @Override + public PreparedStatement createPreparedStatement(Session session) throws DriverException { + return session.prepare(this.cql); + } + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/CqlStringUtils.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/CqlStringUtils.java new file mode 100644 index 000000000..614dd14ab --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/CqlStringUtils.java @@ -0,0 +1,117 @@ +package org.springframework.cassandra.core.cql; + +import java.util.regex.Pattern; + +public class CqlStringUtils { + + protected static final String SINGLE_QUOTE = "\'"; + protected static final String DOUBLE_SINGLE_QUOTE = "\'\'"; + protected static final String DOUBLE_QUOTE = "\""; + protected static final String DOUBLE_DOUBLE_QUOTE = "\"\""; + + public static StringBuilder noNull(StringBuilder sb) { + return sb == null ? new StringBuilder() : sb; + } + + public static final String UNESCAPED_DOUBLE_QUOTE_REGEX = "TODO"; + public static final Pattern UNESCAPED_DOUBLE_QUOTE_PATTERN = Pattern.compile(UNESCAPED_DOUBLE_QUOTE_REGEX); + + public static final String UNQUOTED_IDENTIFIER_REGEX = "[a-zA-Z_][a-zA-Z0-9_]*"; + public static final Pattern UNQUOTED_IDENTIFIER_PATTERN = Pattern.compile(UNQUOTED_IDENTIFIER_REGEX); + + public static boolean isUnquotedIdentifier(CharSequence chars) { + return UNQUOTED_IDENTIFIER_PATTERN.matcher(chars).matches(); + } + + public static void checkUnquotedIdentifier(CharSequence chars) { + if (!CqlStringUtils.isUnquotedIdentifier(chars)) { + throw new IllegalArgumentException("[" + chars + "] is not a valid CQL identifier"); + } + } + + public static final String QUOTED_IDENTIFIER_REGEX = "[a-zA-Z_]([a-zA-Z0-9_]|\"{2}+)*"; + public static final Pattern QUOTED_IDENTIFIER_PATTERN = Pattern.compile(QUOTED_IDENTIFIER_REGEX); + + public static boolean isQuotedIdentifier(CharSequence chars) { + return QUOTED_IDENTIFIER_PATTERN.matcher(chars).matches(); + } + + public static void checkQuotedIdentifier(CharSequence chars) { + if (!CqlStringUtils.isQuotedIdentifier(chars)) { + throw new IllegalArgumentException("[" + chars + "] is not a valid CQL quoted identifier"); + } + } + + public static boolean isIdentifier(CharSequence chars) { + return isUnquotedIdentifier(chars) || isQuotedIdentifier(chars); + } + + public static void checkIdentifier(CharSequence chars) { + if (!CqlStringUtils.isIdentifier(chars)) { + throw new IllegalArgumentException("[" + chars + "] is not a valid CQL quoted or unquoted identifier"); + } + } + + /** + * Renders the given string as a legal Cassandra identifier. + *
    + *
  • If the given identifier is a legal unquoted identifier, it is returned unchanged.
  • + *
  • If the given identifier is a legal quoted identifier, it is returned encased in double quotes.
  • + *
  • If the given identifier is illegal, an {@link IllegalArgumentException} is thrown.
  • + *
+ */ + public static String identifize(String candidate) { + + checkIdentifier(candidate); + + if (isUnquotedIdentifier(candidate)) { + return candidate; + } + // else it must be quoted + return doubleQuote(candidate); + } + + /** + * Renders the given string as a legal Cassandra string column or table option value, by escaping single quotes and + * encasing the result in single quotes. Given null, returns null. + */ + public static String valuize(String candidate) { + + if (candidate == null) { + return null; + } + return singleQuote(escapeSingle(candidate)); + } + + /** + * Doubles single quote characters (' -> ''). Given null, returns null. + */ + public static String escapeSingle(Object things) { + return things == null ? (String) null : things.toString().replace(SINGLE_QUOTE, DOUBLE_SINGLE_QUOTE); + } + + /** + * Doubles double quote characters (" -> ""). Given null, returns null. + */ + public static String escapeDouble(Object things) { + return things == null ? (String) null : things.toString().replace(DOUBLE_QUOTE, DOUBLE_DOUBLE_QUOTE); + } + + /** + * Surrounds given object's {@link Object#toString()} with single quotes. Given null, returns + * null. + */ + public static String singleQuote(Object thing) { + return thing == null ? (String) null : new StringBuilder().append(SINGLE_QUOTE).append(thing).append(SINGLE_QUOTE) + .toString(); + } + + /** + * Surrounds given object's {@link Object#toString()} with double quotes. Given null, returns + * null. + */ + public static String doubleQuote(Object thing) { + return thing == null ? (String) null : new StringBuilder().append(DOUBLE_QUOTE).append(thing).append(DOUBLE_QUOTE) + .toString(); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AddColumnCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AddColumnCqlGenerator.java new file mode 100644 index 000000000..e64d1acf7 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AddColumnCqlGenerator.java @@ -0,0 +1,22 @@ +package org.springframework.cassandra.core.cql.generator; + +import static org.springframework.cassandra.core.cql.CqlStringUtils.noNull; + +import org.springframework.cassandra.core.keyspace.AddColumnSpecification; + +/** + * CQL generator for generating an ADD clause of an ALTER TABLE statement. + * + * @author Matthew T. Adams + */ +public class AddColumnCqlGenerator extends ColumnChangeCqlGenerator { + + public AddColumnCqlGenerator(AddColumnSpecification specification) { + super(specification); + } + + public StringBuilder toCql(StringBuilder cql) { + return noNull(cql).append("ADD ").append(spec().getNameAsIdentifier()).append(" TYPE ") + .append(spec().getType().getName()); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AlterColumnCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AlterColumnCqlGenerator.java new file mode 100644 index 000000000..51759379e --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AlterColumnCqlGenerator.java @@ -0,0 +1,22 @@ +package org.springframework.cassandra.core.cql.generator; + +import static org.springframework.cassandra.core.cql.CqlStringUtils.noNull; + +import org.springframework.cassandra.core.keyspace.AlterColumnSpecification; + +/** + * CQL generator for generating an ALTER column clause of an ALTER TABLE statement. + * + * @author Matthew T. Adams + */ +public class AlterColumnCqlGenerator extends ColumnChangeCqlGenerator { + + public AlterColumnCqlGenerator(AlterColumnSpecification specification) { + super(specification); + } + + public StringBuilder toCql(StringBuilder cql) { + return noNull(cql).append("ALTER ").append(spec().getNameAsIdentifier()).append(" TYPE ") + .append(spec().getType().getName()); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AlterTableCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AlterTableCqlGenerator.java new file mode 100644 index 000000000..64ed9e5b7 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AlterTableCqlGenerator.java @@ -0,0 +1,106 @@ +package org.springframework.cassandra.core.cql.generator; + +import static org.springframework.cassandra.core.cql.CqlStringUtils.noNull; + +import java.util.Map; + +import org.springframework.cassandra.core.keyspace.AddColumnSpecification; +import org.springframework.cassandra.core.keyspace.AlterColumnSpecification; +import org.springframework.cassandra.core.keyspace.AlterTableSpecification; +import org.springframework.cassandra.core.keyspace.ColumnChangeSpecification; +import org.springframework.cassandra.core.keyspace.DropColumnSpecification; +import org.springframework.cassandra.core.keyspace.Option; + +/** + * CQL generator for generating ALTER TABLE statements. + * + * @author Matthew T. Adams + */ +public class AlterTableCqlGenerator extends TableOptionsCqlGenerator { + + public AlterTableCqlGenerator(AlterTableSpecification specification) { + super(specification); + } + + public StringBuilder toCql(StringBuilder cql) { + cql = noNull(cql); + + preambleCql(cql); + changesCql(cql); + optionsCql(cql); + + cql.append(";"); + + return cql; + } + + protected StringBuilder preambleCql(StringBuilder cql) { + return noNull(cql).append("ALTER TABLE ").append(spec().getNameAsIdentifier()).append(" "); + } + + protected StringBuilder changesCql(StringBuilder cql) { + cql = noNull(cql); + + boolean first = true; + for (ColumnChangeSpecification change : spec().getChanges()) { + if (first) { + first = false; + } else { + cql.append(" "); + } + getCqlGeneratorFor(change).toCql(cql); + } + + return cql; + } + + protected ColumnChangeCqlGenerator getCqlGeneratorFor(ColumnChangeSpecification change) { + if (change instanceof AddColumnSpecification) { + return new AddColumnCqlGenerator((AddColumnSpecification) change); + } + if (change instanceof DropColumnSpecification) { + return new DropColumnCqlGenerator((DropColumnSpecification) change); + } + if (change instanceof AlterColumnSpecification) { + return new AlterColumnCqlGenerator((AlterColumnSpecification) change); + } + throw new Error("unknown ColumnChangeSpecification type: " + change.getClass().getName()); + } + + @SuppressWarnings("unchecked") + protected StringBuilder optionsCql(StringBuilder cql) { + cql = noNull(cql); + + Map options = spec().getOptions(); + if (options == null || options.isEmpty()) { + return cql; + } + + cql.append(" WITH "); + boolean first = true; + for (String key : options.keySet()) { + if (first) { + first = false; + } else { + cql.append(" AND "); + } + + cql.append(key); + + Object value = options.get(key); + if (value == null) { + continue; + } + cql.append(" = "); + + if (value instanceof Map) { + optionValueMap((Map) value, cql); + continue; + } + + // else just use value as string + cql.append(value.toString()); + } + return cql; + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/ColumnChangeCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/ColumnChangeCqlGenerator.java new file mode 100644 index 000000000..ee1402f13 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/ColumnChangeCqlGenerator.java @@ -0,0 +1,35 @@ +package org.springframework.cassandra.core.cql.generator; + +import org.springframework.cassandra.core.keyspace.ColumnChangeSpecification; +import org.springframework.util.Assert; + +/** + * Base class for column change CQL generators. + * + * @author Matthew T. Adams + * @param The corresponding {@link ColumnChangeSpecification} type for this CQL generator. + */ +public abstract class ColumnChangeCqlGenerator { + + public abstract StringBuilder toCql(StringBuilder cql); + + private ColumnChangeSpecification specification; + + public ColumnChangeCqlGenerator(ColumnChangeSpecification specification) { + setSpecification(specification); + } + + protected void setSpecification(ColumnChangeSpecification specification) { + Assert.notNull(specification); + this.specification = specification; + } + + @SuppressWarnings("unchecked") + public T getSpecification() { + return (T) specification; + } + + protected T spec() { + return getSpecification(); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/CreateTableCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/CreateTableCqlGenerator.java new file mode 100644 index 000000000..ad433a5cb --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/CreateTableCqlGenerator.java @@ -0,0 +1,173 @@ +package org.springframework.cassandra.core.cql.generator; + +import static org.springframework.cassandra.core.cql.CqlStringUtils.noNull; +import static org.springframework.cassandra.core.KeyType.PARTITION; +import static org.springframework.cassandra.core.KeyType.PRIMARY; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.springframework.cassandra.core.keyspace.ColumnSpecification; +import org.springframework.cassandra.core.keyspace.CreateTableSpecification; +import org.springframework.cassandra.core.keyspace.Option; + +/** + * CQL generator for generating a CREATE TABLE statement. + * + * @author Matthew T. Adams + */ +public class CreateTableCqlGenerator extends TableCqlGenerator { + + public CreateTableCqlGenerator(CreateTableSpecification specification) { + super(specification); + } + + public StringBuilder toCql(StringBuilder cql) { + + cql = noNull(cql); + + preambleCql(cql); + columnsAndOptionsCql(cql); + + cql.append(";"); + + return cql; + } + + protected StringBuilder preambleCql(StringBuilder cql) { + return noNull(cql).append("CREATE TABLE ").append(spec().getIfNotExists() ? "IF NOT EXISTS " : "") + .append(spec().getNameAsIdentifier()); + } + + @SuppressWarnings("unchecked") + protected StringBuilder columnsAndOptionsCql(StringBuilder cql) { + + cql = noNull(cql); + + // begin columns + cql.append(" ("); + + List partitionKeys = new ArrayList(); + List primaryKeys = new ArrayList(); + for (ColumnSpecification col : spec().getColumns()) { + col.toCql(cql).append(", "); + + if (col.getKeyType() == PARTITION) { + partitionKeys.add(col); + } else if (col.getKeyType() == PRIMARY) { + primaryKeys.add(col); + } + } + + // begin primary key clause + cql.append("PRIMARY KEY "); + StringBuilder partitions = new StringBuilder(); + StringBuilder primaries = new StringBuilder(); + + if (partitionKeys.size() > 1) { + partitions.append("("); + } + + boolean first = true; + for (ColumnSpecification col : partitionKeys) { + if (first) { + first = false; + } else { + partitions.append(", "); + } + partitions.append(col.getName()); + + } + if (partitionKeys.size() > 1) { + partitions.append(")"); + } + + StringBuilder clustering = null; + boolean clusteringFirst = true; + first = true; + for (ColumnSpecification col : primaryKeys) { + if (first) { + first = false; + } else { + primaries.append(", "); + } + primaries.append(col.getName()); + + if (col.getOrdering() != null) { // then ordering specified + if (clustering == null) { // then initialize clustering clause + clustering = new StringBuilder().append("CLUSTERING ORDER BY ("); + } + if (clusteringFirst) { + clusteringFirst = false; + } else { + clustering.append(", "); + } + clustering.append(col.getName()).append(" ").append(col.getOrdering().cql()); + } + } + if (clustering != null) { // then end clustering option + clustering.append(")"); + } + + boolean parenthesize = true;// partitionKeys.size() + primaryKeys.size() > 1; + + cql.append(parenthesize ? "(" : ""); + cql.append(partitions); + cql.append(primaryKeys.size() > 0 ? ", " : ""); + cql.append(primaries); + cql.append(parenthesize ? ")" : ""); + // end primary key clause + + cql.append(")"); + // end columns + + // begin options + // begin option clause + Map options = spec().getOptions(); + + if (clustering != null || !options.isEmpty()) { + + // option preamble + first = true; + cql.append(" WITH "); + // end option preamble + + if (clustering != null) { + cql.append(clustering); + first = false; + } + if (!options.isEmpty()) { + for (String name : options.keySet()) { + // append AND if we're not on first option + if (first) { + first = false; + } else { + cql.append(" AND "); + } + + // append = + cql.append(name); + + Object value = options.get(name); + if (value == null) { // then assume string-only, valueless option like "COMPACT STORAGE" + continue; + } + + cql.append(" = "); + + if (value instanceof Map) { + optionValueMap((Map) value, cql); + continue; // end non-empty value map + } + + // else just use value as string + cql.append(value.toString()); + } + } + } + // end options + + return cql; + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropColumnCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropColumnCqlGenerator.java new file mode 100644 index 000000000..1f10f6a30 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropColumnCqlGenerator.java @@ -0,0 +1,21 @@ +package org.springframework.cassandra.core.cql.generator; + +import static org.springframework.cassandra.core.cql.CqlStringUtils.noNull; + +import org.springframework.cassandra.core.keyspace.DropColumnSpecification; + +/** + * CQL generator for generating a DROP column clause of an ALTER TABLE statement. + * + * @author Matthew T. Adams + */ +public class DropColumnCqlGenerator extends ColumnChangeCqlGenerator { + + public DropColumnCqlGenerator(DropColumnSpecification specification) { + super(specification); + } + + public StringBuilder toCql(StringBuilder cql) { + return noNull(cql).append("DROP ").append(spec().getNameAsIdentifier()); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropTableCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropTableCqlGenerator.java new file mode 100644 index 000000000..21049e638 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropTableCqlGenerator.java @@ -0,0 +1,22 @@ +package org.springframework.cassandra.core.cql.generator; + +import static org.springframework.cassandra.core.cql.CqlStringUtils.noNull; + +import org.springframework.cassandra.core.keyspace.DropTableSpecification; + +/** + * CQL generator for generating a DROP TABLE statement. + * + * @author Matthew T. Adams + */ +public class DropTableCqlGenerator extends TableNameCqlGenerator { + + public DropTableCqlGenerator(DropTableSpecification specification) { + super(specification); + } + + public StringBuilder toCql(StringBuilder cql) { + return noNull(cql).append("DROP TABLE ").append(spec().getIfExists() ? "IF EXISTS " : "") + .append(spec().getNameAsIdentifier()).append(";"); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/TableCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/TableCqlGenerator.java new file mode 100644 index 000000000..3887f01bc --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/TableCqlGenerator.java @@ -0,0 +1,65 @@ +package org.springframework.cassandra.core.cql.generator; + +import static org.springframework.cassandra.core.cql.CqlStringUtils.escapeSingle; +import static org.springframework.cassandra.core.cql.CqlStringUtils.noNull; +import static org.springframework.cassandra.core.cql.CqlStringUtils.singleQuote; + +import java.util.Map; + +import org.springframework.cassandra.core.keyspace.Option; +import org.springframework.cassandra.core.keyspace.TableSpecification; + +/** + * Base class that contains behavior common to CQL generation for table operations. + * + * @author Matthew T. Adams + * @param T The subtype of this class for which this is a CQL generator. + */ +public abstract class TableCqlGenerator> extends + TableOptionsCqlGenerator> { + + public TableCqlGenerator(TableSpecification specification) { + super(specification); + } + + @SuppressWarnings("unchecked") + protected T spec() { + return (T) getSpecification(); + } + + protected StringBuilder optionValueMap(Map valueMap, StringBuilder cql) { + cql = noNull(cql); + + if (valueMap == null || valueMap.isEmpty()) { + return cql; + } + // else option value is a non-empty map + + // append { 'name' : 'value', ... } + cql.append("{ "); + boolean mapFirst = true; + for (Map.Entry entry : valueMap.entrySet()) { + if (mapFirst) { + mapFirst = false; + } else { + cql.append(", "); + } + + Option option = entry.getKey(); + cql.append(singleQuote(option.getName())); // entries in map keys are always quoted + cql.append(" : "); + Object entryValue = entry.getValue(); + entryValue = entryValue == null ? "" : entryValue.toString(); + if (option.escapesValue()) { + entryValue = escapeSingle(entryValue); + } + if (option.quotesValue()) { + entryValue = singleQuote(entryValue); + } + cql.append(entryValue); + } + cql.append(" }"); + + return cql; + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/TableNameCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/TableNameCqlGenerator.java new file mode 100644 index 000000000..b36c62b96 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/TableNameCqlGenerator.java @@ -0,0 +1,36 @@ +package org.springframework.cassandra.core.cql.generator; + +import org.springframework.cassandra.core.keyspace.TableNameSpecification; +import org.springframework.util.Assert; + +public abstract class TableNameCqlGenerator> { + + public abstract StringBuilder toCql(StringBuilder cql); + + private TableNameSpecification specification; + + public TableNameCqlGenerator(TableNameSpecification specification) { + setSpecification(specification); + } + + protected void setSpecification(TableNameSpecification specification) { + Assert.notNull(specification); + this.specification = specification; + } + + @SuppressWarnings("unchecked") + public T getSpecification() { + return (T) specification; + } + + /** + * Convenient synonymous method of {@link #getSpecification()}. + */ + protected T spec() { + return getSpecification(); + } + + public String toCql() { + return toCql(null).toString(); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/TableOptionsCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/TableOptionsCqlGenerator.java new file mode 100644 index 000000000..3ddbaa40a --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/TableOptionsCqlGenerator.java @@ -0,0 +1,65 @@ +package org.springframework.cassandra.core.cql.generator; + +import static org.springframework.cassandra.core.cql.CqlStringUtils.escapeSingle; +import static org.springframework.cassandra.core.cql.CqlStringUtils.noNull; +import static org.springframework.cassandra.core.cql.CqlStringUtils.singleQuote; + +import java.util.Map; + +import org.springframework.cassandra.core.keyspace.Option; +import org.springframework.cassandra.core.keyspace.TableOptionsSpecification; + +/** + * Base class that contains behavior common to CQL generation for table operations. + * + * @author Matthew T. Adams + * @param T The subtype of this class for which this is a CQL generator. + */ +public abstract class TableOptionsCqlGenerator> extends + TableNameCqlGenerator> { + + public TableOptionsCqlGenerator(TableOptionsSpecification specification) { + super(specification); + } + + @SuppressWarnings("unchecked") + protected T spec() { + return (T) getSpecification(); + } + + protected StringBuilder optionValueMap(Map valueMap, StringBuilder cql) { + cql = noNull(cql); + + if (valueMap == null || valueMap.isEmpty()) { + return cql; + } + // else option value is a non-empty map + + // append { 'name' : 'value', ... } + cql.append("{ "); + boolean mapFirst = true; + for (Map.Entry entry : valueMap.entrySet()) { + if (mapFirst) { + mapFirst = false; + } else { + cql.append(", "); + } + + Option option = entry.getKey(); + cql.append(singleQuote(option.getName())); // entries in map keys are always quoted + cql.append(" : "); + Object entryValue = entry.getValue(); + entryValue = entryValue == null ? "" : entryValue.toString(); + if (option.escapesValue()) { + entryValue = escapeSingle(entryValue); + } + if (option.quotesValue()) { + entryValue = singleQuote(entryValue); + } + cql.append(entryValue); + } + cql.append(" }"); + + return cql; + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/AddColumnSpecification.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/AddColumnSpecification.java new file mode 100644 index 000000000..e42adcefd --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/AddColumnSpecification.java @@ -0,0 +1,10 @@ +package org.springframework.cassandra.core.keyspace; + +import com.datastax.driver.core.DataType; + +public class AddColumnSpecification extends ColumnTypeChangeSpecification { + + public AddColumnSpecification(String name, DataType type) { + super(name, type); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/AlterColumnSpecification.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/AlterColumnSpecification.java new file mode 100644 index 000000000..d64fc6406 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/AlterColumnSpecification.java @@ -0,0 +1,10 @@ +package org.springframework.cassandra.core.keyspace; + +import com.datastax.driver.core.DataType; + +public class AlterColumnSpecification extends ColumnTypeChangeSpecification { + + public AlterColumnSpecification(String name, DataType type) { + super(name, type); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/AlterTableSpecification.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/AlterTableSpecification.java new file mode 100644 index 000000000..7e192133e --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/AlterTableSpecification.java @@ -0,0 +1,51 @@ +package org.springframework.cassandra.core.keyspace; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.datastax.driver.core.DataType; + +/** + * Builder class to construct an ALTER TABLE specification. + * + * @author Matthew T. Adams + */ +public class AlterTableSpecification extends TableOptionsSpecification { + + /** + * The list of column changes. + */ + private List changes = new ArrayList(); + + /** + * Adds a DROP to the list of column changes. + */ + public AlterTableSpecification drop(String column) { + changes.add(new DropColumnSpecification(column)); + return this; + } + + /** + * Adds an ADD to the list of column changes. + */ + public AlterTableSpecification add(String column, DataType type) { + changes.add(new AddColumnSpecification(column, type)); + return this; + } + + /** + * Adds an ALTER to the list of column changes. + */ + public AlterTableSpecification alter(String column, DataType type) { + changes.add(new AlterColumnSpecification(column, type)); + return this; + } + + /** + * Returns an unmodifiable list of column changes. + */ + public List getChanges() { + return Collections.unmodifiableList(changes); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/ColumnChangeSpecification.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/ColumnChangeSpecification.java new file mode 100644 index 000000000..6344b888f --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/ColumnChangeSpecification.java @@ -0,0 +1,26 @@ +package org.springframework.cassandra.core.keyspace; + +import static org.springframework.cassandra.core.cql.CqlStringUtils.checkIdentifier; +import static org.springframework.cassandra.core.cql.CqlStringUtils.identifize; + +public abstract class ColumnChangeSpecification { + + private String name; + + public ColumnChangeSpecification(String name) { + setName(name); + } + + private void setName(String name) { + checkIdentifier(name); + this.name = name; + } + + public String getName() { + return name; + } + + public String getNameAsIdentifier() { + return identifize(name); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/ColumnSpecification.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/ColumnSpecification.java new file mode 100644 index 000000000..1ef827972 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/ColumnSpecification.java @@ -0,0 +1,167 @@ +package org.springframework.cassandra.core.keyspace; + +import static org.springframework.cassandra.core.cql.CqlStringUtils.checkIdentifier; +import static org.springframework.cassandra.core.cql.CqlStringUtils.identifize; +import static org.springframework.cassandra.core.cql.CqlStringUtils.noNull; +import static org.springframework.cassandra.core.KeyType.PARTITION; +import static org.springframework.cassandra.core.KeyType.PRIMARY; +import static org.springframework.cassandra.core.Ordering.ASCENDING; + +import org.springframework.cassandra.core.KeyType; +import org.springframework.cassandra.core.Ordering; + +import com.datastax.driver.core.DataType; + +/** + * Builder class to help construct CQL statements that involve column manipulation. Not threadsafe. + *

+ * Use {@link #name(String)} and {@link #type(String)} to set the name and type of the column, respectively. To specify + * a PRIMARY KEY column, use {@link #primary()} or {@link #primary(Ordering)}. To specify that the + * PRIMARY KEY column is or is part of the partition key, use {@link #partition()} instead of + * {@link #primary()} or {@link #primary(Ordering)}. + * + * @author Matthew T. Adams + */ +public class ColumnSpecification { + + /** + * Default ordering of primary key fields; value is {@link Ordering#ASCENDING}. + */ + public static final Ordering DFAULT_ORDERING = ASCENDING; + + private String name; + private DataType type; // TODO: determining if we should be coupling this to Datastax Java Driver type? + private KeyType keyType; + private Ordering ordering; + + /** + * Sets the column's name. + * + * @return this + */ + public ColumnSpecification name(String name) { + checkIdentifier(name); + this.name = name; + return this; + } + + /** + * Sets the column's type. + * + * @return this + */ + public ColumnSpecification type(DataType type) { + this.type = type; + return this; + } + + /** + * Identifies this column as a primary key column that is also part of a partition key. Sets the column's + * {@link #keyType} to {@link KeyType#PARTITION} and its {@link #ordering} to null. + * + * @return this + */ + public ColumnSpecification partition() { + return partition(true); + } + + /** + * Toggles the identification of this column as a primary key column that also is or is part of a partition key. Sets + * {@link #ordering} to null and, if the given boolean is true, then sets the column's + * {@link #keyType} to {@link KeyType#PARTITION}, else sets it to null. + * + * @return this + */ + public ColumnSpecification partition(boolean partition) { + this.keyType = partition ? PARTITION : null; + this.ordering = null; + return this; + } + + /** + * Identifies this column as a primary key column with default ordering. Sets the column's {@link #keyType} to + * {@link KeyType#PRIMARY} and its {@link #ordering} to {@link #DFAULT_ORDERING}. + * + * @return this + */ + public ColumnSpecification primary() { + return primary(DFAULT_ORDERING); + } + + /** + * Identifies this column as a primary key column with the given ordering. Sets the column's {@link #keyType} to + * {@link KeyType#PRIMARY} and its {@link #ordering} to the given {@link Ordering}. + * + * @return this + */ + public ColumnSpecification primary(Ordering order) { + return primary(order, true); + } + + /** + * Toggles the identification of this column as a primary key column. If the given boolean is true, then + * sets the column's {@link #keyType} to {@link KeyType#PARTITION} and {@link #ordering} to the given {@link Ordering} + * , else sets both {@link #keyType} and {@link #ordering} to null. + * + * @return this + */ + public ColumnSpecification primary(Ordering order, boolean primary) { + this.keyType = primary ? PRIMARY : null; + this.ordering = primary ? order : null; + return this; + } + + /** + * Sets the column's {@link #keyType}. + * + * @return this + */ + /* package */ColumnSpecification keyType(KeyType keyType) { + this.keyType = keyType; + return this; + } + + /** + * Sets the column's {@link #ordering}. + * + * @return this + */ + /* package */ColumnSpecification ordering(Ordering ordering) { + this.ordering = ordering; + return this; + } + + public String getName() { + return name; + } + + public String getNameAsIdentifier() { + return identifize(name); + } + + public DataType getType() { + return type; + } + + public KeyType getKeyType() { + return keyType; + } + + public Ordering getOrdering() { + return ordering; + } + + public String toCql() { + return toCql(null).toString(); + } + + public StringBuilder toCql(StringBuilder cql) { + return (cql = noNull(cql)).append(name).append(" ").append(type); + } + + @Override + public String toString() { + return toCql(null).append(" /* keyType=").append(keyType).append(", ordering=").append(ordering).append(" */ ") + .toString(); + } +} \ No newline at end of file diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/ColumnTypeChangeSpecification.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/ColumnTypeChangeSpecification.java new file mode 100644 index 000000000..3f1a4d4f9 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/ColumnTypeChangeSpecification.java @@ -0,0 +1,24 @@ +package org.springframework.cassandra.core.keyspace; + +import org.springframework.util.Assert; + +import com.datastax.driver.core.DataType; + +public abstract class ColumnTypeChangeSpecification extends ColumnChangeSpecification { + + private DataType type; + + public ColumnTypeChangeSpecification(String name, DataType type) { + super(name); + setType(type); + } + + private void setType(DataType type) { + Assert.notNull(type); + this.type = type; + } + + public DataType getType() { + return type; + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/CreateTableSpecification.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/CreateTableSpecification.java new file mode 100644 index 000000000..28981ede9 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/CreateTableSpecification.java @@ -0,0 +1,34 @@ +package org.springframework.cassandra.core.keyspace; + +/** + * Builder class to construct a CREATE TABLE specification. + * + * @author Matthew T. Adams + */ +public class CreateTableSpecification extends TableSpecification { + + private boolean ifNotExists = false; + + /** + * Causes the inclusion of an IF NOT EXISTS clause. + * + * @return this + */ + public CreateTableSpecification ifNotExists() { + return ifNotExists(true); + } + + /** + * Toggles the inclusion of an IF NOT EXISTS clause. + * + * @return this + */ + public CreateTableSpecification ifNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + return this; + } + + public boolean getIfNotExists() { + return ifNotExists; + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DefaultOption.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DefaultOption.java new file mode 100644 index 000000000..093409870 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DefaultOption.java @@ -0,0 +1,157 @@ +package org.springframework.cassandra.core.keyspace; + +import static org.springframework.cassandra.core.cql.CqlStringUtils.escapeSingle; +import static org.springframework.cassandra.core.cql.CqlStringUtils.singleQuote; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.Map; + +import org.springframework.util.Assert; + +/** + * A default implementation of {@link Option}. + * + * @author Matthew T. Adams + */ +public class DefaultOption implements Option { + + private String name; + private Class type; + private boolean requiresValue; + private boolean escapesValue; + private boolean quotesValue; + + public DefaultOption(String name, Class type, boolean requiresValue, boolean escapesValue, boolean quotesValue) { + setName(name); + setType(type); + this.requiresValue = requiresValue; + this.escapesValue = escapesValue; + this.quotesValue = quotesValue; + + } + + protected void setName(String name) { + Assert.hasLength(name); + this.name = name; + } + + protected void setType(Class type) { + if (type != null) { + if (type.isInterface() && !(Map.class.isAssignableFrom(type) || Collection.class.isAssignableFrom(type))) { + throw new IllegalArgumentException("given type [" + type.getName() + "] must be a class, Map or Collection"); + } + } + this.type = type; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public boolean isCoerceable(Object value) { + if (value == null || type == null) { + return true; + } + + // check map + if (Map.class.isAssignableFrom(type)) { + return Map.class.isAssignableFrom(value.getClass()); + } + // check collection + if (Collection.class.isAssignableFrom(type)) { + return Collection.class.isAssignableFrom(value.getClass()); + } + // check enum + if (type.isEnum()) { + try { + String name = value instanceof Enum ? name = ((Enum) value).name() : value.toString(); + Enum.valueOf((Class) type, name); + return true; + } catch (NullPointerException x) { + return false; + } catch (IllegalArgumentException x) { + return false; + } + } + + // check class via String constructor + try { + Constructor ctor = type.getConstructor(String.class); + if (!ctor.isAccessible()) { + ctor.setAccessible(true); + } + ctor.newInstance(value.toString()); + return true; + } catch (InstantiationException e) { + } catch (IllegalAccessException e) { + } catch (IllegalArgumentException e) { + } catch (InvocationTargetException e) { + } catch (NoSuchMethodException e) { + } catch (SecurityException e) { + } + return false; + } + + public Class getType() { + return type; + } + + public String getName() { + return name; + } + + public boolean takesValue() { + return type != null; + } + + public boolean requiresValue() { + return this.requiresValue; + } + + public boolean escapesValue() { + return this.escapesValue; + } + + public boolean quotesValue() { + return this.quotesValue; + } + + public void checkValue(Object value) { + if (takesValue()) { + if (value == null) { + if (requiresValue) { + throw new IllegalArgumentException("Option [" + getName() + "] requires a value"); + } + return; // doesn't require a value, so null is ok + } + // else value is not null + if (isCoerceable(value)) { + return; + } + // else value is not coerceable into the expected type + throw new IllegalArgumentException("Option [" + getName() + "] takes value coerceable to type [" + + getType().getName() + "]"); + } + // else this option doesn't take a value + if (value != null) { + throw new IllegalArgumentException("Option [" + getName() + "] takes no value"); + } + } + + public String toString(Object value) { + if (value == null) { + return null; + } + checkValue(value); + + String string = value.toString(); + string = escapesValue ? escapeSingle(string) : string; + string = quotesValue ? singleQuote(string) : string; + return string; + } + + @Override + public String toString() { + return "[name=" + name + ", type=" + type.getName() + ", requiresValue=" + requiresValue + ", escapesValue=" + + escapesValue + ", quotesValue=" + quotesValue + "]"; + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DefaultTableDescriptor.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DefaultTableDescriptor.java new file mode 100644 index 000000000..0e7c24638 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DefaultTableDescriptor.java @@ -0,0 +1,17 @@ +package org.springframework.cassandra.core.keyspace; + +/** + * Convenient default implementation of {@link TableDescriptor} as an extension of {@link TableSpecification} that + * doesn't require the use of generics. + * + * @author Matthew T. Adams + */ +public class DefaultTableDescriptor extends TableSpecification { + + /** + * Factory method to produce a new {@link DefaultTableDescriptor}. Convenient if imported statically. + */ + public static DefaultTableDescriptor table() { + return new DefaultTableDescriptor(); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DropColumnSpecification.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DropColumnSpecification.java new file mode 100644 index 000000000..d485b852c --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DropColumnSpecification.java @@ -0,0 +1,8 @@ +package org.springframework.cassandra.core.keyspace; + +public class DropColumnSpecification extends ColumnChangeSpecification { + + public DropColumnSpecification(String name) { + super(name); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DropTableSpecification.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DropTableSpecification.java new file mode 100644 index 000000000..a3e66b274 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DropTableSpecification.java @@ -0,0 +1,24 @@ +package org.springframework.cassandra.core.keyspace; + +/** + * Builder class that supports the construction of DROP TABLE specifications. + * + * @author Matthew T. Adams + */ +public class DropTableSpecification extends TableNameSpecification { + + private boolean ifExists; + + public DropTableSpecification ifExists() { + return ifExists(true); + } + + public DropTableSpecification ifExists(boolean ifExists) { + this.ifExists = ifExists; + return this; + } + + public boolean getIfExists() { + return ifExists; + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/Option.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/Option.java new file mode 100644 index 000000000..055512414 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/Option.java @@ -0,0 +1,61 @@ +package org.springframework.cassandra.core.keyspace; + +/** + * Interface to represent option types. + * + * @author Matthew T. Adams + */ +public interface Option { + + /** + * The type that values must be able to be coerced into for this option. + */ + Class getType(); + + /** + * The (usually lower-cased, underscore-separated) name of this table option. + */ + String getName(); + + /** + * Whether this option takes a value. + */ + boolean takesValue(); + + /** + * Whether this option should escape single quotes in its value. + */ + boolean escapesValue(); + + /** + * Whether this option's value should be single-quoted. + */ + boolean quotesValue(); + + /** + * Whether this option requires a value. + */ + boolean requiresValue(); + + /** + * Checks that the given value can be coerced into the type given by {@link #getType()}. + */ + void checkValue(Object value); + + /** + * Tests whether the given value can be coerced into the type given by {@link #getType()}. + */ + boolean isCoerceable(Object value); + + /** + * First ensures that the given value is coerceable into the type expected by this option, then returns the result of + * {@link Object#toString()} called on the given value. If this option is escaping quotes ({@link #escapesValue()} is + * true), then single quotes will be escaped, and if this option is quoting values ( + * {@link #quotesValue()} is true), then the value will be surrounded by single quotes. Given + * null, returns null. + * + * @see #escapesValue() + * @see #quotesValue() + */ + String toString(Object value); +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableDescriptor.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableDescriptor.java new file mode 100644 index 000000000..2a17486a0 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableDescriptor.java @@ -0,0 +1,52 @@ +package org.springframework.cassandra.core.keyspace; + +import java.util.List; +import java.util.Map; + +/** + * Describes a table. + * + * @author Matthew T. Adams + */ +public interface TableDescriptor { + + /** + * Returns the name of the table. + */ + String getName(); + + /** + * Returns the name of the table as an identifer or quoted identifier as appropriate. + */ + String getNameAsIdentifier(); + + /** + * Returns an unmodifiable {@link List} of {@link ColumnSpecification}s. + */ + List getColumns(); + + /** + * Returns an unmodifiable list of all partition key columns. + */ + public List getPartitionKeyColumns(); + + /** + * Returns an unmodifiable list of all primary key columns that are not also partition key columns. + */ + public List getPrimaryKeyColumns(); + + /** + * Returns an unmodifiable list of all partition and primary key columns. + */ + public List getKeyColumns(); + + /** + * Returns an unmodifiable list of all non-key columns. + */ + public List getNonKeyColumns(); + + /** + * Returns an unmodifiable {@link Map} of table options. + */ + Map getOptions(); +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableNameSpecification.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableNameSpecification.java new file mode 100644 index 000000000..2cf3b993e --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableNameSpecification.java @@ -0,0 +1,38 @@ +package org.springframework.cassandra.core.keyspace; + +import static org.springframework.cassandra.core.cql.CqlStringUtils.checkIdentifier; +import static org.springframework.cassandra.core.cql.CqlStringUtils.identifize; + +/** + * Abstract builder class to support the construction of table specifications. + * + * @author Matthew T. Adams + * @param The subtype of the {@link TableNameSpecification} + */ +public abstract class TableNameSpecification> { + + /** + * The name of the table. + */ + private String name; + + /** + * Sets the table name. + * + * @return this + */ + @SuppressWarnings("unchecked") + public T name(String name) { + checkIdentifier(name); + this.name = name; + return (T) this; + } + + public String getName() { + return name; + } + + public String getNameAsIdentifier() { + return identifize(name); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableOperations.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableOperations.java new file mode 100644 index 000000000..0f85aa421 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableOperations.java @@ -0,0 +1,34 @@ +package org.springframework.cassandra.core.keyspace; + +/** + * Class that offers static methods as entry points into the fluent API for building create, drop and alter table + * specifications. These methods are most convenient when imported statically. + * + * @author Matthew T. Adams + */ +public class TableOperations { + + /** + * Entry point into the {@link CreateTableSpecification}'s fluent API to create a table. Convenient if imported + * statically. + */ + public static CreateTableSpecification createTable() { + return new CreateTableSpecification(); + } + + /** + * Entry point into the {@link DropTableSpecification}'s fluent API to drop a table. Convenient if imported + * statically. + */ + public static DropTableSpecification dropTable() { + return new DropTableSpecification(); + } + + /** + * Entry point into the {@link AlterTableSpecification}'s fluent API to alter a table. Convenient if imported + * statically. + */ + public static AlterTableSpecification alterTable() { + return new AlterTableSpecification(); + } +} \ No newline at end of file diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableOption.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableOption.java new file mode 100644 index 000000000..7f6d56fca --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableOption.java @@ -0,0 +1,287 @@ +package org.springframework.cassandra.core.keyspace; + +import java.util.Map; + +/** + * Enumeration that represents all known table options. If a table option is not listed here, but is supported by + * Cassandra, use the method {@link CreateTableSpecification#with(String, Object, boolean, boolean)} to write the raw + * value. + * + * Implements {@link Option} via delegation, since {@link Enum}s can't extend anything. + * + * @author Matthew T. Adams + * @see CompactionOption + * @see CompressionOption + * @see CachingOption + */ +public enum TableOption implements Option { + /** + * comment + */ + COMMENT("comment", String.class, true, true, true), + /** + * COMPACT STORAGE + */ + COMPACT_STORAGE("COMPACT STORAGE", null, false, false, false), + /** + * compaction. Value is a Map<CompactionOption,Object>. + * + * @see CompactionOption + */ + COMPACTION("compaction", Map.class, true, false, false), + /** + * compression. Value is a Map<CompressionOption,Object>. + * + * @see {@link CompressionOption} + */ + COMPRESSION("compression", Map.class, true, false, false), + /** + * replicate_on_write + */ + REPLICATE_ON_WRITE("replicate_on_write", Boolean.class, true, false, false), + /** + * caching + * + * @see CachingOption + */ + CACHING("caching", CachingOption.class, true, false, false), + /** + * bloom_filter_fp_chance + */ + BLOOM_FILTER_FP_CHANCE("bloom_filter_fp_chance", Double.class, true, false, false), + /** + * read_repair_chance + */ + READ_REPAIR_CHANCE("read_repair_chance", Double.class, true, false, false), + /** + * dclocal_read_repair_chance + */ + DCLOCAL_READ_REPAIR_CHANCE("dclocal_read_repair_chance", Double.class, true, false, false), + /** + * gc_grace_seconds + */ + GC_GRACE_SECONDS("gc_grace_seconds", Long.class, true, false, false); + + private Option delegate; + + private TableOption(String name, Class type, boolean requiresValue, boolean escapesValue, boolean quotesValue) { + this.delegate = new DefaultOption(name, type, requiresValue, escapesValue, quotesValue); + } + + public Class getType() { + return delegate.getType(); + } + + public boolean takesValue() { + return delegate.takesValue(); + } + + public String getName() { + return delegate.getName(); + } + + public boolean escapesValue() { + return delegate.escapesValue(); + } + + public boolean quotesValue() { + return delegate.quotesValue(); + } + + public boolean requiresValue() { + return delegate.requiresValue(); + } + + public void checkValue(Object value) { + delegate.checkValue(value); + } + + public boolean isCoerceable(Object value) { + return delegate.isCoerceable(value); + } + + public String toString() { + return delegate.toString(); + } + + public String toString(Object value) { + return delegate.toString(value); + } + + /** + * Known caching options. + * + * @author Matthew T. Adams + */ + public enum CachingOption { + ALL("all"), KEYS_ONLY("keys_only"), ROWS_ONLY("rows_only"), NONE("none"); + + private String value; + + private CachingOption(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public String toString() { + return getValue(); + } + } + + /** + * Known compaction options. + * + * @author Matthew T. Adams + */ + public enum CompactionOption implements Option { + /** + * tombstone_threshold + */ + TOMBSTONE_THRESHOLD("tombstone_threshold", Double.class, true, false, false), + /** + * tombstone_compaction_interval + */ + TOMBSTONE_COMPACTION_INTERVAL("tombstone_compaction_interval", Double.class, true, false, false), + /** + * min_sstable_size + */ + MIN_SSTABLE_SIZE("min_sstable_size", Long.class, true, false, false), + /** + * min_threshold + */ + MIN_THRESHOLD("min_threshold", Long.class, true, false, false), + /** + * max_threshold + */ + MAX_THRESHOLD("max_threshold", Long.class, true, false, false), + /** + * bucket_low + */ + BUCKET_LOW("bucket_low", Double.class, true, false, false), + /** + * bucket_high + */ + BUCKET_HIGH("bucket_high", Double.class, true, false, false), + /** + * sstable_size_in_mb + */ + SSTABLE_SIZE_IN_MB("sstable_size_in_mb", Long.class, true, false, false); + + private Option delegate; + + private CompactionOption(String name, Class type, boolean requiresValue, boolean escapesValue, + boolean quotesValue) { + this.delegate = new DefaultOption(name, type, requiresValue, escapesValue, quotesValue); + } + + public Class getType() { + return delegate.getType(); + } + + public boolean takesValue() { + return delegate.takesValue(); + } + + public String getName() { + return delegate.getName(); + } + + public boolean escapesValue() { + return delegate.escapesValue(); + } + + public boolean quotesValue() { + return delegate.quotesValue(); + } + + public boolean requiresValue() { + return delegate.requiresValue(); + } + + public void checkValue(Object value) { + delegate.checkValue(value); + } + + public boolean isCoerceable(Object value) { + return delegate.isCoerceable(value); + } + + public String toString() { + return delegate.toString(); + } + + public String toString(Object value) { + return delegate.toString(value); + } + } + + /** + * Known compression options. + * + * @author Matthew T. Adams + */ + public enum CompressionOption implements Option { + /** + * sstable_compression + */ + STABLE_COMPRESSION("sstable_compression", String.class, true, false, false), + /** + * chunk_length_kb + */ + CHUNK_LENGTH_KB("chunk_length_kb", Long.class, true, false, false), + /** + * crc_check_chance + */ + CRC_CHECK_CHANCE("crc_check_chance", Double.class, true, false, false); + + private Option delegate; + + private CompressionOption(String name, Class type, boolean requiresValue, boolean escapesValue, + boolean quotesValue) { + this.delegate = new DefaultOption(name, type, requiresValue, escapesValue, quotesValue); + } + + public Class getType() { + return delegate.getType(); + } + + public boolean takesValue() { + return delegate.takesValue(); + } + + public String getName() { + return delegate.getName(); + } + + public boolean escapesValue() { + return delegate.escapesValue(); + } + + public boolean quotesValue() { + return delegate.quotesValue(); + } + + public boolean requiresValue() { + return delegate.requiresValue(); + } + + public void checkValue(Object value) { + delegate.checkValue(value); + } + + public boolean isCoerceable(Object value) { + return delegate.isCoerceable(value); + } + + public String toString() { + return delegate.toString(); + } + + public String toString(Object value) { + return delegate.toString(value); + } + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableOptionsSpecification.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableOptionsSpecification.java new file mode 100644 index 000000000..de6c8d9d2 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableOptionsSpecification.java @@ -0,0 +1,93 @@ +package org.springframework.cassandra.core.keyspace; + +import static org.springframework.cassandra.core.cql.CqlStringUtils.escapeSingle; +import static org.springframework.cassandra.core.cql.CqlStringUtils.singleQuote; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.cassandra.core.cql.CqlStringUtils; + +/** + * Abstract builder class to support the construction of table specifications that have table options, that is, those + * options normally specified by WITH ... AND .... + *

+ * It is important to note that although this class depends on {@link TableOption} for convenient and typesafe use, it + * ultimately stores its options in a Map for flexibility. This means that + * {@link #with(TableOption)} and {@link #with(TableOption, Object)} delegate to + * {@link #with(String, Object, boolean, boolean)}. This design allows the API to support new Cassandra options as they + * are introduced without having to update the code immediately. + * + * @author Matthew T. Adams + * @param The subtype of the {@link TableOptionsSpecification}. + */ +public abstract class TableOptionsSpecification> extends + TableNameSpecification> { + + protected Map options = new LinkedHashMap(); + + @SuppressWarnings("unchecked") + public T name(String name) { + return (T) super.name(name); + } + + /** + * Convenience method that calls with(option, null). + * + * @return this + */ + public T with(TableOption option) { + return with(option, null); + } + + /** + * Sets the given table option. This is a convenience method that calls + * {@link #with(String, Object, boolean, boolean)} appropriately from the given {@link TableOption} and value for that + * option. + * + * @param option The option to set. + * @param value The value of the option. Must be type-compatible with the {@link TableOption}. + * @return this + * @see #with(String, Object, boolean, boolean) + */ + public T with(TableOption option, Object value) { + option.checkValue(value); + return (T) with(option.getName(), value, option.escapesValue(), option.quotesValue()); + } + + /** + * Adds the given option by name to this table's options. + *

+ * Options that have null values are considered single string options where the name of the option is the + * string to be used. Otherwise, the result of {@link Object#toString()} is considered to be the value of the option + * with the given name. The value, after conversion to string, may have embedded single quotes escaped according to + * parameter escape and may be single-quoted according to parameter quote. + * + * @param name The name of the option + * @param value The value of the option. If null, the value is ignored and the option is considered to be + * composed of only the name, otherwise the value's {@link Object#toString()} value is used. + * @param escape Whether to escape the value via {@link CqlStringUtils#escapeSingle(Object)}. Ignored if given value + * is an instance of a {@link Map}. + * @param quote Whether to quote the value via {@link CqlStringUtils#singleQuote(Object)}. Ignored if given value is + * an instance of a {@link Map}. + * @return this + */ + @SuppressWarnings("unchecked") + public T with(String name, Object value, boolean escape, boolean quote) { + if (!(value instanceof Map)) { + if (escape) { + value = escapeSingle(value); + } + if (quote) { + value = singleQuote(value); + } + } + options.put(name, value); + return (T) this; + } + + public Map getOptions() { + return Collections.unmodifiableMap(options); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableSpecification.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableSpecification.java new file mode 100644 index 000000000..b583eeb7e --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/TableSpecification.java @@ -0,0 +1,162 @@ +package org.springframework.cassandra.core.keyspace; + +import static org.springframework.cassandra.core.KeyType.PARTITION; +import static org.springframework.cassandra.core.KeyType.PRIMARY; +import static org.springframework.cassandra.core.Ordering.ASCENDING; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.cassandra.core.KeyType; +import org.springframework.cassandra.core.Ordering; + +import com.datastax.driver.core.DataType; + +/** + * Builder class to support the construction of table specifications that have columns. This class can also be used as a + * standalone {@link TableDescriptor}, independent of {@link CreateTableSpecification}. + * + * @author Matthew T. Adams + */ +public class TableSpecification extends TableOptionsSpecification> implements TableDescriptor { + + /** + * List of all columns. + */ + private List columns = new ArrayList(); + + /** + * List of only those columns that comprise the partition key. + */ + private List partitionKeyColumns = new ArrayList(); + + /** + * List of only those columns that comprise the primary key that are not also part of the partition key. + */ + private List primaryKeyColumns = new ArrayList(); + + /** + * List of only those columns that are not partition or primary key columns. + */ + private List nonKeyColumns = new ArrayList(); + + /** + * Adds the given non-key column to the table. Must be specified after all primary key columns. + * + * @param name The column name; must be a valid unquoted or quoted identifier without the surrounding double quotes. + * @param type The data type of the column. + */ + public T column(String name, DataType type) { + return column(name, type, null, null); + } + + /** + * Adds the given partition key column to the table. Must be specified before any other columns. + * + * @param name The column name; must be a valid unquoted or quoted identifier without the surrounding double quotes. + * @param type The data type of the column. + * @return this + */ + public T partitionKeyColumn(String name, DataType type) { + return column(name, type, PARTITION, null); + } + + /** + * Adds the given primary key column to the table with ascending ordering. Must be specified after all partition key + * columns and before any non-key columns. + * + * @param name The column name; must be a valid unquoted or quoted identifier without the surrounding double quotes. + * @param type The data type of the column. + * @return this + */ + public T primaryKeyColumn(String name, DataType type) { + return primaryKeyColumn(name, type, ASCENDING); + } + + /** + * Adds the given primary key column to the table with the given ordering (null meaning ascending). Must + * be specified after all partition key columns and before any non-key columns. + * + * @param name The column name; must be a valid unquoted or quoted identifier without the surrounding double quotes. + * @param type The data type of the column. + * @return this + */ + public T primaryKeyColumn(String name, DataType type, Ordering ordering) { + return column(name, type, PRIMARY, ordering); + } + + /** + * Adds the given info as a new column to the table. Partition key columns must precede primary key columns, which + * must precede non-key columns. + * + * @param name The column name; must be a valid unquoted or quoted identifier without the surrounding double quotes. + * @param type The data type of the column. + * @param keyType Indicates key type. Null means that the column is not a key column. + * @param ordering If the given {@link KeyType} is {@link KeyType#PRIMARY}, then the given ordering is used, else + * ignored. + * @return this + */ + @SuppressWarnings("unchecked") + protected T column(String name, DataType type, KeyType keyType, Ordering ordering) { + + ColumnSpecification column = new ColumnSpecification().name(name).type(type).keyType(keyType) + .ordering(keyType == PRIMARY ? ordering : null); + + columns.add(column); + + if (keyType == KeyType.PARTITION) { + partitionKeyColumns.add(column); + } + + if (keyType == KeyType.PRIMARY) { + primaryKeyColumns.add(column); + } + + if (keyType == null) { + nonKeyColumns.add(column); + } + + return (T) this; + } + + /** + * Returns an unmodifiable list of all columns. + */ + public List getColumns() { + return Collections.unmodifiableList(columns); + } + + /** + * Returns an unmodifiable list of all partition key columns. + */ + public List getPartitionKeyColumns() { + return Collections.unmodifiableList(partitionKeyColumns); + } + + /** + * Returns an unmodifiable list of all primary key columns that are not also partition key columns. + */ + public List getPrimaryKeyColumns() { + return Collections.unmodifiableList(primaryKeyColumns); + } + + /** + * Returns an unmodifiable list of all primary key columns that are not also partition key columns. + */ + public List getKeyColumns() { + + ArrayList keyColumns = new ArrayList(); + keyColumns.addAll(partitionKeyColumns); + keyColumns.addAll(primaryKeyColumns); + + return Collections.unmodifiableList(keyColumns); + } + + /** + * Returns an unmodifiable list of all non-key columns. + */ + public List getNonKeyColumns() { + return Collections.unmodifiableList(nonKeyColumns); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/util/MapBuilder.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/util/MapBuilder.java new file mode 100644 index 000000000..7b50dd549 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/util/MapBuilder.java @@ -0,0 +1,126 @@ +package org.springframework.cassandra.core.util; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +/** + * Builder for maps, which also conveniently implements {@link Map} via delegation for convenience so you don't have to + * actually {@link #build()} it (or forget to). + * + * @author Matthew T. Adams + * @param The key type of the map. + * @param The value type of the map. + */ +public class MapBuilder implements Map { + + /** + * Factory method to construct a new MapBuilder<Object,Object>. Convenient if imported statically. + */ + public static MapBuilder map() { + return map(Object.class, Object.class); + } + + /** + * Factory method to construct a new builder with the given key & value types. Convenient if imported statically. + */ + public static MapBuilder map(Class keyType, Class valueType) { + return new MapBuilder(); + } + + /** + * Factory method to construct a new builder with a shallow copy of the given map. Convenient if imported statically. + */ + public static MapBuilder map(Map source) { + return new MapBuilder(source); + } + + private Map map; + + public MapBuilder() { + this(new LinkedHashMap()); + } + + /** + * Constructs a new instance with a copy of the given map. + */ + public MapBuilder(Map source) { + this.map = new LinkedHashMap(source); + } + + /** + * Adds an entry to this map, then returns this. + * + * @return this + */ + public MapBuilder entry(K key, V value) { + map.put(key, value); + return this; + } + + /** + * Returns a new map based on the current state of this builder's map. + * + * @return A new Map with this builder's map's current content. + */ + public Map build() { + return new LinkedHashMap(map); + } + + public int size() { + return map.size(); + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + public V get(Object key) { + return map.get(key); + } + + public V put(K key, V value) { + return map.put(key, value); + } + + public V remove(Object key) { + return map.remove(key); + } + + public void putAll(Map m) { + map.putAll(m); + } + + public void clear() { + map.clear(); + } + + public Set keySet() { + return map.keySet(); + } + + public Collection values() { + return map.values(); + } + + public Set> entrySet() { + return map.entrySet(); + } + + public boolean equals(Object o) { + return map.equals(o); + } + + public int hashCode() { + return map.hashCode(); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/CassandraAccessor.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/CassandraAccessor.java new file mode 100644 index 000000000..12c7c9e39 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/CassandraAccessor.java @@ -0,0 +1,76 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.cassandra.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +import com.datastax.driver.core.Session; + +/** + * @author David Webb + * + */ +public class CassandraAccessor implements InitializingBean { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private Session session; + + private CassandraExceptionTranslator exceptionTranslator; + + /** + * Set the exception translator for this instance. + * + * @see org.springframework.cassandra.support.CassandraExceptionTranslator + */ + public void setExceptionTranslator(CassandraExceptionTranslator exceptionTranslator) { + this.exceptionTranslator = exceptionTranslator; + } + + /** + * Return the exception translator for this instance. + */ + public CassandraExceptionTranslator getExceptionTranslator() { + return this.exceptionTranslator; + } + + /** + * Ensure that the Cassandra Session has been set + */ + public void afterPropertiesSet() { + if (getSession() == null) { + throw new IllegalArgumentException("Property 'session' is required"); + } + } + + /** + * @return Returns the session. + */ + public Session getSession() { + return session; + } + + /** + * @param session The session to set. + */ + public void setSession(Session session) { + this.session = session; + } + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/CassandraExceptionTranslator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/CassandraExceptionTranslator.java new file mode 100644 index 000000000..1733e2b76 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/CassandraExceptionTranslator.java @@ -0,0 +1,136 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.cassandra.support; + +import org.springframework.cassandra.support.exception.CassandraAuthenticationException; +import org.springframework.cassandra.support.exception.CassandraConnectionFailureException; +import org.springframework.cassandra.support.exception.CassandraInsufficientReplicasAvailableException; +import org.springframework.cassandra.support.exception.CassandraInternalException; +import org.springframework.cassandra.support.exception.CassandraInvalidConfigurationInQueryException; +import org.springframework.cassandra.support.exception.CassandraInvalidQueryException; +import org.springframework.cassandra.support.exception.CassandraKeyspaceExistsException; +import org.springframework.cassandra.support.exception.CassandraQuerySyntaxException; +import org.springframework.cassandra.support.exception.CassandraReadTimeoutException; +import org.springframework.cassandra.support.exception.CassandraTableExistsException; +import org.springframework.cassandra.support.exception.CassandraTraceRetrievalException; +import org.springframework.cassandra.support.exception.CassandraTruncateException; +import org.springframework.cassandra.support.exception.CassandraTypeMismatchException; +import org.springframework.cassandra.support.exception.CassandraUnauthorizedException; +import org.springframework.cassandra.support.exception.CassandraUncategorizedException; +import org.springframework.cassandra.support.exception.CassandraWriteTimeoutException; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.support.PersistenceExceptionTranslator; + +import com.datastax.driver.core.WriteType; +import com.datastax.driver.core.exceptions.AlreadyExistsException; +import com.datastax.driver.core.exceptions.AuthenticationException; +import com.datastax.driver.core.exceptions.DriverException; +import com.datastax.driver.core.exceptions.DriverInternalError; +import com.datastax.driver.core.exceptions.InvalidConfigurationInQueryException; +import com.datastax.driver.core.exceptions.InvalidQueryException; +import com.datastax.driver.core.exceptions.InvalidTypeException; +import com.datastax.driver.core.exceptions.NoHostAvailableException; +import com.datastax.driver.core.exceptions.ReadTimeoutException; +import com.datastax.driver.core.exceptions.SyntaxError; +import com.datastax.driver.core.exceptions.TraceRetrievalException; +import com.datastax.driver.core.exceptions.TruncateException; +import com.datastax.driver.core.exceptions.UnauthorizedException; +import com.datastax.driver.core.exceptions.UnavailableException; +import com.datastax.driver.core.exceptions.WriteTimeoutException; + +/** + * Simple {@link PersistenceExceptionTranslator} for Cassandra. Convert the given runtime exception to an appropriate + * exception from the {@code org.springframework.dao} hierarchy. Return {@literal null} if no translation is + * appropriate: any other exception may have resulted from user code, and should not be translated. + * + * @author Alex Shvid + * @author Matthew T. Adams + */ + +public class CassandraExceptionTranslator implements PersistenceExceptionTranslator { + + /* + * (non-Javadoc) + * + * @see org.springframework.dao.support.PersistenceExceptionTranslator# + * translateExceptionIfPossible(java.lang.RuntimeException) + */ + public DataAccessException translateExceptionIfPossible(RuntimeException x) { + + if (!(x instanceof DriverException)) { + return null; + } + + if (x instanceof DataAccessException) { + return (DataAccessException) x; + } + + // Remember: subclasses must come before superclasses, otherwise the + // superclass would match before the subclass! + + if (x instanceof AuthenticationException) { + return new CassandraAuthenticationException(((AuthenticationException) x).getHost(), x.getMessage(), x); + } + if (x instanceof DriverInternalError) { + return new CassandraInternalException(x.getMessage(), x); + } + if (x instanceof InvalidTypeException) { + return new CassandraTypeMismatchException(x.getMessage(), x); + } + if (x instanceof NoHostAvailableException) { + return new CassandraConnectionFailureException(((NoHostAvailableException) x).getErrors(), x.getMessage(), x); + } + if (x instanceof ReadTimeoutException) { + return new CassandraReadTimeoutException(((ReadTimeoutException) x).wasDataRetrieved(), x.getMessage(), x); + } + if (x instanceof WriteTimeoutException) { + WriteType writeType = ((WriteTimeoutException) x).getWriteType(); + return new CassandraWriteTimeoutException(writeType == null ? null : writeType.name(), x.getMessage(), x); + } + if (x instanceof TruncateException) { + return new CassandraTruncateException(x.getMessage(), x); + } + if (x instanceof UnavailableException) { + UnavailableException ux = (UnavailableException) x; + return new CassandraInsufficientReplicasAvailableException(ux.getRequiredReplicas(), ux.getAliveReplicas(), + x.getMessage(), x); + } + if (x instanceof AlreadyExistsException) { + AlreadyExistsException aex = (AlreadyExistsException) x; + + return aex.wasTableCreation() ? new CassandraTableExistsException(aex.getTable(), x.getMessage(), x) + : new CassandraKeyspaceExistsException(aex.getKeyspace(), x.getMessage(), x); + } + if (x instanceof InvalidConfigurationInQueryException) { + return new CassandraInvalidConfigurationInQueryException(x.getMessage(), x); + } + if (x instanceof InvalidQueryException) { + return new CassandraInvalidQueryException(x.getMessage(), x); + } + if (x instanceof SyntaxError) { + return new CassandraQuerySyntaxException(x.getMessage(), x); + } + if (x instanceof UnauthorizedException) { + return new CassandraUnauthorizedException(x.getMessage(), x); + } + if (x instanceof TraceRetrievalException) { + return new CassandraTraceRetrievalException(x.getMessage(), x); + } + + // unknown or unhandled exception + return new CassandraUncategorizedException(x.getMessage(), x); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraAuthenticationException.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraAuthenticationException.java new file mode 100644 index 000000000..9e5a57153 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraAuthenticationException.java @@ -0,0 +1,42 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.cassandra.support.exception; + +import java.net.InetAddress; + +import org.springframework.dao.PermissionDeniedDataAccessException; + +/** + * Spring data access exception for a Cassandra authentication failure. + * + * @author Matthew T. Adams + */ +public class CassandraAuthenticationException extends PermissionDeniedDataAccessException { + + private static final long serialVersionUID = 8556304586797273927L; + + private InetAddress host; + + public CassandraAuthenticationException(InetAddress host, String msg, Throwable cause) { + super(msg, cause); + this.host = host; + } + + public InetAddress getHost() { + return host; + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraConnectionFailureException.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraConnectionFailureException.java new file mode 100644 index 000000000..633ad8b6b --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraConnectionFailureException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.cassandra.support.exception; + +import java.net.InetAddress; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.dao.DataAccessResourceFailureException; + +/** + * Spring data access exception for Cassandra when no host is available. + * + * @author Matthew T. Adams + */ +public class CassandraConnectionFailureException extends DataAccessResourceFailureException { + + private static final long serialVersionUID = 6299912054261646552L; + + private final Map messagesByHost = new HashMap(); + + public CassandraConnectionFailureException(Map messagesByHost, String msg, Throwable cause) { + super(msg, cause); + this.messagesByHost.putAll(messagesByHost); + } + + public Map getMessagesByHost() { + return Collections.unmodifiableMap(messagesByHost); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraInsufficientReplicasAvailableException.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraInsufficientReplicasAvailableException.java new file mode 100644 index 000000000..7072fd9fe --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraInsufficientReplicasAvailableException.java @@ -0,0 +1,51 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.cassandra.support.exception; + +import org.springframework.dao.TransientDataAccessException; + +/** + * Spring data access exception for Cassandra when insufficient replicas are available for a given consistency level. + * + * @author Matthew T. Adams + */ +public class CassandraInsufficientReplicasAvailableException extends TransientDataAccessException { + + private static final long serialVersionUID = 6415130674604814905L; + + private int numberRequired; + private int numberAlive; + + public CassandraInsufficientReplicasAvailableException(String msg) { + super(msg); + } + + public CassandraInsufficientReplicasAvailableException(int numberRequired, int numberAlive, String msg, + Throwable cause) { + super(msg, cause); + this.numberRequired = numberRequired; + this.numberAlive = numberAlive; + } + + public int getNumberRequired() { + return numberRequired; + } + + public int getNumberAlive() { + return numberAlive; + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraInternalException.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraInternalException.java new file mode 100644 index 000000000..1da76f11b --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraInternalException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.cassandra.support.exception; + +import org.springframework.dao.DataAccessException; + +/** + * Spring data access exception for a Cassandra internal error. + * + * @author Matthew T. Adams + */ +public class CassandraInternalException extends DataAccessException { + + private static final long serialVersionUID = 433061676465346338L; + + public CassandraInternalException(String msg) { + super(msg); + } + + public CassandraInternalException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraInvalidConfigurationInQueryException.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraInvalidConfigurationInQueryException.java new file mode 100644 index 000000000..ae29eff31 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraInvalidConfigurationInQueryException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.cassandra.support.exception; + +import org.springframework.dao.InvalidDataAccessApiUsageException; + +/** + * Spring data access exception for a Cassandra query that is syntactically correct but has an invalid configuration + * clause. + * + * @author Matthew T. Adams + */ +public class CassandraInvalidConfigurationInQueryException extends InvalidDataAccessApiUsageException { + + private static final long serialVersionUID = 4594321191806182918L; + + public CassandraInvalidConfigurationInQueryException(String msg) { + super(msg); + } + + public CassandraInvalidConfigurationInQueryException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraInvalidQueryException.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraInvalidQueryException.java new file mode 100644 index 000000000..3ee84ac47 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraInvalidQueryException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.cassandra.support.exception; + +import org.springframework.dao.InvalidDataAccessApiUsageException; + +/** + * Spring data access exception for a Cassandra query that's syntactically correct but invalid. + * + * @author Matthew T. Adams + */ +public class CassandraInvalidQueryException extends InvalidDataAccessApiUsageException { + + private static final long serialVersionUID = 4594321191806182918L; + + public CassandraInvalidQueryException(String msg) { + super(msg); + } + + public CassandraInvalidQueryException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraKeyspaceExistsException.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraKeyspaceExistsException.java new file mode 100644 index 000000000..5fd297e3b --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraKeyspaceExistsException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.cassandra.support.exception; + +/** + * Spring data access exception for Cassandra when a keyspace being created already exists. + * + * @author Matthew T. Adams + */ +public class CassandraKeyspaceExistsException extends CassandraSchemaElementExistsException { + + private static final long serialVersionUID = 6032967419751410352L; + + public CassandraKeyspaceExistsException(String keyspaceName, String msg, Throwable cause) { + super(keyspaceName, ElementType.KEYSPACE, msg, cause); + } + + public String getKeyspaceName() { + return getElementName(); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraQuerySyntaxException.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraQuerySyntaxException.java new file mode 100644 index 000000000..8cbc1d979 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraQuerySyntaxException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.cassandra.support.exception; + +import org.springframework.dao.InvalidDataAccessApiUsageException; + +/** + * Spring data access exception for a Cassandra query syntax error. + * + * @author Matthew T. Adams + */ +public class CassandraQuerySyntaxException extends InvalidDataAccessApiUsageException { + + private static final long serialVersionUID = 4398474399882434154L; + + public CassandraQuerySyntaxException(String msg) { + super(msg); + } + + public CassandraQuerySyntaxException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraReadTimeoutException.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraReadTimeoutException.java new file mode 100644 index 000000000..a2bff225f --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraReadTimeoutException.java @@ -0,0 +1,40 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.cassandra.support.exception; + +import org.springframework.dao.QueryTimeoutException; + +/** + * Spring data access exception for a Cassandra read timeout. + * + * @author Matthew T. Adams + */ +public class CassandraReadTimeoutException extends QueryTimeoutException { + + private static final long serialVersionUID = -787022307935203387L; + + private boolean wasDataReceived; + + public CassandraReadTimeoutException(boolean wasDataReceived, String msg, Throwable cause) { + super(msg); + this.wasDataReceived = wasDataReceived; + } + + public boolean getWasDataReceived() { + return wasDataReceived; + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraSchemaElementExistsException.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraSchemaElementExistsException.java new file mode 100644 index 000000000..79d981190 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraSchemaElementExistsException.java @@ -0,0 +1,50 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.cassandra.support.exception; + +import org.springframework.dao.NonTransientDataAccessException; + +/** + * Spring data access exception for when Cassandra schema element being created already exists. + * + * @author Matthew T. Adams + */ +public class CassandraSchemaElementExistsException extends NonTransientDataAccessException { + + private static final long serialVersionUID = 7798361273692300162L; + + public enum ElementType { + KEYSPACE, TABLE, COLUMN, INDEX; + } + + private String elementName; + private ElementType elementType; + + public CassandraSchemaElementExistsException(String elementName, ElementType elementType, String msg, Throwable cause) { + super(msg, cause); + this.elementName = elementName; + this.elementType = elementType; + } + + public String getElementName() { + return elementName; + } + + public ElementType getElementType() { + return elementType; + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraTableExistsException.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraTableExistsException.java new file mode 100644 index 000000000..7c23f242a --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraTableExistsException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.cassandra.support.exception; + +/** + * Spring data access exception for when a Cassandra table being created already exists. + * + * @author Matthew T. Adams + */ +public class CassandraTableExistsException extends CassandraSchemaElementExistsException { + + private static final long serialVersionUID = 6032967419751410352L; + + public CassandraTableExistsException(String tableName, String msg, Throwable cause) { + super(tableName, ElementType.TABLE, msg, cause); + } + + public String getTableName() { + return getElementName(); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraTraceRetrievalException.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraTraceRetrievalException.java new file mode 100644 index 000000000..9dc700a83 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraTraceRetrievalException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.cassandra.support.exception; + +import org.springframework.dao.TransientDataAccessException; + +/** + * Spring data access exception for a Cassandra trace retrieval exception. + * + * @author Matthew T. Adams + */ +public class CassandraTraceRetrievalException extends TransientDataAccessException { + + private static final long serialVersionUID = -3163557220324700239L; + + public CassandraTraceRetrievalException(String msg) { + super(msg); + } + + public CassandraTraceRetrievalException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraTruncateException.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraTruncateException.java new file mode 100644 index 000000000..fe7d18205 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraTruncateException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.cassandra.support.exception; + +import org.springframework.dao.TransientDataAccessException; + +/** + * Spring data access exception for a Cassandra truncate exception. + * + * @author Matthew T. Adams + */ +public class CassandraTruncateException extends TransientDataAccessException { + + private static final long serialVersionUID = 5730642491362430311L; + + public CassandraTruncateException(String msg) { + super(msg); + } + + public CassandraTruncateException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraTypeMismatchException.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraTypeMismatchException.java new file mode 100644 index 000000000..972aa01e7 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraTypeMismatchException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.cassandra.support.exception; + +import org.springframework.dao.TypeMismatchDataAccessException; + +/** + * Spring data access exception for a Cassandra type mismatch exception. + * + * @author Matthew T. Adams + */ +public class CassandraTypeMismatchException extends TypeMismatchDataAccessException { + + private static final long serialVersionUID = -7420058975444905629L; + + public CassandraTypeMismatchException(String msg) { + super(msg); + } + + public CassandraTypeMismatchException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraUnauthorizedException.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraUnauthorizedException.java new file mode 100644 index 000000000..7893b8b93 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraUnauthorizedException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.cassandra.support.exception; + +import org.springframework.dao.PermissionDeniedDataAccessException; + +/** + * Spring data access exception for when access to a Cassandra element is denied. + * + * @author Matthew T. Adams + */ +public class CassandraUnauthorizedException extends PermissionDeniedDataAccessException { + + private static final long serialVersionUID = 4618185356687726647L; + + public CassandraUnauthorizedException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraUncategorizedException.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraUncategorizedException.java new file mode 100644 index 000000000..f3ee3c9b6 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraUncategorizedException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.cassandra.support.exception; + +import org.springframework.dao.UncategorizedDataAccessException; + +/** + * Spring data access exception for an uncategorized Cassandra exception. + * + * @author Alex Shvid + * @author Matthew T. Adams + */ +public class CassandraUncategorizedException extends UncategorizedDataAccessException { + + private static final long serialVersionUID = 1029525121238025444L; + + public CassandraUncategorizedException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraWriteTimeoutException.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraWriteTimeoutException.java new file mode 100644 index 000000000..2836668df --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/exception/CassandraWriteTimeoutException.java @@ -0,0 +1,40 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.cassandra.support.exception; + +import org.springframework.dao.QueryTimeoutException; + +/** + * Spring data access exception for a Cassandra write timeout. + * + * @author Matthew T. Adams + */ +public class CassandraWriteTimeoutException extends QueryTimeoutException { + + private static final long serialVersionUID = -4374826375213670718L; + + private String writeType; + + public CassandraWriteTimeoutException(String writeType, String msg, Throwable cause) { + super(msg, cause); + this.writeType = writeType; + } + + public String getWriteType() { + return writeType; + } +} diff --git a/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/AbstractEmbeddedCassandraIntegrationTest.java b/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/AbstractEmbeddedCassandraIntegrationTest.java new file mode 100644 index 000000000..f6046ac02 --- /dev/null +++ b/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/AbstractEmbeddedCassandraIntegrationTest.java @@ -0,0 +1,71 @@ +package org.springframework.cassandra.test.integration.core.cql.generator; + +import java.io.IOException; +import java.util.UUID; + +import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.thrift.transport.TTransportException; +import org.cassandraunit.utils.EmbeddedCassandraServerHelper; +import org.junit.Before; +import org.junit.BeforeClass; + +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.KeyspaceMetadata; +import com.datastax.driver.core.Session; + +public abstract class AbstractEmbeddedCassandraIntegrationTest { + + @BeforeClass + public static void beforeClass() throws ConfigurationException, TTransportException, IOException, + InterruptedException { + EmbeddedCassandraServerHelper.startEmbeddedCassandra("cassandra.yaml"); + } + + /** + * Whether to connect to Cassandra. + */ + protected boolean connect = true; + /** + * The {@link Cluster} that's connected to Cassandra. + */ + protected Cluster cluster; + /** + * If not null, get a {@link Session} for the from the {@link #cluster}. + */ + protected String keyspace = "ks" + UUID.randomUUID().toString().replace("-", ""); + /** + * The {@link Session} for the {@link #keyspace} from the {@link #cluster}. + */ + protected Session session; + + /** + * Returns whether we're currently connected to the cluster. + */ + public boolean connected() { + return session != null; + } + + public Cluster cluster() { + return Cluster.builder().addContactPoint("localhost").withPort(9042).build(); + } + + @Before + public void before() { + if (connect && !connected()) { + cluster = cluster(); + + if (keyspace == null) { + session = cluster.connect(); + } else { + + KeyspaceMetadata kmd = cluster.getMetadata().getKeyspace(keyspace); + if (kmd == null) { // then create keyspace + session = cluster.connect(); + session.execute("CREATE KEYSPACE " + keyspace + + " WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 1};"); + session.execute("USE " + keyspace + ";"); + } // else keyspace already exists + } + } + } +} diff --git a/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/CqlTableSpecificationAssertions.java b/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/CqlTableSpecificationAssertions.java new file mode 100644 index 000000000..865be7af9 --- /dev/null +++ b/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/CqlTableSpecificationAssertions.java @@ -0,0 +1,144 @@ +package org.springframework.cassandra.test.integration.core.cql.generator; + +import static junit.framework.Assert.assertEquals; + +import java.util.List; +import java.util.Map; + +import org.springframework.cassandra.core.keyspace.ColumnSpecification; +import org.springframework.cassandra.core.keyspace.TableDescriptor; +import org.springframework.cassandra.core.keyspace.TableOption; +import org.springframework.cassandra.core.keyspace.TableOption.CachingOption; + +import com.datastax.driver.core.ColumnMetadata; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.TableMetadata; +import com.datastax.driver.core.TableMetadata.Options; + +public class CqlTableSpecificationAssertions { + + public static double DELTA = 1e-6; // delta for comparisons of doubles + + public static void assertTable(TableDescriptor expected, String keyspace, Session session) { + TableMetadata tmd = session.getCluster().getMetadata().getKeyspace(keyspace.toLowerCase()) + .getTable(expected.getName()); + + assertEquals(expected.getName().toLowerCase(), tmd.getName().toLowerCase()); + assertPartitionKeyColumns(expected, tmd); + assertPrimaryKeyColumns(expected, tmd); + assertColumns(expected.getColumns(), tmd.getColumns()); + assertOptions(expected.getOptions(), tmd.getOptions()); + } + + public static void assertPartitionKeyColumns(TableDescriptor expected, TableMetadata actual) { + assertColumns(expected.getPartitionKeyColumns(), actual.getPartitionKey()); + } + + public static void assertPrimaryKeyColumns(TableDescriptor expected, TableMetadata actual) { + assertColumns(expected.getKeyColumns(), actual.getPrimaryKey()); + } + + public static void assertOptions(Map expected, Options actual) { + + for (String key : expected.keySet()) { + + Object value = expected.get(key); + TableOption tableOption = getTableOptionFor(key); + + if (tableOption == null && key.equalsIgnoreCase(TableOption.COMPACT_STORAGE.getName())) { + // TODO: figure out how to tell if COMPACT STORAGE was used + continue; + } + + assertOption(tableOption, key, value, getOptionFor(tableOption, tableOption.getType(), actual)); + } + } + + @SuppressWarnings({ "unchecked", "incomplete-switch" }) + public static void assertOption(TableOption tableOption, String key, Object expected, Object actual) { + + if (tableOption == null) { // then this is a string-only or unknown value + key.equalsIgnoreCase(actual.toString()); // TODO: determine if this is the right test + } + + switch (tableOption) { + + case BLOOM_FILTER_FP_CHANCE: + case READ_REPAIR_CHANCE: + case DCLOCAL_READ_REPAIR_CHANCE: + assertEquals((Double) expected, (Double) actual, DELTA); + return; + + case CACHING: + assertEquals(CachingOption.valueOf((String) expected).getValue(), actual); + return; + + case COMPACTION: + assertCompaction((Map) expected, (Map) actual); + return; + + case COMPRESSION: + assertCompression((Map) expected, (Map) actual); + return; + } + + assertEquals(expected, actual); + } + + public static void assertCompaction(Map expected, Map actual) { + // TODO + } + + public static void assertCompression(Map expected, Map actual) { + // TODO + } + + public static TableOption getTableOptionFor(String key) { + try { + return TableOption.valueOf(key); + } catch (IllegalArgumentException x) { + return null; + } + } + + @SuppressWarnings("unchecked") + public static T getOptionFor(TableOption option, Class type, Options options) { + switch (option) { + case BLOOM_FILTER_FP_CHANCE: + return (T) (Double) options.getBloomFilterFalsePositiveChance(); + case CACHING: + return (T) options.getCaching(); + case COMMENT: + return (T) options.getComment(); + case COMPACTION: + return (T) options.getCompaction(); + case COMPACT_STORAGE: + throw new Error(); // TODO: figure out + case COMPRESSION: + return (T) options.getCompression(); + case DCLOCAL_READ_REPAIR_CHANCE: + return (T) (Double) options.getReadRepairChance(); + case GC_GRACE_SECONDS: + return (T) new Long(options.getGcGraceInSeconds()); + case READ_REPAIR_CHANCE: + return (T) (Double) options.getReadRepairChance(); + case REPLICATE_ON_WRITE: + return (T) (Boolean) options.getReplicateOnWrite(); + } + return null; + } + + public static void assertColumns(List expected, List actual) { + for (int i = 0; i < expected.size(); i++) { + ColumnSpecification expectedColumn = expected.get(i); + ColumnMetadata actualColumn = actual.get(i); + + assertColumn(expectedColumn, actualColumn); + } + } + + public static void assertColumn(ColumnSpecification expected, ColumnMetadata actual) { + assertEquals(expected.getName().toLowerCase(), actual.getName().toLowerCase()); + assertEquals(expected.getType(), actual.getType()); + } +} \ No newline at end of file diff --git a/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/CreateTableCqlGeneratorIntegrationTests.java b/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/CreateTableCqlGeneratorIntegrationTests.java new file mode 100644 index 000000000..be7b762d9 --- /dev/null +++ b/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/CreateTableCqlGeneratorIntegrationTests.java @@ -0,0 +1,55 @@ +package org.springframework.cassandra.test.integration.core.cql.generator; + +import static org.springframework.cassandra.test.integration.core.cql.generator.CqlTableSpecificationAssertions.assertTable; + +import org.junit.Test; +import org.springframework.cassandra.test.unit.core.cql.generator.CreateTableCqlGeneratorTests.BasicTest; +import org.springframework.cassandra.test.unit.core.cql.generator.CreateTableCqlGeneratorTests.CompositePartitionKeyTest; +import org.springframework.cassandra.test.unit.core.cql.generator.CreateTableCqlGeneratorTests.CreateTableTest; + +/** + * Integration tests that reuse unit tests. + * + * @author Matthew T. Adams + */ +public class CreateTableCqlGeneratorIntegrationTests { + + /** + * Integration test base class that knows how to do everything except instantiate the concrete unit test type T. + * + * @author Matthew T. Adams + * + * @param The concrete unit test class to which this integration test corresponds. + */ + public static abstract class Base extends AbstractEmbeddedCassandraIntegrationTest { + T unit; + + public abstract T unit(); + + @Test + public void test() { + unit = unit(); + unit.prepare(); + + session.execute(unit.cql); + + assertTable(unit.specification, keyspace, session); + } + } + + public static class BasicIntegrationTest extends Base { + + @Override + public BasicTest unit() { + return new BasicTest(); + } + } + + public static class CompositePartitionKeyIntegrationTest extends Base { + + @Override + public CompositePartitionKeyTest unit() { + return new CompositePartitionKeyTest(); + } + } +} \ No newline at end of file diff --git a/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/CqlStringUtilsTest.java b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/CqlStringUtilsTest.java new file mode 100644 index 000000000..a8c97c3e1 --- /dev/null +++ b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/CqlStringUtilsTest.java @@ -0,0 +1,19 @@ +package org.springframework.cassandra.test.unit.core.cql; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; +import static org.springframework.cassandra.core.cql.CqlStringUtils.isQuotedIdentifier; +import static org.springframework.cassandra.core.cql.CqlStringUtils.isUnquotedIdentifier; + +import org.junit.Test; + +public class CqlStringUtilsTest { + + @Test + public void testIsQuotedIdentifier() throws Exception { + assertFalse(isQuotedIdentifier("my\"id")); + assertTrue(isQuotedIdentifier("my\"\"id")); + assertFalse(isUnquotedIdentifier("my\"id")); + assertTrue(isUnquotedIdentifier("myid")); + } +} diff --git a/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/AlterTableCqlGeneratorTests.java b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/AlterTableCqlGeneratorTests.java new file mode 100644 index 000000000..420c7b35d --- /dev/null +++ b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/AlterTableCqlGeneratorTests.java @@ -0,0 +1,65 @@ +package org.springframework.cassandra.test.unit.core.cql.generator; + +import static junit.framework.Assert.assertTrue; +import static org.springframework.cassandra.core.keyspace.TableOperations.alterTable; + +import org.junit.Test; +import org.springframework.cassandra.core.cql.generator.AlterTableCqlGenerator; +import org.springframework.cassandra.core.keyspace.AlterTableSpecification; + +import com.datastax.driver.core.DataType; + +public class AlterTableCqlGeneratorTests { + + /** + * Asserts that the preamble is first & correctly formatted in the given CQL string. + */ + public static void assertPreamble(String tableName, String cql) { + assertTrue(cql.startsWith("ALTER TABLE " + tableName + " ")); + } + + /** + * Asserts that the given list of columns definitions are contained in the given CQL string properly. + * + * @param columnSpec IE, "foo text, bar blob" + */ + public static void assertColumnChanges(String columnSpec, String cql) { + assertTrue(cql.contains("")); + } + + /** + * Convenient base class that other test classes can use so as not to repeat the generics declarations. + */ + public static abstract class AlterTableTest extends + TableOperationCqlGeneratorTest { + } + + public static class BasicTest extends AlterTableTest { + + public String name = "mytable"; + public DataType alteredType = DataType.text(); + public String altered = "altered"; + + public DataType addedType = DataType.text(); + public String added = "added"; + + public String dropped = "dropped"; + + public AlterTableSpecification specification() { + return alterTable().name(name).alter(altered, alteredType).add(added, addedType).drop(dropped); + } + + public AlterTableCqlGenerator generator() { + return new AlterTableCqlGenerator(specification); + } + + @Test + public void test() { + prepare(); + + assertPreamble(name, cql); + assertColumnChanges( + String.format("ALTER %s TYPE %s, ADD %s %s, DROP %s", altered, alteredType, added, addedType, dropped), cql); + } + } +} diff --git a/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/CreateTableCqlGeneratorTests.java b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/CreateTableCqlGeneratorTests.java new file mode 100644 index 000000000..7ebd71580 --- /dev/null +++ b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/CreateTableCqlGeneratorTests.java @@ -0,0 +1,100 @@ +package org.springframework.cassandra.test.unit.core.cql.generator; + +import static junit.framework.Assert.assertTrue; +import static org.springframework.cassandra.core.keyspace.TableOperations.createTable; + +import org.junit.Test; +import org.springframework.cassandra.core.cql.generator.CreateTableCqlGenerator; +import org.springframework.cassandra.core.keyspace.CreateTableSpecification; + +import com.datastax.driver.core.DataType; + +public class CreateTableCqlGeneratorTests { + + /** + * Asserts that the preamble is first & correctly formatted in the given CQL string. + */ + public static void assertPreamble(String tableName, String cql) { + assertTrue(cql.startsWith("CREATE TABLE " + tableName + " ")); + } + + /** + * Asserts that the given primary key definition is contained in the given CQL string properly. + * + * @param primaryKeyString IE, "foo", "foo, bar, baz", "(foo, bar), baz", etc + */ + public static void assertPrimaryKey(String primaryKeyString, String cql) { + assertTrue(cql.contains(", PRIMARY KEY (" + primaryKeyString + "))")); + } + + /** + * Asserts that the given list of columns definitions are contained in the given CQL string properly. + * + * @param columnSpec IE, "foo text, bar blob" + */ + public static void assertColumns(String columnSpec, String cql) { + assertTrue(cql.contains("(" + columnSpec + ",")); + } + + /** + * Convenient base class that other test classes can use so as not to repeat the generics declarations or + * {@link #generator()} method. + */ + public static abstract class CreateTableTest extends + TableOperationCqlGeneratorTest { + + public CreateTableCqlGenerator generator() { + return new CreateTableCqlGenerator(specification); + } + } + + public static class BasicTest extends CreateTableTest { + + public String name = "mytable"; + public DataType partitionKeyType0 = DataType.text(); + public String partitionKey0 = "partitionKey0"; + public DataType columnType1 = DataType.text(); + public String column1 = "column1"; + + public CreateTableSpecification specification() { + return createTable().name(name).partitionKeyColumn(partitionKey0, partitionKeyType0).column(column1, columnType1); + } + + @Test + public void test() { + prepare(); + + assertPreamble(name, cql); + assertColumns(String.format("%s %s, %s %s", partitionKey0, partitionKeyType0, column1, columnType1), cql); + assertPrimaryKey(partitionKey0, cql); + } + } + + public static class CompositePartitionKeyTest extends CreateTableTest { + + public String name = "composite_partition_key_table"; + public DataType partKeyType0 = DataType.text(); + public String partKey0 = "partKey0"; + public DataType partKeyType1 = DataType.text(); + public String partKey1 = "partKey1"; + public String column0 = "column0"; + public DataType columnType0 = DataType.text(); + + @Override + public CreateTableSpecification specification() { + return createTable().name(name).partitionKeyColumn(partKey0, partKeyType0) + .partitionKeyColumn(partKey1, partKeyType1).column(column0, columnType0); + } + + @Test + public void test() { + prepare(); + + assertPreamble(name, cql); + assertColumns( + String.format("%s %s, %s %s, %s %s", partKey0, partKeyType0, partKey1, partKeyType1, column0, columnType0), + cql); + assertPrimaryKey(String.format("(%s, %s)", partKey0, partKey1), cql); + } + } +} diff --git a/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/DropTableCqlGeneratorTests.java b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/DropTableCqlGeneratorTests.java new file mode 100644 index 000000000..330fe7382 --- /dev/null +++ b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/DropTableCqlGeneratorTests.java @@ -0,0 +1,54 @@ +package org.springframework.cassandra.test.unit.core.cql.generator; + +import static junit.framework.Assert.assertTrue; +import static org.springframework.cassandra.core.keyspace.TableOperations.dropTable; + +import org.junit.Test; +import org.springframework.cassandra.core.cql.generator.DropTableCqlGenerator; +import org.springframework.cassandra.core.keyspace.DropTableSpecification; + +public class DropTableCqlGeneratorTests { + + /** + * Asserts that the preamble is first & correctly formatted in the given CQL string. + */ + public static void assertStatement(String tableName, String cql) { + assertTrue(cql.equals("DROP TABLE " + tableName + ";")); + } + + /** + * Asserts that the given list of columns definitions are contained in the given CQL string properly. + * + * @param columnSpec IE, "foo text, bar blob" + */ + public static void assertColumnChanges(String columnSpec, String cql) { + assertTrue(cql.contains("")); + } + + /** + * Convenient base class that other test classes can use so as not to repeat the generics declarations. + */ + public static abstract class DropTableTest extends + TableOperationCqlGeneratorTest { + } + + public static class BasicTest extends DropTableTest { + + public String name = "mytable"; + + public DropTableSpecification specification() { + return dropTable().name(name); + } + + public DropTableCqlGenerator generator() { + return new DropTableCqlGenerator(specification); + } + + @Test + public void test() { + prepare(); + + assertStatement(name, cql); + } + } +} diff --git a/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/TableOperationCqlGeneratorTest.java b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/TableOperationCqlGeneratorTest.java new file mode 100644 index 000000000..cb5172d2c --- /dev/null +++ b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/TableOperationCqlGeneratorTest.java @@ -0,0 +1,36 @@ +package org.springframework.cassandra.test.unit.core.cql.generator; + +import org.springframework.cassandra.core.cql.generator.TableNameCqlGenerator; +import org.springframework.cassandra.core.keyspace.TableNameSpecification; + +/** + * Useful test class that specifies just about as much as you can for a CQL generation test. Intended to be extended by + * classes that contain methods annotated with {@link Test}. Everything is public because this is a test class with no + * need for encapsulation, and it makes for easier reuse in other tests like integration tests (hint hint). + * + * @author Matthew T. Adams + * + * @param The type of the {@link TableNameSpecification} + * @param The type of the {@link TableNameCqlGenerator} + */ +public abstract class TableOperationCqlGeneratorTest, G extends TableNameCqlGenerator> { + + public abstract S specification(); + + public abstract G generator(); + + public String tableName; + public S specification; + public G generator; + public String cql; + + public void prepare() { + this.specification = specification(); + this.generator = generator(); + this.cql = generateCql(); + } + + public String generateCql() { + return generator.toCql(); + } +} \ No newline at end of file diff --git a/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/keyspace/OptionTest.java b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/keyspace/OptionTest.java new file mode 100644 index 000000000..1d2eff43e --- /dev/null +++ b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/keyspace/OptionTest.java @@ -0,0 +1,104 @@ +package org.springframework.cassandra.test.unit.core.keyspace; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import java.lang.annotation.RetentionPolicy; + +import org.junit.Test; +import org.springframework.cassandra.core.keyspace.DefaultOption; +import org.springframework.cassandra.core.keyspace.Option; + +public class OptionTest { + + @Test(expected = IllegalArgumentException.class) + public void testOptionWithNullName() { + new DefaultOption(null, Object.class, true, true, true); + } + + @Test(expected = IllegalArgumentException.class) + public void testOptionWithEmptyName() { + new DefaultOption("", Object.class, true, true, true); + } + + @Test + public void testOptionWithNullType() { + new DefaultOption("opt", null, true, true, true); + new DefaultOption("opt", null, false, true, true); + } + + @Test + public void testOptionWithNullTypeIsCoerceable() { + Option op = new DefaultOption("opt", null, true, true, true); + assertTrue(op.isCoerceable("")); + assertTrue(op.isCoerceable(null)); + } + + @Test + public void testOptionValueCoercion() { + String name = "my_option"; + Class type = String.class; + boolean requires = true; + boolean escapes = true; + boolean quotes = true; + + Option op = new DefaultOption(name, type, requires, escapes, quotes); + + assertTrue(op.isCoerceable("opt")); + assertEquals("'opt'", op.toString("opt")); + assertEquals("'opt''n'", op.toString("opt'n")); + + type = Long.class; + escapes = false; + quotes = false; + op = new DefaultOption(name, type, requires, escapes, quotes); + + String expected = "1"; + for (Object value : new Object[] { 1, "1" }) { + assertTrue(op.isCoerceable(value)); + assertEquals(expected, op.toString(value)); + } + assertFalse(op.isCoerceable("x")); + assertTrue(op.isCoerceable(null)); + + type = Long.class; + escapes = false; + quotes = true; + op = new DefaultOption(name, type, requires, escapes, quotes); + + expected = "'1'"; + for (Object value : new Object[] { 1, "1" }) { + assertTrue(op.isCoerceable(value)); + assertEquals(expected, op.toString(value)); + } + assertFalse(op.isCoerceable("x")); + assertTrue(op.isCoerceable(null)); + + type = Double.class; + escapes = false; + quotes = false; + op = new DefaultOption(name, type, requires, escapes, quotes); + + String[] expecteds = new String[] { "1", "1.0", "1.0", "1", "1.0", null }; + Object[] values = new Object[] { 1, 1.0F, 1.0D, "1", "1.0", null }; + for (int i = 0; i < values.length; i++) { + assertTrue(op.isCoerceable(values[i])); + assertEquals(expecteds[i], op.toString(values[i])); + } + assertFalse(op.isCoerceable("x")); + assertTrue(op.isCoerceable(null)); + + type = RetentionPolicy.class; + escapes = false; + quotes = false; + op = new DefaultOption(name, type, requires, escapes, quotes); + + assertTrue(op.isCoerceable(null)); + assertTrue(op.isCoerceable(RetentionPolicy.CLASS)); + assertTrue(op.isCoerceable("CLASS")); + assertFalse(op.isCoerceable("x")); + assertEquals("CLASS", op.toString("CLASS")); + assertEquals("CLASS", op.toString(RetentionPolicy.CLASS)); + } +} diff --git a/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/support/CassandraExceptionTranslatorTest.java b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/support/CassandraExceptionTranslatorTest.java new file mode 100644 index 000000000..255947c18 --- /dev/null +++ b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/support/CassandraExceptionTranslatorTest.java @@ -0,0 +1,71 @@ +package org.springframework.cassandra.test.unit.support; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.springframework.cassandra.support.CassandraExceptionTranslator; +import org.springframework.cassandra.support.exception.CassandraInvalidConfigurationInQueryException; +import org.springframework.cassandra.support.exception.CassandraInvalidQueryException; +import org.springframework.cassandra.support.exception.CassandraKeyspaceExistsException; +import org.springframework.cassandra.support.exception.CassandraSchemaElementExistsException; +import org.springframework.cassandra.support.exception.CassandraTableExistsException; +import org.springframework.dao.DataAccessException; + +import com.datastax.driver.core.exceptions.AlreadyExistsException; +import com.datastax.driver.core.exceptions.InvalidConfigurationInQueryException; +import com.datastax.driver.core.exceptions.InvalidQueryException; + +public class CassandraExceptionTranslatorTest { + + CassandraExceptionTranslator tx = new CassandraExceptionTranslator(); + + @Test + public void testTableExistsException() { + String keyspace = ""; + String table = "tbl"; + AlreadyExistsException cx = new AlreadyExistsException(keyspace, table); + DataAccessException dax = tx.translateExceptionIfPossible(cx); + assertNotNull(dax); + assertTrue(dax instanceof CassandraTableExistsException); + + CassandraTableExistsException x = (CassandraTableExistsException) dax; + assertEquals(table, x.getTableName()); + assertEquals(x.getTableName(), x.getElementName()); + assertEquals(CassandraSchemaElementExistsException.ElementType.TABLE, x.getElementType()); + assertEquals(cx, x.getCause()); + } + + @Test + public void testKeyspaceExistsException() { + String keyspace = "ks"; + String table = ""; + AlreadyExistsException cx = new AlreadyExistsException(keyspace, table); + DataAccessException dax = tx.translateExceptionIfPossible(cx); + assertNotNull(dax); + assertTrue(dax instanceof CassandraKeyspaceExistsException); + + CassandraKeyspaceExistsException x = (CassandraKeyspaceExistsException) dax; + assertEquals(keyspace, x.getKeyspaceName()); + assertEquals(x.getKeyspaceName(), x.getElementName()); + assertEquals(CassandraSchemaElementExistsException.ElementType.KEYSPACE, x.getElementType()); + assertEquals(cx, x.getCause()); + } + + @Test + public void testInvalidConfigurationInQueryException() { + String msg = "msg"; + InvalidQueryException cx = new InvalidConfigurationInQueryException(msg); + DataAccessException dax = tx.translateExceptionIfPossible(cx); + assertNotNull(dax); + assertTrue(dax instanceof CassandraInvalidConfigurationInQueryException); + assertEquals(cx, dax.getCause()); + + cx = new InvalidQueryException(msg); + dax = tx.translateExceptionIfPossible(cx); + assertNotNull(dax); + assertTrue(dax instanceof CassandraInvalidQueryException); + assertEquals(cx, dax.getCause()); + } +} diff --git a/spring-cassandra/src/test/resources/cassandra-keyspace.yaml b/spring-cassandra/src/test/resources/cassandra-keyspace.yaml new file mode 100644 index 000000000..a0e13da6e --- /dev/null +++ b/spring-cassandra/src/test/resources/cassandra-keyspace.yaml @@ -0,0 +1,3 @@ +name: test +replicationFactor: 1 +strategy: org.apache.cassandra.locator.SimpleStrategy \ No newline at end of file diff --git a/spring-cassandra/src/test/resources/cassandra.yaml b/spring-cassandra/src/test/resources/cassandra.yaml new file mode 100644 index 000000000..82fcfc5ad --- /dev/null +++ b/spring-cassandra/src/test/resources/cassandra.yaml @@ -0,0 +1,690 @@ +# Cassandra storage config YAML + +# NOTE: +# See http://wiki.apache.org/cassandra/StorageConfiguration for +# full explanations of configuration directives +# /NOTE + +# The name of the cluster. This is mainly used to prevent machines in +# one logical cluster from joining another. +cluster_name: 'Test Cluster' + +# This defines the number of tokens randomly assigned to this node on the ring +# The more tokens, relative to other nodes, the larger the proportion of data +# that this node will store. You probably want all nodes to have the same number +# of tokens assuming they have equal hardware capability. +# +# If you leave this unspecified, Cassandra will use the default of 1 token for legacy compatibility, +# and will use the initial_token as described below. +# +# Specifying initial_token will override this setting. +# +# If you already have a cluster with 1 token per node, and wish to migrate to +# multiple tokens per node, see http://wiki.apache.org/cassandra/Operations +# num_tokens: 256 + +# If you haven't specified num_tokens, or have set it to the default of 1 then +# you should always specify InitialToken when setting up a production +# cluster for the first time, and often when adding capacity later. +# The principle is that each node should be given an equal slice of +# the token ring; see http://wiki.apache.org/cassandra/Operations +# for more details. +# +# If blank, Cassandra will request a token bisecting the range of +# the heaviest-loaded existing node. If there is no load information +# available, such as is the case with a new cluster, it will pick +# a random token, which will lead to hot spots. +initial_token: + +# See http://wiki.apache.org/cassandra/HintedHandoff +hinted_handoff_enabled: true +# this defines the maximum amount of time a dead host will have hints +# generated. After it has been dead this long, new hints for it will not be +# created until it has been seen alive and gone down again. +max_hint_window_in_ms: 10800000 # 3 hours +# throttle in KBs per second, per delivery thread +hinted_handoff_throttle_in_kb: 1024 +# Number of threads with which to deliver hints; +# Consider increasing this number when you have multi-dc deployments, since +# cross-dc handoff tends to be slower +max_hints_delivery_threads: 2 + +# The following setting populates the page cache on memtable flush and compaction +# WARNING: Enable this setting only when the whole node's data fits in memory. +# Defaults to: false +# populate_io_cache_on_flush: false + +# Authentication backend, implementing IAuthenticator; used to identify users +# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthenticator, +# PasswordAuthenticator}. +# +# - AllowAllAuthenticator performs no checks - set it to disable authentication. +# - PasswordAuthenticator relies on username/password pairs to authenticate +# users. It keeps usernames and hashed passwords in system_auth.credentials table. +# Please increase system_auth keyspace replication factor if you use this authenticator. +authenticator: org.apache.cassandra.auth.AllowAllAuthenticator + +# Authorization backend, implementing IAuthorizer; used to limit access/provide permissions +# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthorizer, +# CassandraAuthorizer}. +# +# - AllowAllAuthorizer allows any action to any user - set it to disable authorization. +# - CassandraAuthorizer stores permissions in system_auth.permissions table. Please +# increase system_auth keyspace replication factor if you use this authorizer. +authorizer: org.apache.cassandra.auth.AllowAllAuthorizer + +# Validity period for permissions cache (fetching permissions can be an +# expensive operation depending on the authorizer, CassandraAuthorizer is +# one example). Defaults to 2000, set to 0 to disable. +# Will be disabled automatically for AllowAllAuthorizer. +# permissions_validity_in_ms: 2000 + +# The partitioner is responsible for distributing rows (by key) across +# nodes in the cluster. Any IPartitioner may be used, including your +# own as long as it is on the classpath. Out of the box, Cassandra +# provides org.apache.cassandra.dht.{Murmur3Partitioner, RandomPartitioner +# ByteOrderedPartitioner, OrderPreservingPartitioner (deprecated)}. +# +# - RandomPartitioner distributes rows across the cluster evenly by md5. +# This is the default prior to 1.2 and is retained for compatibility. +# - Murmur3Partitioner is similar to RandomPartioner but uses Murmur3_128 +# Hash Function instead of md5. When in doubt, this is the best option. +# - ByteOrderedPartitioner orders rows lexically by key bytes. BOP allows +# scanning rows in key order, but the ordering can generate hot spots +# for sequential insertion workloads. +# - OrderPreservingPartitioner is an obsolete form of BOP, that stores +# - keys in a less-efficient format and only works with keys that are +# UTF8-encoded Strings. +# - CollatingOPP collates according to EN,US rules rather than lexical byte +# ordering. Use this as an example if you need custom collation. +# +# See http://wiki.apache.org/cassandra/Operations for more on +# partitioners and token selection. +partitioner: org.apache.cassandra.dht.Murmur3Partitioner + +# Directories where Cassandra should store data on disk. Cassandra +# will spread data evenly across them, subject to the granularity of +# the configured compaction strategy. +data_file_directories: + - target/embeddedCassandra/data + +# commit log +commitlog_directory: target/embeddedCassandra/commitlog + +# policy for data disk failures: +# stop: shut down gossip and Thrift, leaving the node effectively dead, but +# can still be inspected via JMX. +# best_effort: stop using the failed disk and respond to requests based on +# remaining available sstables. This means you WILL see obsolete +# data at CL.ONE! +# ignore: ignore fatal errors and let requests fail, as in pre-1.2 Cassandra +disk_failure_policy: stop + +# Maximum size of the key cache in memory. +# +# Each key cache hit saves 1 seek and each row cache hit saves 2 seeks at the +# minimum, sometimes more. The key cache is fairly tiny for the amount of +# time it saves, so it's worthwhile to use it at large numbers. +# The row cache saves even more time, but must contain the entire row, +# so it is extremely space-intensive. It's best to only use the +# row cache if you have hot rows or static rows. +# +# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup. +# +# Default value is empty to make it "auto" (min(5% of Heap (in MB), 100MB)). Set to 0 to disable key cache. +key_cache_size_in_mb: + +# Duration in seconds after which Cassandra should +# save the key cache. Caches are saved to saved_caches_directory as +# specified in this configuration file. +# +# Saved caches greatly improve cold-start speeds, and is relatively cheap in +# terms of I/O for the key cache. Row cache saving is much more expensive and +# has limited use. +# +# Default is 14400 or 4 hours. +key_cache_save_period: 14400 + +# Number of keys from the key cache to save +# Disabled by default, meaning all keys are going to be saved +# key_cache_keys_to_save: 100 + +# Maximum size of the row cache in memory. +# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup. +# +# Default value is 0, to disable row caching. +row_cache_size_in_mb: 0 + +# Duration in seconds after which Cassandra should +# safe the row cache. Caches are saved to saved_caches_directory as specified +# in this configuration file. +# +# Saved caches greatly improve cold-start speeds, and is relatively cheap in +# terms of I/O for the key cache. Row cache saving is much more expensive and +# has limited use. +# +# Default is 0 to disable saving the row cache. +row_cache_save_period: 0 + +# Number of keys from the row cache to save +# Disabled by default, meaning all keys are going to be saved +# row_cache_keys_to_save: 100 + +# The provider for the row cache to use. +# +# Supported values are: ConcurrentLinkedHashCacheProvider, SerializingCacheProvider +# +# SerializingCacheProvider serialises the contents of the row and stores +# it in native memory, i.e., off the JVM Heap. Serialized rows take +# significantly less memory than "live" rows in the JVM, so you can cache +# more rows in a given memory footprint. And storing the cache off-heap +# means you can use smaller heap sizes, reducing the impact of GC pauses. +# Note however that when a row is requested from the row cache, it must be +# deserialized into the heap for use. +# +# It is also valid to specify the fully-qualified class name to a class +# that implements org.apache.cassandra.cache.IRowCacheProvider. +# +# Defaults to SerializingCacheProvider +row_cache_provider: SerializingCacheProvider + +# saved caches +saved_caches_directory: target/embeddedCassandra/saved_caches + +# commitlog_sync may be either "periodic" or "batch." +# When in batch mode, Cassandra won't ack writes until the commit log +# has been fsynced to disk. It will wait up to +# commitlog_sync_batch_window_in_ms milliseconds for other writes, before +# performing the sync. +# +# commitlog_sync: batch +# commitlog_sync_batch_window_in_ms: 50 +# +# the other option is "periodic" where writes may be acked immediately +# and the CommitLog is simply synced every commitlog_sync_period_in_ms +# milliseconds. +commitlog_sync: periodic +commitlog_sync_period_in_ms: 10000 + +# The size of the individual commitlog file segments. A commitlog +# segment may be archived, deleted, or recycled once all the data +# in it (potentially from each columnfamily in the system) has been +# flushed to sstables. +# +# The default size is 32, which is almost always fine, but if you are +# archiving commitlog segments (see commitlog_archiving.properties), +# then you probably want a finer granularity of archiving; 8 or 16 MB +# is reasonable. +commitlog_segment_size_in_mb: 32 + +# any class that implements the SeedProvider interface and has a +# constructor that takes a Map of parameters will do. +seed_provider: + # Addresses of hosts that are deemed contact points. + # Cassandra nodes use this list of hosts to find each other and learn + # the topology of the ring. You must change this if you are running + # multiple nodes! + - class_name: org.apache.cassandra.locator.SimpleSeedProvider + parameters: + # seeds is actually a comma-delimited list of addresses. + # Ex: ",," + - seeds: "127.0.0.1" + +# emergency pressure valve: each time heap usage after a full (CMS) +# garbage collection is above this fraction of the max, Cassandra will +# flush the largest memtables. +# +# Set to 1.0 to disable. Setting this lower than +# CMSInitiatingOccupancyFraction is not likely to be useful. +# +# RELYING ON THIS AS YOUR PRIMARY TUNING MECHANISM WILL WORK POORLY: +# it is most effective under light to moderate load, or read-heavy +# workloads; under truly massive write load, it will often be too +# little, too late. +flush_largest_memtables_at: 0.75 + +# emergency pressure valve #2: the first time heap usage after a full +# (CMS) garbage collection is above this fraction of the max, +# Cassandra will reduce cache maximum _capacity_ to the given fraction +# of the current _size_. Should usually be set substantially above +# flush_largest_memtables_at, since that will have less long-term +# impact on the system. +# +# Set to 1.0 to disable. Setting this lower than +# CMSInitiatingOccupancyFraction is not likely to be useful. +reduce_cache_sizes_at: 0.85 +reduce_cache_capacity_to: 0.6 + +# For workloads with more data than can fit in memory, Cassandra's +# bottleneck will be reads that need to fetch data from +# disk. "concurrent_reads" should be set to (16 * number_of_drives) in +# order to allow the operations to enqueue low enough in the stack +# that the OS and drives can reorder them. +# +# On the other hand, since writes are almost never IO bound, the ideal +# number of "concurrent_writes" is dependent on the number of cores in +# your system; (8 * number_of_cores) is a good rule of thumb. +concurrent_reads: 32 +concurrent_writes: 32 + +# Total memory to use for memtables. Cassandra will flush the largest +# memtable when this much memory is used. +# If omitted, Cassandra will set it to 1/3 of the heap. +# memtable_total_space_in_mb: 2048 + +# Total space to use for commitlogs. Since commitlog segments are +# mmapped, and hence use up address space, the default size is 32 +# on 32-bit JVMs, and 1024 on 64-bit JVMs. +# +# If space gets above this value (it will round up to the next nearest +# segment multiple), Cassandra will flush every dirty CF in the oldest +# segment and remove it. So a small total commitlog space will tend +# to cause more flush activity on less-active columnfamilies. +# commitlog_total_space_in_mb: 4096 + +# This sets the amount of memtable flush writer threads. These will +# be blocked by disk io, and each one will hold a memtable in memory +# while blocked. If you have a large heap and many data directories, +# you can increase this value for better flush performance. +# By default this will be set to the amount of data directories defined. +#memtable_flush_writers: 1 + +# the number of full memtables to allow pending flush, that is, +# waiting for a writer thread. At a minimum, this should be set to +# the maximum number of secondary indexes created on a single CF. +memtable_flush_queue_size: 4 + +# Whether to, when doing sequential writing, fsync() at intervals in +# order to force the operating system to flush the dirty +# buffers. Enable this to avoid sudden dirty buffer flushing from +# impacting read latencies. Almost always a good idea on SSDs; not +# necessarily on platters. +trickle_fsync: false +trickle_fsync_interval_in_kb: 10240 + +# TCP port, for commands and data +storage_port: 7000 + +# SSL port, for encrypted communication. Unused unless enabled in +# encryption_options +ssl_storage_port: 7001 + +# Address to bind to and tell other Cassandra nodes to connect to. You +# _must_ change this if you want multiple nodes to be able to +# communicate! +# +# Leaving it blank leaves it up to InetAddress.getLocalHost(). This +# will always do the Right Thing _if_ the node is properly configured +# (hostname, name resolution, etc), and the Right Thing is to use the +# address associated with the hostname (it might not be). +# +# Setting this to 0.0.0.0 is always wrong. +listen_address: localhost + +# Address to broadcast to other Cassandra nodes +# Leaving this blank will set it to the same value as listen_address +# broadcast_address: 1.2.3.4 + +# Internode authentication backend, implementing IInternodeAuthenticator; +# used to allow/disallow connections from peer nodes. +# internode_authenticator: org.apache.cassandra.auth.AllowAllInternodeAuthenticator + +# Whether to start the native transport server. +# Please note that the address on which the native transport is bound is the +# same as the rpc_address. The port however is different and specified below. +start_native_transport: true +# port for the CQL native transport to listen for clients on +native_transport_port: 9042 +# The minimum and maximum threads for handling requests when the native +# transport is used. They are similar to rpc_min_threads and rpc_max_threads, +# though the defaults differ slightly. +# native_transport_min_threads: 16 +# native_transport_max_threads: 128 + +# Whether to start the thrift rpc server. +start_rpc: true + +# The address to bind the Thrift RPC service to -- clients connect +# here. Unlike ListenAddress above, you _can_ specify 0.0.0.0 here if +# you want Thrift to listen on all interfaces. +# +# Leaving this blank has the same effect it does for ListenAddress, +# (i.e. it will be based on the configured hostname of the node). +rpc_address: localhost +# port for Thrift to listen for clients on +rpc_port: 9160 + +# enable or disable keepalive on rpc connections +rpc_keepalive: true + +# Cassandra provides three out-of-the-box options for the RPC Server: +# +# sync -> One thread per thrift connection. For a very large number of clients, memory +# will be your limiting factor. On a 64 bit JVM, 180KB is the minimum stack size +# per thread, and that will correspond to your use of virtual memory (but physical memory +# may be limited depending on use of stack space). +# +# hsha -> Stands for "half synchronous, half asynchronous." All thrift clients are handled +# asynchronously using a small number of threads that does not vary with the amount +# of thrift clients (and thus scales well to many clients). The rpc requests are still +# synchronous (one thread per active request). +# +# The default is sync because on Windows hsha is about 30% slower. On Linux, +# sync/hsha performance is about the same, with hsha of course using less memory. +# +# Alternatively, can provide your own RPC server by providing the fully-qualified class name +# of an o.a.c.t.TServerFactory that can create an instance of it. +rpc_server_type: sync + +# Uncomment rpc_min|max_thread to set request pool size limits. +# +# Regardless of your choice of RPC server (see above), the number of maximum requests in the +# RPC thread pool dictates how many concurrent requests are possible (but if you are using the sync +# RPC server, it also dictates the number of clients that can be connected at all). +# +# The default is unlimited and thus provides no protection against clients overwhelming the server. You are +# encouraged to set a maximum that makes sense for you in production, but do keep in mind that +# rpc_max_threads represents the maximum number of client requests this server may execute concurrently. +# +# rpc_min_threads: 16 +# rpc_max_threads: 2048 + +# uncomment to set socket buffer sizes on rpc connections +# rpc_send_buff_size_in_bytes: +# rpc_recv_buff_size_in_bytes: + +# Uncomment to set socket buffer size for internode communication +# Note that when setting this, the buffer size is limited by net.core.wmem_max +# and when not setting it it is defined by net.ipv4.tcp_wmem +# See: +# /proc/sys/net/core/wmem_max +# /proc/sys/net/core/rmem_max +# /proc/sys/net/ipv4/tcp_wmem +# /proc/sys/net/ipv4/tcp_wmem +# and: man tcp +# internode_send_buff_size_in_bytes: +# internode_recv_buff_size_in_bytes: + +# Frame size for thrift (maximum field length). +thrift_framed_transport_size_in_mb: 15 + +# The max length of a thrift message, including all fields and +# internal thrift overhead. +thrift_max_message_length_in_mb: 16 + +# Set to true to have Cassandra create a hard link to each sstable +# flushed or streamed locally in a backups/ subdirectory of the +# keyspace data. Removing these links is the operator's +# responsibility. +incremental_backups: false + +# Whether or not to take a snapshot before each compaction. Be +# careful using this option, since Cassandra won't clean up the +# snapshots for you. Mostly useful if you're paranoid when there +# is a data format change. +snapshot_before_compaction: false + +# Whether or not a snapshot is taken of the data before keyspace truncation +# or dropping of column families. The STRONGLY advised default of true +# should be used to provide data safety. If you set this flag to false, you will +# lose data on truncation or drop. +auto_snapshot: true + +# Add column indexes to a row after its contents reach this size. +# Increase if your column values are large, or if you have a very large +# number of columns. The competing causes are, Cassandra has to +# deserialize this much of the row to read a single column, so you want +# it to be small - at least if you do many partial-row reads - but all +# the index data is read for each access, so you don't want to generate +# that wastefully either. +column_index_size_in_kb: 64 + +# Size limit for rows being compacted in memory. Larger rows will spill +# over to disk and use a slower two-pass compaction process. A message +# will be logged specifying the row key. +in_memory_compaction_limit_in_mb: 64 + +# Number of simultaneous compactions to allow, NOT including +# validation "compactions" for anti-entropy repair. Simultaneous +# compactions can help preserve read performance in a mixed read/write +# workload, by mitigating the tendency of small sstables to accumulate +# during a single long running compactions. The default is usually +# fine and if you experience problems with compaction running too +# slowly or too fast, you should look at +# compaction_throughput_mb_per_sec first. +# +# concurrent_compactors defaults to the number of cores. +# Uncomment to make compaction mono-threaded, the pre-0.8 default. +#concurrent_compactors: 1 + +# Multi-threaded compaction. When enabled, each compaction will use +# up to one thread per core, plus one thread per sstable being merged. +# This is usually only useful for SSD-based hardware: otherwise, +# your concern is usually to get compaction to do LESS i/o (see: +# compaction_throughput_mb_per_sec), not more. +multithreaded_compaction: false + +# Throttles compaction to the given total throughput across the entire +# system. The faster you insert data, the faster you need to compact in +# order to keep the sstable count down, but in general, setting this to +# 16 to 32 times the rate you are inserting data is more than sufficient. +# Setting this to 0 disables throttling. Note that this account for all types +# of compaction, including validation compaction. +compaction_throughput_mb_per_sec: 16 + +# Track cached row keys during compaction, and re-cache their new +# positions in the compacted sstable. Disable if you use really large +# key caches. +compaction_preheat_key_cache: true + +# Throttles all outbound streaming file transfers on this node to the +# given total throughput in Mbps. This is necessary because Cassandra does +# mostly sequential IO when streaming data during bootstrap or repair, which +# can lead to saturating the network connection and degrading rpc performance. +# When unset, the default is 200 Mbps or 25 MB/s. +# stream_throughput_outbound_megabits_per_sec: 200 + +# How long the coordinator should wait for read operations to complete +read_request_timeout_in_ms: 10000 +# How long the coordinator should wait for seq or index scans to complete +range_request_timeout_in_ms: 10000 +# How long the coordinator should wait for writes to complete +write_request_timeout_in_ms: 10000 +# How long the coordinator should wait for truncates to complete +# (This can be much longer, because unless auto_snapshot is disabled +# we need to flush first so we can snapshot before removing the data.) +truncate_request_timeout_in_ms: 60000 +# The default timeout for other, miscellaneous operations +request_timeout_in_ms: 10000 + +# Enable operation timeout information exchange between nodes to accurately +# measure request timeouts, If disabled cassandra will assuming the request +# was forwarded to the replica instantly by the coordinator +# +# Warning: before enabling this property make sure to ntp is installed +# and the times are synchronized between the nodes. +cross_node_timeout: false + +# Enable socket timeout for streaming operation. +# When a timeout occurs during streaming, streaming is retried from the start +# of the current file. This _can_ involve re-streaming an important amount of +# data, so you should avoid setting the value too low. +# Default value is 0, which never timeout streams. +# streaming_socket_timeout_in_ms: 0 + +# phi value that must be reached for a host to be marked down. +# most users should never need to adjust this. +# phi_convict_threshold: 8 + +# endpoint_snitch -- Set this to a class that implements +# IEndpointSnitch. The snitch has two functions: +# - it teaches Cassandra enough about your network topology to route +# requests efficiently +# - it allows Cassandra to spread replicas around your cluster to avoid +# correlated failures. It does this by grouping machines into +# "datacenters" and "racks." Cassandra will do its best not to have +# more than one replica on the same "rack" (which may not actually +# be a physical location) +# +# IF YOU CHANGE THE SNITCH AFTER DATA IS INSERTED INTO THE CLUSTER, +# YOU MUST RUN A FULL REPAIR, SINCE THE SNITCH AFFECTS WHERE REPLICAS +# ARE PLACED. +# +# Out of the box, Cassandra provides +# - SimpleSnitch: +# Treats Strategy order as proximity. This improves cache locality +# when disabling read repair, which can further improve throughput. +# Only appropriate for single-datacenter deployments. +# - PropertyFileSnitch: +# Proximity is determined by rack and data center, which are +# explicitly configured in cassandra-topology.properties. +# - GossipingPropertyFileSnitch +# The rack and datacenter for the local node are defined in +# cassandra-rackdc.properties and propagated to other nodes via gossip. If +# cassandra-topology.properties exists, it is used as a fallback, allowing +# migration from the PropertyFileSnitch. +# - RackInferringSnitch: +# Proximity is determined by rack and data center, which are +# assumed to correspond to the 3rd and 2nd octet of each node's +# IP address, respectively. Unless this happens to match your +# deployment conventions (as it did Facebook's), this is best used +# as an example of writing a custom Snitch class. +# - Ec2Snitch: +# Appropriate for EC2 deployments in a single Region. Loads Region +# and Availability Zone information from the EC2 API. The Region is +# treated as the datacenter, and the Availability Zone as the rack. +# Only private IPs are used, so this will not work across multiple +# Regions. +# - Ec2MultiRegionSnitch: +# Uses public IPs as broadcast_address to allow cross-region +# connectivity. (Thus, you should set seed addresses to the public +# IP as well.) You will need to open the storage_port or +# ssl_storage_port on the public IP firewall. (For intra-Region +# traffic, Cassandra will switch to the private IP after +# establishing a connection.) +# +# You can use a custom Snitch by setting this to the full class name +# of the snitch, which will be assumed to be on your classpath. +endpoint_snitch: SimpleSnitch + +# controls how often to perform the more expensive part of host score +# calculation +dynamic_snitch_update_interval_in_ms: 100 +# controls how often to reset all host scores, allowing a bad host to +# possibly recover +dynamic_snitch_reset_interval_in_ms: 600000 +# if set greater than zero and read_repair_chance is < 1.0, this will allow +# 'pinning' of replicas to hosts in order to increase cache capacity. +# The badness threshold will control how much worse the pinned host has to be +# before the dynamic snitch will prefer other replicas over it. This is +# expressed as a double which represents a percentage. Thus, a value of +# 0.2 means Cassandra would continue to prefer the static snitch values +# until the pinned host was 20% worse than the fastest. +dynamic_snitch_badness_threshold: 0.1 + +# request_scheduler -- Set this to a class that implements +# RequestScheduler, which will schedule incoming client requests +# according to the specific policy. This is useful for multi-tenancy +# with a single Cassandra cluster. +# NOTE: This is specifically for requests from the client and does +# not affect inter node communication. +# org.apache.cassandra.scheduler.NoScheduler - No scheduling takes place +# org.apache.cassandra.scheduler.RoundRobinScheduler - Round robin of +# client requests to a node with a separate queue for each +# request_scheduler_id. The scheduler is further customized by +# request_scheduler_options as described below. +request_scheduler: org.apache.cassandra.scheduler.NoScheduler + +# Scheduler Options vary based on the type of scheduler +# NoScheduler - Has no options +# RoundRobin +# - throttle_limit -- The throttle_limit is the number of in-flight +# requests per client. Requests beyond +# that limit are queued up until +# running requests can complete. +# The value of 80 here is twice the number of +# concurrent_reads + concurrent_writes. +# - default_weight -- default_weight is optional and allows for +# overriding the default which is 1. +# - weights -- Weights are optional and will default to 1 or the +# overridden default_weight. The weight translates into how +# many requests are handled during each turn of the +# RoundRobin, based on the scheduler id. +# +# request_scheduler_options: +# throttle_limit: 80 +# default_weight: 5 +# weights: +# Keyspace1: 1 +# Keyspace2: 5 + +# request_scheduler_id -- An identifier based on which to perform +# the request scheduling. Currently the only valid option is keyspace. +# request_scheduler_id: keyspace + +# index_interval controls the sampling of entries from the primrary +# row index in terms of space versus time. The larger the interval, +# the smaller and less effective the sampling will be. In technicial +# terms, the interval coresponds to the number of index entries that +# are skipped between taking each sample. All the sampled entries +# must fit in memory. Generally, a value between 128 and 512 here +# coupled with a large key cache size on CFs results in the best trade +# offs. This value is not often changed, however if you have many +# very small rows (many to an OS page), then increasing this will +# often lower memory usage without a impact on performance. +index_interval: 128 + +# Enable or disable inter-node encryption +# Default settings are TLS v1, RSA 1024-bit keys (it is imperative that +# users generate their own keys) TLS_RSA_WITH_AES_128_CBC_SHA as the cipher +# suite for authentication, key exchange and encryption of the actual data transfers. +# NOTE: No custom encryption options are enabled at the moment +# The available internode options are : all, none, dc, rack +# +# If set to dc cassandra will encrypt the traffic between the DCs +# If set to rack cassandra will encrypt the traffic between the racks +# +# The passwords used in these options must match the passwords used when generating +# the keystore and truststore. For instructions on generating these files, see: +# http://download.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#CreateKeystore +# +server_encryption_options: + internode_encryption: none + keystore: conf/.keystore + keystore_password: cassandra + truststore: conf/.truststore + truststore_password: cassandra + # More advanced defaults below: + # protocol: TLS + # algorithm: SunX509 + # store_type: JKS + # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA] + # require_client_auth: false + +# enable or disable client/server encryption. +client_encryption_options: + enabled: false + keystore: conf/.keystore + keystore_password: cassandra + # require_client_auth: false + # Set trustore and truststore_password if require_client_auth is true + # truststore: conf/.truststore + # truststore_password: cassandra + # More advanced defaults below: + # protocol: TLS + # algorithm: SunX509 + # store_type: JKS + # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA] + +# internode_compression controls whether traffic between nodes is +# compressed. +# can be: all - all traffic is compressed +# dc - traffic between different datacenters is compressed +# none - nothing is compressed. +internode_compression: all + +# Enable or disable tcp_nodelay for inter-dc communication. +# Disabling it will result in larger (but fewer) network packets being sent, +# reducing overhead from the TCP protocol itself, at the cost of increasing +# latency if you block for cross-datacenter responses. +# inter_dc_tcp_nodelay: true diff --git a/spring-cassandra/src/test/resources/cassandraOperationsTest-cql-dataload.cql b/spring-cassandra/src/test/resources/cassandraOperationsTest-cql-dataload.cql new file mode 100644 index 000000000..239ae3e25 --- /dev/null +++ b/spring-cassandra/src/test/resources/cassandraOperationsTest-cql-dataload.cql @@ -0,0 +1,3 @@ +create table book (isbn text, title text, author text, pages int, PRIMARY KEY (isbn)); +create table book_alt (isbn text, title text, author text, pages int, PRIMARY KEY (isbn)); +insert into book (isbn, title, author, pages) values ('999999999', 'Book of Nines', 'Nine Nine', 999); \ No newline at end of file diff --git a/spring-cassandra/src/test/resources/cql-dataload.cql b/spring-cassandra/src/test/resources/cql-dataload.cql new file mode 100644 index 000000000..e38d18d36 --- /dev/null +++ b/spring-cassandra/src/test/resources/cql-dataload.cql @@ -0,0 +1,3 @@ +create table book (isbn text, title text, author text, pages int, PRIMARY KEY (isbn)); +create table book_alt (isbn text, title text, author text, pages int, PRIMARY KEY (isbn)); +/*insert into book (isbn, title, author, pages) values ('999999999', 'Book of Nines', 'Nine Nine', 999);*/ \ No newline at end of file diff --git a/spring-cassandra/src/test/resources/log4j.properties b/spring-cassandra/src/test/resources/log4j.properties new file mode 100644 index 000000000..6e2ec3286 --- /dev/null +++ b/spring-cassandra/src/test/resources/log4j.properties @@ -0,0 +1,6 @@ +log4j.rootLogger=WARN, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c - %m%n +log4j.logger.org.springframework.data.cassandra=INFO + diff --git a/spring-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/CassandraNamespaceTests-context.xml b/spring-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/CassandraNamespaceTests-context.xml new file mode 100644 index 000000000..4050ec523 --- /dev/null +++ b/spring-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/CassandraNamespaceTests-context.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/cassandra.properties b/spring-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/cassandra.properties new file mode 100644 index 000000000..6a0dd3197 --- /dev/null +++ b/spring-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/cassandra.properties @@ -0,0 +1,7 @@ +cassandra.contactPoints=localhost +cassandra.port=9042 +cassandra.keyspace=TestKS123 + + + + diff --git a/spring-cassandra/template.mf b/spring-cassandra/template.mf new file mode 100644 index 000000000..9ca02b82f --- /dev/null +++ b/spring-cassandra/template.mf @@ -0,0 +1,29 @@ +Bundle-SymbolicName: org.springframework.cassandra +Bundle-Name: Spring Cassandra +Bundle-ManifestVersion: 2 +Import-Package: + sun.reflect;version="0";resolution:=optional +Import-Template: + org.springframework.beans.*;version="[3.1.0, 4.0.0)", + org.springframework.cache.*;version="[3.1.0, 4.0.0)", + org.springframework.context.*;version="[3.1.0, 4.0.0)", + org.springframework.core.*;version="[3.1.0, 4.0.0)", + org.springframework.dao.*;version="[3.1.0, 4.0.0)", + org.springframework.scheduling.*;resolution:="optional";version="[3.1.0, 4.0.0)", + org.springframework.util.*;version="[3.1.0, 4.0.0)", + org.springframework.oxm.*;resolution:="optional";version="[3.1.0, 4.0.0)", + org.springframework.transaction.support.*;version="[3.1.0, 4.0.0)", + org.springframework.data.*;version="[1.5.0, 2.0.0)", + org.springframework.expression.*;version="[3.1.0, 4.0.0)", + org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional, + org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", + org.w3c.dom.*;version="0", + javax.xml.transform.*;resolution:="optional";version="0", + com.datastax.driver.core.*;resolution:="optional";version="[0.1.0, 1.0.0)", + org.apache.cassandra.db.marshal.*;version="[1.2.0, 1.3.0)", + org.slf4j.*;version="[1.5.0, 1.8.0)", + org.idevlab.rjc.*;resolution:="optional";version="[0.6.4, 0.6.4]", + org.apache.commons.pool.impl.*;resolution:="optional";version="[1.0.0, 3.0.0)", + org.codehaus.jackson.*;resolution:="optional";version="[1.6, 2.0.0)", + org.apache.commons.beanutils.*;resolution:="optional";version=1.8.5, + com.google.common.*;resolution:="optional";version="[11.0.0, 20.0.0)" \ No newline at end of file diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/Constants.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/Constants.java new file mode 100644 index 000000000..fb0341d05 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/Constants.java @@ -0,0 +1,24 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra; + +/** + * @author David Webb + * + */ +public interface Constants { + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/AbstractCassandraConfiguration.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/AbstractCassandraConfiguration.java new file mode 100644 index 000000000..9b9cb50bd --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/AbstractCassandraConfiguration.java @@ -0,0 +1,203 @@ +/* + * Copyright 2011-2012 the original author or authors. + * + * 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.springframework.data.cassandra.config; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.cassandra.core.CassandraOperations; +import org.springframework.cassandra.core.CassandraTemplate; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.data.annotation.Persistent; +import org.springframework.data.cassandra.convert.CassandraConverter; +import org.springframework.data.cassandra.convert.MappingCassandraConverter; +import org.springframework.data.cassandra.core.CassandraAdminOperations; +import org.springframework.data.cassandra.core.CassandraAdminTemplate; +import org.springframework.data.cassandra.core.Keyspace; +import org.springframework.data.cassandra.mapping.CassandraMappingContext; +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.cassandra.mapping.Table; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.Session; + +/** + * Base class for Spring Data Cassandra configuration using JavaConfig. + * + * @author Alex Shvid + */ +@Configuration +public abstract class AbstractCassandraConfiguration implements BeanClassLoaderAware { + + /** + * Used by CassandraTemplate and CassandraAdminTemplate + */ + + private ClassLoader beanClassLoader; + + /** + * Return the name of the keyspace to connect to. + * + * @return must not be {@literal null}. + */ + protected abstract String getKeyspaceName(); + + /** + * Return the {@link Cluster} instance to connect to. + * + * @return + * @throws Exception + */ + @Bean + public abstract Cluster cluster() throws Exception; + + /** + * Creates a {@link Session} to be used by the {@link Keyspace}. Will use the {@link Cluster} instance configured in + * {@link #cluster()}. + * + * @see #cluster() + * @see #Keyspace() + * @return + * @throws Exception + */ + @Bean + public Session session() throws Exception { + String keyspace = getKeyspaceName(); + if (StringUtils.hasText(keyspace)) { + return cluster().connect(keyspace); + } else { + return cluster().connect(); + } + } + + /** + * Creates a {@link Keyspace} to be used by the {@link CassandraTemplate}. Will use the {@link Session} instance + * configured in {@link #session()} and {@link CassandraConverter} configured in {@link #converter()}. + * + * @see #cluster() + * @see #Keyspace() + * @return + * @throws Exception + */ + @Bean + public Keyspace keyspace() throws Exception { + return new Keyspace(getKeyspaceName(), session(), converter()); + } + + /** + * Return the base package to scan for mapped {@link Table}s. Will return the package name of the configuration class' + * (the concrete class, not this one here) by default. So if you have a {@code com.acme.AppConfig} extending + * {@link AbstractCassandraConfiguration} the base package will be considered {@code com.acme} unless the method is + * overriden to implement alternate behaviour. + * + * @return the base package to scan for mapped {@link Table} classes or {@literal null} to not enable scanning for + * entities. + */ + protected String getMappingBasePackage() { + return getClass().getPackage().getName(); + } + + /** + * Creates a {@link CassandraTemplate}. + * + * @return + * @throws Exception + */ + @Bean + public CassandraOperations cassandraTemplate() throws Exception { + return new CassandraTemplate(session()); + } + + /** + * Creates a {@link CassandraAdminTemplate}. + * + * @return + * @throws Exception + */ + @Bean + public CassandraAdminOperations cassandraAdminTemplate() throws Exception { + return new CassandraAdminTemplate(keyspace()); + } + + /** + * Return the {@link MappingContext} instance to map Entities to properties. + * + * @return + * @throws Exception + */ + @Bean + public MappingContext, CassandraPersistentProperty> mappingContext() { + return new CassandraMappingContext(); + } + + /** + * Return the {@link CassandraConverter} instance to convert Rows to Objects, Objects to BuiltStatements + * + * @return + * @throws Exception + */ + @Bean + public CassandraConverter converter() { + MappingCassandraConverter converter = new MappingCassandraConverter(mappingContext()); + converter.setBeanClassLoader(beanClassLoader); + return converter; + } + + /** + * Scans the mapping base package for classes annotated with {@link Table}. + * + * @see #getMappingBasePackage() + * @return + * @throws ClassNotFoundException + */ + protected Set> getInitialEntitySet() throws ClassNotFoundException { + + String basePackage = getMappingBasePackage(); + Set> initialEntitySet = new HashSet>(); + + if (StringUtils.hasText(basePackage)) { + ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider( + false); + componentProvider.addIncludeFilter(new AnnotationTypeFilter(Table.class)); + componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class)); + + for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) { + initialEntitySet.add(ClassUtils.forName(candidate.getBeanClassName(), + AbstractCassandraConfiguration.class.getClassLoader())); + } + } + + return initialEntitySet; + } + + /** + * Bean ClassLoader Aware for CassandraTemplate/CassandraAdminTemplate + */ + + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/BeanNames.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/BeanNames.java new file mode 100644 index 000000000..762b206fe --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/BeanNames.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * 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.springframework.data.cassandra.config; + +/** + * @author Alex Shvid + * @author David Webb + */ +public final class BeanNames { + + private BeanNames() { + } + + public static final String CASSANDRA_CLUSTER = "cassandra-cluster"; + public static final String CASSANDRA_KEYSPACE = "cassandra-keyspace"; + public static final String CASSANDRA_SESSION = "cassandra-session"; + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraClusterParser.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraClusterParser.java new file mode 100644 index 000000000..1718a283f --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraClusterParser.java @@ -0,0 +1,118 @@ +/* + * Copyright 2011-2012 the original author or authors. + * + * 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.springframework.data.cassandra.config; + +import java.util.List; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.data.cassandra.core.CassandraClusterFactoryBean; +import org.springframework.data.config.ParsingUtils; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; + +/** + * Parser for <cluster;gt; definitions. + * + * @author Alex Shvid + */ + +public class CassandraClusterParser extends AbstractSimpleBeanDefinitionParser { + + @Override + protected Class getBeanClass(Element element) { + return CassandraClusterFactoryBean.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#resolveId(org.w3c.dom.Element, org.springframework.beans.factory.support.AbstractBeanDefinition, org.springframework.beans.factory.xml.ParserContext) + */ + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) + throws BeanDefinitionStoreException { + + String id = super.resolveId(element, definition, parserContext); + return StringUtils.hasText(id) ? id : BeanNames.CASSANDRA_CLUSTER; + } + + @Override + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + + String contactPoints = element.getAttribute("contactPoints"); + if (StringUtils.hasText(contactPoints)) { + builder.addPropertyValue("contactPoints", contactPoints); + } + + String port = element.getAttribute("port"); + if (StringUtils.hasText(port)) { + builder.addPropertyValue("port", port); + } + + String compression = element.getAttribute("compression"); + if (StringUtils.hasText(compression)) { + builder.addPropertyValue("compressionType", CompressionType.valueOf(compression)); + } + + postProcess(builder, element); + } + + @Override + protected void postProcess(BeanDefinitionBuilder builder, Element element) { + List subElements = DomUtils.getChildElements(element); + + // parse nested elements + for (Element subElement : subElements) { + String name = subElement.getLocalName(); + + if ("local-pooling-options".equals(name)) { + builder.addPropertyValue("localPoolingOptions", parsePoolingOptions(subElement)); + } else if ("remote-pooling-options".equals(name)) { + builder.addPropertyValue("remotePoolingOptions", parsePoolingOptions(subElement)); + } else if ("socket-options".equals(name)) { + builder.addPropertyValue("socketOptions", parseSocketOptions(subElement)); + } + } + + } + + private BeanDefinition parsePoolingOptions(Element element) { + BeanDefinitionBuilder defBuilder = BeanDefinitionBuilder.genericBeanDefinition(PoolingOptionsConfig.class); + ParsingUtils.setPropertyValue(defBuilder, element, "min-simultaneous-requests", "minSimultaneousRequests"); + ParsingUtils.setPropertyValue(defBuilder, element, "max-simultaneous-requests", "maxSimultaneousRequests"); + ParsingUtils.setPropertyValue(defBuilder, element, "core-connections", "coreConnections"); + ParsingUtils.setPropertyValue(defBuilder, element, "max-connections", "maxConnections"); + return defBuilder.getBeanDefinition(); + } + + private BeanDefinition parseSocketOptions(Element element) { + BeanDefinitionBuilder defBuilder = BeanDefinitionBuilder.genericBeanDefinition(SocketOptionsConfig.class); + ParsingUtils.setPropertyValue(defBuilder, element, "connect-timeout-mls", "connectTimeoutMls"); + ParsingUtils.setPropertyValue(defBuilder, element, "keep-alive", "keepAlive"); + ParsingUtils.setPropertyValue(defBuilder, element, "reuse-address", "reuseAddress"); + ParsingUtils.setPropertyValue(defBuilder, element, "so-linger", "soLinger"); + ParsingUtils.setPropertyValue(defBuilder, element, "tcp-no-delay", "tcpNoDelay"); + ParsingUtils.setPropertyValue(defBuilder, element, "receive-buffer-size", "receiveBufferSize"); + ParsingUtils.setPropertyValue(defBuilder, element, "send-buffer-size", "sendBufferSize"); + return defBuilder.getBeanDefinition(); + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraKeyspaceParser.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraKeyspaceParser.java new file mode 100644 index 000000000..a9a724452 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraKeyspaceParser.java @@ -0,0 +1,127 @@ +/* + * Copyright 2011-2012 the original author or authors. + * + * 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.springframework.data.cassandra.config; + +import java.util.List; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.ManagedList; +import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.data.cassandra.core.CassandraKeyspaceFactoryBean; +import org.springframework.data.config.ParsingUtils; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; + +/** + * Parser for <keyspace;gt; definitions. + * + * @author Alex Shvid + */ + +public class CassandraKeyspaceParser extends AbstractSimpleBeanDefinitionParser { + + @Override + protected Class getBeanClass(Element element) { + return CassandraKeyspaceFactoryBean.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#resolveId(org.w3c.dom.Element, org.springframework.beans.factory.support.AbstractBeanDefinition, org.springframework.beans.factory.xml.ParserContext) + */ + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) + throws BeanDefinitionStoreException { + + String id = super.resolveId(element, definition, parserContext); + return StringUtils.hasText(id) ? id : BeanNames.CASSANDRA_KEYSPACE; + } + + @Override + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + + String name = element.getAttribute("name"); + if (StringUtils.hasText(name)) { + builder.addPropertyValue("keyspace", name); + } + + String clusterRef = element.getAttribute("cassandra-cluster-ref"); + if (!StringUtils.hasText(clusterRef)) { + clusterRef = BeanNames.CASSANDRA_CLUSTER; + } + builder.addPropertyReference("cluster", clusterRef); + + String converterRef = element.getAttribute("cassandra-converter-ref"); + if (StringUtils.hasText(converterRef)) { + builder.addPropertyReference("converter", converterRef); + } + + postProcess(builder, element); + } + + @Override + protected void postProcess(BeanDefinitionBuilder builder, Element element) { + List subElements = DomUtils.getChildElements(element); + + // parse nested elements + for (Element subElement : subElements) { + String name = subElement.getLocalName(); + + if ("keyspace-attributes".equals(name)) { + builder.addPropertyValue("keyspaceAttributes", parseKeyspaceAttributes(subElement)); + } + } + + } + + private BeanDefinition parseKeyspaceAttributes(Element element) { + BeanDefinitionBuilder defBuilder = BeanDefinitionBuilder.genericBeanDefinition(KeyspaceAttributes.class); + ParsingUtils.setPropertyValue(defBuilder, element, "auto", "auto"); + ParsingUtils.setPropertyValue(defBuilder, element, "replication-strategy", "replicationStrategy"); + ParsingUtils.setPropertyValue(defBuilder, element, "replication-factor", "replicationFactor"); + ParsingUtils.setPropertyValue(defBuilder, element, "durable-writes", "durableWrites"); + + List subElements = DomUtils.getChildElements(element); + ManagedList tables = new ManagedList(subElements.size()); + + // parse nested elements + for (Element subElement : subElements) { + String name = subElement.getLocalName(); + + if ("table".equals(name)) { + tables.add(parseTable(subElement)); + } + } + if (!tables.isEmpty()) { + defBuilder.addPropertyValue("tables", tables); + } + + return defBuilder.getBeanDefinition(); + } + + private BeanDefinition parseTable(Element element) { + BeanDefinitionBuilder defBuilder = BeanDefinitionBuilder.genericBeanDefinition(TableAttributes.class); + ParsingUtils.setPropertyValue(defBuilder, element, "entity", "entity"); + ParsingUtils.setPropertyValue(defBuilder, element, "name", "name"); + return defBuilder.getBeanDefinition(); + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraNamespaceHandler.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraNamespaceHandler.java new file mode 100644 index 000000000..c69c39cf6 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraNamespaceHandler.java @@ -0,0 +1,36 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.config; + +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + +/** + * Namespace handler for <cassandra;gt;. + * + * @author Alex Shvid + */ + +public class CassandraNamespaceHandler extends NamespaceHandlerSupport { + + public void init() { + + registerBeanDefinitionParser("cluster", new CassandraClusterParser()); + registerBeanDefinitionParser("keyspace", new CassandraKeyspaceParser()); + registerBeanDefinitionParser("session", new CassandraSessionParser()); + + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraSessionParser.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraSessionParser.java new file mode 100644 index 000000000..f99cfb5bf --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraSessionParser.java @@ -0,0 +1,69 @@ +/* + * Copyright 2011-2012 the original author or authors. + * + * 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.springframework.data.cassandra.config; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.cassandra.core.SessionFactoryBean; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * Parser for <session;gt; definitions. + * + * @author David Webb + */ + +public class CassandraSessionParser extends AbstractSimpleBeanDefinitionParser { + + @Override + protected Class getBeanClass(Element element) { + return SessionFactoryBean.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#resolveId(org.w3c.dom.Element, org.springframework.beans.factory.support.AbstractBeanDefinition, org.springframework.beans.factory.xml.ParserContext) + */ + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) + throws BeanDefinitionStoreException { + + String id = super.resolveId(element, definition, parserContext); + return StringUtils.hasText(id) ? id : BeanNames.CASSANDRA_SESSION; + } + + @Override + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + + String keyspaceRef = element.getAttribute("cassandra-keyspace-ref"); + if (!StringUtils.hasText(keyspaceRef)) { + keyspaceRef = BeanNames.CASSANDRA_KEYSPACE; + } + builder.addPropertyReference("keyspace", keyspaceRef); + + postProcess(builder, element); + } + + @Override + protected void postProcess(BeanDefinitionBuilder builder, Element element) { + + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CompressionType.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CompressionType.java new file mode 100644 index 000000000..c74c36676 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CompressionType.java @@ -0,0 +1,25 @@ +/* + * Copyright 2010-2012 the original author or authors. + * + * 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.springframework.data.cassandra.config; + +/** + * Simple enumeration for the various compression types. + * + * @author Alex Shvid + */ +public enum CompressionType { + NONE, SNAPPY; +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/KeyspaceAttributes.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/KeyspaceAttributes.java new file mode 100644 index 000000000..748b94726 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/KeyspaceAttributes.java @@ -0,0 +1,107 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.config; + +import java.util.Collection; + +/** + * Keyspace attributes are used for manipulation around keyspace at the startup. Auto property defines the way how to do + * this. Other attributes used to ensure or update keyspace settings. + * + * @author Alex Shvid + */ +public class KeyspaceAttributes { + + public static final String DEFAULT_REPLICATION_STRATEGY = "SimpleStrategy"; + public static final int DEFAULT_REPLICATION_FACTOR = 1; + public static final boolean DEFAULT_DURABLE_WRITES = true; + + /* + * auto possible values: + * validate: validate the keyspace, makes no changes. + * update: update the keyspace. + * create: creates the keyspace, destroying previous data. + * create-drop: drop the keyspace at the end of the session. + */ + public static final String AUTO_VALIDATE = "validate"; + public static final String AUTO_UPDATE = "update"; + public static final String AUTO_CREATE = "create"; + public static final String AUTO_CREATE_DROP = "create-drop"; + + private String auto = AUTO_VALIDATE; + private String replicationStrategy = DEFAULT_REPLICATION_STRATEGY; + private int replicationFactor = DEFAULT_REPLICATION_FACTOR; + private boolean durableWrites = DEFAULT_DURABLE_WRITES; + + private Collection tables; + + public String getAuto() { + return auto; + } + + public void setAuto(String auto) { + this.auto = auto; + } + + public boolean isValidate() { + return AUTO_VALIDATE.equals(auto); + } + + public boolean isUpdate() { + return AUTO_UPDATE.equals(auto); + } + + public boolean isCreate() { + return AUTO_CREATE.equals(auto); + } + + public boolean isCreateDrop() { + return AUTO_CREATE_DROP.equals(auto); + } + + public String getReplicationStrategy() { + return replicationStrategy; + } + + public void setReplicationStrategy(String replicationStrategy) { + this.replicationStrategy = replicationStrategy; + } + + public int getReplicationFactor() { + return replicationFactor; + } + + public void setReplicationFactor(int replicationFactor) { + this.replicationFactor = replicationFactor; + } + + public boolean isDurableWrites() { + return durableWrites; + } + + public void setDurableWrites(boolean durableWrites) { + this.durableWrites = durableWrites; + } + + public Collection getTables() { + return tables; + } + + public void setTables(Collection tables) { + this.tables = tables; + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/PoolingOptionsConfig.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/PoolingOptionsConfig.java new file mode 100644 index 000000000..4ba96539e --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/PoolingOptionsConfig.java @@ -0,0 +1,62 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.config; + +/** + * Pooling options POJO. Can be remote or local. + * + * @author Alex Shvid + */ +public class PoolingOptionsConfig { + + private Integer minSimultaneousRequests; + private Integer maxSimultaneousRequests; + private Integer coreConnections; + private Integer maxConnections; + + public Integer getMinSimultaneousRequests() { + return minSimultaneousRequests; + } + + public void setMinSimultaneousRequests(Integer minSimultaneousRequests) { + this.minSimultaneousRequests = minSimultaneousRequests; + } + + public Integer getMaxSimultaneousRequests() { + return maxSimultaneousRequests; + } + + public void setMaxSimultaneousRequests(Integer maxSimultaneousRequests) { + this.maxSimultaneousRequests = maxSimultaneousRequests; + } + + public Integer getCoreConnections() { + return coreConnections; + } + + public void setCoreConnections(Integer coreConnections) { + this.coreConnections = coreConnections; + } + + public Integer getMaxConnections() { + return maxConnections; + } + + public void setMaxConnections(Integer maxConnections) { + this.maxConnections = maxConnections; + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/SocketOptionsConfig.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/SocketOptionsConfig.java new file mode 100644 index 000000000..1e72c7742 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/SocketOptionsConfig.java @@ -0,0 +1,89 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.config; + +/** + * Socket options POJO. Uses to configure Netty. + * + * @author Alex Shvid + */ +public class SocketOptionsConfig { + + private Integer connectTimeoutMls; + private Boolean keepAlive; + private Boolean reuseAddress; + private Integer soLinger; + private Boolean tcpNoDelay; + private Integer receiveBufferSize; + private Integer sendBufferSize; + + public Integer getConnectTimeoutMls() { + return connectTimeoutMls; + } + + public void setConnectTimeoutMls(Integer connectTimeoutMls) { + this.connectTimeoutMls = connectTimeoutMls; + } + + public Boolean getKeepAlive() { + return keepAlive; + } + + public void setKeepAlive(Boolean keepAlive) { + this.keepAlive = keepAlive; + } + + public Boolean getReuseAddress() { + return reuseAddress; + } + + public void setReuseAddress(Boolean reuseAddress) { + this.reuseAddress = reuseAddress; + } + + public Integer getSoLinger() { + return soLinger; + } + + public void setSoLinger(Integer soLinger) { + this.soLinger = soLinger; + } + + public Boolean getTcpNoDelay() { + return tcpNoDelay; + } + + public void setTcpNoDelay(Boolean tcpNoDelay) { + this.tcpNoDelay = tcpNoDelay; + } + + public Integer getReceiveBufferSize() { + return receiveBufferSize; + } + + public void setReceiveBufferSize(Integer receiveBufferSize) { + this.receiveBufferSize = receiveBufferSize; + } + + public Integer getSendBufferSize() { + return sendBufferSize; + } + + public void setSendBufferSize(Integer sendBufferSize) { + this.sendBufferSize = sendBufferSize; + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/TableAttributes.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/TableAttributes.java new file mode 100644 index 000000000..c10e2eefb --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/TableAttributes.java @@ -0,0 +1,49 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.config; + +/** + * Table attributes are used for manipulation around table at the startup (create/update/validate). + * + * @author Alex Shvid + */ +public class TableAttributes { + + private String entity; + private String name; + + public String getEntity() { + return entity; + } + + public void setEntity(String entity) { + this.entity = entity; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "TableAttributes [entity=" + entity + "]"; + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/AbstractCassandraConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/AbstractCassandraConverter.java new file mode 100644 index 000000000..9f4195758 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/AbstractCassandraConverter.java @@ -0,0 +1,67 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.convert; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.data.convert.EntityInstantiators; + +/** + * Base class for {@link CassandraConverter} implementations. Sets up a {@link GenericConversionService} and populates + * basic converters. + * + * @author Alex Shvid + */ +public abstract class AbstractCassandraConverter implements CassandraConverter, InitializingBean { + + protected final GenericConversionService conversionService; + protected EntityInstantiators instantiators = new EntityInstantiators(); + + /** + * Creates a new {@link AbstractCassandraConverter} using the given {@link GenericConversionService}. + * + * @param conversionService + */ + public AbstractCassandraConverter(GenericConversionService conversionService) { + this.conversionService = conversionService == null ? new DefaultConversionService() : conversionService; + } + + /** + * Registers {@link EntityInstantiators} to customize entity instantiation. + * + * @param instantiators + */ + public void setInstantiators(EntityInstantiators instantiators) { + this.instantiators = instantiators == null ? new EntityInstantiators() : instantiators; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.core.convert.MongoConverter#getConversionService() + */ + public ConversionService getConversionService() { + return conversionService; + } + + /* (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() { + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraConverter.java new file mode 100644 index 000000000..a26ad094d --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraConverter.java @@ -0,0 +1,30 @@ +/* + * Copyright 2010-2011 the original author or authors. + * + * 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.springframework.data.cassandra.convert; + +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.convert.EntityConverter; + +/** + * Central Cassandra specific converter interface from Object to Row. + * + * @author Alex Shvid + */ +public interface CassandraConverter extends + EntityConverter, CassandraPersistentProperty, Object, Object> { + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraPropertyValueProvider.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraPropertyValueProvider.java new file mode 100644 index 000000000..109086254 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraPropertyValueProvider.java @@ -0,0 +1,95 @@ +/* + * Copyright 2012 the original author or authors. + * + * 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.springframework.data.cassandra.convert; + +import java.nio.ByteBuffer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.cassandra.util.CqlUtils; +import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator; +import org.springframework.data.mapping.model.PropertyValueProvider; +import org.springframework.data.mapping.model.SpELExpressionEvaluator; +import org.springframework.util.Assert; + +import com.datastax.driver.core.DataType; +import com.datastax.driver.core.Row; + +/** + * {@link PropertyValueProvider} to read property values from a {@link Row}. + * + * @author Alex Shvid + */ +public class CassandraPropertyValueProvider implements PropertyValueProvider { + + private static Logger log = LoggerFactory.getLogger(CassandraPropertyValueProvider.class); + + private final Row source; + private final SpELExpressionEvaluator evaluator; + + /** + * Creates a new {@link CassandraPropertyValueProvider} with the given {@link Row} and + * {@link DefaultSpELExpressionEvaluator}. + * + * @param source must not be {@literal null}. + * @param evaluator must not be {@literal null}. + */ + public CassandraPropertyValueProvider(Row source, DefaultSpELExpressionEvaluator evaluator) { + Assert.notNull(source); + Assert.notNull(evaluator); + + this.source = source; + this.evaluator = evaluator; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.convert.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty) + */ + @SuppressWarnings("unchecked") + public T getPropertyValue(CassandraPersistentProperty property) { + + String expression = property.getSpelExpression(); + if (expression != null) { + return evaluator.evaluate(expression); + } + + String columnName = property.getColumnName(); + if (source.isNull(property.getColumnName())) { + return null; + } + DataType columnType = source.getColumnDefinitions().getType(columnName); + + log.info(columnType.getName().name()); + + /* + * Dave Webb - Added handler for text since getBytes was throwing + * InvalidTypeException when using getBytes on a text column. + */ + // TODO Might need to qualify all DataTypes as we encounter them. + if (columnType.equals(DataType.text())) { + return (T) source.getString(columnName); + } + if (columnType.equals(DataType.cint())) { + return (T) new Integer(source.getInt(columnName)); + } + + ByteBuffer bytes = source.getBytes(columnName); + return (T) columnType.deserialize(bytes); + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java new file mode 100644 index 000000000..e40e6bd5c --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java @@ -0,0 +1,278 @@ +/* + * Copyright 2011-2013 by the original author(s). + * + * 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.springframework.data.cassandra.convert; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.convert.EntityInstantiator; +import org.springframework.data.mapping.PropertyHandler; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.model.BeanWrapper; +import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator; +import org.springframework.data.mapping.model.MappingException; +import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider; +import org.springframework.data.mapping.model.PropertyValueProvider; +import org.springframework.data.mapping.model.SpELContext; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; +import org.springframework.util.ClassUtils; + +import com.datastax.driver.core.Row; +import com.datastax.driver.core.querybuilder.Delete.Where; +import com.datastax.driver.core.querybuilder.Insert; +import com.datastax.driver.core.querybuilder.QueryBuilder; +import com.datastax.driver.core.querybuilder.Update; + +/** + * {@link CassandraConverter} that uses a {@link MappingContext} to do sophisticated mapping of domain objects to + * {@link Row}. + * + * @author Alex Shvid + */ +public class MappingCassandraConverter extends AbstractCassandraConverter implements ApplicationContextAware, + BeanClassLoaderAware { + + protected static final Logger log = LoggerFactory.getLogger(MappingCassandraConverter.class); + + protected final MappingContext, CassandraPersistentProperty> mappingContext; + protected ApplicationContext applicationContext; + private SpELContext spELContext; + private boolean useFieldAccessOnly = true; + + private ClassLoader beanClassLoader; + + /** + * Creates a new {@link MappingCassandraConverter} given the new {@link MappingContext}. + * + * @param mappingContext must not be {@literal null}. + */ + public MappingCassandraConverter( + MappingContext, CassandraPersistentProperty> mappingContext) { + super(new DefaultConversionService()); + this.mappingContext = mappingContext; + this.spELContext = new SpELContext(RowReaderPropertyAccessor.INSTANCE); + } + + @SuppressWarnings("unchecked") + public R readRow(Class clazz, Row row) { + + Class beanClassLoaderClass = transformClassToBeanClassLoaderClass(clazz); + + TypeInformation type = ClassTypeInformation.from(beanClassLoaderClass); + // TypeInformation typeToUse = typeMapper.readType(row, type); + TypeInformation typeToUse = type; + Class rawType = typeToUse.getType(); + + if (Row.class.isAssignableFrom(rawType)) { + return (R) row; + } + + CassandraPersistentEntity persistentEntity = (CassandraPersistentEntity) mappingContext + .getPersistentEntity(typeToUse); + if (persistentEntity == null) { + throw new MappingException("No mapping metadata found for " + rawType.getName()); + } + + return readRowInternal(persistentEntity, row); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.convert.EntityConverter#getMappingContext() + */ + public MappingContext, CassandraPersistentProperty> getMappingContext() { + return mappingContext; + } + + /* + * (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + this.spELContext = new SpELContext(this.spELContext, applicationContext); + } + + private S readRowInternal(final CassandraPersistentEntity entity, final Row row) { + + final DefaultSpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(row, spELContext); + + final PropertyValueProvider propertyProvider = new CassandraPropertyValueProvider(row, + evaluator); + PersistentEntityParameterValueProvider parameterProvider = new PersistentEntityParameterValueProvider( + entity, propertyProvider, null); + + EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity); + S instance = instantiator.createInstance(entity, parameterProvider); + + final BeanWrapper, S> wrapper = BeanWrapper.create(instance, conversionService); + final S result = wrapper.getBean(); + + // Set properties not already set in the constructor + entity.doWithProperties(new PropertyHandler() { + public void doWithPersistentProperty(CassandraPersistentProperty prop) { + + boolean isConstructorProperty = entity.isConstructorArgument(prop); + boolean hasValueForProperty = row.getColumnDefinitions().contains(prop.getColumnName()); + + if (!hasValueForProperty || isConstructorProperty) { + return; + } + + Object obj = propertyProvider.getPropertyValue(prop); + wrapper.setProperty(prop, obj, useFieldAccessOnly); + } + }); + + return result; + } + + public void setUseFieldAccessOnly(boolean useFieldAccessOnly) { + this.useFieldAccessOnly = useFieldAccessOnly; + } + + /* (non-Javadoc) + * @see org.springframework.data.convert.EntityWriter#write(java.lang.Object, java.lang.Object) + */ + @Override + public R read(Class type, Object row) { + if (row instanceof Row) { + return readRow(type, (Row) row); + } + throw new MappingException("Unknown row object " + row.getClass().getName()); + } + + /* (non-Javadoc) + * @see org.springframework.data.convert.EntityWriter#write(java.lang.Object, java.lang.Object) + */ + @Override + public void write(Object obj, Object builtStatement) { + + if (obj == null) { + return; + } + + Class beanClassLoaderClass = transformClassToBeanClassLoaderClass(obj.getClass()); + CassandraPersistentEntity entity = mappingContext.getPersistentEntity(beanClassLoaderClass); + + if (entity == null) { + throw new MappingException("No mapping metadata found for " + obj.getClass()); + } + + if (builtStatement instanceof Insert) { + writeInsertInternal(obj, (Insert) builtStatement, entity); + } else if (builtStatement instanceof Update) { + writeUpdateInternal(obj, (Update) builtStatement, entity); + } else if (builtStatement instanceof Where) { + writeDeleteWhereInternal(obj, (Where) builtStatement, entity); + } else { + throw new MappingException("Unknown buildStatement " + builtStatement.getClass().getName()); + } + } + + private void writeInsertInternal(final Object objectToSave, final Insert insert, CassandraPersistentEntity entity) { + + final BeanWrapper, Object> wrapper = BeanWrapper.create(objectToSave, + conversionService); + + // Write the properties + entity.doWithProperties(new PropertyHandler() { + public void doWithPersistentProperty(CassandraPersistentProperty prop) { + + Object propertyObj = wrapper.getProperty(prop, prop.getType(), useFieldAccessOnly); + + if (propertyObj != null) { + insert.value(prop.getColumnName(), propertyObj); + } + + } + }); + + } + + private void writeUpdateInternal(final Object objectToSave, final Update update, CassandraPersistentEntity entity) { + + final BeanWrapper, Object> wrapper = BeanWrapper.create(objectToSave, + conversionService); + + // Write the properties + entity.doWithProperties(new PropertyHandler() { + public void doWithPersistentProperty(CassandraPersistentProperty prop) { + + Object propertyObj = wrapper.getProperty(prop, prop.getType(), useFieldAccessOnly); + + if (propertyObj != null) { + if (prop.isIdProperty()) { + update.where(QueryBuilder.eq(prop.getColumnName(), propertyObj)); + } else { + update.with(QueryBuilder.set(prop.getColumnName(), propertyObj)); + } + } + + } + }); + + } + + private void writeDeleteWhereInternal(final Object objectToSave, final Where whereId, + CassandraPersistentEntity entity) { + + final BeanWrapper, Object> wrapper = BeanWrapper.create(objectToSave, + conversionService); + + // Write the properties + entity.doWithProperties(new PropertyHandler() { + public void doWithPersistentProperty(CassandraPersistentProperty prop) { + + if (prop.isIdProperty()) { + + Object propertyObj = wrapper.getProperty(prop, prop.getType(), useFieldAccessOnly); + + if (propertyObj != null) { + whereId.and(QueryBuilder.eq(prop.getColumnName(), propertyObj)); + } + } + + } + }); + + } + + @SuppressWarnings("unchecked") + private Class transformClassToBeanClassLoaderClass(Class entity) { + try { + return (Class) ClassUtils.forName(entity.getName(), beanClassLoader); + } catch (ClassNotFoundException e) { + return entity; + } catch (LinkageError e) { + return entity; + } + } + + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/RowReaderPropertyAccessor.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/RowReaderPropertyAccessor.java new file mode 100644 index 000000000..020b6afd2 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/RowReaderPropertyAccessor.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012 the original author or authors. + * + * 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.springframework.data.cassandra.convert; + +import java.nio.ByteBuffer; + +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.TypedValue; + +import com.datastax.driver.core.DataType; +import com.datastax.driver.core.Row; + +/** + * {@link PropertyAccessor} to read values from a {@link Row}. + * + * @author Alex Shvid + */ +enum RowReaderPropertyAccessor implements PropertyAccessor { + + INSTANCE; + + /* + * (non-Javadoc) + * @see org.springframework.expression.PropertyAccessor#getSpecificTargetClasses() + */ + public Class[] getSpecificTargetClasses() { + return new Class[] { Row.class }; + } + + /* + * (non-Javadoc) + * @see org.springframework.expression.PropertyAccessor#canRead(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String) + */ + public boolean canRead(EvaluationContext context, Object target, String name) { + return ((Row) target).getColumnDefinitions().contains(name); + } + + /* + * (non-Javadoc) + * @see org.springframework.expression.PropertyAccessor#read(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String) + */ + public TypedValue read(EvaluationContext context, Object target, String name) { + Row row = (Row) target; + if (row.isNull(name)) { + return TypedValue.NULL; + } + DataType columnType = row.getColumnDefinitions().getType(name); + ByteBuffer bytes = row.getBytes(name); + Object object = columnType.deserialize(bytes); + return new TypedValue(object); + } + + /* + * (non-Javadoc) + * @see org.springframework.expression.PropertyAccessor#canWrite(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String) + */ + public boolean canWrite(EvaluationContext context, Object target, String name) { + return false; + } + + /* + * (non-Javadoc) + * @see org.springframework.expression.PropertyAccessor#write(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String, java.lang.Object) + */ + public void write(EvaluationContext context, Object target, String name, Object newValue) { + throw new UnsupportedOperationException(); + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminOperations.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminOperations.java new file mode 100644 index 000000000..d8cb5b64a --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminOperations.java @@ -0,0 +1,79 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.core; + +import java.util.Map; + +import com.datastax.driver.core.TableMetadata; + +/** + * Operations for managing a Cassandra keyspace. + * + * @author David Webb + * @author Matthew T. Adams + */ +public interface CassandraAdminOperations { + + /** + * Get the given table's metadata. + * + * @param tableName The name of the table. + */ + TableMetadata getTableMetadata(String tableName); + + /** + * Create a table with the name given and fields corresponding to the given class. If the table already exists and + * parameter ifNotExists is {@literal true}, this is a no-op and {@literal false} is returned. If the + * table doesn't exist, parameter ifNotExists is ignored, the table is created and {@literal true} is + * returned. + * + * @param ifNotExists If true, will only create the table if it doesn't exist, else the create operation will be + * ignored and the method will return {@literal false}. + * @param tableName The name of the table. + * @param entityClass The class whose fields determine the columns created. + * @param optionsByName Table options, given by the string option name and the appropriate option value. + * @return Returns true if a table was created, false if not. + */ + boolean createTable(boolean ifNotExists, String tableName, Class entityClass, Map optionsByName); + + /** + * Add columns to the given table from the given class. If parameter dropRemovedAttributColumns is true, then this + * effectively becomes a synchronization operation between the class's fields and the existing table's columns. + * + * @param tableName The name of the existing table. + * @param entityClass The class whose fields determine the columns added. + * @param dropRemovedAttributeColumns Whether to drop columns that exist on the table but that don't have + * corresponding fields in the class. If true, this effectively becomes a synchronziation operation. + */ + void alterTable(String tableName, Class entityClass, boolean dropRemovedAttributeColumns); + + /** + * Drops the existing table with the given name and creates a new one; basically a {@link #dropTable(String)} followed + * by a {@link #createTable(boolean, String, Class, Map)}. + * + * @param tableName The name of the table. + * @param entityClass The class whose fields determine the new table's columns. + * @param optionsByName Table options, given by the string option name and the appropriate option value. + */ + void replaceTable(String tableName, Class entityClass, Map optionsByName); + + /** + * Drops the named table. + * + * @param tableName The name of the table. + */ + void dropTable(String tableName); +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminTemplate.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminTemplate.java new file mode 100644 index 000000000..83e51c2a8 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminTemplate.java @@ -0,0 +1,243 @@ +package org.springframework.data.cassandra.core; + +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cassandra.core.SessionCallback; +import org.springframework.cassandra.support.CassandraExceptionTranslator; +import org.springframework.cassandra.support.exception.CassandraTableExistsException; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.data.cassandra.convert.CassandraConverter; +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.cassandra.util.CqlUtils; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.util.Assert; + +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.TableMetadata; + +/** + * Default implementation of {@link CassandraAdminOperations}. + */ +public class CassandraAdminTemplate implements CassandraAdminOperations { + + private static Logger log = LoggerFactory.getLogger(CassandraAdminTemplate.class); + + private Keyspace keyspace; + private Session session; + private CassandraConverter converter; + private MappingContext, CassandraPersistentProperty> mappingContext; + + private final PersistenceExceptionTranslator exceptionTranslator = new CassandraExceptionTranslator(); + + /** + * Constructor used for a basic template configuration + * + * @param keyspace must not be {@literal null}. + */ + public CassandraAdminTemplate(Keyspace keyspace) { + setKeyspace(keyspace); + } + + protected CassandraAdminTemplate setKeyspace(Keyspace keyspace) { + Assert.notNull(keyspace); + this.keyspace = keyspace; + return setSession(keyspace.getSession()).setCassandraConverter(keyspace.getCassandraConverter()); + } + + protected CassandraAdminTemplate setSession(Session session) { + Assert.notNull(session); + return this; + } + + protected CassandraAdminTemplate setCassandraConverter(CassandraConverter converter) { + Assert.notNull(converter); + this.converter = converter; + return setMappingContext(converter.getMappingContext()); + } + + protected CassandraAdminTemplate setMappingContext( + MappingContext, CassandraPersistentProperty> mappingContext) { + Assert.notNull(mappingContext); + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraAdminOperations#createTable(boolean, java.lang.String, java.lang.Class, java.util.Map) + */ + @Override + public boolean createTable(boolean ifNotExists, final String tableName, Class entityClass, + Map optionsByName) { + + try { + + final CassandraPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + + execute(new SessionCallback() { + public Object doInSession(Session s) throws DataAccessException { + + String cql = CqlUtils.createTable(tableName, entity); + log.info("CREATE TABLE CQL -> " + cql); + s.execute(cql); + return null; + } + }); + return true; + + } catch (CassandraTableExistsException ctex) { + return !ifNotExists; + } catch (RuntimeException x) { + throw tryToConvert(x); + } + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraAdminOperations#alterTable(java.lang.String, java.lang.Class, boolean) + */ + @Override + public void alterTable(String tableName, Class entityClass, boolean dropRemovedAttributeColumns) { + // TODO Auto-generated method stub + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraAdminOperations#replaceTable(java.lang.String, java.lang.Class) + */ + @Override + public void replaceTable(String tableName, Class entityClass, Map optionsByName) { + // TODO + } + + /** + * Create a list of query operations to alter the table for the given entity + * + * @param entityClass + * @param tableName + */ + protected void doAlterTable(Class entityClass, String tableName) { + + CassandraPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + + Assert.notNull(entity); + + final TableMetadata tableMetadata = getTableMetadata(tableName); + + final List queryList = CqlUtils.alterTable(tableName, entity, tableMetadata); + + execute(new SessionCallback() { + + public Object doInSession(Session s) throws DataAccessException { + + for (String q : queryList) { + log.info(q); + s.execute(q); + } + + return null; + + } + }); + + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#dropTable(java.lang.Class) + */ + public void dropTable(Class entityClass) { + + final String tableName = determineTableName(entityClass); + + dropTable(tableName); + + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#dropTable(java.lang.String) + */ + @Override + public void dropTable(String tableName) { + + log.info("Dropping table => " + tableName); + + final String q = CqlUtils.dropTable(tableName); + log.info(q); + + execute(new SessionCallback() { + + @Override + public ResultSet doInSession(Session s) throws DataAccessException { + + return s.execute(q); + + } + + }); + + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#getTableMetadata(java.lang.Class) + */ + @Override + public TableMetadata getTableMetadata(final String tableName) { + + Assert.notNull(tableName); + + return execute(new SessionCallback() { + + public TableMetadata doInSession(Session s) throws DataAccessException { + + log.info("Keyspace => " + keyspace.getKeyspace()); + + return s.getCluster().getMetadata().getKeyspace(keyspace.getKeyspace()).getTable(tableName); + } + }); + } + + /** + * Execute a command at the Session Level + * + * @param callback + * @return + */ + protected T execute(SessionCallback callback) { + + Assert.notNull(callback); + + try { + return callback.doInSession(session); + } catch (RuntimeException x) { + throw tryToConvert(x); + } + } + + protected RuntimeException tryToConvert(RuntimeException x) { + RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(x); + return resolved == null ? x : resolved; + } + + /** + * @param entityClass + * @return + */ + public String determineTableName(Class entityClass) { + + if (entityClass == null) { + throw new InvalidDataAccessApiUsageException( + "No class parameter provided, entity table name can't be determined!"); + } + + CassandraPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + if (entity == null) { + throw new InvalidDataAccessApiUsageException("No Persitent Entity information found for the class " + + entityClass.getName()); + } + return entity.getTable(); + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraClusterFactoryBean.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraClusterFactoryBean.java new file mode 100644 index 000000000..5221028cb --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraClusterFactoryBean.java @@ -0,0 +1,263 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.core; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.cassandra.support.CassandraExceptionTranslator; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.data.cassandra.config.CompressionType; +import org.springframework.data.cassandra.config.PoolingOptionsConfig; +import org.springframework.data.cassandra.config.SocketOptionsConfig; +import org.springframework.util.StringUtils; + +import com.datastax.driver.core.AuthProvider; +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.HostDistance; +import com.datastax.driver.core.PoolingOptions; +import com.datastax.driver.core.ProtocolOptions.Compression; +import com.datastax.driver.core.SocketOptions; +import com.datastax.driver.core.policies.LoadBalancingPolicy; +import com.datastax.driver.core.policies.ReconnectionPolicy; +import com.datastax.driver.core.policies.RetryPolicy; + +/** + * Convenient factory for configuring a Cassandra Cluster. + * + * @author Alex Shvid + */ + +public class CassandraClusterFactoryBean implements FactoryBean, InitializingBean, DisposableBean, + PersistenceExceptionTranslator { + + private static final int DEFAULT_PORT = 9042; + + private Cluster cluster; + + private String contactPoints; + private int port = DEFAULT_PORT; + private CompressionType compressionType; + + private PoolingOptionsConfig localPoolingOptions; + private PoolingOptionsConfig remotePoolingOptions; + private SocketOptionsConfig socketOptions; + + private AuthProvider authProvider; + private LoadBalancingPolicy loadBalancingPolicy; + private ReconnectionPolicy reconnectionPolicy; + private RetryPolicy retryPolicy; + + private boolean metricsEnabled = true; + + private final PersistenceExceptionTranslator exceptionTranslator = new CassandraExceptionTranslator(); + + public Cluster getObject() throws Exception { + return cluster; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + public Class getObjectType() { + return Cluster.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#isSingleton() + */ + public boolean isSingleton() { + return true; + } + + /* + * (non-Javadoc) + * @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(java.lang.RuntimeException) + */ + public DataAccessException translateExceptionIfPossible(RuntimeException ex) { + return exceptionTranslator.translateExceptionIfPossible(ex); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception { + + if (!StringUtils.hasText(contactPoints)) { + throw new IllegalArgumentException("at least one server is required"); + } + + Cluster.Builder builder = Cluster.builder(); + + builder.addContactPoints(StringUtils.commaDelimitedListToStringArray(contactPoints)).withPort(port); + + if (compressionType != null) { + builder.withCompression(convertCompressionType(compressionType)); + } + + if (localPoolingOptions != null) { + builder.withPoolingOptions(configPoolingOptions(HostDistance.LOCAL, localPoolingOptions)); + } + + if (remotePoolingOptions != null) { + builder.withPoolingOptions(configPoolingOptions(HostDistance.REMOTE, remotePoolingOptions)); + } + + if (socketOptions != null) { + builder.withSocketOptions(configSocketOptions(socketOptions)); + } + + if (authProvider != null) { + builder.withAuthProvider(authProvider); + } + + if (loadBalancingPolicy != null) { + builder.withLoadBalancingPolicy(loadBalancingPolicy); + } + + if (reconnectionPolicy != null) { + builder.withReconnectionPolicy(reconnectionPolicy); + } + + if (retryPolicy != null) { + builder.withRetryPolicy(retryPolicy); + } + + if (!metricsEnabled) { + builder.withoutMetrics(); + } + + Cluster cluster = builder.build(); + + // initialize property + this.cluster = cluster; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.DisposableBean#destroy() + */ + public void destroy() throws Exception { + this.cluster.shutdown(); + } + + public void setContactPoints(String contactPoints) { + this.contactPoints = contactPoints; + } + + public void setPort(int port) { + this.port = port; + } + + public void setCompressionType(CompressionType compressionType) { + this.compressionType = compressionType; + } + + public void setLocalPoolingOptions(PoolingOptionsConfig localPoolingOptions) { + this.localPoolingOptions = localPoolingOptions; + } + + public void setRemotePoolingOptions(PoolingOptionsConfig remotePoolingOptions) { + this.remotePoolingOptions = remotePoolingOptions; + } + + public void setSocketOptions(SocketOptionsConfig socketOptions) { + this.socketOptions = socketOptions; + } + + public void setAuthProvider(AuthProvider authProvider) { + this.authProvider = authProvider; + } + + public void setLoadBalancingPolicy(LoadBalancingPolicy loadBalancingPolicy) { + this.loadBalancingPolicy = loadBalancingPolicy; + } + + public void setReconnectionPolicy(ReconnectionPolicy reconnectionPolicy) { + this.reconnectionPolicy = reconnectionPolicy; + } + + public void setRetryPolicy(RetryPolicy retryPolicy) { + this.retryPolicy = retryPolicy; + } + + public void setMetricsEnabled(boolean metricsEnabled) { + this.metricsEnabled = metricsEnabled; + } + + private static Compression convertCompressionType(CompressionType type) { + switch (type) { + case NONE: + return Compression.NONE; + case SNAPPY: + return Compression.SNAPPY; + } + throw new IllegalArgumentException("unknown compression type " + type); + } + + private static PoolingOptions configPoolingOptions(HostDistance hostDistance, PoolingOptionsConfig config) { + PoolingOptions poolingOptions = new PoolingOptions(); + + if (config.getMinSimultaneousRequests() != null) { + poolingOptions + .setMinSimultaneousRequestsPerConnectionThreshold(hostDistance, config.getMinSimultaneousRequests()); + } + if (config.getMaxSimultaneousRequests() != null) { + poolingOptions + .setMaxSimultaneousRequestsPerConnectionThreshold(hostDistance, config.getMaxSimultaneousRequests()); + } + if (config.getCoreConnections() != null) { + poolingOptions.setCoreConnectionsPerHost(hostDistance, config.getCoreConnections()); + } + if (config.getMaxConnections() != null) { + poolingOptions.setMaxConnectionsPerHost(hostDistance, config.getMaxConnections()); + } + + return poolingOptions; + } + + private static SocketOptions configSocketOptions(SocketOptionsConfig config) { + SocketOptions socketOptions = new SocketOptions(); + + if (config.getConnectTimeoutMls() != null) { + socketOptions.setConnectTimeoutMillis(config.getConnectTimeoutMls()); + } + if (config.getKeepAlive() != null) { + socketOptions.setKeepAlive(config.getKeepAlive()); + } + if (config.getReuseAddress() != null) { + socketOptions.setReuseAddress(config.getReuseAddress()); + } + if (config.getSoLinger() != null) { + socketOptions.setSoLinger(config.getSoLinger()); + } + if (config.getTcpNoDelay() != null) { + socketOptions.setTcpNoDelay(config.getTcpNoDelay()); + } + if (config.getReceiveBufferSize() != null) { + socketOptions.setReceiveBufferSize(config.getReceiveBufferSize()); + } + if (config.getSendBufferSize() != null) { + socketOptions.setSendBufferSize(config.getSendBufferSize()); + } + + return socketOptions; + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraDataOperations.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraDataOperations.java new file mode 100644 index 000000000..39a0722a8 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraDataOperations.java @@ -0,0 +1,619 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.core; + +import java.util.List; +import java.util.Map; + +import org.springframework.data.cassandra.convert.CassandraConverter; + +import com.datastax.driver.core.querybuilder.Select; + +/** + * Operations for interacting with Cassandra. These operations are used by the Repository implementation, but can also + * be used directly when that is desired by the developer. + * + * @author Alex Shvid + * @author David Webb + * @author Matthew Adams + * + */ +public interface CassandraDataOperations { + + /** + * The table name used for the specified class by this template. + * + * @param entityClass must not be {@literal null}. + * @return + */ + String getTableName(Class entityClass); + + /** + * Execute query and convert ResultSet to the list of entities + * + * @param query must not be {@literal null}. + * @param selectClass must not be {@literal null}, mapped entity type. + * @return + */ + List select(String cql, Class selectClass); + + /** + * Execute query and convert ResultSet to the entity + * + * @param query must not be {@literal null}. + * @param selectClass must not be {@literal null}, mapped entity type. + * @return + */ + T selectOne(String cql, Class selectClass); + + List select(Select selectQuery, Class selectClass); + + T selectOne(Select selectQuery, Class selectClass); + + Long count(Select selectQuery); + + /** + * Insert the given object to the table by id. + * + * @param entity + */ + T insert(T entity); + + /** + * Insert the given object to the table by id. + * + * @param entity + * @param tableName + * @return + */ + T insert(T entity, String tableName); + + /** + * @param entity + * @param tableName + * @param options + * @return + */ + T insert(T entity, String tableName, QueryOptions options); + + /** + * @param entity + * @param tableName + * @param options + * @return + */ + T insert(T entity, QueryOptions options); + + /** + * @param entity + * @param tableName + * @param options + * @return + */ + T insert(T entity, Map optionsByName); + + /** + * @param entity + * @param tableName + * @param optionsByName + * @return + */ + T insert(T entity, String tableName, Map optionsByName); + + /** + * Insert the given list of objects to the table by annotation table name. + * + * @param entities + * @return + */ + List insert(List entities); + + /** + * Insert the given list of objects to the table by name. + * + * @param entities + * @param tableName + * @return + */ + List insert(List entities, String tableName); + + /** + * @param entities + * @param tableName + * @param options + * @return + */ + List insert(List entities, QueryOptions options); + + /** + * @param entities + * @param tableName + * @param optionsByName + * @return + */ + List insert(List entities, Map optionsByName); + + /** + * @param entities + * @param tableName + * @param options + * @return + */ + List insert(List entities, String tableName, QueryOptions options); + + /** + * @param entities + * @param tableName + * @param optionsByName + * @return + */ + List insert(List entities, String tableName, Map optionsByName); + + /** + * Insert the given object to the table by id. + * + * @param object + */ + T insertAsynchronously(T entity); + + /** + * Insert the given object to the table by id. + * + * @param object + */ + T insertAsynchronously(T entity, String tableName); + + /** + * @param entity + * @param tableName + * @param options + * @return + */ + T insertAsynchronously(T entity, QueryOptions options); + + /** + * @param entity + * @param tableName + * @param optionsByName + * @return + */ + T insertAsynchronously(T entity, Map optionsByName); + + /** + * @param entity + * @param tableName + * @param options + * @return + */ + T insertAsynchronously(T entity, String tableName, QueryOptions options); + + /** + * @param entity + * @param tableName + * @param optionsByName + * @return + */ + T insertAsynchronously(T entity, String tableName, Map optionsByName); + + /** + * Insert the given object to the table by id. + * + * @param object + */ + List insertAsynchronously(List entities); + + /** + * Insert the given object to the table by id. + * + * @param object + */ + List insertAsynchronously(List entities, String tableName); + + /** + * @param entities + * @param tableName + * @param options + * @return + */ + List insertAsynchronously(List entities, QueryOptions options); + + /** + * @param entities + * @param tableName + * @param optionsByName + * @return + */ + List insertAsynchronously(List entities, Map optionsByName); + + /** + * @param entities + * @param tableName + * @param options + * @return + */ + List insertAsynchronously(List entities, String tableName, QueryOptions options); + + /** + * @param entities + * @param tableName + * @param optionsByName + * @return + */ + List insertAsynchronously(List entities, String tableName, Map optionsByName); + + /** + * Insert the given object to the table by id. + * + * @param object + */ + T update(T entity); + + /** + * Insert the given object to the table by id. + * + * @param object + */ + T update(T entity, String tableName); + + /** + * @param entity + * @param tableName + * @param options + * @return + */ + T update(T entity, QueryOptions options); + + /** + * @param entity + * @param tableName + * @param optionsByName + * @return + */ + T update(T entity, Map optionsByName); + + /** + * @param entity + * @param tableName + * @param options + * @return + */ + T update(T entity, String tableName, QueryOptions options); + + /** + * @param entity + * @param tableName + * @param optionsByName + * @return + */ + T update(T entity, String tableName, Map optionsByName); + + /** + * Insert the given object to the table by id. + * + * @param object + */ + List update(List entities); + + /** + * Insert the given object to the table by id. + * + * @param object + */ + List update(List entities, String tableName); + + /** + * @param entities + * @param tableName + * @param options + * @return + */ + List update(List entities, QueryOptions options); + + /** + * @param entities + * @param tableName + * @param optionsByName + * @return + */ + List update(List entities, Map optionsByName); + + /** + * @param entities + * @param tableName + * @param options + * @return + */ + List update(List entities, String tableName, QueryOptions options); + + /** + * @param entities + * @param tableName + * @param optionsByName + * @return + */ + List update(List entities, String tableName, Map optionsByName); + + /** + * Insert the given object to the table by id. + * + * @param object + */ + T updateAsynchronously(T entity); + + /** + * Insert the given object to the table by id. + * + * @param object + */ + T updateAsynchronously(T entity, String tableName); + + /** + * @param entity + * @param tableName + * @param options + * @return + */ + T updateAsynchronously(T entity, QueryOptions options); + + /** + * @param entity + * @param tableName + * @param optionsByName + * @return + */ + T updateAsynchronously(T entity, Map optionsByName); + + /** + * @param entity + * @param tableName + * @param options + * @return + */ + T updateAsynchronously(T entity, String tableName, QueryOptions options); + + /** + * @param entity + * @param tableName + * @param optionsByName + * @return + */ + T updateAsynchronously(T entity, String tableName, Map optionsByName); + + /** + * Insert the given object to the table by id. + * + * @param object + */ + List updateAsynchronously(List entities); + + /** + * Insert the given object to the table by id. + * + * @param object + */ + List updateAsynchronously(List entities, String tableName); + + /** + * @param entities + * @param tableName + * @param options + * @return + */ + List updateAsynchronously(List entities, QueryOptions options); + + /** + * @param entities + * @param tableName + * @param optionsByName + * @return + */ + List updateAsynchronously(List entities, Map optionsByName); + + /** + * @param entities + * @param tableName + * @param options + * @return + */ + List updateAsynchronously(List entities, String tableName, QueryOptions options); + + /** + * @param entities + * @param tableName + * @param optionsByName + * @return + */ + List updateAsynchronously(List entities, String tableName, Map optionsByName); + + /** + * Remove the given object from the table by id. + * + * @param object + */ + void delete(T entity); + + /** + * Removes the given object from the given table. + * + * @param object + * @param table must not be {@literal null} or empty. + */ + void delete(T entity, String tableName); + + /** + * @param entity + * @param tableName + * @param options + */ + void delete(T entity, QueryOptions options); + + /** + * @param entity + * @param tableName + * @param optionsByName + */ + void delete(T entity, Map optionsByName); + + /** + * @param entity + * @param tableName + * @param options + */ + void delete(T entity, String tableName, QueryOptions options); + + /** + * @param entity + * @param tableName + * @param optionsByName + */ + void delete(T entity, String tableName, Map optionsByName); + + /** + * Remove the given object from the table by id. + * + * @param object + */ + void delete(List entities); + + /** + * Removes the given object from the given table. + * + * @param object + * @param table must not be {@literal null} or empty. + */ + void delete(List entities, String tableName); + + /** + * @param entities + * @param tableName + * @param options + */ + void delete(List entities, QueryOptions options); + + /** + * @param entities + * @param tableName + * @param optionsByName + */ + void delete(List entities, Map optionsByName); + + /** + * @param entities + * @param tableName + * @param options + */ + void delete(List entities, String tableName, QueryOptions options); + + /** + * @param entities + * @param tableName + * @param optionsByName + */ + void delete(List entities, String tableName, Map optionsByName); + + /** + * Remove the given object from the table by id. + * + * @param object + */ + void deleteAsynchronously(T entity); + + /** + * @param entity + * @param tableName + * @param options + */ + void deleteAsynchronously(T entity, QueryOptions options); + + /** + * @param entity + * @param tableName + * @param optionsByName + */ + void deleteAsynchronously(T entity, Map optionsByName); + + /** + * @param entity + * @param tableName + * @param options + */ + void deleteAsynchronously(T entity, String tableName, QueryOptions options); + + /** + * @param entity + * @param tableName + * @param optionsByName + */ + void deleteAsynchronously(T entity, String tableName, Map optionsByName); + + /** + * Removes the given object from the given table. + * + * @param object + * @param table must not be {@literal null} or empty. + */ + void deleteAsynchronously(T entity, String tableName); + + /** + * Remove the given object from the table by id. + * + * @param object + */ + void deleteAsynchronously(List entities); + + /** + * Removes the given object from the given table. + * + * @param object + * @param table must not be {@literal null} or empty. + */ + void deleteAsynchronously(List entities, String tableName); + + /** + * @param entities + * @param tableName + * @param options + */ + void deleteAsynchronously(List entities, QueryOptions options); + + /** + * @param entities + * @param tableName + * @param optionsByName + */ + void deleteAsynchronously(List entities, Map optionsByName); + + /** + * @param entities + * @param tableName + * @param options + */ + void deleteAsynchronously(List entities, String tableName, QueryOptions options); + + /** + * @param entities + * @param tableName + * @param optionsByName + */ + void deleteAsynchronously(List entities, String tableName, Map optionsByName); + + /** + * Returns the underlying {@link CassandraConverter}. + * + * @return + */ + CassandraConverter getConverter(); +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraDataTemplate.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraDataTemplate.java new file mode 100644 index 000000000..32e577a9b --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraDataTemplate.java @@ -0,0 +1,1269 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.core; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.cassandra.core.CassandraTemplate; +import org.springframework.cassandra.core.SessionCallback; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.cassandra.convert.CassandraConverter; +import org.springframework.data.cassandra.exception.EntityWriterException; +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.cassandra.util.CqlUtils; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.util.Assert; + +import com.datastax.driver.core.Query; +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.Row; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.querybuilder.Batch; +import com.datastax.driver.core.querybuilder.Select; + +/** + * The Cassandra Data Template is a convenience API for all Cassandra Operations using POJOs. This is the "Spring Data" + * flavor of the template. For low level Cassandra Operations use the {@link CassandraTemplate} + * + * @author Alex Shvid + * @author David Webb + */ +public class CassandraDataTemplate extends CassandraTemplate implements CassandraDataOperations { + + /* + * Default Keyspace if none is passed in. + */ + private static final String KEYSPACE_DEFAULT = "system"; + + /* + * List of iterable classes when testing POJOs for specific operations. + */ + public static final Collection ITERABLE_CLASSES; + static { + + Set iterableClasses = new HashSet(); + iterableClasses.add(List.class.getName()); + iterableClasses.add(Collection.class.getName()); + iterableClasses.add(Iterator.class.getName()); + + ITERABLE_CLASSES = Collections.unmodifiableCollection(iterableClasses); + + } + + /* + * Required elements for successful Template Operations. These can be set with the Constructor, or wired in + * later. + * + * TODO - DW - Discuss Autowiring these. + */ + private String keyspace; + private CassandraConverter cassandraConverter; + private MappingContext, CassandraPersistentProperty> mappingContext; + + /** + * Default Constructor for wiring in the required components later + */ + public CassandraDataTemplate() { + } + + /** + * Constructor if only session is known at time of Template Creation + * + * @param session must not be {@literal null} + */ + public CassandraDataTemplate(Session session) { + this(session, null, null); + } + + /** + * Constructor if only session and converter are known at time of Template Creation + * + * @param session must not be {@literal null} + * @param converter must not be {@literal null}. + */ + public CassandraDataTemplate(Session session, CassandraConverter converter) { + this(session, converter, null); + } + + /** + * Constructor used for a basic template configuration + * + * @param session must not be {@literal null}. + * @param converter must not be {@literal null}. + */ + public CassandraDataTemplate(Session session, CassandraConverter converter, String keyspace) { + setSession(session); + this.keyspace = keyspace == null ? KEYSPACE_DEFAULT : keyspace; + this.cassandraConverter = converter; + this.mappingContext = this.cassandraConverter.getMappingContext(); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#selectCount(com.datastax.driver.core.querybuilder.Select) + */ + @Override + public Long count(Select selectQuery) { + return doSelectCount(selectQuery); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#delete(java.util.List) + */ + @Override + public void delete(List entities) { + String tableName = getTableName(entities.get(0).getClass()); + Assert.notNull(tableName); + delete(entities, tableName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#delete(java.util.List, java.util.Map) + */ + @Override + public void delete(List entities, Map optionsByName) { + String tableName = getTableName(entities.get(0).getClass()); + Assert.notNull(tableName); + delete(entities, tableName, optionsByName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#delete(java.util.List, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public void delete(List entities, QueryOptions options) { + String tableName = getTableName(entities.get(0).getClass()); + Assert.notNull(tableName); + delete(entities, tableName, options); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#delete(java.util.List, java.lang.String) + */ + @Override + public void delete(List entities, String tableName) { + delete(entities, tableName, new HashMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#delete(java.util.List, java.lang.String, java.util.Map) + */ + @Override + public void delete(List entities, String tableName, Map optionsByName) { + Assert.notNull(entities); + Assert.notEmpty(entities); + Assert.notNull(tableName); + Assert.notNull(optionsByName); + doBatchDelete(tableName, entities, optionsByName, false); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#delete(java.util.List, java.lang.String, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public void delete(List entities, String tableName, QueryOptions options) { + delete(entities, tableName, options.toMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#delete(java.lang.Object) + */ + @Override + public void delete(T entity) { + String tableName = getTableName(entity.getClass()); + Assert.notNull(tableName); + delete(entity, tableName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#delete(java.lang.Object, java.util.Map) + */ + @Override + public void delete(T entity, Map optionsByName) { + String tableName = getTableName(entity.getClass()); + Assert.notNull(tableName); + delete(entity, tableName, optionsByName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#delete(java.lang.Object, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public void delete(T entity, QueryOptions options) { + String tableName = getTableName(entity.getClass()); + Assert.notNull(tableName); + delete(entity, tableName, options); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#delete(java.lang.Object, java.lang.String) + */ + @Override + public void delete(T entity, String tableName) { + delete(entity, tableName, new HashMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#delete(java.lang.Object, java.lang.String, java.util.Map) + */ + @Override + public void delete(T entity, String tableName, Map optionsByName) { + Assert.notNull(entity); + Assert.notNull(tableName); + Assert.notNull(optionsByName); + doDelete(tableName, entity, optionsByName, false); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#delete(java.lang.Object, java.lang.String, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public void delete(T entity, String tableName, QueryOptions options) { + delete(entity, tableName, options.toMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#deleteAsynchronously(java.util.List) + */ + @Override + public void deleteAsynchronously(List entities) { + String tableName = getTableName(entities.get(0).getClass()); + Assert.notNull(tableName); + deleteAsynchronously(entities, tableName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#deleteAsynchronously(java.util.List, java.util.Map) + */ + @Override + public void deleteAsynchronously(List entities, Map optionsByName) { + String tableName = getTableName(entities.get(0).getClass()); + Assert.notNull(tableName); + deleteAsynchronously(entities, tableName, optionsByName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#deleteAsynchronously(java.util.List, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public void deleteAsynchronously(List entities, QueryOptions options) { + String tableName = getTableName(entities.get(0).getClass()); + Assert.notNull(tableName); + deleteAsynchronously(entities, tableName, options); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#deleteAsynchronously(java.util.List, java.lang.String) + */ + @Override + public void deleteAsynchronously(List entities, String tableName) { + insertAsynchronously(entities, tableName, new HashMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#deleteAsynchronously(java.util.List, java.lang.String, java.util.Map) + */ + @Override + public void deleteAsynchronously(List entities, String tableName, Map optionsByName) { + Assert.notNull(entities); + Assert.notEmpty(entities); + Assert.notNull(tableName); + Assert.notNull(optionsByName); + doBatchDelete(tableName, entities, optionsByName, true); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#deleteAsynchronously(java.util.List, java.lang.String, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public void deleteAsynchronously(List entities, String tableName, QueryOptions options) { + deleteAsynchronously(entities, tableName, options.toMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#deleteAsynchronously(java.lang.Object) + */ + @Override + public void deleteAsynchronously(T entity) { + String tableName = getTableName(entity.getClass()); + Assert.notNull(tableName); + deleteAsynchronously(entity, tableName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#deleteAsynchronously(java.lang.Object, java.util.Map) + */ + @Override + public void deleteAsynchronously(T entity, Map optionsByName) { + String tableName = getTableName(entity.getClass()); + Assert.notNull(tableName); + deleteAsynchronously(entity, tableName, optionsByName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#deleteAsynchronously(java.lang.Object, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public void deleteAsynchronously(T entity, QueryOptions options) { + String tableName = getTableName(entity.getClass()); + Assert.notNull(tableName); + deleteAsynchronously(entity, tableName, options); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#deleteAsynchronously(java.lang.Object, java.lang.String) + */ + @Override + public void deleteAsynchronously(T entity, String tableName) { + deleteAsynchronously(entity, tableName, new HashMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#deleteAsynchronously(java.lang.Object, java.lang.String, java.util.Map) + */ + @Override + public void deleteAsynchronously(T entity, String tableName, Map optionsByName) { + Assert.notNull(entity); + Assert.notNull(tableName); + Assert.notNull(optionsByName); + doDelete(tableName, entity, optionsByName, true); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#deleteAsynchronously(java.lang.Object, java.lang.String, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public void deleteAsynchronously(T entity, String tableName, QueryOptions options) { + deleteAsynchronously(entity, tableName, options.toMap()); + } + + /** + * @param entityClass + * @return + */ + public String determineTableName(Class entityClass) { + + if (entityClass == null) { + throw new InvalidDataAccessApiUsageException( + "No class parameter provided, entity table name can't be determined!"); + } + + CassandraPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + if (entity == null) { + throw new InvalidDataAccessApiUsageException("No Persitent Entity information found for the class " + + entityClass.getName()); + } + return entity.getTable(); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#getConverter() + */ + @Override + public CassandraConverter getConverter() { + return cassandraConverter; + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#getTableName(java.lang.Class) + */ + @Override + public String getTableName(Class entityClass) { + return determineTableName(entityClass); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.util.List) + */ + @Override + public List insert(List entities) { + String tableName = getTableName(entities.get(0).getClass()); + Assert.notNull(tableName); + return insert(entities, tableName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.util.List, java.util.Map) + */ + @Override + public List insert(List entities, Map optionsByName) { + String tableName = getTableName(entities.get(0).getClass()); + Assert.notNull(tableName); + return insert(entities, tableName, optionsByName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.util.List, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public List insert(List entities, QueryOptions options) { + String tableName = getTableName(entities.get(0).getClass()); + Assert.notNull(tableName); + return insert(entities, tableName, options); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.util.List, java.lang.String) + */ + @Override + public List insert(List entities, String tableName) { + return insert(entities, tableName, new HashMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.util.List, java.lang.String, java.util.Map) + */ + @Override + public List insert(List entities, String tableName, Map optionsByName) { + Assert.notNull(entities); + Assert.notEmpty(entities); + Assert.notNull(tableName); + Assert.notNull(optionsByName); + return doBatchInsert(tableName, entities, optionsByName, false); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.util.List, java.lang.String, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public List insert(List entities, String tableName, QueryOptions options) { + return insert(entities, tableName, options.toMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.lang.Object) + */ + @Override + public T insert(T entity) { + String tableName = determineTableName(entity); + Assert.notNull(tableName); + return insert(entity, tableName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.lang.Object, java.util.Map) + */ + @Override + public T insert(T entity, Map optionsByName) { + String tableName = determineTableName(entity); + Assert.notNull(tableName); + return insert(entity, tableName, optionsByName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.lang.Object, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public T insert(T entity, QueryOptions options) { + String tableName = determineTableName(entity); + Assert.notNull(tableName); + return insert(entity, tableName, options); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.lang.Object, java.lang.String) + */ + @Override + public T insert(T entity, String tableName) { + return insert(entity, tableName, new HashMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.lang.Object, java.lang.String, java.util.Map) + */ + @Override + public T insert(T entity, String tableName, Map optionsByName) { + Assert.notNull(entity); + Assert.notNull(tableName); + ensureNotIterable(entity); + return doInsert(tableName, entity, optionsByName, false); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.lang.Object, java.lang.String, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public T insert(T entity, String tableName, QueryOptions options) { + return insert(entity, tableName, options.toMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insertAsynchronously(java.util.List) + */ + @Override + public List insertAsynchronously(List entities) { + String tableName = getTableName(entities.get(0).getClass()); + Assert.notNull(tableName); + return insertAsynchronously(entities, tableName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insertAsynchronously(java.util.List, java.util.Map) + */ + @Override + public List insertAsynchronously(List entities, Map optionsByName) { + String tableName = getTableName(entities.get(0).getClass()); + Assert.notNull(tableName); + return insertAsynchronously(entities, tableName, optionsByName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insertAsynchronously(java.util.List, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public List insertAsynchronously(List entities, QueryOptions options) { + String tableName = getTableName(entities.get(0).getClass()); + Assert.notNull(tableName); + return insertAsynchronously(entities, tableName, options); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insertAsynchronously(java.util.List, java.lang.String) + */ + @Override + public List insertAsynchronously(List entities, String tableName) { + return insertAsynchronously(entities, tableName, new HashMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insertAsynchronously(java.util.List, java.lang.String, java.util.Map) + */ + @Override + public List insertAsynchronously(List entities, String tableName, Map optionsByName) { + Assert.notNull(entities); + Assert.notEmpty(entities); + Assert.notNull(tableName); + Assert.notNull(optionsByName); + return doBatchInsert(tableName, entities, optionsByName, true); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insertAsynchronously(java.util.List, java.lang.String, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public List insertAsynchronously(List entities, String tableName, QueryOptions options) { + return insertAsynchronously(entities, tableName, options.toMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insertAsynchronously(java.lang.Object) + */ + @Override + public T insertAsynchronously(T entity) { + String tableName = determineTableName(entity); + Assert.notNull(tableName); + return insertAsynchronously(entity, tableName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insertAsynchronously(java.lang.Object, java.util.Map) + */ + @Override + public T insertAsynchronously(T entity, Map optionsByName) { + String tableName = determineTableName(entity); + Assert.notNull(tableName); + return insertAsynchronously(entity, tableName, optionsByName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insertAsynchronously(java.lang.Object, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public T insertAsynchronously(T entity, QueryOptions options) { + String tableName = determineTableName(entity); + Assert.notNull(tableName); + return insertAsynchronously(entity, tableName, options); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insertAsynchronously(java.lang.Object, java.lang.String) + */ + @Override + public T insertAsynchronously(T entity, String tableName) { + return insertAsynchronously(entity, tableName, new HashMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insertAsynchronously(java.lang.Object, java.lang.String, java.util.Map) + */ + @Override + public T insertAsynchronously(T entity, String tableName, Map optionsByName) { + Assert.notNull(entity); + Assert.notNull(tableName); + Assert.notNull(optionsByName); + + ensureNotIterable(entity); + + return doInsert(tableName, entity, optionsByName, true); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insertAsynchronously(java.lang.Object, java.lang.String, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public T insertAsynchronously(T entity, String tableName, QueryOptions options) { + return insertAsynchronously(entity, tableName, options.toMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#select(com.datastax.driver.core.querybuilder.Select, java.lang.Class) + */ + @Override + public List select(Select cql, Class selectClass) { + return select(cql.getQueryString(), selectClass); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#select(java.lang.String, java.lang.Class) + */ + @Override + public List select(String cql, Class selectClass) { + return doSelect(cql, new ReadRowCallback(cassandraConverter, selectClass)); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#selectOne(com.datastax.driver.core.querybuilder.Select, java.lang.Class) + */ + @Override + public T selectOne(Select selectQuery, Class selectClass) { + return selectOne(selectQuery.getQueryString(), selectClass); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#selectOne(java.lang.String, java.lang.Class) + */ + @Override + public T selectOne(String cql, Class selectClass) { + return doSelectOne(cql, new ReadRowCallback(cassandraConverter, selectClass)); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#update(java.util.List) + */ + @Override + public List update(List entities) { + String tableName = getTableName(entities.get(0).getClass()); + Assert.notNull(tableName); + return update(entities, tableName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#update(java.util.List, java.util.Map) + */ + @Override + public List update(List entities, Map optionsByName) { + String tableName = getTableName(entities.get(0).getClass()); + Assert.notNull(tableName); + return update(entities, tableName, optionsByName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#update(java.util.List, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public List update(List entities, QueryOptions options) { + String tableName = getTableName(entities.get(0).getClass()); + Assert.notNull(tableName); + return update(entities, tableName, options); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#update(java.util.List, java.lang.String) + */ + @Override + public List update(List entities, String tableName) { + return update(entities, tableName, new HashMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#update(java.util.List, java.lang.String, java.util.Map) + */ + @Override + public List update(List entities, String tableName, Map optionsByName) { + Assert.notNull(entities); + Assert.notEmpty(entities); + Assert.notNull(tableName); + Assert.notNull(optionsByName); + return doBatchUpdate(tableName, entities, optionsByName, false); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#update(java.util.List, java.lang.String, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public List update(List entities, String tableName, QueryOptions options) { + return update(entities, tableName, options.toMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#update(java.lang.Object) + */ + @Override + public T update(T entity) { + String tableName = getTableName(entity.getClass()); + Assert.notNull(tableName); + return update(entity, tableName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#update(java.lang.Object, java.util.Map) + */ + @Override + public T update(T entity, Map optionsByName) { + String tableName = getTableName(entity.getClass()); + Assert.notNull(tableName); + return update(entity, tableName, optionsByName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#update(java.lang.Object, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public T update(T entity, QueryOptions options) { + String tableName = getTableName(entity.getClass()); + Assert.notNull(tableName); + return update(entity, tableName, options); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#update(java.lang.Object, java.lang.String) + */ + @Override + public T update(T entity, String tableName) { + return update(entity, tableName, new HashMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#update(java.lang.Object, java.lang.String, java.util.Map) + */ + @Override + public T update(T entity, String tableName, Map optionsByName) { + Assert.notNull(entity); + Assert.notNull(tableName); + Assert.notNull(optionsByName); + return doUpdate(tableName, entity, optionsByName, false); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#update(java.lang.Object, java.lang.String, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public T update(T entity, String tableName, QueryOptions options) { + return update(entity, tableName, options.toMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#updateAsynchronously(java.util.List) + */ + @Override + public List updateAsynchronously(List entities) { + String tableName = getTableName(entities.get(0).getClass()); + Assert.notNull(tableName); + return updateAsynchronously(entities, tableName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#updateAsynchronously(java.util.List, java.util.Map) + */ + @Override + public List updateAsynchronously(List entities, Map optionsByName) { + String tableName = getTableName(entities.get(0).getClass()); + Assert.notNull(tableName); + return updateAsynchronously(entities, tableName, optionsByName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#updateAsynchronously(java.util.List, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public List updateAsynchronously(List entities, QueryOptions options) { + String tableName = getTableName(entities.get(0).getClass()); + Assert.notNull(tableName); + return updateAsynchronously(entities, tableName, options); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#updateAsynchronously(java.util.List, java.lang.String) + */ + @Override + public List updateAsynchronously(List entities, String tableName) { + return updateAsynchronously(entities, tableName, new HashMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#updateAsynchronously(java.util.List, java.lang.String, java.util.Map) + */ + @Override + public List updateAsynchronously(List entities, String tableName, Map optionsByName) { + Assert.notNull(entities); + Assert.notEmpty(entities); + Assert.notNull(tableName); + Assert.notNull(optionsByName); + return doBatchUpdate(tableName, entities, optionsByName, true); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#updateAsynchronously(java.util.List, java.lang.String, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public List updateAsynchronously(List entities, String tableName, QueryOptions options) { + return updateAsynchronously(entities, tableName, options.toMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#updateAsynchronously(java.lang.Object) + */ + @Override + public T updateAsynchronously(T entity) { + String tableName = getTableName(entity.getClass()); + Assert.notNull(tableName); + return updateAsynchronously(entity, tableName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#updateAsynchronously(java.lang.Object, java.util.Map) + */ + @Override + public T updateAsynchronously(T entity, Map optionsByName) { + String tableName = getTableName(entity.getClass()); + Assert.notNull(tableName); + return updateAsynchronously(entity, tableName, optionsByName); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#updateAsynchronously(java.lang.Object, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public T updateAsynchronously(T entity, QueryOptions options) { + String tableName = getTableName(entity.getClass()); + Assert.notNull(tableName); + return updateAsynchronously(entity, tableName, options); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#updateAsynchronously(java.lang.Object, java.lang.String) + */ + @Override + public T updateAsynchronously(T entity, String tableName) { + return updateAsynchronously(entity, tableName, new HashMap()); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#updateAsynchronously(java.lang.Object, java.lang.String, java.util.Map) + */ + @Override + public T updateAsynchronously(T entity, String tableName, Map optionsByName) { + Assert.notNull(entity); + Assert.notNull(tableName); + Assert.notNull(optionsByName); + return doUpdate(tableName, entity, optionsByName, true); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#updateAsynchronously(java.lang.Object, java.lang.String, org.springframework.data.cassandra.core.QueryOptions) + */ + @Override + public T updateAsynchronously(T entity, String tableName, QueryOptions options) { + return updateAsynchronously(entity, tableName, options.toMap()); + } + + /** + * @param obj + * @return + */ + private String determineTableName(T obj) { + if (null != obj) { + return determineTableName(obj.getClass()); + } + + return null; + } + + /** + * @param query + * @param readRowCallback + * @return + */ + private List doSelect(final String query, ReadRowCallback readRowCallback) { + + ResultSet resultSet = doExecute(new SessionCallback() { + + @Override + public ResultSet doInSession(Session s) throws DataAccessException { + return s.execute(query); + } + }); + + if (resultSet == null) { + return null; + } + + List result = new ArrayList(); + Iterator iterator = resultSet.iterator(); + while (iterator.hasNext()) { + Row row = iterator.next(); + result.add(readRowCallback.doWith(row)); + } + + return result; + } + + /** + * @param selectQuery + * @return + */ + private Long doSelectCount(final Select query) { + + Long count = null; + + ResultSet resultSet = doExecute(new SessionCallback() { + + @Override + public ResultSet doInSession(Session s) throws DataAccessException { + return s.execute(query); + } + }); + + if (resultSet == null) { + return null; + } + + Iterator iterator = resultSet.iterator(); + while (iterator.hasNext()) { + Row row = iterator.next(); + count = row.getLong(0); + } + + return count; + + } + + /** + * @param query + * @param readRowCallback + * @return + */ + private T doSelectOne(final String query, ReadRowCallback readRowCallback) { + + /* + * Run the Query + */ + ResultSet resultSet = doExecute(new SessionCallback() { + + @Override + public ResultSet doInSession(Session s) throws DataAccessException { + return s.execute(query); + } + }); + + if (resultSet == null) { + return null; + } + + Iterator iterator = resultSet.iterator(); + if (iterator.hasNext()) { + Row row = iterator.next(); + T result = readRowCallback.doWith(row); + if (iterator.hasNext()) { + throw new DuplicateKeyException("found two or more results in query " + query); + } + return result; + } + + return null; + } + + /** + * Perform the deletion on a list of objects + * + * @param tableName + * @param objectToRemove + */ + protected void doBatchDelete(final String tableName, final List entities, Map optionsByName, + final boolean deleteAsynchronously) { + + Assert.notEmpty(entities); + + try { + + final Batch b = CqlUtils.toDeleteBatchQuery(keyspace, tableName, entities, optionsByName, cassandraConverter); + logger.info(b.toString()); + + doExecute(new SessionCallback() { + + @Override + public Object doInSession(Session s) throws DataAccessException { + + if (deleteAsynchronously) { + s.executeAsync(b); + } else { + s.execute(b); + } + + return null; + + } + }); + + } catch (EntityWriterException e) { + throw getExceptionTranslator().translateExceptionIfPossible( + new RuntimeException("Failed to translate Object to Query", e)); + } + } + + /** + * Insert a row into a Cassandra CQL Table + * + * @param tableName + * @param entities + * @param optionsByName + * @param insertAsychronously + * @return + */ + protected List doBatchInsert(final String tableName, final List entities, + Map optionsByName, final boolean insertAsychronously) { + + Assert.notEmpty(entities); + + try { + + final Batch b = CqlUtils.toInsertBatchQuery(keyspace, tableName, entities, optionsByName, cassandraConverter); + logger.info(b.getQueryString()); + + return doExecute(new SessionCallback>() { + + @Override + public List doInSession(Session s) throws DataAccessException { + + if (insertAsychronously) { + s.executeAsync(b); + } else { + s.execute(b); + } + + return entities; + + } + }); + + } catch (EntityWriterException e) { + throw getExceptionTranslator().translateExceptionIfPossible( + new RuntimeException("Failed to translate Object to Query", e)); + } + } + + /** + * Update a Batch of rows in a Cassandra CQL Table + * + * @param tableName + * @param entities + * @param optionsByName + * @param updateAsychronously + * @return + */ + protected List doBatchUpdate(final String tableName, final List entities, + Map optionsByName, final boolean updateAsychronously) { + + Assert.notEmpty(entities); + + try { + + final Batch b = CqlUtils.toUpdateBatchQuery(keyspace, tableName, entities, optionsByName, cassandraConverter); + logger.info(b.toString()); + + return doExecute(new SessionCallback>() { + + @Override + public List doInSession(Session s) throws DataAccessException { + + if (updateAsychronously) { + s.executeAsync(b); + } else { + s.execute(b); + } + + return entities; + + } + }); + + } catch (EntityWriterException e) { + throw getExceptionTranslator().translateExceptionIfPossible( + new RuntimeException("Failed to translate Object to Query", e)); + } + } + + /** + * Perform the removal of a Row. + * + * @param tableName + * @param objectToRemove + */ + protected void doDelete(final String tableName, final T objectToRemove, Map optionsByName, + final boolean deleteAsynchronously) { + + try { + + final Query q = CqlUtils.toDeleteQuery(keyspace, tableName, objectToRemove, optionsByName, cassandraConverter); + logger.info(q.toString()); + + doExecute(new SessionCallback() { + + @Override + public Object doInSession(Session s) throws DataAccessException { + + if (deleteAsynchronously) { + s.executeAsync(q); + } else { + s.execute(q); + } + + return null; + + } + }); + + } catch (EntityWriterException e) { + throw getExceptionTranslator().translateExceptionIfPossible( + new RuntimeException("Failed to translate Object to Query", e)); + } + } + + /** + * Execute a command at the Session Level + * + * @param callback + * @return + */ + protected T doExecute(SessionCallback callback) { + + Assert.notNull(callback); + + try { + + return callback.doInSession(getSession()); + + } catch (DataAccessException e) { + throw throwTranslated(e); + } + } + + /** + * Insert a row into a Cassandra CQL Table + * + * @param tableName + * @param entity + */ + protected T doInsert(final String tableName, final T entity, final Map optionsByName, + final boolean insertAsychronously) { + + try { + + final Query q = CqlUtils.toInsertQuery(keyspace, tableName, entity, optionsByName, cassandraConverter); + logger.info(q.toString()); + if (q.getConsistencyLevel() != null) { + logger.info(q.getConsistencyLevel().name()); + } + if (q.getRetryPolicy() != null) { + logger.info(q.getRetryPolicy().toString()); + } + + return doExecute(new SessionCallback() { + + @Override + public T doInSession(Session s) throws DataAccessException { + + if (insertAsychronously) { + s.executeAsync(q); + } else { + s.execute(q); + } + + return entity; + + } + }); + + } catch (EntityWriterException e) { + throw getExceptionTranslator().translateExceptionIfPossible( + new RuntimeException("Failed to translate Object to Query", e)); + } + + } + + /** + * Update a row into a Cassandra CQL Table + * + * @param tableName + * @param entity + * @param optionsByName + * @param updateAsychronously + * @return + */ + protected T doUpdate(final String tableName, final T entity, final Map optionsByName, + final boolean updateAsychronously) { + + try { + + final Query q = CqlUtils.toUpdateQuery(keyspace, tableName, entity, optionsByName, cassandraConverter); + logger.info(q.toString()); + + return doExecute(new SessionCallback() { + + @Override + public T doInSession(Session s) throws DataAccessException { + + if (updateAsychronously) { + s.executeAsync(q); + } else { + s.execute(q); + } + + return entity; + + } + }); + + } catch (EntityWriterException e) { + throw getExceptionTranslator().translateExceptionIfPossible( + new RuntimeException("Failed to translate Object to Query", e)); + } + + } + + /** + * Verify the object is not an iterable type + * + * @param o + */ + protected void ensureNotIterable(Object o) { + if (null != o) { + if (o.getClass().isArray() || ITERABLE_CLASSES.contains(o.getClass().getName())) { + throw new IllegalArgumentException("Cannot use a collection here."); + } + } + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraKeyspaceFactoryBean.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraKeyspaceFactoryBean.java new file mode 100644 index 000000000..c3c421ac9 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraKeyspaceFactoryBean.java @@ -0,0 +1,341 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.core; + +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.cassandra.support.CassandraExceptionTranslator; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.data.cassandra.config.KeyspaceAttributes; +import org.springframework.data.cassandra.config.TableAttributes; +import org.springframework.data.cassandra.convert.CassandraConverter; +import org.springframework.data.cassandra.convert.MappingCassandraConverter; +import org.springframework.data.cassandra.mapping.CassandraMappingContext; +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.cassandra.util.CqlUtils; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.KeyspaceMetadata; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.TableMetadata; +import com.datastax.driver.core.exceptions.NoHostAvailableException; + +/** + * Convenient factory for configuring a Cassandra Session. Session is a thread safe singleton and created per a + * keyspace. So, it is enough to have one session per application. + * + * @author Alex Shvid + */ + +public class CassandraKeyspaceFactoryBean implements FactoryBean, InitializingBean, DisposableBean, + BeanClassLoaderAware, PersistenceExceptionTranslator { + + private static final Logger log = LoggerFactory.getLogger(CassandraKeyspaceFactoryBean.class); + + public static final String DEFAULT_REPLICATION_STRATEGY = "SimpleStrategy"; + public static final int DEFAULT_REPLICATION_FACTOR = 1; + + private ClassLoader beanClassLoader; + + private Cluster cluster; + private Session session; + private String keyspace; + + private CassandraConverter converter; + private MappingContext, CassandraPersistentProperty> mappingContext; + + private Keyspace keyspaceBean; + + private KeyspaceAttributes keyspaceAttributes; + + private final PersistenceExceptionTranslator exceptionTranslator = new CassandraExceptionTranslator(); + + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + public Keyspace getObject() { + return keyspaceBean; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + public Class getObjectType() { + return Session.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#isSingleton() + */ + public boolean isSingleton() { + return true; + } + + /* + * (non-Javadoc) + * @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(java.lang.RuntimeException) + */ + public DataAccessException translateExceptionIfPossible(RuntimeException ex) { + return exceptionTranslator.translateExceptionIfPossible(ex); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception { + + if (this.converter == null) { + this.converter = getDefaultCassandraConverter(); + } + this.mappingContext = this.converter.getMappingContext(); + + if (cluster == null) { + throw new IllegalArgumentException("at least one cluster is required"); + } + + Session session = null; + session = cluster.connect(); + + if (StringUtils.hasText(keyspace)) { + + KeyspaceMetadata keyspaceMetadata = cluster.getMetadata().getKeyspace(keyspace.toLowerCase()); + boolean keyspaceExists = keyspaceMetadata != null; + boolean keyspaceCreated = false; + + if (keyspaceExists) { + log.info("keyspace exists " + keyspaceMetadata.asCQLQuery()); + } + + if (keyspaceAttributes == null) { + keyspaceAttributes = new KeyspaceAttributes(); + } + + // drop the old keyspace if needed + if (keyspaceExists && (keyspaceAttributes.isCreate() || keyspaceAttributes.isCreateDrop())) { + log.info("Drop keyspace " + keyspace + " on afterPropertiesSet"); + session.execute("DROP KEYSPACE " + keyspace + ";"); + keyspaceExists = false; + } + + // create the new keyspace if needed + if (!keyspaceExists + && (keyspaceAttributes.isCreate() || keyspaceAttributes.isCreateDrop() || keyspaceAttributes.isUpdate())) { + + String query = String + .format( + "CREATE KEYSPACE %1$s WITH replication = { 'class' : '%2$s', 'replication_factor' : %3$d } AND DURABLE_WRITES = %4$b", + keyspace, keyspaceAttributes.getReplicationStrategy(), keyspaceAttributes.getReplicationFactor(), + keyspaceAttributes.isDurableWrites()); + + log.info("Create keyspace " + keyspace + " on afterPropertiesSet " + query); + + session.execute(query); + keyspaceCreated = true; + } + + // update keyspace if needed + if (keyspaceAttributes.isUpdate() && !keyspaceCreated) { + + if (compareKeyspaceAttributes(keyspaceAttributes, keyspaceMetadata) != null) { + + String query = String + .format( + "ALTER KEYSPACE %1$s WITH replication = { 'class' : '%2$s', 'replication_factor' : %3$d } AND DURABLE_WRITES = %4$b", + keyspace, keyspaceAttributes.getReplicationStrategy(), keyspaceAttributes.getReplicationFactor(), + keyspaceAttributes.isDurableWrites()); + + log.info("Update keyspace " + keyspace + " on afterPropertiesSet " + query); + session.execute(query); + } + + } + + // validate keyspace if needed + if (keyspaceAttributes.isValidate()) { + + if (!keyspaceExists) { + throw new InvalidDataAccessApiUsageException("keyspace '" + keyspace + "' not found in the Cassandra"); + } + + String errorField = compareKeyspaceAttributes(keyspaceAttributes, keyspaceMetadata); + if (errorField != null) { + throw new InvalidDataAccessApiUsageException(errorField + " attribute is not much in the keyspace '" + + keyspace + "'"); + } + + } + + session.execute("USE " + keyspace); + + if (!CollectionUtils.isEmpty(keyspaceAttributes.getTables())) { + + for (TableAttributes tableAttributes : keyspaceAttributes.getTables()) { + + String entityClassName = tableAttributes.getEntity(); + Class entityClass = ClassUtils.forName(entityClassName, this.beanClassLoader); + CassandraPersistentEntity entity = determineEntity(entityClass); + String useTableName = tableAttributes.getName() != null ? tableAttributes.getName() : entity.getTable(); + + if (keyspaceCreated) { + createNewTable(session, useTableName, entity); + } else if (keyspaceAttributes.isUpdate()) { + TableMetadata table = keyspaceMetadata.getTable(useTableName.toLowerCase()); + if (table == null) { + createNewTable(session, useTableName, entity); + } else { + // alter table columns + for (String cql : CqlUtils.alterTable(useTableName, entity, table)) { + log.info("Execute on keyspace " + keyspace + " CQL " + cql); + session.execute(cql); + } + } + } else if (keyspaceAttributes.isValidate()) { + TableMetadata table = keyspaceMetadata.getTable(useTableName.toLowerCase()); + if (table == null) { + throw new InvalidDataAccessApiUsageException("not found table " + useTableName + " for entity " + + entityClassName); + } + // validate columns + List alter = CqlUtils.alterTable(useTableName, entity, table); + if (!alter.isEmpty()) { + throw new InvalidDataAccessApiUsageException("invalid table " + useTableName + " for entity " + + entityClassName + ". modify it by " + alter); + } + } + + // System.out.println("tableAttributes, entityClass=" + entityClass + ", table = " + entity.getTable()); + + } + } + + } + + // initialize property + this.session = session; + + this.keyspaceBean = new Keyspace(keyspace, session, converter); + } + + private void createNewTable(Session session, String useTableName, CassandraPersistentEntity entity) + throws NoHostAvailableException { + String cql = CqlUtils.createTable(useTableName, entity); + log.info("Execute on keyspace " + keyspace + " CQL " + cql); + session.execute(cql); + for (String indexCQL : CqlUtils.createIndexes(useTableName, entity)) { + log.info("Execute on keyspace " + keyspace + " CQL " + indexCQL); + session.execute(indexCQL); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.DisposableBean#destroy() + */ + public void destroy() throws Exception { + + if (StringUtils.hasText(keyspace) && keyspaceAttributes != null && keyspaceAttributes.isCreateDrop()) { + log.info("Drop keyspace " + keyspace + " on destroy"); + session.execute("USE system"); + session.execute("DROP KEYSPACE " + keyspace); + } + this.session.shutdown(); + } + + public void setKeyspace(String keyspace) { + this.keyspace = keyspace; + } + + public void setCluster(Cluster cluster) { + this.cluster = cluster; + } + + public void setKeyspaceAttributes(KeyspaceAttributes keyspaceAttributes) { + this.keyspaceAttributes = keyspaceAttributes; + } + + public void setConverter(CassandraConverter converter) { + this.converter = converter; + } + + private static String compareKeyspaceAttributes(KeyspaceAttributes keyspaceAttributes, + KeyspaceMetadata keyspaceMetadata) { + if (keyspaceAttributes.isDurableWrites() != keyspaceMetadata.isDurableWrites()) { + return "durableWrites"; + } + Map replication = keyspaceMetadata.getReplication(); + String replicationFactorStr = replication.get("replication_factor"); + if (replicationFactorStr == null) { + return "replication_factor"; + } + try { + int replicationFactor = Integer.parseInt(replicationFactorStr); + if (keyspaceAttributes.getReplicationFactor() != replicationFactor) { + return "replication_factor"; + } + } catch (NumberFormatException e) { + return "replication_factor"; + } + + String attributesStrategy = keyspaceAttributes.getReplicationStrategy(); + if (attributesStrategy.indexOf('.') == -1) { + attributesStrategy = "org.apache.cassandra.locator." + attributesStrategy; + } + String replicationStrategy = replication.get("class"); + if (!attributesStrategy.equals(replicationStrategy)) { + return "replication_class"; + } + return null; + } + + CassandraPersistentEntity determineEntity(Class entityClass) { + + if (entityClass == null) { + throw new InvalidDataAccessApiUsageException( + "No class parameter provided, entity table name can't be determined!"); + } + + CassandraPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + if (entity == null) { + throw new InvalidDataAccessApiUsageException("No Persitent Entity information found for the class " + + entityClass.getName()); + } + return entity; + } + + private static final CassandraConverter getDefaultCassandraConverter() { + MappingCassandraConverter converter = new MappingCassandraConverter(new CassandraMappingContext()); + converter.afterPropertiesSet(); + return converter; + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraValue.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraValue.java new file mode 100644 index 000000000..8d165908d --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraValue.java @@ -0,0 +1,45 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.core; + +import java.nio.ByteBuffer; + +import com.datastax.driver.core.DataType; + +/** + * Simple Cassandra value of the ByteBuffer with DataType + * + * @author Alex Shvid + */ +public class CassandraValue { + + private final ByteBuffer value; + private final DataType type; + + public CassandraValue(ByteBuffer value, DataType type) { + this.value = value; + this.type = type; + } + + public ByteBuffer getValue() { + return value; + } + + public DataType getType() { + return type; + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ClassNameToTableNameConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ClassNameToTableNameConverter.java new file mode 100644 index 000000000..aee4faf3f --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ClassNameToTableNameConverter.java @@ -0,0 +1,6 @@ +package org.springframework.data.cassandra.core; + +import org.springframework.core.convert.converter.Converter; + +public interface ClassNameToTableNameConverter extends Converter { +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ColumnNameToFieldNameConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ColumnNameToFieldNameConverter.java new file mode 100644 index 000000000..627873cd1 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ColumnNameToFieldNameConverter.java @@ -0,0 +1,6 @@ +package org.springframework.data.cassandra.core; + +import org.springframework.core.convert.converter.Converter; + +public interface ColumnNameToFieldNameConverter extends Converter { +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ConsistencyLevel.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ConsistencyLevel.java new file mode 100644 index 000000000..e8b1247f2 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ConsistencyLevel.java @@ -0,0 +1,28 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.core; + +/** + * Generic Consistency Levels associated with Cassandra. + * + * @author David Webb + * + */ +public enum ConsistencyLevel { + + ANY, ONE, TWO, THREE, QUOROM, LOCAL_QUOROM, EACH_QUOROM, ALL + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ConsistencyLevelResolver.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ConsistencyLevelResolver.java new file mode 100644 index 000000000..cb02b869c --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ConsistencyLevelResolver.java @@ -0,0 +1,78 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.core; + +/** + * Determine driver consistency level based on ConsistencyLevel + * + * @author David Webb + * + */ +public final class ConsistencyLevelResolver { + + /** + * No instances allowed + */ + private ConsistencyLevelResolver() { + } + + /** + * Decode the generic spring data cassandra enum to the type required by the DataStax Driver. + * + * @param level + * @return The DataStax Driver Consistency Level. + */ + public static com.datastax.driver.core.ConsistencyLevel resolve(ConsistencyLevel level) { + + com.datastax.driver.core.ConsistencyLevel resolvedLevel = com.datastax.driver.core.ConsistencyLevel.ONE; + + /* + * Determine the driver level based on our enum + */ + switch (level) { + case ONE: + resolvedLevel = com.datastax.driver.core.ConsistencyLevel.ONE; + break; + case ALL: + resolvedLevel = com.datastax.driver.core.ConsistencyLevel.ALL; + break; + case ANY: + resolvedLevel = com.datastax.driver.core.ConsistencyLevel.ANY; + break; + case EACH_QUOROM: + resolvedLevel = com.datastax.driver.core.ConsistencyLevel.EACH_QUORUM; + break; + case LOCAL_QUOROM: + resolvedLevel = com.datastax.driver.core.ConsistencyLevel.LOCAL_QUORUM; + break; + case QUOROM: + resolvedLevel = com.datastax.driver.core.ConsistencyLevel.QUORUM; + break; + case THREE: + resolvedLevel = com.datastax.driver.core.ConsistencyLevel.THREE; + break; + case TWO: + resolvedLevel = com.datastax.driver.core.ConsistencyLevel.TWO; + break; + default: + break; + } + + return resolvedLevel; + + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/FieldNameToColumnNameConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/FieldNameToColumnNameConverter.java new file mode 100644 index 000000000..7d0d888ee --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/FieldNameToColumnNameConverter.java @@ -0,0 +1,6 @@ +package org.springframework.data.cassandra.core; + +import org.springframework.core.convert.converter.Converter; + +public interface FieldNameToColumnNameConverter extends Converter { +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/QueryOptions.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/QueryOptions.java new file mode 100644 index 000000000..5d3755dfb --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/QueryOptions.java @@ -0,0 +1,106 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.core; + +import java.util.HashMap; +import java.util.Map; + +/** + * Contains Query Options for Cassandra queries. This controls the Consistency Tuning and Retry Policy for a Query. + * + * @author David Webb + * + */ +public class QueryOptions { + + private ConsistencyLevel consistencyLevel; + private RetryPolicy retryPolicy; + private Integer ttl; + + /** + * Create a Map of all these options. + */ + public Map toMap() { + + Map m = new HashMap(); + + if (getConsistencyLevel() != null) { + m.put(QueryOptionMapKeys.CONSISTENCY_LEVEL, getConsistencyLevel()); + } + if (getRetryPolicy() != null) { + m.put(QueryOptionMapKeys.RETRY_POLICY, getRetryPolicy()); + } + if (getTtl() != null) { + m.put(QueryOptionMapKeys.TTL, getTtl()); + } + + return m; + } + + /** + * @return Returns the consistencyLevel. + */ + public ConsistencyLevel getConsistencyLevel() { + return consistencyLevel; + } + + /** + * @param consistencyLevel The consistencyLevel to set. + */ + public void setConsistencyLevel(ConsistencyLevel consistencyLevel) { + this.consistencyLevel = consistencyLevel; + } + + /** + * @return Returns the retryPolicy. + */ + public RetryPolicy getRetryPolicy() { + return retryPolicy; + } + + /** + * @param retryPolicy The retryPolicy to set. + */ + public void setRetryPolicy(RetryPolicy retryPolicy) { + this.retryPolicy = retryPolicy; + } + + /** + * @return Returns the ttl. + */ + public Integer getTtl() { + return ttl; + } + + /** + * @param ttl The ttl to set. + */ + public void setTtl(Integer ttl) { + this.ttl = ttl; + } + + /** + * Constants for looking up Map Elements by Key + * + * @author David Webb + * + */ + public static interface QueryOptionMapKeys { + public final String CONSISTENCY_LEVEL = "ConsistencyLevel"; + public final String RETRY_POLICY = "RetryPolicy"; + public final String TTL = "TTL"; + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReadRowCallback.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReadRowCallback.java new file mode 100644 index 000000000..a87050ebb --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReadRowCallback.java @@ -0,0 +1,47 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.core; + +import org.springframework.cassandra.core.RowCallback; +import org.springframework.data.convert.EntityReader; +import org.springframework.util.Assert; + +import com.datastax.driver.core.Row; + +/** + * Simple {@link RowCallback} that will transform {@link Row} into the given target type using the given + * {@link EntityReader}. + * + * @author Alex Shvid + */ +public class ReadRowCallback implements RowCallback { + + private final EntityReader reader; + private final Class type; + + public ReadRowCallback(EntityReader reader, Class type) { + Assert.notNull(reader); + Assert.notNull(type); + this.reader = reader; + this.type = type; + } + + @Override + public T doWith(Row row) { + T source = reader.read(type, row); + return source; + } +} \ No newline at end of file diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/RetryPolicy.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/RetryPolicy.java new file mode 100644 index 000000000..be617f2e5 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/RetryPolicy.java @@ -0,0 +1,28 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.core; + +/** + * Retry Policies associated with Cassandra. + * + * @author David Webb + * + */ +public enum RetryPolicy { + + DEFAULT, DOWNGRADING_CONSISTENCY, FALLTHROUGH, LOGGING + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/RetryPolicyResolver.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/RetryPolicyResolver.java new file mode 100644 index 000000000..fbff98cb0 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/RetryPolicyResolver.java @@ -0,0 +1,67 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.core; + +import com.datastax.driver.core.policies.DefaultRetryPolicy; +import com.datastax.driver.core.policies.DowngradingConsistencyRetryPolicy; +import com.datastax.driver.core.policies.FallthroughRetryPolicy; + +/** + * Determine driver query retry policy + * + * @author David Webb + * + */ +public final class RetryPolicyResolver { + + /** + * No instances allowed + */ + private RetryPolicyResolver() { + } + + /** + * Decode the generic spring data cassandra enum to the type required by the DataStax Driver. + * + * @param level + * @return The DataStax Driver Consistency Level. + */ + public static com.datastax.driver.core.policies.RetryPolicy resolve(RetryPolicy policy) { + + com.datastax.driver.core.policies.RetryPolicy resolvedPolicy = DefaultRetryPolicy.INSTANCE; + + /* + * Determine the driver level based on our enum + */ + switch (policy) { + case DEFAULT: + resolvedPolicy = DefaultRetryPolicy.INSTANCE; + break; + case DOWNGRADING_CONSISTENCY: + resolvedPolicy = DowngradingConsistencyRetryPolicy.INSTANCE; + break; + case FALLTHROUGH: + resolvedPolicy = FallthroughRetryPolicy.INSTANCE; + break; + default: + resolvedPolicy = DefaultRetryPolicy.INSTANCE; + break; + } + + return resolvedPolicy; + + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/TableNameToClassNameConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/TableNameToClassNameConverter.java new file mode 100644 index 000000000..02a88d3fe --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/TableNameToClassNameConverter.java @@ -0,0 +1,6 @@ +package org.springframework.data.cassandra.core; + +import org.springframework.core.convert.converter.Converter; + +public interface TableNameToClassNameConverter extends Converter { +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/exception/EntityWriterException.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/exception/EntityWriterException.java new file mode 100644 index 000000000..d0275733e --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/exception/EntityWriterException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.exception; + +/** + * Exception to handle failing to write a PersistedEntity to a CQL String or Query object + * + * @author David Webb + * + */ +public class EntityWriterException extends Exception { + + /** + * @param message + */ + public EntityWriterException(String message) { + super(message); + } + + /** + * @param cause + */ + public EntityWriterException(Throwable cause) { + super(cause); + } + + /** + * @param message + * @param cause + */ + public EntityWriterException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntity.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntity.java new file mode 100644 index 000000000..bfaa039bb --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntity.java @@ -0,0 +1,120 @@ +/* + * Copyright 2011-2012 the original author or authors. + * + * 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.springframework.data.cassandra.mapping; + +import java.util.Comparator; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.expression.BeanFactoryAccessor; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.data.cassandra.util.CassandraNamingUtils; +import org.springframework.data.mapping.model.BasicPersistentEntity; +import org.springframework.data.util.TypeInformation; +import org.springframework.expression.Expression; +import org.springframework.expression.ParserContext; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.util.StringUtils; + +/** + * Cassandra specific {@link BasicPersistentEntity} implementation that adds Cassandra specific meta-data such as the + * table name. + * + * @author Alex Shvid + */ +public class BasicCassandraPersistentEntity extends BasicPersistentEntity implements + CassandraPersistentEntity, ApplicationContextAware { + + private final String table; + private final SpelExpressionParser parser; + private final StandardEvaluationContext context; + + /** + * Creates a new {@link BasicCassandraPersistentEntity} with the given {@link TypeInformation}. Will default the table + * name to the entities simple type name. + * + * @param typeInformation + */ + public BasicCassandraPersistentEntity(TypeInformation typeInformation) { + + super(typeInformation, CassandraPersistentPropertyComparator.INSTANCE); + + this.parser = new SpelExpressionParser(); + this.context = new StandardEvaluationContext(); + + Class rawType = typeInformation.getType(); + String fallback = CassandraNamingUtils.getPreferredTableName(rawType); + + if (rawType.isAnnotationPresent(Table.class)) { + Table d = rawType.getAnnotation(Table.class); + this.table = StringUtils.hasText(d.name()) ? d.name() : fallback; + } else { + this.table = fallback; + } + } + + /* + * (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + + context.addPropertyAccessor(new BeanFactoryAccessor()); + context.setBeanResolver(new BeanFactoryResolver(applicationContext)); + context.setRootObject(applicationContext); + } + + /** + * Returns the table the entity shall be persisted to. + * + * @return + */ + public String getTable() { + Expression expression = parser.parseExpression(table, ParserContext.TEMPLATE_EXPRESSION); + return expression.getValue(context, String.class); + } + + /** + * {@link Comparator} implementation inspecting the {@link CassandraPersistentProperty}'s order. + * + * @author Alex Shvid + */ + static enum CassandraPersistentPropertyComparator implements Comparator { + + INSTANCE; + + /* + * (non-Javadoc) + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + public int compare(CassandraPersistentProperty o1, CassandraPersistentProperty o2) { + + if (o1.isColumnId()) { + return 1; + } + + if (o2.isColumnId()) { + return -1; + } + + return o1.getColumnName().compareTo(o2.getColumnName()); + + } + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentProperty.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentProperty.java new file mode 100644 index 000000000..c77b95204 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentProperty.java @@ -0,0 +1,188 @@ +/* + * Copyright 2011-2012 the original author or authors. + * + * 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.springframework.data.cassandra.mapping; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Set; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.mapping.Association; +import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; +import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.util.TypeInformation; +import org.springframework.util.StringUtils; + +import com.datastax.driver.core.DataType; + +/** + * Cassandra specific {@link org.springframework.data.mapping.model.AnnotationBasedPersistentProperty} implementation. + * + * @author Alex Shvid + */ +public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentProperty + implements CassandraPersistentProperty { + + /** + * Creates a new {@link BasicCassandraPersistentProperty}. + * + * @param field + * @param propertyDescriptor + * @param owner + * @param simpleTypeHolder + */ + public BasicCassandraPersistentProperty(Field field, PropertyDescriptor propertyDescriptor, + CassandraPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { + super(field, propertyDescriptor, owner, simpleTypeHolder); + } + + /** + * Also considers fields that has a RowId annotation. + * + */ + @Override + public boolean isIdProperty() { + + if (super.isIdProperty()) { + return true; + } + + return getField().isAnnotationPresent(RowId.class); + } + + /** + * For dynamic tables returns true if property value is used as column name. + * + * @return + */ + public boolean isColumnId() { + return getField().isAnnotationPresent(ColumnId.class); + } + + /** + * Returns the column name to be used to store the value of the property inside the Cassandra. + * + * @return + */ + public String getColumnName() { + Column annotation = getField().getAnnotation(Column.class); + return annotation != null && StringUtils.hasText(annotation.value()) ? annotation.value() : field.getName(); + } + + /** + * Returns the data type information if exists. + * + * @return + */ + public DataType getDataType() { + Qualify annotation = getField().getAnnotation(Qualify.class); + if (annotation != null && annotation.type() != null) { + return qualifyAnnotatedType(annotation); + } + if (isMap()) { + List> args = getTypeInformation().getTypeArguments(); + ensureTypeArguments(args.size(), 2); + return DataType.map(autodetectPrimitiveType(args.get(0).getType()), + autodetectPrimitiveType(args.get(1).getType())); + } + if (isCollectionLike()) { + List> args = getTypeInformation().getTypeArguments(); + ensureTypeArguments(args.size(), 1); + if (Set.class.isAssignableFrom(getType())) { + return DataType.set(autodetectPrimitiveType(args.get(0).getType())); + } else if (List.class.isAssignableFrom(getType())) { + return DataType.list(autodetectPrimitiveType(args.get(0).getType())); + } + } + DataType dataType = CassandraSimpleTypes.autodetectPrimitive(this.getType()); + if (dataType == null) { + throw new InvalidDataAccessApiUsageException( + "only primitive types and Set,List,Map collections are allowed, unknown type for property '" + this.getName() + + "' type is '" + this.getType() + "' in the entity " + this.getOwner().getName()); + } + return dataType; + } + + private DataType qualifyAnnotatedType(Qualify annotation) { + DataType.Name type = annotation.type(); + if (type.isCollection()) { + switch (type) { + case MAP: + ensureTypeArguments(annotation.typeArguments().length, 2); + return DataType.map(resolvePrimitiveType(annotation.typeArguments()[0]), + resolvePrimitiveType(annotation.typeArguments()[1])); + case LIST: + ensureTypeArguments(annotation.typeArguments().length, 1); + return DataType.list(resolvePrimitiveType(annotation.typeArguments()[0])); + case SET: + ensureTypeArguments(annotation.typeArguments().length, 1); + return DataType.set(resolvePrimitiveType(annotation.typeArguments()[0])); + default: + throw new InvalidDataAccessApiUsageException("unknown collection DataType for property '" + this.getName() + + "' type is '" + this.getType() + "' in the entity " + this.getOwner().getName()); + } + } else { + return CassandraSimpleTypes.resolvePrimitive(type); + } + } + + /** + * Returns true if the property has secondary index on this column. + * + * @return + */ + public boolean isIndexed() { + return getField().isAnnotationPresent(Index.class); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.AbstractPersistentProperty#createAssociation() + */ + @Override + protected Association createAssociation() { + return new Association(this, null); + } + + DataType resolvePrimitiveType(DataType.Name typeName) { + DataType dataType = CassandraSimpleTypes.resolvePrimitive(typeName); + if (dataType == null) { + throw new InvalidDataAccessApiUsageException( + "only primitive types are allowed inside collections for the property '" + this.getName() + "' type is '" + + this.getType() + "' in the entity " + this.getOwner().getName()); + } + return dataType; + } + + DataType autodetectPrimitiveType(Class javaType) { + DataType dataType = CassandraSimpleTypes.autodetectPrimitive(javaType); + if (dataType == null) { + throw new InvalidDataAccessApiUsageException( + "only primitive types are allowed inside collections for the property '" + this.getName() + "' type is '" + + this.getType() + "' in the entity " + this.getOwner().getName()); + } + return dataType; + } + + void ensureTypeArguments(int args, int expected) { + if (args != expected) { + throw new InvalidDataAccessApiUsageException("expected " + expected + " of typed arguments for the property '" + + this.getName() + "' type is '" + this.getType() + "' in the entity " + this.getOwner().getName()); + } + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CachingCassandraPersistentProperty.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CachingCassandraPersistentProperty.java new file mode 100644 index 000000000..0e59b5318 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CachingCassandraPersistentProperty.java @@ -0,0 +1,103 @@ +/* + * Copyright 2011-2012 the original author or authors. + * + * 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.springframework.data.cassandra.mapping; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; + +import org.springframework.data.mapping.model.SimpleTypeHolder; + +/** + * {@link CassandraPersistentProperty} caching access to {@link #isIdProperty()} and {@link #getColumnName()}. + * + * @author Alex Shvid + */ +public class CachingCassandraPersistentProperty extends BasicCassandraPersistentProperty { + + private Boolean isIdProperty; + private Boolean isColumnId; + private String columnName; + private Boolean isIndexed; + + /** + * Creates a new {@link CachingCassandraPersistentProperty}. + * + * @param field + * @param propertyDescriptor + * @param owner + * @param simpleTypeHolder + */ + public CachingCassandraPersistentProperty(Field field, PropertyDescriptor propertyDescriptor, + CassandraPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { + super(field, propertyDescriptor, owner, simpleTypeHolder); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.mapping.BasicCassandraPersistentProperty#isIdProperty() + */ + @Override + public boolean isIdProperty() { + + if (this.isIdProperty == null) { + this.isIdProperty = super.isIdProperty(); + } + + return this.isIdProperty; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.mapping.BasicCassandraPersistentProperty#isColumnId() + */ + @Override + public boolean isColumnId() { + + if (this.isColumnId == null) { + this.isColumnId = super.isColumnId(); + } + + return this.isColumnId; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.mapping.BasicCassandraPersistentProperty#getFieldName() + */ + @Override + public String getColumnName() { + + if (this.columnName == null) { + this.columnName = super.getColumnName(); + } + + return this.columnName; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.mapping.BasicCassandraPersistentProperty#isIndexed() + */ + @Override + public boolean isIndexed() { + + if (this.isIndexed == null) { + this.isIndexed = super.isIndexed(); + } + + return this.isIndexed; + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraMappingContext.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraMappingContext.java new file mode 100644 index 000000000..ec59a044c --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraMappingContext.java @@ -0,0 +1,85 @@ +/* + * Copyright 2011-2012 the original author or authors. + * + * 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.springframework.data.cassandra.mapping; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.data.mapping.context.AbstractMappingContext; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.util.TypeInformation; + +/** + * Default implementation of a {@link MappingContext} for Cassandra using {@link BasicCassandraPersistentEntity} and + * {@link BasicCassandraPersistentProperty} as primary abstractions. + * + * @author Alex Shvid + */ +public class CassandraMappingContext extends + AbstractMappingContext, CassandraPersistentProperty> implements + ApplicationContextAware { + + private ApplicationContext context; + + /** + * Creates a new {@link CassandraMappingContext}. + */ + public CassandraMappingContext() { + setSimpleTypeHolder(CassandraSimpleTypes.HOLDER); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.AbstractMappingContext#createPersistentProperty(java.lang.reflect.Field, java.beans.PropertyDescriptor, org.springframework.data.mapping.MutablePersistentEntity, org.springframework.data.mapping.SimpleTypeHolder) + */ + @Override + public CassandraPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, + BasicCassandraPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { + return new CachingCassandraPersistentProperty(field, descriptor, owner, simpleTypeHolder); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.BasicMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation, org.springframework.data.mapping.model.MappingContext) + */ + @Override + protected BasicCassandraPersistentEntity createPersistentEntity(TypeInformation typeInformation) { + + BasicCassandraPersistentEntity entity = new BasicCassandraPersistentEntity(typeInformation); + + if (context != null) { + entity.setApplicationContext(context); + } + + return entity; + } + + /* + * (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + + this.context = applicationContext; + super.setApplicationContext(applicationContext); + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentEntity.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentEntity.java new file mode 100644 index 000000000..1c39ed007 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentEntity.java @@ -0,0 +1,34 @@ +/* + * Copyright 2011-2012 the original author or authors. + * + * 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.springframework.data.cassandra.mapping; + +import org.springframework.data.mapping.PersistentEntity; + +/** + * Cassandra specific {@link PersistentEntity} abstraction. + * + * @author Alex Shvid + */ +public interface CassandraPersistentEntity extends PersistentEntity { + + /** + * Returns the table the entity shall be persisted to. + * + * @return + */ + String getTable(); + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentProperty.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentProperty.java new file mode 100644 index 000000000..cdb98d38b --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentProperty.java @@ -0,0 +1,57 @@ +/* + * Copyright 2011-2012 the original author or authors. + * + * 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.springframework.data.cassandra.mapping; + +import org.springframework.data.mapping.PersistentProperty; + +import com.datastax.driver.core.DataType; + +/** + * Cassandra specific {@link org.springframework.data.mapping.PersistentProperty} extension. + * + * @author Alex Shvid + */ +public interface CassandraPersistentProperty extends PersistentProperty { + + /** + * For dynamic tables returns true if property value is used as column name. + * + * @return + */ + boolean isColumnId(); + + /** + * Returns the name of the field a property is persisted to. + * + * @return + */ + String getColumnName(); + + /** + * Returns the data type. + * + * @return + */ + DataType getDataType(); + + /** + * Returns true if the property has secondary index on this column. + * + * @return + */ + boolean isIndexed(); + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraSimpleTypes.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraSimpleTypes.java new file mode 100644 index 000000000..b03537275 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraSimpleTypes.java @@ -0,0 +1,98 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.data.cassandra.mapping; + +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 org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.util.TypeInformation; + +import com.datastax.driver.core.DataType; + +/** + * Simple constant holder for a {@link SimpleTypeHolder} enriched with Cassandra specific simple types. + * + * @author Alex Shvid + */ +public class CassandraSimpleTypes { + + private static final Map, Class> primitiveWrapperTypeMap = new HashMap, Class>(8); + + private static final Map, DataType> javaClassToDataType = new HashMap, DataType>(); + + private static final Map nameToDataType = new HashMap(); + + static { + + primitiveWrapperTypeMap.put(Boolean.class, boolean.class); + primitiveWrapperTypeMap.put(Byte.class, byte.class); + primitiveWrapperTypeMap.put(Character.class, char.class); + primitiveWrapperTypeMap.put(Double.class, double.class); + primitiveWrapperTypeMap.put(Float.class, float.class); + primitiveWrapperTypeMap.put(Integer.class, int.class); + primitiveWrapperTypeMap.put(Long.class, long.class); + primitiveWrapperTypeMap.put(Short.class, short.class); + + Set> simpleTypes = new HashSet>(); + for (DataType dataType : DataType.allPrimitiveTypes()) { + simpleTypes.add(dataType.asJavaClass()); + Class javaClass = dataType.asJavaClass(); + javaClassToDataType.put(javaClass, dataType); + Class primitiveJavaClass = primitiveWrapperTypeMap.get(javaClass); + if (primitiveJavaClass != null) { + javaClassToDataType.put(primitiveJavaClass, dataType); + } + nameToDataType.put(dataType.getName(), dataType); + } + javaClassToDataType.put(String.class, DataType.text()); + CASSANDRA_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes); + } + + private static final Set> CASSANDRA_SIMPLE_TYPES; + public static final SimpleTypeHolder HOLDER = new SimpleTypeHolder(CASSANDRA_SIMPLE_TYPES, true); + + private CassandraSimpleTypes() { + } + + public static DataType resolvePrimitive(DataType.Name name) { + return nameToDataType.get(name); + } + + public static DataType autodetectPrimitive(Class javaClass) { + return javaClassToDataType.get(javaClass); + } + + public static DataType.Name[] convertPrimitiveTypeArguments(List> arguments) { + DataType.Name[] result = new DataType.Name[arguments.size()]; + for (int i = 0; i != result.length; ++i) { + TypeInformation type = arguments.get(i); + DataType dataType = autodetectPrimitive(type.getType()); + if (dataType == null) { + throw new InvalidDataAccessApiUsageException("not found appropriate primitive DataType for type = '" + + type.getType()); + } + result[i] = dataType.getName(); + } + return result; + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Column.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Column.java new file mode 100644 index 000000000..eb87656d5 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Column.java @@ -0,0 +1,23 @@ +package org.springframework.data.cassandra.mapping; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Annotation to define custom metadata for document fields. + * + * @author Alex Shvid + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Column { + + /** + * The name of the column in the table. + * + * @return + */ + String value() default ""; + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/ColumnId.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/ColumnId.java new file mode 100644 index 000000000..36a9aa3d9 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/ColumnId.java @@ -0,0 +1,33 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.data.cassandra.mapping; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Uses in dynamic tables where column names are values of this field. Usually it is a Date/Time field or UUIDTime + * field. + * + * @author Alex Shvid + */ +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +public @interface ColumnId { + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CompositeRowId.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CompositeRowId.java new file mode 100644 index 000000000..2ceca4e1d --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CompositeRowId.java @@ -0,0 +1,44 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.data.cassandra.mapping; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.data.annotation.Id; + +/** + * Identifies composite row ID in the Cassandra table that contains several fields. Same as + * @org.springframework.data.annotation.Id + * + * Example: + * + * class AccountPK { String account; String region; } + * + * @Table class Account { + * @CompositeRowId Account pk; } + * + * + * @author Alex Shvid + */ +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Id +public @interface CompositeRowId { + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DataTypeInformation.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DataTypeInformation.java new file mode 100644 index 000000000..51e71f5d0 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DataTypeInformation.java @@ -0,0 +1,74 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.data.cassandra.mapping; + +import com.datastax.driver.core.DataType; + +/** + * Uses to transfer DataType and attributes for the property. + * + * @author Alex Shvid + */ +public class DataTypeInformation { + + public static DataType.Name[] EMPTY_ATTRIBUTES = {}; + + private DataType.Name typeName; + private DataType.Name[] typeAttributes; + + public DataTypeInformation(DataType.Name typeName) { + this(typeName, EMPTY_ATTRIBUTES); + } + + public DataTypeInformation(DataType.Name typeName, DataType.Name[] typeAttributes) { + this.typeName = typeName; + this.typeAttributes = typeAttributes; + } + + public DataType.Name getTypeName() { + return typeName; + } + + public void setTypeName(DataType.Name typeName) { + this.typeName = typeName; + } + + public DataType.Name[] getTypeAttributes() { + return typeAttributes; + } + + public void setTypeAttributes(DataType.Name[] typeAttributes) { + this.typeAttributes = typeAttributes; + } + + public String toCQL() { + if (typeAttributes.length == 0) { + return typeName.name(); + } else { + StringBuilder str = new StringBuilder(); + str.append(typeName.name()); + str.append('<'); + for (int i = 0; i != typeAttributes.length; ++i) { + if (i != 0) { + str.append(','); + } + str.append(typeAttributes[i].name()); + } + str.append('>'); + return str.toString(); + } + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Index.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Index.java new file mode 100644 index 000000000..bc60d8121 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Index.java @@ -0,0 +1,35 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.data.cassandra.mapping; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Identifies a secondary index in the table. Usually it is a field with common dublicate values for the hole table. + * such as city, place, educationType, state flags ant etc. + * + * Using unique fields is not common and has overhead, such as email, username and etc. + * + * @author Alex Shvid + */ +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +public @interface Index { + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Qualify.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Qualify.java new file mode 100644 index 000000000..5c6772762 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Qualify.java @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.data.cassandra.mapping; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import com.datastax.driver.core.DataType; + +/** + * Qualifies data type as Cassandra type. + * + * @author Alex Shvid + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Qualify { + + DataType.Name type(); + + DataType.Name[] typeArguments() default {}; + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/RowId.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/RowId.java new file mode 100644 index 000000000..10ef7608a --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/RowId.java @@ -0,0 +1,34 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.data.cassandra.mapping; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.data.annotation.Id; + +/** + * Identifies row ID in the Cassandra table. Same as @org.springframework.data.annotation.Id + * + * @author Alex Shvid + */ +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Id +public @interface RowId { +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Table.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Table.java new file mode 100644 index 000000000..c875102af --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Table.java @@ -0,0 +1,39 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.data.cassandra.mapping; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.data.annotation.Persistent; + +/** + * Identifies a domain object to be persisted to Cassandra as a table. + * + * @author Alex Shvid + */ +@Persistent +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE }) +public @interface Table { + + String name() default ""; + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/CassandraRepository.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/CassandraRepository.java new file mode 100644 index 000000000..a2c245c6e --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/CassandraRepository.java @@ -0,0 +1,29 @@ +/* + * Copyright 2011-2012 the original author or authors. + * + * 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.springframework.data.cassandra.repository; + +import java.io.Serializable; + +import org.springframework.data.repository.CrudRepository; + +/** + * Cassandra-specific extension of the {@link CrudRepository} interface. + * + * @author Alex Shvid + */ +public interface CassandraRepository extends CrudRepository { + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/Query.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/Query.java new file mode 100644 index 000000000..4098ebbf0 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/Query.java @@ -0,0 +1,42 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.repository; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to declare finder queries directly on repository methods. + * + * @author Alex Shvid + */ + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Documented +public @interface Query { + + /** + * Takes a Cassandra CQL3 string to define the actual query to be executed. + * + * @return + */ + String value() default ""; + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/util/CassandraNamingUtils.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/util/CassandraNamingUtils.java new file mode 100644 index 000000000..4e752568e --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/util/CassandraNamingUtils.java @@ -0,0 +1,36 @@ +/* + * Copyright 2011 the original author or authors. + * + * 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.springframework.data.cassandra.util; + +/** + * Helper class featuring helper methods for working with Cassandra tables. Mainly intended for internal use within the + * framework. + * + * @author Alex Shvid + */ +public abstract class CassandraNamingUtils { + + /** + * Obtains the table name to use for the provided class + * + * @param entityClass The class to determine the preferred table name for + * @return The preferred collection name + */ + public static String getPreferredTableName(Class entityClass) { + return entityClass.getSimpleName().toLowerCase(); + } + +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/util/CqlUtils.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/util/CqlUtils.java new file mode 100644 index 000000000..3781d47e9 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/util/CqlUtils.java @@ -0,0 +1,467 @@ +package org.springframework.data.cassandra.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.cassandra.core.ConsistencyLevel; +import org.springframework.data.cassandra.core.ConsistencyLevelResolver; +import org.springframework.data.cassandra.core.QueryOptions; +import org.springframework.data.cassandra.core.RetryPolicy; +import org.springframework.data.cassandra.core.RetryPolicyResolver; +import org.springframework.data.cassandra.exception.EntityWriterException; +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.convert.EntityWriter; +import org.springframework.data.mapping.PropertyHandler; + +import com.datastax.driver.core.ColumnMetadata; +import com.datastax.driver.core.DataType; +import com.datastax.driver.core.Query; +import com.datastax.driver.core.Statement; +import com.datastax.driver.core.TableMetadata; +import com.datastax.driver.core.querybuilder.Batch; +import com.datastax.driver.core.querybuilder.Delete; +import com.datastax.driver.core.querybuilder.Delete.Where; +import com.datastax.driver.core.querybuilder.Insert; +import com.datastax.driver.core.querybuilder.QueryBuilder; +import com.datastax.driver.core.querybuilder.Update; + +/** + * Utilties to convert Cassandra Annotated objects to Queries and CQL. + * + * @author Alex Shvid + * @author David Webb + * + */ +public abstract class CqlUtils { + + private static Logger log = LoggerFactory.getLogger(CqlUtils.class); + + /** + * Generates the CQL String to create a table in Cassandra + * + * @param tableName + * @param entity + * @return The CQL that can be passed to session.execute() + */ + public static String createTable(String tableName, final CassandraPersistentEntity entity) { + + final StringBuilder str = new StringBuilder(); + str.append("CREATE TABLE "); + str.append(tableName); + str.append('('); + + final List ids = new ArrayList(); + final List idColumns = new ArrayList(); + + entity.doWithProperties(new PropertyHandler() { + public void doWithPersistentProperty(CassandraPersistentProperty prop) { + + if (str.charAt(str.length() - 1) != '(') { + str.append(','); + } + + String columnName = prop.getColumnName(); + + str.append(columnName); + str.append(' '); + + DataType dataType = prop.getDataType(); + + str.append(toCQL(dataType)); + + if (prop.isIdProperty()) { + ids.add(prop.getColumnName()); + } + + if (prop.isColumnId()) { + idColumns.add(prop.getColumnName()); + } + + } + + }); + + if (ids.isEmpty()) { + throw new InvalidDataAccessApiUsageException("not found primary ID in the entity " + entity.getType()); + } + + str.append(",PRIMARY KEY("); + + // if (ids.size() > 1) { + // str.append('('); + // } + + for (String id : ids) { + if (str.charAt(str.length() - 1) != '(') { + str.append(','); + } + str.append(id); + } + + // if (ids.size() > 1) { + // str.append(')'); + // } + + for (String id : idColumns) { + str.append(','); + str.append(id); + } + + str.append("));"); + + return str.toString(); + } + + /** + * Create the List of CQL for the indexes required for Cassandra mapped Table. + * + * @param tableName + * @param entity + * @return The list of CQL statements to run with session.execute() + */ + public static List createIndexes(final String tableName, final CassandraPersistentEntity entity) { + final List result = new ArrayList(); + + entity.doWithProperties(new PropertyHandler() { + public void doWithPersistentProperty(CassandraPersistentProperty prop) { + + if (prop.isIndexed()) { + + final StringBuilder str = new StringBuilder(); + str.append("CREATE INDEX ON "); + str.append(tableName); + str.append(" ("); + str.append(prop.getColumnName()); + str.append(");"); + + result.add(str.toString()); + } + + } + }); + + return result; + } + + /** + * Alter the table to refelct the entity annotations + * + * @param tableName + * @param entity + * @param table + * @return + */ + public static List alterTable(final String tableName, final CassandraPersistentEntity entity, + final TableMetadata table) { + final List result = new ArrayList(); + + entity.doWithProperties(new PropertyHandler() { + public void doWithPersistentProperty(CassandraPersistentProperty prop) { + + String columnName = prop.getColumnName(); + DataType columnDataType = prop.getDataType(); + ColumnMetadata columnMetadata = table.getColumn(columnName.toLowerCase()); + + if (columnMetadata != null && columnDataType.equals(columnMetadata.getType())) { + return; + } + + final StringBuilder str = new StringBuilder(); + str.append("ALTER TABLE "); + str.append(tableName); + if (columnMetadata == null) { + str.append(" ADD "); + } else { + str.append(" ALTER "); + } + + str.append(columnName); + str.append(' '); + + if (columnMetadata != null) { + str.append("TYPE "); + } + + str.append(toCQL(columnDataType)); + + str.append(';'); + result.add(str.toString()); + + } + }); + + return result; + } + + /** + * Generates a Query Object for an insert + * + * @param keyspaceName + * @param tableName + * @param objectToSave + * @param entity + * @param optionsByName + * + * @return The Query object to run with session.execute(); + * @throws EntityWriterException + */ + public static Query toInsertQuery(String keyspaceName, String tableName, final Object objectToSave, + Map optionsByName, EntityWriter entityWriter) throws EntityWriterException { + + final Insert q = QueryBuilder.insertInto(keyspaceName, tableName); + + /* + * Write properties + */ + entityWriter.write(objectToSave, q); + + /* + * Add Query Options + */ + addQueryOptions(q, optionsByName); + + /* + * Add TTL to Insert object + */ + if (optionsByName.get(QueryOptions.QueryOptionMapKeys.TTL) != null) { + q.using(QueryBuilder.ttl((Integer) optionsByName.get(QueryOptions.QueryOptionMapKeys.TTL))); + } + + return q; + + } + + /** + * Generates a Query Object for an Update + * + * @param keyspaceName + * @param tableName + * @param objectToSave + * @param entity + * @param optionsByName + * + * @return The Query object to run with session.execute(); + * @throws EntityWriterException + */ + public static Query toUpdateQuery(String keyspaceName, String tableName, final Object objectToSave, + Map optionsByName, EntityWriter entityWriter) throws EntityWriterException { + + final Update q = QueryBuilder.update(keyspaceName, tableName); + + /* + * Write properties + */ + entityWriter.write(objectToSave, q); + + /* + * Add Query Options + */ + addQueryOptions(q, optionsByName); + + /* + * Add TTL to Insert object + */ + if (optionsByName.get(QueryOptions.QueryOptionMapKeys.TTL) != null) { + q.using(QueryBuilder.ttl((Integer) optionsByName.get(QueryOptions.QueryOptionMapKeys.TTL))); + } + + return q; + + } + + /** + * Generates a Batch Object for multiple Updates + * + * @param keyspaceName + * @param tableName + * @param objectsToSave + * @param entity + * @param optionsByName + * + * @return The Query object to run with session.execute(); + * @throws EntityWriterException + */ + public static Batch toUpdateBatchQuery(final String keyspaceName, final String tableName, + final List objectsToSave, Map optionsByName, EntityWriter entityWriter) + throws EntityWriterException { + + /* + * Return variable is a Batch statement + */ + final Batch b = QueryBuilder.batch(); + + for (final T objectToSave : objectsToSave) { + + b.add((Statement) toUpdateQuery(keyspaceName, tableName, objectToSave, optionsByName, entityWriter)); + + } + + addQueryOptions(b, optionsByName); + + return b; + + } + + /** + * Generates a Batch Object for multiple inserts + * + * @param keyspaceName + * @param tableName + * @param objectsToSave + * @param entity + * @param optionsByName + * + * @return The Query object to run with session.execute(); + * @throws EntityWriterException + */ + public static Batch toInsertBatchQuery(final String keyspaceName, final String tableName, + final List objectsToSave, Map optionsByName, EntityWriter entityWriter) + throws EntityWriterException { + + /* + * Return variable is a Batch statement + */ + final Batch b = QueryBuilder.batch(); + + for (final T objectToSave : objectsToSave) { + + b.add((Statement) toInsertQuery(keyspaceName, tableName, objectToSave, optionsByName, entityWriter)); + + } + + addQueryOptions(b, optionsByName); + + return b; + + } + + /** + * Create a Delete Query Object from an annotated POJO + * + * @param keyspace + * @param tableName + * @param objectToRemove + * @param entity + * @param optionsByName + * @return + * @throws EntityWriterException + */ + public static Query toDeleteQuery(String keyspace, String tableName, final Object objectToRemove, + Map optionsByName, EntityWriter entityWriter) throws EntityWriterException { + + final Delete.Selection ds = QueryBuilder.delete(); + final Delete q = ds.from(keyspace, tableName); + final Where w = q.where(); + + /* + * Write where condition to find by Id + */ + entityWriter.write(objectToRemove, w); + + addQueryOptions(q, optionsByName); + + return q; + + } + + /** + * @param dataType + * @return + */ + public static String toCQL(DataType dataType) { + if (dataType.getTypeArguments().isEmpty()) { + return dataType.getName().name(); + } else { + StringBuilder str = new StringBuilder(); + str.append(dataType.getName().name()); + str.append('<'); + for (DataType argDataType : dataType.getTypeArguments()) { + if (str.charAt(str.length() - 1) != '<') { + str.append(','); + } + str.append(argDataType.getName().name()); + } + str.append('>'); + return str.toString(); + } + } + + /** + * @param tableName + * @return + */ + public static String dropTable(String tableName) { + + if (tableName == null) { + return null; + } + + StringBuilder str = new StringBuilder(); + str.append("DROP TABLE " + tableName + ";"); + return str.toString(); + } + + /** + * Create a Batch Query object for multiple deletes. + * + * @param keyspace + * @param tableName + * @param entities + * @param entity + * @param optionsByName + * + * @return + * @throws EntityWriterException + */ + public static Batch toDeleteBatchQuery(String keyspaceName, String tableName, List entities, + Map optionsByName, EntityWriter entityWriter) throws EntityWriterException { + + /* + * Return variable is a Batch statement + */ + final Batch b = QueryBuilder.batch(); + + for (final T objectToSave : entities) { + + b.add((Statement) toDeleteQuery(keyspaceName, tableName, objectToSave, optionsByName, entityWriter)); + + } + + addQueryOptions(b, optionsByName); + + return b; + + } + + /** + * Add common Query options for all types of queries. + * + * @param q + * @param optionsByName + */ + private static void addQueryOptions(Query q, Map optionsByName) { + + if (optionsByName == null) { + return; + } + + /* + * Add Query Options + */ + if (optionsByName.get(QueryOptions.QueryOptionMapKeys.CONSISTENCY_LEVEL) != null) { + q.setConsistencyLevel(ConsistencyLevelResolver.resolve((ConsistencyLevel) optionsByName + .get(QueryOptions.QueryOptionMapKeys.CONSISTENCY_LEVEL))); + } + if (optionsByName.get(QueryOptions.QueryOptionMapKeys.RETRY_POLICY) != null) { + q.setRetryPolicy(RetryPolicyResolver.resolve((RetryPolicy) optionsByName + .get(QueryOptions.QueryOptionMapKeys.RETRY_POLICY))); + } + + } + +} diff --git a/spring-data-cassandra/src/main/resources/META-INF/spring.handlers b/spring-data-cassandra/src/main/resources/META-INF/spring.handlers new file mode 100644 index 000000000..8a812d6ba --- /dev/null +++ b/spring-data-cassandra/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.springframework.org/schema/data/cassandra=org.springframework.data.cassandra.config.CassandraNamespaceHandler diff --git a/spring-data-cassandra/src/main/resources/META-INF/spring.schemas b/spring-data-cassandra/src/main/resources/META-INF/spring.schemas new file mode 100644 index 000000000..7cd18a56c --- /dev/null +++ b/spring-data-cassandra/src/main/resources/META-INF/spring.schemas @@ -0,0 +1,2 @@ +http\://www.springframework.org/schema/data/cassandra/spring-cassandra-1.0.xsd=org/springframework/data/cassandra/config/spring-cassandra-1.0.xsd +http\://www.springframework.org/schema/data/cassandra/spring-cassandra.xsd=org/springframework/data/cassandra/config/spring-cassandra-1.0.xsd \ No newline at end of file diff --git a/spring-data-cassandra/src/main/resources/META-INF/spring.tooling b/spring-data-cassandra/src/main/resources/META-INF/spring.tooling new file mode 100644 index 000000000..3769be0bd --- /dev/null +++ b/spring-data-cassandra/src/main/resources/META-INF/spring.tooling @@ -0,0 +1,4 @@ +# Tooling related information for the cassandra namespace +http\://www.springframework.org/schema/data/cassandra@name=Cassandra Namespace +http\://www.springframework.org/schema/data/cassandra@prefix=cassandra +http\://www.springframework.org/schema/data/cassandra@icon=org/springframework/data/cassandra/config/spring-cassandra.gif diff --git a/spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra-1.0.xsd b/spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra-1.0.xsd new file mode 100644 index 000000000..72094d834 --- /dev/null +++ b/spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra-1.0.xsd @@ -0,0 +1,454 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The name of the Cassandra Cluster definition (by + default "cassandra-cluster") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The name of the Keyspace definition (by default + "cassandra-keyspace") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The name of the Session definition (by default + "cassandra-session") + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra.gif b/spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra.gif new file mode 100644 index 0000000000000000000000000000000000000000..20ed1f9a4438054835c3bd7231c59dcc36d9f24e GIT binary patch literal 581 zcmZ?wbhEHb6krfwc*ekR>cRs}r)GW6W=W@M$1Xhi{Pm|(La%4WG|h-<))@;Tnzydr ze|7(^se4|>h4R zUy{Z383{M%q|7Yz$#T`0m|!y{*)GRZ@9L!3yxD&uuMPIl1|0Luj6Z zdPkV$k=o$-X|CH!1CBLB?5vH+vQyt$61=G-B*R91O}5{ryoi-KQOi@qrbqb9j0v0; z5;QF^;Q#;s3^WFcKUo+V7~&apK=y#*gn@lgLwr+nOKUS18yg1`6IWXkmxPoeFOQ^9 zUmMe;Dbs|Q`WZ#VWF+Oqg&7ylnJUT8uzIpIkARk*zGf@q8bK!uB^^Jrmfe#@w3X~5 zjco%=nvW{7>YA$?xVgIcdp2DZF^sU&u#O1|bhtZ*RXNt(TP-*$)Z^}A1#USb$B=N< rFkfeu(hb`mG&f7x?8#9)=yFn#m0d_d!a entity = new BasicCassandraPersistentEntity( + ClassTypeInformation.from(Notification.class)); + assertThat(entity.getTable(), is("messages")); + } + + @Test + public void evaluatesSpELExpression() { + + BasicCassandraPersistentEntity entity = new BasicCassandraPersistentEntity( + ClassTypeInformation.from(Area.class)); + assertThat(entity.getTable(), is("123")); + } + + @Test + public void collectionAllowsReferencingSpringBean() { + + MappingBean bean = new MappingBean(); + bean.userLine = "user_line"; + + when(context.getBean("mappingBean")).thenReturn(bean); + when(context.containsBean("mappingBean")).thenReturn(true); + + BasicCassandraPersistentEntity entity = new BasicCassandraPersistentEntity( + ClassTypeInformation.from(UserLine.class)); + entity.setApplicationContext(context); + + assertThat(entity.getTable(), is("user_line")); + } + + @After + public void clearCassandra() { + EmbeddedCassandraServerHelper.cleanEmbeddedCassandra(); + } + + @AfterClass + public static void stopCassandra() { + EmbeddedCassandraServerHelper.stopEmbeddedCassandra(); + } + + @Table(name = "messages") + class Message { + + } + + class Notification extends Message { + + } + + @Table(name = "#{123}") + class Area { + + } + + @Table(name = "#{mappingBean.userLine}") + class UserLine { + + } + + class MappingBean { + + String userLine; + + public String getUserLine() { + return userLine; + } + } + +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/BasicCassandraPersistentPropertyIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/BasicCassandraPersistentPropertyIntegrationTests.java new file mode 100644 index 000000000..dcc25c126 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/BasicCassandraPersistentPropertyIntegrationTests.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * 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.springframework.data.cassandra.test.integration.mapping; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Date; + +import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.thrift.transport.TTransportException; +import org.cassandraunit.utils.EmbeddedCassandraServerHelper; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.data.annotation.Id; +import org.springframework.data.cassandra.mapping.BasicCassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.BasicCassandraPersistentProperty; +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.cassandra.mapping.Column; +import org.springframework.data.cassandra.mapping.ColumnId; +import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.util.ReflectionUtils; + +/** + * Integration test for {@link BasicCassandraPersistentProperty}. + * + * @author Alex Shvid + */ +public class BasicCassandraPersistentPropertyIntegrationTests { + + CassandraPersistentEntity entity; + + @BeforeClass + public static void startCassandra() throws IOException, TTransportException, ConfigurationException, + InterruptedException { + EmbeddedCassandraServerHelper.startEmbeddedCassandra("cassandra.yaml"); + } + + @Before + public void setup() { + entity = new BasicCassandraPersistentEntity(ClassTypeInformation.from(Timeline.class)); + } + + @Test + public void usesAnnotatedColumnName() { + + Field field = ReflectionUtils.findField(Timeline.class, "text"); + assertThat(getPropertyFor(field).getColumnName(), is("message")); + } + + @Test + public void checksIdProperty() { + Field field = ReflectionUtils.findField(Timeline.class, "id"); + CassandraPersistentProperty property = getPropertyFor(field); + assertThat(property.isIdProperty(), is(true)); + } + + @Test + public void returnsPropertyNameForUnannotatedProperties() { + Field field = ReflectionUtils.findField(Timeline.class, "time"); + assertThat(getPropertyFor(field).getColumnName(), is("time")); + } + + @Test + public void checksColumnIdProperty() { + CassandraPersistentProperty property = getPropertyFor(ReflectionUtils.findField(Timeline.class, "time")); + assertThat(property.isColumnId(), is(true)); + } + + @After + public void clearCassandra() { + EmbeddedCassandraServerHelper.cleanEmbeddedCassandra(); + } + + @AfterClass + public static void stopCassandra() { + EmbeddedCassandraServerHelper.stopEmbeddedCassandra(); + } + + private CassandraPersistentProperty getPropertyFor(Field field) { + return new BasicCassandraPersistentProperty(field, null, entity, new SimpleTypeHolder()); + } + + class Timeline { + + @Id + String id; + + @ColumnId + Date time; + + @Column("message") + String text; + + } + +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Book.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Book.java new file mode 100644 index 000000000..b5e07e29f --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Book.java @@ -0,0 +1,104 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.test.integration.table; + +import org.springframework.data.cassandra.mapping.RowId; +import org.springframework.data.cassandra.mapping.Table; + +/** + * Test POJO + * + * @author David Webb + * + */ +@Table(name = "book") +public class Book { + + @RowId + private String isbn; + + private String title; + private String author; + private int pages; + + /** + * @return Returns the isbn. + */ + public String getIsbn() { + return isbn; + } + + /** + * @param isbn The isbn to set. + */ + public void setIsbn(String isbn) { + this.isbn = isbn; + } + + /** + * @return Returns the title. + */ + public String getTitle() { + return title; + } + + /** + * @param title The title to set. + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * @return Returns the author. + */ + public String getAuthor() { + return author; + } + + /** + * @param author The author to set. + */ + public void setAuthor(String author) { + this.author = author; + } + + /** + * @return Returns the pages. + */ + public int getPages() { + return pages; + } + + /** + * @param pages The pages to set. + */ + public void setPages(int pages) { + this.pages = pages; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("isbn -> " + isbn).append("\n"); + sb.append("tile -> " + title).append("\n"); + sb.append("author -> " + author).append("\n"); + sb.append("pages -> " + pages).append("\n"); + return sb.toString(); + } +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Comment.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Comment.java new file mode 100644 index 000000000..ff0fd3f6b --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Comment.java @@ -0,0 +1,112 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.data.cassandra.test.integration.table; + +import java.util.Date; +import java.util.Set; + +import org.springframework.data.annotation.Id; +import org.springframework.data.cassandra.mapping.ColumnId; +import org.springframework.data.cassandra.mapping.Qualify; +import org.springframework.data.cassandra.mapping.Table; + +import com.datastax.driver.core.DataType; + +/** + * This is an example of dynamic table that creates each time new column with Post timestamp annotated by @ColumnId. + * + * It is possible to use a static table for posts and identify them by PostId(UUID), but in this case we need to use + * MapReduce for Big Data to find posts for particular user, so it is better to have index (userId) -> index (post time) + * architecture. It helps a lot to build eventually a search index for the particular user. + * + * @author Alex Shvid + */ +@Table(name = "comments") +public class Comment { + + /* + * Primary Row ID + */ + @Id + private String author; + + /* + * Column ID + */ + @ColumnId + @Qualify(type = DataType.Name.TIMESTAMP) + private Date time; + + private String text; + + @Qualify(type = DataType.Name.SET, typeArguments = { DataType.Name.TEXT }) + private Set likes; + + /* + * Reference to the Post + */ + private String postAuthor; + private Date postTime; + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public Date getTime() { + return time; + } + + public void setTime(Date time) { + this.time = time; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public Set getLikes() { + return likes; + } + + public void setLikes(Set likes) { + this.likes = likes; + } + + public String getPostAuthor() { + return postAuthor; + } + + public void setPostAuthor(String postAuthor) { + this.postAuthor = postAuthor; + } + + public Date getPostTime() { + return postTime; + } + + public void setPostTime(Date postTime) { + this.postTime = postTime; + } + +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/LogEntry.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/LogEntry.java new file mode 100644 index 000000000..5797dec9f --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/LogEntry.java @@ -0,0 +1,89 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.data.cassandra.test.integration.table; + +import java.util.Date; + +import org.springframework.data.annotation.Id; +import org.springframework.data.cassandra.mapping.Column; +import org.springframework.data.cassandra.mapping.RowId; +import org.springframework.data.cassandra.mapping.Table; + +/** + * This is an example of the Users statis table, where all fields are columns in Cassandra row. Some fields can be + * Set,List,Map like emails. + * + * User contains base information related for separate user, like names, additional information, emails, following + * users, friends. + * + * @author Alex Shvid + */ +@Table(name = "log_entry") +public class LogEntry { + + /* + * Primary Row ID + */ + @RowId + private Date logDate; + + private String hostname; + + private String logData; + + /** + * @return Returns the logDate. + */ + public Date getLogDate() { + return logDate; + } + + /** + * @param logDate The logDate to set. + */ + public void setLogDate(Date logDate) { + this.logDate = logDate; + } + + /** + * @return Returns the hostname. + */ + public String getHostname() { + return hostname; + } + + /** + * @param hostname The hostname to set. + */ + public void setHostname(String hostname) { + this.hostname = hostname; + } + + /** + * @return Returns the logData. + */ + public String getLogData() { + return logData; + } + + /** + * @param logData The logData to set. + */ + public void setLogData(String logData) { + this.logData = logData; + } + +} \ No newline at end of file diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Notification.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Notification.java new file mode 100644 index 000000000..6bde2543d --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Notification.java @@ -0,0 +1,108 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.data.cassandra.test.integration.table; + +import java.util.Date; + +import org.springframework.data.annotation.Id; +import org.springframework.data.cassandra.mapping.ColumnId; +import org.springframework.data.cassandra.mapping.Index; +import org.springframework.data.cassandra.mapping.Table; + +/** + * This is an example of dynamic table that creates each time new column with Notification timestamp annotated by + * @ColumnId. + * + * By default it is active Notification until user deactivate it. This table uses index on the field active to access in + * WHERE cause only for active notifications. + * + * @author Alex Shvid + */ +@Table(name = "notifications") +public class Notification { + + /* + * Primary Row ID + */ + @Id + private String username; + + /* + * Column ID + */ + @ColumnId + private Date time; + + @Index + private boolean active; + + /* + * Reference data + */ + + private String type; // comment, post + private String refAuthor; + private Date refTime; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public Date getTime() { + return time; + } + + public void setTime(Date time) { + this.time = time; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getRefAuthor() { + return refAuthor; + } + + public void setRefAuthor(String refAuthor) { + this.refAuthor = refAuthor; + } + + public Date getRefTime() { + return refTime; + } + + public void setRefTime(Date refTime) { + this.refTime = refTime; + } + +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Post.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Post.java new file mode 100644 index 000000000..ea3de9f3d --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Post.java @@ -0,0 +1,122 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.data.cassandra.test.integration.table; + +import java.util.Date; +import java.util.Map; +import java.util.Set; + +import org.springframework.data.annotation.Id; +import org.springframework.data.cassandra.mapping.ColumnId; +import org.springframework.data.cassandra.mapping.Table; + +/** + * This is an example of dynamic table that creates each time new column with Post timestamp annotated by @ColumnId. + * + * It is possible to use a static table for posts and identify them by PostId(UUID), but in this case we need to use + * MapReduce for Big Data to find posts for particular user, so it is better to have index (userId) -> index (post time) + * architecture. It helps a lot to build eventually a search index for the particular user. + * + * @author Alex Shvid + */ +@Table(name = "posts") +public class Post { + + /* + * Primary Row ID + */ + @Id + private String author; + + /* + * Column ID + */ + @ColumnId + private Date time; + + private String type; // status, share + + private String text; + private Set resources; + private Map comments; + private Set likes; + private Set followers; + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public Date getTime() { + return time; + } + + public void setTime(Date time) { + this.time = time; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public Set getResources() { + return resources; + } + + public void setResources(Set resources) { + this.resources = resources; + } + + public Map getComments() { + return comments; + } + + public void setComments(Map comments) { + this.comments = comments; + } + + public Set getLikes() { + return likes; + } + + public void setLikes(Set likes) { + this.likes = likes; + } + + public Set getFollowers() { + return followers; + } + + public void setFollowers(Set followers) { + this.followers = followers; + } + +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Timeline.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Timeline.java new file mode 100644 index 000000000..ce670b0c1 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Timeline.java @@ -0,0 +1,86 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.data.cassandra.test.integration.table; + +import java.util.Date; + +import org.springframework.data.annotation.Id; +import org.springframework.data.cassandra.mapping.ColumnId; +import org.springframework.data.cassandra.mapping.Table; + +/** + * This is an example of the users timeline dynamic table, where all columns are dynamically created by @ColumnId field + * value. The rest fields are places in Cassandra value. + * + * Timeline entity is used to store user's status updates that it follows in the site. Timeline always ordered by @ColumnId + * field and we can retrieve last top status updates by using limits. + * + * @author Alex Shvid + */ +@Table(name = "timeline") +public class Timeline { + + /* + * Primary Row ID + */ + @Id + private String username; + + /* + * Column ID + */ + @ColumnId + private Date time; + + /* + * Reference to the post by author and postUID + */ + private String author; + private Date postTime; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public Date getTime() { + return time; + } + + public void setTime(Date time) { + this.time = time; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public Date getPostTime() { + return postTime; + } + + public void setPostTime(Date postTime) { + this.postTime = postTime; + } + +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/User.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/User.java new file mode 100644 index 000000000..91349c4ac --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/User.java @@ -0,0 +1,158 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.data.cassandra.test.integration.table; + +import java.util.Set; + +import org.springframework.data.annotation.Id; +import org.springframework.data.cassandra.mapping.Index; +import org.springframework.data.cassandra.mapping.Table; + +/** + * This is an example of the Users statis table, where all fields are columns in Cassandra row. Some fields can be + * Set,List,Map like emails. + * + * User contains base information related for separate user, like names, additional information, emails, following + * users, friends. + * + * @author Alex Shvid + */ +@Table(name = "users") +public class User { + + /* + * Primary Row ID + */ + @Id + private String username; + + /* + * Public information + */ + private String firstName; + private String lastName; + + /* + * Secondary index, used only on fields with common information, + * not effective on email, username + */ + @Index + private String place; + + /* + * User emails + */ + private Set emails; + + /* + * Password + */ + private String password; + + /* + * Age + */ + private int age; + + /* + * Following other users in userline + */ + private Set following; + + /* + * Friends of the user + */ + private Set friends; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getPlace() { + return place; + } + + public void setPlace(String place) { + this.place = place; + } + + public Set getEmails() { + return emails; + } + + public void setEmails(Set emails) { + this.emails = emails; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Set getFollowing() { + return following; + } + + public void setFollowing(Set following) { + this.following = following; + } + + public Set getFriends() { + return friends; + } + + public void setFriends(Set friends) { + this.friends = friends; + } + + /** + * @return Returns the age. + */ + public int getAge() { + return age; + } + + /** + * @param age The age to set. + */ + public void setAge(int age) { + this.age = age; + } + +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/UserAlter.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/UserAlter.java new file mode 100644 index 000000000..7baad7368 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/UserAlter.java @@ -0,0 +1,161 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * 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.springframework.data.cassandra.test.integration.table; + +import java.util.Set; + +import org.springframework.data.annotation.Id; +import org.springframework.data.cassandra.mapping.Index; +import org.springframework.data.cassandra.mapping.Table; + +/** + * This is an example of the Users statis table, where all fields are columns in Cassandra row. Some fields can be + * Set,List,Map like emails. + * + * User contains base information related for separate user, like names, additional information, emails, following + * users, friends. + * + * @author Alex Shvid + */ +@Table(name = "users") +public class UserAlter { + + /* + * Primary Row ID + */ + @Id + private String username; + + /* + * Public information + */ + private String firstName; + private String lastName; + + /* + * Secondary index, used only on fields with common information, + * not effective on email, username + */ + @Index + private String place; + + private String nickName; + + /* + * Password + */ + private String password; + + /* + * Age + */ + private int age; + + /* + * Following other users in userline + */ + private Set following; + + /* + * Friends of the user + */ + private Set friends; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getPlace() { + return place; + } + + public void setPlace(String place) { + this.place = place; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Set getFollowing() { + return following; + } + + public void setFollowing(Set following) { + this.following = following; + } + + public Set getFriends() { + return friends; + } + + public void setFriends(Set friends) { + this.friends = friends; + } + + /** + * @return Returns the age. + */ + public int getAge() { + return age; + } + + /** + * @param age The age to set. + */ + public void setAge(int age) { + this.age = age; + } + + /** + * @return Returns the nickName. + */ + public String getNickName() { + return nickName; + } + + /** + * @param nickName The nickName to set. + */ + public void setNickName(String nickName) { + this.nickName = nickName; + } + +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraAdminTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraAdminTest.java new file mode 100644 index 000000000..ff1b8f81e --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraAdminTest.java @@ -0,0 +1,106 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.test.integration.template; + +import java.io.IOException; + +import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.thrift.transport.TTransportException; +import org.cassandraunit.DataLoader; +import org.cassandraunit.dataset.yaml.ClassPathYamlDataSet; +import org.cassandraunit.utils.EmbeddedCassandraServerHelper; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cassandra.core.CassandraOperations; +import org.springframework.context.ApplicationContext; +import org.springframework.data.cassandra.test.integration.config.TestConfig; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +/** + * @author David Webb + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { TestConfig.class }, loader = AnnotationConfigContextLoader.class) +public class CassandraAdminTest { + + @Autowired + private CassandraOperations cassandraTemplate; + + @Mock + ApplicationContext context; + + private static Logger log = LoggerFactory.getLogger(CassandraAdminTest.class); + + @BeforeClass + public static void startCassandra() throws IOException, TTransportException, ConfigurationException, + InterruptedException { + EmbeddedCassandraServerHelper.startEmbeddedCassandra("cassandra.yaml"); + + /* + * Load data file to creat the test keyspace before we init the template + */ + DataLoader dataLoader = new DataLoader("Test Cluster", "localhost:9160"); + dataLoader.load(new ClassPathYamlDataSet("cassandra-keyspace.yaml")); + + } + + @Before + public void setupKeyspace() { + + /* + * Load data file to creat the test keyspace before we init the template + */ + DataLoader dataLoader = new DataLoader("Test Cluster", "localhost:9160"); + dataLoader.load(new ClassPathYamlDataSet("cassandra-keyspace.yaml")); + + } + + @Test + public void alterTableTest() { + + // cassandraTemplate.alterTable(UserAlter.class); + + } + + @Test + public void dropTableTest() { + + // cassandraTemplate.dropTable(User.class); + // cassandraTemplate.dropTable("comments"); + + } + + @After + public void clearCassandra() { + EmbeddedCassandraServerHelper.cleanEmbeddedCassandra(); + } + + @AfterClass + public static void stopCassandra() { + EmbeddedCassandraServerHelper.cleanEmbeddedCassandra(); + } +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraDataOperationsTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraDataOperationsTest.java new file mode 100644 index 000000000..8034645ad --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraDataOperationsTest.java @@ -0,0 +1,940 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.test.integration.template; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import junit.framework.Assert; + +import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.thrift.transport.TTransportException; +import org.cassandraunit.CassandraCQLUnit; +import org.cassandraunit.DataLoader; +import org.cassandraunit.dataset.cql.ClassPathCQLDataSet; +import org.cassandraunit.dataset.yaml.ClassPathYamlDataSet; +import org.cassandraunit.utils.EmbeddedCassandraServerHelper; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.cassandra.core.CassandraDataOperations; +import org.springframework.data.cassandra.core.ConsistencyLevel; +import org.springframework.data.cassandra.core.QueryOptions; +import org.springframework.data.cassandra.core.RetryPolicy; +import org.springframework.data.cassandra.test.integration.config.TestConfig; +import org.springframework.data.cassandra.test.integration.table.Book; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import com.datastax.driver.core.querybuilder.QueryBuilder; +import com.datastax.driver.core.querybuilder.Select; + +/** + * Unit Tests for CassandraTemplate + * + * @author David Webb + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { TestConfig.class }, loader = AnnotationConfigContextLoader.class) +public class CassandraDataOperationsTest { + + @Autowired + private CassandraDataOperations cassandraDataTemplate; + + private static Logger log = LoggerFactory.getLogger(CassandraDataOperationsTest.class); + + private final static String CASSANDRA_CONFIG = "cassandra.yaml"; + private final static String KEYSPACE_NAME = "test"; + private final static String CASSANDRA_HOST = "localhost"; + private final static int CASSANDRA_NATIVE_PORT = 9042; + private final static int CASSANDRA_THRIFT_PORT = 9160; + + @Rule + public CassandraCQLUnit cassandraCQLUnit = new CassandraCQLUnit(new ClassPathCQLDataSet("cql-dataload.cql", + KEYSPACE_NAME), CASSANDRA_CONFIG, CASSANDRA_HOST, CASSANDRA_NATIVE_PORT); + + @BeforeClass + public static void startCassandra() throws IOException, TTransportException, ConfigurationException, + InterruptedException { + + EmbeddedCassandraServerHelper.startEmbeddedCassandra(CASSANDRA_CONFIG); + + /* + * Load data file to creat the test keyspace before we init the template + */ + DataLoader dataLoader = new DataLoader("Test Cluster", CASSANDRA_HOST + ":" + CASSANDRA_THRIFT_PORT); + dataLoader.load(new ClassPathYamlDataSet("cassandra-keyspace.yaml")); + } + + @Test + public void insertTest() { + + /* + * Test Single Insert with entity + */ + Book b1 = new Book(); + b1.setIsbn("123456-1"); + b1.setTitle("Spring Data Cassandra Guide"); + b1.setAuthor("Cassandra Guru"); + b1.setPages(521); + + cassandraDataTemplate.insert(b1); + + Book b2 = new Book(); + b2.setIsbn("123456-2"); + b2.setTitle("Spring Data Cassandra Guide"); + b2.setAuthor("Cassandra Guru"); + b2.setPages(521); + + cassandraDataTemplate.insert(b2, "book_alt"); + + /* + * Test Single Insert with entity + */ + Book b3 = new Book(); + b3.setIsbn("123456-3"); + b3.setTitle("Spring Data Cassandra Guide"); + b3.setAuthor("Cassandra Guru"); + b3.setPages(265); + + QueryOptions options = new QueryOptions(); + options.setConsistencyLevel(ConsistencyLevel.ONE); + options.setRetryPolicy(RetryPolicy.DOWNGRADING_CONSISTENCY); + + cassandraDataTemplate.insert(b3, "book", options); + + /* + * Test Single Insert with entity + */ + Book b4 = new Book(); + b4.setIsbn("123456-4"); + b4.setTitle("Spring Data Cassandra Guide"); + b4.setAuthor("Cassandra Guru"); + b4.setPages(465); + + Map optionsByName = new HashMap(); + optionsByName.put(QueryOptions.QueryOptionMapKeys.CONSISTENCY_LEVEL, ConsistencyLevel.ALL); + optionsByName.put(QueryOptions.QueryOptionMapKeys.RETRY_POLICY, RetryPolicy.FALLTHROUGH); + optionsByName.put(QueryOptions.QueryOptionMapKeys.TTL, 30); + + cassandraDataTemplate.insert(b4, "book", optionsByName); + + /* + * Test Single Insert with entity + */ + Book b5 = new Book(); + b5.setIsbn("123456-5"); + b5.setTitle("Spring Data Cassandra Guide"); + b5.setAuthor("Cassandra Guru"); + b5.setPages(265); + + cassandraDataTemplate.insert(b5, options); + + /* + * Test Single Insert with entity + */ + Book b6 = new Book(); + b6.setIsbn("123456-6"); + b6.setTitle("Spring Data Cassandra Guide"); + b6.setAuthor("Cassandra Guru"); + b6.setPages(465); + + cassandraDataTemplate.insert(b6, optionsByName); + + } + + @Test + public void insertAsynchronouslyTest() { + + /* + * Test Single Insert with entity + */ + Book b1 = new Book(); + b1.setIsbn("123456-1"); + b1.setTitle("Spring Data Cassandra Guide"); + b1.setAuthor("Cassandra Guru"); + b1.setPages(521); + + cassandraDataTemplate.insertAsynchronously(b1); + + Book b2 = new Book(); + b2.setIsbn("123456-2"); + b2.setTitle("Spring Data Cassandra Guide"); + b2.setAuthor("Cassandra Guru"); + b2.setPages(521); + + cassandraDataTemplate.insertAsynchronously(b2, "book_alt"); + + /* + * Test Single Insert with entity + */ + Book b3 = new Book(); + b3.setIsbn("123456-3"); + b3.setTitle("Spring Data Cassandra Guide"); + b3.setAuthor("Cassandra Guru"); + b3.setPages(265); + + QueryOptions options = new QueryOptions(); + options.setConsistencyLevel(ConsistencyLevel.ONE); + options.setRetryPolicy(RetryPolicy.DOWNGRADING_CONSISTENCY); + + cassandraDataTemplate.insertAsynchronously(b3, "book", options); + + /* + * Test Single Insert with entity + */ + Book b4 = new Book(); + b4.setIsbn("123456-4"); + b4.setTitle("Spring Data Cassandra Guide"); + b4.setAuthor("Cassandra Guru"); + b4.setPages(465); + + Map optionsByName = new HashMap(); + optionsByName.put(QueryOptions.QueryOptionMapKeys.CONSISTENCY_LEVEL, ConsistencyLevel.ALL); + optionsByName.put(QueryOptions.QueryOptionMapKeys.RETRY_POLICY, RetryPolicy.FALLTHROUGH); + optionsByName.put(QueryOptions.QueryOptionMapKeys.TTL, 30); + + cassandraDataTemplate.insertAsynchronously(b4, "book", optionsByName); + + /* + * Test Single Insert with entity + */ + Book b5 = new Book(); + b5.setIsbn("123456-5"); + b5.setTitle("Spring Data Cassandra Guide"); + b5.setAuthor("Cassandra Guru"); + b5.setPages(265); + + cassandraDataTemplate.insertAsynchronously(b5, options); + + /* + * Test Single Insert with entity + */ + Book b6 = new Book(); + b6.setIsbn("123456-6"); + b6.setTitle("Spring Data Cassandra Guide"); + b6.setAuthor("Cassandra Guru"); + b6.setPages(465); + + cassandraDataTemplate.insertAsynchronously(b6, optionsByName); + + } + + @Test + public void insertBatchTest() { + + QueryOptions options = new QueryOptions(); + options.setConsistencyLevel(ConsistencyLevel.ONE); + options.setRetryPolicy(RetryPolicy.DOWNGRADING_CONSISTENCY); + + Map optionsByName = new HashMap(); + optionsByName.put(QueryOptions.QueryOptionMapKeys.CONSISTENCY_LEVEL, ConsistencyLevel.ALL); + optionsByName.put(QueryOptions.QueryOptionMapKeys.RETRY_POLICY, RetryPolicy.FALLTHROUGH); + optionsByName.put(QueryOptions.QueryOptionMapKeys.TTL, 30); + + List books = null; + + books = getBookList(20); + + cassandraDataTemplate.insert(books); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, "book_alt"); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, "book", options); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, "book", optionsByName); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, options); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, optionsByName); + + } + + @Test + public void insertBatchAsynchronouslyTest() { + + QueryOptions options = new QueryOptions(); + options.setConsistencyLevel(ConsistencyLevel.ONE); + options.setRetryPolicy(RetryPolicy.DOWNGRADING_CONSISTENCY); + + Map optionsByName = new HashMap(); + optionsByName.put(QueryOptions.QueryOptionMapKeys.CONSISTENCY_LEVEL, ConsistencyLevel.ALL); + optionsByName.put(QueryOptions.QueryOptionMapKeys.RETRY_POLICY, RetryPolicy.FALLTHROUGH); + optionsByName.put(QueryOptions.QueryOptionMapKeys.TTL, 30); + + List books = null; + + books = getBookList(20); + + cassandraDataTemplate.insertAsynchronously(books); + + books = getBookList(20); + + cassandraDataTemplate.insertAsynchronously(books, "book_alt"); + + books = getBookList(20); + + cassandraDataTemplate.insertAsynchronously(books, "book", options); + + books = getBookList(20); + + cassandraDataTemplate.insertAsynchronously(books, "book", optionsByName); + + books = getBookList(20); + + cassandraDataTemplate.insertAsynchronously(books, options); + + books = getBookList(20); + + cassandraDataTemplate.insertAsynchronously(books, optionsByName); + + } + + /** + * @return + */ + private List getBookList(int numBooks) { + + List books = new ArrayList(); + + Book b = null; + for (int i = 0; i < numBooks; i++) { + b = new Book(); + b.setIsbn(UUID.randomUUID().toString()); + b.setTitle("Spring Data Cassandra Guide"); + b.setAuthor("Cassandra Guru"); + b.setPages(i * 10 + 5); + books.add(b); + } + + return books; + } + + @Test + public void updateTest() { + + insertTest(); + + QueryOptions options = new QueryOptions(); + options.setConsistencyLevel(ConsistencyLevel.ONE); + options.setRetryPolicy(RetryPolicy.DOWNGRADING_CONSISTENCY); + + Map optionsByName = new HashMap(); + optionsByName.put(QueryOptions.QueryOptionMapKeys.CONSISTENCY_LEVEL, ConsistencyLevel.ALL); + optionsByName.put(QueryOptions.QueryOptionMapKeys.RETRY_POLICY, RetryPolicy.FALLTHROUGH); + optionsByName.put(QueryOptions.QueryOptionMapKeys.TTL, 30); + + /* + * Test Single Insert with entity + */ + Book b1 = new Book(); + b1.setIsbn("123456-1"); + b1.setTitle("Spring Data Cassandra Book"); + b1.setAuthor("Cassandra Guru"); + b1.setPages(521); + + cassandraDataTemplate.update(b1); + + Book b2 = new Book(); + b2.setIsbn("123456-2"); + b2.setTitle("Spring Data Cassandra Book"); + b2.setAuthor("Cassandra Guru"); + b2.setPages(521); + + cassandraDataTemplate.update(b2, "book_alt"); + + /* + * Test Single Insert with entity + */ + Book b3 = new Book(); + b3.setIsbn("123456-3"); + b3.setTitle("Spring Data Cassandra Book"); + b3.setAuthor("Cassandra Guru"); + b3.setPages(265); + + cassandraDataTemplate.update(b3, "book", options); + + /* + * Test Single Insert with entity + */ + Book b4 = new Book(); + b4.setIsbn("123456-4"); + b4.setTitle("Spring Data Cassandra Book"); + b4.setAuthor("Cassandra Guru"); + b4.setPages(465); + + cassandraDataTemplate.update(b4, "book", optionsByName); + + /* + * Test Single Insert with entity + */ + Book b5 = new Book(); + b5.setIsbn("123456-5"); + b5.setTitle("Spring Data Cassandra Book"); + b5.setAuthor("Cassandra Guru"); + b5.setPages(265); + + cassandraDataTemplate.update(b5, options); + + /* + * Test Single Insert with entity + */ + Book b6 = new Book(); + b6.setIsbn("123456-6"); + b6.setTitle("Spring Data Cassandra Book"); + b6.setAuthor("Cassandra Guru"); + b6.setPages(465); + + cassandraDataTemplate.update(b6, optionsByName); + + } + + @Test + public void updateAsynchronouslyTest() { + + insertTest(); + + QueryOptions options = new QueryOptions(); + options.setConsistencyLevel(ConsistencyLevel.ONE); + options.setRetryPolicy(RetryPolicy.DOWNGRADING_CONSISTENCY); + + Map optionsByName = new HashMap(); + optionsByName.put(QueryOptions.QueryOptionMapKeys.CONSISTENCY_LEVEL, ConsistencyLevel.ALL); + optionsByName.put(QueryOptions.QueryOptionMapKeys.RETRY_POLICY, RetryPolicy.FALLTHROUGH); + optionsByName.put(QueryOptions.QueryOptionMapKeys.TTL, 30); + + /* + * Test Single Insert with entity + */ + Book b1 = new Book(); + b1.setIsbn("123456-1"); + b1.setTitle("Spring Data Cassandra Book"); + b1.setAuthor("Cassandra Guru"); + b1.setPages(521); + + cassandraDataTemplate.updateAsynchronously(b1); + + Book b2 = new Book(); + b2.setIsbn("123456-2"); + b2.setTitle("Spring Data Cassandra Book"); + b2.setAuthor("Cassandra Guru"); + b2.setPages(521); + + cassandraDataTemplate.updateAsynchronously(b2, "book_alt"); + + /* + * Test Single Insert with entity + */ + Book b3 = new Book(); + b3.setIsbn("123456-3"); + b3.setTitle("Spring Data Cassandra Book"); + b3.setAuthor("Cassandra Guru"); + b3.setPages(265); + + cassandraDataTemplate.updateAsynchronously(b3, "book", options); + + /* + * Test Single Insert with entity + */ + Book b4 = new Book(); + b4.setIsbn("123456-4"); + b4.setTitle("Spring Data Cassandra Book"); + b4.setAuthor("Cassandra Guru"); + b4.setPages(465); + + cassandraDataTemplate.updateAsynchronously(b4, "book", optionsByName); + + /* + * Test Single Insert with entity + */ + Book b5 = new Book(); + b5.setIsbn("123456-5"); + b5.setTitle("Spring Data Cassandra Book"); + b5.setAuthor("Cassandra Guru"); + b5.setPages(265); + + cassandraDataTemplate.updateAsynchronously(b5, options); + + /* + * Test Single Insert with entity + */ + Book b6 = new Book(); + b6.setIsbn("123456-6"); + b6.setTitle("Spring Data Cassandra Book"); + b6.setAuthor("Cassandra Guru"); + b6.setPages(465); + + cassandraDataTemplate.updateAsynchronously(b6, optionsByName); + + } + + @Test + public void updateBatchTest() { + + QueryOptions options = new QueryOptions(); + options.setConsistencyLevel(ConsistencyLevel.ONE); + options.setRetryPolicy(RetryPolicy.DOWNGRADING_CONSISTENCY); + + Map optionsByName = new HashMap(); + optionsByName.put(QueryOptions.QueryOptionMapKeys.CONSISTENCY_LEVEL, ConsistencyLevel.ALL); + optionsByName.put(QueryOptions.QueryOptionMapKeys.RETRY_POLICY, RetryPolicy.FALLTHROUGH); + optionsByName.put(QueryOptions.QueryOptionMapKeys.TTL, 30); + + List books = null; + + books = getBookList(20); + + cassandraDataTemplate.insert(books); + + alterBooks(books); + + cassandraDataTemplate.update(books); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, "book_alt"); + + alterBooks(books); + + cassandraDataTemplate.update(books, "book_alt"); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, "book", options); + + alterBooks(books); + + cassandraDataTemplate.update(books, "book", options); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, "book", optionsByName); + + alterBooks(books); + + cassandraDataTemplate.update(books, "book", optionsByName); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, options); + + alterBooks(books); + + cassandraDataTemplate.update(books, options); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, optionsByName); + + alterBooks(books); + + cassandraDataTemplate.update(books, optionsByName); + + } + + @Test + public void updateBatchAsynchronouslyTest() { + + QueryOptions options = new QueryOptions(); + options.setConsistencyLevel(ConsistencyLevel.ONE); + options.setRetryPolicy(RetryPolicy.DOWNGRADING_CONSISTENCY); + + Map optionsByName = new HashMap(); + optionsByName.put(QueryOptions.QueryOptionMapKeys.CONSISTENCY_LEVEL, ConsistencyLevel.ALL); + optionsByName.put(QueryOptions.QueryOptionMapKeys.RETRY_POLICY, RetryPolicy.FALLTHROUGH); + optionsByName.put(QueryOptions.QueryOptionMapKeys.TTL, 30); + + List books = null; + + books = getBookList(20); + + cassandraDataTemplate.insert(books); + + alterBooks(books); + + cassandraDataTemplate.updateAsynchronously(books); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, "book_alt"); + + alterBooks(books); + + cassandraDataTemplate.updateAsynchronously(books, "book_alt"); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, "book", options); + + alterBooks(books); + + cassandraDataTemplate.updateAsynchronously(books, "book", options); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, "book", optionsByName); + + alterBooks(books); + + cassandraDataTemplate.updateAsynchronously(books, "book", optionsByName); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, options); + + alterBooks(books); + + cassandraDataTemplate.updateAsynchronously(books, options); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, optionsByName); + + alterBooks(books); + + cassandraDataTemplate.updateAsynchronously(books, optionsByName); + + } + + /** + * @param books + */ + private void alterBooks(List books) { + + for (Book b : books) { + b.setAuthor("Ernest Hemmingway"); + b.setTitle("The Old Man and the Sea"); + b.setPages(115); + } + } + + @Test + public void deleteTest() { + + insertTest(); + + QueryOptions options = new QueryOptions(); + options.setConsistencyLevel(ConsistencyLevel.ONE); + options.setRetryPolicy(RetryPolicy.DOWNGRADING_CONSISTENCY); + + Map optionsByName = new HashMap(); + optionsByName.put(QueryOptions.QueryOptionMapKeys.CONSISTENCY_LEVEL, ConsistencyLevel.ALL); + optionsByName.put(QueryOptions.QueryOptionMapKeys.RETRY_POLICY, RetryPolicy.FALLTHROUGH); + + /* + * Test Single Insert with entity + */ + Book b1 = new Book(); + b1.setIsbn("123456-1"); + + cassandraDataTemplate.delete(b1); + + Book b2 = new Book(); + b2.setIsbn("123456-2"); + + cassandraDataTemplate.delete(b2, "book_alt"); + + /* + * Test Single Insert with entity + */ + Book b3 = new Book(); + b3.setIsbn("123456-3"); + + cassandraDataTemplate.delete(b3, "book", options); + + /* + * Test Single Insert with entity + */ + Book b4 = new Book(); + b4.setIsbn("123456-4"); + + cassandraDataTemplate.delete(b4, "book", optionsByName); + + /* + * Test Single Insert with entity + */ + Book b5 = new Book(); + b5.setIsbn("123456-5"); + + cassandraDataTemplate.delete(b5, options); + + /* + * Test Single Insert with entity + */ + Book b6 = new Book(); + b6.setIsbn("123456-6"); + + cassandraDataTemplate.delete(b6, optionsByName); + + } + + @Test + public void deleteAsynchronouslyTest() { + + insertTest(); + + QueryOptions options = new QueryOptions(); + options.setConsistencyLevel(ConsistencyLevel.ONE); + options.setRetryPolicy(RetryPolicy.DOWNGRADING_CONSISTENCY); + + Map optionsByName = new HashMap(); + optionsByName.put(QueryOptions.QueryOptionMapKeys.CONSISTENCY_LEVEL, ConsistencyLevel.ALL); + optionsByName.put(QueryOptions.QueryOptionMapKeys.RETRY_POLICY, RetryPolicy.FALLTHROUGH); + + /* + * Test Single Insert with entity + */ + Book b1 = new Book(); + b1.setIsbn("123456-1"); + + cassandraDataTemplate.deleteAsynchronously(b1); + + Book b2 = new Book(); + b2.setIsbn("123456-2"); + + cassandraDataTemplate.deleteAsynchronously(b2, "book_alt"); + + /* + * Test Single Insert with entity + */ + Book b3 = new Book(); + b3.setIsbn("123456-3"); + + cassandraDataTemplate.deleteAsynchronously(b3, "book", options); + + /* + * Test Single Insert with entity + */ + Book b4 = new Book(); + b4.setIsbn("123456-4"); + + cassandraDataTemplate.deleteAsynchronously(b4, "book", optionsByName); + + /* + * Test Single Insert with entity + */ + Book b5 = new Book(); + b5.setIsbn("123456-5"); + + cassandraDataTemplate.deleteAsynchronously(b5, options); + + /* + * Test Single Insert with entity + */ + Book b6 = new Book(); + b6.setIsbn("123456-6"); + + cassandraDataTemplate.deleteAsynchronously(b6, optionsByName); + } + + @Test + public void deleteBatchTest() { + + QueryOptions options = new QueryOptions(); + options.setConsistencyLevel(ConsistencyLevel.ONE); + options.setRetryPolicy(RetryPolicy.DOWNGRADING_CONSISTENCY); + + Map optionsByName = new HashMap(); + optionsByName.put(QueryOptions.QueryOptionMapKeys.CONSISTENCY_LEVEL, ConsistencyLevel.ALL); + optionsByName.put(QueryOptions.QueryOptionMapKeys.RETRY_POLICY, RetryPolicy.FALLTHROUGH); + optionsByName.put(QueryOptions.QueryOptionMapKeys.TTL, 30); + + List books = null; + + books = getBookList(20); + + cassandraDataTemplate.insert(books); + + cassandraDataTemplate.delete(books); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, "book_alt"); + + cassandraDataTemplate.delete(books, "book_alt"); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, "book", options); + + cassandraDataTemplate.delete(books, "book", options); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, "book", optionsByName); + + cassandraDataTemplate.delete(books, "book", optionsByName); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, options); + + cassandraDataTemplate.delete(books, options); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, optionsByName); + + cassandraDataTemplate.delete(books, optionsByName); + + } + + @Test + public void deleteBatchAsynchronouslyTest() { + + QueryOptions options = new QueryOptions(); + options.setConsistencyLevel(ConsistencyLevel.ONE); + options.setRetryPolicy(RetryPolicy.DOWNGRADING_CONSISTENCY); + + Map optionsByName = new HashMap(); + optionsByName.put(QueryOptions.QueryOptionMapKeys.CONSISTENCY_LEVEL, ConsistencyLevel.ALL); + optionsByName.put(QueryOptions.QueryOptionMapKeys.RETRY_POLICY, RetryPolicy.FALLTHROUGH); + optionsByName.put(QueryOptions.QueryOptionMapKeys.TTL, 30); + + List books = null; + + books = getBookList(20); + + cassandraDataTemplate.insert(books); + + cassandraDataTemplate.deleteAsynchronously(books); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, "book_alt"); + + cassandraDataTemplate.deleteAsynchronously(books, "book_alt"); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, "book", options); + + cassandraDataTemplate.deleteAsynchronously(books, "book", options); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, "book", optionsByName); + + cassandraDataTemplate.deleteAsynchronously(books, "book", optionsByName); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, options); + + cassandraDataTemplate.deleteAsynchronously(books, options); + + books = getBookList(20); + + cassandraDataTemplate.insert(books, optionsByName); + + cassandraDataTemplate.deleteAsynchronously(books, optionsByName); + + } + + @Test + public void selectOneTest() { + + /* + * Test Single Insert with entity + */ + Book b1 = new Book(); + b1.setIsbn("123456-1"); + b1.setTitle("Spring Data Cassandra Guide"); + b1.setAuthor("Cassandra Guru"); + b1.setPages(521); + + cassandraDataTemplate.insert(b1); + + Select select = QueryBuilder.select().all().from("book"); + select.where(QueryBuilder.eq("isbn", "123456-1")); + + Book b = cassandraDataTemplate.selectOne(select, Book.class); + + log.info("SingleSelect Book Title -> " + b.getTitle()); + log.info("SingleSelect Book Author -> " + b.getAuthor()); + + Assert.assertEquals(b.getTitle(), "Spring Data Cassandra Guide"); + Assert.assertEquals(b.getAuthor(), "Cassandra Guru"); + + } + + @Test + public void selectTest() { + + List books = getBookList(20); + + cassandraDataTemplate.insert(books); + + Select select = QueryBuilder.select().all().from("book"); + + List b = cassandraDataTemplate.select(select, Book.class); + + log.info("Book Count -> " + b.size()); + + Assert.assertEquals(b.size(), 20); + + } + + @Test + public void selectCountTest() { + + List books = getBookList(20); + + cassandraDataTemplate.insert(books); + + Select select = QueryBuilder.select().countAll().from("book"); + + Long count = cassandraDataTemplate.count(select); + + log.info("Book Count -> " + count); + + Assert.assertEquals(count, new Long(20)); + + } + + @After + public void clearCassandra() { + EmbeddedCassandraServerHelper.cleanEmbeddedCassandra(); + + } + + @AfterClass + public static void stopCassandra() { + EmbeddedCassandraServerHelper.stopEmbeddedCassandra(); + } +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraOperationsTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraOperationsTest.java new file mode 100644 index 000000000..3945a1942 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraOperationsTest.java @@ -0,0 +1,299 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * 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.springframework.data.cassandra.test.integration.template; + +import static org.junit.Assert.assertNotNull; + +import java.io.IOException; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import junit.framework.Assert; + +import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.thrift.transport.TTransportException; +import org.cassandraunit.CassandraCQLUnit; +import org.cassandraunit.DataLoader; +import org.cassandraunit.dataset.cql.ClassPathCQLDataSet; +import org.cassandraunit.dataset.yaml.ClassPathYamlDataSet; +import org.cassandraunit.utils.EmbeddedCassandraServerHelper; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.interceptor.DefaultKeyGenerator; +import org.springframework.cassandra.core.CachedPreparedStatementCreator; +import org.springframework.cassandra.core.CassandraOperations; +import org.springframework.cassandra.core.CqlParameter; +import org.springframework.cassandra.core.CqlParameterValue; +import org.springframework.cassandra.core.HostMapper; +import org.springframework.cassandra.core.PreparedStatementBinder; +import org.springframework.cassandra.core.PreparedStatementCreatorFactory; +import org.springframework.cassandra.core.ResultSetExtractor; +import org.springframework.cassandra.core.RingMember; +import org.springframework.dao.DataAccessException; +import org.springframework.data.cassandra.test.integration.config.TestConfig; +import org.springframework.data.cassandra.test.integration.table.Book; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import com.datastax.driver.core.BoundStatement; +import com.datastax.driver.core.DataType; +import com.datastax.driver.core.Host; +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.Row; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.exceptions.DriverException; + +/** + * Unit Tests for CassandraTemplate + * + * @author David Webb + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { TestConfig.class }, loader = AnnotationConfigContextLoader.class) +public class CassandraOperationsTest { + + /** + * @author David Webb + * + */ + public class MyHost { + + public String someName; + + } + + @Autowired + private CassandraOperations cassandraTemplate; + + private static Logger log = LoggerFactory.getLogger(CassandraOperationsTest.class); + + private final static String CASSANDRA_CONFIG = "cassandra.yaml"; + private final static String KEYSPACE_NAME = "test"; + private final static String CASSANDRA_HOST = "localhost"; + private final static int CASSANDRA_NATIVE_PORT = 9042; + private final static int CASSANDRA_THRIFT_PORT = 9160; + + @Rule + public CassandraCQLUnit cassandraCQLUnit = new CassandraCQLUnit(new ClassPathCQLDataSet( + "cassandraOperationsTest-cql-dataload.cql", KEYSPACE_NAME), CASSANDRA_CONFIG, CASSANDRA_HOST, + CASSANDRA_NATIVE_PORT); + + @BeforeClass + public static void startCassandra() throws IOException, TTransportException, ConfigurationException, + InterruptedException { + + EmbeddedCassandraServerHelper.startEmbeddedCassandra(CASSANDRA_CONFIG); + + /* + * Load data file to creat the test keyspace before we init the template + */ + DataLoader dataLoader = new DataLoader("Test Cluster", CASSANDRA_HOST + ":" + CASSANDRA_THRIFT_PORT); + dataLoader.load(new ClassPathYamlDataSet("cassandra-keyspace.yaml")); + } + + @Test + public void ringTest() { + + List ring = cassandraTemplate.describeRing(); + + /* + * There must be 1 node in the cluster if the embedded server is + * running. + */ + assertNotNull(ring); + + for (RingMember h : ring) { + log.info("ringTest Host -> " + h.address); + } + } + + @Test + public void hostMapperTest() { + + List ring = (List) cassandraTemplate.describeRing(new HostMapper() { + + @Override + public Collection mapHosts(Set host) throws DriverException { + + List list = new LinkedList(); + + for (Host h : host) { + MyHost mh = new MyHost(); + mh.someName = h.getAddress().getCanonicalHostName(); + list.add(mh); + } + + return list; + } + + }); + + assertNotNull(ring); + Assert.assertTrue(ring.size() > 0); + + for (MyHost h : ring) { + log.info("hostMapperTest Host -> " + h.someName); + } + + } + + @Test + public void preparedStatementFactoryTest() { + + String cql = "select * from book where isbn = ?"; + + List parameters = new LinkedList(); + parameters.add(new CqlParameter("isbn", DataType.text())); + + PreparedStatementCreatorFactory factory = new PreparedStatementCreatorFactory(cql, parameters); + + List values = new LinkedList(); + values.add(new CqlParameterValue(DataType.text(), "999999999")); + + Book b = cassandraTemplate.query(factory.newPreparedStatementCreator(values), + factory.newPreparedStatementBinder(values), new ResultSetExtractor() { + + @Override + public Book extractData(ResultSet rs) throws DriverException, DataAccessException { + Row r = rs.one(); + Book b = new Book(); + b.setIsbn(r.getString("isbn")); + b.setTitle(r.getString("title")); + b.setAuthor(r.getString("author")); + b.setPages(r.getInt("pages")); + return b; + } + }); + + log.info(b.toString()); + + } + + // @Test + public void cachedPreparedStatementTest() { + + log.info(echoString("Hello")); + log.info(echoString("Hello")); + + String cql = "select * from book where isbn = ?"; + + CachedPreparedStatementCreator cpsc = new CachedPreparedStatementCreator(cql); + + Book b = cassandraTemplate.query(cpsc, new PreparedStatementBinder() { + + @Override + public BoundStatement bindValues(PreparedStatement ps) throws DriverException { + return ps.bind("999999999"); + } + }, new ResultSetExtractor() { + + @Override + public Book extractData(ResultSet rs) throws DriverException, DataAccessException { + Row r = rs.one(); + Book b = new Book(); + b.setIsbn(r.getString("isbn")); + b.setTitle(r.getString("title")); + b.setAuthor(r.getString("author")); + b.setPages(r.getInt("pages")); + return b; + } + }); + + assertNotNull(b); + + log.info(b.toString()); + + try { + DefaultKeyGenerator generator = new DefaultKeyGenerator(); + + // TODO Why does method have to be public to work? Options? + Object cacheKey = generator.generate(CachedPreparedStatementCreator.class, + CachedPreparedStatementCreator.class.getMethod("getCachedPreparedStatement", Session.class, String.class), + cassandraTemplate.getSession(), cql); + + log.info("cacheKey -> " + cacheKey); + + // ConcurrentMapCache cache = (ConcurrentMapCache) cacheManager.getCache("sdc-pstmts"); + // ConcurrentMap cacheMap = cache.getNativeCache(); + // assertNotNull(cacheMap); + // log.info("CacheMap.size() -> " + cacheMap.size()); + // ValueWrapper vw = cache.get(cacheKey); + // PreparedStatement pstmt = (PreparedStatement) vw.get(); + // assertNotNull(pstmt); + // log.info(pstmt.getQueryString()); + // assertEquals(pstmt.getQueryString(), cql); + } catch (NoSuchMethodException e) { + log.error("Failed to find method", e); + } + + CachedPreparedStatementCreator cpsc2 = new CachedPreparedStatementCreator(cql); + + Book b2 = cassandraTemplate.query(cpsc2, new PreparedStatementBinder() { + + @Override + public BoundStatement bindValues(PreparedStatement ps) throws DriverException { + return ps.bind("999999999"); + } + }, new ResultSetExtractor() { + + @Override + public Book extractData(ResultSet rs) throws DriverException, DataAccessException { + Row r = rs.one(); + Book b = new Book(); + b.setIsbn(r.getString("isbn")); + b.setTitle(r.getString("title")); + b.setAuthor(r.getString("author")); + b.setPages(r.getInt("pages")); + return b; + } + }); + + assertNotNull(b2); + + log.info(b2.toString()); + + } + + @Cacheable("sdc-pstmts") + public String echoString(String s) { + log.info("In EchoString"); + return s; + } + + @After + public void clearCassandra() { + EmbeddedCassandraServerHelper.cleanEmbeddedCassandra(); + + } + + @AfterClass + public static void stopCassandra() { + // EmbeddedCassandraServerHelper.stopEmbeddedCassandra(); + } +} diff --git a/spring-data-cassandra/src/test/resources/cassandra-keyspace.yaml b/spring-data-cassandra/src/test/resources/cassandra-keyspace.yaml new file mode 100644 index 000000000..a0e13da6e --- /dev/null +++ b/spring-data-cassandra/src/test/resources/cassandra-keyspace.yaml @@ -0,0 +1,3 @@ +name: test +replicationFactor: 1 +strategy: org.apache.cassandra.locator.SimpleStrategy \ No newline at end of file diff --git a/spring-data-cassandra/src/test/resources/cassandra.yaml b/spring-data-cassandra/src/test/resources/cassandra.yaml new file mode 100644 index 000000000..82fcfc5ad --- /dev/null +++ b/spring-data-cassandra/src/test/resources/cassandra.yaml @@ -0,0 +1,690 @@ +# Cassandra storage config YAML + +# NOTE: +# See http://wiki.apache.org/cassandra/StorageConfiguration for +# full explanations of configuration directives +# /NOTE + +# The name of the cluster. This is mainly used to prevent machines in +# one logical cluster from joining another. +cluster_name: 'Test Cluster' + +# This defines the number of tokens randomly assigned to this node on the ring +# The more tokens, relative to other nodes, the larger the proportion of data +# that this node will store. You probably want all nodes to have the same number +# of tokens assuming they have equal hardware capability. +# +# If you leave this unspecified, Cassandra will use the default of 1 token for legacy compatibility, +# and will use the initial_token as described below. +# +# Specifying initial_token will override this setting. +# +# If you already have a cluster with 1 token per node, and wish to migrate to +# multiple tokens per node, see http://wiki.apache.org/cassandra/Operations +# num_tokens: 256 + +# If you haven't specified num_tokens, or have set it to the default of 1 then +# you should always specify InitialToken when setting up a production +# cluster for the first time, and often when adding capacity later. +# The principle is that each node should be given an equal slice of +# the token ring; see http://wiki.apache.org/cassandra/Operations +# for more details. +# +# If blank, Cassandra will request a token bisecting the range of +# the heaviest-loaded existing node. If there is no load information +# available, such as is the case with a new cluster, it will pick +# a random token, which will lead to hot spots. +initial_token: + +# See http://wiki.apache.org/cassandra/HintedHandoff +hinted_handoff_enabled: true +# this defines the maximum amount of time a dead host will have hints +# generated. After it has been dead this long, new hints for it will not be +# created until it has been seen alive and gone down again. +max_hint_window_in_ms: 10800000 # 3 hours +# throttle in KBs per second, per delivery thread +hinted_handoff_throttle_in_kb: 1024 +# Number of threads with which to deliver hints; +# Consider increasing this number when you have multi-dc deployments, since +# cross-dc handoff tends to be slower +max_hints_delivery_threads: 2 + +# The following setting populates the page cache on memtable flush and compaction +# WARNING: Enable this setting only when the whole node's data fits in memory. +# Defaults to: false +# populate_io_cache_on_flush: false + +# Authentication backend, implementing IAuthenticator; used to identify users +# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthenticator, +# PasswordAuthenticator}. +# +# - AllowAllAuthenticator performs no checks - set it to disable authentication. +# - PasswordAuthenticator relies on username/password pairs to authenticate +# users. It keeps usernames and hashed passwords in system_auth.credentials table. +# Please increase system_auth keyspace replication factor if you use this authenticator. +authenticator: org.apache.cassandra.auth.AllowAllAuthenticator + +# Authorization backend, implementing IAuthorizer; used to limit access/provide permissions +# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthorizer, +# CassandraAuthorizer}. +# +# - AllowAllAuthorizer allows any action to any user - set it to disable authorization. +# - CassandraAuthorizer stores permissions in system_auth.permissions table. Please +# increase system_auth keyspace replication factor if you use this authorizer. +authorizer: org.apache.cassandra.auth.AllowAllAuthorizer + +# Validity period for permissions cache (fetching permissions can be an +# expensive operation depending on the authorizer, CassandraAuthorizer is +# one example). Defaults to 2000, set to 0 to disable. +# Will be disabled automatically for AllowAllAuthorizer. +# permissions_validity_in_ms: 2000 + +# The partitioner is responsible for distributing rows (by key) across +# nodes in the cluster. Any IPartitioner may be used, including your +# own as long as it is on the classpath. Out of the box, Cassandra +# provides org.apache.cassandra.dht.{Murmur3Partitioner, RandomPartitioner +# ByteOrderedPartitioner, OrderPreservingPartitioner (deprecated)}. +# +# - RandomPartitioner distributes rows across the cluster evenly by md5. +# This is the default prior to 1.2 and is retained for compatibility. +# - Murmur3Partitioner is similar to RandomPartioner but uses Murmur3_128 +# Hash Function instead of md5. When in doubt, this is the best option. +# - ByteOrderedPartitioner orders rows lexically by key bytes. BOP allows +# scanning rows in key order, but the ordering can generate hot spots +# for sequential insertion workloads. +# - OrderPreservingPartitioner is an obsolete form of BOP, that stores +# - keys in a less-efficient format and only works with keys that are +# UTF8-encoded Strings. +# - CollatingOPP collates according to EN,US rules rather than lexical byte +# ordering. Use this as an example if you need custom collation. +# +# See http://wiki.apache.org/cassandra/Operations for more on +# partitioners and token selection. +partitioner: org.apache.cassandra.dht.Murmur3Partitioner + +# Directories where Cassandra should store data on disk. Cassandra +# will spread data evenly across them, subject to the granularity of +# the configured compaction strategy. +data_file_directories: + - target/embeddedCassandra/data + +# commit log +commitlog_directory: target/embeddedCassandra/commitlog + +# policy for data disk failures: +# stop: shut down gossip and Thrift, leaving the node effectively dead, but +# can still be inspected via JMX. +# best_effort: stop using the failed disk and respond to requests based on +# remaining available sstables. This means you WILL see obsolete +# data at CL.ONE! +# ignore: ignore fatal errors and let requests fail, as in pre-1.2 Cassandra +disk_failure_policy: stop + +# Maximum size of the key cache in memory. +# +# Each key cache hit saves 1 seek and each row cache hit saves 2 seeks at the +# minimum, sometimes more. The key cache is fairly tiny for the amount of +# time it saves, so it's worthwhile to use it at large numbers. +# The row cache saves even more time, but must contain the entire row, +# so it is extremely space-intensive. It's best to only use the +# row cache if you have hot rows or static rows. +# +# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup. +# +# Default value is empty to make it "auto" (min(5% of Heap (in MB), 100MB)). Set to 0 to disable key cache. +key_cache_size_in_mb: + +# Duration in seconds after which Cassandra should +# save the key cache. Caches are saved to saved_caches_directory as +# specified in this configuration file. +# +# Saved caches greatly improve cold-start speeds, and is relatively cheap in +# terms of I/O for the key cache. Row cache saving is much more expensive and +# has limited use. +# +# Default is 14400 or 4 hours. +key_cache_save_period: 14400 + +# Number of keys from the key cache to save +# Disabled by default, meaning all keys are going to be saved +# key_cache_keys_to_save: 100 + +# Maximum size of the row cache in memory. +# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup. +# +# Default value is 0, to disable row caching. +row_cache_size_in_mb: 0 + +# Duration in seconds after which Cassandra should +# safe the row cache. Caches are saved to saved_caches_directory as specified +# in this configuration file. +# +# Saved caches greatly improve cold-start speeds, and is relatively cheap in +# terms of I/O for the key cache. Row cache saving is much more expensive and +# has limited use. +# +# Default is 0 to disable saving the row cache. +row_cache_save_period: 0 + +# Number of keys from the row cache to save +# Disabled by default, meaning all keys are going to be saved +# row_cache_keys_to_save: 100 + +# The provider for the row cache to use. +# +# Supported values are: ConcurrentLinkedHashCacheProvider, SerializingCacheProvider +# +# SerializingCacheProvider serialises the contents of the row and stores +# it in native memory, i.e., off the JVM Heap. Serialized rows take +# significantly less memory than "live" rows in the JVM, so you can cache +# more rows in a given memory footprint. And storing the cache off-heap +# means you can use smaller heap sizes, reducing the impact of GC pauses. +# Note however that when a row is requested from the row cache, it must be +# deserialized into the heap for use. +# +# It is also valid to specify the fully-qualified class name to a class +# that implements org.apache.cassandra.cache.IRowCacheProvider. +# +# Defaults to SerializingCacheProvider +row_cache_provider: SerializingCacheProvider + +# saved caches +saved_caches_directory: target/embeddedCassandra/saved_caches + +# commitlog_sync may be either "periodic" or "batch." +# When in batch mode, Cassandra won't ack writes until the commit log +# has been fsynced to disk. It will wait up to +# commitlog_sync_batch_window_in_ms milliseconds for other writes, before +# performing the sync. +# +# commitlog_sync: batch +# commitlog_sync_batch_window_in_ms: 50 +# +# the other option is "periodic" where writes may be acked immediately +# and the CommitLog is simply synced every commitlog_sync_period_in_ms +# milliseconds. +commitlog_sync: periodic +commitlog_sync_period_in_ms: 10000 + +# The size of the individual commitlog file segments. A commitlog +# segment may be archived, deleted, or recycled once all the data +# in it (potentially from each columnfamily in the system) has been +# flushed to sstables. +# +# The default size is 32, which is almost always fine, but if you are +# archiving commitlog segments (see commitlog_archiving.properties), +# then you probably want a finer granularity of archiving; 8 or 16 MB +# is reasonable. +commitlog_segment_size_in_mb: 32 + +# any class that implements the SeedProvider interface and has a +# constructor that takes a Map of parameters will do. +seed_provider: + # Addresses of hosts that are deemed contact points. + # Cassandra nodes use this list of hosts to find each other and learn + # the topology of the ring. You must change this if you are running + # multiple nodes! + - class_name: org.apache.cassandra.locator.SimpleSeedProvider + parameters: + # seeds is actually a comma-delimited list of addresses. + # Ex: ",," + - seeds: "127.0.0.1" + +# emergency pressure valve: each time heap usage after a full (CMS) +# garbage collection is above this fraction of the max, Cassandra will +# flush the largest memtables. +# +# Set to 1.0 to disable. Setting this lower than +# CMSInitiatingOccupancyFraction is not likely to be useful. +# +# RELYING ON THIS AS YOUR PRIMARY TUNING MECHANISM WILL WORK POORLY: +# it is most effective under light to moderate load, or read-heavy +# workloads; under truly massive write load, it will often be too +# little, too late. +flush_largest_memtables_at: 0.75 + +# emergency pressure valve #2: the first time heap usage after a full +# (CMS) garbage collection is above this fraction of the max, +# Cassandra will reduce cache maximum _capacity_ to the given fraction +# of the current _size_. Should usually be set substantially above +# flush_largest_memtables_at, since that will have less long-term +# impact on the system. +# +# Set to 1.0 to disable. Setting this lower than +# CMSInitiatingOccupancyFraction is not likely to be useful. +reduce_cache_sizes_at: 0.85 +reduce_cache_capacity_to: 0.6 + +# For workloads with more data than can fit in memory, Cassandra's +# bottleneck will be reads that need to fetch data from +# disk. "concurrent_reads" should be set to (16 * number_of_drives) in +# order to allow the operations to enqueue low enough in the stack +# that the OS and drives can reorder them. +# +# On the other hand, since writes are almost never IO bound, the ideal +# number of "concurrent_writes" is dependent on the number of cores in +# your system; (8 * number_of_cores) is a good rule of thumb. +concurrent_reads: 32 +concurrent_writes: 32 + +# Total memory to use for memtables. Cassandra will flush the largest +# memtable when this much memory is used. +# If omitted, Cassandra will set it to 1/3 of the heap. +# memtable_total_space_in_mb: 2048 + +# Total space to use for commitlogs. Since commitlog segments are +# mmapped, and hence use up address space, the default size is 32 +# on 32-bit JVMs, and 1024 on 64-bit JVMs. +# +# If space gets above this value (it will round up to the next nearest +# segment multiple), Cassandra will flush every dirty CF in the oldest +# segment and remove it. So a small total commitlog space will tend +# to cause more flush activity on less-active columnfamilies. +# commitlog_total_space_in_mb: 4096 + +# This sets the amount of memtable flush writer threads. These will +# be blocked by disk io, and each one will hold a memtable in memory +# while blocked. If you have a large heap and many data directories, +# you can increase this value for better flush performance. +# By default this will be set to the amount of data directories defined. +#memtable_flush_writers: 1 + +# the number of full memtables to allow pending flush, that is, +# waiting for a writer thread. At a minimum, this should be set to +# the maximum number of secondary indexes created on a single CF. +memtable_flush_queue_size: 4 + +# Whether to, when doing sequential writing, fsync() at intervals in +# order to force the operating system to flush the dirty +# buffers. Enable this to avoid sudden dirty buffer flushing from +# impacting read latencies. Almost always a good idea on SSDs; not +# necessarily on platters. +trickle_fsync: false +trickle_fsync_interval_in_kb: 10240 + +# TCP port, for commands and data +storage_port: 7000 + +# SSL port, for encrypted communication. Unused unless enabled in +# encryption_options +ssl_storage_port: 7001 + +# Address to bind to and tell other Cassandra nodes to connect to. You +# _must_ change this if you want multiple nodes to be able to +# communicate! +# +# Leaving it blank leaves it up to InetAddress.getLocalHost(). This +# will always do the Right Thing _if_ the node is properly configured +# (hostname, name resolution, etc), and the Right Thing is to use the +# address associated with the hostname (it might not be). +# +# Setting this to 0.0.0.0 is always wrong. +listen_address: localhost + +# Address to broadcast to other Cassandra nodes +# Leaving this blank will set it to the same value as listen_address +# broadcast_address: 1.2.3.4 + +# Internode authentication backend, implementing IInternodeAuthenticator; +# used to allow/disallow connections from peer nodes. +# internode_authenticator: org.apache.cassandra.auth.AllowAllInternodeAuthenticator + +# Whether to start the native transport server. +# Please note that the address on which the native transport is bound is the +# same as the rpc_address. The port however is different and specified below. +start_native_transport: true +# port for the CQL native transport to listen for clients on +native_transport_port: 9042 +# The minimum and maximum threads for handling requests when the native +# transport is used. They are similar to rpc_min_threads and rpc_max_threads, +# though the defaults differ slightly. +# native_transport_min_threads: 16 +# native_transport_max_threads: 128 + +# Whether to start the thrift rpc server. +start_rpc: true + +# The address to bind the Thrift RPC service to -- clients connect +# here. Unlike ListenAddress above, you _can_ specify 0.0.0.0 here if +# you want Thrift to listen on all interfaces. +# +# Leaving this blank has the same effect it does for ListenAddress, +# (i.e. it will be based on the configured hostname of the node). +rpc_address: localhost +# port for Thrift to listen for clients on +rpc_port: 9160 + +# enable or disable keepalive on rpc connections +rpc_keepalive: true + +# Cassandra provides three out-of-the-box options for the RPC Server: +# +# sync -> One thread per thrift connection. For a very large number of clients, memory +# will be your limiting factor. On a 64 bit JVM, 180KB is the minimum stack size +# per thread, and that will correspond to your use of virtual memory (but physical memory +# may be limited depending on use of stack space). +# +# hsha -> Stands for "half synchronous, half asynchronous." All thrift clients are handled +# asynchronously using a small number of threads that does not vary with the amount +# of thrift clients (and thus scales well to many clients). The rpc requests are still +# synchronous (one thread per active request). +# +# The default is sync because on Windows hsha is about 30% slower. On Linux, +# sync/hsha performance is about the same, with hsha of course using less memory. +# +# Alternatively, can provide your own RPC server by providing the fully-qualified class name +# of an o.a.c.t.TServerFactory that can create an instance of it. +rpc_server_type: sync + +# Uncomment rpc_min|max_thread to set request pool size limits. +# +# Regardless of your choice of RPC server (see above), the number of maximum requests in the +# RPC thread pool dictates how many concurrent requests are possible (but if you are using the sync +# RPC server, it also dictates the number of clients that can be connected at all). +# +# The default is unlimited and thus provides no protection against clients overwhelming the server. You are +# encouraged to set a maximum that makes sense for you in production, but do keep in mind that +# rpc_max_threads represents the maximum number of client requests this server may execute concurrently. +# +# rpc_min_threads: 16 +# rpc_max_threads: 2048 + +# uncomment to set socket buffer sizes on rpc connections +# rpc_send_buff_size_in_bytes: +# rpc_recv_buff_size_in_bytes: + +# Uncomment to set socket buffer size for internode communication +# Note that when setting this, the buffer size is limited by net.core.wmem_max +# and when not setting it it is defined by net.ipv4.tcp_wmem +# See: +# /proc/sys/net/core/wmem_max +# /proc/sys/net/core/rmem_max +# /proc/sys/net/ipv4/tcp_wmem +# /proc/sys/net/ipv4/tcp_wmem +# and: man tcp +# internode_send_buff_size_in_bytes: +# internode_recv_buff_size_in_bytes: + +# Frame size for thrift (maximum field length). +thrift_framed_transport_size_in_mb: 15 + +# The max length of a thrift message, including all fields and +# internal thrift overhead. +thrift_max_message_length_in_mb: 16 + +# Set to true to have Cassandra create a hard link to each sstable +# flushed or streamed locally in a backups/ subdirectory of the +# keyspace data. Removing these links is the operator's +# responsibility. +incremental_backups: false + +# Whether or not to take a snapshot before each compaction. Be +# careful using this option, since Cassandra won't clean up the +# snapshots for you. Mostly useful if you're paranoid when there +# is a data format change. +snapshot_before_compaction: false + +# Whether or not a snapshot is taken of the data before keyspace truncation +# or dropping of column families. The STRONGLY advised default of true +# should be used to provide data safety. If you set this flag to false, you will +# lose data on truncation or drop. +auto_snapshot: true + +# Add column indexes to a row after its contents reach this size. +# Increase if your column values are large, or if you have a very large +# number of columns. The competing causes are, Cassandra has to +# deserialize this much of the row to read a single column, so you want +# it to be small - at least if you do many partial-row reads - but all +# the index data is read for each access, so you don't want to generate +# that wastefully either. +column_index_size_in_kb: 64 + +# Size limit for rows being compacted in memory. Larger rows will spill +# over to disk and use a slower two-pass compaction process. A message +# will be logged specifying the row key. +in_memory_compaction_limit_in_mb: 64 + +# Number of simultaneous compactions to allow, NOT including +# validation "compactions" for anti-entropy repair. Simultaneous +# compactions can help preserve read performance in a mixed read/write +# workload, by mitigating the tendency of small sstables to accumulate +# during a single long running compactions. The default is usually +# fine and if you experience problems with compaction running too +# slowly or too fast, you should look at +# compaction_throughput_mb_per_sec first. +# +# concurrent_compactors defaults to the number of cores. +# Uncomment to make compaction mono-threaded, the pre-0.8 default. +#concurrent_compactors: 1 + +# Multi-threaded compaction. When enabled, each compaction will use +# up to one thread per core, plus one thread per sstable being merged. +# This is usually only useful for SSD-based hardware: otherwise, +# your concern is usually to get compaction to do LESS i/o (see: +# compaction_throughput_mb_per_sec), not more. +multithreaded_compaction: false + +# Throttles compaction to the given total throughput across the entire +# system. The faster you insert data, the faster you need to compact in +# order to keep the sstable count down, but in general, setting this to +# 16 to 32 times the rate you are inserting data is more than sufficient. +# Setting this to 0 disables throttling. Note that this account for all types +# of compaction, including validation compaction. +compaction_throughput_mb_per_sec: 16 + +# Track cached row keys during compaction, and re-cache their new +# positions in the compacted sstable. Disable if you use really large +# key caches. +compaction_preheat_key_cache: true + +# Throttles all outbound streaming file transfers on this node to the +# given total throughput in Mbps. This is necessary because Cassandra does +# mostly sequential IO when streaming data during bootstrap or repair, which +# can lead to saturating the network connection and degrading rpc performance. +# When unset, the default is 200 Mbps or 25 MB/s. +# stream_throughput_outbound_megabits_per_sec: 200 + +# How long the coordinator should wait for read operations to complete +read_request_timeout_in_ms: 10000 +# How long the coordinator should wait for seq or index scans to complete +range_request_timeout_in_ms: 10000 +# How long the coordinator should wait for writes to complete +write_request_timeout_in_ms: 10000 +# How long the coordinator should wait for truncates to complete +# (This can be much longer, because unless auto_snapshot is disabled +# we need to flush first so we can snapshot before removing the data.) +truncate_request_timeout_in_ms: 60000 +# The default timeout for other, miscellaneous operations +request_timeout_in_ms: 10000 + +# Enable operation timeout information exchange between nodes to accurately +# measure request timeouts, If disabled cassandra will assuming the request +# was forwarded to the replica instantly by the coordinator +# +# Warning: before enabling this property make sure to ntp is installed +# and the times are synchronized between the nodes. +cross_node_timeout: false + +# Enable socket timeout for streaming operation. +# When a timeout occurs during streaming, streaming is retried from the start +# of the current file. This _can_ involve re-streaming an important amount of +# data, so you should avoid setting the value too low. +# Default value is 0, which never timeout streams. +# streaming_socket_timeout_in_ms: 0 + +# phi value that must be reached for a host to be marked down. +# most users should never need to adjust this. +# phi_convict_threshold: 8 + +# endpoint_snitch -- Set this to a class that implements +# IEndpointSnitch. The snitch has two functions: +# - it teaches Cassandra enough about your network topology to route +# requests efficiently +# - it allows Cassandra to spread replicas around your cluster to avoid +# correlated failures. It does this by grouping machines into +# "datacenters" and "racks." Cassandra will do its best not to have +# more than one replica on the same "rack" (which may not actually +# be a physical location) +# +# IF YOU CHANGE THE SNITCH AFTER DATA IS INSERTED INTO THE CLUSTER, +# YOU MUST RUN A FULL REPAIR, SINCE THE SNITCH AFFECTS WHERE REPLICAS +# ARE PLACED. +# +# Out of the box, Cassandra provides +# - SimpleSnitch: +# Treats Strategy order as proximity. This improves cache locality +# when disabling read repair, which can further improve throughput. +# Only appropriate for single-datacenter deployments. +# - PropertyFileSnitch: +# Proximity is determined by rack and data center, which are +# explicitly configured in cassandra-topology.properties. +# - GossipingPropertyFileSnitch +# The rack and datacenter for the local node are defined in +# cassandra-rackdc.properties and propagated to other nodes via gossip. If +# cassandra-topology.properties exists, it is used as a fallback, allowing +# migration from the PropertyFileSnitch. +# - RackInferringSnitch: +# Proximity is determined by rack and data center, which are +# assumed to correspond to the 3rd and 2nd octet of each node's +# IP address, respectively. Unless this happens to match your +# deployment conventions (as it did Facebook's), this is best used +# as an example of writing a custom Snitch class. +# - Ec2Snitch: +# Appropriate for EC2 deployments in a single Region. Loads Region +# and Availability Zone information from the EC2 API. The Region is +# treated as the datacenter, and the Availability Zone as the rack. +# Only private IPs are used, so this will not work across multiple +# Regions. +# - Ec2MultiRegionSnitch: +# Uses public IPs as broadcast_address to allow cross-region +# connectivity. (Thus, you should set seed addresses to the public +# IP as well.) You will need to open the storage_port or +# ssl_storage_port on the public IP firewall. (For intra-Region +# traffic, Cassandra will switch to the private IP after +# establishing a connection.) +# +# You can use a custom Snitch by setting this to the full class name +# of the snitch, which will be assumed to be on your classpath. +endpoint_snitch: SimpleSnitch + +# controls how often to perform the more expensive part of host score +# calculation +dynamic_snitch_update_interval_in_ms: 100 +# controls how often to reset all host scores, allowing a bad host to +# possibly recover +dynamic_snitch_reset_interval_in_ms: 600000 +# if set greater than zero and read_repair_chance is < 1.0, this will allow +# 'pinning' of replicas to hosts in order to increase cache capacity. +# The badness threshold will control how much worse the pinned host has to be +# before the dynamic snitch will prefer other replicas over it. This is +# expressed as a double which represents a percentage. Thus, a value of +# 0.2 means Cassandra would continue to prefer the static snitch values +# until the pinned host was 20% worse than the fastest. +dynamic_snitch_badness_threshold: 0.1 + +# request_scheduler -- Set this to a class that implements +# RequestScheduler, which will schedule incoming client requests +# according to the specific policy. This is useful for multi-tenancy +# with a single Cassandra cluster. +# NOTE: This is specifically for requests from the client and does +# not affect inter node communication. +# org.apache.cassandra.scheduler.NoScheduler - No scheduling takes place +# org.apache.cassandra.scheduler.RoundRobinScheduler - Round robin of +# client requests to a node with a separate queue for each +# request_scheduler_id. The scheduler is further customized by +# request_scheduler_options as described below. +request_scheduler: org.apache.cassandra.scheduler.NoScheduler + +# Scheduler Options vary based on the type of scheduler +# NoScheduler - Has no options +# RoundRobin +# - throttle_limit -- The throttle_limit is the number of in-flight +# requests per client. Requests beyond +# that limit are queued up until +# running requests can complete. +# The value of 80 here is twice the number of +# concurrent_reads + concurrent_writes. +# - default_weight -- default_weight is optional and allows for +# overriding the default which is 1. +# - weights -- Weights are optional and will default to 1 or the +# overridden default_weight. The weight translates into how +# many requests are handled during each turn of the +# RoundRobin, based on the scheduler id. +# +# request_scheduler_options: +# throttle_limit: 80 +# default_weight: 5 +# weights: +# Keyspace1: 1 +# Keyspace2: 5 + +# request_scheduler_id -- An identifier based on which to perform +# the request scheduling. Currently the only valid option is keyspace. +# request_scheduler_id: keyspace + +# index_interval controls the sampling of entries from the primrary +# row index in terms of space versus time. The larger the interval, +# the smaller and less effective the sampling will be. In technicial +# terms, the interval coresponds to the number of index entries that +# are skipped between taking each sample. All the sampled entries +# must fit in memory. Generally, a value between 128 and 512 here +# coupled with a large key cache size on CFs results in the best trade +# offs. This value is not often changed, however if you have many +# very small rows (many to an OS page), then increasing this will +# often lower memory usage without a impact on performance. +index_interval: 128 + +# Enable or disable inter-node encryption +# Default settings are TLS v1, RSA 1024-bit keys (it is imperative that +# users generate their own keys) TLS_RSA_WITH_AES_128_CBC_SHA as the cipher +# suite for authentication, key exchange and encryption of the actual data transfers. +# NOTE: No custom encryption options are enabled at the moment +# The available internode options are : all, none, dc, rack +# +# If set to dc cassandra will encrypt the traffic between the DCs +# If set to rack cassandra will encrypt the traffic between the racks +# +# The passwords used in these options must match the passwords used when generating +# the keystore and truststore. For instructions on generating these files, see: +# http://download.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#CreateKeystore +# +server_encryption_options: + internode_encryption: none + keystore: conf/.keystore + keystore_password: cassandra + truststore: conf/.truststore + truststore_password: cassandra + # More advanced defaults below: + # protocol: TLS + # algorithm: SunX509 + # store_type: JKS + # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA] + # require_client_auth: false + +# enable or disable client/server encryption. +client_encryption_options: + enabled: false + keystore: conf/.keystore + keystore_password: cassandra + # require_client_auth: false + # Set trustore and truststore_password if require_client_auth is true + # truststore: conf/.truststore + # truststore_password: cassandra + # More advanced defaults below: + # protocol: TLS + # algorithm: SunX509 + # store_type: JKS + # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA] + +# internode_compression controls whether traffic between nodes is +# compressed. +# can be: all - all traffic is compressed +# dc - traffic between different datacenters is compressed +# none - nothing is compressed. +internode_compression: all + +# Enable or disable tcp_nodelay for inter-dc communication. +# Disabling it will result in larger (but fewer) network packets being sent, +# reducing overhead from the TCP protocol itself, at the cost of increasing +# latency if you block for cross-datacenter responses. +# inter_dc_tcp_nodelay: true diff --git a/spring-data-cassandra/src/test/resources/cassandraOperationsTest-cql-dataload.cql b/spring-data-cassandra/src/test/resources/cassandraOperationsTest-cql-dataload.cql new file mode 100644 index 000000000..239ae3e25 --- /dev/null +++ b/spring-data-cassandra/src/test/resources/cassandraOperationsTest-cql-dataload.cql @@ -0,0 +1,3 @@ +create table book (isbn text, title text, author text, pages int, PRIMARY KEY (isbn)); +create table book_alt (isbn text, title text, author text, pages int, PRIMARY KEY (isbn)); +insert into book (isbn, title, author, pages) values ('999999999', 'Book of Nines', 'Nine Nine', 999); \ No newline at end of file diff --git a/spring-data-cassandra/src/test/resources/cql-dataload.cql b/spring-data-cassandra/src/test/resources/cql-dataload.cql new file mode 100644 index 000000000..e38d18d36 --- /dev/null +++ b/spring-data-cassandra/src/test/resources/cql-dataload.cql @@ -0,0 +1,3 @@ +create table book (isbn text, title text, author text, pages int, PRIMARY KEY (isbn)); +create table book_alt (isbn text, title text, author text, pages int, PRIMARY KEY (isbn)); +/*insert into book (isbn, title, author, pages) values ('999999999', 'Book of Nines', 'Nine Nine', 999);*/ \ No newline at end of file diff --git a/spring-data-cassandra/src/test/resources/log4j.properties b/spring-data-cassandra/src/test/resources/log4j.properties new file mode 100644 index 000000000..6e2ec3286 --- /dev/null +++ b/spring-data-cassandra/src/test/resources/log4j.properties @@ -0,0 +1,6 @@ +log4j.rootLogger=WARN, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c - %m%n +log4j.logger.org.springframework.data.cassandra=INFO + diff --git a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/CassandraNamespaceTests-context.xml b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/CassandraNamespaceTests-context.xml new file mode 100644 index 000000000..4050ec523 --- /dev/null +++ b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/CassandraNamespaceTests-context.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/cassandra.properties b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/cassandra.properties new file mode 100644 index 000000000..6a0dd3197 --- /dev/null +++ b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/cassandra.properties @@ -0,0 +1,7 @@ +cassandra.contactPoints=localhost +cassandra.port=9042 +cassandra.keyspace=TestKS123 + + + + diff --git a/spring-data-cassandra/template.mf b/spring-data-cassandra/template.mf new file mode 100644 index 000000000..3cbe47034 --- /dev/null +++ b/spring-data-cassandra/template.mf @@ -0,0 +1,30 @@ +Bundle-SymbolicName: org.springframework.data.cassandra +Bundle-Name: Spring Data Cassandra +Bundle-Vendor: Mirantis +Bundle-ManifestVersion: 2 +Import-Package: + sun.reflect;version="0";resolution:=optional +Import-Template: + org.springframework.beans.*;version="[3.1.0, 4.0.0)", + org.springframework.cache.*;version="[3.1.0, 4.0.0)", + org.springframework.context.*;version="[3.1.0, 4.0.0)", + org.springframework.core.*;version="[3.1.0, 4.0.0)", + org.springframework.dao.*;version="[3.1.0, 4.0.0)", + org.springframework.scheduling.*;resolution:="optional";version="[3.1.0, 4.0.0)", + org.springframework.util.*;version="[3.1.0, 4.0.0)", + org.springframework.oxm.*;resolution:="optional";version="[3.1.0, 4.0.0)", + org.springframework.transaction.support.*;version="[3.1.0, 4.0.0)", + org.springframework.data.*;version="[1.5.0, 2.0.0)", + org.springframework.expression.*;version="[3.1.0, 4.0.0)", + org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional, + org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", + org.w3c.dom.*;version="0", + javax.xml.transform.*;resolution:="optional";version="0", + com.datastax.driver.core.*;resolution:="optional";version="[0.1.0, 1.0.0)", + org.apache.cassandra.db.marshal.*;version="[1.2.0, 1.3.0)", + org.slf4j.*;version="[1.5.0, 1.8.0)", + org.idevlab.rjc.*;resolution:="optional";version="[0.6.4, 0.6.4]", + org.apache.commons.pool.impl.*;resolution:="optional";version="[1.0.0, 3.0.0)", + org.codehaus.jackson.*;resolution:="optional";version="[1.6, 2.0.0)", + org.apache.commons.beanutils.*;resolution:="optional";version=1.8.5, + com.google.common.*;resolution:="optional";version="[11.0.0, 20.0.0)" \ No newline at end of file