/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.ignite.cache.store.cassandra.persistence;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.Row;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import org.apache.ignite.IgniteException;
import org.apache.ignite.cache.store.cassandra.common.PropertyMappingHelper;
import org.apache.ignite.cache.store.cassandra.serializer.Serializer;
import org.w3c.dom.Element;
/**
* Descriptor for particular field in a POJO object, specifying how this field
* should be written to or loaded from Cassandra.
*/
public abstract class PojoField implements Serializable {
/** Name attribute of XML element describing Pojo field. */
private static final String NAME_ATTR = "name";
/** Column attribute of XML element describing Pojo field. */
private static final String COLUMN_ATTR = "column";
/** Field name. */
private String name;
/** Java class to which the field belongs. */
private Class objJavaCls;
/** Field column name in Cassandra table. */
private String col;
/** Field column DDL. */
private String colDDL;
/** Indicator for calculated field. */
private Boolean calculated;
/** Field property descriptor. */
private transient PropertyDescriptor desc;
/**
* Creates instance of {@link PojoField} based on it's description in XML element.
*
* @param el XML element describing Pojo field
* @param pojoCls Pojo java class.
*/
public PojoField(Element el, Class<?> pojoCls) {
if (el == null)
throw new IllegalArgumentException("DOM element representing POJO field object can't be null");
if (!el.hasAttribute(NAME_ATTR)) {
throw new IllegalArgumentException("DOM element representing POJO field object should have '"
+ NAME_ATTR + "' attribute");
}
this.name = el.getAttribute(NAME_ATTR).trim();
this.col = el.hasAttribute(COLUMN_ATTR) ? el.getAttribute(COLUMN_ATTR).trim() : name.toLowerCase();
init(PropertyMappingHelper.getPojoPropertyDescriptor(pojoCls, name));
}
/**
* Creates instance of {@link PojoField} from its property descriptor.
*
* @param desc Field property descriptor.
*/
public PojoField(PropertyDescriptor desc) {
this.name = desc.getName();
col = name.toLowerCase();
init(desc);
}
/**
* @return field name.
*/
public String getName() {
return name;
}
/**
* Returns java class of the field.
*
* @return Java class.
*/
public Class getJavaClass() {
return propDesc().getPropertyType();
}
/**
* @return Cassandra table column name.
*/
public String getColumn() {
return col;
}
/**
* @return Cassandra table column DDL statement.
*/
public String getColumnDDL() {
return colDDL;
}
/**
* Indicates if it's a calculated field - field which value just generated based on other field values.
* Such field will be stored in Cassandra as all other POJO fields, but it's value shouldn't be read from
* Cassandra - cause it's again just generated based on other field values. One of the good applications of such
* kind of fields - Cassandra materialized views build on top of other tables.
*
* @return {@code true} if it's auto generated field, {@code false} if not.
*/
public boolean calculatedField() {
if (calculated != null)
return calculated;
return calculated = propDesc().getWriteMethod() == null;
}
/**
* Gets field value as an object having Cassandra compatible type.
* This it could be stored directly into Cassandra without any conversions.
*
* @param obj Object instance.
* @param serializer {@link org.apache.ignite.cache.store.cassandra.serializer.Serializer} to use.
* @return Object to store in Cassandra table column.
*/
public Object getValueFromObject(Object obj, Serializer serializer) {
try {
Object val = propDesc().getReadMethod().invoke(obj);
if (val == null)
return null;
DataType.Name cassandraType = PropertyMappingHelper.getCassandraType(val.getClass());
if (cassandraType != null)
return val;
if (serializer == null) {
throw new IllegalStateException("Can't serialize value from object '" +
val.getClass().getName() + "' field '" + name + "', cause there is no BLOB serializer specified");
}
return serializer.serialize(val);
}
catch (Throwable e) {
throw new IgniteException("Failed to get value of the field '" + name + "' from the instance " +
" of '" + obj.getClass().toString() + "' class", e);
}
}
/**
* Sets object field value from a {@link com.datastax.driver.core.Row} returned by Cassandra CQL statement.
*
* @param row {@link com.datastax.driver.core.Row}
* @param obj object which field should be populated from {@link com.datastax.driver.core.Row}
* @param serializer {@link org.apache.ignite.cache.store.cassandra.serializer.Serializer} to use.
*/
public void setValueFromRow(Row row, Object obj, Serializer serializer) {
if (calculatedField())
return;
Object val = PropertyMappingHelper.getCassandraColumnValue(row, col, propDesc().getPropertyType(), serializer);
try {
propDesc().getWriteMethod().invoke(obj, val);
}
catch (Throwable e) {
throw new IgniteException("Failed to set value of the field '" + name + "' of the instance " +
" of '" + obj.getClass().toString() + "' class", e);
}
}
/**
* Initializes field info from property descriptor.
*
* @param desc {@link PropertyDescriptor} descriptor.
*/
protected void init(PropertyDescriptor desc) {
if (desc.getReadMethod() == null) {
throw new IllegalArgumentException("Field '" + desc.getName() +
"' of the class instance '" + desc.getPropertyType().getName() +
"' doesn't provide getter method");
}
if (!desc.getReadMethod().isAccessible())
desc.getReadMethod().setAccessible(true);
if (desc.getWriteMethod() != null && !desc.getWriteMethod().isAccessible())
desc.getWriteMethod().setAccessible(true);
DataType.Name cassandraType = PropertyMappingHelper.getCassandraType(desc.getPropertyType());
cassandraType = cassandraType == null ? DataType.Name.BLOB : cassandraType;
this.objJavaCls = desc.getReadMethod().getDeclaringClass();
this.desc = desc;
this.colDDL = "\"" + col + "\" " + cassandraType.toString();
}
/**
* Returns property descriptor of the POJO field
*
* @return Property descriptor
*/
private PropertyDescriptor propDesc() {
return desc != null ? desc : (desc = PropertyMappingHelper.getPojoPropertyDescriptor(objJavaCls, name));
}
}