Bringing together the Apache Cassandra experts from the community and DataStax.

Want to learn? Have a question? Want to share your expertise? You are in the right place!

Not sure where to begin? Getting Started

 

question

mwkohout avatar image
mwkohout asked mwkohout edited

Using EntityMapper and java.util.Optional<> fields on Immutable Entities

Hi I'm trying to set up the Java Mapper system(version 4.13.0) to work properly with Immutable Entity classes(annotated with @PropertyStrategy(mutable = false, getterStyle = JAVABEANS)) where I've modified the entities so that nullable values(which can be base types like text and numbers as well as @Entity mapped UDTs ) are only accessible via getters that return an java.util.Optional<?>.

I'm able to write the Entity class, and the java-driver-mapper-processor will generate it's helper code(I'm not sure it's right, but I'm not getting errors at build time).

But at runtime it doesn't seem to be happy with me. It's complaining about not finding a valid codex:

Codec not found for requested operation: [UDT(testSchema."com.example.EntityClass") <-> java.util.Optional<com.example.EntityClass>]
com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException: Codec not found for requested operation: [UDT(testSchema."com.example.EntityClass") <-> java.util.Optional<com.example.EntityClass>]
at app//com.datastax.oss.driver.internal.core.type.codec.registry.CachingCodecRegistry.createCodec(CachingCodecRegistry.java:609)

I understand at a basic level what a codex does, and I've registered some previously to do mundane things like timestamp<=>Instant, and deal with with java Enums. I've also seen the pages in the docs that show the Optional codex(ExtraTypeCodecs.optionalOf(TypeCodecs.UUID);).

But I'm having trouble finding something that can help me with how to set it up to work with a UDT. Is there some example in the docs that I've missed? Secondly, is there something I can do to have this codex for Optional<>, once registered, just work for all Entity annotated UDTs and even the native types?

thank you for your time.

FYI, here's a minimal code example, FYI, before I added the optionalB field, it was working correctly. If you want more information or in a different format let me know.
cql:

create type testSchema.EntityB(
    mandatory text,
    optional text
);

create table testSchema.EntityA(
    mandatory text,
    optional text,

    mandatory_b frozen<EntityB>,
    optional_b frozen<EntityB>,
    PRIMARY KEY ( mandatory )

)

session configuration:

    CqlSessionBuilder builder = CqlSession.builder()
        .withConfigLoader(loader)
        .addTypeCodecs(ExtraTypeCodecs.TIMESTAMP_UTC,
            ExtraTypeCodecs.enumNamesOf(OperationStatus.class),
            ExtraTypeCodecs.optionalOf(TypeCodecs.TEXT));

entity classes:

@Entity
@CqlName("EntityA")
@PropertyStrategy(mutable = false, getterStyle = JAVABEANS)
public class EntityA {

  @PartitionKey
  private final String mandatory;
  private final String optional;

  private final EntityB mandatoryB;
  private final EntityB optionalB;

  public EntityA(String mandatory, String optional, EntityB mandatoryB,EntityB optionalB ){
    assert mandatory != null;
    this.mandatory = mandatory;
    this.optional = optional;
    this.mandatoryB = mandatoryB;
    this.optionalB = optionalB;
  }

  public EntityA(
      String mandatory, Optional<String> optional,
      EntityB mandatoryB, Optional<EntityB> optionalB ){
    assert mandatory != null;
    assert mandatoryB != null;
    this.mandatory = mandatory;
    this.optional = optional.orElse(null);
    this.mandatoryB = mandatoryB;
    this.optionalB = optionalB.orElse(null);
  }

  public String getMandatory(){
    return mandatory;
  }
  public Optional<String> getOptional(){
    return Optional.ofNullable(optional);
  }

  public EntityB getMandatoryB(){
    return mandatoryB;
  }
  public Optional<EntityB> getOptionalB(){
    return Optional.ofNullable(mandatoryB);
  }

}

and

@Entity
@CqlName("EntityB")
@PropertyStrategy(mutable = false, getterStyle = JAVABEANS)
public class EntityB {

  private final String mandatory;
  private final String optional;

  public EntityB(String mandatory, String optional){
    assert mandatory != null;
    this.mandatory = mandatory;
    this.optional = optional;
  }

  public EntityB(String mandatory, Optional<String> optional){
    assert mandatory != null;
    this.mandatory = mandatory;
    this.optional = optional.orElse(null);
  }

  public String getMandatory(){
    return mandatory;
  }
  public Optional<String> getOptional(){
    return Optional.ofNullable(optional);
 }

}

here's the error:

Caused by: java.lang.IllegalArgumentException: The CQL ks.table: exampleSchema.entitya defined in the entity class: com.example.EntityA declares type mappings that are not supported by the codec registry:
Field: optional_b, Entity Type: java.util.Optional<com.example.EntityB>, CQL type: UDT(exampleSchema.entityb)
at com.datastax.oss.driver.internal.mapper.entity.EntityHelperBase.throwMissingTypesIfNotEmpty(EntityHelperBase.java:221)
at com.datastax.oss.driver.internal.mapper.entity.EntityHelperBase.throwMissingTableTypesIfNotEmpty(EntityHelperBase.java:210)
at com.example.EntityAHelper__MapperGenerated.validateEntityFields(EntityAHelper__MapperGenerated.java:256)
at com.example.ReadDaoImpl__MapperGenerated.initAsync(ReadDaoImpl__MapperGenerated.java:267)
java driver
10 |1000

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

1 Answer

Erick Ramirez avatar image
Erick Ramirez answered mwkohout edited

Please update your original question and provide a minimal code sample so we have an idea of what you're doing.

After you've done that, I'll get the driver devs here to review and respond to you. Cheers!

1 comment Share
10 |1000

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

does what I add make my idea clear?


secondly, if the current codebase doesn't support my use case will you folks accept a PR?

I'm not familiar with this codebase but it looks like this is where the ${Entity}Mapper__MapperGenerated.set(..) method, which...seems where the current version of the preprocessor determines how to extract the field value. It also appears DefaultPropertyDefinition/PropertyDefinition need to be modified to include this new field information to trigger this new behavior.


Is that a good place to change? Would you folks would accept a PR?


How should this be exposed to the preprocessor? How about a field annotation named

@com.datastax.oss.driver.api.mapper.annotations.OptionalValue
0 Likes 0 ·