Why won't astyanax (java) recognize my @Id annotated values in my scala case class parameter list?

So here's my dilemma: I have a domain model with a bunch of case classes in scala such as User and Organization. Within my data access layer (dao, repository, etc) I am using astyanax(a java library from netflix) and it's entity persister to save the object to a cassandra column family.

Here is some sample code for my cassandra/astyanax-backed DAOs (yes i know I need to do something more scala-ish, but I'm still learning =))

After reading through this long-winded description, I'm basically looking to see why annotated vals in the param list won't work when java does getDeclaredAnnotations() on the Field I would hate to have to go back and refactor everything so I can use the persister which makes it really simple to save an entity (i.e. manager.put(entity)). If I want to keep using case classes so I can work with more immutable style scala and Lens from scalaz, I'll then have to update the DAO and do all the persisting manually which can really kill time.

So, if anyone knows something that I'm not seeing, please let me know! Thanks in advance for taking the time to read through this.

Scenario 1 - Case Class

Astyanax fails to pick up the annotation @Id on val

@Entity
case class Organization(@Id @Column(name = "id") override val id: Option[UUID] = None,
                        @Column(name = "created_on") override val createdOn: Option[Date] = None,
                        @Column(name = "modified_on") override val modifiedOn: Option[Date] = None,
                        @Column(name = "name") name: Option[String] = None,
                        @Column(name = "is_paid_account") isPaidAccount: Boolean = false) extends IdBaseEntity[UUID](id, createdOn, modifiedOn)

Scenario 2 - Class with companion object or class without companion object

Astyanax fails to pick up the @Id annotation on val

@Entity
class Organization(@Id @Column(name = "id") override val id: Option[UUID] = None,
                       @Column(name = "created_on") override val createdOn: Option[Date] = None,
                       @Column(name = "modified_on") override val modifiedOn: Option[Date] = None,
                       @Column(name = "name") name: Option[String] = None,
                       @Column(name = "is_paid_account") isPaidAccount: Boolean = false) extends IdBaseEntity[UUID](id, createdOn, modifiedOn)

object Organization {
  def apply(id: Option[UUID] = None,
            createdOn: Option[Date] = None,
            modifiedOn: Option[Date] = None,
            name: Option[String] = None,
            isPaidAccount: Boolean = false) = new Organization(id, createdOn, modifiedOn, name, isPaidAccount)
}

Scenario 3 - Case class or class with val defined inside block

This works fine because it picks up theId as being annotated with @Id, but I don't want do do this because IdBaseEntity already defines and id val and defeats the whole purpose of inheritance and being able to pass id to the superclass

@Entity
case class Organization(@Id @Column(name = "id") override val id: Option[UUID] = None,
                        @Column(name = "created_on") override val createdOn: Option[Date] = None,
                        @Column(name = "modified_on") override val modifiedOn: Option[Date] = None,
                        @Column(name = "name") name: Option[String] = None,
                        @Column(name = "is_paid_account") isPaidAccount: Boolean = false) extends IdBaseEntity[UUID](id, createdOn, modifiedOn) {
  @Id @Column(name = "id") val theId: Option[UUID] = id
}

The Data access portion

Way down in manager you will see a call to build(). Astyanax examines the class that was passed in to withEntityType(), which in this case is classOf[Organization]

Every one of my scenarios fails except #3 when I have a val declared inside the class block instead of the parameter list for the case class or a regular class/regular class with companion object. Astyanax says that there is know member of that class that is annotated with @Id so it throws an exception. Before I dig any further, I figured I would ask the community about the nuances of annotation a scala class and sending it to a java library that does reflection. The source is nothing special. In fact here are the relevant lines where things fail: https://github.com/Netflix/astyanax/blob/master/astyanax-entity-mapper/src/main/java/com/netflix/astyanax/entitystore/EntityMapper.java#L89-120

class CassandraOrganizationDAO extends BaseCassandraDAO[Organization, UUID](Astyanax.context) with OrganizationDAO {
  val ColumnFamilyOrganizations: ColumnFamily[UUID, String] = new ColumnFamily[UUID, String](
    "organizations",
    TimeUUIDSerializer.get(),
    StringSerializer.get(),
    ByteBufferSerializer.get())

  val ColumnFamilyOrganizationMembers: ColumnFamily[UUID, UUID] = new ColumnFamily[UUID, UUID](
    "organization_members",
    TimeUUIDSerializer.get(),
    TimeUUIDSerializer.get(),
    DateSerializer.get())

  val manager: EntityManager[Organization, UUID] = new DefaultEntityManager.Builder[Organization, UUID]()
    .withEntityType(classOf[Organization])
    .withKeyspace(getKeyspace())
    .withColumnFamily(ColumnFamilyOrganizations)
    .build()

 // the rest of the class is omitted 
}

Answers


I came across a similar issue, where my annotation were never taken in account in my constructor's fields. Indeed, I used SpringData and it was impossible to directly map annotation (like @Indexed) on a field.

To enable annotations in your case class's constructor, you should first create this kind of class:

object FixedScalaAnnotations {
  type Id = com.packagecontainingid.Id @field //replace by the right package
  type Column = com.packagecontainingcolumn.Column @field
}

And then import it in your case class and use it instead of the original:

import FixedScalaAnnotations._

@Entity
case class Organization(@Id @Column(name = "id") override val id: Option[UUID] = None,
                        @Column(name = "created_on") override val createdOn: Option[Date] = None,
                        @Column(name = "modified_on") override val modifiedOn: Option[Date] = None,
                        @Column(name = "name") name: Option[String] = None,
                        @Column(name = "is_paid_account") isPaidAccount: Boolean = false) extends IdBaseEntity[UUID](id, createdOn, modifiedOn)

Be sure the original packages aren't used.

Here a related article dealing with JPA: http://blog.fakod.eu/2010/07/14/constructor-arguments-with-jpa-annotations/


Need Your Help

MySQL Select Response Size .NET

mysql .net database vb.net response

Is it possible to receive the size of a MySQL response before reading it? To be able to have a progressbar giving status about the receiving process.

About UNIX Resources Network

Original, collect and organize Developers related documents, information and materials, contains jQuery, Html, CSS, MySQL, .NET, ASP.NET, SQL, objective-c, iPhone, Ruby on Rails, C, SQL Server, Ruby, Arrays, Regex, ASP.NET MVC, WPF, XML, Ajax, DataBase, and so on.