DATACASS-172 - Support for UDTs.

We now support Cassandra User-defined types. UDTs can be created using CQL generators and used inside of mapped domain classes. User-defined types can be used either raw as UDTValue that is passed through or as mapped object. Mapped UDTs must be annotated with @UserDefinedType. Types are included into schema generation so known and defined UDTs are created before any tables are created. UDTs can be used with set and list collection types and in primary keys. UDTs can also be used in repository query methods as query predicates.
Updating UDTs will update the whole UDT.

@UserDefinedType
public class Address {
  String city;
  String country;
}

@Table
public class Person {

  @Id String id;
  Address address;
  UDTValue genericUdt;
}

The XML namespace support was extended with new schema versions to support provide a User Type resolver so UDTs can be resolved:

<cassandra:mapping>
  <cassandra:user-type-resolver keyspace-name="${cassandra.keyspace}" />
</cassandra:mapping>
This commit is contained in:
Mark Paluch
2016-09-07 13:12:46 +02:00
committed by John Blum
parent 84f680ee3e
commit cfa8a6177f
82 changed files with 4924 additions and 627 deletions

View File

@@ -33,18 +33,23 @@ public class RenameColumnCqlGenerator extends ColumnChangeCqlGenerator<RenameCol
static final String RENAME = "RENAME";
final String keyword;
private final String keyword;
RenameColumnCqlGenerator(RenameColumnSpecification specification) {
this(RENAME, specification);
}
public RenameColumnCqlGenerator(String keyword, ColumnChangeSpecification specification) {
/**
* @param keyword the keyword to use for {@code RENAME}.
* @param specification the specification.
*/
RenameColumnCqlGenerator(String keyword, ColumnChangeSpecification specification) {
super(specification);
this.keyword = keyword;
}
/* (non-Javadoc)
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.cql.generator.ColumnChangeCqlGenerator#toCql(java.lang.StringBuilder)
*/
public StringBuilder toCql(StringBuilder cql) {

View File

@@ -23,7 +23,7 @@ import org.springframework.util.Assert;
*
* @author Fabio J. Mendes
* @author Mark Paluch
* @param <T> The subtype of the {@link UserTypeNameSpecification}
* @param <T> Subtype of {@link UserTypeNameSpecification}.
* @since 1.5
* @see CqlIdentifier
*/

View File

@@ -1,2 +1,3 @@
http\://www.springframework.org/schema/cql/spring-cql-1.0.xsd=org/springframework/cassandra/config/spring-cql-1.0.xsd
http\://www.springframework.org/schema/cql/spring-cql.xsd=org/springframework/cassandra/config/spring-cql-1.0.xsd
http\://www.springframework.org/schema/cql/spring-cql-1.5.xsd=org/springframework/cassandra/config/spring-cql-1.5.xsd
http\://www.springframework.org/schema/cql/spring-cql.xsd=org/springframework/cassandra/config/spring-cql-1.5.xsd

View File

@@ -0,0 +1,730 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2016 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.
-->
<xsd:schema xmlns="http://www.springframework.org/schema/cql"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.springframework.org/schema/cql"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/tool"
schemaLocation="http://www.springframework.org/schema/tool/spring-tool.xsd" />
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the configuration elements in the XML namespace for Spring Cassandra.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType name="executorRef" final="union">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="java.util.concurrent.Executor"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
<xsd:element name="cluster" type="clusterType">
<xsd:annotation>
<xsd:documentation
source="org.springframework.cassandra.config.xml.CassandraClusterFactoryBean"><![CDATA[
Defines a Cassandra cluster.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="com.datastax.driver.core.Cluster" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<xsd:simpleType name="clusterRef" final="union">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="com.datastax.driver.core.Cluster"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
<xsd:complexType name="clusterType">
<xsd:sequence>
<xsd:element name="local-pooling-options" type="poolingOptionsType" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[
Local pooling options.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="remote-pooling-options" type="poolingOptionsType" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[
Remote pooling options.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="socket-options" type="socketOptionsType" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[
Socket options.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="keyspace" type="keyspaceType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
Provides the ability to define a keyspace.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="startup-cql" type="xsd:string" minOccurs="0" maxOccurs="unbounded">
<!-- TODO: cql could come from a resource via a resource attribute... -->
<xsd:annotation>
<xsd:documentation><![CDATA[
Arbitrary CQL script to be executed against the system keyspace during bean initialization. Multiple elements will be executed in document order.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="shutdown-cql" type="xsd:string" minOccurs="0" maxOccurs="unbounded">
<!-- TODO: cql could come from a resource via a resource attribute... -->
<xsd:annotation>
<xsd:documentation><![CDATA[
Arbitrary CQL script to be executed against the system keyspace during bean destruction. Multiple elements will be executed in document order.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the Cassandra Cluster definition; default is "cassandra-cluster".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="address-translator-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Configures the address translator to use for the new cluster.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="com.datastax.driver.core.policies.AddressTranslator"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="auth-info-provider-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
AuthInfoProvider implementation.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="com.datastax.driver.core.AuthInfoProvider" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="cluster-builder-configurer-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets the ClusterBuilderConfigurer used to apply additional configuration logic
to the com.datastax.driver.core.Cluster.Builder.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.cassandra.config.ClusterBuilderConfigurer"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="cluster-name" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
An optional name for the create cluster.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="compression" type="xsd:string" default="NONE" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The protocol compression option. Default is "NONE".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="contact-points" type="xsd:string" default="localhost" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The comma separated list of Cassandra servers. Default is "localhost".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="heartbeat-interval-seconds" type="xsd:string" default="30" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Pooling option to set the heartbeat interval seconds, after which a message is sent on an idle connection
to make sure it's still alive. Applies to both local and remote pooling options (see
com.datastax.driver.core.HostDistance and com.datastax.driver.core.PoolingOptions) for more details.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="host-state-listener-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Custom Host State Listener for the Cassandra Cluster.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="com.datastax.driver.core.Host.StateListener" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="idle-timeout-seconds" type="xsd:string" default="120" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Pooling option to set the timeout in seconds before an idle connection is removed. Applies to both local and remote
pooling options (see com.datastax.driver.core.HostDistance and com.datastax.driver.core.PoolingOptions) for more details.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="initialization-executor-ref" type="executorRef" use="optional">
<xsd:annotation>
<xsd:documentation source="org.springframework.cassandra.config.PoolingOptionsFactoryBean"><![CDATA[
Pooling option defining a reference to an Executor used to initialize the Cassandra Pool. Applies to both local
and remote pooling options (see com.datastax.driver.core.HostDistance
and com.datastax.driver.core.PoolingOptions) for more details.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="jmx-reporting-enabled" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
Determine whether or not to enable JMX Reporting. Defaults to true.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="latency-tracker-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Custom Latency Tracker for the Cassandra Cluster.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="com.datastax.driver.core.LatencyTracker" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="load-balancing-policy-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
LoadBalancingPolicy implementation.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to
type="com.datastax.driver.core.policies.LoadBalancingPolicy" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="netty-options-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
NettyOptions implementation reference.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to
type="com.datastax.driver.core.NettyOptions" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="max-queue-size" type="xsd:string" default="256" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets the maximum number of requests that get enqueued if no connection is available.
If the queue grows past this value, new requests will be rejected immediately (and the driver will move to the
next host in the query plan). This limit is per connection pool, not global to the driver.
See com.datastax.driver.core.PoolingOption for more details. Available since Cassandra Driver 3.1.1.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="max-schema-agreement-wait-seconds" type="xsd:string" default="10" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets the maximum time to wait for schema agreement before returning from a DDL query. Defaults to 10 seconds.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="metrics-enabled" type="xsd:string"
default="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
Determine whether or not to collect metrics. Defaults to true.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="password" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
When Authentication is enabled, the password to use when connecting to the Cluster.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="pool-timeout-milliseconds" type="xsd:string" default="5000" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Pooling option to set the timeout in milliseconds when trying to acquire a connection from a host's pool. Applies to
both local and remote pooling options (see com.datastax.driver.core.HostDistance
and com.datastax.driver.core.PoolingOptions) for more details.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="port" type="xsd:string" use="optional" default="9042">
<xsd:annotation>
<xsd:documentation><![CDATA[
The native CQL port to connect to. Default is 9042.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="reconnection-policy-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
ReconnectionPolicy implementation.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to
type="com.datastax.driver.core.policies.ReconnectionPolicy" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="retry-policy-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
RetryPolicy implementation.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to
type="com.datastax.driver.core.policies.RetryPolicy" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="speculative-execution-policy-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Configures the speculative execution policy to use for the new cluster.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="com.datastax.driver.core.policies.SpeculativeExecutionPolicy"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="ssl-enabled" type="xsd:string" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
Determine if SSL is used for Cassandra communication. Defaults to false.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="ssl-options-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Custom SSL Options. sslEnabled must be true for sslOptions to be used.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="com.datastax.driver.core.SSLOptions" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="timestamp-generator-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Configures the generator that will produce the client-side timestamp sent with each query.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="com.datastax.driver.core.TimestampGenerator"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="username" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
When Authentication is enabled, the username to use when connecting to the Cluster.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="session" type="sessionType">
<xsd:annotation>
<xsd:documentation
source="org.springframework.data.cassandra.config.xml.CassandraDataSessionFactoryBean"><![CDATA[
Defines a Cassandra session.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="com.datastax.driver.core.Session" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<xsd:simpleType name="sessionRef" final="union">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="com.datastax.driver.core.Session" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
<xsd:complexType name="sessionType">
<xsd:sequence>
<xsd:element name="startup-cql" type="xsd:string" minOccurs="0" maxOccurs="unbounded">
<!-- TODO: cql could come from a resource via a resource attribute... -->
<xsd:annotation>
<xsd:documentation><![CDATA[
Arbitrary CQL script to be executed against the session's keyspace during bean initialization. Multiple elements will be executed in document order.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="shutdown-cql" type="xsd:string" minOccurs="0" maxOccurs="unbounded">
<!-- TODO: cql could come from a resource via a resource attribute... -->
<xsd:annotation>
<xsd:documentation><![CDATA[
Arbitrary CQL script to be executed against the session's keyspace during bean destruction. Multiple elements will be executed in document order.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the session definition; default is "cassandra-session".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="cluster-ref" type="clusterRef" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The reference to a Cassandra cluster; default is "cassandra-cluster".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="keyspace-name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of a Cassandra Keyspace. No default; for the system keyspace, use the empty string.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="template" type="templateType">
<xsd:annotation>
<xsd:documentation
source="org.springframework.data.cassandra.config.xml.CassandraDataTemplateFactoryBean"><![CDATA[
Defines a CassandraTemplate.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports
type="org.springframework.data.cassandra.CassandraTemplate" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<xsd:complexType name="templateType">
<xsd:attribute name="id" type="xsd:ID" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the template; default is "cassandraTemplate".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="session-ref" type="sessionRef" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The reference to a Cassandra session; default is "cassandra-session".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="datacenterType">
<xsd:annotation>
<xsd:documentation><![CDATA[
Provides the ability to specify replication factors by data center.
]]></xsd:documentation>
</xsd:annotation>
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the data center.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="replication-factor" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The replication factor for the data center.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="keyspaceType">
<xsd:annotation>
<xsd:documentation><![CDATA[
Provides the ability to define keyspaces.
]]></xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="replication" type="replicationType" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[
Provides the ability to configure the keyspace's replication settings.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="action" use="required" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The keyspace action to take at startup and possibly shutdown.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="durable-writes" type="xsd:string" use="optional" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
Whether or not the keyspace supports durable writes.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of this keyspace. Required.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="poolingOptionsType">
<xsd:attribute name="core-connections" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
For each host, the driver keeps a core amount of connections open at all time.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="max-connections" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
More connections are created up to a configurable maximum number of connections.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="max-simultaneous-requests" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
If the utilisation of connections reaches this configurable threshold, then cassandra creates more connections up to max-connections.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="min-simultaneous-requests" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
If the utilisation of opened connections drops below by this configured threshold, then cassandra drops connections till core-connections.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="socketOptionsType">
<xsd:attribute name="connect-timeout-millis" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets connection timeout for client socket in milliseconds.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="keep-alive" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets the SO_KEEPALIVE socket option.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="read-timeout-millis" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets read timeout for client socket in milliseconds.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="receive-buffer-size" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets the SO_RCVBUF socket option.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="reuse-address" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets the SO_REUSEADDR socket option.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="send-buffer-size" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets the SO_SNDBUF socket option.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="so-linger" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets the SO_LINGER socket option.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="tcp-no-delay" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets the SO_TCPNODELAY socket option.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="replicationType">
<xsd:annotation>
<xsd:documentation><![CDATA[
Provides the ability to configure the keyspace's replication settings.
]]></xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="data-center" type="datacenterType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
Provides the ability to specify replication factors by data center.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="class" type="xsd:string" use="optional" default="SimpleStrategy">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the replication class; default is "SIMPLE_STRATEGY".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="replication-factor" type="xsd:string" use="optional" default="1">
<xsd:annotation>
<xsd:documentation><![CDATA[
The replication factor; default is 1.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:schema>

View File

@@ -102,5 +102,4 @@ public class AlterUserTypeCqlGeneratorIntegrationTests extends AbstractKeyspaceC
public void generationFailsWithoutFields() {
toCql(AlterUserTypeSpecification.alterType().name("hello"));
}
}

View File

@@ -15,8 +15,7 @@
*/
package org.springframework.cassandra.core.cql.generator;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.cassandra.core.cql.generator.AlterUserTypeCqlGenerator.*;
import org.junit.Test;
@@ -40,7 +39,7 @@ public class AlterUserTypeCqlGeneratorUnitTests {
AlterUserTypeSpecification spec = AlterUserTypeSpecification.alterType("address") //
.add("zip", DataType.varchar());
assertThat(toCql(spec), is(equalTo("ALTER TYPE address ADD zip varchar;")));
assertThat(toCql(spec)).isEqualTo("ALTER TYPE address ADD zip varchar;");
}
/**
@@ -52,7 +51,7 @@ public class AlterUserTypeCqlGeneratorUnitTests {
AlterUserTypeSpecification spec = AlterUserTypeSpecification.alterType("address") //
.alter("zip", DataType.varchar());
assertThat(toCql(spec), is(equalTo("ALTER TYPE address ALTER zip TYPE varchar;")));
assertThat(toCql(spec)).isEqualTo("ALTER TYPE address ALTER zip TYPE varchar;");
}
/**
@@ -64,7 +63,7 @@ public class AlterUserTypeCqlGeneratorUnitTests {
AlterUserTypeSpecification spec = AlterUserTypeSpecification.alterType("address") //
.rename("zip", "zap");
assertThat(toCql(spec), is(equalTo("ALTER TYPE address RENAME zip TO zap;")));
assertThat(toCql(spec)).isEqualTo("ALTER TYPE address RENAME zip TO zap;");
}
/**
@@ -77,7 +76,7 @@ public class AlterUserTypeCqlGeneratorUnitTests {
.rename("zip", "zap") //
.rename("city", "county");
assertThat(toCql(spec), is(equalTo("ALTER TYPE address RENAME zip TO zap AND city TO county;")));
assertThat(toCql(spec)).isEqualTo("ALTER TYPE address RENAME zip TO zap AND city TO county;");
}
/**

View File

@@ -15,8 +15,7 @@
*/
package org.springframework.cassandra.core.cql.generator;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cassandra.core.cql.generator.CreateUserTypeCqlGenerator.*;
import org.junit.Before;
@@ -57,7 +56,7 @@ public class CreateUserTypeCqlGeneratorIntegrationTests extends AbstractKeyspace
KeyspaceMetadata keyspace = session.getCluster().getMetadata().getKeyspace(session.getLoggedKeyspace());
UserType address = keyspace.getUserType("address");
assertThat(address.getFieldNames(), contains("zip", "city"));
assertThat(address.getFieldNames()).contains("zip", "city");
}
/**
@@ -75,7 +74,7 @@ public class CreateUserTypeCqlGeneratorIntegrationTests extends AbstractKeyspace
KeyspaceMetadata keyspace = session.getCluster().getMetadata().getKeyspace(session.getLoggedKeyspace());
UserType address = keyspace.getUserType("address");
assertThat(address.getFieldNames(), contains("zip", "city"));
assertThat(address.getFieldNames()).contains("zip", "city");
}
/**

View File

@@ -15,8 +15,7 @@
*/
package org.springframework.cassandra.core.cql.generator;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.cassandra.core.cql.generator.CreateUserTypeCqlGenerator.*;
import org.junit.Test;
@@ -41,7 +40,7 @@ public class CreateUserTypeCqlGeneratorUnitTests {
.createType("address") //
.field("city", DataType.varchar());
assertThat(toCql(spec), is(equalTo("CREATE TYPE address (city varchar);")));
assertThat(toCql(spec)).isEqualTo("CREATE TYPE address (city varchar);");
}
/**
@@ -55,7 +54,7 @@ public class CreateUserTypeCqlGeneratorUnitTests {
.field("zip", DataType.ascii()) //
.field("city", DataType.varchar());
assertThat(toCql(spec), is(equalTo("CREATE TYPE address (zip ascii, city varchar);")));
assertThat(toCql(spec)).isEqualTo("CREATE TYPE address (zip ascii, city varchar);");
}
/**
@@ -69,7 +68,7 @@ public class CreateUserTypeCqlGeneratorUnitTests {
.name("address").ifNotExists().field("zip", DataType.ascii()) //
.field("city", DataType.varchar());
assertThat(toCql(spec), is(equalTo("CREATE TYPE IF NOT EXISTS address (zip ascii, city varchar);")));
assertThat(toCql(spec)).isEqualTo("CREATE TYPE IF NOT EXISTS address (zip ascii, city varchar);");
}
/**

View File

@@ -15,8 +15,7 @@
*/
package org.springframework.cassandra.core.cql.generator;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cassandra.core.cql.generator.DropUserTypeCqlGenerator.*;
import org.junit.Test;
@@ -29,19 +28,25 @@ import org.springframework.cassandra.core.keyspace.DropUserTypeSpecification;
*/
public class DropUserTypeCqlGeneratorUnitTests {
/**
* @see DATACASS-172
*/
@Test
public void shouldDropUserType() throws Exception {
public void shouldDropUserType() {
DropUserTypeSpecification spec = DropUserTypeSpecification.dropType("address");
assertThat(toCql(spec), is(equalTo("DROP TYPE address;")));
assertThat(toCql(spec)).isEqualTo("DROP TYPE address;");
}
/**
* @see DATACASS-172
*/
@Test
public void shouldDropUserTypeIfExists() throws Exception {
public void shouldDropUserTypeIfExists() {
DropUserTypeSpecification spec = DropUserTypeSpecification.dropType("address").ifExists();
assertThat(toCql(spec), is(equalTo("DROP TYPE IF EXISTS address;")));
assertThat(toCql(spec)).isEqualTo("DROP TYPE IF EXISTS address;");
}
}

View File

@@ -41,7 +41,7 @@ import com.datastax.driver.core.policies.AddressTranslator;
import com.datastax.driver.core.policies.SpeculativeExecutionPolicy;
/**
* Test XML namespace configuration using the spring-cql-1.0.xsd.
* Test XML namespace configuration using the spring-cql XSD.
*
* @author Matthews T. Adams
* @author Oliver Gierke

View File

@@ -3,7 +3,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cass="http://www.springframework.org/schema/cql"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/cql http://www.springframework.org/schema/cql/spring-cql-1.0.xsd
http://www.springframework.org/schema/cql http://www.springframework.org/schema/cql/spring-cql.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

View File

@@ -3,7 +3,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cass="http://www.springframework.org/schema/cql"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/cql http://www.springframework.org/schema/cql/spring-cql-1.0.xsd
http://www.springframework.org/schema/cql http://www.springframework.org/schema/cql/spring-cql.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

View File

@@ -2,7 +2,7 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cass="http://www.springframework.org/schema/cql"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/cql http://www.springframework.org/schema/cql/spring-cql-1.0.xsd
xsi:schemaLocation="http://www.springframework.org/schema/cql http://www.springframework.org/schema/cql/spring-cql.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

View File

@@ -16,30 +16,24 @@
package org.springframework.data.cassandra.config;
import static org.springframework.cassandra.core.cql.CqlIdentifier.*;
import java.util.Collection;
import org.springframework.cassandra.config.CassandraCqlSessionFactoryBean;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.core.CassandraAdminOperations;
import org.springframework.data.cassandra.core.CassandraAdminTemplate;
import org.springframework.data.cassandra.core.CassandraPersistentEntitySchemaCreator;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import org.springframework.util.Assert;
import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.Metadata;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.TableMetadata;
/**
* Factory to create and configure a Cassandra {@link com.datastax.driver.core.Session} with support
* for executing CQL and initializing the database schema (a.k.a. keyspace).
* Factory to create and configure a Cassandra {@link com.datastax.driver.core.Session} with support for executing CQL
* and initializing the database schema (a.k.a. keyspace).
*
* @author Mathew Adams
* @author David Webb
* @author John Blum
* @author Mark Paluch
* @see com.datastax.driver.core.KeyspaceMetadata
* @see com.datastax.driver.core.TableMetadata
*/
@@ -74,61 +68,37 @@ public class CassandraSessionFactoryBean extends CassandraCqlSessionFactoryBean
/* (non-Javadoc) */
protected void performSchemaAction() {
boolean dropTables = DEFAULT_DROP_TABLES;
boolean drop = DEFAULT_DROP_TABLES;
boolean dropUnused = DEFAULT_DROP_UNUSED_TABLES;
boolean ifNotExists = DEFAULT_CREATE_IF_NOT_EXISTS;
boolean create = false;
switch (schemaAction) {
case RECREATE_DROP_UNUSED:
dropUnused = true;
case RECREATE:
dropTables = true;
drop = true;
case CREATE_IF_NOT_EXISTS:
ifNotExists = SchemaAction.CREATE_IF_NOT_EXISTS.equals(schemaAction);
case CREATE:
createTables(dropTables, dropUnused, ifNotExists);
create = true;
case NONE:
default:
// do nothing
}
}
/* (non-Javadoc) */
protected void createTables(boolean dropTables, boolean dropUnused, boolean ifNotExists) {
if (dropTables) {
dropTables(dropUnused);
}
Collection<? extends CassandraPersistentEntity<?>> entities =
getConverter().getMappingContext().getNonPrimaryKeyEntities();
for (CassandraPersistentEntity<?> entity : entities) {
// TODO: pass specification of user configurable table options
getCassandraAdminOperations().createTable(ifNotExists, entity.getTableName(), entity.getType(), null);
if (create) {
createTables(drop, dropUnused, ifNotExists);
}
}
/* (non-Javadoc) */
@SuppressWarnings("all")
protected void dropTables(boolean dropUnused) {
protected void createTables(boolean drop, boolean dropUnused, boolean ifNotExists) {
String keyspaceName = getKeyspaceName();
CassandraPersistentEntitySchemaCreator schemaCreator = new CassandraPersistentEntitySchemaCreator(
getMappingContext(), getCassandraAdminOperations());
Metadata clusterMetadata = getSession().getCluster().getMetadata();
KeyspaceMetadata keyspaceMetadata = clusterMetadata.getKeyspace(keyspaceName);
// TODO: fix this with KeyspaceIdentifier
keyspaceMetadata = (keyspaceMetadata != null ? keyspaceMetadata
: clusterMetadata.getKeyspace(keyspaceName.toLowerCase()));
Assert.state(keyspaceMetadata != null, String.format("keyspace [%s] does not exist", keyspaceName));
for (TableMetadata table : keyspaceMetadata.getTables()) {
if (dropUnused || getMappingContext().usesTable(table)) {
getCassandraAdminOperations().dropTable(cqlId(table.getName()));
}
}
schemaCreator.createUserTypes(drop, dropUnused, ifNotExists);
schemaCreator.createTables(drop, dropUnused, ifNotExists);
}
/* (non-Javadoc) */

View File

@@ -22,4 +22,5 @@ public interface DefaultBeanNames extends DefaultCqlBeanNames {
public static final String DATA_TEMPLATE = "cassandraTemplate";
public static final String CONVERTER = "cassandraConverter";
public static final String CONTEXT = "cassandraMapping";
public static final String USER_TYPE_RESOLVER = "userTypeResolver";
}

View File

@@ -32,6 +32,7 @@ import org.springframework.data.cassandra.core.CassandraAdminOperations;
import org.springframework.data.cassandra.core.CassandraAdminTemplate;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.SimpleUserTypeResolver;
import org.springframework.data.cassandra.mapping.Table;
import org.springframework.data.mapping.context.MappingContext;
@@ -50,7 +51,7 @@ public abstract class AbstractCassandraConfiguration extends AbstractClusterConf
protected ClassLoader beanClassLoader;
@Bean
public CassandraSessionFactoryBean session() throws Exception {
public CassandraSessionFactoryBean session() throws ClassNotFoundException {
CassandraSessionFactoryBean session = new CassandraSessionFactoryBean();
@@ -74,7 +75,7 @@ public abstract class AbstractCassandraConfiguration extends AbstractClusterConf
* @see #customConversions()
*/
@Bean
public CassandraConverter cassandraConverter() throws Exception {
public CassandraConverter cassandraConverter() throws ClassNotFoundException {
MappingCassandraConverter mappingCassandraConverter = new MappingCassandraConverter(cassandraMapping());
@@ -115,6 +116,7 @@ public abstract class AbstractCassandraConfiguration extends AbstractClusterConf
mappingContext.setCustomConversions(customConversions);
mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder());
mappingContext.setUserTypeResolver(new SimpleUserTypeResolver(cluster().getObject(), getKeyspaceName()));
return mappingContext;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors
* Copyright 2013-2016 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.
@@ -17,20 +17,24 @@ package org.springframework.data.cassandra.config.xml;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.cassandra.config.xml.DefaultCqlBeanNames;
import org.springframework.data.cassandra.config.CassandraEntityClassScanner;
import org.springframework.data.cassandra.config.DefaultBeanNames;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
import org.springframework.data.cassandra.mapping.EntityMapping;
import org.springframework.data.cassandra.mapping.Mapping;
import org.springframework.data.cassandra.mapping.PropertyMapping;
import org.springframework.data.cassandra.mapping.SimpleUserTypeResolver;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
@@ -68,12 +72,12 @@ public class CassandraMappingContextParser extends AbstractSingleBeanDefinitionP
String packages = element.getAttribute("entity-base-packages");
if (StringUtils.hasText(packages)) {
try {
Set<Class<?>> entityClasses = CassandraEntityClassScanner.scan(StringUtils
.commaDelimitedListToStringArray(packages));
Set<Class<?>> entityClasses = CassandraEntityClassScanner
.scan(StringUtils.commaDelimitedListToStringArray(packages));
builder.addPropertyValue("initialEntitySet", entityClasses);
} catch (Exception x) {
throw new IllegalArgumentException(String.format(
"encountered exception while scanning for entity classes in package(s) [%s]", packages), x);
throw new IllegalArgumentException(
String.format("encountered exception while scanning for entity classes in package(s) [%s]", packages), x);
}
}
@@ -88,6 +92,21 @@ public class CassandraMappingContextParser extends AbstractSingleBeanDefinitionP
}
}
List<Element> userTypeResolvers = DomUtils.getChildElementsByTagName(element, "user-type-resolver");
String userTypeResolverRef = element.getAttribute("user-type-resolver-ref");
if (StringUtils.hasText(userTypeResolverRef)) {
if (!userTypeResolvers.isEmpty()) {
throw new IllegalArgumentException("Must not define user-type-resolver and user-type-resolver-ref");
}
builder.addPropertyReference("userTypeResolver", userTypeResolverRef);
}
if(!userTypeResolvers.isEmpty()){
BeanDefinition userTypeResolver = parseUserTypeResolver(userTypeResolvers.get(0));
builder.addPropertyValue("userTypeResolver", userTypeResolver);
}
Mapping mapping = new Mapping();
mapping.setEntityMappings(mappings);
@@ -126,6 +145,22 @@ public class CassandraMappingContextParser extends AbstractSingleBeanDefinitionP
return entityMapping;
}
protected BeanDefinition parseUserTypeResolver(Element entity) {
String keyspaceName = entity.getAttribute("keyspace-name");
if (!StringUtils.hasText(keyspaceName)) {
throw new IllegalStateException("keyspace-name attribute must not be null or empty");
}
String clusterRef = entity.getAttribute("cluster-ref");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SimpleUserTypeResolver.class);
builder.addConstructorArgReference(StringUtils.hasText(clusterRef) ? clusterRef : DefaultCqlBeanNames.CLUSTER);
builder.addConstructorArgValue(keyspaceName);
return builder.getBeanDefinition();
}
protected Map<String, PropertyMapping> parsePropertyMappings(Element entity) {
Map<String, PropertyMapping> pms = new HashMap<String, PropertyMapping>();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors
* Copyright 2013-2016 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.
@@ -15,8 +15,6 @@
*/
package org.springframework.data.cassandra.convert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.cassandra.mapping.CassandraPersistentProperty;
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
import org.springframework.data.mapping.model.PropertyValueProvider;
@@ -26,11 +24,12 @@ import org.springframework.util.Assert;
import com.datastax.driver.core.Row;
/**
* {@link PropertyValueProvider} to read property values from a {@link Row}.
* {@link CassandraValueProvider} to read property values from a {@link Row}.
*
* @author Alex Shvid
* @author Matthew T. Adams
* @author David Webb
* @author Mark Paluch
*/
public class BasicCassandraRowValueProvider implements CassandraRowValueProvider {
@@ -45,6 +44,7 @@ public class BasicCassandraRowValueProvider implements CassandraRowValueProvider
* @param evaluator must not be {@literal null}.
*/
public BasicCassandraRowValueProvider(Row source, DefaultSpELExpressionEvaluator evaluator) {
Assert.notNull(source);
Assert.notNull(evaluator);
@@ -52,6 +52,9 @@ public class BasicCassandraRowValueProvider implements CassandraRowValueProvider
this.evaluator = evaluator;
}
/* (non-Javadoc)
* @see org.springframework.data.mapping.model.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty)
*/
@Override
@SuppressWarnings("unchecked")
public Object getPropertyValue(CassandraPersistentProperty property) {
@@ -64,8 +67,22 @@ public class BasicCassandraRowValueProvider implements CassandraRowValueProvider
return reader.get(property.getColumnName());
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.convert.CassandraRowValueProvider#getRow()
*/
@Override
public Row getRow() {
return reader.getRow();
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.convert.CassandraValueProvider#hasProperty(org.springframework.data.cassandra.mapping.CassandraPersistentProperty)
*/
@Override
public boolean hasProperty(CassandraPersistentProperty property) {
Assert.notNull(property, "CassandraPersistentProperty must not be null");
return getRow().getColumnDefinitions().contains(property.getColumnName().toCql());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors
* Copyright 2013-2016 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.
@@ -15,12 +15,18 @@
*/
package org.springframework.data.cassandra.convert;
import org.springframework.data.cassandra.mapping.CassandraPersistentProperty;
import org.springframework.data.mapping.model.PropertyValueProvider;
import com.datastax.driver.core.Row;
public interface CassandraRowValueProvider extends PropertyValueProvider<CassandraPersistentProperty> {
/**
* {@link CassandraValueProvider} providing values based on a {@link Row}.
*
* @author Matthew T. Adams
* @author Mark Paluch
*/
public interface CassandraRowValueProvider extends CassandraValueProvider {
/**
* @return the underlying {@link Row}.
*/
Row getRow();
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2016 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.CassandraPersistentProperty;
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.CodecRegistry;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.UDTValue;
/**
* {@link CassandraValueProvider} to read property values from a {@link UDTValue}.
*
* @author Mark Paluch
* @since 1.5
*/
public class CassandraUDTValueProvider implements CassandraValueProvider {
private final UDTValue udtValue;
private final CodecRegistry codecRegistry;
private final SpELExpressionEvaluator evaluator;
/**
* Creates a new {@link CassandraUDTValueProvider} with the given {@link UDTValue} and
* {@link DefaultSpELExpressionEvaluator}.
*
* @param udtValue must not be {@literal null}.
* @param codecRegistry must not be {@literal null}.
* @param evaluator must not be {@literal null}.
*/
public CassandraUDTValueProvider(UDTValue udtValue, CodecRegistry codecRegistry,
DefaultSpELExpressionEvaluator evaluator) {
Assert.notNull(udtValue, "UDTValue must not be null");
Assert.notNull(codecRegistry, "CodecRegistry must not be null");
Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null");
this.udtValue = udtValue;
this.codecRegistry = codecRegistry;
this.evaluator = evaluator;
}
/* (non-Javadoc)
* @see org.springframework.data.mapping.model.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty)
*/
@SuppressWarnings("unchecked")
public Object getPropertyValue(CassandraPersistentProperty property) {
String expression = property.getSpelExpression();
if (expression != null) {
return evaluator.evaluate(expression);
}
String name = property.getColumnName().toCql();
DataType fieldType = udtValue.getType().getFieldType(name);
return udtValue.get(name, codecRegistry.codecFor(fieldType));
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.convert.CassandraValueProvider#hasProperty(org.springframework.data.cassandra.mapping.CassandraPersistentProperty)
*/
@Override
public boolean hasProperty(CassandraPersistentProperty property) {
return udtValue.getType().contains(property.getColumnName().toCql());
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2016 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.CassandraPersistentProperty;
import org.springframework.data.mapping.model.PropertyValueProvider;
/**
* {@link PropertyValueProvider} for {@link CassandraPersistentProperty}. This {@link PropertyValueProvider} allows
* querying whether the source contains a data source for {@link CassandraPersistentProperty} like a field or a column.
*
* @author Mark Paluch
* @since 1.5
*/
public interface CassandraValueProvider extends PropertyValueProvider<CassandraPersistentProperty> {
/**
* Returns whether the underlying source contains a data source for the given {@link CassandraPersistentProperty}.
*
* @param property must not be {@literal null}.
* @return {@literal true} if the underlying source contains a data source for the given
* {@link CassandraPersistentProperty}.
*/
boolean hasProperty(CassandraPersistentProperty property);
}

View File

@@ -119,7 +119,7 @@ public class ColumnReader {
return row.getMap(i, keyTypeCodec.getJavaType().getRawType(), valueTypeCodec.getJavaType().getRawType());
}
throw new IllegalStateException("Unknown Collection type encountered. Valid collections are Set, List and Map.");
throw new IllegalStateException("Unknown Collection type encountered. Valid collections are Set, List and Map.");
}
public Row getRow() {

View File

@@ -135,6 +135,12 @@ public class CustomConversions {
* @return
*/
public boolean isSimpleType(Class<?> type) {
// Enums have no native Cassandra support
if (type.isEnum()) {
return false;
}
return simpleTypeHolder.isSimpleType(type);
}

View File

@@ -44,6 +44,7 @@ import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
import org.springframework.data.mapping.model.MappingException;
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;
@@ -52,8 +53,11 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import com.datastax.driver.core.CodecRegistry;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.TypeCodec;
import com.datastax.driver.core.UDTValue;
import com.datastax.driver.core.UserType;
import com.datastax.driver.core.querybuilder.Clause;
import com.datastax.driver.core.querybuilder.Delete;
import com.datastax.driver.core.querybuilder.Insert;
@@ -79,7 +83,7 @@ import com.datastax.driver.core.querybuilder.Update;
* @see org.springframework.data.convert.EntityWriter
*/
public class MappingCassandraConverter extends AbstractCassandraConverter
implements CassandraConverter, ApplicationContextAware, BeanClassLoaderAware {
implements CassandraConverter, ApplicationContextAware, BeanClassLoaderAware {
protected final CassandraMappingContext mappingContext;
protected ApplicationContext applicationContext;
@@ -129,8 +133,8 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
return conversionService.convert(row, type);
}
CassandraPersistentEntity<R> persistentEntity =
(CassandraPersistentEntity<R>) mappingContext.getPersistentEntity(typeInfo);
CassandraPersistentEntity<R> persistentEntity = (CassandraPersistentEntity<R>) mappingContext
.getPersistentEntity(typeInfo);
if (persistentEntity == null) {
throw new MappingException("No mapping metadata found for " + rawType.getName());
@@ -153,8 +157,8 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
DefaultSpELExpressionEvaluator expressionEvaluator = new DefaultSpELExpressionEvaluator(row, spELContext);
BasicCassandraRowValueProvider rowValueProvider = new BasicCassandraRowValueProvider(row, expressionEvaluator);
CassandraPersistentEntityParameterValueProvider parameterProvider =
new CassandraPersistentEntityParameterValueProvider(entity, rowValueProvider, null);
CassandraPersistentEntityParameterValueProvider parameterProvider = new CassandraPersistentEntityParameterValueProvider(
entity, rowValueProvider, null);
EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
S instance = instantiator.createInstance(entity, parameterProvider);
@@ -164,20 +168,56 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
return instance;
}
protected void readPropertiesFromRow(final CassandraPersistentEntity<?> entity,
final BasicCassandraRowValueProvider row, final PersistentPropertyAccessor propertyAccessor) {
protected <S> S readEntityFromUdt(CassandraPersistentEntity<S> entity, UDTValue udtValue) {
DefaultSpELExpressionEvaluator expressionEvaluator = new DefaultSpELExpressionEvaluator(udtValue, spELContext);
CassandraUDTValueProvider valueProvider = new CassandraUDTValueProvider(udtValue, CodecRegistry.DEFAULT_INSTANCE,
expressionEvaluator);
CassandraPersistentEntityParameterValueProvider parameterProvider = new CassandraPersistentEntityParameterValueProvider(
entity, valueProvider, null);
EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
S instance = instantiator.createInstance(entity, parameterProvider);
readProperties(entity, valueProvider, getConvertingAccessor(instance, entity));
return instance;
}
protected void readPropertiesFromRow(final CassandraPersistentEntity<?> entity, final CassandraRowValueProvider row,
final PersistentPropertyAccessor propertyAccessor) {
readProperties(entity, row, propertyAccessor);
}
protected void readProperties(final CassandraPersistentEntity<?> entity, final CassandraValueProvider valueProvider,
final PersistentPropertyAccessor propertyAccessor) {
entity.doWithProperties(new PropertyHandler<CassandraPersistentProperty>() {
@Override
public void doWithPersistentProperty(CassandraPersistentProperty property) {
MappingCassandraConverter.this.readPropertyFromRow(entity, property, row, propertyAccessor);
MappingCassandraConverter.this.readProperty(entity, property, valueProvider, propertyAccessor);
}
});
}
/**
* @param entity
* @param property
* @param valueProvider
* @param propertyAccessor
* @deprecated Use
* {@link #readProperty(CassandraPersistentEntity, CassandraPersistentProperty, CassandraValueProvider, PersistentPropertyAccessor)}
*/
@Deprecated
protected void readPropertyFromRow(CassandraPersistentEntity<?> entity, CassandraPersistentProperty property,
BasicCassandraRowValueProvider row, PersistentPropertyAccessor propertyAccessor) {
CassandraRowValueProvider valueProvider, PersistentPropertyAccessor propertyAccessor) {
readProperty(entity, property, valueProvider, propertyAccessor);
}
protected void readProperty(CassandraPersistentEntity<?> entity, CassandraPersistentProperty property,
CassandraValueProvider valueProvider, PersistentPropertyAccessor propertyAccessor) {
// if true then skip; property was set in constructor
if (entity.isConstructorArgument(property)) {
@@ -192,11 +232,11 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
Object key = propertyAccessor.getProperty(keyProperty);
if (key == null) {
key = instantiatePrimaryKey(keyEntity, keyProperty, row);
key = instantiatePrimaryKey(keyEntity, keyProperty, valueProvider);
}
// now recurse on using the key this time
readPropertiesFromRow(property.getCompositePrimaryKeyEntity(), row, getConvertingAccessor(key, keyEntity));
readProperties(property.getCompositePrimaryKeyEntity(), valueProvider, getConvertingAccessor(key, keyEntity));
// now that the key's properties have been populated, set the key property on the entity
propertyAccessor.setProperty(keyProperty, key);
@@ -204,18 +244,18 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
return;
}
if (!row.getRow().getColumnDefinitions().contains(property.getColumnName().toCql())) {
if (!valueProvider.hasProperty(property)) {
return;
}
Object obj = getReadValue(property, row);
Object obj = getReadValue(property, valueProvider);
propertyAccessor.setProperty(property, obj);
}
@SuppressWarnings("unused")
protected Object instantiatePrimaryKey(CassandraPersistentEntity<?> entity, CassandraPersistentProperty keyProperty,
BasicCassandraRowValueProvider propertyProvider) {
PropertyValueProvider<CassandraPersistentProperty> propertyProvider) {
return instantiators.getInstantiatorFor(entity).createInstance(entity,
new CassandraPersistentEntityParameterValueProvider(entity, propertyProvider, null));
@@ -264,6 +304,8 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
writeSelectWhereFromObject(source, (Select.Where) sink, entity);
} else if (sink instanceof Delete.Where) {
writeDeleteWhereFromObject(source, (Delete.Where) sink, entity);
} else if (sink instanceof UDTValue) {
writeUDTValueWhereFromObject(getConvertingAccessor(source, entity), (UDTValue) sink, entity);
} else {
throw new MappingException("Unknown write target " + sink.getClass().getName());
}
@@ -312,7 +354,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
}
protected void writeUpdateFromWrapper(final ConvertingPropertyAccessor accessor, final Update update,
final CassandraPersistentEntity<?> entity) {
final CassandraPersistentEntity<?> entity) {
entity.doWithProperties(new PropertyHandler<CassandraPersistentProperty>() {
@@ -356,6 +398,32 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
}
}
protected void writeUDTValueWhereFromObject(final ConvertingPropertyAccessor accessor, final UDTValue udtValue,
CassandraPersistentEntity<?> entity) {
entity.doWithProperties(new PropertyHandler<CassandraPersistentProperty>() {
@Override
public void doWithPersistentProperty(CassandraPersistentProperty property) {
Object value = getWriteValue(property, accessor);
if (log.isDebugEnabled()) {
log.debug("writeUDTValueWhereFromObject Property.type {}, Property.value {}", property.getType().getName(),
value);
}
if (log.isDebugEnabled()) {
log.debug("Adding udt.value [{}] - [{}]", property.getColumnName().toCql(), value);
}
TypeCodec<Object> typeCodec = CodecRegistry.DEFAULT_INSTANCE.codecFor(mappingContext.getDataType(property));
udtValue.set(property.getColumnName().toCql(), value, typeCodec);
}
});
}
private Collection<Clause> getWhereClauses(Object source, CassandraPersistentEntity<?> entity) {
Assert.notNull(source, "Id source must not be null");
@@ -370,7 +438,8 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
}
if (id instanceof MapId) {
return getWhereClauses((MapId) id, idProperty != null && idProperty.isCompositePrimaryKey() ? idProperty.getCompositePrimaryKeyEntity() : entity);
return getWhereClauses((MapId) id, idProperty != null && idProperty.isCompositePrimaryKey()
? idProperty.getCompositePrimaryKeyEntity() : entity);
}
if (idProperty == null) {
@@ -389,10 +458,10 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
}
}
TypeCodec<Object> codec = getCodec(idProperty);
if(conversionService.canConvert(id.getClass(), codec.getJavaType().getRawType())){
return Collections.singleton(QueryBuilder.eq(idProperty.getColumnName().toCql(), conversionService.convert(id, codec.getJavaType().getRawType())));
Class<?> targetType = getTargetType(idProperty);
if (conversionService.canConvert(id.getClass(), targetType)) {
return Collections
.singleton(QueryBuilder.eq(idProperty.getColumnName().toCql(), conversionService.convert(id, targetType)));
}
return Collections.singleton(QueryBuilder.eq(idProperty.getColumnName().toCql(), id));
@@ -423,8 +492,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
public void doWithPersistentProperty(CassandraPersistentProperty property) {
TypeCodec<Object> codec = getCodec(property);
Object value = accessor.getProperty(property,
codec.getJavaType().getRawType());
Object value = accessor.getProperty(property, codec.getJavaType().getRawType());
clauses.add(QueryBuilder.eq(property.getColumnName().toCql(), value));
}
});
@@ -441,16 +509,19 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
for (Entry<String, Serializable> entry : id.entrySet()) {
CassandraPersistentProperty persistentProperty = entity.getPersistentProperty(entry.getKey());
if (persistentProperty == null) {
throw new IllegalArgumentException(String.format("MapId contains references [%s] that is an unknown property of [%s]", entry.getKey(), entity.getName()));
throw new IllegalArgumentException(String.format(
"MapId contains references [%s] that is an unknown property of [%s]", entry.getKey(), entity.getName()));
}
clauses.add(QueryBuilder.eq(persistentProperty.getColumnName().toCql(), getWriteValue(persistentProperty, entry.getValue())));
clauses.add(QueryBuilder.eq(persistentProperty.getColumnName().toCql(),
getWriteValue(persistentProperty, entry.getValue())));
}
return clauses;
}
@Override
@SuppressWarnings("unchecked")
public Object getId(Object object, CassandraPersistentEntity<?> entity) {
Assert.notNull(object, "Object instance must not be null");
@@ -471,8 +542,9 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
CassandraPersistentProperty idProperty = entity.getIdProperty();
if (idProperty != null) {
return accessor.getProperty(idProperty, (Class<?>) (idProperty.isCompositePrimaryKey() ? idProperty.getType()
: getCodec(idProperty).getJavaType().getRawType()));
return accessor.getProperty(idProperty,
idProperty.isCompositePrimaryKey() ? (Class<Object>) idProperty.getType()
: (Class<Object>) getTargetType(idProperty));
}
// if the class doesn't have an id property, then it's using MapId
@@ -522,7 +594,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
private ConvertingPropertyAccessor getConvertingAccessor(Object source, CassandraPersistentEntity<?> entity) {
PersistentPropertyAccessor propertyAccessor = (source instanceof PersistentPropertyAccessor
? (PersistentPropertyAccessor) source : entity.getPropertyAccessor(source));
? (PersistentPropertyAccessor) source : entity.getPropertyAccessor(source));
return new ConvertingPropertyAccessor(propertyAccessor, conversionService);
}
@@ -538,7 +610,30 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
}
private Class<?> getTargetType(CassandraPersistentProperty property) {
return (property.isCompositePrimaryKey() ? property.getType() : getCodec(property).getJavaType().getRawType());
if (conversions.hasCustomWriteTarget(property.getType())) {
return conversions.getCustomWriteTarget(property.getType());
}
if (conversions.isSimpleType(property.getType())) {
return property.getType();
}
if (property.isCompositePrimaryKey()) {
return property.getType();
}
if (property.isCollectionLike()) {
return property.getType();
}
DataType dataType = mappingContext.getDataType(property);
if (dataType instanceof UserType) {
return property.getType();
}
TypeCodec<Object> codec = CodecRegistry.DEFAULT_INSTANCE.codecFor(mappingContext.getDataType(property));
return codec.getJavaType().getRawType();
}
/**
@@ -579,9 +674,33 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
converted.add(getConversionService().convert(o, customWriteTarget));
}
value = converted;
return converted;
}
}
CassandraPersistentEntity<?> persistentEntity = getMappingContext().getPersistentEntity(property.getActualType());
if (persistentEntity != null && persistentEntity.isUserDefinedType()) {
if (property.isCollectionLike() && value instanceof Collection) {
Collection<Object> original = (Collection<Object>) value;
Collection<Object> converted = CollectionFactory.createCollection(property.getType(), original.size());
for (Object element : original) {
if (element instanceof UDTValue) {
converted.add(element);
} else {
converted.add(getWriteValue(property, element));
}
}
return converted;
}
UDTValue udtValue = persistentEntity.getUserType().newValue();
write(value, udtValue, persistentEntity);
return udtValue;
}
}
return value;
@@ -596,7 +715,8 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
* @return the return value, may be {@literal null}.
*/
@SuppressWarnings("unchecked")
private Object getReadValue(CassandraPersistentProperty property, BasicCassandraRowValueProvider row) {
private Object getReadValue(CassandraPersistentProperty property,
PropertyValueProvider<CassandraPersistentProperty> row) {
Object obj = row.getPropertyValue(property);
@@ -608,8 +728,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
Collection<Object> original = (Collection<Object>) obj;
Collection<Object> converted = CollectionFactory.createCollection(
property.getType(), original.size());
Collection<Object> converted = CollectionFactory.createCollection(property.getType(), original.size());
for (Object element : original) {
converted.add(getConversionService().convert(element, property.getActualType()));
@@ -620,6 +739,25 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
}
}
CassandraPersistentEntity<?> persistentEntity = getMappingContext().getPersistentEntity(property.getActualType());
if (persistentEntity != null && persistentEntity.isUserDefinedType()) {
if (property.isCollectionLike() && obj instanceof Collection) {
Collection<Object> original = (Collection<Object>) obj;
Collection<Object> converted = CollectionFactory.createCollection(property.getType(), original.size());
for (Object element : original) {
if (element instanceof UDTValue) {
converted.add(readEntityFromUdt(persistentEntity, (UDTValue) element));
}
}
return converted;
} else if (obj instanceof UDTValue) {
return readEntityFromUdt(persistentEntity, (UDTValue) obj);
}
}
return obj;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2013-2016 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.
@@ -19,8 +19,8 @@ import java.util.Map;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.TableMetadata;
import com.datastax.driver.core.UserType;
/**
* Operations for managing a Cassandra keyspace.
@@ -45,7 +45,7 @@ public interface CassandraAdminOperations extends CassandraOperations {
* @param optionsByName Table options, given by the string option name and the appropriate option value.
*/
void createTable(boolean ifNotExists, CqlIdentifier tableName, Class<?> entityClass,
Map<String, Object> optionsByName);
Map<String, Object> optionsByName);
/**
* Add columns to the given table from the given class. If parameter dropRemovedAttributColumns is true, then this
@@ -85,12 +85,18 @@ public interface CassandraAdminOperations extends CassandraOperations {
TableMetadata getTableMetadata(String keyspace, CqlIdentifier tableName);
/**
* Lookup {@link UserType} metadata.
* Returns {@link KeyspaceMetadata} for the current keyspace.
*
* @param keyspace must not be empty or {@literal null}.
* @param userTypeName must not be {@literal null}.
* @return the {@link UserType} or {@literal null}.
* @return {@link KeyspaceMetadata} for the current keyspace.
* @since 1.5
*/
UserType getUserTypeMetadata(String keyspace, CqlIdentifier userTypeName);
KeyspaceMetadata getKeyspaceMetadata();
/**
* Drops a user type.
*
* @param typeName must not be {@literal null}.
* @since 1.5
*/
void dropUserType(CqlIdentifier typeName);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2013-2016 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.
@@ -23,13 +23,16 @@ import org.slf4j.LoggerFactory;
import org.springframework.cassandra.core.SessionCallback;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.cassandra.core.cql.generator.CreateTableCqlGenerator;
import org.springframework.cassandra.core.cql.generator.DropUserTypeCqlGenerator;
import org.springframework.cassandra.core.keyspace.DropTableSpecification;
import org.springframework.cassandra.core.keyspace.DropUserTypeSpecification;
import org.springframework.dao.DataAccessException;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.util.CqlUtils;
import org.springframework.util.Assert;
import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.TableMetadata;
import com.datastax.driver.core.UserType;
@@ -133,18 +136,34 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand
dropTable(getTableName(entityClass));
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraAdminOperations#dropTable(org.springframework.cassandra.core.cql.CqlIdentifier)
*/
@Override
public void dropTable(CqlIdentifier tableName) {
Assert.notNull(tableName, "Type name must not be null");
log.info("Dropping table => " + tableName);
execute(DropTableSpecification.dropTable(tableName));
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraAdminOperations#dropUserType(org.springframework.cassandra.core.cql.CqlIdentifier)
*/
@Override
public void dropUserType(CqlIdentifier typeName) {
Assert.notNull(typeName, "Type name must not be null");
log.info("Dropping user type => {}", typeName);
execute(DropUserTypeCqlGenerator.toCql(DropUserTypeSpecification.dropType(typeName)));
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraAdminOperations#getTableMetadata(java.lang.String, org.springframework.cassandra.core.cql.CqlIdentifier)
@@ -163,20 +182,21 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand
});
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraAdminOperations#getUserTypeMetadata(java.lang.String, org.springframework.cassandra.core.cql.CqlIdentifier)
* @see org.springframework.data.cassandra.core.CassandraAdminOperations#getKeyspaceMetadata()
*/
@Override
public UserType getUserTypeMetadata(final String keyspace, final CqlIdentifier userTypeName) {
public KeyspaceMetadata getKeyspaceMetadata() {
Assert.hasText(keyspace, "Keyspace name must not be empty");
Assert.notNull(userTypeName, "User type name must not be null");
return execute(new SessionCallback<UserType>() {
return execute(new SessionCallback<KeyspaceMetadata>() {
@Override
public UserType doInSession(Session s) {
return s.getCluster().getMetadata().getKeyspace(keyspace).getUserType(userTypeName.toCql());
public KeyspaceMetadata doInSession(Session s) throws DataAccessException {
KeyspaceMetadata keyspaceMetadata = s.getCluster().getMetadata().getKeyspace(s.getLoggedKeyspace());
Assert.state(keyspaceMetadata != null,
String.format("Metadata for keyspace [%s] not available", s.getLoggedKeyspace()));
return keyspaceMetadata;
}
});
}

View File

@@ -0,0 +1,213 @@
/*
* Copyright 2016 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.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.cassandra.core.cql.generator.CreateTableCqlGenerator;
import org.springframework.cassandra.core.cql.generator.CreateUserTypeCqlGenerator;
import org.springframework.cassandra.core.keyspace.CreateTableSpecification;
import org.springframework.cassandra.core.keyspace.CreateUserTypeSpecification;
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.mapping.PropertyHandler;
import org.springframework.util.Assert;
import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.TableMetadata;
import com.datastax.driver.core.UserType;
/**
* Schema creation support for Cassandra based on {@link CassandraMappingContext} and {@link CassandraPersistentEntity}.
* This class generates CQL to drop, recreate and create user types (UDT) and tables.
*
* @author Mark Paluch
* @since 1.5
* @see org.springframework.data.cassandra.mapping.Table
* @see org.springframework.data.cassandra.mapping.UserDefinedType
* @see org.springframework.data.cassandra.mapping.CassandraType
*/
public class CassandraPersistentEntitySchemaCreator {
private final CassandraMappingContext mappingContext;
private final CassandraAdminOperations cassandraAdminOperations;
/**
* Creates a new {@link CassandraPersistentEntitySchemaCreator} for the given {@link CassandraMappingContext} and
* {@link CassandraAdminOperations}.
*
* @param mappingContext must not be {@literal null}.
* @param cassandraAdminOperations must not be {@literal null}.
*/
public CassandraPersistentEntitySchemaCreator(CassandraMappingContext mappingContext,
CassandraAdminOperations cassandraAdminOperations) {
Assert.notNull(mappingContext, "CassandraMappingContext must not be null");
Assert.notNull(cassandraAdminOperations, "CassandraAdminOperations must not be null");
this.mappingContext = mappingContext;
this.cassandraAdminOperations = cassandraAdminOperations;
}
/**
* Create user types. Can drop types and drop unused types.
*
* @param dropUserTypes {@literal true} to drop types before creation.
* @param dropUnused {@literal true} to drop unused types before creation. Type usage is determined from existing
* mapped {@link org.springframework.data.cassandra.mapping.UserDefinedType}s and UDT names on field
* specifications.
* @param ifNotExists {@literal true} to create types using {@code IF NOT EXISTS}.
*/
public void createUserTypes(boolean dropUserTypes, boolean dropUnused, boolean ifNotExists) {
if (dropUserTypes) {
dropUserTypes(dropUnused);
}
List<CreateUserTypeSpecification> specifications = createUserTypeSpecifications(ifNotExists);
for (CreateUserTypeSpecification specification : specifications) {
cassandraAdminOperations.execute(CreateUserTypeCqlGenerator.toCql(specification));
}
}
/**
* Create user types. Can drop types and drop unused types.
*
* @param dropTables {@literal true} to drop tables before creation.
* @param dropUnused {@literal true} to drop unused tables before creation. Table usage is determined by existing
* table mappings.
* @param ifNotExists {@literal true} to create tables using {@code IF NOT EXISTS}.
*/
public void createTables(boolean dropTables, boolean dropUnused, boolean ifNotExists) {
if (dropTables) {
dropTables(dropUnused);
}
List<CreateTableSpecification> specifications = createTableSpecifications(ifNotExists);
for (CreateTableSpecification specification : specifications) {
cassandraAdminOperations.execute(CreateTableCqlGenerator.toCql(specification));
}
}
protected List<CreateUserTypeSpecification> createUserTypeSpecifications(boolean ifNotExists) {
Collection<? extends CassandraPersistentEntity<?>> entities = new ArrayList<CassandraPersistentEntity<?>>(
mappingContext.getUserDefinedTypeEntities());
Map<CqlIdentifier, CassandraPersistentEntity<?>> byName = new HashMap<CqlIdentifier, CassandraPersistentEntity<?>>();
for (CassandraPersistentEntity<?> entity : entities) {
byName.put(entity.getTableName(), entity);
}
List<CreateUserTypeSpecification> specifications = new ArrayList<CreateUserTypeSpecification>();
Set<CqlIdentifier> created = new HashSet<CqlIdentifier>();
for (CassandraPersistentEntity<?> entity : entities) {
Set<CqlIdentifier> seen = new LinkedHashSet<CqlIdentifier>();
seen.add(entity.getTableName());
visitUserTypes(entity, seen);
List<CqlIdentifier> ordered = new ArrayList<CqlIdentifier>(seen);
Collections.reverse(ordered);
for (CqlIdentifier identifier : ordered) {
if (created.add(identifier)) {
specifications
.add(mappingContext.getCreateUserTypeSpecificationFor(byName.get(identifier)).ifNotExists(ifNotExists));
}
}
}
return specifications;
}
protected List<CreateTableSpecification> createTableSpecifications(boolean ifNotExists) {
Collection<? extends CassandraPersistentEntity<?>> entities = new ArrayList<CassandraPersistentEntity<?>>(
mappingContext.getNonPrimaryKeyEntities());
List<CreateTableSpecification> specifications = new ArrayList<CreateTableSpecification>();
for (CassandraPersistentEntity<?> entity : entities) {
specifications.add(mappingContext.getCreateTableSpecificationFor(entity).ifNotExists(ifNotExists));
}
return specifications;
}
private void visitUserTypes(CassandraPersistentEntity<?> entity, final Set<CqlIdentifier> seen) {
entity.doWithProperties(new PropertyHandler<CassandraPersistentProperty>() {
@Override
public void doWithPersistentProperty(CassandraPersistentProperty persistentProperty) {
CassandraPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(persistentProperty);
if (persistentEntity != null && persistentEntity.isUserDefinedType()) {
if (seen.add(persistentEntity.getTableName())) {
visitUserTypes(persistentEntity, seen);
}
}
}
});
}
private void dropUserTypes(boolean dropUnused) {
KeyspaceMetadata keyspaceMetadata = cassandraAdminOperations.getKeyspaceMetadata();
Collection<CassandraPersistentEntity<?>> userDefinedTypeEntities = mappingContext.getUserDefinedTypeEntities();
Set<CqlIdentifier> canRecreate = new HashSet<CqlIdentifier>();
for (CassandraPersistentEntity<?> userDefinedTypeEntity : userDefinedTypeEntities) {
canRecreate.add(userDefinedTypeEntity.getTableName());
}
for (UserType userType : keyspaceMetadata.getUserTypes()) {
CqlIdentifier identifier = CqlIdentifier.cqlId(userType.getTypeName());
if (canRecreate.contains(identifier)) {
cassandraAdminOperations.dropUserType(identifier);
} else if (dropUnused && !mappingContext.usesUserType(userType)) {
cassandraAdminOperations.dropUserType(identifier);
}
}
}
private void dropTables(boolean dropUnused) {
KeyspaceMetadata keyspaceMetadata = cassandraAdminOperations.getKeyspaceMetadata();
for (TableMetadata table : keyspaceMetadata.getTables()) {
if (dropUnused || mappingContext.usesTable(table)) {
cassandraAdminOperations.dropTable(CqlIdentifier.cqlId(table.getName()));
}
}
}
}

View File

@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.mapping;
import static org.springframework.cassandra.core.cql.CqlIdentifier.*;
@@ -29,12 +28,16 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.beans.BeansException;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.cassandra.core.keyspace.CreateTableSpecification;
import org.springframework.cassandra.core.keyspace.CreateUserTypeSpecification;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.cassandra.convert.CustomConversions;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.context.AbstractMappingContext;
@@ -48,6 +51,7 @@ import org.springframework.util.StringUtils;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.TableMetadata;
import com.datastax.driver.core.UserType;
/**
* Default implementation of a {@link MappingContext} for Cassandra using {@link CassandraPersistentEntity} and
@@ -77,8 +81,10 @@ public class BasicCassandraMappingContext
protected Set<CassandraPersistentEntity<?>> nonPrimaryKeyEntities = new HashSet<CassandraPersistentEntity<?>>();
protected Set<CassandraPersistentEntity<?>> primaryKeyEntities = new HashSet<CassandraPersistentEntity<?>>();
protected Set<CassandraPersistentEntity<?>> userDefinedTypes = new HashSet<CassandraPersistentEntity<?>>();
private CustomConversions customConversions;
private UserTypeResolver userTypeResolver;
/**
* Creates a new {@link BasicCassandraMappingContext}.
@@ -95,11 +101,25 @@ public class BasicCassandraMappingContext
* @since 1.5
*/
public void setCustomConversions(CustomConversions customConversions) {
Assert.notNull(customConversions, "CustomConversions must not be null");
this.customConversions = customConversions;
}
/**
* Sets the {@link UserTypeResolver}.
*
* @param userTypeResolver must not be {@literal null}.
* @since 1.5
*/
public void setUserTypeResolver(UserTypeResolver userTypeResolver) {
Assert.notNull(userTypeResolver, "UserTypeResolver must not be null");
this.userTypeResolver = userTypeResolver;
}
@Override
public void initialize() {
super.initialize();
@@ -122,8 +142,18 @@ public class BasicCassandraMappingContext
}
@Override
public Collection<CassandraPersistentEntity<?>> getPersistentEntities(boolean includePrimaryKeyTypes) {
if (includePrimaryKeyTypes) {
public Collection<CassandraPersistentEntity<?>> getUserDefinedTypeEntities() {
return Collections.unmodifiableSet(userDefinedTypes);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.mapping.CassandraMappingContext#getPersistentEntities(boolean)
*/
@Override
public Collection<CassandraPersistentEntity<?>> getPersistentEntities(boolean includePrimaryKeyTypesAndUdts) {
if (includePrimaryKeyTypesAndUdts) {
return super.getPersistentEntities();
}
@@ -140,13 +170,23 @@ public class BasicCassandraMappingContext
public CassandraPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor,
CassandraPersistentEntity<?> owner, CassandraSimpleTypeHolder simpleTypeHolder) {
return new BasicCassandraPersistentProperty(field, descriptor, owner, simpleTypeHolder);
return new BasicCassandraPersistentProperty(field, descriptor, owner, simpleTypeHolder, userTypeResolver);
}
@Override
protected <T> CassandraPersistentEntity<T> createPersistentEntity(TypeInformation<T> typeInformation) {
CassandraPersistentEntity<T> entity = new BasicCassandraPersistentEntity<T>(typeInformation, this, verifier);
UserDefinedType userDefinedType = AnnotatedElementUtils.findMergedAnnotation(typeInformation.getType(),
UserDefinedType.class);
CassandraPersistentEntity<T> entity;
if (userDefinedType != null) {
entity = new CassandraUserTypePersistentEntity<T>(typeInformation, this, verifier, userTypeResolver);
userDefinedTypes.add(entity);
} else {
entity = new BasicCassandraPersistentEntity<T>(typeInformation, this, verifier);
}
if (context != null) {
entity.setApplicationContext(context);
@@ -163,10 +203,14 @@ public class BasicCassandraMappingContext
entities.add(entity);
if (entity.isCompositePrimaryKey()) {
primaryKeyEntities.add(entity);
} else {
nonPrimaryKeyEntities.add(entity);
if (!entity.isUserDefinedType()) {
if (entity.isCompositePrimaryKey()) {
primaryKeyEntities.add(entity);
} else {
if (entity.findAnnotation(Persistent.class) != null) {
nonPrimaryKeyEntities.add(entity);
}
}
}
entitiesByType.put(entity.getType(), entity);
@@ -174,20 +218,75 @@ public class BasicCassandraMappingContext
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;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.mapping.CassandraMappingContext#usesTable(com.datastax.driver.core.TableMetadata)
*/
@Override
public boolean usesTable(TableMetadata table) {
return entitySetsByTableName.containsKey(cqlId(table.getName()));
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.mapping.CassandraMappingContext#usesUserType(com.datastax.driver.core.UserType)
*/
@Override
public boolean usesUserType(final UserType userType) {
CqlIdentifier identifier = CqlIdentifier.cqlId(userType.getTypeName());
return hasMappedUserType(identifier) || hasReferencedUserType(identifier);
}
private boolean hasReferencedUserType(final CqlIdentifier identifier) {
final AtomicBoolean foundReference = new AtomicBoolean();
for (CassandraPersistentEntity<?> entity : getPersistentEntities()) {
entity.doWithProperties(new PropertyHandler<CassandraPersistentProperty>() {
@Override
public void doWithPersistentProperty(CassandraPersistentProperty persistentProperty) {
CassandraType cassandraType = persistentProperty.findAnnotation(CassandraType.class);
if (cassandraType == null) {
return;
}
if (StringUtils.hasText(cassandraType.userTypeName())
&& CqlIdentifier.cqlId(cassandraType.userTypeName()).equals(identifier)) {
foundReference.set(true);
}
}
});
}
return foundReference.get();
}
private boolean hasMappedUserType(CqlIdentifier identifier) {
for (CassandraPersistentEntity<?> userDefinedType : userDefinedTypes) {
if (userDefinedType.getTableName().equals(identifier)) {
return true;
}
}
return false;
}
@Override
public CreateTableSpecification getCreateTableSpecificationFor(CassandraPersistentEntity<?> entity) {
Assert.notNull(entity);
Assert.notNull(entity, "CassandraPersistentEntity must not be null");
final CreateTableSpecification spec = createTable().name(entity.getTableName());
@@ -231,19 +330,44 @@ public class BasicCassandraMappingContext
});
if (spec.getPartitionKeyColumns().isEmpty()) {
throw new MappingException("no partition key columns found in the entity " + entity.getType());
throw new MappingException(String.format("No partition key columns found in entity [%s]", entity.getType()));
}
return spec;
}
/* (non-Javadoc)
* @see org.springframework.data.mapping.context.AbstractMappingContext#shouldCreatePersistentEntityFor(org.springframework.data.util.TypeInformation)
* @see org.springframework.data.cassandra.mapping.CassandraMappingContext#getCreateUserTypeSpecificationFor(org.springframework.data.cassandra.mapping.CassandraPersistentEntity)
*/
@Override
public CreateUserTypeSpecification getCreateUserTypeSpecificationFor(CassandraPersistentEntity<?> entity) {
Assert.notNull(entity, "CassandraPersistentEntity must not be null");
final CreateUserTypeSpecification spec = CreateUserTypeSpecification.createType(entity.getTableName());
entity.doWithProperties(new PropertyHandler<CassandraPersistentProperty>() {
@Override
public void doWithPersistentProperty(CassandraPersistentProperty property) {
spec.field(property.getColumnName(), getDataType(property));
}
});
if (spec.getFields().isEmpty()) {
throw new MappingException(String.format("No fields in user type [%s]", entity.getType()));
}
return spec;
}
/* (non-Javadoc)
* @see org.springframework.data.mapping.context.AbstractMappingContext#shouldCreatePersistentEntityFor(org.springframework.data.util.TypeInformation)
*/
@Override
protected boolean shouldCreatePersistentEntityFor(TypeInformation<?> typeInfo) {
return (!customConversions.hasCustomWriteTarget(typeInfo.getType())
&& super.shouldCreatePersistentEntityFor(typeInfo));
&& super.shouldCreatePersistentEntityFor(typeInfo));
}
/* (non-Javadoc)
@@ -269,6 +393,12 @@ public class BasicCassandraMappingContext
return property.getDataType();
}
CassandraPersistentEntity<?> persistentEntity = getPersistentEntity(property.getType());
if (persistentEntity != null && persistentEntity.isUserDefinedType()) {
return persistentEntity.getUserType();
}
if (customConversions.hasCustomWriteTarget(property.getType())) {
return getDataTypeFor(customConversions.getCustomWriteTarget(property.getType()));
}
@@ -299,8 +429,8 @@ public class BasicCassandraMappingContext
*/
@Override
public DataType getDataType(Class<?> type) {
return (customConversions.hasCustomWriteTarget(type)
? getDataTypeFor(customConversions.getCustomWriteTarget(type)) : getDataTypeFor(type));
return customConversions.hasCustomWriteTarget(type)
? getDataTypeFor(customConversions.getCustomWriteTarget(type)) : getDataTypeFor(type);
}
public void setMapping(Mapping mapping) {
@@ -311,32 +441,36 @@ public class BasicCassandraMappingContext
@SuppressWarnings("all")
protected void processMappingOverrides() {
if (mapping != null) {
for (EntityMapping entityMapping : mapping.getEntityMappings()) {
if (entityMapping != null) {
String entityClassName = entityMapping.getEntityClassName();
try {
Class<?> entityClass = ClassUtils.forName(entityClassName, beanClassLoader);
if (mapping == null) {
return;
}
CassandraPersistentEntity<?> entity = getPersistentEntity(entityClass);
for (EntityMapping entityMapping : mapping.getEntityMappings()) {
Assert.state(entity != null, String.format("Unknown persistent entity class name [%s]",
entityClassName));
if (entityMapping == null) {
continue;
}
String entityClassName = entityMapping.getEntityClassName();
String tableName = entityMapping.getTableName();
try {
Class<?> entityClass = ClassUtils.forName(entityClassName, beanClassLoader);
if (StringUtils.hasText(tableName)) {
entity.setTableName(cqlId(tableName, Boolean.valueOf(entityMapping.getForceQuote())));
}
CassandraPersistentEntity<?> entity = getPersistentEntity(entityClass);
processMappingOverrides(entity, entityMapping);
Assert.state(entity != null, String.format("Unknown persistent entity class name [%s]", entityClassName));
} catch (ClassNotFoundException e) {
throw new IllegalStateException(String.format(
"unknown persistent entity name [%s]", entityClassName), e);
}
String tableName = entityMapping.getTableName();
if (StringUtils.hasText(tableName)) {
entity.setTableName(cqlId(tableName, Boolean.valueOf(entityMapping.getForceQuote())));
}
processMappingOverrides(entity, entityMapping);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(String.format("Unknown persistent entity name [%s]", entityClassName), e);
}
}
}
@@ -353,7 +487,7 @@ public class BasicCassandraMappingContext
if (property == null) {
throw new IllegalArgumentException(String.format("Entity class [%s] has no persistent property named [%s]",
entity.getType().getName(), mapping.getPropertyName()));
entity.getType().getName(), mapping.getPropertyName()));
}
boolean forceQuote = false;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors
* Copyright 2013-2016 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.
@@ -15,7 +15,7 @@
*/
package org.springframework.data.cassandra.mapping;
import static org.springframework.cassandra.core.cql.CqlIdentifier.cqlId;
import static org.springframework.cassandra.core.cql.CqlIdentifier.*;
import java.util.ArrayList;
import java.util.List;
@@ -38,12 +38,15 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.datastax.driver.core.UserType;
/**
* Cassandra specific {@link BasicPersistentEntity} implementation that adds Cassandra specific metadata.
*
* @author Alex Shvid
* @author Matthew T. Adams
* @author John Blum
* @author Mark Paluch
*/
public class BasicCassandraPersistentEntity<T> extends BasicPersistentEntity<T, CassandraPersistentProperty>
implements CassandraPersistentEntity<T>, ApplicationContextAware {
@@ -75,7 +78,6 @@ public class BasicCassandraPersistentEntity<T> extends BasicPersistentEntity<T,
*/
public BasicCassandraPersistentEntity(TypeInformation<T> typeInformation, CassandraMappingContext mappingContext) {
this(typeInformation, mappingContext, DEFAULT_VERIFIER);
}
/**
@@ -98,12 +100,11 @@ public class BasicCassandraPersistentEntity<T> extends BasicPersistentEntity<T,
Table tableAnnotation = getType().getAnnotation(Table.class);
if (tableAnnotation == null || !StringUtils.hasText(tableAnnotation.value())) {
return cqlId(getType().getSimpleName(), tableAnnotation != null && tableAnnotation.forceQuote());
if (tableAnnotation == null) {
return determineDefaultName();
}
return cqlId(spelContext == null ? tableAnnotation.value()
: SpelUtils.evaluate(tableAnnotation.value(), spelContext), tableAnnotation.forceQuote());
return determineName(tableAnnotation.value(), tableAnnotation.forceQuote());
}
@Override
@@ -215,4 +216,33 @@ public class BasicCassandraPersistentEntity<T> extends BasicPersistentEntity<T,
public CassandraPersistentEntityMetadataVerifier getVerifier() {
return verifier;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.mapping.CassandraPersistentEntity#isUserDefinedType()
*/
@Override
public boolean isUserDefinedType() {
return false;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.mapping.CassandraPersistentEntity#getUserType()
*/
@Override
public UserType getUserType() {
return null;
}
protected CqlIdentifier determineDefaultName() {
return cqlId(getType().getSimpleName(), false);
}
protected CqlIdentifier determineName(String value, boolean forceQuote) {
if (!StringUtils.hasText(value)) {
return cqlId(getType().getSimpleName(), forceQuote);
}
return cqlId(spelContext == null ? value : SpelUtils.evaluate(value, spelContext), forceQuote);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors
* Copyright 2013-2016 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.
@@ -15,6 +15,7 @@
*/
package org.springframework.data.cassandra.mapping;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@@ -79,7 +80,10 @@ public class BasicCassandraPersistentEntityMetadataVerifier implements Cassandra
// Perform rules verification on Table/Persistent
// TODO Verify annotation values with CqlIndentifier
/*
* Perform rules verification on Table/Persistent
*/
// Ensure only one PK or at least one partitioned PK Column and not both PK(s) & PK Column(s) exist
if (primaryKeyColumns.isEmpty()) {
@@ -91,17 +95,6 @@ public class BasicCassandraPersistentEntityMetadataVerifier implements Cassandra
fail(entity, exceptions);
}
// Ensure that Id is a supported Type. At this point there is only 1.
CassandraPersistentProperty idProperty = idProperties.get(0);
Class<?> idType = idProperty.getType();
if (!idType.isAnnotationPresent(PrimaryKeyClass.class)
&& CassandraSimpleTypeHolder.getDataTypeFor(idType) == null) {
exceptions.add(new MappingException(String.format(
"Property [%s] annotated with @%s must be a simple CassandraType",
idProperty.getName(), Id.class.getSimpleName())));
}
}
if (!idProperties.isEmpty() && !primaryKeyColumns.isEmpty()) {
@@ -120,14 +113,6 @@ public class BasicCassandraPersistentEntityMetadataVerifier implements Cassandra
PrimaryKeyColumn.class.getSimpleName())));
}
for (CassandraPersistentProperty property : primaryKeyColumns) {
if (CassandraSimpleTypeHolder.getDataTypeFor(property.getType()) == null) {
exceptions.add(new MappingException(String.format(
"Property [%s] annotated with @PrimaryKeyColumn must be a simple CassandraType",
property.getName())));
}
}
// Determine whether or not to throw Exception based on errors found
if (!exceptions.isEmpty()) {
fail(entity, exceptions);

View File

@@ -38,6 +38,7 @@ import org.springframework.data.cassandra.util.SpelUtils;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@@ -46,6 +47,8 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.DataType.Name;
import com.datastax.driver.core.UserType;
/**
* Cassandra specific {@link org.springframework.data.mapping.model.AnnotationBasedPersistentProperty} implementation.
@@ -59,6 +62,8 @@ import com.datastax.driver.core.DataType;
public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentProperty<CassandraPersistentProperty>
implements CassandraPersistentProperty, ApplicationContextAware {
private final UserTypeResolver userTypeResolver;
protected ApplicationContext context;
/**
@@ -89,7 +94,24 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
public BasicCassandraPersistentProperty(Field field, PropertyDescriptor propertyDescriptor,
CassandraPersistentEntity<?> owner, CassandraSimpleTypeHolder simpleTypeHolder) {
this(field, propertyDescriptor, owner, simpleTypeHolder, null);
}
/**
* Creates a new {@link BasicCassandraPersistentProperty}.
*
* @param field the actual {@link Field} in the domain entity corresponding to this persistent entity.
* @param propertyDescriptor a {@link PropertyDescriptor} for the corresponding property in the domain entity.
* @param owner the containing object or {@link CassandraPersistentEntity} of this persistent property.
* @param simpleTypeHolder mapping of Java [simple|wrapper] types to Cassandra data types.
* @param userTypeResolver resolver for user-defined types.
*/
public BasicCassandraPersistentProperty(Field field, PropertyDescriptor propertyDescriptor,
CassandraPersistentEntity<?> owner, CassandraSimpleTypeHolder simpleTypeHolder,
UserTypeResolver userTypeResolver) {
super(field, propertyDescriptor, owner, simpleTypeHolder);
this.userTypeResolver = userTypeResolver;
if (owner.getApplicationContext() != null) {
setApplicationContext(owner.getApplicationContext());
@@ -157,7 +179,7 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
if (dataType == null) {
throw new InvalidDataAccessApiUsageException(String.format(
"Unknown type [%s] for property [%s] in entity [%s]; only primitive types and Collections or Maps of primitive types are allowed",
"Unknown type [%s] for property [%s] in entity [%s]; only primitive types and Collections or Maps of primitive types are allowed",
getType(), getName(), getOwner().getName()));
}
@@ -208,15 +230,41 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
getDataTypeFor(annotation.typeArguments()[1]));
case LIST:
ensureTypeArguments(annotation.typeArguments().length, 1);
if (annotation.typeArguments()[0] == Name.UDT) {
return DataType.list(getUserType(annotation));
}
return DataType.list(getDataTypeFor(annotation.typeArguments()[0]));
case SET:
ensureTypeArguments(annotation.typeArguments().length, 1);
if (annotation.typeArguments()[0] == Name.UDT) {
return DataType.set(getUserType(annotation));
}
return DataType.set(getDataTypeFor(annotation.typeArguments()[0]));
case UDT:
return getUserType(annotation);
default:
return CassandraSimpleTypeHolder.getDataTypeFor(type);
}
}
private DataType getUserType(CassandraType annotation) {
if (!StringUtils.hasText(annotation.userTypeName())) {
throw new InvalidDataAccessApiUsageException(
String.format("Expected user type name in property ['%s'] of type ['%s'] in entity [%s]", getName(),
getType(), getOwner().getName()));
}
CqlIdentifier identifier = CqlIdentifier.cqlId(annotation.userTypeName());
UserType userType = userTypeResolver.resolveType(identifier);
if (userType == null) {
throw new MappingException(String.format("User type [%s] not found", identifier));
}
return userType;
}
@Override
public boolean isIndexed() {
return isAnnotationPresent(Indexed.class);
@@ -249,7 +297,7 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
if (dataType == null) {
throw new InvalidDataAccessApiUsageException(String.format(
"Only primitive types are allowed inside Collections for property [%1$s] of type [%2$s] in entity [%3$s]",
"Only primitive types are allowed inside Collections for property [%1$s] of type [%2$s] in entity [%3$s]",
getName(), getType(), getOwner().getName()));
}
@@ -258,11 +306,16 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
protected DataType getDataTypeFor(Class<?> javaType) {
CassandraPersistentEntity<?> persistentEntity = getOwner().getMappingContext().getPersistentEntity(javaType);
if (persistentEntity != null && persistentEntity.isUserDefinedType()) {
return persistentEntity.getUserType();
}
DataType dataType = CassandraSimpleTypeHolder.getDataTypeFor(javaType);
if (dataType == null) {
throw new InvalidDataAccessApiUsageException(String.format(
"Only primitive types are allowed inside Collections for property [%1$s] of type ['%2$s'] in entity [%3$s]",
"Only primitive types are allowed inside Collections for property [%1$s] of type ['%2$s'] in entity [%3$s]",
getName(), getType(), getOwner().getName()));
}
@@ -272,8 +325,8 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
protected void ensureTypeArguments(int args, int expected) {
if (args != expected) {
throw new InvalidDataAccessApiUsageException(
String.format("Expected [%1$s] typed arguments for property ['%2$s'] of type ['%3$s'] in entity [%4$s]",
expected, getName(), getType(), getOwner().getName()));
String.format("Expected [%1$s] typed arguments for property ['%2$s'] of type ['%3$s'] in entity [%4$s]",
expected, getName(), getType(), getOwner().getName()));
}
}
@@ -362,13 +415,13 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
if (this.columnNames.size() != columnNames.size()) {
throw new IllegalStateException(String.format(
"Property [%s] of entity [%s] is mapped to [%s] column%s, but given column name list has size [%s]",
getName(), getOwner().getType().getName(), this.columnNames.size(),
this.columnNames.size() == 1 ? "" : "s", columnNames.size()));
"Property [%s] of entity [%s] is mapped to [%s] column%s, but given column name list has size [%s]",
getName(), getOwner().getType().getName(), this.columnNames.size(), this.columnNames.size() == 1 ? "" : "s",
columnNames.size()));
}
this.columnNames = this.explicitColumnNames =
Collections.unmodifiableList(new ArrayList<CqlIdentifier>(columnNames));
this.columnNames = this.explicitColumnNames = Collections
.unmodifiableList(new ArrayList<CqlIdentifier>(columnNames));
}
@Override
@@ -394,8 +447,8 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
public List<CassandraPersistentProperty> getCompositePrimaryKeyProperties() {
if (!isCompositePrimaryKey()) {
throw new IllegalStateException(String.format(
"[%s] does not represent a composite primary key property", getName()));
throw new IllegalStateException(
String.format("[%s] does not represent a composite primary key property", getName()));
}
return getCompositePrimaryKeyEntity().getCompositePrimaryKeyProperties();

View File

@@ -18,11 +18,13 @@ package org.springframework.data.cassandra.mapping;
import java.util.Collection;
import org.springframework.cassandra.core.keyspace.CreateTableSpecification;
import org.springframework.cassandra.core.keyspace.CreateUserTypeSpecification;
import org.springframework.data.cassandra.convert.CustomConversions;
import org.springframework.data.mapping.context.MappingContext;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.TableMetadata;
import com.datastax.driver.core.UserType;
/**
* A {@link MappingContext} for Cassandra.
@@ -44,10 +46,10 @@ public interface CassandraMappingContext
/**
* Returns all persistent entities or only non-primary-key entities.
*
* @param includePrimaryKeyTypes If <code>true</code>, returns all entities, including entities that represent primary
* key types. If <code>false</code>, returns only entities that don't represent primary key types.
* @param includePrimaryKeyTypesAndUdts If {@literal true}, returns all entities, including entities that represent primary
* key types and user-defined types. If {@literal false}, returns only entities that don't represent primary key types and no user-defined types.
*/
Collection<CassandraPersistentEntity<?>> getPersistentEntities(boolean includePrimaryKeyTypes);
Collection<CassandraPersistentEntity<?>> getPersistentEntities(boolean includePrimaryKeyTypesAndUdts);
/**
* Returns only those entities representing primary key types.
@@ -61,20 +63,45 @@ public interface CassandraMappingContext
*/
Collection<CassandraPersistentEntity<?>> getNonPrimaryKeyEntities();
/**
* Returns only those entities representing a user defined type.
*
* @see #getPersistentEntities(boolean)
* @since 1.5
*/
Collection<CassandraPersistentEntity<?>> getUserDefinedTypeEntities();
/**
* Returns a {@link CreateTableSpecification} for the given entity, including all mapping information.
*
* @param The entity. May not be null.
* @param entity must not be {@literal null}.
*/
CreateTableSpecification getCreateTableSpecificationFor(CassandraPersistentEntity<?> entity);
/**
* Returns a {@link CreateUserTypeSpecification} for the given entity, including all mapping information.
*
* @param entity must not be {@literal null}.
*/
CreateUserTypeSpecification getCreateUserTypeSpecificationFor(CassandraPersistentEntity<?> entity);
/**
* Returns whether this mapping context has any entities mapped to the given table.
*
* @param table May not be null.
* @param table must not be {@literal null}.
* @return @return {@literal true} is this {@literal TableMetadata} is used by a mapping.
*/
boolean usesTable(TableMetadata table);
/**
* Returns whether this mapping context has any entities using the given user type.
*
* @param userType must not be {@literal null}.
* @return {@literal true} is this {@literal UserType} is used.
* @since 1.5
*/
boolean usesUserType(UserType userType);
/**
* Returns the existing {@link CassandraPersistentEntity} for the given {@link Class}. If it is not yet known to this
* {@link CassandraMappingContext}, an {@link IllegalArgumentException} is thrown.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors
* Copyright 2013-2016 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.
@@ -23,14 +23,16 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.model.MutablePersistentEntity;
import com.datastax.driver.core.UserType;
/**
* Cassandra specific {@link PersistentEntity} abstraction.
*
* @author Alex Shvid
* @author Matthew T. Adams
*/
public interface CassandraPersistentEntity<T> extends MutablePersistentEntity<T, CassandraPersistentProperty>,
ApplicationContextAware {
public interface CassandraPersistentEntity<T>
extends MutablePersistentEntity<T, CassandraPersistentProperty>, ApplicationContextAware {
/**
* Returns whether this entity represents a composite primary key.
@@ -51,4 +53,18 @@ public interface CassandraPersistentEntity<T> extends MutablePersistentEntity<T,
ApplicationContext getApplicationContext();
void setForceQuote(boolean forceQuote);
/**
* @return {@literal true} if the type is a mapped user defined type
* @since 1.5
* @see UserDefinedType
*/
boolean isUserDefinedType();
/**
* @return the CQL {@link UserType} if the type is a mapped user defined type, otherwise {@literal null}.
* @since 1.5
* @see UserDefinedType
*/
UserType getUserType();
}

View File

@@ -30,6 +30,7 @@ import com.datastax.driver.core.CodecRegistry;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.DataType.Name;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.UDTValue;
/**
* Simple constant holder for a {@link SimpleTypeHolder} enriched with Cassandra specific simple types.
@@ -63,6 +64,7 @@ public class CassandraSimpleTypeHolder extends SimpleTypeHolder {
Set<Class<?>> simpleTypes = getCassandraPrimitiveTypes(codecRegistry);
simpleTypes.add(Number.class);
simpleTypes.add(Row.class);
simpleTypes.add(UDTValue.class);
classToDataType = Collections.unmodifiableMap(classToDataType(primitiveWrappers, codecRegistry));
nameToDataType = Collections.unmodifiableMap(nameToDataType());

View File

@@ -47,4 +47,14 @@ public @interface CassandraType {
* If the property is neither collection-like or a map, then this attribute is ignored.
*/
DataType.Name[] typeArguments() default {};
/**
* If the property maps to a user-defined type then this attribute holds the user type name. For collection-like
* properties the user type name applies to the component type. The user type name is only required if the UDT does
* not map to a class annotated with {@link UserDefinedType}.
*
* @return name of the user type
* @since 1.5
*/
String userTypeName() default "";
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2016 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.cassandra.core.cql.CqlIdentifier;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import com.datastax.driver.core.UserType;
/**
* {@link org.springframework.data.mapping.PersistentEntity} for a mapped user-defined type (UDT). A mapped UDT consists
* of a set of fields. Each field requires a data type that can be either a simple Cassandra type or an UDT.
*
* @author Mark Paluch
* @since 1.5
* @see UserDefinedType
*/
public class CassandraUserTypePersistentEntity<T> extends BasicCassandraPersistentEntity<T> {
private final UserTypeResolver resolver;
private final Object lock = new Object();
private volatile UserType userType;
/**
* Creates a new {@link CassandraUserTypePersistentEntity}.
*
* @param typeInformation must not be {@literal null}.
* @param mappingContext must not be {@literal null}.
* @param verifier must not be {@literal null}.
* @param resolver must not be {@literal null}.
*/
public CassandraUserTypePersistentEntity(TypeInformation<T> typeInformation, CassandraMappingContext mappingContext,
CassandraPersistentEntityMetadataVerifier verifier, UserTypeResolver resolver) {
super(typeInformation, mappingContext, verifier);
Assert.notNull(resolver, "UserTypeResolver must not be null");
this.resolver = resolver;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.mapping.BasicCassandraPersistentEntity#determineTableName()
*/
@Override
protected CqlIdentifier determineTableName() {
UserDefinedType typeAnnotation = findAnnotation(UserDefinedType.class);
return determineName(typeAnnotation.value(), typeAnnotation.forceQuote());
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.mapping.BasicCassandraPersistentEntity#isUserDefinedType()
*/
@Override
public boolean isUserDefinedType() {
return true;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.mapping.BasicCassandraPersistentEntity#getUserType()
*/
@Override
public UserType getUserType() {
if (userType == null) {
synchronized (lock) {
if (userType == null) {
CqlIdentifier identifier = determineTableName();
UserType userType = resolver.resolveType(identifier);
if (userType == null) {
throw new MappingException(String.format("User type [%s] not found", identifier));
}
this.userType = userType;
}
}
}
return userType;
}
}

View File

@@ -81,15 +81,18 @@ public class CompositeCassandraPersistentEntityMetadataVerifier implements Cassa
@Override
public void verify(CassandraPersistentEntity<?> entity) throws MappingException {
if (entity.getType().isInterface()) {
return;
}
// Ensure entity is either a @Table/@Persistent or a @PrimaryKey
if (entity.findAnnotation(Persistent.class) == null) {
throw new VerifierMappingExceptions(entity, Collections.singletonList(new MappingException(
String.format("Cassandra entities must be annotated with either @%s, @%s, or @%s",
Persistent.class.getSimpleName(), Table.class.getSimpleName(), PrimaryKeyClass.class.getSimpleName()))));
// Ensure entity is either a @Table/@Persistent, @UserDefinedType or a @PrimaryKey
if (entity.findAnnotation(Persistent.class) == null && entity.findAnnotation(UserDefinedType.class) == null) {
throw new VerifierMappingExceptions(entity,
Collections.singletonList(new MappingException(
String.format("Cassandra entities must be annotated with either @%s, @%s, @%s or @%s",
Persistent.class.getSimpleName(), Table.class.getSimpleName(),
UserDefinedType.class.getSimpleName(), PrimaryKeyClass.class.getSimpleName()))));
}
}
}

View File

@@ -107,15 +107,6 @@ public class PrimaryKeyClassEntityMetadataVerifier implements CassandraPersisten
Id.class.getSimpleName(), PrimaryKey.class.getSimpleName(), PrimaryKeyClass.class.getSimpleName())));
}
// Ensure that PrimaryKeyColumn is a supported Type.
for (CassandraPersistentProperty property : primaryKeyColumns) {
if (CassandraSimpleTypeHolder.getDataTypeFor(property.getType()) == null) {
exceptions.add(new MappingException(String.format(
"Property [%1$s] annotated with @%2$s must be a simple CassandraType", property.getName(),
PrimaryKeyColumn.class.getSimpleName())));
}
}
// Determine whether or not to throw Exception based on errors found
if (!exceptions.isEmpty()) {
fail(entity, exceptions);

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2016 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.cassandra.core.cql.CqlIdentifier;
import org.springframework.util.Assert;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.UserType;
/**
* Default implementation of {@link UserTypeResolver} that resolves {@link UserType} by their name from
* {@link Cluster#getMetadata()}.
*
* @author Mark Paluch
* @since 1.5
*/
public class SimpleUserTypeResolver implements UserTypeResolver {
private final String keyspaceName;
private final Cluster cluster;
/**
* Creates a new {@link SimpleUserTypeResolver}.
*
* @param cluster must not be {@literal null}.
* @param keyspaceName must not be empty or {@literal null}.
*/
public SimpleUserTypeResolver(Cluster cluster, String keyspaceName) {
Assert.notNull(cluster, "Cluster must not be null");
Assert.hasText(keyspaceName, "Keyspace must not be null or empty");
this.keyspaceName = keyspaceName;
this.cluster = cluster;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.mapping.UserTypeResolver#resolveType(org.springframework.cassandra.core.cql.CqlIdentifier)
*/
@Override
public UserType resolveType(CqlIdentifier typeName) {
KeyspaceMetadata keyspace = cluster.getMetadata().getKeyspace(keyspaceName);
return keyspace.getUserType(typeName.toCql());
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2016 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.cassandra.core.cql.CqlIdentifier;
import com.datastax.driver.core.UserType;
/**
* Strategy interface to resolve {@link UserType} by its name.
*
* @author Mark Paluch
* @since 1.5
*/
public interface UserTypeResolver {
/**
* Resolve a {@link UserType} by its name.
*
* @param typeName must not be {@literal null}.
* @return the type or {@literal null}, if not found.
*/
UserType resolveType(CqlIdentifier typeName);
}

View File

@@ -24,6 +24,7 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.convert.CustomConversions;
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.CassandraSimpleTypeHolder;
import org.springframework.data.cassandra.mapping.CassandraType;
@@ -36,6 +37,7 @@ import com.datastax.driver.core.CodecRegistry;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.DataType.CollectionType;
import com.datastax.driver.core.TypeCodec;
import com.datastax.driver.core.UDTValue;
/**
* Custom {@link org.springframework.data.repository.query.ParameterAccessor} that uses a {@link CassandraConverter} to
@@ -146,34 +148,70 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
return bindableValue;
}
if (property == null && getCustomConversions().hasCustomWriteTarget(bindableValue.getClass())) {
return converter.getConversionService().convert(bindableValue,
getCustomConversions().getCustomWriteTarget(bindableValue.getClass()));
}
// TODO: Polishing necessary
DataType parameterType = getDataType(index, property);
TypeCodec<?> cassandraType = CodecRegistry.DEFAULT_INSTANCE.codecFor(parameterType);
if (parameterType != null) {
if (property != null && getCustomConversions().hasCustomWriteTarget(property.getActualType())
&& property.isCollectionLike()) {
if (property != null && getCustomConversions().hasCustomWriteTarget(property.getActualType())
&& property.isCollectionLike()) {
Class<?> customWriteTarget = getCustomConversions().getCustomWriteTarget(property.getActualType());
Class<?> customWriteTarget = getCustomConversions().getCustomWriteTarget(property.getActualType());
if (Collection.class.isAssignableFrom(property.getType()) && bindableValue instanceof Collection) {
if (Collection.class.isAssignableFrom(property.getType()) && bindableValue instanceof Collection) {
Collection<Object> original = (Collection<Object>) bindableValue;
Collection<Object> converted = CollectionFactory.createCollection(property.getType(), original.size());
Collection<Object> original = (Collection<Object>) bindableValue;
Collection<Object> converted = CollectionFactory.createCollection(property.getType(), original.size());
for (Object element : original) {
converted.add(getConversionService().convert(element, customWriteTarget));
for (Object element : original) {
converted.add(getConversionService().convert(element, customWriteTarget));
}
return converted;
}
return converted;
}
}
if (cassandraType.getJavaType().getRawType().isAssignableFrom(bindableValue.getClass())) {
return bindableValue;
}
if (property != null) {
CassandraPersistentEntity<?> persistentEntity = converter.getMappingContext()
.getPersistentEntity(property.getActualType());
if (persistentEntity != null && persistentEntity.isUserDefinedType()) {
return toUDTValue(bindableValue, persistentEntity);
}
}
TypeCodec<?> cassandraType = CodecRegistry.DEFAULT_INSTANCE.codecFor(parameterType);
if (cassandraType.getJavaType().getRawType().isAssignableFrom(bindableValue.getClass())) {
return bindableValue;
}
return converter.getConversionService().convert(bindableValue, cassandraType.getJavaType().getRawType());
}
CassandraPersistentEntity<?> persistentEntity = converter.getMappingContext()
.getPersistentEntity(bindableValue.getClass());
if (persistentEntity != null && persistentEntity.isUserDefinedType()) {
return toUDTValue(bindableValue, persistentEntity);
}
return bindableValue;
}
private UDTValue toUDTValue(Object bindableValue, CassandraPersistentEntity<?> persistentEntity) {
if (bindableValue instanceof UDTValue) {
return (UDTValue) bindableValue;
}
UDTValue udtValue = persistentEntity.getUserType().newValue();
converter.write(bindableValue, udtValue, persistentEntity);
return udtValue;
}
private CustomConversions getCustomConversions() {
return converter.getCustomConversions();
}

View File

@@ -117,7 +117,7 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
}
return boundQuery;
} catch (RuntimeException e) {
} catch (RuntimeException e) { e.printStackTrace();
throw QueryCreationException.create(getQueryMethod(), e);
}
}

View File

@@ -1,2 +1,3 @@
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
http\://www.springframework.org/schema/data/cassandra/spring-cassandra-1.5.xsd=org/springframework/data/cassandra/config/spring-cassandra-1.5.xsd
http\://www.springframework.org/schema/data/cassandra/spring-cassandra.xsd=org/springframework/data/cassandra/config/spring-cassandra-1.5.xsd

View File

@@ -789,6 +789,7 @@ Defines a CassandraMappingContext for holding rich entity mapping information.
<xsd:complexType>
<xsd:sequence>
<xsd:element name="entity" type="entityType" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="user-type-resolver" type="userTypeResolverType" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="entity-base-packages" type="xsd:string" use="optional">
<xsd:annotation>
@@ -797,6 +798,17 @@ The comma-delimited base packages in which to scan for entities and their mappin
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="user-type-resolver-ref" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The reference to a UserTypeResolver. UserTypeResolver is required when working with User-defined types.
]]></xsd:documentation>
<xsd:appinfo>
<tool:exports
type="org.springframework.data.cassandra.mapping.UserTypeResolver" />
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
@@ -811,6 +823,23 @@ The comma-delimited base packages in which to scan for entities and their mappin
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:complexType name="userTypeResolverType">
<xsd:attribute name="keyspace-name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of a Cassandra Keyspace. No default; for the system keyspace, use the empty string.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="cluster-ref" type="clusterRef" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The reference to a Cassandra cluster; default is "cassandra-cluster".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="entityType">
<xsd:sequence>
<xsd:element name="table" type="tableType" minOccurs="0" maxOccurs="1" />

View File

@@ -0,0 +1,944 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2016 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.
-->
<xsd:schema xmlns="http://www.springframework.org/schema/data/cassandra"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.springframework.org/schema/data/cassandra"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"
schemaLocation="http://www.springframework.org/schema/beans/spring-beans.xsd" />
<xsd:import namespace="http://www.springframework.org/schema/tool"
schemaLocation="http://www.springframework.org/schema/tool/spring-tool.xsd" />
<xsd:import namespace="http://www.springframework.org/schema/context"
schemaLocation="http://www.springframework.org/schema/context/spring-context.xsd" />
<xsd:import namespace="http://www.springframework.org/schema/data/repository"
schemaLocation="http://www.springframework.org/schema/data/repository/spring-repository.xsd" />
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the configuration elements in the XML namespace for Spring Data Cassandra.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType name="executorRef" final="union">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="java.util.concurrent.Executor"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
<xsd:element name="cluster" type="clusterType">
<xsd:annotation>
<xsd:documentation
source="org.springframework.cassandra.config.xml.CassandraClusterFactoryBean"><![CDATA[
Defines a Cassandra cluster.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="com.datastax.driver.core.Cluster" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<xsd:simpleType name="clusterRef" final="union">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="com.datastax.driver.core.Cluster"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
<xsd:complexType name="clusterType">
<xsd:sequence>
<xsd:element name="local-pooling-options" type="poolingOptionsType" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[
Local pooling options.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="remote-pooling-options" type="poolingOptionsType" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[
Remote pooling options.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="socket-options" type="socketOptionsType" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[
Socket options.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="keyspace" type="keyspaceType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
Provides the ability to define a keyspace.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="startup-cql" type="xsd:string" minOccurs="0" maxOccurs="unbounded">
<!-- TODO: cql could come from a resource via a resource attribute... -->
<xsd:annotation>
<xsd:documentation><![CDATA[
Arbitrary CQL script to be executed against the system keyspace during bean initialization. Multiple elements will be executed in document order.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="shutdown-cql" type="xsd:string" minOccurs="0" maxOccurs="unbounded">
<!-- TODO: cql could come from a resource via a resource attribute... -->
<xsd:annotation>
<xsd:documentation><![CDATA[
Arbitrary CQL script to be executed against the system keyspace during bean destruction. Multiple elements will be executed in document order.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the Cassandra Cluster definition; default is "cassandra-cluster".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="address-translator-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Configures the address translator to use for the new cluster.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="com.datastax.driver.core.policies.AddressTranslator"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="auth-info-provider-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
AuthInfoProvider implementation.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="com.datastax.driver.core.AuthInfoProvider" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="cluster-builder-configurer-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets the ClusterBuilderConfigurer used to apply additional configuration logic
to the com.datastax.driver.core.Cluster.Builder.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.cassandra.config.ClusterBuilderConfigurer"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="cluster-name" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
An optional name for the create cluster.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="compression" type="xsd:string" default="NONE" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The protocol compression option. Default is "NONE".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="contact-points" type="xsd:string" default="localhost" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The comma separated list of Cassandra servers. Default is "localhost".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="heartbeat-interval-seconds" type="xsd:string" default="30" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Pooling option to set the heartbeat interval seconds, after which a message is sent on an idle connection
to make sure it's still alive. Applies to both local and remote pooling options (see
com.datastax.driver.core.HostDistance and com.datastax.driver.core.PoolingOptions) for more details.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="host-state-listener-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Custom Host State Listener for the Cassandra Cluster.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="com.datastax.driver.core.Host.StateListener" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="idle-timeout-seconds" type="xsd:string" default="120" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Pooling option to set the timeout in seconds before an idle connection is removed. Applies to both local and remote
pooling options (see com.datastax.driver.core.HostDistance and com.datastax.driver.core.PoolingOptions) for more details.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="initialization-executor-ref" type="executorRef" use="optional">
<xsd:annotation>
<xsd:documentation source="org.springframework.cassandra.config.PoolingOptionsFactoryBean"><![CDATA[
Pooling option defining a reference to an Executor used to initialize the Cassandra Pool. Applies to both local
and remote pooling options (see com.datastax.driver.core.HostDistance
and com.datastax.driver.core.PoolingOptions) for more details.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="jmx-reporting-enabled" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
Determine whether or not to enable JMX Reporting. Defaults to true.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="latency-tracker-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Custom Latency Tracker for the Cassandra Cluster.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="com.datastax.driver.core.LatencyTracker" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="load-balancing-policy-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
LoadBalancingPolicy implementation.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to
type="com.datastax.driver.core.policies.LoadBalancingPolicy" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="netty-options-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
NettyOptions implementation reference.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to
type="com.datastax.driver.core.NettyOptions" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="max-queue-size" type="xsd:string" default="256" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets the maximum number of requests that get enqueued if no connection is available.
If the queue grows past this value, new requests will be rejected immediately (and the driver will move to the
next host in the query plan). This limit is per connection pool, not global to the driver.
See com.datastax.driver.core.PoolingOption for more details. Available since Cassandra Driver 3.1.1.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="max-schema-agreement-wait-seconds" type="xsd:string" default="10" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets the maximum time to wait for schema agreement before returning from a DDL query. Defaults to 10 seconds.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="metrics-enabled" type="xsd:string"
default="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
Determine whether or not to collect metrics. Defaults to true.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="password" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
When Authentication is enabled, the password to use when connecting to the Cluster.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="pool-timeout-milliseconds" type="xsd:string" default="5000" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Pooling option to set the timeout in milliseconds when trying to acquire a connection from a host's pool. Applies to
both local and remote pooling options (see com.datastax.driver.core.HostDistance
and com.datastax.driver.core.PoolingOptions) for more details.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="port" type="xsd:string" use="optional" default="9042">
<xsd:annotation>
<xsd:documentation><![CDATA[
The native CQL port to connect to. Default is 9042.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="reconnection-policy-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
ReconnectionPolicy implementation.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to
type="com.datastax.driver.core.policies.ReconnectionPolicy" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="retry-policy-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
RetryPolicy implementation.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to
type="com.datastax.driver.core.policies.RetryPolicy" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="speculative-execution-policy-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Configures the speculative execution policy to use for the new cluster.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="com.datastax.driver.core.policies.SpeculativeExecutionPolicy"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="ssl-enabled" type="xsd:string" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
Determine if SSL is used for Cassandra communication. Defaults to false.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="ssl-options-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Custom SSL Options. sslEnabled must be true for sslOptions to be used.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="com.datastax.driver.core.SSLOptions" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="timestamp-generator-ref" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Configures the generator that will produce the client-side timestamp sent with each query.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="com.datastax.driver.core.TimestampGenerator"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="username" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
When Authentication is enabled, the username to use when connecting to the Cluster.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="session" type="sessionType">
<xsd:annotation>
<xsd:documentation
source="org.springframework.data.cassandra.config.xml.CassandraDataSessionFactoryBean"><![CDATA[
Defines a Cassandra session.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="com.datastax.driver.core.Session" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<xsd:simpleType name="sessionRef" final="union">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="com.datastax.driver.core.Session" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
<xsd:complexType name="sessionType">
<xsd:sequence>
<xsd:element name="startup-cql" type="xsd:string" minOccurs="0" maxOccurs="unbounded">
<!-- TODO: cql could come from a resource via a resource attribute... -->
<xsd:annotation>
<xsd:documentation><![CDATA[
Arbitrary CQL script to be executed against the session's keyspace during bean initialization. Multiple elements will be executed in document order.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="shutdown-cql" type="xsd:string" minOccurs="0" maxOccurs="unbounded">
<!-- TODO: cql could come from a resource via a resource attribute... -->
<xsd:annotation>
<xsd:documentation><![CDATA[
Arbitrary CQL script to be executed against the session's keyspace during bean destruction. Multiple elements will be executed in document order.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the session definition; default is "cassandra-session".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="cassandra-converter-ref" type="cassandraConverterRef" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The reference to a CassandraConverter; default is "cassandraConverter".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="cluster-ref" type="clusterRef" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The reference to a Cassandra cluster; default is "cassandra-cluster".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="keyspace-name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of a Cassandra Keyspace. No default; for the system keyspace, use the empty string.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="schema-action" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The schema action to perform; default is NONE.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="template" type="templateType">
<xsd:annotation>
<xsd:documentation
source="org.springframework.data.cassandra.config.xml.CassandraDataTemplateFactoryBean"><![CDATA[
Defines a CassandraTemplate.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports
type="org.springframework.data.cassandra.CassandraTemplate" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<xsd:complexType name="templateType">
<xsd:attribute name="id" type="xsd:ID" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the template; default is "cassandraTemplate".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="cassandra-converter-ref" type="cassandraConverterRef" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The reference to a CassandraConverter; default is "cassandraConverter".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="session-ref" type="sessionRef" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The reference to a Cassandra session; default is "cassandra-session".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="datacenterType">
<xsd:annotation>
<xsd:documentation><![CDATA[
Provides the ability to specify replication factors by data center.
]]></xsd:documentation>
</xsd:annotation>
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the data center.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="replication-factor" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The replication factor for the data center.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="keyspaceType">
<xsd:annotation>
<xsd:documentation><![CDATA[
Provides the ability to define keyspaces.
]]></xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="replication" type="replicationType" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[
Provides the ability to configure the keyspace's replication settings.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="action" use="required" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The keyspace action to take at startup and possibly shutdown.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="durable-writes" type="xsd:string" use="optional" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
Whether or not the keyspace supports durable writes.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of this keyspace. Required.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="poolingOptionsType">
<xsd:attribute name="core-connections" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
For each host, the driver keeps a core amount of connections open at all time.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="max-connections" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
More connections are created up to a configurable maximum number of connections.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="max-simultaneous-requests" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
If the utilisation of connections reaches this configurable threshold, then cassandra creates more connections up to max-connections.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="min-simultaneous-requests" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
If the utilisation of opened connections drops below by this configured threshold, then cassandra drops connections till core-connections.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="socketOptionsType">
<xsd:attribute name="connect-timeout-millis" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets connection timeout for client socket in milliseconds.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="keep-alive" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets the SO_KEEPALIVE socket option.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="read-timeout-millis" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets read timeout for client socket in milliseconds.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="receive-buffer-size" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets the SO_RCVBUF socket option.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="reuse-address" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets the SO_REUSEADDR socket option.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="send-buffer-size" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets the SO_SNDBUF socket option.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="so-linger" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets the SO_LINGER socket option.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="tcp-no-delay" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Sets the SO_TCPNODELAY socket option.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="replicationType">
<xsd:annotation>
<xsd:documentation><![CDATA[
Provides the ability to configure the keyspace's replication settings.
]]></xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="data-center" type="datacenterType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
Provides the ability to specify replication factors by data center.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="class" type="xsd:string" use="optional" default="SimpleStrategy">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the replication class; default is "SIMPLE_STRATEGY".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="replication-factor" type="xsd:string" use="optional" default="1">
<xsd:annotation>
<xsd:documentation><![CDATA[
The replication factor; default is 1.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="repositories">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="repository:repositories">
<xsd:attributeGroup ref="cassandra-repository-attributes" />
<xsd:attributeGroup ref="repository:repository-attributes" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:attributeGroup name="cassandra-repository-attributes">
<xsd:attribute name="cassandra-template-ref" type="cassandraTemplateRef">
<xsd:annotation>
<xsd:documentation><![CDATA[
The reference to a cassandraTemplate. Will default to 'cassandraTemplate'.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:simpleType name="cassandraTemplateRef" final="union">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to
type="org.springframework.data.cassandra.core.CassandraTemplate" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:element name="mapping">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines a CassandraMappingContext for holding rich entity mapping information.
]]></xsd:documentation>
<xsd:appinfo>
<tool:exports
type="org.springframework.data.cassandra.mapping.CassandraMappingContext" />
</xsd:appinfo>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="entity" type="entityType" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="user-type-resolver" type="userTypeResolverType" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="entity-base-packages" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The comma-delimited base packages in which to scan for entities and their mapping information.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="user-type-resolver-ref" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The reference to a UserTypeResolver. UserTypeResolver is required when working with User-defined types.
]]></xsd:documentation>
<xsd:appinfo>
<tool:exports
type="org.springframework.data.cassandra.mapping.UserTypeResolver" />
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="mappingContextRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.cassandra.mapping.CassandraMappingContext"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:complexType name="userTypeResolverType">
<xsd:attribute name="keyspace-name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of a Cassandra Keyspace. No default; for the system keyspace, use the empty string.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="cluster-ref" type="clusterRef" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The reference to a Cassandra cluster; default is "cassandra-cluster".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="entityType">
<xsd:sequence>
<xsd:element name="table" type="tableType" minOccurs="0" maxOccurs="1" />
<xsd:element name="property" type="propertyType" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="class" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Entity class name.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="propertyType">
<xsd:attribute name="column-name" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The column-name that the property should be mapped to.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="force-quote" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Whether the column name should be force-quoted.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="name" type="xsd:string" use="required" >
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the property. Required.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="tableType">
<xsd:attribute name="force-quote" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Whether to force-quote the table name.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="name" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Table name override.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<!-- TODO: allow specification of C* table options here -->
</xsd:complexType>
<xsd:element name="converter">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines a CassandraConverter for getting rich mapping functionality.
]]></xsd:documentation>
<xsd:appinfo>
<tool:exports
type="org.springframework.data.cassandra.convert.CassandraConverter" />
</xsd:appinfo>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="mapping-ref" type="mappingContextRef" use="optional">
<xsd:annotation>
<xsd:documentation
source="org.springframework.data.cassandra.mapping.CassandraMappingContext"><![CDATA[
The reference to a CassandraMappingContext. Will default to 'cassandraMapping'.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="cassandraConverterRef" final="union">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.cassandra.convert.CassandraConverter"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
</xsd:schema>

View File

@@ -62,16 +62,15 @@ public class CassandraSessionFactoryBeanUnitTests {
@Rule public ExpectedException exception = ExpectedException.none();
@Mock private CassandraConverter mockConverter;
@Mock CassandraConverter mockConverter;
@Mock Cluster mockCluster;
@Mock Session mockSession;
@Mock private Cluster mockCluster;
@Mock private Session mockSession;
private CassandraSessionFactoryBean factoryBean;
CassandraSessionFactoryBean factoryBean;
@Before
public void setup() {
when(mockCluster.connect()).thenReturn(mockSession);
when(mockSession.getCluster()).thenReturn(mockCluster);
@@ -85,6 +84,7 @@ public class CassandraSessionFactoryBeanUnitTests {
@Test
public void afterPropertiesSetPerformsSchemaAction() throws Exception {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
@@ -109,6 +109,7 @@ public class CassandraSessionFactoryBeanUnitTests {
@Test
public void afterPropertiesSetThrowsIllegalStateExceptionWhenConverterIsNull() throws Exception {
exception.expect(IllegalStateException.class);
exception.expectMessage("Converter was not properly initialized");
@@ -164,6 +165,7 @@ public class CassandraSessionFactoryBeanUnitTests {
@Test
public void performsSchemaActionDoesNotCallCreateTablesWhenSchemaActionIsNone() {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
@@ -181,101 +183,9 @@ public class CassandraSessionFactoryBeanUnitTests {
verify(factoryBean, never()).createTables(anyBoolean(), anyBoolean(), anyBoolean());
}
@Test
@SuppressWarnings("unchecked")
public void createsTableForEntity() throws Exception {
Metadata mockMetadata = mock(Metadata.class);
KeyspaceMetadata mockKeyspaceMetadata = mock(KeyspaceMetadata.class);
CassandraMappingContext mockMappingContext = mock(CassandraMappingContext.class);
CassandraPersistentEntity<Person> mockPersistentEntity = mock(CassandraPersistentEntity.class);
CassandraAdminOperations mockCassandraAdminOperations = mock(CassandraAdminOperations.class);
doReturn(mockCassandraAdminOperations).when(factoryBean).getCassandraAdminOperations();
doReturn(mockSession).when(factoryBean).getObject();
when(mockCluster.getMetadata()).thenReturn(mockMetadata);
when(mockMetadata.getKeyspace(eq("TestKeyspace"))).thenReturn(mockKeyspaceMetadata);
when(mockKeyspaceMetadata.getTables()).thenReturn(Collections.<TableMetadata> emptyList());
when(mockConverter.getMappingContext()).thenReturn(mockMappingContext);
when(mockMappingContext.getNonPrimaryKeyEntities())
.thenReturn(Collections.<CassandraPersistentEntity<?>> singletonList(mockPersistentEntity));
when(mockPersistentEntity.getTableName()).thenReturn(newCqlIdentifier("TestTable"));
when(mockPersistentEntity.getType()).thenReturn(Person.class);
factoryBean.setConverter(mockConverter);
factoryBean.setKeyspaceName("TestKeyspace");
assertThat(factoryBean.getConverter()).isEqualTo(mockConverter);
factoryBean.createTables(true, false, false);
verify(mockSession, times(1)).getCluster();
verify(mockCluster, times(1)).getMetadata();
verify(mockMetadata, times(1)).getKeyspace(eq("TestKeyspace"));
verify(mockKeyspaceMetadata, times(1)).getTables();
verify(mockConverter, times(1)).getMappingContext();
verify(mockMappingContext, times(1)).getNonPrimaryKeyEntities();
verify(mockPersistentEntity, times(1)).getTableName();
verify(mockPersistentEntity, times(1)).getType();
verify(mockCassandraAdminOperations, times(1)).createTable(eq(false), eq(newCqlIdentifier("TestTable")),
eq(Person.class), isNull(Map.class));
}
@Test
@SuppressWarnings("unchecked")
public void createTableForEntityIfNotExists() {
CassandraMappingContext mockMappingContext = mock(CassandraMappingContext.class);
CassandraPersistentEntity<Person> mockPersistentEntity = mock(CassandraPersistentEntity.class);
CassandraAdminOperations mockCassandraAdminOperations = mock(CassandraAdminOperations.class);
doReturn(mockCassandraAdminOperations).when(factoryBean).getCassandraAdminOperations();
doReturn(mockSession).when(factoryBean).getObject();
when(mockConverter.getMappingContext()).thenReturn(mockMappingContext);
when(mockMappingContext.getNonPrimaryKeyEntities())
.thenReturn(Collections.<CassandraPersistentEntity<?>> singletonList(mockPersistentEntity));
when(mockPersistentEntity.getTableName()).thenReturn(newCqlIdentifier("TestTable"));
when(mockPersistentEntity.getType()).thenReturn(Person.class);
factoryBean.setConverter(mockConverter);
factoryBean.setKeyspaceName("TestKeyspace");
assertThat(factoryBean.getConverter()).isEqualTo(mockConverter);
factoryBean.createTables(false, false, true);
verify(mockSession, never()).getCluster();
verify(mockCluster, never()).getMetadata();
verify(mockConverter, times(1)).getMappingContext();
verify(mockMappingContext, times(1)).getNonPrimaryKeyEntities();
verify(mockPersistentEntity, times(1)).getTableName();
verify(mockPersistentEntity, times(1)).getType();
verify(mockCassandraAdminOperations, times(1)).createTable(eq(true), eq(newCqlIdentifier("TestTable")),
eq(Person.class), isNull(Map.class));
}
@Test
public void createTableThrowsIllegalStateExceptionWhenKeyspaceNotFound() {
Metadata mockMetadata = mock(Metadata.class);
doReturn(mockSession).when(factoryBean).getObject();
when(mockCluster.getMetadata()).thenReturn(mockMetadata);
when(mockMetadata.getKeyspace(anyString())).thenReturn(null);
exception.expect(IllegalStateException.class);
exception.expectMessage("keyspace [TestKeyspace] does not exist");
factoryBean.setKeyspaceName("TestKeyspace");
factoryBean.createTables(true, false, true);
verify(mockSession, times(1)).getCluster();
verify(mockCluster, times(1)).getMetadata();
verify(mockMetadata, times(1)).getKeyspace(eq("TestKeyspace"));
verify(mockMetadata, times(1)).getKeyspace(eq("testkeyspace"));
}
// TODO: add more createTable tests covering drop tables, etc
@Test
public void setAndGetConverter() {
assertThat(factoryBean.getConverter()).isNull();
factoryBean.setConverter(mockConverter);
assertThat(factoryBean.getConverter()).isEqualTo(mockConverter);
@@ -284,6 +194,7 @@ public class CassandraSessionFactoryBeanUnitTests {
@Test
public void setConverterToNull() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage("CassandraConverter must not be null");
@@ -292,6 +203,7 @@ public class CassandraSessionFactoryBeanUnitTests {
@Test
public void setAndGetSchemaAction() {
assertThat(factoryBean.getSchemaAction()).isEqualTo(SchemaAction.NONE);
factoryBean.setSchemaAction(SchemaAction.CREATE);
assertThat(factoryBean.getSchemaAction()).isEqualTo(SchemaAction.CREATE);
@@ -308,5 +220,4 @@ public class CassandraSessionFactoryBeanUnitTests {
}
static class Person {}
}

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2013-2016 the original author or authors
* Copyright 2016 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
* 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
* 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.
* 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.config;
package org.springframework.data.cassandra.config;
import static org.assertj.core.api.Assertions.*;
@@ -34,7 +34,6 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataAccessException;
import org.springframework.data.cassandra.config.SchemaAction;
import org.springframework.data.cassandra.config.java.AbstractCassandraConfiguration;
import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.Person;
@@ -49,6 +48,7 @@ import com.datastax.driver.core.TableMetadata;
* {@link SchemaAction}s on startup of a Spring configured, Cassandra application client.
*
* @author John Blum
* @author Mark Paluch
* @see <a href="https://jira.spring.io/browse/DATACASS-219>DATACASS-219</a>
*/
public class SchemaActionIntegrationTests extends AbstractEmbeddedCassandraIntegrationTest {
@@ -84,6 +84,7 @@ public class SchemaActionIntegrationTests extends AbstractEmbeddedCassandraInteg
}
protected void assertHasTableWithColumns(Session session, String tableName, String... columns) {
Metadata clusterMetadata = session.getCluster().getMetadata();
KeyspaceMetadata keyspaceMetadata = clusterMetadata.getKeyspace(KEYSPACE_NAME);
@@ -92,12 +93,13 @@ public class SchemaActionIntegrationTests extends AbstractEmbeddedCassandraInteg
TableMetadata tableMetadata = keyspaceMetadata.getTable(tableName);
assertThat(tableMetadata).isNotNull();
assertThat(tableMetadata.getColumns()).hasSize(columns.length);
for (String columnName : columns) {
assertThat(tableMetadata.getColumn(columnName)).isNotNull();
}
assertThat(tableMetadata.getColumns()).hasSize(columns.length);
}
@Test
@@ -106,7 +108,7 @@ public class SchemaActionIntegrationTests extends AbstractEmbeddedCassandraInteg
@Override
public Void doInSession(Session session) throws DataAccessException {
assertHasTableWithColumns(session, "person", "firstName", "lastName", "nickname", "birthDate",
"numberOfChildren", "cool", "createdDate", "zoneId");
"numberOfChildren", "cool", "createdDate", "zoneId", "mainAddress", "alternativeAddresses");
return null;
}
});
@@ -135,7 +137,7 @@ public class SchemaActionIntegrationTests extends AbstractEmbeddedCassandraInteg
@Override
public Void doInSession(Session session) throws DataAccessException {
assertHasTableWithColumns(session, "person", "firstName", "lastName", "nickname", "birthDate",
"numberOfChildren", "cool", "createdDate", "zoneId");
"numberOfChildren", "cool", "createdDate", "zoneId", "mainAddress", "alternativeAddresses");
return null;
}
});
@@ -158,7 +160,7 @@ public class SchemaActionIntegrationTests extends AbstractEmbeddedCassandraInteg
@Override
public Void doInSession(Session session) throws DataAccessException {
assertHasTableWithColumns(session, "person", "firstName", "lastName", "nickname", "birthDate",
"numberOfChildren", "cool", "createdDate", "zoneId");
"numberOfChildren", "cool", "createdDate", "zoneId", "mainAddress", "alternativeAddresses");
return null;
}
});

View File

@@ -21,9 +21,12 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
import org.springframework.data.cassandra.mapping.SimpleUserTypeResolver;
import org.springframework.data.cassandra.test.integration.support.AbstractSpringDataEmbeddedCassandraIntegrationTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Configuration;
@@ -91,4 +94,17 @@ public class CassandraNamespaceIntegrationTests extends AbstractSpringDataEmbedd
assertThat(socketOptions.getReceiveBufferSize()).isEqualTo(65536);
assertThat(socketOptions.getSendBufferSize()).isEqualTo(65536);
}
/**
* @see DATACASS-172
*/
@Test
public void mappingContextShouldHaveUserTypeResolverConfigured() {
BasicCassandraMappingContext mappingContext = applicationContext.getBean(BasicCassandraMappingContext.class);
SimpleUserTypeResolver userTypeResolver = (SimpleUserTypeResolver) ReflectionTestUtils.getField(mappingContext, "userTypeResolver");
assertThat(userTypeResolver).isNotNull();
}
}

View File

@@ -0,0 +1,556 @@
/*
* Copyright 2016 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 static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Currency;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.config.SchemaAction;
import org.springframework.data.cassandra.domain.AllPossibleTypes;
import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.mapping.SimpleUserTypeResolver;
import org.springframework.data.cassandra.mapping.Table;
import org.springframework.data.cassandra.mapping.UserDefinedType;
import org.springframework.data.cassandra.mapping.UserTypeResolver;
import org.springframework.data.cassandra.test.integration.support.AbstractSpringDataEmbeddedCassandraIntegrationTest;
import org.springframework.data.cassandra.test.integration.support.IntegrationTestConfig;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.UDTValue;
import com.datastax.driver.core.UserType;
import com.datastax.driver.core.querybuilder.Delete;
import com.datastax.driver.core.querybuilder.Insert;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
import com.datastax.driver.core.querybuilder.Update;
import lombok.Data;
/**
* Integration tests for UDT types through {@link MappingCassandraConverter}.
*
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class MappingCassandraConverterUDTIntegrationTests extends AbstractSpringDataEmbeddedCassandraIntegrationTest {
private static AtomicBoolean initialized = new AtomicBoolean();
@Configuration
public static class Config extends IntegrationTestConfig {
@Override
public SchemaAction getSchemaAction() {
return SchemaAction.NONE;
}
@Override
public String[] getEntityBasePackages() {
return new String[] { AllPossibleTypes.class.getPackage().getName() };
}
@Override
public CustomConversions customConversions() {
return new CustomConversions(Arrays.asList(new UDTToCurrencyConverter(),
new CurrencyToUDTConverter(new SimpleUserTypeResolver(cluster().getObject(), getKeyspaceName()))));
}
}
@Autowired Session session;
@Autowired MappingCassandraConverter converter;
@Before
public void setUp() {
if (initialized.compareAndSet(false, true)) {
session.execute("DROP TABLE IF EXISTS addressbook;");
session.execute("CREATE TYPE IF NOT EXISTS address (zip text, city text, streetlines list<text>);");
session.execute("CREATE TABLE addressbook (id text PRIMARY KEY, currentaddress FROZEN<address>, "
+ "alternate FROZEN<address>, previousaddresses FROZEN<list<address>>);");
session.execute("DROP TABLE IF EXISTS bank;");
session.execute("CREATE TYPE IF NOT EXISTS currency (currency text);");
session.execute(
"CREATE TABLE bank (id text PRIMARY KEY, currency FROZEN<currency>, othercurrencies FROZEN<list<currency>>);");
session.execute("DROP TABLE IF EXISTS money;");
session.execute("CREATE TYPE IF NOT EXISTS currency (currency text);");
session.execute("CREATE TABLE money (currency FROZEN<currency> PRIMARY KEY);");
session.execute("DROP TABLE IF EXISTS car;");
session.execute("CREATE TYPE IF NOT EXISTS manufacturer (name text);");
session.execute("CREATE TYPE IF NOT EXISTS engine (manufacturer FROZEN<manufacturer>);");
session.execute("CREATE TABLE car (id text PRIMARY KEY, engine FROZEN<engine>);");
} else {
session.execute("TRUNCATE addressbook;");
session.execute("TRUNCATE bank;");
session.execute("TRUNCATE money;");
session.execute("TRUNCATE car;");
}
}
/**
* @see DATACASS-172
*/
@Test
public void shouldReadMappedUdt() {
session.execute("INSERT INTO addressbook (id, currentaddress) " + "VALUES ('1', "
+ "{zip:'69469', city: 'Weinheim', streetlines: ['Heckenpfad', '14']});");
ResultSet resultSet = session.execute("SELECT * from addressbook");
AddressBook addressBook = converter.read(AddressBook.class, resultSet.one());
assertThat(addressBook.getCurrentaddress()).isNotNull();
AddressUserType address = addressBook.getCurrentaddress();
assertThat(address.getCity()).isEqualTo("Weinheim");
assertThat(address.getZip()).isEqualTo("69469");
assertThat(address.getStreetLines()).contains("Heckenpfad", "14");
}
/**
* @see DATACASS-172
*/
@Test
public void shouldWriteMappedUdt() {
AddressUserType addressUserType = new AddressUserType();
addressUserType.setZip("69469");
addressUserType.setCity("Weinheim");
addressUserType.setStreetLines(Arrays.asList("Heckenpfad", "14"));
AddressBook addressBook = new AddressBook();
addressBook.setId("1");
addressBook.setCurrentaddress(addressUserType);
Insert insert = QueryBuilder.insertInto("addressbook");
converter.write(addressBook, insert);
assertThat(insert.toString()).isEqualTo("INSERT INTO addressbook (alternate,currentaddress,id,previousaddresses) "
+ "VALUES (null,{zip:'69469',city:'Weinheim',streetlines:['Heckenpfad','14']},'1',null);");
}
/**
* @see DATACASS-172
*/
@Test
public void shouldReadMappedUdtCollection() {
session.execute("INSERT INTO addressbook (id, previousaddresses) " + "VALUES ('1', "
+ " [{zip:'53773', city: 'Bonn'}, {zip:'12345', city: 'Bonn'}]);");
ResultSet resultSet = session.execute("SELECT * from addressbook");
AddressBook addressBook = converter.read(AddressBook.class, resultSet.one());
assertThat(addressBook.getPreviousaddresses()).hasSize(2);
AddressUserType address = addressBook.getPreviousaddresses().get(0);
assertThat(address.getCity()).isEqualTo("Bonn");
assertThat(address.getZip()).isEqualTo("53773");
assertThat(address.getStreetLines()).isEmpty();
}
/**
* @see DATACASS-172
*/
@Test
public void shouldWriteMappedUdtCollection() {
AddressUserType addressUserType = new AddressUserType();
addressUserType.setZip("69469");
addressUserType.setCity("Weinheim");
addressUserType.setStreetLines(Arrays.asList("Heckenpfad", "14"));
AddressBook addressBook = new AddressBook();
addressBook.setId("1");
addressBook.setPreviousaddresses(Collections.singletonList(addressUserType));
Insert insert = QueryBuilder.insertInto("addressbook");
converter.write(addressBook, insert);
assertThat(insert.toString()).isEqualTo("INSERT INTO addressbook (alternate,currentaddress,id,previousaddresses) "
+ "VALUES (null,null,'1',[{zip:'69469',city:'Weinheim',streetlines:['Heckenpfad','14']}]);");
}
/**
* @see DATACASS-172
*/
@Test
public void shouldReadUdt() {
session.execute("INSERT INTO addressbook (id, alternate) " + "VALUES ('1', "
+ "{zip:'69469', city: 'Weinheim', streetlines: ['Heckenpfad', '14']});");
ResultSet resultSet = session.execute("SELECT * from addressbook");
AddressBook addressBook = converter.read(AddressBook.class, resultSet.one());
assertThat(addressBook.getAlternate()).isNotNull();
assertThat(addressBook.getAlternate().getString("city")).isEqualTo("Weinheim");
assertThat(addressBook.getAlternate().getString("zip")).isEqualTo("69469");
}
/**
* @see DATACASS-172
*/
@Test
public void shouldWriteUdt() {
CassandraPersistentEntity<?> persistentEntity = converter.getMappingContext()
.getPersistentEntity(AddressUserType.class);
UDTValue udtValue = persistentEntity.getUserType().newValue();
udtValue.setString("zip", "69469");
udtValue.setString("city", "Weinheim");
udtValue.setList("streetlines", Arrays.asList("Heckenpfad", "14"));
AddressBook addressBook = new AddressBook();
addressBook.setId("1");
addressBook.setAlternate(udtValue);
Insert insert = QueryBuilder.insertInto("addressbook");
converter.write(addressBook, insert);
assertThat(insert.toString()).isEqualTo("INSERT INTO addressbook (alternate,currentaddress,id,previousaddresses) "
+ "VALUES ({zip:'69469',city:'Weinheim',streetlines:['Heckenpfad','14']},null,'1',null);");
}
/**
* @see DATACASS-172
*/
@Test
public void shouldWriteUdtPk() {
AddressUserType addressUserType = new AddressUserType();
addressUserType.setZip("69469");
addressUserType.setCity("Weinheim");
addressUserType.setStreetLines(Arrays.asList("Heckenpfad", "14"));
WithMappedUdtId withUdtId = new WithMappedUdtId();
withUdtId.setId(addressUserType);
Insert insert = QueryBuilder.insertInto("addressbook");
converter.write(withUdtId, insert);
assertThat(insert.toString()).isEqualTo(
"INSERT INTO addressbook (id) " + "VALUES ({zip:'69469',city:'Weinheim',streetlines:['Heckenpfad','14']});");
}
/**
* @see DATACASS-172
*/
@Test
public void shouldWriteMappedUdtPk() {
CassandraPersistentEntity<?> persistentEntity = converter.getMappingContext()
.getPersistentEntity(AddressUserType.class);
UDTValue udtValue = persistentEntity.getUserType().newValue();
udtValue.setString("zip", "69469");
udtValue.setString("city", "Weinheim");
udtValue.setList("streetlines", Arrays.asList("Heckenpfad", "14"));
WithUdtId withUdtId = new WithUdtId();
withUdtId.setId(udtValue);
Insert insert = QueryBuilder.insertInto("addressbook");
converter.write(withUdtId, insert);
assertThat(insert.toString()).isEqualTo(
"INSERT INTO addressbook (id) " + "VALUES ({zip:'69469',city:'Weinheim',streetlines:['Heckenpfad','14']});");
}
/**
* @see DATACASS-172
*/
@Test
public void shouldReadUdtWithCustomConversion() {
session.execute("INSERT INTO bank (id, currency) " + "VALUES ('1', {currency:'EUR'});");
ResultSet resultSet = session.execute("SELECT * from bank");
Bank addressBook = converter.read(Bank.class, resultSet.one());
assertThat(addressBook.getCurrency()).isNotNull();
assertThat(addressBook.getCurrency().getCurrencyCode()).isEqualTo("EUR");
}
/**
* @see DATACASS-172
*/
@Test
public void shouldReadUdtListWithCustomConversion() {
session.execute("INSERT INTO bank (id, othercurrencies) " + "VALUES ('1', [{currency:'EUR'}]);");
ResultSet resultSet = session.execute("SELECT * from bank");
Bank addressBook = converter.read(Bank.class, resultSet.one());
assertThat(addressBook.getOtherCurrencies()).hasSize(1).contains(Currency.getInstance("EUR"));
}
/**
* @see DATACASS-172
*/
@Test
public void shouldWriteUdtWithCustomConversion() {
Bank bank = new Bank();
bank.setCurrency(Currency.getInstance("EUR"));
Insert insert = QueryBuilder.insertInto("bank");
converter.write(bank, insert);
assertThat(insert.toString()).isEqualTo("INSERT INTO bank (currency,id,othercurrencies) VALUES ({currency:'EUR'},null,null);");
}
/**
* @see DATACASS-172
*/
@Test
public void shouldWriteUdtUpdateWherePrimaryKeyWithCustomConversion() {
Money money = new Money();
money.setCurrency(Currency.getInstance("EUR"));
Update update = QueryBuilder.update("money");
converter.write(money, update);
assertThat(update.toString()).isEqualTo("UPDATE money WHERE currency={currency:'EUR'};");
}
/**
* @see DATACASS-172
*/
@Test
public void shouldWriteUdtUpdateAssignmentsWithCustomConversion() {
MoneyTransfer money = new MoneyTransfer();
money.setId("1");
money.setCurrency(Currency.getInstance("EUR"));
Update update = QueryBuilder.update("money");
converter.write(money, update);
assertThat(update.toString()).isEqualTo("UPDATE money SET currency={currency:'EUR'} WHERE id='1';");
}
/**
* @see DATACASS-172
*/
@Test
public void shouldWriteUdtSelectWherePrimaryKeyWithCustomConversion() {
Money money = new Money();
money.setCurrency(Currency.getInstance("EUR"));
Select select = QueryBuilder.select().from("money");
converter.write(money, select.where());
assertThat(select.toString()).isEqualTo("SELECT * FROM money WHERE currency={currency:'EUR'};");
}
/**
* @see DATACASS-172
*/
@Test
public void shouldWriteUdtDeleteWherePrimaryKeyWithCustomConversion() {
Money money = new Money();
money.setCurrency(Currency.getInstance("EUR"));
Delete delete = QueryBuilder.delete().from("money");
converter.write(money, delete.where());
assertThat(delete.toString()).isEqualTo("DELETE FROM money WHERE currency={currency:'EUR'};");
}
/**
* @see DATACASS-172
*/
@Test
public void shouldWriteUdtListWithCustomConversion() {
Bank bank = new Bank();
bank.setOtherCurrencies(Collections.singletonList(Currency.getInstance("EUR")));
Insert insert = QueryBuilder.insertInto("bank");
converter.write(bank, insert);
assertThat(insert.toString()).isEqualTo("INSERT INTO bank (currency,id,othercurrencies) VALUES (null,null,[{currency:'EUR'}]);");
}
/**
* @see DATACASS-172
*/
@Test
public void shouldReadNestedUdt() {
session.execute("INSERT INTO car (id, engine) VALUES ('1', {manufacturer: {name:'a good one'}});");
ResultSet resultSet = session.execute("SELECT * from car");
Car car = converter.read(Car.class, resultSet.one());
assertThat(car.getEngine()).isNotNull();
assertThat(car.getEngine().getManufacturer()).isNotNull();
assertThat(car.getEngine().getManufacturer().getName()).isEqualTo("a good one");
}
/**
* @see DATACASS-172
*/
@Test
public void shouldWriteNestedUdt() {
session.execute("INSERT INTO car (id, engine) VALUES ('1', {manufacturer: {name:'a good one'}});");
Manufacturer manufacturer = new Manufacturer();
manufacturer.setName("a good one");
Engine engine = new Engine();
engine.setManufacturer(manufacturer);
Car car = new Car();
car.setId("1");
car.setEngine(engine);
Insert insert = QueryBuilder.insertInto("car");
converter.write(car, insert);
assertThat(insert.toString()).isEqualTo("INSERT INTO car (engine,id) VALUES ({manufacturer:{name:'a good one'}},'1');");
}
@Table
@Data
private static class Bank {
@Id String id;
Currency currency;
List<Currency> otherCurrencies;
}
@Data
@Table
public static class Money {
@Id private Currency currency;
}
@Data
@Table
public static class MoneyTransfer {
@Id String id;
private Currency currency;
}
@Table
@Data
private static class Car {
@Id String id;
Engine engine;
}
@UserDefinedType
@Data
private static class Engine {
Manufacturer manufacturer;
}
@UserDefinedType
@Data
private static class Manufacturer {
String name;
}
@Data
@Table
public static class AddressBook {
@Id private String id;
private AddressUserType currentaddress;
private List<AddressUserType> previousaddresses;
private UDTValue alternate;
}
@Data
@Table
public static class WithUdtId {
@Id private UDTValue id;
}
@Data
@Table
public static class WithMappedUdtId {
@Id private AddressUserType id;
}
@UserDefinedType("address")
@Data
public static class AddressUserType {
String zip;
String city;
List<String> streetLines;
}
private static class UDTToCurrencyConverter implements Converter<UDTValue, Currency> {
@Override
public Currency convert(UDTValue source) {
return Currency.getInstance(source.getString("currency"));
}
}
private static class CurrencyToUDTConverter implements Converter<Currency, UDTValue> {
final UserTypeResolver userTypeResolver;
CurrencyToUDTConverter(UserTypeResolver userTypeResolver) {
this.userTypeResolver = userTypeResolver;
}
@Override
public UDTValue convert(Currency source) {
UserType userType = userTypeResolver.resolveType(CqlIdentifier.cqlId("currency"));
UDTValue udtValue = userType.newValue();
udtValue.setString("currency", source.getCurrencyCode());
return udtValue;
}
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2016 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 static org.mockito.Mockito.*;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
import org.springframework.data.cassandra.mapping.UserDefinedType;
import org.springframework.data.cassandra.mapping.UserTypeResolver;
import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.UserType;
import lombok.Data;
/**
* Unit tests for {@link CassandraPersistentEntitySchemaCreator}.
*
* @author Mark Paluch.
*/
@RunWith(MockitoJUnitRunner.class)
public class CassandraPersistentEntitySchemaCreatorUnitTests {
@Mock CassandraAdminOperations operations;
@Mock KeyspaceMetadata metadata;
@Mock UserType universetype;
@Mock UserType moontype;
@Mock UserType manufacturertype;
@Mock UserType biketype;
@Mock UserType tiretype;
BasicCassandraMappingContext context = new BasicCassandraMappingContext();
@Before
public void setUp() throws Exception {
context.setUserTypeResolver(new UserTypeResolver() {
@Override
public UserType resolveType(CqlIdentifier typeName) {
return metadata.getUserType(typeName.toCql());
}
});
}
@Test
public void shouldCreateTypesInOrder() throws Exception {
context.getPersistentEntity(MoonType.class);
context.getPersistentEntity(PlanetType.class);
context.getPersistentEntity(UniverseType.class);
when(metadata.getUserType("universetype")).thenReturn(universetype);
when(metadata.getUserType("moontype")).thenReturn(moontype);
CassandraPersistentEntitySchemaCreator schemaCreator = new CassandraPersistentEntitySchemaCreator(context,
operations);
schemaCreator.createUserTypes(false, false, false);
verify(operations).execute(Mockito.contains("CREATE TYPE universetype"));
verify(operations).execute(Mockito.contains("CREATE TYPE moontype"));
verify(operations).execute(Mockito.contains("CREATE TYPE planettype"));
InOrder inOrder = Mockito.inOrder(operations);
inOrder.verify(operations).execute(Mockito.contains("CREATE TYPE universetype"));
inOrder.verify(operations).execute(Mockito.contains("CREATE TYPE moontype"));
inOrder.verify(operations).execute(Mockito.contains("CREATE TYPE planettype"));
}
/**
* @author Mark Paluch
*/
@UserDefinedType
@Data
static class UniverseType {
String name;
}
/**
* @author Mark Paluch
*/
@UserDefinedType
static class MoonType {
UniverseType universeType;
}
/**
* @author Mark Paluch
*/
@UserDefinedType
static class PlanetType {
Set<MoonType> moons;
UniverseType universeType;
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.data.cassandra.mapping;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import java.util.Collection;
@@ -23,6 +24,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cassandra.core.Ordering;
import org.springframework.cassandra.core.PrimaryKeyType;
@@ -30,6 +32,7 @@ import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.cassandra.core.keyspace.ColumnSpecification;
import org.springframework.cassandra.core.keyspace.CreateTableSpecification;
import org.springframework.core.convert.converter.Converter;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.convert.CustomConversions;
import org.springframework.data.convert.WritingConverter;
@@ -38,6 +41,8 @@ import org.springframework.data.util.ClassTypeInformation;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.DataType.Name;
import com.datastax.driver.core.UDTValue;
import com.datastax.driver.core.UserType;
/**
* Unit tests for {@link BasicCassandraMappingContext}.
@@ -49,6 +54,18 @@ public class BasicCassandraMappingContextUnitTests {
BasicCassandraMappingContext mappingContext = new BasicCassandraMappingContext();
@Before
public void before() {
mappingContext.setUserTypeResolver(new UserTypeResolver() {
@Override
public UserType resolveType(CqlIdentifier typeName) {
return null;
}
});
}
@Test(expected = MappingException.class)
public void testGetPersistentEntityOfTransientType() {
mappingContext.getPersistentEntity(Transient.class);
@@ -66,16 +83,6 @@ public class BasicCassandraMappingContextUnitTests {
assertThat(mappingContext.contains(Y.class)).isFalse();
}
@Table
private static class X {
@PrimaryKey String key;
}
@Table
private static class Y {
@PrimaryKey String key;
}
/**
* @see DATACASS-248
*/
@@ -366,8 +373,173 @@ public class BasicCassandraMappingContextUnitTests {
.isEqualTo(DataType.list(DataType.varchar()));
}
/**
* @see DATACASS-172
*/
@Test
public void shouldRegisterUdtTypes() {
CassandraPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(MappedUdt.class);
assertThat(persistentEntity.isUserDefinedType()).isTrue();
}
/**
* @see DATACASS-172
*/
@Test
public void getNonPrimaryKeyEntitiesShouldNotContainUdt() {
CassandraPersistentEntity<?> existingPersistentEntity = mappingContext.getPersistentEntity(MappedUdt.class);
assertThat(mappingContext.getNonPrimaryKeyEntities()).doesNotContain(existingPersistentEntity);
}
/**
* @see DATACASS-172
*/
@Test
public void getPersistentEntitiesShouldContainUdt() {
CassandraPersistentEntity<?> existingPersistentEntity = mappingContext.getPersistentEntity(MappedUdt.class);
assertThat(mappingContext.getPersistentEntities(true)).contains(existingPersistentEntity);
assertThat(mappingContext.getPersistentEntities(false)).doesNotContain(existingPersistentEntity);
}
/**
* @see DATACASS-172
*/
@Test
public void usesTypeShouldNotReportTypeUsage() {
UserType myTypeMock = mock(UserType.class, "mappedudt");
when(myTypeMock.getTypeName()).thenReturn("mappedudt");
assertThat(mappingContext.usesUserType(myTypeMock)).isFalse();
}
/**
* @see DATACASS-172
*/
@Test
public void usesTypeShouldReportTypeUsageInMappedUdt() {
final UserType myTypeMock = mock(UserType.class, "mappedudt");
when(myTypeMock.getTypeName()).thenReturn("mappedudt");
mappingContext.setUserTypeResolver(new UserTypeResolver() {
@Override
public UserType resolveType(CqlIdentifier typeName) {
return myTypeMock;
}
});
mappingContext.getPersistentEntity(WithUdt.class);
assertThat(mappingContext.usesUserType(myTypeMock)).isTrue();
}
/**
* @see DATACASS-172
*/
@Test
public void usesTypeShouldReportTypeUsageInColumn() {
final UserType myTypeMock = mock(UserType.class, "mappedudt");
when(myTypeMock.getTypeName()).thenReturn("mappedudt");
mappingContext.setUserTypeResolver(new UserTypeResolver() {
@Override
public UserType resolveType(CqlIdentifier typeName) {
return myTypeMock;
}
});
mappingContext.getPersistentEntity(MappedUdt.class);
assertThat(mappingContext.usesUserType(myTypeMock)).isTrue();
}
/**
* @see DATACASS-172
*/
@Test
public void createTableForComplexPrimaryKeyShouldFail() {
try {
mappingContext
.getCreateTableSpecificationFor(mappingContext.getPersistentEntity(EntityWithComplexPrimaryKeyColumn.class));
fail("Missing InvalidDataAccessApiUsageException");
} catch (InvalidDataAccessApiUsageException e) {
assertThat(e).hasMessageContaining("Unknown type [class java.lang.Object] for property [complexObject]");
}
try {
mappingContext.getCreateTableSpecificationFor(mappingContext.getPersistentEntity(EntityWithComplexId.class));
fail("Missing InvalidDataAccessApiUsageException");
} catch (InvalidDataAccessApiUsageException e) {
assertThat(e).hasMessageContaining("Unknown type [class java.lang.Object] for property [complexObject]");
}
try {
mappingContext.getCreateTableSpecificationFor(
mappingContext.getPersistentEntity(EntityWithPrimaryKeyClassWithComplexId.class));
fail("Missing InvalidDataAccessApiUsageException");
} catch (InvalidDataAccessApiUsageException e) {
assertThat(e).hasMessageContaining("Unknown type [class java.lang.Object] for property [complexObject]");
}
}
@Table
static class EntityWithComplexPrimaryKeyColumn {
@PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) Object complexObject;
}
@Table
static class EntityWithComplexId {
@Id Object complexObject;
}
@PrimaryKeyClass
static class PrimaryKeyClassWithComplexId {
@PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) Object complexObject;
}
@Table
static class EntityWithPrimaryKeyClassWithComplexId {
@Id PrimaryKeyClassWithComplexId primaryKeyClassWithComplexId;
}
private static class Human {}
@Table
private static class X {
@PrimaryKey String key;
}
@Table
private static class Y {
@PrimaryKey String key;
}
@UserDefinedType
private static class MappedUdt {}
@Table
private static class WithUdt {
@Id String id;
@CassandraType(type = DataType.Name.UDT, userTypeName = "mappedudt") UDTValue udtValue;
}
enum HumanToStringConverter implements Converter<Human, String> {
INSTANCE;

View File

@@ -87,35 +87,6 @@ public class BasicCassandraPersistentEntityMetadataVerifierUnitTests {
}
}
/**
* @see DATACASS-258
*/
@Test
public void shouldFailWithComplexTypePrimaryKey() {
try {
verifier.verify(getEntity(EntityWithComplexTypePrimaryKey.class));
fail("Missing MappingException");
} catch (MappingException e) {
assertThat(e)
.hasMessageContaining("Property [species] annotated with @PrimaryKeyColumn must be a simple CassandraType");
}
}
/**
* @see DATACASS-258
*/
@Test
public void shouldFailWithComplexTypeId() {
try {
verifier.verify(getEntity(EntityWithComplexTypeId.class));
fail("Missing MappingException");
} catch (MappingException e) {
assertThat(e).hasMessageContaining("Property [species] annotated with @Id must be a simple CassandraType");
}
}
/**
* @see DATACASS-258
*/
@@ -203,12 +174,6 @@ public class BasicCassandraPersistentEntityMetadataVerifierUnitTests {
@PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) Object species;
}
@Table
static class EntityWithComplexTypeId {
@Id Object species;
}
@Table
@PrimaryKeyClass
static class TooManyAnnotations {}

View File

@@ -32,6 +32,7 @@ import org.springframework.data.util.ClassTypeInformation;
* @author Alex Shvid
* @author Matthew T. Adams
* @author John Blum
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class BasicCassandraPersistentEntityUnitTests {
@@ -100,6 +101,18 @@ public class BasicCassandraPersistentEntityUnitTests {
verify(entitySpy, never()).setTableName(isA(CqlIdentifier.class));
}
/**
* @see DATACASS-172
*/
@Test
public void isUserDefinedTypeShouldReturnFalse() {
BasicCassandraPersistentEntity<UserLine> entity = new BasicCassandraPersistentEntity<UserLine>(
ClassTypeInformation.from(UserLine.class));
assertThat(entity.isUserDefinedType()).isFalse();
}
@Table("messages")
static class Message {}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2016 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 static org.assertj.core.api.Assertions.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.data.util.ClassTypeInformation;
/**
* Unit tests for {@link CassandraUserTypePersistentEntity}.
*
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class CassandraUserTypePersistentEntityUnitTests {
@Mock CassandraMappingContext mappingContextMock;
@Mock UserTypeResolver userTypeResolverMock;
/**
* @see DATACASS-172
*/
@Test
public void isUserDefinedTypeShouldReportTrue() {
CassandraUserTypePersistentEntity<MappedUdt> type = getEntity(MappedUdt.class);
assertThat(type.isUserDefinedType()).isTrue();
}
/**
* @see DATACASS-172
*/
@Test
public void getTableNameShouldReturnDefaultName() {
CassandraUserTypePersistentEntity<MappedUdt> type = getEntity(MappedUdt.class);
assertThat(type.getTableName()).isEqualTo(CqlIdentifier.cqlId("mappedudt"));
assertThat(type.getTableName()).isEqualTo(CqlIdentifier.cqlId("Mappedudt"));
}
/**
* @see DATACASS-172
*/
@Test
public void getTableNameShouldReturnDefinedName() {
CassandraUserTypePersistentEntity<WithName> type = getEntity(WithName.class);
assertThat(type.getTableName()).isEqualTo(CqlIdentifier.cqlId("withname"));
assertThat(type.getTableName()).isEqualTo(CqlIdentifier.cqlId("Withname"));
}
/**
* @see DATACASS-172
*/
@Test
public void getTableNameShouldReturnDefinedNameUsingForceQuote() {
CassandraUserTypePersistentEntity<WithForceQuote> type = getEntity(WithForceQuote.class);
assertThat(type.getTableName()).isNotEqualTo(CqlIdentifier.cqlId("upperCase", true));
assertThat(type.getTableName()).isEqualTo(CqlIdentifier.cqlId("UpperCase", true));
}
private <T> CassandraUserTypePersistentEntity<T> getEntity(Class<T> entityClass) {
return new CassandraUserTypePersistentEntity<T>(ClassTypeInformation.from(entityClass), mappingContextMock, null,
userTypeResolverMock);
}
@UserDefinedType
static class MappedUdt {}
@UserDefinedType("withname")
static class WithName {}
@UserDefinedType(value = "UpperCase", forceQuote = true)
static class WithForceQuote {}
}

View File

@@ -75,7 +75,7 @@ public class CompositeCassandraPersistentEntityMetadataVerifierUnitTests {
fail("Missing MappingException");
} catch (MappingException e) {
assertThat(e).hasMessageContaining(
"Cassandra entities must be annotated with either @Persistent, @Table, or @PrimaryKeyClass");
"Cassandra entities must be annotated with either @Persistent, @Table, @UserDefinedType or @PrimaryKeyClass");
}
}

View File

@@ -16,11 +16,13 @@
package org.springframework.data.cassandra.mapping;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.codehaus.jackson.map.ObjectMapper;
@@ -38,6 +40,8 @@ import org.springframework.util.StringUtils;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.DataType.CollectionType;
import com.datastax.driver.core.DataType.Name;
import com.datastax.driver.core.UDTValue;
import com.datastax.driver.core.UserType;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -51,7 +55,7 @@ import lombok.NoArgsConstructor;
*/
public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
BasicCassandraMappingContext ctx = new BasicCassandraMappingContext();
private BasicCassandraMappingContext ctx = new BasicCassandraMappingContext();
@Before
public void setUp() throws Exception {
@@ -74,7 +78,7 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
CreateTableSpecification specification = ctx.getCreateTableSpecificationFor(persistentEntity);
assertThat(getColumn("human", specification).getType()).isEqualTo(DataType.varchar());
assertThat(getColumnType("human", specification)).isEqualTo(DataType.varchar());
ColumnSpecification friends = getColumn("friends", specification);
assertThat(friends.getType().isCollection()).isTrue();
@@ -103,7 +107,7 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
CreateTableSpecification specification = ctx.getCreateTableSpecificationFor(persistentEntity);
assertThat(getColumn("floater", specification).getType()).isEqualTo(DataType.cfloat());
assertThat(getColumnType("floater", specification)).isEqualTo(DataType.cfloat());
ColumnSpecification enemies = getColumn("enemies", specification);
assertThat(enemies.getType().isCollection()).isTrue();
@@ -122,10 +126,10 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
CreateTableSpecification specification = getCreateTableSpecificationFor(AllPossibleTypes.class);
assertThat(getColumn("id", specification).getType()).isEqualTo(DataType.varchar());
assertThat(getColumn("zoneId", specification).getType()).isEqualTo(DataType.varchar());
assertThat(getColumn("bpZoneId", specification).getType()).isEqualTo(DataType.varchar());
assertThat(getColumn("anEnum", specification).getType()).isEqualTo(DataType.varchar());
assertThat(getColumnType("id", specification)).isEqualTo(DataType.varchar());
assertThat(getColumnType("zoneId", specification)).isEqualTo(DataType.varchar());
assertThat(getColumnType("bpZoneId", specification)).isEqualTo(DataType.varchar());
assertThat(getColumnType("anEnum", specification)).isEqualTo(DataType.varchar());
}
/**
@@ -136,8 +140,8 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
CreateTableSpecification specification = getCreateTableSpecificationFor(AllPossibleTypes.class);
assertThat(getColumn("boxedByte", specification).getType()).isEqualTo(DataType.tinyint());
assertThat(getColumn("primitiveByte", specification).getType()).isEqualTo(DataType.tinyint());
assertThat(getColumnType("boxedByte", specification)).isEqualTo(DataType.tinyint());
assertThat(getColumnType("primitiveByte", specification)).isEqualTo(DataType.tinyint());
}
/**
@@ -148,8 +152,8 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
CreateTableSpecification specification = getCreateTableSpecificationFor(AllPossibleTypes.class);
assertThat(getColumn("boxedShort", specification).getType()).isEqualTo(DataType.smallint());
assertThat(getColumn("primitiveShort", specification).getType()).isEqualTo(DataType.smallint());
assertThat(getColumnType("boxedShort", specification)).isEqualTo(DataType.smallint());
assertThat(getColumnType("primitiveShort", specification)).isEqualTo(DataType.smallint());
}
/**
@@ -160,8 +164,8 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
CreateTableSpecification specification = getCreateTableSpecificationFor(AllPossibleTypes.class);
assertThat(getColumn("boxedLong", specification).getType()).isEqualTo(DataType.bigint());
assertThat(getColumn("primitiveLong", specification).getType()).isEqualTo(DataType.bigint());
assertThat(getColumnType("boxedLong", specification)).isEqualTo(DataType.bigint());
assertThat(getColumnType("primitiveLong", specification)).isEqualTo(DataType.bigint());
}
/**
@@ -172,7 +176,7 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
CreateTableSpecification specification = getCreateTableSpecificationFor(AllPossibleTypes.class);
assertThat(getColumn("bigInteger", specification).getType()).isEqualTo(DataType.varint());
assertThat(getColumnType("bigInteger", specification)).isEqualTo(DataType.varint());
}
/**
@@ -183,7 +187,7 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
CreateTableSpecification specification = getCreateTableSpecificationFor(AllPossibleTypes.class);
assertThat(getColumn("bigDecimal", specification).getType()).isEqualTo(DataType.decimal());
assertThat(getColumnType("bigDecimal", specification)).isEqualTo(DataType.decimal());
}
/**
@@ -194,8 +198,8 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
CreateTableSpecification specification = getCreateTableSpecificationFor(AllPossibleTypes.class);
assertThat(getColumn("boxedInteger", specification).getType()).isEqualTo(DataType.cint());
assertThat(getColumn("primitiveInteger", specification).getType()).isEqualTo(DataType.cint());
assertThat(getColumnType("boxedInteger", specification)).isEqualTo(DataType.cint());
assertThat(getColumnType("primitiveInteger", specification)).isEqualTo(DataType.cint());
}
/**
@@ -206,8 +210,8 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
CreateTableSpecification specification = getCreateTableSpecificationFor(AllPossibleTypes.class);
assertThat(getColumn("boxedFloat", specification).getType()).isEqualTo(DataType.cfloat());
assertThat(getColumn("primitiveFloat", specification).getType()).isEqualTo(DataType.cfloat());
assertThat(getColumnType("boxedFloat", specification)).isEqualTo(DataType.cfloat());
assertThat(getColumnType("primitiveFloat", specification)).isEqualTo(DataType.cfloat());
}
/**
@@ -218,8 +222,8 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
CreateTableSpecification specification = getCreateTableSpecificationFor(AllPossibleTypes.class);
assertThat(getColumn("boxedDouble", specification).getType()).isEqualTo(DataType.cdouble());
assertThat(getColumn("primitiveDouble", specification).getType()).isEqualTo(DataType.cdouble());
assertThat(getColumnType("boxedDouble", specification)).isEqualTo(DataType.cdouble());
assertThat(getColumnType("primitiveDouble", specification)).isEqualTo(DataType.cdouble());
}
/**
@@ -230,8 +234,8 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
CreateTableSpecification specification = getCreateTableSpecificationFor(AllPossibleTypes.class);
assertThat(getColumn("boxedBoolean", specification).getType()).isEqualTo(DataType.cboolean());
assertThat(getColumn("primitiveBoolean", specification).getType()).isEqualTo(DataType.cboolean());
assertThat(getColumnType("boxedBoolean", specification)).isEqualTo(DataType.cboolean());
assertThat(getColumnType("primitiveBoolean", specification)).isEqualTo(DataType.cboolean());
}
/**
@@ -242,11 +246,11 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
CreateTableSpecification specification = getCreateTableSpecificationFor(AllPossibleTypes.class);
assertThat(getColumn("date", specification).getType()).isEqualTo(DataType.date());
assertThat(getColumn("localDate", specification).getType()).isEqualTo(DataType.date());
assertThat(getColumn("jodaLocalDate", specification).getType()).isEqualTo(DataType.date());
assertThat(getColumn("jodaDateMidnight", specification).getType()).isEqualTo(DataType.date());
assertThat(getColumn("bpLocalDate", specification).getType()).isEqualTo(DataType.date());
assertThat(getColumnType("date", specification)).isEqualTo(DataType.date());
assertThat(getColumnType("localDate", specification)).isEqualTo(DataType.date());
assertThat(getColumnType("jodaLocalDate", specification)).isEqualTo(DataType.date());
assertThat(getColumnType("jodaDateMidnight", specification)).isEqualTo(DataType.date());
assertThat(getColumnType("bpLocalDate", specification)).isEqualTo(DataType.date());
}
/**
@@ -257,13 +261,13 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
CreateTableSpecification specification = getCreateTableSpecificationFor(AllPossibleTypes.class);
assertThat(getColumn("timestamp", specification).getType()).isEqualTo(DataType.timestamp());
assertThat(getColumn("localDateTime", specification).getType()).isEqualTo(DataType.timestamp());
assertThat(getColumn("instant", specification).getType()).isEqualTo(DataType.timestamp());
assertThat(getColumn("jodaLocalDateTime", specification).getType()).isEqualTo(DataType.timestamp());
assertThat(getColumn("jodaDateTime", specification).getType()).isEqualTo(DataType.timestamp());
assertThat(getColumn("bpLocalDateTime", specification).getType()).isEqualTo(DataType.timestamp());
assertThat(getColumn("bpInstant", specification).getType()).isEqualTo(DataType.timestamp());
assertThat(getColumnType("timestamp", specification)).isEqualTo(DataType.timestamp());
assertThat(getColumnType("localDateTime", specification)).isEqualTo(DataType.timestamp());
assertThat(getColumnType("instant", specification)).isEqualTo(DataType.timestamp());
assertThat(getColumnType("jodaLocalDateTime", specification)).isEqualTo(DataType.timestamp());
assertThat(getColumnType("jodaDateTime", specification)).isEqualTo(DataType.timestamp());
assertThat(getColumnType("bpLocalDateTime", specification)).isEqualTo(DataType.timestamp());
assertThat(getColumnType("bpInstant", specification)).isEqualTo(DataType.timestamp());
}
/**
@@ -274,8 +278,8 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
CreateTableSpecification specification = getCreateTableSpecificationFor(TypeWithOverrides.class);
assertThat(getColumn("localDate", specification).getType()).isEqualTo(DataType.timestamp());
assertThat(getColumn("jodaLocalDate", specification).getType()).isEqualTo(DataType.timestamp());
assertThat(getColumnType("localDate", specification)).isEqualTo(DataType.timestamp());
assertThat(getColumnType("jodaLocalDate", specification)).isEqualTo(DataType.timestamp());
}
/**
@@ -286,10 +290,76 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
CreateTableSpecification specification = getCreateTableSpecificationFor(AllPossibleTypes.class);
assertThat(getColumn("blob", specification).getType()).isEqualTo(DataType.blob());
assertThat(getColumnType("blob", specification)).isEqualTo(DataType.blob());
}
public CreateTableSpecification getCreateTableSpecificationFor(Class<?> persistentEntityClass) {
/**
* @see DATACASS-172
*/
@Test
public void columnsShouldMapToUdt() {
final UserType human_udt = mock(UserType.class, "human_udt");
final UserType species_udt = mock(UserType.class, "species_udt");
final UserType peeps_udt = mock(UserType.class, "peeps_udt");
ctx.setUserTypeResolver(new UserTypeResolver() {
@Override
public UserType resolveType(CqlIdentifier typeName) {
if (typeName.toCql().equals(human_udt.toString())) {
return human_udt;
}
if (typeName.toCql().equals(species_udt.toString())) {
return species_udt;
}
if (typeName.toCql().equals(peeps_udt.toString())) {
return peeps_udt;
}
return null;
}
});
CreateTableSpecification specification = getCreateTableSpecificationFor(WithUdtFields.class);
assertThat(getColumnType("human", specification)).isEqualTo(human_udt);
assertThat(getColumnType("friends", specification)).isEqualTo(DataType.list(species_udt));
assertThat(getColumnType("people", specification)).isEqualTo(DataType.set(peeps_udt));
}
/**
* @see DATACASS-172
*/
@Test
public void columnsShouldMapToMapped() {
final UserType mappedUdt = mock(UserType.class, "mappedudt");
ctx.setUserTypeResolver(new UserTypeResolver() {
@Override
public UserType resolveType(CqlIdentifier typeName) {
if (typeName.toCql().equals(mappedUdt.toString())) {
return mappedUdt;
}
return null;
}
});
CreateTableSpecification specification = getCreateTableSpecificationFor(WithMappedUdtFields.class);
assertThat(getColumnType("human", specification)).isEqualTo(mappedUdt);
assertThat(getColumnType("friends", specification)).isEqualTo(DataType.list(mappedUdt));
assertThat(getColumnType("people", specification)).isEqualTo(DataType.set(mappedUdt));
assertThat(getColumnType("stringToUdt", specification))
.isEqualTo(DataType.map(DataType.varchar(), mappedUdt));
assertThat(getColumnType("udtToString", specification))
.isEqualTo(DataType.map(mappedUdt, DataType.varchar()));
}
private CreateTableSpecification getCreateTableSpecificationFor(Class<?> persistentEntityClass) {
CustomConversions customConversions = new CustomConversions(Collections.EMPTY_LIST);
ctx.setCustomConversions(customConversions);
@@ -298,6 +368,10 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
return ctx.getCreateTableSpecificationFor(persistentEntity);
}
private DataType getColumnType(String columnName, CreateTableSpecification specification) {
return getColumn(columnName, specification).getType();
}
private ColumnSpecification getColumn(String columnName, CreateTableSpecification specification) {
for (ColumnSpecification columnSpecification : specification.getColumns()) {
@@ -310,12 +384,9 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
String.format("Cannot find column '%s' amongst '%s'", columnName, specification.getColumns()));
}
/**
* @author Mark Paluch
*/
@Data
@Table
public static class Employee {
private static class Employee {
@Id String id;
@@ -327,9 +398,33 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
@CassandraType(type = Name.SET, typeArguments = Name.BIGINT) List<Human> enemies;
}
/**
* @author Mark Paluch
*/
@Data
@Table
private static class WithUdtFields {
@Id String id;
@CassandraType(type = Name.UDT, userTypeName = "human_udt") UDTValue human;
@CassandraType(type = Name.LIST, typeArguments = Name.UDT, userTypeName = "species_udt") List<UDTValue> friends;
@CassandraType(type = Name.SET, typeArguments = Name.UDT, userTypeName = "peeps_udt") Set<UDTValue> people;
}
@Data
@Table
private static class WithMappedUdtFields {
@Id String id;
MappedUdt human;
List<MappedUdt> friends;
Set<MappedUdt> people;
Map<String, MappedUdt> stringToUdt;
Map<MappedUdt, String> udtToString;
}
@UserDefinedType
private static class MappedUdt {}
@Data
@AllArgsConstructor
@NoArgsConstructor
@@ -339,21 +434,17 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
String lastname;
}
/**
* @author Mark Paluch
*/
@Data
@Table
static class TypeWithOverrides {
private static class TypeWithOverrides {
@Id String id;
@CassandraType(type = Name.TIMESTAMP) java.time.LocalDate localDate;
@CassandraType(type = Name.TIMESTAMP) org.joda.time.LocalDate jodaLocalDate;
}
static class PersonReadConverter implements Converter<String, Human> {
private static class PersonReadConverter implements Converter<String, Human> {
public Human convert(String source) {
@@ -369,7 +460,7 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests {
}
}
static class PersonWriteConverter implements Converter<Human, String> {
private static class PersonWriteConverter implements Converter<Human, String> {
public String convert(Human source) {

View File

@@ -142,20 +142,6 @@ public class PrimaryKeyClassEntityMetadataVerifierUnitTests {
}
}
/**
* @see DATACASS-258
*/
@Test
public void shouldFailWithComplexType() {
try {
verifier.verify(getEntity(PKWithComplexType.class));
fail("Missing MappingException");
} catch (MappingException e) {
assertThat(e).hasMessageContaining("Property [species] annotated with @PrimaryKeyColumn must be a simple CassandraType");
}
}
/**
* @see DATACASS-258
*/
@@ -224,12 +210,6 @@ public class PrimaryKeyClassEntityMetadataVerifierUnitTests {
@PrimaryKeyColumn(ordinal = 2, type = PrimaryKeyType.CLUSTERED, ordering = Ordering.DESCENDING) String color;
}
@PrimaryKeyClass
static class PKWithComplexType {
@PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) Object species;
}
@Table
@PrimaryKeyClass
static class TooManyAnnotations {

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.test.integration.repository.querymethods.conversion;
package org.springframework.data.cassandra.repository.conversion;
import lombok.AllArgsConstructor;
import lombok.Data;

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2016 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.conversion;
import java.util.List;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.mapping.CassandraType;
import org.springframework.data.cassandra.mapping.Table;
import lombok.Data;
import lombok.NoArgsConstructor;
import com.datastax.driver.core.DataType.Name;
/**
* @author Mark Paluch
*/
@Table
@Data
@NoArgsConstructor
class Contact {
@Id String id;
Address address;
List<Address> addresses;
@CassandraType(type = Name.UDT, userTypeName = "phone")
Phone mainPhone;
@CassandraType(type = Name.LIST,typeArguments = Name.UDT, userTypeName = "phone")
List<Phone> alternativePhones;
public Contact(String id) {
this.id = id;
}
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright 2016 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.conversion;
import static org.assertj.core.api.Assertions.*;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.base.PersonRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.UDTValue;
/**
* Integration tests for query argument conversion through {@link PersonRepository}.
*
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ParameterConversionTestSupport.Config.class)
public class DerivedQueryMethodsParameterConversionIntegrationTests extends ParameterConversionTestSupport {
@Autowired ContactRepository contactRepository;
/**
* @see DATACASS-7
*/
@Test
public void shouldFindByConvertedParameter() {
List<Contact> contacts = contactRepository.findByAddress(walter.getAddress());
assertThat(contacts).contains(walter, flynn);
}
/**
* @see DATACASS-7
*/
@Test
public void shouldFindByStringParameter() {
String parameter = AddressWriteConverter.INSTANCE.convert(walter.getAddress());
List<Contact> contacts = contactRepository.findByAddress(parameter);
assertThat(contacts).contains(walter, flynn);
}
/**
* @see DATACASS-7
*/
@Test
public void findByAddressesIn() {
assertThat(contactRepository.findByAddressesContains(flynn.address)).contains(flynn, walter);
assertThat(contactRepository.findByAddressesContains(walter.addresses.get(1))).contains(walter);
}
/**
* @see DATACASS-172
*/
@Test
public void findByMainPhone() {
assertThat(contactRepository.findByMainPhone(walter.getMainPhone())).contains(walter);
}
/**
* @see DATACASS-172
*/
@Test
public void findByMainPhoneUdtValue() {
KeyspaceMetadata keyspace = adminOperations.getKeyspaceMetadata();
UDTValue udtValue = keyspace.getUserType("phone").newValue();
udtValue.setString("number", walter.getMainPhone().getNumber());
assertThat(contactRepository.findByMainPhone(udtValue)).contains(walter);
}
/**
* @see DATACASS-172
*/
@Test
public void findByAlternativePhones() {
Phone phone = walter.getAlternativePhones().get(0);
assertThat(contactRepository.findByAlternativePhonesContains(phone)).contains(walter);
}
/**
* @see DATACASS-172
*/
@Test
public void findByAlternativePhonesUdtValue() {
Phone phone = walter.getAlternativePhones().get(0);
KeyspaceMetadata keyspace = adminOperations.getKeyspaceMetadata();
UDTValue udtValue = keyspace.getUserType("phone").newValue();
udtValue.setString("number", phone.getNumber());
assertThat(contactRepository.findByAlternativePhonesContains(udtValue)).contains(walter);
}
interface ContactRepository extends CassandraRepository<Contact> {
List<Contact> findByAddress(Address address);
List<Contact> findByAddress(String address);
List<Contact> findByAddressesContains(Address address);
List<Contact> findByMainPhone(Phone phone);
List<Contact> findByMainPhone(UDTValue udtValue);
List<Contact> findByAlternativePhonesContains(Phone phone);
List<Contact> findByAlternativePhonesContains(UDTValue udtValue);
}
}

View File

@@ -13,42 +13,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.test.integration.repository.querymethods.conversion;
import static org.assertj.core.api.Assertions.*;
package org.springframework.data.cassandra.repository.conversion;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.cassandra.config.CassandraSessionFactoryBean;
import org.springframework.data.cassandra.config.SchemaAction;
import org.springframework.data.cassandra.convert.CustomConversions;
import org.springframework.data.cassandra.core.CassandraAdminOperations;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.data.cassandra.mapping.SimpleUserTypeResolver;
import org.springframework.data.cassandra.mapping.UserTypeResolver;
import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories;
import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.base.PersonRepository;
import org.springframework.data.cassandra.test.integration.support.AbstractSpringDataEmbeddedCassandraIntegrationTest;
import org.springframework.data.cassandra.test.integration.support.IntegrationTestConfig;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.StringUtils;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.UDTValue;
import com.datastax.driver.core.UserType;
/**
* Integration tests for query derivation through {@link PersonRepository}.
*
* Test support for query method parameter type conversion.
*
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class ParameterConversionIntegrationTests extends AbstractSpringDataEmbeddedCassandraIntegrationTest {
abstract class ParameterConversionTestSupport extends AbstractSpringDataEmbeddedCassandraIntegrationTest {
@Configuration
@EnableCassandraRepositories(considerNestedRepositories = true)
@@ -64,14 +63,27 @@ public class ParameterConversionIntegrationTests extends AbstractSpringDataEmbed
return SchemaAction.RECREATE_DROP_UNUSED;
}
@Override
public CassandraSessionFactoryBean session() throws ClassNotFoundException {
Cluster cluster = cluster().getObject();
Session session = cluster.connect(getKeyspaceName());
session.execute("CREATE TYPE IF NOT EXISTS phone (number text);");
session.close();
return super.session();
}
@Override
public CustomConversions customConversions() {
return new CustomConversions(Arrays.asList(AddressReadConverter.INSTANCE, AddressWriteConverter.INSTANCE));
return new CustomConversions(
Arrays.asList(AddressReadConverter.INSTANCE, AddressWriteConverter.INSTANCE, PhoneReadConverter.INSTANCE,
new PhoneWriteConverter(new SimpleUserTypeResolver(cluster().getObject(), getKeyspaceName()))));
}
}
@Autowired CassandraOperations template;
@Autowired ContactRepository contactRepository;
@Autowired CassandraAdminOperations adminOperations;
Contact walter, flynn;
@@ -83,65 +95,88 @@ public class ParameterConversionIntegrationTests extends AbstractSpringDataEmbed
template.execute("CREATE INDEX IF NOT EXISTS contact_address ON contact (address);");
template.execute("CREATE INDEX IF NOT EXISTS contact_addresses ON contact (addresses);");
template.execute("CREATE INDEX IF NOT EXISTS contact_main_phones ON contact (mainphone);");
template.execute("CREATE INDEX IF NOT EXISTS contact_alternative_phones ON contact (alternativephones);");
walter = new Contact("Walter");
walter.setAddress(new Address("Albuquerque", "USA"));
walter.setAddresses(Arrays.asList(new Address("Albuquerque", "USA"), new Address("New Hampshire", "USA"),
new Address("Grocery Store", "Mexico")));
Phone phone = new Phone();
phone.setNumber("(505) 555-1258");
Phone alternative = new Phone();
alternative.setNumber("505-842-4205");
walter.setMainPhone(phone);
walter.setAlternativePhones(Collections.singletonList(alternative));
flynn = new Contact("Flynn");
flynn.setAddress(new Address("Albuquerque", "USA"));
flynn.setAddresses(Collections.singletonList(new Address("Albuquerque", "USA")));
walter = contactRepository.save(walter);
flynn = contactRepository.save(flynn);
}
/**
* @see DATACASS-7
*/
@Test
public void shouldFindByConvertedParameter() {
List<Contact> contacts = contactRepository.findByAddress(walter.getAddress());
assertThat(contacts).contains(walter, flynn);
}
/**
* @see DATACASS-7
*/
@Test
public void shouldFindByStringParameter() {
String parameter = AddressWriteConverter.INSTANCE.convert(walter.getAddress());
List<Contact> contacts = contactRepository.findByAddress(parameter);
assertThat(contacts).contains(walter, flynn);
}
/**
* @see DATACASS-7
*/
@Test
public void findByAddressesIn() {
assertThat(contactRepository.findByAddressesContains(flynn.address)).contains(flynn, walter);
assertThat(contactRepository.findByAddressesContains(walter.addresses.get(1))).contains(walter);
}
interface ContactRepository extends CassandraRepository<Contact> {
List<Contact> findByAddress(Address address);
List<Contact> findByAddress(String address);
List<Contact> findByAddressesContains(Address address);
template.insert(walter);
template.insert(flynn);
}
/**
* @author Mark Paluch
*/
static enum AddressReadConverter implements Converter<String, Address> {
enum AddressWriteConverter implements Converter<Address, String> {
INSTANCE;
public String convert(Address source) {
try {
return new ObjectMapper().writeValueAsString(source);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
/**
* @author Mark Paluch
*/
private enum PhoneReadConverter implements Converter<UDTValue, Phone> {
INSTANCE;
public Phone convert(UDTValue source) {
Phone phone = new Phone();
phone.setNumber(source.getString("number"));
return phone;
}
}
/**
* @author Mark Paluch
*/
private static class PhoneWriteConverter implements Converter<Phone, UDTValue> {
private UserTypeResolver userTypeResolver;
PhoneWriteConverter(UserTypeResolver userTypeResolver) {
this.userTypeResolver = userTypeResolver;
}
public UDTValue convert(Phone source) {
UserType userType = userTypeResolver.resolveType(CqlIdentifier.cqlId("phone"));
UDTValue udtValue = userType.newValue();
udtValue.setString("number", source.getNumber());
return udtValue;
}
}
/**
* @author Mark Paluch
*/
private enum AddressReadConverter implements Converter<String, Address> {
INSTANCE;
@@ -158,20 +193,4 @@ public class ParameterConversionIntegrationTests extends AbstractSpringDataEmbed
return null;
}
}
/**
* @author Mark Paluch
*/
static enum AddressWriteConverter implements Converter<Address, String> {
INSTANCE;
public String convert(Address source) {
try {
return new ObjectMapper().writeValueAsString(source);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2016 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.conversion;
import lombok.Data;
/**
* @author Mark Paluch
*/
@Data
class Phone {
String number;
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright 2016 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.conversion;
import static org.assertj.core.api.Assertions.*;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.base.PersonRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.UDTValue;
/**
* Integration tests for query argument conversion through {@link PersonRepository}.
*
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ParameterConversionTestSupport.Config.class)
public class StringQueryMethodsParameterConversionIntegrationTests extends ParameterConversionTestSupport {
@Autowired ContactStringQueryRepository contactRepository;
/**
* @see DATACASS-172
*/
@Test
public void shouldFindByConvertedParameter() {
List<Contact> contacts = contactRepository.findByAddress(walter.getAddress());
assertThat(contacts).contains(walter, flynn);
}
/**
* @see DATACASS-172
*/
@Test
public void shouldFindByStringParameter() {
String parameter = AddressWriteConverter.INSTANCE.convert(walter.getAddress());
List<Contact> contacts = contactRepository.findByAddress(parameter);
assertThat(contacts).contains(walter, flynn);
}
/**
* @see DATACASS-172
*/
@Test
public void findByAddressesIn() {
assertThat(contactRepository.findByAddressesContains(flynn.address)).contains(flynn, walter);
assertThat(contactRepository.findByAddressesContains(walter.addresses.get(1))).contains(walter);
}
/**
* @see DATACASS-172
*/
@Test
public void findByMainPhone() {
assertThat(contactRepository.findByMainPhone(walter.getMainPhone())).contains(walter);
}
/**
* @see DATACASS-172
*/
@Test
public void findByMainPhoneUdtValue() {
KeyspaceMetadata keyspace = adminOperations.getKeyspaceMetadata();
UDTValue udtValue = keyspace.getUserType("phone").newValue();
udtValue.setString("number", walter.getMainPhone().getNumber());
assertThat(contactRepository.findByMainPhone(udtValue)).contains(walter);
}
/**
* @see DATACASS-172
*/
@Test
public void findByAlternativePhones() {
Phone phone = walter.getAlternativePhones().get(0);
assertThat(contactRepository.findByAlternativePhonesContains(phone)).contains(walter);
}
/**
* @see DATACASS-172
*/
@Test
public void findByAlternativePhonesUdtValue() {
Phone phone = walter.getAlternativePhones().get(0);
KeyspaceMetadata keyspace = adminOperations.getKeyspaceMetadata();
UDTValue udtValue = keyspace.getUserType("phone").newValue();
udtValue.setString("number", phone.getNumber());
assertThat(contactRepository.findByAlternativePhonesContains(udtValue)).contains(walter);
}
interface ContactStringQueryRepository extends CassandraRepository<Contact> {
@Query("SELECT * from contact where address = ?0;")
List<Contact> findByAddress(Address address);
@Query("SELECT * from contact where address = ?0;")
List<Contact> findByAddress(String address);
@Query("SELECT * from contact where addresses contains ?0;")
List<Contact> findByAddressesContains(Address address);
@Query("SELECT * from contact where mainphone = ?0;")
List<Contact> findByMainPhone(Phone phone);
@Query("SELECT * from contact where mainphone = ?0;")
List<Contact> findByMainPhone(UDTValue udtValue);
@Query("SELECT * from contact where alternativephones contains ?0;")
List<Contact> findByAlternativePhonesContains(Phone phone);
@Query("SELECT * from contact where alternativephones contains ?0;")
List<Contact> findByAlternativePhonesContains(UDTValue udtValue);
}
}

View File

@@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import java.util.Collections;
import org.junit.Before;
import org.junit.Rule;
@@ -27,17 +28,23 @@ import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.domain.Person;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.UserTypeResolver;
import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.Address;
import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.Person;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.util.ClassUtils;
import com.datastax.driver.core.UDTValue;
import com.datastax.driver.core.UserType;
/**
* Unit tests for {@link PartTreeCassandraQuery}.
@@ -49,17 +56,25 @@ public class PartTreeCassandraQueryUnitTests {
@Rule public ExpectedException exception = ExpectedException.none();
@Mock private CassandraOperations mockCassandraOperations;
@Mock CassandraOperations mockCassandraOperations;
@Mock UserTypeResolver userTypeResolverMock;
@Mock UserType userTypeMock;
@Mock UDTValue udtValueMock;
private CassandraMappingContext mappingContext;
private CassandraConverter converter;
BasicCassandraMappingContext mappingContext;
CassandraConverter converter;
@Before
public void setUp() {
mappingContext = new BasicCassandraMappingContext();
converter = new MappingCassandraConverter(mappingContext);
this.mappingContext = new BasicCassandraMappingContext();
this.mappingContext.setUserTypeResolver(userTypeResolverMock);
this.converter = new MappingCassandraConverter(mappingContext);
when(mockCassandraOperations.getConverter()).thenReturn(converter);
when(udtValueMock.getType()).thenReturn(userTypeMock);
when(userTypeMock.iterator()).thenReturn(Collections.<UserType.Field> emptyIterator());
}
/**
@@ -102,11 +117,39 @@ public class PartTreeCassandraQueryUnitTests {
assertThat(query).isEqualTo("SELECT * FROM person;");
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-172">DATACASS-172</a>
*/
@Test
public void shouldDeriveSimpleQueryWithMappedUDT() {
when(userTypeResolverMock.resolveType(CqlIdentifier.cqlId("address"))).thenReturn(userTypeMock);
when(userTypeMock.newValue()).thenReturn(udtValueMock);
String query = deriveQueryFromMethod("findByMainAddress", new Address());
assertThat(query).isEqualTo("SELECT * FROM person WHERE mainaddress={};");
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-172">DATACASS-172</a>
*/
@Test
public void shouldDeriveSimpleQueryWithUDTValue() {
when(userTypeResolverMock.resolveType(CqlIdentifier.cqlId("address"))).thenReturn(userTypeMock);
when(userTypeMock.newValue()).thenReturn(udtValueMock);
String query = deriveQueryFromMethod("findByMainAddress", udtValueMock);
assertThat(query).isEqualTo("SELECT * FROM person WHERE mainaddress={};");
}
private String deriveQueryFromMethod(String method, Object... args) {
Class<?>[] types = new Class<?>[args.length];
for (int i = 0; i < args.length; i++) {
types[i] = args[i].getClass();
types[i] = ClassUtils.getUserClass(args[i].getClass());
}
PartTreeCassandraQuery partTreeQuery = createQueryForMethod(method, types);
@@ -146,6 +189,10 @@ public class PartTreeCassandraQueryUnitTests {
Person findPersonBy();
Person findByMainAddress(Address address);
Person findByMainAddress(UDTValue udtValue);
PersonProjection findPersonProjectedBy();
<T> T findDynamicallyProjectedBy(Class<T> type);

View File

@@ -18,6 +18,7 @@ package org.springframework.data.cassandra.repository.query;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.time.LocalDate;
@@ -30,10 +31,13 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
import org.springframework.data.cassandra.mapping.UserTypeResolver;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.Address;
import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.Person;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
@@ -49,7 +53,12 @@ import org.springframework.util.ReflectionUtils;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.CodecRegistry;
import com.datastax.driver.core.Configuration;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.ProtocolVersion;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.UDTValue;
import com.datastax.driver.core.UserType;
import com.datastax.driver.core.UserType.Field;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
@@ -61,7 +70,7 @@ import com.datastax.driver.core.querybuilder.Select;
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class StringBasedCassandraQueryIntegrationUnitTests {
public class StringBasedCassandraQueryUnitTests {
SpelExpressionParser PARSER = new SpelExpressionParser();
@@ -69,6 +78,8 @@ public class StringBasedCassandraQueryIntegrationUnitTests {
@Mock Session session;
@Mock Cluster cluster;
@Mock Configuration configuration;
@Mock UserTypeResolver userTypeResolver;
@Mock UDTValue udtValue;
RepositoryMetadata metadata;
MappingCassandraConverter converter;
@@ -77,14 +88,18 @@ public class StringBasedCassandraQueryIntegrationUnitTests {
@Before
public void setUp() {
BasicCassandraMappingContext mappingContext = new BasicCassandraMappingContext();
mappingContext.setUserTypeResolver(userTypeResolver);
when(operations.getConverter()).thenReturn(converter);
when(operations.getSession()).thenReturn(session);
when(operations.getConverter()).thenReturn(converter);
when(session.getCluster()).thenReturn(cluster);
when(cluster.getConfiguration()).thenReturn(configuration);
when(configuration.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT_INSTANCE);
this.metadata = AbstractRepositoryMetadata.getMetadata(SampleRepository.class);
this.converter = new MappingCassandraConverter(new BasicCassandraMappingContext());
this.converter = new MappingCassandraConverter(mappingContext);
this.factory = new SpelAwareProxyProjectionFactory();
this.converter.afterPropertiesSet();
@@ -322,6 +337,50 @@ public class StringBasedCassandraQueryIntegrationUnitTests {
assertThat(actual).isEqualTo("SELECT * FROM person WHERE createdDate='2010-07-04';");
}
/**
* @see DATACASS-172
*/
@Test
public void bindsMappedUdtPropertyCorrectly() throws Exception {
Field city = createField("city", DataType.varchar());
Field country = createField("country", DataType.varchar());
UserType addressType = createUserType("address", Arrays.asList(city, country));
when(userTypeResolver.resolveType(CqlIdentifier.cqlId("address"))).thenReturn(addressType);
when(udtValue.getType()).thenReturn(addressType);
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByMainAddress", Address.class);
CassandraParameterAccessor accessor = new ConvertingParameterAccessor(converter,
new CassandraParametersParameterAccessor(cassandraQuery.getQueryMethod(), new Address()));
String stringQuery = cassandraQuery.createQuery(accessor);
// udtValueMock because that's the mock's UDTValue.toString() representation
assertThat(stringQuery).isEqualTo("SELECT * FROM person WHERE address={city:NULL,country:NULL};");
}
/**
* @see DATACASS-172
*/
@Test
public void bindsUdtValuePropertyCorrectly() throws Exception {
Field city = createField("city", DataType.varchar());
Field country = createField("country", DataType.varchar());
UserType addressType = createUserType("address", Arrays.asList(city, country));
when(udtValue.getType()).thenReturn(addressType);
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByMainAddress", UDTValue.class);
CassandraParameterAccessor accessor = new ConvertingParameterAccessor(converter,
new CassandraParametersParameterAccessor(cassandraQuery.getQueryMethod(), udtValue));
String stringQuery = cassandraQuery.createQuery(accessor);
// udtValueMock because that's the mock's UDTValue.toString() representation
assertThat(stringQuery).isEqualTo("SELECT * FROM person WHERE address={city:NULL,country:NULL};");
}
private StringBasedCassandraQuery getQueryMethod(String name, Class<?>... args) {
Method method = ReflectionUtils.findMethod(SampleRepository.class, name, args);
CassandraQueryMethod queryMethod = new CassandraQueryMethod(method, metadata, factory,
@@ -330,6 +389,30 @@ public class StringBasedCassandraQueryIntegrationUnitTests {
new ExtensionAwareEvaluationContextProvider());
}
private Field createField(String fieldName, DataType dataType) {
try {
Constructor<Field> constructor = Field.class.getDeclaredConstructor(String.class, DataType.class);
constructor.setAccessible(true);
return constructor.newInstance(fieldName, dataType);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private UserType createUserType(String typeName, Collection<Field> fields) {
try {
Constructor<UserType> constructor = UserType.class.getDeclaredConstructor(String.class, String.class,
Collection.class, ProtocolVersion.class, CodecRegistry.class);
constructor.setAccessible(true);
return constructor.newInstance(typeName, typeName, fields, ProtocolVersion.NEWEST_SUPPORTED,
CodecRegistry.DEFAULT_INSTANCE);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private interface SampleRepository extends Repository<Person, String> {
@Query("SELECT * FROM person WHERE lastname = ?0;")
@@ -367,5 +450,11 @@ public class StringBasedCassandraQueryIntegrationUnitTests {
@Query("SELECT * FROM person WHERE createdDate=?0;")
Person findByCreatedDate(LocalDate createdDate);
@Query("SELECT * FROM person WHERE address=?0;")
Person findByMainAddress(Address address);
@Query("SELECT * FROM person WHERE address=?0;")
Person findByMainAddress(UDTValue udtValue);
}
}

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.data.cassandra.test.integration.repository.cdi;
import java.util.HashMap;
import java.util.Collections;
import java.util.Set;
import javax.enterprise.context.ApplicationScoped;
@@ -23,7 +23,6 @@ import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;
import javax.inject.Singleton;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.cassandra.core.keyspace.CreateKeyspaceSpecification;
import org.springframework.cassandra.core.keyspace.DropKeyspaceSpecification;
import org.springframework.cassandra.support.RandomKeySpaceName;
@@ -31,7 +30,10 @@ import org.springframework.cassandra.test.integration.support.CassandraConnectio
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.CassandraAdminTemplate;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.core.CassandraPersistentEntitySchemaCreator;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.mapping.SimpleUserTypeResolver;
import org.springframework.data.cassandra.test.integration.repository.simple.User;
import com.datastax.driver.core.Cluster;
@@ -59,7 +61,12 @@ class CassandraOperationsProducer {
@ApplicationScoped
public CassandraOperations createCassandraOperations(Cluster cluster) throws Exception {
MappingCassandraConverter cassandraConverter = new MappingCassandraConverter();
BasicCassandraMappingContext mappingContext = new BasicCassandraMappingContext();
mappingContext.setUserTypeResolver(new SimpleUserTypeResolver(cluster, KEYSPACE_NAME));
mappingContext.setInitialEntitySet(Collections.singleton(User.class));
mappingContext.afterPropertiesSet();
MappingCassandraConverter cassandraConverter = new MappingCassandraConverter(mappingContext);
CassandraAdminTemplate cassandraTemplate = new CassandraAdminTemplate(cluster.connect(), cassandraConverter);
@@ -68,10 +75,12 @@ class CassandraOperationsProducer {
cassandraTemplate.execute(createKeyspaceSpecification);
cassandraTemplate.execute("USE " + KEYSPACE_NAME);
cassandraTemplate.createTable(true, CqlIdentifier.cqlId("users"), User.class, new HashMap<String, Object>());
CassandraPersistentEntitySchemaCreator schemaCreator = new CassandraPersistentEntitySchemaCreator(mappingContext, cassandraTemplate);
schemaCreator.createUserTypes(false, false, true);
schemaCreator.createTables(false, false, true);
for (CassandraPersistentEntity<?> entity : cassandraTemplate.getConverter().getMappingContext()
.getPersistentEntities()) {
.getNonPrimaryKeyEntities()) {
cassandraTemplate.truncate(entity.getTableName());
}

View File

@@ -13,30 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.test.integration.repository.querymethods.conversion;
import java.util.List;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.mapping.Table;
package org.springframework.data.cassandra.test.integration.repository.querymethods.declared;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.cassandra.mapping.UserDefinedType;
/**
* @author Mark Paluch
*/
@Table
@Data
@UserDefinedType
@AllArgsConstructor
@NoArgsConstructor
class Contact {
@Data
public class Address {
@Id String id;
Address address;
List<Address> addresses;
public Contact(String id) {
this.id = id;
}
String city;
String country;
}

View File

@@ -18,6 +18,7 @@ package org.springframework.data.cassandra.test.integration.repository.querymeth
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;
import org.springframework.cassandra.core.PrimaryKeyType;
import org.springframework.data.cassandra.mapping.PrimaryKeyColumn;
@@ -28,6 +29,8 @@ import lombok.NoArgsConstructor;
/**
* Sample domain class.
*
* @author Mark Paluch
*/
@Table
@Data
@@ -46,6 +49,9 @@ public class Person {
private LocalDate createdDate;
private ZoneId zoneId;
private Address mainAddress;
private List<Address> alternativeAddresses;
public Person(String firstname, String lastname) {
this.firstname = firstname;

View File

@@ -21,6 +21,7 @@ import java.util.List;
import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.Address;
import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.Person;
import org.springframework.data.domain.Sort;
@@ -37,6 +38,11 @@ interface PersonRepository extends CassandraRepository<Person> {
Person findByFirstnameAndLastname(String firstname, String lastname);
Person findByMainAddress(Address address);
@Query("select * from person where mainaddress = ?0")
Person findByAddress(Address address);
Person findByCreatedDate(LocalDate createdDate);
Person findByNicknameStartsWith(String prefix);

View File

@@ -20,6 +20,7 @@ import static org.hamcrest.Matchers.*;
import static org.junit.Assume.*;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@@ -32,6 +33,7 @@ import org.springframework.core.SpringVersion;
import org.springframework.data.cassandra.config.SchemaAction;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories;
import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.Address;
import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.Person;
import org.springframework.data.cassandra.test.integration.repository.querymethods.derived.PersonRepository.NumberOfChildren;
import org.springframework.data.cassandra.test.integration.repository.querymethods.derived.PersonRepository.PersonProjection;
@@ -84,6 +86,10 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC
Person person = new Person("Walter", "White");
person.setNumberOfChildren(2);
person.setMainAddress(new Address("Albuquerque", "USA"));
person.setAlternativeAddresses(Arrays.asList(new Address("Albuquerque", "USA"), new Address("New Hampshire", "USA"),
new Address("Grocery Store", "Mexico")));
walter = personRepository.save(person);
skyler = personRepository.save(new Person("Skyler", "White"));
flynn = personRepository.save(new Person("Flynn (Walter Jr.)", "White"));
@@ -133,6 +139,38 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC
assertThat(result).isEqualTo(walter);
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-172">DATACASS-172</a>
*/
@Test
public void shouldFindByMappedUdt() throws InterruptedException {
template.execute("CREATE INDEX IF NOT EXISTS person_main_address ON person (mainaddress);");
// Give Cassandra some time to build the index
Thread.sleep(500);
Person result = personRepository.findByMainAddress(walter.getMainAddress());
assertThat(result).isEqualTo(walter);
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-172">DATACASS-172</a>
*/
@Test
public void shouldFindByMappedUdtStringQuery() throws InterruptedException {
template.execute("CREATE INDEX IF NOT EXISTS person_main_address ON person (mainaddress);");
// Give Cassandra some time to build the index
Thread.sleep(500);
Person result = personRepository.findByAddress(walter.getMainAddress());
assertThat(result).isEqualTo(walter);
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-7">DATACASS-7</a>
*/

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2016 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.repository.simple;
import lombok.Data;
import org.springframework.data.cassandra.mapping.UserDefinedType;
/**
* @author Mark Paluch
*/
@UserDefinedType("address")
@Data
public class AddressType {
String street;
String city;
}

View File

@@ -27,6 +27,7 @@ import org.springframework.data.cassandra.mapping.Table;
* information, emails, following users, friends.
*
* @author Alex Shvid
* @author Mark Paluch
*/
@Table("users")
public class User {
@@ -73,6 +74,8 @@ public class User {
*/
private Set<String> friends;
private AddressType address;
public String getUsername() {
return username;
}
@@ -151,6 +154,14 @@ public class User {
this.birthYear = birthYear;
}
public AddressType getAddress() {
return address;
}
public void setAddress(AddressType address) {
this.address = address;
}
@Override
public int hashCode() {
final int prime = 31;

View File

@@ -17,6 +17,8 @@ package org.springframework.data.cassandra.test.integration.repository.simple;
import org.springframework.data.cassandra.repository.TypedIdCassandraRepository;
import com.datastax.driver.core.UDTValue;
/**
* Sample repository managing {@link User} entities.
*
@@ -26,4 +28,6 @@ import org.springframework.data.cassandra.repository.TypedIdCassandraRepository;
public interface UserRepository extends TypedIdCassandraRepository<User, String> {
String findByNamedQuery(String username);
User findByAddress(AddressType addressType);
}

View File

@@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Assert;
import org.springframework.data.cassandra.core.CassandraOperations;
import com.google.common.collect.Lists;
@@ -50,6 +51,8 @@ public class UserRepositoryIntegrationTests {
public void setUp() {
template.execute("CREATE INDEX IF NOT EXISTS users_address ON users (address);");
repository.deleteAll();
tom = new User();
@@ -59,6 +62,12 @@ public class UserRepositoryIntegrationTests {
tom.setPassword("123");
tom.setPlace("SF");
AddressType address = new AddressType();
address.setCity("San Francisco");
address.setStreet("Golden Gate Way 1");
tom.setAddress(address);
bob = new User();
bob.setUsername("bob");
bob.setFirstName("Bob");
@@ -94,24 +103,33 @@ public class UserRepositoryIntegrationTests {
assertThat(name).isEqualTo("Bob");
}
public void findByDerivedQuery() {
User user = repository.findByAddress(tom.getAddress());
assertThat(user).isNotNull().isEqualTo(tom);
}
public void findsUserById() throws Exception {
User user = repository.findOne(bob.getUsername());
assertThat(user).isNotNull();
assertEquals(bob, user);
User user = repository.findOne(tom.getUsername());
assertThat(user).isNotNull().isEqualTo(tom);
}
public void findsAll() throws Exception {
List<User> result = Lists.newArrayList(repository.findAll());
assertThat(result).hasSize(all.size());
assertThat(result.containsAll(all)).isTrue();
}
public void findsAllWithGivenIds() {
Iterable<User> result = repository.findAll(Arrays.asList(bob.getUsername(), tom.getUsername()));
assertThat(result).contains(bob, tom);
assertThat(result).doesNotContain(alice, scott);
}
@@ -144,8 +162,7 @@ public class UserRepositoryIntegrationTests {
repository.delete(id);
assertThat(!repository.exists(id)).isTrue();
assertThat(repository.exists(id)).isFalse();
}
/**
@@ -163,12 +180,4 @@ public class UserRepositoryIntegrationTests {
assertThat(loadedTom.getPassword()).isNull();
assertThat(loadedTom.getFriends()).isNull();
}
private static void assertEquals(User user1, User user2) {
assertThat(user2.getUsername()).isEqualTo(user1.getUsername());
assertThat(user2.getFirstName()).isEqualTo(user1.getFirstName());
assertThat(user2.getLastName()).isEqualTo(user1.getLastName());
assertThat(user2.getPlace()).isEqualTo(user1.getPlace());
assertThat(user2.getPassword()).isEqualTo(user1.getPassword());
}
}

View File

@@ -48,6 +48,11 @@ public abstract class UserRepositoryIntegrationTestsDelegator
tests.findByNamedQuery();
}
@Test
public void findByDerivedQuery() {
tests.findByDerivedQuery();
}
@Test
public void findsUserById() throws Exception {
tests.findsUserById();

View File

@@ -3,7 +3,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cass="http://www.springframework.org/schema/data/cassandra"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra-1.0.xsd
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
@@ -27,8 +27,10 @@
durable-writes="true" />
</cass:cluster>
<!-- TODO: not require that these beans be defined -->
<cass:mapping />
<cass:mapping>
<cass:user-type-resolver keyspace-name="${cassandra.keyspace}" />
</cass:mapping>
<cass:converter />
<cass:session keyspace-name="${cassandra.keyspace}"

View File

@@ -2,7 +2,7 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cass="http://www.springframework.org/schema/data/cassandra"
xsi:schemaLocation="
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra-1.0.xsd
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<import resource="classpath:/config/spring-data-cassandra-basic.xml" />

View File

@@ -2,7 +2,7 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cass="http://www.springframework.org/schema/data/cassandra"
xsi:schemaLocation="
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra-1.0.xsd
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
">

View File

@@ -2,7 +2,7 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cass="http://www.springframework.org/schema/data/cassandra"
xsi:schemaLocation="
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra-1.0.xsd
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
">

View File

@@ -4,7 +4,7 @@
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra-1.0.xsd
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

View File

@@ -2,14 +2,20 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cass="http://www.springframework.org/schema/data/cassandra"
xsi:schemaLocation="
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra-1.0.xsd
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
">
<import resource="classpath:/config/spring-data-cassandra-basic.xml" />
<bean id="userTypeResolver" class="org.springframework.data.cassandra.mapping.SimpleUserTypeResolver">
<constructor-arg ref="cassandraCluster" />
<constructor-arg value="#{randomKeyspaceName}" />
</bean>
<cass:mapping
entity-base-packages="org.springframework.data.cassandra.test.integration.repository.simple">
entity-base-packages="org.springframework.data.cassandra.test.integration.repository.simple"
user-type-resolver-ref="userTypeResolver">
<cass:entity
class="org.springframework.data.cassandra.test.integration.repository.simple.User">
<cass:table name="users" />

View File

@@ -2,7 +2,7 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cass="http://www.springframework.org/schema/data/cassandra"
xsi:schemaLocation="
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra-1.0.xsd
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
">