aironman
aironman

Reputation: 869

About an exception org.springframework.core.convert.ConverterNotFoundException in spring-data-cassandra when i try to insert a row

i am quite newwbee with spring-data-cassandra and i am facing problems when i try to create one row within a cassandra table.

This is the exception when i try to run the test, setUp method is never executed:

org.springframework.core.convert.ConversionFailedException: **Failed to convert from type [java.util.HashSet<?>] to type [java.lang.String] for value '[unicon.matthews.entity.DataSync@79135a38[**
id=data_sync_id
orgId=identifier
tenantId=_tenand_id
syncDateTime=2017-09-25T13:35:14.153
syncType=all
syncStatus=fully_completed
]]'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [unicon.matthews.entity.DataSync] to type [java.lang.String]

...
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [unicon.matthews.entity.DataSync] to type [java.lang.String]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:324)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:206)
at org.springframework.core.convert.support.CollectionToStringConverter.convert(CollectionToStringConverter.java:71)
at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:37)
... 60 more

This is the test:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = unicon.matthews.oneroster.service.repository.CassandraConfiguration.class)
public class CassandraOrgRepositoryTests {

final String _userName = UUID.randomUUID().toString();
final String _orgName = UUID.randomUUID().toString();
final String _sourceId = UUID.randomUUID().toString();
final String _id = UUID.randomUUID().toString();
final String _api_key = UUID.randomUUID().toString();
final String _api_secret = UUID.randomUUID().toString();
final String _tenant_id = "_tenand_id";
final Status _status = Status.inactive;

final OrgType _org_type = OrgType.school;
final String _org_identifier = UUID.randomUUID().toString();
@ClassRule public final static CassandraKeyspace CASSANDRA_KEYSPACE = CassandraKeyspace.onLocalhost().atLeast(Version.parse("3.0"));

@Autowired CassandraOrgRepository repository;

@Before
public void setUp() throws Exception {

    repository.deleteAll();
    OrgCassandraTable aPojo = new OrgCassandraTable();
    aPojo.setTenantId(_tenant_id );
    Org.Builder myOrgBuilder = Org.Builder.class.newInstance();
    Map<String, String> metadata = new TreeMap<String,String>();
    metadata.put("key","value");
    Org myOrgPojo = myOrgBuilder.withIdentifier("identifier")
                .withDateLastModified(LocalDateTime.now())
                .withMetadata(metadata)
                .withName(_orgName)
                .withSourcedId(_sourceId)
                .withStatus(_status)
                .withType(_org_type)
                .build();

    aPojo.setSourcedId(_sourceId);
    // active 0,
    // inactive 1,
    // tobedeleted 2;
    aPojo.setStatus("1");
    aPojo.setDateLastModified(LocalDateTime.now() );
    aPojo.setName(_orgName);
    aPojo.setType(_org_type.toString());
    aPojo.setIdentifier(_org_identifier);
    aPojo.setTenantId(_tenant_id);

    // THIS MUST BE THE PROBLEM!
    Set<DataSync> _dataSyncSet = new HashSet<DataSync>();
    DataSync.Builder _dataSyncBuilder = DataSync.Builder.class.newInstance();
    DataSync new_data_sync=_dataSyncBuilder.withId("data_sync_id")
                    .withOrgId(myOrgPojo.getIdentifier())
                    .withSyncDateTime(LocalDateTime.now())
                    .withSyncStatus(DataSync.DataSyncStatus.fully_completed)
                    .withSyncType(DataSync.DataSyncType.all)
                    .withTenantId(_tenant_id)
                    .build();
    _dataSyncSet.add(new_data_sync);
    aPojo.setDataSyncs(_dataSyncSet);
    aPojo.setApiSecret(_api_secret);
    aPojo.setApiKey(_api_key);
    aPojo.setId(_id);
    repository.save(aPojo);
    assertTrue(repository.count() > 0);
    System.out.println("Created a org with fake data...");
}

@Test
public void testFindbyId() {
    Optional<WrapperOrg> loaded = repository.findById(_id);
    Assert.assertNotNull(loaded);
    Assert.assertEquals("something went wrong...",_id,loaded.get().getId());
}

}

This is the repository:

import java.util.Optional;
import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.data.cassandra.repository.Query;

// this repo must implement something that paginates rows, because ALLOW FILTERING must not be used
public interface CassandraOrgRepository extends CassandraRepository<OrgCassandraTable> {

 @Query("SELECT * FROM org WHERE id = ?0")
 Optional<WrapperOrg> findById(final String id);
 @Query("SELECT * FROM org WHERE api_key = ?0 AND api_secret = ?1 ALLOW FILTERING")
 Optional<WrapperOrg> findByApiKeyAndApiSecret(final String apiKey, final String apiSecret);
 @Query("SELECT * FROM org WHERE api_key = ?0 ALLOW FILTERING")
 Optional<WrapperOrg> findByApiKey(final String apiKey);
 }

This is the CassandraConfiguration class that i mention in the test class. I suspect that i will have to do something here:

import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cassandra.core.keyspace.CreateKeyspaceSpecification;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.config.SchemaAction;
import org.springframework.data.cassandra.config.java.AbstractCassandraConfiguration;
import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories;

@Configuration
@EnableAutoConfiguration
public class CassandraConfiguration {

@Configuration
@EnableCassandraRepositories
static class CassandraConfig extends AbstractCassandraConfiguration {

private static final String KEYSPACE = "example";

@Override
public String getKeyspaceName() {
  return KEYSPACE;
}

@Override
public SchemaAction getSchemaAction() {
  return SchemaAction.RECREATE_DROP_UNUSED;
}

protected List<CreateKeyspaceSpecification> getKeyspaceCreations() {
  List<CreateKeyspaceSpecification> createKeyspaceSpecifications = new ArrayList<>();
  createKeyspaceSpecifications.add(getKeySpaceSpecification());
  return createKeyspaceSpecifications;
}

// Below method creates KEYSPACE if it doesnt exist.
private CreateKeyspaceSpecification getKeySpaceSpecification() {

  CreateKeyspaceSpecification pandaCoopKeyspace = new CreateKeyspaceSpecification();
  pandaCoopKeyspace.name(KEYSPACE);
  pandaCoopKeyspace.ifNotExists(true)
           .createKeyspace();
  return pandaCoopKeyspace;
}


@Override
public String getContactPoints() {
  return "localhost";
}

@Override
public String[] getEntityBasePackages() {
  return new String[] {"unicon.matthews.oneroster.service.repository"};
}
}
}

This is the Entity pojo class:

import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Set;
import org.springframework.cassandra.core.PrimaryKeyType;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.cassandra.mapping.CassandraType;
import org.springframework.data.cassandra.mapping.Column;
import org.springframework.data.cassandra.mapping.Indexed;
import org.springframework.data.cassandra.mapping.PrimaryKeyColumn;
import org.springframework.data.cassandra.mapping.Table;
import com.datastax.driver.core.DataType;
import unicon.matthews.entity.DataSync;
import unicon.matthews.oneroster.Org;
import unicon.matthews.oneroster.OrgType;
import unicon.matthews.oneroster.Status;

@Table(value=OrgCassandraTable.tableName)
public class OrgCassandraTable implements Serializable{

@org.springframework.data.annotation.Transient
public static final String tableName = "org";

@PrimaryKeyColumn(name = "id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
@CassandraType(type = DataType.Name.TEXT)
@Column("id")
private String id;

@Indexed
@CassandraType(type = DataType.Name.TEXT)
@Column("tenant_id")
private String tenantId;

@Indexed
@CassandraType(type = DataType.Name.TEXT)
@Column("api_key")
private String apiKey;

@Indexed
@CassandraType(type = DataType.Name.TEXT)
@Column("api_secret")
private String apiSecret;

@Indexed
@CassandraType(type = DataType.Name.TEXT)
@Column("org_source_id")
private String sourcedId;

@CassandraType(type = DataType.Name.TEXT)
@Column("org_status")
private String status;

@Column("org_metadata")
private Map<String, String> metadata;

@Column("org_dateLastModified")
@LastModifiedDate
private LocalDateTime dateLastModified;

@Column("org_name")
@CassandraType(type = DataType.Name.TEXT)
private String name;

// ojito que esto es un enum
@Column("org_type")
@CassandraType(type = DataType.Name.TEXT)
private String type;

@Column("org_identifier")
@CassandraType(type = DataType.Name.TEXT)
@Indexed
private String identifier;

// THIS FIELD LOOKS TO BE THE PROBLEM!
@Column("org_data_syncs")
@CassandraType(type = DataType.Name.TEXT)
private Set<DataSync> dataSyncs;

public OrgCassandraTable(){

}

This is DataSync class. It belongs to a third party library, i do not have the code. What do am i doing wrong?

public class DataSync implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private String orgId;
private String tenantId;
private LocalDateTime syncDateTime;
private DataSync.DataSyncType syncType;
private DataSync.DataSyncStatus syncStatus;


...getters, setters, equals, hashCode, toString methods
}


...

// getters, setters, hashCode, equals, toString methods.
}

Upvotes: 1

Views: 7052

Answers (1)

mp911de
mp911de

Reputation: 18119

Cassandra is a column-oriented store – Spring Data Cassandra maps each domain class to a single table, there are no relations, and there is no (not yet, but might come) support for embedded objects. Embedded objects in the sense of flattening the data structure to the columns of the table the enclosing object maps to.

However, there is support for user-defined types via @UserDefinedType on the object class representing the data structure. Adding @UserDefinedType requires having control over the class/code.

If you want to stick to the class, then you still have an option to serialize the data yourself, e.g., using Jackson and storing the JSON inside a single Cassandra column:

static class DataSyncWriteConverter implements Converter<DataSync, String> {

  public String convert(DataSync source) {

    try {
      return new ObjectMapper().writeValueAsString(source);
    } catch (IOException e) {
      throw new IllegalStateException(e);
    }
  }
}

You should be able to work with collection types as well, meaning, that you can persist a Set<DataSync> within a set<varchar> column in Cassandra with this approach.

One last thing: Using 3rd-party classes comes at the risk of changes to the external classes where you don't have control over. Creating an own data structure by replicating all fields and mapping the data to the 3rd-party-class give you control over the lifecycle of changes.

References:

Upvotes: 1

Related Questions