Java tutorial
/* * Copyright (C) 2015-2015 The Helenus Driver Project Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.helenusdriver.driver.impl; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.io.IOException; import java.util.Arrays; import java.util.Map; import java.util.Optional; import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.text.WordUtils; import com.datastax.driver.core.Row; import com.datastax.driver.core.UDTValue; import com.datastax.driver.core.exceptions.InvalidTypeException; import com.github.helenusdriver.commons.lang3.reflect.ReflectionUtils; import com.github.helenusdriver.driver.ColumnPersistenceException; import com.github.helenusdriver.driver.ObjectConversionException; import com.github.helenusdriver.driver.info.ClassInfo; import com.github.helenusdriver.driver.info.FieldInfo; import com.github.helenusdriver.driver.info.TableInfo; import com.github.helenusdriver.persistence.CQLDataType; import com.github.helenusdriver.persistence.ClusteringKey; import com.github.helenusdriver.persistence.Column; import com.github.helenusdriver.persistence.DataType; import com.github.helenusdriver.persistence.Index; import com.github.helenusdriver.persistence.Mandatory; import com.github.helenusdriver.persistence.PartitionKey; import com.github.helenusdriver.persistence.Persisted; import com.github.helenusdriver.persistence.Persister; import com.github.helenusdriver.persistence.SuffixKey; import com.github.helenusdriver.persistence.Table; import com.github.helenusdriver.persistence.TypeKey; /** * The <code>FieldInfo</code> class caches all the field information needed by * the class ClassInfo. * <p> * <i>Note:</i> A fake {@link TableInfoImpl} class with no table annotations * might be passed in for user-defined type entities. By design, this class * will not allow any type of keys but only columns. * * @copyright 2015-2015 The Helenus Driver Project Authors * * @author The Helenus Driver Project Authors * @version 1 - Jan 19, 2015 - paouelle, vasu - Creation * * @param <T> The type of POJO represented by this field * * @since 1.0 */ @lombok.ToString(exclude = { "cinfo", "tinfo" }) public class FieldInfoImpl<T> implements FieldInfo<T> { /** * Holds the class for the POJO. * * @author vasu */ private final Class<T> clazz; /** * Holds the class info for the POJO. * * @author paouelle */ private final ClassInfoImpl<T> cinfo; /** * Holds the table information for this field. Can be <code>null</code> if * the field is only used as a keyspace suffix. * * @author vasu */ public final TableInfoImpl<T> tinfo; /** * Holds the declaring class for this field * * @author vasu */ private final Class<?> declaringClass; /** * Holds the reflection field represented by this field info object * * @author vasu */ private final Field field; /** * Holds the name for this field. * * @author paouelle */ private final String name; /** * Holds the type for this field. * * @author paouelle */ private final Class<?> type; /** * This variable is used to cache Column annotation * * @author vasu */ private final Column column; /** * Holds the persisted annotation for this field. If <code>null</code> then * there is no persister required and standard mapping to Cassandra data * types should be used. * * @author paouelle */ private final Persisted persisted; /** * Holds the persister to use to encode/decode the value in this field * (or the elements of the collection). If <code>null</code> then there is * no persister required and standard mapping to Cassandra data types should * be used. * * @author paouelle */ private final Persister<?, ?> persister; /** * Suffix annotation for this field if any. * * @author vasu */ private final SuffixKey suffix; /** * Flag indicating if the field is mandatory (i.e. cannot be <code>null</code>). * * @author paouelle */ private final boolean mandatory; /** * Index annotation for the field if any.. * * @author paouelle */ private final Index index; /** * PartitionKey annotation for the field if any. * * @author paouelle */ private final PartitionKey partitionKey; /** * ClusteringKey annotation for the field if any. * * @author paouelle */ private final ClusteringKey clusteringKey; /** * TypeKey annotation for the field if any. * * @author paouelle */ private final TypeKey typeKey; /** * Element type of the set when this field is a multi-key. * * @author paouelle */ private final Class<?> multiKeyType; /** * Holds the data type definition for this field (if it is a column). * * @author paouelle */ private final DataTypeImpl.Definition definition; /** * Holds the data decoder for this field (if it is a column). * * @author paouelle */ private final DataDecoder<?> decoder; /** * Flag indicating if the field is final. * * @author paouelle */ private final boolean isFinal; /** * Holds the final value for the field if defined final. * * @author paouelle */ private final Object finalValue; /** * Holds the getter method to retrieve the value of this field from an * instance. * * @author vasu */ private final Method getter; /** * Holds the setter method to update the value for this field from an * instance. * * @author vasu */ private final Method setter; /** * Flag indicating if this is the last key in the partition or the cluster. * * @author paouelle */ private volatile boolean isLast = false; /** * Instantiates a new <code>FieldInfo</code> object for a root element pojo * class. * * @author paouelle * * @param cinfo the non-<code>null</code> class info for the POJO root element * @param tinfo the non-<code>null</code> table info from the POJO root element * @param field the non-<code>null</code> field to copy */ FieldInfoImpl(RootClassInfoImpl<T> cinfo, TableInfoImpl<T> tinfo, FieldInfoImpl<? extends T> field) { this.clazz = cinfo.getObjectClass(); this.cinfo = cinfo; this.tinfo = tinfo; this.declaringClass = field.declaringClass; this.field = field.field; this.name = field.name; this.type = field.type; this.column = field.column; this.persisted = field.persisted; this.persister = field.persister; this.suffix = field.suffix; this.mandatory = field.mandatory; this.index = field.index; this.partitionKey = field.partitionKey; this.clusteringKey = field.clusteringKey; this.typeKey = field.typeKey; this.multiKeyType = field.multiKeyType; this.definition = field.definition; this.decoder = field.decoder; this.isFinal = field.isFinal; this.finalValue = field.finalValue; this.getter = field.getter; this.setter = field.setter; this.isLast = field.isLast; } /** * Instantiates a new <code>FieldInfo</code> object not part of a defined * table. * * @author vasu * * @param cinfo the non-<code>null</code> class info for the POJO * @param field the non-<code>null</code> field to create an info object for * @throws IllegalArgumentException if unable to find a getter or setter * method for the field of if improperly annotated */ FieldInfoImpl(ClassInfoImpl<T> cinfo, Field field) { this.clazz = cinfo.getObjectClass(); this.cinfo = cinfo; this.tinfo = null; this.declaringClass = field.getDeclaringClass(); this.field = field; field.setAccessible(true); // make it accessible in case we need to this.isFinal = Modifier.isFinal(field.getModifiers()); this.name = field.getName(); this.type = DataTypeImpl.unwrapOptionalIfPresent(field.getType(), field.getGenericType()); this.column = null; this.persisted = null; this.persister = null; this.suffix = field.getAnnotation(SuffixKey.class); this.mandatory = true; // keyspace suffixes are mandatory fields this.index = null; // we don't care about this for keyspace suffixes this.partitionKey = null; // we don't care about this for keyspace suffixes this.clusteringKey = null; // we don't care about this for keyspace suffixes this.typeKey = null; // we don't care about this for keyspace suffixes this.multiKeyType = null; // we don't care about this for keyspace suffixes this.definition = null; // we don't care about this for keyspace suffixes this.decoder = null; // we don't care about this for keyspace suffixes this.getter = findGetterMethod(declaringClass); this.setter = findSetterMethod(declaringClass); this.finalValue = findFinalValue(); } /** * Instantiates a new <code>FieldInfo</code> object as a column part of a * defined table. * * @author vasu * * @param tinfo the table info for the field * @param field the non-<code>null</code> field to create an info object for * @throws IllegalArgumentException if unable to find a getter or setter * method for the field of if improperly annotated */ FieldInfoImpl(TableInfoImpl<T> tinfo, Field field) { this.clazz = tinfo.getObjectClass(); this.cinfo = (ClassInfoImpl<T>) tinfo.getClassInfo(); this.tinfo = tinfo; this.declaringClass = field.getDeclaringClass(); this.field = field; field.setAccessible(true); // make it accessible in case we need to this.isFinal = Modifier.isFinal(field.getModifiers()); this.name = field.getName(); this.type = DataTypeImpl.unwrapOptionalIfPresent(field.getType(), field.getGenericType()); this.persisted = field.getAnnotation(Persisted.class); if (persisted != null) { org.apache.commons.lang3.Validate.isTrue( (persisted.as() != DataType.INFERRED) && !persisted.as().isCollection(), "@Persisted annotation cannot be of type '%s': %s.%s", persisted.as(), declaringClass.getName(), field.getName()); this.persister = newPersister(); } else { this.persister = null; } this.suffix = field.getAnnotation(SuffixKey.class); this.mandatory = field.getAnnotation(Mandatory.class) != null; final Map<String, Column> columns = ReflectionUtils.getAnnotationsByType(String.class, Column.class, field); final Map<String, Index> indexes = ReflectionUtils.getAnnotationsByType(String.class, Index.class, field); final Map<String, PartitionKey> partitionKeys = ReflectionUtils.getAnnotationsByType(String.class, PartitionKey.class, field); final Map<String, ClusteringKey> clusteringKeys = ReflectionUtils.getAnnotationsByType(String.class, ClusteringKey.class, field); final Map<String, TypeKey> typeKeys = ReflectionUtils.getAnnotationsByType(String.class, TypeKey.class, field); final boolean isInTable = tinfo.getTable() != null; if (isInTable) { org.apache.commons.lang3.Validate.isTrue(!(!indexes.isEmpty() && columns.isEmpty()), "field must be annotated with @Column if it is annotated with @Index: %s.%s", declaringClass.getName(), field.getName()); org.apache.commons.lang3.Validate.isTrue(!(!partitionKeys.isEmpty() && columns.isEmpty()), "field must be annotated with @Column if it is annotated with @PartitionKey: %s.%s", declaringClass.getName(), field.getName()); org.apache.commons.lang3.Validate.isTrue(!(!clusteringKeys.isEmpty() && columns.isEmpty()), "field must be annotated with @Column if it is annotated with @ClusteringKey: %s.%s", declaringClass.getName(), field.getName()); org.apache.commons.lang3.Validate.isTrue(!(!typeKeys.isEmpty() && columns.isEmpty()), "field must be annotated with @Column if it is annotated with @TypeKey: %s.%s", declaringClass.getName(), field.getName()); } // Note: while searching for the matching table, uses the name from the // table annotation instead of the one returned by getName() as the later // might have been cleaned and hence would not match what was defined in // the POJO final String tname = isInTable ? tinfo.getTable().name() : Table.ALL; Column column = columns.get(tname); Index index = indexes.get(tname); PartitionKey partitionKey = partitionKeys.get(tname); ClusteringKey clusteringKey = clusteringKeys.get(tname); TypeKey typeKey = typeKeys.get(tname); if (column == null) { // fallback to special Table.ALL name column = columns.get(Table.ALL); } this.column = column; if (index == null) { // fallback to special Table.ALL name index = indexes.get(Table.ALL); } this.index = index; if (partitionKey == null) { // fallback to special Table.ALL name partitionKey = partitionKeys.get(Table.ALL); } this.partitionKey = partitionKey; if (clusteringKey == null) { // fallback to special Table.ALL name clusteringKey = clusteringKeys.get(Table.ALL); } this.clusteringKey = clusteringKey; if (typeKey == null) { // fallback to special Table.ALL name typeKey = typeKeys.get(Table.ALL); } this.typeKey = typeKey; // validate some UDT stuff if (!isInTable) { org.apache.commons.lang3.Validate.isTrue(!isIndex(), "field cannot be annotated with @Index: %s.%s", declaringClass.getName(), field.getName()); org.apache.commons.lang3.Validate.isTrue(!isPartitionKey(), "field cannot be annotated with @PartitionKey: %s.%s", declaringClass.getName(), field.getName()); org.apache.commons.lang3.Validate.isTrue(!isClusteringKey(), "field cannot be annotated with @ClusteringKey: %s.%s", declaringClass.getName(), field.getName()); org.apache.commons.lang3.Validate.isTrue(!isTypeKey(), "field cannot be annotated with @TypeKey: %s.%s", declaringClass.getName(), field.getName()); } if (isColumn()) { this.definition = DataTypeImpl.inferDataTypeFrom(field); this.decoder = definition.getDecoder(field, isMandatory() || isPartitionKey() || isClusteringKey()); if (isInTable && ((clusteringKey != null) || (partitionKey != null)) && (definition.getType() == DataType.SET)) { final Type type = field.getGenericType(); if (type instanceof ParameterizedType) { final ParameterizedType ptype = (ParameterizedType) type; this.multiKeyType = ReflectionUtils.getRawClass(ptype.getActualTypeArguments()[0]); // sets will always have 1 argument } else { throw new IllegalArgumentException( "unable to determine the element type of multi-field in table '" + tname + "': " + declaringClass.getName() + "." + field.getName()); } } else { this.multiKeyType = null; } } else { this.definition = null; this.decoder = null; this.multiKeyType = null; } this.getter = findGetterMethod(declaringClass); this.setter = findSetterMethod(declaringClass); this.finalValue = findFinalValue(); // validate some stuff if (isInTable) { org.apache.commons.lang3.Validate.isTrue(!(isIndex() && !isColumn()), "field in table '%s' must be annotated with @Column if it is annotated with @Index: %s.%s", tname, declaringClass.getName(), field.getName()); org.apache.commons.lang3.Validate.isTrue(!(isPartitionKey() && isClusteringKey()), "field in table '%s' must not be annotated with @ClusteringKey if it is annotated with @PartitionKey: %s.%s", tname, declaringClass.getName(), field.getName()); org.apache.commons.lang3.Validate.isTrue(!(isPartitionKey() && !isColumn()), "field in table '%s' must be annotated with @Column if it is annotated with @PartitionKey: %s.%s", tname, declaringClass.getName(), field.getName()); org.apache.commons.lang3.Validate.isTrue(!(isClusteringKey() && !isColumn()), "field in table '%s' must be annotated with @Column if it is annotated with @ClusteringKey: %s.%s", tname, declaringClass.getName(), field.getName()); org.apache.commons.lang3.Validate.isTrue(!(isTypeKey() && !isColumn()), "field in table '%s' must be annotated with @Column if it is annotated with @TypeKey: %s.%s", tname, declaringClass.getName(), field.getName()); org.apache.commons.lang3.Validate.isTrue(!(isTypeKey() && !String.class.equals(getType())), "field in table '%s' must be a String if it is annotated with @TypeKey: %s.%s", tname, declaringClass.getName(), field.getName()); org.apache.commons.lang3.Validate.isTrue(!(isTypeKey() && isFinal()), "field in table '%s' must not be final if it is annotated with @TypeKey: %s.%s", tname, declaringClass.getName(), field.getName()); org.apache.commons.lang3.Validate.isTrue( !(isTypeKey() && !(cinfo instanceof RootClassInfoImpl) && !(cinfo instanceof TypeClassInfoImpl)), "field in table '%s' must not be annotated with @TypeKey if class is annotated with @Entity: %s.%s", tname, declaringClass.getName(), field.getName()); if (isColumn() && definition.isCollection()) { org.apache.commons.lang3.Validate.isTrue( !((isClusteringKey() || isPartitionKey()) && (multiKeyType == null)), "field in table '%s' cannot be '%s' if it is annotated with @ClusteringKey or @PartitionKey: %s.%s", tname, definition, declaringClass.getName(), field.getName()); } } } /** * Instantiates a new fake <code>FieldInfo</code> object to represent a * suffix key associated with the POJO class of a user-defined type. * * @author paouelle * * @param cinfo the non-<code>null</code> class info for the POJO * @param suffix the non-<code>null</code> suffix annotation for the POJO class */ FieldInfoImpl(ClassInfoImpl<T> cinfo, SuffixKey suffix) { this.clazz = cinfo.getObjectClass(); this.cinfo = cinfo; this.tinfo = null; this.declaringClass = cinfo.getObjectClass(); this.field = null; this.isFinal = true; this.name = suffix.name(); this.type = String.class; this.column = null; this.persisted = null; this.persister = null; this.suffix = suffix; this.mandatory = true; // keyspace suffixes are mandatory fields this.index = null; // we don't care about this for keyspace suffixes this.partitionKey = null; // we don't care about this for keyspace suffixes this.clusteringKey = null; // we don't care about this for keyspace suffixes this.typeKey = null; // we don't care about this for keyspace suffixes this.multiKeyType = null; // we don't care about this for keyspace suffixes this.definition = null; // we don't care about this for keyspace suffixes this.decoder = null; // we don't care about this for keyspace suffixes this.getter = null; this.setter = null; this.finalValue = null; } /** * Instantiates a new persister object based on the @Persisted annotation. * * @author paouelle * * @return a non-<code>null</code> persister object corresponding to the * @Persisted annotation * @throws IllegalArgumentException if unable to instantiate a persister or * the persister is not compatible with the @Persisted annotation */ private Persister<?, ?> newPersister() { final Persister<?, ?> persister; if (persisted.arguments().length == 0) { // use default ctor try { persister = persisted.using().newInstance(); } catch (IllegalAccessException | InstantiationException e) { throw new IllegalArgumentException( "unable to instantiate persister: " + persisted.using().getName(), e); } } else { // use a String[] ctor try { persister = persisted.using().getConstructor(String[].class) .newInstance((Object) persisted.arguments()); } catch (NoSuchMethodException | IllegalAccessException | InstantiationException e) { throw new IllegalArgumentException("unable to instantiate persister: " + persisted.using().getName() + ", using arguments: " + Arrays.toString(persisted.arguments()), e); } catch (InvocationTargetException e) { throw new IllegalArgumentException("unable to instantiate persister: " + persisted.using().getName() + ", using arguments: " + Arrays.toString(persisted.arguments()), e.getTargetException()); } } org.apache.commons.lang3.Validate.isTrue(persister.getDecodedClass() != null, "@Persisted annotation's persister must be defined with a decoded class: %s", persisted.using().getName()); org.apache.commons.lang3.Validate.isTrue(persister.getPersistedClass() == persisted.as().CLASS, "@Persisted annotation's persister must be defined with persisted class '%s': %s", persisted.as().CLASS.getName(), persisted.using().getName()); return persister; } /** * Finds the getter method from the declaring class suitable to get a * reference to the field. * * @author paouelle * * @param declaringClass the non-<code>null</code> class declaring the field * @return the getter method for the field or <code>null</code> if none found * @throws IllegalArgumentException if unable to find a suitable getter */ private Method findGetterMethod(Class<?> declaringClass) { Method getter = findGetterMethod(declaringClass, "get"); if ((getter == null) && (type == Boolean.class) || (type == Boolean.TYPE)) { // try for "is" getter = findGetterMethod(declaringClass, "is"); } return getter; } /** * Finds the getter method from the declaring class suitable to get a * reference to the field. * * @author paouelle * * @param declaringClass the non-<code>null</code> class declaring the field * @param prefix the non-<code>null</code> getter prefix to use * @return the getter method for the field or <code>null</code> if none found * @throws IllegalArgumentException if unable to find a suitable getter */ private Method findGetterMethod(Class<?> declaringClass, String prefix) { final String mname = prefix + WordUtils.capitalize(name, '_', '-'); try { final Method m = declaringClass.getDeclaredMethod(mname); final int mods = m.getModifiers(); if (Modifier.isAbstract(mods) || Modifier.isStatic(mods)) { return null; } final Class<?> wtype = ClassUtils.primitiveToWrapper(type); final Class<?> wrtype = ClassUtils.primitiveToWrapper( DataTypeImpl.unwrapOptionalIfPresent(m.getReturnType(), m.getGenericReturnType())); org.apache.commons.lang3.Validate.isTrue(wtype.isAssignableFrom(wrtype), "expecting getter for field '%s' with return type: %s", field, type.getName()); m.setAccessible(true); return m; } catch (NoSuchMethodException e) { return null; } } /** * Finds the setter method from the declaring class suitable to set a * value for the field. * * @author paouelle * * @param declaringClass the non-<code>null</code> class declaring the field * @return the setter method for the field or <code>null</code> if none found * @throws IllegalArgumentException if unable to find a suitable setter */ private Method findSetterMethod(Class<?> declaringClass) { final String mname = "set" + WordUtils.capitalize(name, '_', '-'); try { final Method m = declaringClass.getDeclaredMethod(mname, type); final int mods = m.getModifiers(); if (Modifier.isAbstract(mods) || Modifier.isStatic(mods)) { return null; } org.apache.commons.lang3.Validate.isTrue(m.getParameterCount() == 1, "expecting setter for field '%s' with one parameter", field); final Class<?> wtype = ClassUtils.primitiveToWrapper(type); final Class<?> wptype = ClassUtils.primitiveToWrapper(DataTypeImpl.unwrapOptionalIfPresent( m.getParameterTypes()[0], m.getParameters()[0].getParameterizedType())); org.apache.commons.lang3.Validate.isTrue(wtype.isAssignableFrom(wptype), "expecting setter for field '%s' with parameter type: %s", field, type.getName()); m.setAccessible(true); return m; } catch (NoSuchMethodException e) { return null; } } /** * If the field is defined as final then finds and encode its default value. * * @author paouelle * * @return the encoded default value for this field or <code>null</code> if the * field is not defined as final * @throws IllegalArgumentException if the field is final and we are unable to * instantiate a dummy version of the pojo or access the field's final * value or again we failed to encode it * encode its default value */ private Object findFinalValue() { if (isFinal) { Object val; try { val = cinfo.getDefaultValue(field); } catch (IllegalArgumentException e) { // final field was not introspected by class info (declared class // must not be annotated with @Entity) // instantiates a dummy version and access its value try { // find default ctor even if private final Constructor<T> ctor = clazz.getDeclaredConstructor(); ctor.setAccessible(true); // in case it was private final T t = ctor.newInstance(); val = field.get(t); } catch (NoSuchMethodException | IllegalAccessException | InstantiationException ee) { throw new IllegalArgumentException("unable to instantiate object: " + clazz.getName(), ee); } catch (InvocationTargetException ee) { final Throwable t = ee.getTargetException(); if (t instanceof Error) { throw (Error) t; } else if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { // we don't expect any of those throw new IllegalArgumentException("unable to instantiate object: " + clazz.getName(), t); } } } if (persister != null) { // must encode it using the persister final String fname = declaringClass.getName() + "." + name; try { val = definition.encode(val, persisted, persister, fname); } catch (IOException e) { throw new IllegalArgumentException("failed to encode final field '" + fname + "' to " + persisted.as().CQL + "' with persister: " + persister.getClass().getName(), e); } } return val; } return null; } /** * Marks this field as being the last key in the partition or the cluster. * * @author paouelle */ void setLast() { this.isLast = true; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#getObjectClass() */ @Override public Class<T> getObjectClass() { return clazz; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#getDeclaringClass() */ @Override public Class<?> getDeclaringClass() { return declaringClass; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#getClassInfo() */ @Override public ClassInfo<T> getClassInfo() { return cinfo; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#getTableInfo() */ @Override public TableInfo<T> getTableInfo() { return tinfo; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#getName() */ @Override public String getName() { return name; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#getType() */ @Override public Class<?> getType() { return type; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#isColumn() */ @Override public boolean isColumn() { return column != null; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#getColumnName() */ @Override public String getColumnName() { return (column != null) ? column.name() : null; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#getSuffixKeyName() */ @Override public String getSuffixKeyName() { return (suffix != null) ? suffix.name() : null; } /** * Gets the column data type for this field. * * @author paouelle * * @return the column data type for this field if it is annotated as a * column; <code>null</code> otherwise */ public DataTypeImpl.Definition getDataType() { return definition; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#isSuffixKey() */ @Override public boolean isSuffixKey() { return suffix != null; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#getSuffixKey() */ @Override public SuffixKey getSuffixKey() { return suffix; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#isMandatory() */ @Override public boolean isMandatory() { return mandatory; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#isIndex() */ @Override public boolean isIndex() { return index != null; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#getIndex() */ @Override public Index getIndex() { return index; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#isCounter() */ @Override public boolean isCounter() { return (definition != null) ? definition.getType() == DataType.COUNTER : false; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#isLast() */ @Override public boolean isLast() { return isLast; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#isPartitionKey() */ @Override public boolean isPartitionKey() { return partitionKey != null; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#getPartitionKey() */ @Override public PartitionKey getPartitionKey() { return partitionKey; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#isClusteringKey() */ @Override public boolean isClusteringKey() { return clusteringKey != null; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#getClusteringKey() */ @Override public ClusteringKey getClusteringKey() { return clusteringKey; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#isTypeKey() */ @Override public boolean isTypeKey() { return typeKey != null; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#getClusteringKey() */ @Override public TypeKey getTypeKey() { return typeKey; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#isMultiKey() */ @Override public boolean isMultiKey() { return (multiKeyType != null); } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#isPersisted() */ @Override public boolean isPersisted() { return (persister != null); } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#getAnnotation(java.lang.Class) */ @Override public <A extends Annotation> A getAnnotation(Class<A> annotationClass) { return field.getAnnotation(annotationClass); } /** * Validate the provided value for this field. * * @author paouelle * * @param value the value to be validated * @throws IllegalArgumentException if the specified value is not of the * right type or is <code>null</code> when the field is mandatory */ public void validateValue(Object value) { if (value == null) { org.apache.commons.lang3.Validate.isTrue(!isMandatory(), "invalid null value for mandatory column '%s'", getColumnName()); org.apache.commons.lang3.Validate.isTrue(!isPartitionKey() && !isClusteringKey(), "invalid null value for primary key column '%s'", getColumnName()); org.apache.commons.lang3.Validate.isTrue(!isTypeKey(), "invalid null value for type key column '%s'", getColumnName()); } if (isColumn()) { if (value != null) { if (!((definition.getType() == DataType.BLOB) && !isPersisted() // persisted columns will be serialized later ? byte[].class : type).isInstance(value)) { if (isMultiKey()) { // in such case, the value can also be an element of the set if (!multiKeyType.isInstance(value)) { throw new IllegalArgumentException("invalid value for column '" + getColumnName() + "'; expecting class '" + multiKeyType.getName() + "' or '" + type.getName() + "' but found '" + value.getClass().getName() + "'"); } } else { throw new IllegalArgumentException( "invalid value for column '" + getColumnName() + "'; expecting class '" + type.getName() + "' but found '" + value.getClass().getName() + "'"); } } } } else { org.apache.commons.lang3.Validate.isTrue(type.isInstance(value), "invalid value for suffix '%s'; expecting class '%s' but found '%s'", this.getSuffixKey().name(), type.getName(), (value != null) ? value.getClass().getName() : "null"); } } /** * Validate the provided element value for this collection field. * * @author paouelle * * @param value the element value to be validated * @param type the collection data type of the column to validate * @throws IllegalArgumentException if the specified value is not of the * right type or is <code>null</code> when the field is mandatory */ public void validateCollectionValue(CQLDataType type, Object value) { if (persister != null) { // will be persisted anyway so no need to check return; } final CQLDataType dtype = definition.getType(); org.apache.commons.lang3.Validate.isTrue(dtype.isCollection(), "column '%s' is not a collection", getColumnName()); org.apache.commons.lang3.Validate.isTrue(type.equals(dtype), "column '%s' is not a %s", getColumnName(), type.name()); if (value == null) { org.apache.commons.lang3.Validate.isTrue(!isPartitionKey() && !isClusteringKey(), "invalid null element value for primary key column '%s'", getColumnName()); org.apache.commons.lang3.Validate.isTrue(!isTypeKey(), "invalid null element value for type key column '%s'", getColumnName()); } final CQLDataType etype = definition.getElementType(); org.apache.commons.lang3.Validate.isTrue(DataTypeImpl.isInstance(etype, value), "invalid element value for column '%s'; expecting type '%s': %s", getColumnName(), etype.name(), value); } /** * Validate the provided mapping key/value for this map field. * * @author paouelle * * @param key the mapping key to be validated * @param value the mapping value to be validated * @throws IllegalArgumentException if the specified key/value are not * of the right mapping types or the value is <code>null</code> * when the column is mandatory */ public void validateMapKeyValue(Object key, Object value) { validateCollectionValue(DataType.MAP, value); final CQLDataType ktype = definition.getArgumentTypes().get(0); // #0 is the data type for the key org.apache.commons.lang3.Validate.isTrue(DataTypeImpl.isInstance(ktype, key), "invalid element key for column '%s'; expecting type '%s': %s", getColumnName(), ktype.name(), key); } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.info.FieldInfo#isFinal() */ @Override public boolean isFinal() { return isFinal; } /** * Gets the final value for the field if it is defined as final. * * @author paouelle * * @return the final value for the field if defined as final; <code>null</code> * otherwise */ public Object getFinalValue() { return finalValue; } /** * Retrieves the field's value from the specified POJO. * * @author paouelle * * @param object the POJO from which to retrieve the field's value * @return the POJO's field value * @throws NullPointerException if <code>object</code> is <code>null</code> * @throws ColumnPersistenceException if unable to persist the field's value */ public Object getValue(T object) { return getValue(Object.class, object); } /** * Retrieves the non-encoded field's value from the specified POJO. * * @author paouelle * * @param object the POJO from which to retrieve the field's value * @return the POJO's field non-encoded value * @throws NullPointerException if <code>object</code> is <code>null</code> */ public Object getNonEncodedValue(T object) { return getNonEncodedValue(Object.class, object); } /** * Retrieves the field's value from the specified POJO. * * @author paouelle * * @param clazz the class for the expected value * @param object the POJO from which to retrieve the field's value * @return the POJO's field value * @throws NullPointerException if <code>object</code> is <code>null</code> * @throws ClassCastException if the field value from the given object * cannot be type casted to the specified class * @throws ColumnPersistenceException if unable to persist the field's value */ public Object getValue(Class<?> clazz, T object) { return encodeValue(getNonEncodedValue(clazz, object)); } /** * Retrieves the field's non-encoded value from the specified POJO. * * @author paouelle * * @param clazz the class for the expected value * @param object the POJO from which to retrieve the field's value * @return the POJO's field non-encoded value * @throws NullPointerException if <code>object</code> is <code>null</code> * @throws ClassCastException if the field value from the given object * cannot be type casted to the specified class */ public Object getNonEncodedValue(Class<?> clazz, T object) { org.apache.commons.lang3.Validate.notNull(object, "invalid null object"); Object val; try { if (getter != null) { val = getter.invoke(object); } else { // get it from field directly val = field.get(object); } if (val instanceof Optional) { val = ((Optional<?>) val).orElse(null); } val = clazz.cast(val); } catch (IllegalAccessException e) { // should not happen throw new IllegalStateException(declaringClass.getName(), e); } catch (InvocationTargetException e) { final Throwable t = e.getTargetException(); if (t instanceof Error) { throw (Error) t; } else if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { // we don't expect any of those throw new IllegalStateException(declaringClass.getName(), t); } } if (isTypeKey()) { // this is the type key final String type; // the value is fixed by the schema so get it from the type class info if (cinfo instanceof TypeClassInfoImpl) { type = ((TypeClassInfoImpl<T>) cinfo).getType(); } else { // must be a RootClassInfoImpl type = ((RootClassInfoImpl<T>) cinfo).getType(cinfo.getObjectClass()).getType(); } if (!type.equals(val)) { // force value to the type and re-update in pojo setValue(object, type); val = clazz.cast(type); } } return val; } /** * Encodes the specified value based on any configured persister. * * @author paouelle * * @param val the value to be encoded * @return the corresponding encoded value or <code>val</code> if no encoding * was required * @throws ColumnPersistenceException if unable to persist the field's value */ public Object encodeValue(Object val) { if (persister != null) { // must encode it using the persister final String fname = declaringClass.getName() + "." + name; try { val = definition.encode(val, persisted, persister, fname); } catch (IOException e) { throw new ColumnPersistenceException(declaringClass, name, "failed to encode field '" + fname + "' to " + persisted.as().CQL + " with persister: " + persister.getClass().getName(), e); } } return val; } /** * Encodes the specified value as an element based on any configured persister. * * @author paouelle * * @param val the value to be encoded * @return the corresponding encoded value or <code>val</code> if no encoding * was required * @throws ColumnPersistenceException if unable to persist the field's value */ public Object encodeElementValue(Object val) { if (persister != null) { // must encode it using the persister final String fname = declaringClass.getName() + "." + name; try { val = definition.encodeElement(val, persisted, persister, fname); } catch (IOException e) { throw new ColumnPersistenceException(declaringClass, name, "failed to encode field '" + fname + "' to " + persisted.as().CQL + " with persister: " + persister.getClass().getName(), e); } } return val; } /** * Sets the field's value in the specified POJO with the given value. * * @author paouelle * * @param object the POJO in which to set the field's value * @param value the value to set the field with * @throws NullPointerException if <code>object</code> is <code>null</code> * or if the column is a primary key or mandatory and * <code>value</code> is <code>null</code> */ public void setValue(T object, Object value) { org.apache.commons.lang3.Validate.notNull(object, "invalid null object"); // nothing to update if field was declared final and no setter provided // this is in case a field defined as a collection is final but a setter // method is provided to replace the content of the collection instead // of the whole field if (isFinal && (setter == null)) { return; } if (Optional.class.isAssignableFrom(field.getType())) { value = Optional.ofNullable(value); } if (value == null) { org.apache.commons.lang3.Validate.isTrue(!isMandatory(), "invalid null value for mandatory column '%s'", getColumnName()); org.apache.commons.lang3.Validate.isTrue(!isPartitionKey() && !isClusteringKey(), "invalid null value for primary key column '%s'", getColumnName()); org.apache.commons.lang3.Validate.isTrue(!isTypeKey(), "invalid null value for type key column '%s'", getColumnName()); } try { if (setter != null) { setter.invoke(object, value); } else { // set it in field directly field.set(object, value); } } catch (IllegalAccessException e) { // should not happen throw new IllegalStateException(e); } catch (InvocationTargetException e) { final Throwable t = e.getTargetException(); if (t instanceof Error) { throw (Error) t; } else if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { // we don't expect any of those throw new IllegalStateException(t); } } } /** * Decodes the field's value based on the given row. * * @author paouelle * * @param row the row where the column encoded value is defined * @return the decoded value for this field from the given row * @throws NullPointerException if <code>row</code> is <code>null</code> * @throws ObjectConversionException if unable to decode the column or if the * column is not defined in the given row */ @SuppressWarnings({ "rawtypes", "unchecked" }) public Object decodeValue(Row row) { org.apache.commons.lang3.Validate.notNull(row, "invalid null row"); // check if the column is defined in the row if (!row.getColumnDefinitions().contains(getColumnName())) { if (isPartitionKey()) { throw new ObjectConversionException(clazz, row, "missing partition key '" + getColumnName() + "' from result set for field '" + declaringClass.getName() + "." + name + "'"); } if (isClusteringKey()) { throw new ObjectConversionException(clazz, row, "missing clustering key '" + getColumnName() + "' from result set for field '" + declaringClass.getName() + "." + name + "'"); } if (isTypeKey()) { throw new ObjectConversionException(clazz, row, "missing type key '" + getColumnName() + "' from result set for field '" + declaringClass.getName() + "." + name + "'"); } if (isMandatory()) { throw new ObjectConversionException(clazz, row, "missing mandatory column '" + getColumnName() + "' from result set for field '" + declaringClass.getName() + "." + name + "'"); } throw new ObjectConversionException(clazz, row, "missing column '" + getColumnName() + "' from result set for field '" + declaringClass.getName() + "." + name + "'"); } Object val; try { // if we have a persister then we need to decode to persisted.as() first val = decoder.decode(row, getColumnName(), (persister != null) ? (Class) persisted.as().CLASS : (Class) this.type); } catch (IllegalArgumentException | InvalidTypeException e) { throw new ObjectConversionException(clazz, row, "unable to decode value for field '" + declaringClass.getName() + "." + name + "'", e); } if (persister != null) { // must decode it using the persister final String fname = declaringClass.getName() + "." + name; try { val = definition.decode(val, persisted, persister, fname); } catch (Exception e) { throw new ObjectConversionException(clazz, row, "unable to decode persisted " + persisted.as().CQL + " for field '" + fname + "' with persister: " + persister.getClass().getName(), e); } } return val; } /** * Decodes and sets the field's value in the specified POJO based on the given * row. * * @author paouelle * * @param object the POJO in which to set the field's decoded value * @param row the row where the column encoded value is defined * @throws NullPointerException if <code>object</code> or <code>row</code> is * <code>null</code> * @throws ObjectConversionException if unable to decode the column and store * the corresponding value into the POJO object or if the column is * a primary key, type key, or mandatory and not defined in the given * row */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void decodeAndSetValue(T object, Row row) { org.apache.commons.lang3.Validate.notNull(object, "invalid null object"); org.apache.commons.lang3.Validate.notNull(row, "invalid null row"); // check if the column is defined in the row if (!row.getColumnDefinitions().contains(getColumnName())) { if (isPartitionKey()) { throw new ObjectConversionException(clazz, row, "missing partition key '" + getColumnName() + "' from result set for field '" + declaringClass.getName() + "." + name + "'"); } if (isClusteringKey()) { throw new ObjectConversionException(clazz, row, "missing clustering key '" + getColumnName() + "' from result set for field '" + declaringClass.getName() + "." + name + "'"); } if (isTypeKey()) { throw new ObjectConversionException(clazz, row, "missing type key '" + getColumnName() + "' from result set for field '" + declaringClass.getName() + "." + name + "'"); } if (isMandatory()) { throw new ObjectConversionException(clazz, row, "missing mandatory column '" + getColumnName() + "' from result set for field '" + declaringClass.getName() + "." + name + "'"); } // not defined in the row so skip it return; } Object val; try { // if we have a persister then we need to decode to persisted.as() first val = decoder.decode(row, getColumnName(), (persister != null) ? (Class) persisted.as().CLASS : (Class) this.type); } catch (IllegalArgumentException | InvalidTypeException e) { throw new ObjectConversionException(clazz, row, "unable to decode value for field '" + declaringClass.getName() + "." + name + "'", e); } if (persister != null) { // must decode it using the persister final String fname = declaringClass.getName() + "." + name; try { val = definition.decode(val, persisted, persister, fname); } catch (Exception e) { throw new ObjectConversionException(clazz, row, "unable to decode persisted " + persisted.as().CQL + " for field '" + fname + "' with persister: " + persister.getClass().getName(), e); } } try { setValue(object, val); } catch (NullPointerException | IllegalArgumentException e) { throw new ObjectConversionException(clazz, row, "unable to set field '" + declaringClass.getName() + "." + name + "' with: " + val, e); } } /** * Decodes and sets the field's value in the specified POJO based on the given * UDT value. * * @author paouelle * * @param object the POJO in which to set the field's decoded value * @param uval the UDT value where the column encoded value is defined * @throws NullPointerException if <code>object</code> or <code>uval</code> is * <code>null</code> * @throws ObjectConversionException if unable to decode the column and store * the corresponding value into the POJO object or if the column is * mandatory and not defined in the given UDT value */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void decodeAndSetValue(T object, UDTValue uval) { org.apache.commons.lang3.Validate.notNull(object, "invalid null object"); org.apache.commons.lang3.Validate.notNull(uval, "invalid null UDT value"); // check if the column is defined in the row if (!uval.getType().contains(getColumnName())) { if (isMandatory()) { throw new ObjectConversionException(clazz, uval, "missing mandatory column '" + getColumnName() + "' from UDT value for field '" + declaringClass.getName() + "." + name + "'"); } // not defined in the row so skip it return; } Object val; try { // if we have a persister then we need to decode to persisted.as() first val = decoder.decode(uval, getColumnName(), (persister != null) ? (Class) persisted.as().CLASS : (Class) this.type); } catch (IllegalArgumentException | InvalidTypeException e) { throw new ObjectConversionException(clazz, uval, "unable to decode value for field '" + declaringClass.getName() + "." + name + "'", e); } if (persister != null) { // must decode it using the persister final String fname = declaringClass.getName() + "." + name; try { val = definition.decode(val, persisted, persister, fname); } catch (Exception e) { throw new ObjectConversionException(clazz, uval, "unable to decode persisted " + persisted.as().CQL + " for field '" + fname + "' with persister: " + persister.getClass().getName(), e); } } try { setValue(object, val); } catch (NullPointerException | IllegalArgumentException e) { throw new ObjectConversionException(clazz, uval, "unable to set field '" + declaringClass.getName() + "." + name + "' with: " + val, e); } } }