diff --git a/client-java/instrumentation/pom.xml b/client-java/instrumentation/pom.xml
index 961c2f818b..393daed83d 100644
--- a/client-java/instrumentation/pom.xml
+++ b/client-java/instrumentation/pom.xml
@@ -67,6 +67,12 @@
${springboot.version}
test
+
+ org.springframework.boot
+ spring-boot-starter-data-cassandra
+ ${springboot.version}
+ test
+
org.springframework.boot
spring-boot-starter-data-redis
diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/AdditionalInfo.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/AdditionalInfo.java
index 9493cfb078..cb04f623f9 100644
--- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/AdditionalInfo.java
+++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/AdditionalInfo.java
@@ -118,6 +118,8 @@ public StatementDescription(String line, String method) {
private final Set mongoCollectionSchemaData = new CopyOnWriteArraySet<>();
+ private final Set cassandraTableSchemaData = new CopyOnWriteArraySet<>();
+
public Set getSqlInfoData(){
return Collections.unmodifiableSet(executedSqlCommandData);
}
@@ -150,6 +152,10 @@ public Set getMongoCollectionTypeData(){
return Collections.unmodifiableSet(mongoCollectionSchemaData);
}
+ public Set getCassandraTableTypeData(){
+ return Collections.unmodifiableSet(cassandraTableSchemaData);
+ }
+
public void addSqlInfo(ExecutedSqlCommand info){
executedSqlCommandData.add(info);
}
@@ -182,6 +188,10 @@ public void addMongoCollectionType(MongoCollectionSchema mongoCollectionSchema){
mongoCollectionSchemaData.add(mongoCollectionSchema);
}
+ public void addCassandraTableType(CassandraTableSchema cassandraTableSchema){
+ cassandraTableSchemaData.add(cassandraTableSchema);
+ }
+
public Set getParsedDtoNamesView(){
return Collections.unmodifiableSet(parsedDtoNames);
}
diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/CassandraTableSchema.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/CassandraTableSchema.java
new file mode 100644
index 0000000000..e854b35da3
--- /dev/null
+++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/CassandraTableSchema.java
@@ -0,0 +1,46 @@
+package org.evomaster.client.java.instrumentation;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * Schema of rows in a Cassandra table.
+ */
+public class CassandraTableSchema implements Serializable {
+ private final String tableName;
+ private final String tableSchema;
+
+ public CassandraTableSchema(String tableName, String tableSchema) {
+ this.tableName = tableName;
+ this.tableSchema = tableSchema;
+ }
+
+ public String getTableName() {
+ return tableName;
+ }
+
+ public String getTableSchema() {
+ return tableSchema;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ CassandraTableSchema that = (CassandraTableSchema) o;
+ return Objects.equals(tableName, that.tableName) && Objects.equals(tableSchema, that.tableSchema);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(tableName, tableSchema);
+ }
+
+ @Override
+ public String toString() {
+ return "CassandraTableSchema{" +
+ "tableName='" + tableName + '\'' +
+ ", tableSchema='" + tableSchema + '\'' +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ReplacementList.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ReplacementList.java
index 24f2b9d3e2..0c2b87b038 100644
--- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ReplacementList.java
+++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ReplacementList.java
@@ -29,6 +29,7 @@ public static List getList() {
new Base64DecoderClassReplacement(),
new BooleanClassReplacement(),
new ByteClassReplacement(),
+ new CassandraTemplateClassReplacement(),
new CharacterClassReplacement(),
new CollectionClassReplacement(),
new CqlSessionClassReplacement(),
@@ -54,6 +55,7 @@ public static List getList() {
new LocalTimeClassReplacement(),
new LongClassReplacement(),
new MapClassReplacement(),
+ new MappingCassandraEntityInformationClassReplacement(),
new MatcherClassReplacement(),
new MessageBodyReaderClassReplacement(),
new MethodClassReplacement(),
diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/CassandraTemplateClassReplacement.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/CassandraTemplateClassReplacement.java
new file mode 100644
index 0000000000..cf670f711e
--- /dev/null
+++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/CassandraTemplateClassReplacement.java
@@ -0,0 +1,105 @@
+package org.evomaster.client.java.instrumentation.coverage.methodreplacement.thirdpartyclasses;
+
+import org.evomaster.client.java.instrumentation.CassandraTableSchema;
+import org.evomaster.client.java.instrumentation.coverage.methodreplacement.Replacement;
+import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyMethodReplacementClass;
+import org.evomaster.client.java.instrumentation.coverage.methodreplacement.UsageFilter;
+import org.evomaster.client.java.instrumentation.object.ClassToSchema;
+import org.evomaster.client.java.instrumentation.shared.ReplacementCategory;
+import org.evomaster.client.java.instrumentation.shared.ReplacementType;
+import org.evomaster.client.java.instrumentation.staticstate.ExecutionTracer;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The intention of this replacement is the same as {@link MappingCassandraEntityInformationClassReplacement}:
+ * retrieve the entity-type-to-table mapping. But that constructor replacement only fires when Spring Data
+ * instantiates a {@code CassandraRepository}; a SUT calling {@code CassandraTemplate} directly (bypassing
+ * the repository layer) needs this replacement to have that mapping recorded too.
+ */
+public class CassandraTemplateClassReplacement extends ThirdPartyMethodReplacementClass {
+
+ private static final CassandraTemplateClassReplacement singleton = new CassandraTemplateClassReplacement();
+
+ @Override
+ protected String getNameOfThirdPartyTargetClass() {
+ return "org.springframework.data.cassandra.core.CassandraTemplate";
+ }
+
+ private static final String INSERT_ID = "insert";
+ private static final String SELECT_ONE_ID = "selectOneString";
+ private static final String SELECT_ID = "selectString";
+
+ @Replacement(replacingStatic = false,
+ type = ReplacementType.TRACKER,
+ id = INSERT_ID,
+ usageFilter = UsageFilter.ANY,
+ category = ReplacementCategory.CASSANDRA)
+ public static T insert(Object cassandraTemplate, T entity) {
+ try {
+ addCassandraTableType(cassandraTemplate, entity.getClass());
+
+ Method insertMethod = getOriginal(singleton, INSERT_ID, cassandraTemplate);
+ Object result = insertMethod.invoke(cassandraTemplate, entity);
+ return (T) result;
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw (RuntimeException) e.getCause();
+ }
+ }
+
+ @Replacement(replacingStatic = false,
+ type = ReplacementType.TRACKER,
+ id = SELECT_ONE_ID,
+ usageFilter = UsageFilter.ANY,
+ category = ReplacementCategory.CASSANDRA)
+ public static T selectOne(Object cassandraTemplate, String cql, Class entityClass) {
+ try {
+ addCassandraTableType(cassandraTemplate, entityClass);
+
+ Method selectOneMethod = getOriginal(singleton, SELECT_ONE_ID, cassandraTemplate);
+ Object result = selectOneMethod.invoke(cassandraTemplate, cql, entityClass);
+ return (T) result;
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw (RuntimeException) e.getCause();
+ }
+ }
+
+ @Replacement(replacingStatic = false,
+ type = ReplacementType.TRACKER,
+ id = SELECT_ID,
+ usageFilter = UsageFilter.ANY,
+ category = ReplacementCategory.CASSANDRA)
+ public static List select(Object cassandraTemplate, String cql, Class entityClass) {
+ try {
+ addCassandraTableType(cassandraTemplate, entityClass);
+
+ Method selectMethod = getOriginal(singleton, SELECT_ID, cassandraTemplate);
+ Object result = selectMethod.invoke(cassandraTemplate, cql, entityClass);
+ return (List) result;
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw (RuntimeException) e.getCause();
+ }
+ }
+
+ private static void addCassandraTableType(Object cassandraTemplate, Class> entityClass) {
+ try {
+ Object tableNameId = cassandraTemplate.getClass().getMethod("getTableName", Class.class)
+ .invoke(cassandraTemplate, entityClass);
+ String tableName = (String) tableNameId.getClass().getMethod("asInternal").invoke(tableNameId);
+
+ String schema = ClassToSchema.getOrDeriveSchemaWithItsRef(entityClass, true, Collections.emptyList());
+ ExecutionTracer.addCassandraTableType(new CassandraTableSchema(tableName, schema));
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MappingCassandraEntityInformationClassReplacement.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MappingCassandraEntityInformationClassReplacement.java
new file mode 100644
index 0000000000..980206c230
--- /dev/null
+++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MappingCassandraEntityInformationClassReplacement.java
@@ -0,0 +1,79 @@
+package org.evomaster.client.java.instrumentation.coverage.methodreplacement.thirdpartyclasses;
+
+import org.evomaster.client.java.instrumentation.CassandraTableSchema;
+import org.evomaster.client.java.instrumentation.coverage.methodreplacement.Replacement;
+import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyCast;
+import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyMethodReplacementClass;
+import org.evomaster.client.java.instrumentation.object.ClassToSchema;
+import org.evomaster.client.java.instrumentation.shared.ReplacementCategory;
+import org.evomaster.client.java.instrumentation.shared.ReplacementType;
+import org.evomaster.client.java.instrumentation.staticstate.ExecutionTracer;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collections;
+
+/**
+ * When a Cassandra repository is created in Spring, a CqlSession is used under the hood.
+ * But information about the type of the repository's rows is not transferred to the session.
+ * That info is retained on Spring side.
+ * So the intention of this replacement is to retrieve that type info.
+ * This will allow us to create and insert rows of the correct type in the table (and the repository).
+ */
+public class MappingCassandraEntityInformationClassReplacement extends ThirdPartyMethodReplacementClass {
+ private static final MappingCassandraEntityInformationClassReplacement singleton = new MappingCassandraEntityInformationClassReplacement();
+ private static ThreadLocal