How to provide CassandraEntityInformation<T, ID> for a SimpleReactiveCassandraRepository? - cassandra

I'm doing some research bout spring-data-cassandra-reactive and it looks like I can't figure out or find the right documentation about this.
According to the documentation you can create an interface which is annotated with #Repository and then use annotations to create custom queries that will retrieve the data you want. The code would look something like this:
#Repository
public interface UserRepository
extends ReactiveCassandraRepository<UserEntity, UUID>
{
#Query("SELECT u FROM UserEntity u WHERE username = :user ALLOW FILTERING")
Mono<UserEntity> findUserEntityByUsername(#Param("user") String user);
/**
* This is just to illustrate/simulate some custom/advanced logic that cannot be
* done via #Query()
*/
default Mono<UserEntity> saveWithBase64EncodedPassword(UserEntity entity)
{
String encodedPassword = Base64.getEncoder().encodeToString(entity.getPassword().getBytes());
entity.updatePassword(encodedPassword);
return this.save(entity);
}
}
#Table(UserEntity.TABLE_NAME)
public class UserEntity
{
public final static String TABLE_NAME = "users";
#PrimaryKeyColumn(name = "uuid", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
#CassandraType(type = CassandraType.Name.UUID)
#Column
private UUID id;
#Column
#CassandraType(type = CassandraType.Name.VARCHAR)
private String username;
#Column
#CassandraType(type = CassandraType.Name.VARCHAR)
private String password;
public UserEntity()
{
this.id = UUID.randomUUID();
}
public UserEntity(String username, String password)
{
this.id = UUID.randomUUID();
this.username = username;
this.password = password;
}
public UUID getId()
{
return id;
}
public void setId(UUID id)
{
this.id = id;
}
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
public void updatePassword(String newPassword)
{
this.password = newPassword;
}
}
Dependencies:
plugins {
id("org.springframework.boot") version "2.6.6"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
}
dependencies {
// Embedded Cassandra Server - used for testing.
implementation("com.github.nosan:embedded-cassandra-spring-boot-starter:4.1.0")
// Spring Data Cassandra Dependencies
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-data-cassandra-reactive")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude("org.junit.vintage:junit-vintage-engine")
exclude("com.vaadin.external.google:android-json")
}
testImplementation("io.projectreactor:reactor-test")
// /Spring Data Cassandra Dependencies
}
This, so far, works fine. However, I'm trying to figure out how to switch to implementing the interface in order to be able to use #Autowire (i.e. in this example code to autowire
org.springframework.security.crypto.password.PasswordEncoder)
Surfing through the code in the spring-data-cassandra jar I noticed there's a class named org.springframework.data.cassandra.repository.support.SimpleReactiveCassandraRepository which you can extend from. It already has most of the common things you would need implemented for you which is why it looks like the perfect candidate to extend from.
And here comes the problem - it requires CassandraEntityInformation<T, ID> metadata.
I cannot seem to find where this is taken from or how it's supposed to be auto-wired and I feel like I might be missing something or maybe a dependency.
Any ideas?

SimpleReactiveCassandraRepository is a great class as it gives you access to ReactiveCassandraOperations and as such to CqlSession. It is a great way to have fine grained operations. (LWT, Bacthes)
You are correct the different classes would be autowired. Here is a sample code:
#Repository
public class OwnerReactiveCassandraRepository extends SimpleReactiveCassandraRepository<OwnerEntitySpring, UUID> {
protected final CqlSession cqlSession;
protected final ReactiveCassandraOperations reactiveCassandraTemplate;
#SuppressWarnings("unchecked")
public OwnerReactiveCassandraRepository(CqlSession cqlSession, ReactiveCassandraOperations ops) {
super(new MappingCassandraEntityInformation<OwnerEntitySpring, UUID>(
(CassandraPersistentEntity<OwnerEntitySpring>) ops.getConverter().getMappingContext()
.getRequiredPersistentEntity(OwnerEntitySpring.class), ops.getConverter()), ops);
this.cqlSession = cqlSession;
this.reactiveCassandraTemplate = ops;
}
public Flux<OwnerEntitySpring> searchByOwnerName(String ownerLastName) {
return reactiveCassandraTemplate.getReactiveCqlOperations()
.query(SimpleStatement
.builder("SELECT * FROM " + TABLE_NAME + " WHERE " + COLUMN_LASTNAME + "=?")
.addPositionalValues(ownerLastName)
.build(), (row, rownum) -> new OwnerEntitySpring(row));
}
TLDR; I implemented the Spring PetClinic with Spring data Reactive and you can have access to the full code here It is a step by step workshop with the code. You may want to look specially at this folder
In the project you will find reactive with the drivers only, reactive with CassandraRepositories and reactive with SimpleCassandraRepositories. (Check the TEST folder)

Related

Override Spock mock with method instead of value

I know that using Spock I can create a Mock and set a return value on a method e.g.
idpClient.adminCreateUser(_) >> [
user: new UserType()
]
How can I set a method on the Mock so I can customise the value that gets returned? I want to do something like:
idpClient.metaClass.adminCreateUser = { AdminCreateUserRequest adminCreateUserRequest ->
UserType user = new UserType()
user.setUsername(adminCreateUserRequest.getUsername())
AdminCreateUserResult result = new AdminCreateUserResult()
result.setUser(user)
return result
}
Though obviously the above does not work (it returns null). I've looked at the documentation but all I've found is how to return a fixed value (see above).
Maybe you ought to read the Spock manual. There is a section on computing return values. There is no need to use meta classes, the Spock DSL provides syntactical means for stubbing.
I made up this little example for you as an MCVE:
package de.scrum_master.stackoverflow.q64961803;
class UserType {
private String userName;
public void setUserName(String userName) { this.userName = userName; }
public String getUserName() { return userName; }
}
package de.scrum_master.stackoverflow.q64961803;
public class AdminCreateUserResult {
private UserType user;
public void setUser(UserType user) { this.user = user; }
public UserType getUser() { return user; }
}
package de.scrum_master.stackoverflow.q64961803;
public class AdminCreateUserRequest {
private String userName;
public AdminCreateUserRequest(String userName) { this.userName = userName; }
public String getUserName() { return userName; }
}
package de.scrum_master.stackoverflow.q64961803;
public class IDPClient {
public AdminCreateUserResult adminCreateUser(AdminCreateUserRequest adminCreateUserRequest) {
AdminCreateUserResult userResult = new AdminCreateUserResult();
UserType userType = new UserType();
userType.setUserName("real user");
userResult.setUser(userType);
return userResult;
}
}
Shen the corresponding Spock specification would look like this:
package de.scrum_master.stackoverflow.q64961803
import spock.lang.Specification
class IDPClientTest extends Specification {
def test() {
given: "mock IDPClient with stubbed method"
IDPClient idpClient = Mock() {
adminCreateUser(_) >> { AdminCreateUserRequest adminCreateUserRequest ->
UserType user = new UserType()
user.setUserName(adminCreateUserRequest.getUserName())
AdminCreateUserResult result = new AdminCreateUserResult()
result.setUser(user)
result
}
}
def request = new AdminCreateUserRequest("test user")
expect: "real IDPClient behaves normally"
new IDPClient().adminCreateUser(request).user.userName == "real user"
and: "mock IDPClient displays stub behaviour"
idpClient.adminCreateUser(request).user.userName == "test user"
}
}

Spring data Cassandra, Allow filtering

I have the following table
CREATE TABLE magazines.magazine_name (
frequency smallint,
magazine_id varchar,
magazine_name varchar,
PRIMARY KEY (magazine_id,magazine_name)
);
Should I use allow filter annotation to have the following repository method get executed
#Query("SELECT * from magazine_name where magazine_id = ?0")
MagazineName findMagazineCQlQuery(String id);
because I get the folowing execption :
org.springframework.data.cassandra.CassandraInvalidQueryException:Query;
CQL[com.datastax.oss.driver.internal.core.cql.DefaultSimpleStatement#c78c2039];
Cannot execute this query as it might involve data filtering and thus may have unpredictable performance.
If you want to execute this query despite the performance unpredictability, use ALLOW FILTERING;
nested exception is
com.datastax.oss.driver.api.core.servererrors.InvalidQueryException:
Cannot execute this query as it might involve data filtering
and thus may have unpredictable performance.
If you want to execute this query despite the performance unpredictability, use ALLOW FILTERING
By the way, I know that I can use query methods or even findById method, but actually I am just experimenting with cql quires and try to learn about it.
--update
The domain object
#Table(value = "magazine_name")
#Data
#Builder
public class MagazineName {
#PrimaryKeyColumn(name = "magazine_id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private String magazineId;
#PrimaryKeyColumn(name = "magazine_name", ordinal = 1, type = PrimaryKeyType.CLUSTERED)
private String name;
}
I defined the table exactly like yours and here is my repository. I can query without error.
1. My Repository
public interface IMagazineDao extends CrudRepository<Magazine, String> {
#Query("SELECT * from magazine_name where magazine_id = ?0")
Magazine findMagazineCQlQuery(String id);
}
2. Application
#SpringBootApplication
public class Application implements CommandLineRunner {
#Autowired
private IMagazineDao magazineDao;
#Override
public void run(String... args) throws Exception {
this.magazineDao.save(new Magazine("magazine1", "name", (short) 1));
this.magazineDao.findMagazineCQlQuery("magazine1");
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3. Magazine class
#Table(value = "magazine_name")
public class Magazine {
#PrimaryKeyColumn(name = "magazine_id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private String magazineId;
#PrimaryKeyColumn(name = "magazine_name", ordinal = 1, type = PrimaryKeyType.CLUSTERED)
private String name;
#Column
private Short frequency;
public Magazine() {
}
public Magazine(String magazineId, String name, Short frequency) {
this.magazineId = magazineId;
this.name = name;
this.frequency = frequency;
}
public String getMagazineId() {
return magazineId;
}
public void setMagazineId(String magazineId) {
this.magazineId = magazineId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Short getFrequency() {
return frequency;
}
public void setFrequency(Short frequency) {
this.frequency = frequency;
}
}

Spring data support to create tables using java entities

I am using Spring data Cassandra, to connect with Cassandra database, with configuration file extending AbstractCassandraConfiguration and overriding functions -
#Override
public SchemaAction getSchemaAction() {
return SchemaAction.RECREATE_DROP_UNUSED;
}
#Override
public String[] getEntityBasePackages() {
return new String[] {"com.example"};
}
My aim is to create tables automatically in Cassandra from the mentioned entities in com.example package with #Table annotation.
For example -
package com.example;
import org.springframework.data.cassandra.mapping.PrimaryKey;
import org.springframework.data.cassandra.mapping.Table;
#Table(value="goal")
public class Goal {
#PrimaryKey
private int id;
private String description;
public Goal(int id, String description) {
this.id = id;
this.description = description;
}
public Goal() {
}
public int getId() {
return id;
}
public String getDescription() {
return description;
}
public void setId(int id) {
this.id = id;
}
public void setDescription(String description) {
this.description = description;
}
#Override
public String toString() {
return "Goals [id=" + id + ", description=" + description + "]";
}
}
For this entity, with the given configuration, one table should get created during spring initialization, But it fails to do so.
No exception though, It just doesn't create anything in Cassandra.
Any help would be appreciated. Thanks.
I also faced similar requirement and following works for me:-
#Bean
public CassandraClusterFactoryBean cluster() {
CassandraClusterFactoryBean cluster = new CassandraClusterFactoryBean();
cluster.setContactPoints("127.0.0.1");
cluster.setPort(9042);
return cluster;
}
public String[] getEntityBasePackages() {
return new String[] { "com.user.entity" };
}
protected String getKeyspaceName() {
return "user_db";
}
public SchemaAction getSchemaAction() {
return SchemaAction.CREATE_IF_NOT_EXISTS;
}
Once your context is up, you can check the table in cassandra.
It is assumed your keyspace is already created in cassandra.
Please inform if you also want to create keyspace on the fly.

refresh google cloud endpoint in android studio

i'm using android studio 1.2.2 to create an android app that relies on a google cloud backend.
i managed to create the app and the backend, to generate backend endpoint to persist data bu now i have added a new constructor to the entity class (to pass parameters that will initialize the object) but i am not able to refresh the api version of this class in the library generated, so there is no new constructor and i can't use.
what are the steps to refresh the contents of this library?
thanks in advance
FROM
#Entity
public class Coordinates {
#Id
String email;
double latitude;
double longitude;
String timestamp;
public Coordinates(){}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
}
TO
#Entity
public class Coordinates {
#Id
String email;
double latitude;
double longitude;
String timestamp;
public Coordinates(){}
public Coordinates(String email,double latitude,double longitude,String timestamp) {
this.email=email;
this.latitude=latitude;
this.longitude=longitude;
this.timestamp=timestamp;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
}
When you generate your endpoints JAR to be used in Android it does not transfer all the methods or constructors. I don't know the reason for that, probably written somewhere but haven't seen it.
I believe you have two options:
Use your Coordinates class without special constructor, ie use getters and setters
Create Coordinates class in Android app and have a helper class which converts your Android App Coordinates class into Endpoint Coordinates class

Spring-data-cassandra's CassandraTemplate returns String, not a specified Object, when run queryForObject function.

I've been going through the Spring Data Cassandra documentation (http://docs.spring.io/spring-data/cassandra/docs/1.0.1.RELEASE/reference/html/cassandra.core.html)
Basically, with proper annotation, I hoped the CassandraTemplate maps a row to a POJO object, but it didn't work as I expected.
For the call,
cassandraOps.queryForObject(s, Person.class)
I received an error as following:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to Person
Anything that I'm missing? Following is the same copy and paste from the doc above.
Person Class looks like:
#Table
public class Person {
#PrimaryKey
private String id;
private String name;
private int age;
public Person(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
#Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
and the application class looks like...:
public class CassandraApp {
private static final Logger LOG = LoggerFactory.getLogger(CassandraApp.class);
private static Cluster cluster;
private static Session session;
public static void main(String[] args) {
try {
cluster = Cluster.builder().addContactPoints(InetAddress.getLocalHost()).build();
session = cluster.connect("mykeyspace");
CassandraOperations cassandraOps = new CassandraTemplate(session);
cassandraOps.insert(new Person("1234567890", "David", 40));
Select s = QueryBuilder.select().from("person");
s.where(QueryBuilder.eq("id", "1234567890"));
LOG.info(cassandraOps.queryForObject(s, Person.class).getId());
cassandraOps.truncate("person");
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
CassandraTemplate's queryForObject(String,Class) is not meant for arbitrary object mapping. It is modeled after JdbcTemplate's queryForObject(String,Class) method. It's intended to take types that the Cassandra driver can convert directly.
To convert arbitrary application-defined classes, use queryForObject(String,RowMapper<T>) or one of its overloads. CqlTemplate doesn't know how to map arbitrary classes; you have to supply the RowMapper<T> implementation for your class T.
you can do it like this way:-
String myQuery = "select * from person where id=1234567890";
Person personObj = cassandraOperations.selectOne(myQuery, Person.class);
<<
For all
List<Person> personListObj = cassandraOperations.select(myQuery, Person.class); >>
this work for me using cassandraTemplete object perfectly... didn't try for cassandraOperation.
also you might need #Column(value = "your_columnName_in_DB") if your pojo class's variable name is different
like
#Column(value = "name")
private String userName;
#Column(value = "age")
private int userAge;
revert here if its work?
Also can you help me pass dynamic value to that myQuery string.. using object[] same like prepareStatment in SQL
thanks.

Resources