package mil.nga.giat.geowave.core.store.adapter;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import mil.nga.giat.geowave.core.index.ByteArrayId;
import mil.nga.giat.geowave.core.index.Persistable;
import mil.nga.giat.geowave.core.index.PersistenceUtils;
import mil.nga.giat.geowave.core.index.StringUtils;
import mil.nga.giat.geowave.core.store.adapter.NativeFieldHandler.RowBuilder;
import mil.nga.giat.geowave.core.store.data.PersistentDataset;
import mil.nga.giat.geowave.core.store.data.PersistentValue;
import mil.nga.giat.geowave.core.store.data.field.FieldUtils;
import mil.nga.giat.geowave.core.store.data.field.FieldVisibilityHandler;
import mil.nga.giat.geowave.core.store.dimension.NumericDimensionField;
import mil.nga.giat.geowave.core.store.filter.GenericTypeResolver;
import mil.nga.giat.geowave.core.store.index.CommonIndexModel;
import mil.nga.giat.geowave.core.store.index.CommonIndexValue;
import mil.nga.giat.geowave.core.store.index.PrimaryIndex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class generically supports most of the operations necessary to implement
* a Data Adapter and can be easily extended to support specific data types.<br>
* Many of the details are handled by mapping IndexFieldHandler's based on
* either types or exact dimensions. These handler mappings can be supplied in
* the constructor. The dimension matching handlers are used first when trying
* to decode a persistence encoded value. This can be done specifically to match
* a field (for example if there are multiple ways of encoding/decoding the same
* type). Otherwise the type matching handlers will simply match any field with
* the same type as its generic field type.
*
* @param <T>
* The type for the entries handled by this adapter
*/
abstract public class AbstractDataAdapter<T> implements
WritableDataAdapter<T>
{
private final static Logger LOGGER = LoggerFactory.getLogger(AbstractDataAdapter.class);
protected Map<Class<?>, IndexFieldHandler<T, ? extends CommonIndexValue, Object>> typeMatchingFieldHandlers;
protected Map<ByteArrayId, IndexFieldHandler<T, ? extends CommonIndexValue, Object>> dimensionMatchingFieldHandlers;
protected List<NativeFieldHandler<T, Object>> nativeFieldHandlers;
protected FieldVisibilityHandler<T, Object> fieldVisiblityHandler;
protected AbstractDataAdapter() {}
public AbstractDataAdapter(
final List<PersistentIndexFieldHandler<T, ? extends CommonIndexValue, Object>> indexFieldHandlers,
final List<NativeFieldHandler<T, Object>> nativeFieldHandlers,
final FieldVisibilityHandler<T, Object> fieldVisiblityHandler ) {
this(
indexFieldHandlers,
nativeFieldHandlers,
fieldVisiblityHandler,
null);
}
public AbstractDataAdapter(
final List<PersistentIndexFieldHandler<T, ? extends CommonIndexValue, Object>> indexFieldHandlers,
final List<NativeFieldHandler<T, Object>> nativeFieldHandlers ) {
this(
indexFieldHandlers,
nativeFieldHandlers,
null,
null);
}
protected AbstractDataAdapter(
final List<PersistentIndexFieldHandler<T, ? extends CommonIndexValue, Object>> indexFieldHandlers,
final List<NativeFieldHandler<T, Object>> nativeFieldHandlers,
final FieldVisibilityHandler<T, Object> fieldVisiblityHandler,
final Object defaultTypeData ) {
this.nativeFieldHandlers = nativeFieldHandlers;
this.fieldVisiblityHandler = fieldVisiblityHandler;
init(
indexFieldHandlers,
defaultTypeData);
}
protected void init(
final List<? extends IndexFieldHandler<T, ? extends CommonIndexValue, Object>> indexFieldHandlers,
final Object defaultIndexHandlerData ) {
dimensionMatchingFieldHandlers = new HashMap<ByteArrayId, IndexFieldHandler<T, ? extends CommonIndexValue, Object>>();
typeMatchingFieldHandlers = new HashMap<Class<?>, IndexFieldHandler<T, ? extends CommonIndexValue, Object>>();
// --------------------------------------------------------------------
// split out the dimension-matching index handlers from the
// type-matching index handlers
for (final IndexFieldHandler<T, ? extends CommonIndexValue, Object> indexHandler : indexFieldHandlers) {
if (indexHandler instanceof DimensionMatchingIndexFieldHandler) {
final ByteArrayId[] matchedDimensionFieldIds = ((DimensionMatchingIndexFieldHandler<T, ? extends CommonIndexValue, Object>) indexHandler)
.getSupportedIndexFieldIds();
for (final ByteArrayId matchedDimensionId : matchedDimensionFieldIds) {
dimensionMatchingFieldHandlers.put(
matchedDimensionId,
indexHandler);
}
}
else {
// alternatively we could put make the dimension-matching field
// handlers match types as a last resort rather than this else,
// but they shouldn't conflict with an existing type matching
// class
typeMatchingFieldHandlers.put(
GenericTypeResolver.resolveTypeArguments(
indexHandler.getClass(),
IndexFieldHandler.class)[1],
indexHandler);
}
}
// --------------------------------------------------------------------
// add default handlers if the type is not already within the custom
// handlers
final List<IndexFieldHandler<T, ? extends CommonIndexValue, Object>> defaultTypeMatchingHandlers = getDefaultTypeMatchingHandlers(defaultIndexHandlerData);
for (final IndexFieldHandler<T, ? extends CommonIndexValue, Object> defaultFieldHandler : defaultTypeMatchingHandlers) {
final Class<?> defaultFieldHandlerClass = GenericTypeResolver.resolveTypeArguments(
defaultFieldHandler.getClass(),
IndexFieldHandler.class)[1];
// if the type matching handlers can already handle this class,
// don't overload it, otherwise, use this as a default handler
final IndexFieldHandler<T, ? extends CommonIndexValue, Object> existingTypeHandler = FieldUtils
.getAssignableValueFromClassMap(
defaultFieldHandlerClass,
typeMatchingFieldHandlers);
if (existingTypeHandler == null) {
typeMatchingFieldHandlers.put(
defaultFieldHandlerClass,
defaultFieldHandler);
}
}
}
/**
* Returns an empty list of IndexFieldHandlers as default.
*
* @param defaultIndexHandlerData
* - object parameter
* @return Empty list of IndexFieldHandlers.
*/
protected List<IndexFieldHandler<T, ? extends CommonIndexValue, Object>> getDefaultTypeMatchingHandlers(
final Object defaultIndexHandlerData ) {
return new ArrayList<IndexFieldHandler<T, ? extends CommonIndexValue, Object>>();
}
@Override
public AdapterPersistenceEncoding encode(
final T entry,
final CommonIndexModel indexModel ) {
final PersistentDataset<CommonIndexValue> indexData = new PersistentDataset<CommonIndexValue>();
final Set<ByteArrayId> nativeFieldsInIndex = new HashSet<ByteArrayId>();
for (final NumericDimensionField<? extends CommonIndexValue> dimension : indexModel.getDimensions()) {
final IndexFieldHandler<T, ? extends CommonIndexValue, Object> fieldHandler = getFieldHandler(dimension);
if (fieldHandler == null) {
if (LOGGER.isInfoEnabled()) {
// Don't waste time converting IDs to String if "info" level
// is not enabled
LOGGER.info("Unable to find field handler for data adapter '"
+ StringUtils.stringFromBinary(getAdapterId().getBytes()) + "' and indexed field '"
+ StringUtils.stringFromBinary(dimension.getFieldId().getBytes()));
}
continue;
}
final CommonIndexValue value = fieldHandler.toIndexValue(entry);
indexData.addValue(new PersistentValue<CommonIndexValue>(
dimension.getFieldId(),
value));
nativeFieldsInIndex.addAll(Arrays.asList(fieldHandler.getNativeFieldIds()));
}
final PersistentDataset<Object> extendedData = new PersistentDataset<Object>();
// now for the other data
if (nativeFieldHandlers != null) {
for (final NativeFieldHandler<T, Object> fieldHandler : nativeFieldHandlers) {
final ByteArrayId fieldId = fieldHandler.getFieldId();
if (nativeFieldsInIndex.contains(fieldId)) {
continue;
}
extendedData.addValue(new PersistentValue<Object>(
fieldId,
fieldHandler.getFieldValue(entry)));
}
}
return new AdapterPersistenceEncoding(
getAdapterId(),
getDataId(entry),
indexData,
extendedData);
}
@SuppressWarnings("unchecked")
@Override
public T decode(
final IndexedAdapterPersistenceEncoding data,
final PrimaryIndex index ) {
final RowBuilder<T, Object> builder = newBuilder();
if (index != null) {
final CommonIndexModel indexModel = index.getIndexModel();
for (final NumericDimensionField<? extends CommonIndexValue> dimension : indexModel.getDimensions()) {
final IndexFieldHandler<T, CommonIndexValue, Object> fieldHandler = (IndexFieldHandler<T, CommonIndexValue, Object>) getFieldHandler(dimension);
if (fieldHandler == null) {
if (LOGGER.isInfoEnabled()) {
// dont waste time converting IDs to String if info is
// not
// enabled
LOGGER.info("Unable to find field handler for data adapter '"
+ StringUtils.stringFromBinary(getAdapterId().getBytes()) + "' and indexed field '"
+ StringUtils.stringFromBinary(dimension.getFieldId().getBytes()));
}
continue;
}
final CommonIndexValue value = data.getCommonData().getValue(
dimension.getFieldId());
if (value == null) {
continue;
}
final PersistentValue<Object>[] values = fieldHandler.toNativeValues(value);
if ((values != null) && (values.length > 0)) {
for (final PersistentValue<Object> v : values) {
builder.setField(v);
}
}
}
}
for (final PersistentValue<Object> fieldValue : data.getAdapterExtendedData().getValues()) {
builder.setField(fieldValue);
}
return builder.buildRow(data.getDataId());
}
abstract protected RowBuilder<T, Object> newBuilder();
/**
* Get index field handler for the provided dimension.
*
* @param dimension
* @return field handler
*/
private IndexFieldHandler<T, ? extends CommonIndexValue, Object> getFieldHandler(
final NumericDimensionField<? extends CommonIndexValue> dimension ) {
// first try explicit dimension matching
IndexFieldHandler<T, ? extends CommonIndexValue, Object> fieldHandler = dimensionMatchingFieldHandlers
.get(dimension.getFieldId());
if (fieldHandler == null) {
// if that fails, go for type matching
fieldHandler = FieldUtils.getAssignableValueFromClassMap(
GenericTypeResolver.resolveTypeArgument(
dimension.getClass(),
NumericDimensionField.class),
typeMatchingFieldHandlers);
dimensionMatchingFieldHandlers.put(
dimension.getFieldId(),
fieldHandler);
}
return fieldHandler;
}
@Override
public byte[] toBinary() {
// run through the list of field handlers and persist whatever is
// persistable, if the field handler is not persistable the assumption
// is that the data adapter can re-create it by some other means
// use a linked hashset to maintain order and ensure no duplication
final Set<Persistable> persistables = new LinkedHashSet<Persistable>();
for (final NativeFieldHandler<T, Object> nativeHandler : nativeFieldHandlers) {
if (nativeHandler instanceof Persistable) {
persistables.add((Persistable) nativeHandler);
}
}
for (final IndexFieldHandler<T, ? extends CommonIndexValue, Object> indexHandler : typeMatchingFieldHandlers
.values()) {
if (indexHandler instanceof Persistable) {
persistables.add((Persistable) indexHandler);
}
}
for (final IndexFieldHandler<T, ? extends CommonIndexValue, Object> indexHandler : dimensionMatchingFieldHandlers
.values()) {
if (indexHandler instanceof Persistable) {
persistables.add((Persistable) indexHandler);
}
}
if (fieldVisiblityHandler instanceof Persistable) {
persistables.add((Persistable) fieldVisiblityHandler);
}
final byte[] defaultTypeDataBinary = defaultTypeDataToBinary();
final byte[] persistablesBytes = PersistenceUtils.toBinary(persistables);
final ByteBuffer buf = ByteBuffer.allocate(defaultTypeDataBinary.length + persistablesBytes.length + 4);
buf.putInt(defaultTypeDataBinary.length);
buf.put(defaultTypeDataBinary);
buf.put(persistablesBytes);
return buf.array();
}
@SuppressWarnings("unchecked")
@Override
public void fromBinary(
final byte[] bytes ) {
if ((bytes == null) || (bytes.length < 4)) {
LOGGER.warn("Unable to deserialize data adapter. Binary is incomplete.");
return;
}
final List<IndexFieldHandler<T, CommonIndexValue, Object>> indexFieldHandlers = new ArrayList<IndexFieldHandler<T, CommonIndexValue, Object>>();
final List<NativeFieldHandler<T, Object>> nativeFieldHandlers = new ArrayList<NativeFieldHandler<T, Object>>();
final ByteBuffer buf = ByteBuffer.wrap(bytes);
final byte[] defaultTypeDataBinary = new byte[buf.getInt()];
Object defaultTypeData = null;
if (defaultTypeDataBinary.length > 0) {
buf.get(defaultTypeDataBinary);
defaultTypeData = defaultTypeDataFromBinary(defaultTypeDataBinary);
}
final byte[] persistablesBytes = new byte[bytes.length - defaultTypeDataBinary.length - 4];
if (persistablesBytes.length > 0) {
buf.get(persistablesBytes);
final List<Persistable> persistables = PersistenceUtils.fromBinary(persistablesBytes);
for (final Persistable persistable : persistables) {
if (persistable instanceof IndexFieldHandler) {
indexFieldHandlers.add((IndexFieldHandler<T, CommonIndexValue, Object>) persistable);
}
// in case persistable is polymorphic and multi-purpose, check
// both
// handler types and visibility handler
if (persistable instanceof NativeFieldHandler) {
nativeFieldHandlers.add((NativeFieldHandler<T, Object>) persistable);
}
if (persistable instanceof FieldVisibilityHandler) {
fieldVisiblityHandler = (FieldVisibilityHandler<T, Object>) persistable;
}
}
}
this.nativeFieldHandlers = nativeFieldHandlers;
init(
indexFieldHandlers,
defaultTypeData);
}
public FieldVisibilityHandler<T, Object> getFieldVisiblityHandler() {
return fieldVisiblityHandler;
}
protected byte[] defaultTypeDataToBinary() {
return new byte[] {};
}
protected Object defaultTypeDataFromBinary(
final byte[] bytes ) {
return null;
}
}