/*
* Copyright 2014, Stratio.
*
* 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.stratio.deep.aerospike.utils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.stratio.deep.commons.utils.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.aerospike.client.Record;
import com.aerospike.hadoop.mapreduce.AerospikeKey;
import com.aerospike.hadoop.mapreduce.AerospikeRecord;
import com.stratio.deep.aerospike.config.AerospikeDeepJobConfig;
import com.stratio.deep.commons.entity.Cell;
import com.stratio.deep.commons.entity.Cells;
import com.stratio.deep.commons.exception.DeepGenericException;
import com.stratio.deep.commons.utils.AnnotationUtils;
import com.stratio.deep.commons.utils.Utils;
import scala.Tuple2;
/**
* Several utilities to work used in the Spark <=> Aerospike integration.
*/
final public class UtilAerospike {
private static final Logger LOG = LoggerFactory.getLogger(UtilAerospike.class);
/**
* Private default constructor.
*/
private UtilAerospike() {
throw new UnsupportedOperationException();
}
/**
* Converts from AerospikeRecord to an entity class with deep's anotations.
*
* @param classEntity the entity name.
* @param aerospikeRecord the instance of the AerospikeRecord to convert.
* @param aerospikeConfig Aerospike configuration object.
* @param <T> return type.
* @return the provided aerospikeRecord converted to an instance of T.
* @throws IllegalAccessException
* @throws InstantiationException
* @throws java.lang.reflect.InvocationTargetException
*/
public static <T> T getObjectFromAerospikeRecord(Class<T> classEntity, AerospikeRecord aerospikeRecord,
AerospikeDeepJobConfig aerospikeConfig)
throws IllegalAccessException, InstantiationException,
InvocationTargetException {
Tuple2<String, Object> equalsFilter = aerospikeConfig.getEqualsFilter();
String equalsFilterBin = equalsFilter != null ? equalsFilter._1() : null;
Object equalsFilterValue = equalsFilter != null ? equalsFilter._2() : null;
Map<String, Object> bins = aerospikeRecord.bins;
T t = classEntity.newInstance();
if (equalsFilter == null || checkEqualityFilter(bins, equalsFilterBin, equalsFilterValue)) {
Field[] fields = AnnotationUtils.filterDeepFields(classEntity);
Object insert = null;
List<String> inputColumns = null;
if (aerospikeConfig.getInputColumns() != null) {
inputColumns = Arrays.asList(aerospikeConfig.getInputColumns());
}
for (Field field : fields) {
if (inputColumns != null && !inputColumns.contains(AnnotationUtils.deepFieldName(field))) {
continue;
}
Object currentBin = null;
Method method = null;
Class<?> classField = field.getType();
try {
method = Utils.findSetter(field.getName(), classEntity, field.getType());
currentBin = bins.get(AnnotationUtils.deepFieldName(field));
if (currentBin != null) {
if (currentBin instanceof Integer && classField.equals(Long.class)) {
currentBin = new Long((Integer) currentBin);
}
if (currentBin instanceof String || currentBin instanceof Integer
|| currentBin instanceof Long) {
insert = currentBin;
} else {
throw new DeepGenericException("Data type [" + classField.toString()
+ "] not supported in Aerospike entity extractor (only Strings and Integers)");
}
method.invoke(t, insert);
}
} catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException e) {
LOG.error("impossible to create a java object from Bin:" + field.getName() + " and type:"
+ field.getType() + " and value:" + t + "; recordReceived:" + currentBin);
method.invoke(t, Utils.castNumberType(insert, classField));
}
}
}
return t;
}
/**
* Converts from an entity class with deep's anotations to AerospikeRecord.
*
* @param t an instance of an object of type T to convert to AerospikeRecord.
* @param <T> the type of the object to convert.
* @return A pair with the Record key and the Record itself.
* @throws IllegalAccessException
* @throws InstantiationException
* @throws InvocationTargetException
*/
public static <T> Pair<Object, AerospikeRecord> getAerospikeRecordFromObject(T t) throws IllegalAccessException, InstantiationException,
InvocationTargetException {
Field[] fields = AnnotationUtils.filterDeepFields(t.getClass());
Pair<Field[], Field[]> keysAndFields = AnnotationUtils.filterKeyFields(t.getClass());
Field[] keys = keysAndFields.left;
Object key;
Map<String, Object> bins = new HashMap<>();
if(keys.length == 0) {
throw new InvocationTargetException(new Exception("One key field must be defined."));
} else if(keys.length > 1) {
throw new InvocationTargetException(new Exception("Aerospike only supports one key field"));
} else {
Field keyField = keys[0];
Method method = Utils.findGetter(keyField.getName(), t.getClass());
key = method.invoke(t);
}
for (Field field : fields) {
Method method = Utils.findGetter(field.getName(), t.getClass());
Object object = method.invoke(t);
if (object != null) {
bins.put(AnnotationUtils.deepFieldName(field), object);
}
}
Record record = new Record(bins, 0, 0);
AerospikeRecord aerospikeRecord = new AerospikeRecord(record);
Pair<Object, AerospikeRecord> result = Pair.create(key, aerospikeRecord);
return result;
}
/**
* Converts from AerospikeRecord to cell class with deep's anotations.
*
* @param aerospikeRecord
* @param key
* @param aerospikeConfig
* @return
* @throws IllegalAccessException
* @throws InstantiationException
* @throws InvocationTargetException
*/
public static Cells getCellFromAerospikeRecord(AerospikeKey key, AerospikeRecord aerospikeRecord,
AerospikeDeepJobConfig aerospikeConfig) throws IllegalAccessException,
InstantiationException, InvocationTargetException {
String namespace = aerospikeConfig.getNamespace() + "." + aerospikeConfig.getSet();
String setName = aerospikeConfig.getSet();
String[] inputColumns = aerospikeConfig.getInputColumns();
Tuple2<String, Object> equalsFilter = aerospikeConfig.getEqualsFilter();
String equalsFilterBin = equalsFilter != null ? equalsFilter._1() : null;
Object equalsFilterValue = equalsFilter != null ? equalsFilter._2() : null;
Cells cells = namespace != null ? new Cells(namespace) : new Cells();
Map<String, Object> map = aerospikeRecord.bins;
if (inputColumns != null) {
if (equalsFilter == null || checkEqualityFilter(map, equalsFilterBin, equalsFilterValue)) {
for (int i = 0; i < inputColumns.length; i++) {
String binName = inputColumns[i];
if (map.containsKey(binName)) {
Cell cell = Cell.create(binName, map.get(binName));
if(i == 0) {
cell.setIsClusterKey(true);
cell.setIsKey(true);
}
cells.add(namespace, cell);
} else {
throw new InvocationTargetException(new Exception("There is no [" + binName
+ "] on aerospike [" + namespace + "." + setName + "] set"));
}
}
}
} else {
if (equalsFilter == null || checkEqualityFilter(map, equalsFilterBin, equalsFilterValue)) {
int index = 0;
for (Map.Entry<String, Object> bin : map.entrySet()) {
Cell cell = Cell.create(bin.getKey(), bin.getValue());
if(index == 0) {
cell.setIsClusterKey(true);
cell.setIsKey(true);
}
cells.add(namespace, cell);
index ++;
}
}
}
return cells;
}
private static boolean checkEqualityFilter(Map<String, Object> bins, String binName, Object expectedBinValue) {
Object currentBinValue = bins.get(binName);
if (currentBinValue instanceof Integer && expectedBinValue instanceof Long) {
currentBinValue = new Long((Integer) currentBinValue);
}
return bins.containsKey(binName) && currentBinValue.equals(expectedBinValue);
}
/**
* Converts from and entity class with deep's anotations to AerospikeRecord.
*
* @param cells
* @return
* @throws IllegalAccessException
* @throws InstantiationException
* @throws InvocationTargetException
*/
public static Pair<Object, AerospikeRecord> getAerospikeRecordFromCell(Cells cells) throws IllegalAccessException, InstantiationException,
InvocationTargetException {
Map<String, Object> bins = new HashMap<>();
Object key = null;
for (Cell cell : cells.getCells()) {
if(key == null) {
if(cell.isKey()) {
key = cell.getValue();
}
} else {
if(cell.isKey()) {
throw new InvocationTargetException(new Exception("Aerospike records must have only one key"));
}
}
bins.put(cell.getCellName(), cell.getValue());
}
if(key == null) {
throw new InvocationTargetException(new Exception("Aerospike records must have one primary key"));
}
// Expiration time = 0, defaults to namespace configuration ("default-ttl")
Record record = new Record(bins, 0, 0);
return Pair.create(key, new AerospikeRecord(record));
}
}