Skip to content

Achilles Custom Types

DuyHai DOAN edited this page Dec 7, 2014 · 31 revisions

To support all Cassandra specific but powerful features such as tunable consistency levels or counters, Achilles introduces custom Java types:


#### ConsistencyLevel It is an enum exposing all existing consistency levels in Cassandra:
  • ANY
  • ONE
  • TWO
  • THREE
  • QUORUM
  • LOCAL_ONE
  • LOCAL_QUORUM
  • EACH_QUORUM
  • ALL
  • LOCAL_SERIAL
  • SERIAL

See Consistency Level for more details


#### Counter This type represents a **Cassandra** counter column. It exposes the following methods:
	public Long get();

	public void incr();

	public void incr(Long increment);

	public void decr();

	public void decr(Long decrement);

For more details on counters, see Counter type


#### CounterBuilder

The above Counter type can be retrieved only from a managed entity. If you want to insert a transient entity having counter fields, you should use the CounterBuilder type provided by Achilles and call the get() method to have a handle on the counter proxy.

The builder exposes the following static methods:

    public static Counter incr() 

    public static Counter incr(Long incr) 

    public static Counter decr() 

    public static Counter decr(Long decr) 

The only sensible usage for CounterBuilder is for transient entity persistence. Example:

@Entity
public class UserEntity
{
    @Id
    private Long userId;

    @Column
    private Counter 

    private UserEntity(Long userId,.....,Counter popularity)
    {
        this.userId = userId;
        ...
        this.popularity = popularity;
    }
    ...
}

 // Creating a new user with initial popularity value set to 100
 manager.insert(new UserEntity(10L,....,CounterBuilder.incr(100L));

#### Options

An Options is just a holder object for Cassandra specific TTL, Timestamp, Consistency level and other parameters

class Options {

    Optional<ConsistencyLevel> consistency;

    Optional<Integer> ttl;

    Optional<Long> timestamp;

    List<LWTPredicate> lwtPredicates;

    Optional<LWTResultListener> lwtResultListenerO = Optional.absent();
    
    Optional<com.datastax.driver.core.ConsistencyLevel> serialConsistencyO = Optional.absent();

    public Optional<ConsistencyLevel> getConsistencyLevel() 		{
        return Optional.fromNullable(consistency);
    }

    public Optional<Integer> getTtl() {
        return ttl;
    }

    public Optional<Long> getTimestamp() {
        return timestamp;
    }

    public boolean isIfNotExists() {
        return lwtPredicates.size() == 1 && lwtPredicates.get(0).type() == LWTPredicate.LWTType.IF_NOT_EXISTS;
    }

    public boolean isIfExists() {
        return lwtPredicates.size() == 1 && lwtPredicates.get(0).type() == LWTPredicate.LWTType.IF_EXISTS;
    }

    public List<LWTCondition> getLwtConditions() {
        final List<?> lwtPredicates1 = FluentIterable.from(lwtPredicates).filter(FILTER_LWT_CONDITION).toList();
        return (List<LWTCondition>)lwtPredicates1;
    }

    public List<LWTPredicate> getLwtPredicates() {
        return lwtPredicates;
    }

    public boolean hasLWTConditions() {
        return CollectionUtils.isNotEmpty(lwtPredicates);
    }


    public Optional<LWTResultListener> getLWTResultListener() {
        return lwtResultListenerO;
    }
    
    public Optional<com.datastax.driver.core.ConsistencyLevel> getSerialConsistency() {
       return serialConsistencyO;
    }
}

Options are used in conjunction with common PersistenceManager operations insert(), update(), find() and delete().

Normally you cannot instantiate an Options object yourself, you need to use the OptionsBuilder instead, check below.


#### OptionsBuilder

Main builder to create Options.

The exposed methods are:

Options options;

// Consistency
options = OptionsBuilder.withConsistency(QUORUM);

// TTL
options = OptionsBuilder.withTtl(10);

// Timestamp 
options = OptionsBuilder.withTimestamp(100L);


// LWT 'IF NOT EXISTS'
options = OptionsBuilder.ifNotExists();

// LWT 'IF EXISTS'
options = OptionsBuilder.ifExists();

// LWT update conditions
options = OptionsBuilder
        .ifEqualCondition("name","John"),
        .ifEqualCondition("age_in_years",33L);
 
// LWT result listener
options = OptionsBuilder.lwtResultListener(listener);

// LWT LOCAL_SERIAL instead of the default SERIAL value
options = OptionsBuilder.lwtLocalSerial();

// Multiple options
options = OptionsBuilder.withTtl(11)
                .withConsistency(ANY)
                .withTimestamp(111L);

#### IndexCondition

This class defines an index condition necessary for Indexed Query

Below is the public constructor of an IndexCondition instance.

/**
	 * Shortcut constructor to build an EQUAL index condition
	 * 
	 * @param columnName
	 *            name of indexed column
	 * @param columnValue
	 *            value of indexed column
	 */
	public IndexCondition(String columnName, Object columnValue) {
		this.columnName = columnName;
		this.indexRelation = IndexRelation.EQUAL;
		this.columnValue = columnValue;
	}

As you can notice, the support for secondary index is limited to EQUALITY clause. Inequalities on secondary indices do not provide predictable performance so Achilles does not support it by default.


#### TypedMap

The native query API used to return a Map<String,Object> as result. It is not very user-friendly because it forces you do manual type casting. Example:

	
	Map<String,Object> columns = manager.nativeQuery("SELECT * FROM users WHERE userId = 10").getFirst();
	
	String name = (String)columns.get("name");  // BAD !
	Long age = (Long)columns.get("age"); // BAD !

TypedMap is just an extension of Map<String,Object> offering two extras methods for convenience:

	@SuppressWarnings("unchecked")
	public <T> T getTyped(String key) {
		T value = null;
		if (super.containsKey(key) && super.get(key) != null) {
			value = (T) super.get(key);
			return value;
		}
		return value;
	}

	public <T> T getTypedOr(String key, T defaultValue) {
		if (super.containsKey(key)) {
			return getTyped(key);
		} else {
			return defaultValue;
		}
	}

Of course there is no magic, the dirty casting you no longer do, getTyped() will do it for you. The target type is passed at runtime while calling the method. getTypedOr() lets you provide a fall-back value.

Example of usage:

	
	TypedMap columns = manager.nativeQuery("SELECT * FROM users WHERE userId = 10").first();
	
	// Explicit type (String) is passed to method invocation
	String name = columns.<String>getTyped("name");
	
	// No need to provide explicit type. The compiler will infer type in this case
	Long age = columns.get("age");

#### Interceptor

This is an interface defining the contract for a lifecycle interceptor. For more details, please check Interceptors

public interface Interceptor<T> 
{
	public void onEvent(T entity);

	public List<Event> events();
}

#### LWTCondition

This class defines a Lightweight Transaction ( LWT ) condition for conditional update

Below is the public constructor of a LWTCondition instance.

    public LWTCondition(String columnName, Object value) {
                this.columnName = columnName;
                this.value = value;
    }
    

The columnName parameter represents the CQL column name, not the field name of the entity.

Let's consider the following mapping:

  @Column(name = "age_in_years")
  private Long age;

  @Column
  private String name;
  • LWT condition for age: new LWTCondition("age_in_years",...)
  • LWT condition for name: new LWTCondition("name",...)

For more details on LWT operations, please refer to Lightweight Transaction


#### LWTResultListener

For all LWT operations, Cassandra returns an [applied] boolean column telling whether the operation has been successful or not. If a LWT update operation failed, Cassandra also returns the current values that differ from the ones used by the LWT update.

To intercepts the LWT operation result and current values, you can register a LWTResultListener using the Options API (see above)

The signature of the LWTResultListener is:

public interface LWTResultListener {

    public void onSuccess();

    public void onError(LWTResult lwtResult);
}

The LWTResult type is defined as:

public class LWTResult {
        private final Operation operation;
        private final TypedMap currentValues;

        public LWTResult(Operation operation, TypedMap currentValues) {
            this.operation = operation;
            this.currentValues = currentValues;
        }

        ...

        public static enum Operation {INSERT, UPDATE}
    }

For more details on LWT operations, please refer to Lightweight Transaction


#### InsertStrategy

Define the insert strategy on an entity. Right now only 2 values are possible:

  1. info.archinnov.achilles.type.InsertStrategy.NOT_NULL_FIELDS
  2. info.archinnov.achilles.type.InsertStrategy.ALL_FIELDS

Upon call to insert(), depending on the chosen strategy Achilles will

  1. insert all fields on the entity, even if they are null
  2. insert only non null fields of the entity

Check here for more details on the Insert strategy


#### Encoding

Define the encoding type for enum values. 2 choices are available:

  1. info.archinnov.achilles.annotations.Enumerated.Encoding.NAME encode enum using their name()
  2. info.archinnov.achilles.annotations.Enumerated.Encoding.ORDINAL encode enum using their ordinal()

More details on Enum type


#### NamingStrategy

Define the naming strategy for the keyspace, table and column name. There are 3 possible strategies:

  1. SNAKE_CASE: transform all schema name using snake case
  2. CASE_SENSITIVE: enclose the name between double quotes (") for escaping the case
  3. LOWER_CASE: transform the name to lower case

Check here for more details on the Naming strategy


#### Codec

Define a codec to transform a source type into a target type.
The source type can be any Java type. The target type should be a Java type supported by Cassandra

public interface Codec<FROM, TO> {

    Class<FROM> sourceType();

    Class<TO> targetType();

    TO encode(FROM fromJava) throws AchillesTranscodingException;

    FROM decode(TO fromCassandra) throws AchillesTranscodingException;
}


Example of LongToString codec:
public class LongToString implements Codec<Long,String> {
    @Override
    public Class<Long> sourceType() {
        return Long.class;
    }

    @Override
    public Class<String> targetType() {
        return String.class;
    }

    @Override
    public String encode(Long fromJava) throws AchillesTranscodingException {
        return fromJava.toString();
    }
    
    @Override
    public Long decode(String fromCassandra) throws AchillesTranscodingException {
        return Long.parseLong(fromCassandra);
    }
}

The codecs are necessary to define TypeTransformer


#### AchillesFuture<T>

This class simply extends the ListenableFuture class with one method getImmediately(). This method intercepts all exceptions that could be raised during the blocking call to get() and return the following runtime exception

  • DriverException
  • DriverInternalError
  • AchillesLightWeightTransactionException
  • AchillesBeanValidationException
  • AchillesInvalidTableException
  • AchillesStaleObjectStateException
  • AchillesException

For more details, please check Asynchronous Operations


#### Empty

It is just a marker enum for asynchronous operations. Sometimes an operation does not return any meaningful result (like delete() or endBatch()). Still we need to be able to ensure that the asynchronous operation has been executed correctly by calling get(). If the execution has been successful, the singleton Empty is returned, otherwise an exception is raised

For more details, please check Asynchronous Operations

Home

Clone this wiki locally