| /* |
| * 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 java.io.Serializable; |
| import java.lang.annotation.Annotation; |
| import java.util.List; |
| import com.datastax.driver.core.DataType; |
| import com.datastax.driver.core.Row; |
| import org.apache.ignite.cache.query.annotations.QuerySqlField; |
| 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; |
| |
| /** Field column name in Cassandra table. */ |
| private String col; |
| |
| /** Field column DDL. */ |
| private String colDDL; |
| |
| /** Indicator for calculated field. */ |
| private Boolean calculated; |
| |
| /** Field property accessor. */ |
| private transient PojoFieldAccessor accessor; |
| |
| /** |
| * Checks if list contains POJO field with the specified name. |
| * |
| * @param fields list of POJO fields. |
| * @param fieldName field name. |
| * @return true if list contains field or false otherwise. |
| */ |
| public static boolean containsField(List<? extends PojoField> fields, String fieldName) { |
| if (fields == null || fields.isEmpty()) |
| return false; |
| |
| for (PojoField field : fields) { |
| if (field.getName().equals(fieldName)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * 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.getPojoFieldAccessor(pojoCls, name)); |
| } |
| |
| /** |
| * Creates instance of {@link PojoField} from its field accessor. |
| * |
| * @param accessor field accessor. |
| */ |
| public PojoField(PojoFieldAccessor accessor) { |
| this.name = accessor.getName(); |
| |
| QuerySqlField sqlField = (QuerySqlField)accessor.getAnnotation(QuerySqlField.class); |
| |
| col = sqlField != null && sqlField.name() != null && !sqlField.name().isEmpty() ? |
| sqlField.name() : name.toLowerCase(); |
| |
| init(accessor); |
| } |
| |
| /** |
| * Creates instance of {@link PojoField} from the other instance |
| * and java class. |
| */ |
| public PojoField(PojoField field, Class<?> pojoCls) { |
| this.name = field.name; |
| this.col = field.col; |
| this.colDDL = field.colDDL; |
| |
| init(PropertyMappingHelper.getPojoFieldAccessor(pojoCls, name)); |
| } |
| |
| /** |
| * @return field name. |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * Returns java class of the field. |
| * |
| * @return Java class. |
| */ |
| public Class getJavaClass() { |
| return accessor.getFieldType(); |
| } |
| |
| /** |
| * @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 = accessor.isReadOnly(); |
| } |
| |
| /** |
| * 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) { |
| Object val = accessor.getValue(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); |
| } |
| |
| /** |
| * Returns POJO field annotation. |
| * |
| * @return annotation. |
| */ |
| public Annotation getAnnotation(Class clazz) { |
| return accessor.getAnnotation(clazz); |
| } |
| |
| /** |
| * 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, accessor.getFieldType(), serializer); |
| |
| accessor.setValue(obj, val); |
| } |
| |
| /** |
| * Initializes field info from property descriptor. |
| * |
| * @param accessor {@link PojoFieldAccessor} accessor. |
| */ |
| private void init(PojoFieldAccessor accessor) { |
| DataType.Name cassandraType = PropertyMappingHelper.getCassandraType(accessor.getFieldType()); |
| cassandraType = cassandraType == null ? DataType.Name.BLOB : cassandraType; |
| |
| this.colDDL = "\"" + col + "\" " + cassandraType.toString(); |
| |
| this.accessor = accessor; |
| } |
| } |