package mil.nga.giat.geowave.datastore.accumulo.metadata; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import org.apache.accumulo.core.client.BatchScanner; import org.apache.accumulo.core.client.IteratorSetting; import org.apache.accumulo.core.client.TableNotFoundException; import org.apache.accumulo.core.data.Key; import org.apache.accumulo.core.data.Mutation; import org.apache.accumulo.core.data.Range; import org.apache.accumulo.core.data.Value; import org.apache.accumulo.core.security.ColumnVisibility; import org.apache.hadoop.io.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.store.CloseableIterator; import mil.nga.giat.geowave.core.store.CloseableIteratorWrapper; import mil.nga.giat.geowave.core.store.base.Writer; import mil.nga.giat.geowave.core.store.metadata.AbstractGeowavePersistence; import mil.nga.giat.geowave.datastore.accumulo.AccumuloOperations; import mil.nga.giat.geowave.datastore.accumulo.IteratorConfig; import mil.nga.giat.geowave.datastore.accumulo.util.ScannerClosableWrapper; /** * This abstract class does most of the work for storing persistable objects in * Accumulo and can be easily extended for any object that needs to be * persisted. * * There is an LRU cache associated with it so staying in sync with external * updates is not practical - it assumes the objects are not updated often or at * all. The objects are stored in their own table. * * @param <T> * The type of persistable object that this stores */ abstract public class AbstractAccumuloPersistence<T extends Persistable> extends AbstractGeowavePersistence<T> { private final static Logger LOGGER = LoggerFactory.getLogger(AbstractAccumuloPersistence.class); protected final AccumuloOperations accumuloOperations; protected Text row = new Text(); // just attach iterators once per instance private boolean iteratorsAttached = false; public AbstractAccumuloPersistence( final AccumuloOperations accumuloOperations ) { super( accumuloOperations); this.accumuloOperations = accumuloOperations; } private static Text getSafeText( final String text ) { if ((text != null) && !text.isEmpty()) { return new Text( text); } else { return new Text(); } } protected ByteArrayId getSecondaryId( final Key key ) { if (key.getColumnQualifier() != null) { return new ByteArrayId( key.getColumnQualifier().getBytes()); } return null; } protected ByteArrayId getPrimaryId( final Key key ) { return new ByteArrayId( key.getRow().getBytes()); } protected ByteArrayId getPeristenceTypeName( final Key key ) { if (key.getColumnFamily() != null) { return new ByteArrayId( key.getColumnFamily().getBytes()); } return null; } @Override protected void addObject( final ByteArrayId id, final ByteArrayId secondaryId, final T object ) { addObjectToCache( id, secondaryId, object); try { final Writer writer = accumuloOperations.createWriter( getTablename(), true); synchronized (this) { if (!iteratorsAttached) { iteratorsAttached = true; final IteratorConfig[] configs = getIteratorConfig(); if ((configs != null) && (configs.length > 0)) { accumuloOperations.attachIterators( getTablename(), true, true, true, null, configs); } } } final Mutation mutation = new Mutation( new Text( id.getBytes())); final Text cf = getSafeText(getColumnFamily()); final Text cq = getSafeText(getColumnQualifier(object)); final byte[] visibility = getVisibility(object); if (visibility != null) { mutation.put( cf, cq, new ColumnVisibility( visibility), new Value( PersistenceUtils.toBinary(object))); } else { mutation.put( cf, cq, new Value( PersistenceUtils.toBinary(object))); } writer.write(mutation); try { writer.close(); } catch (final IOException e) { LOGGER.warn( "Unable to close metadata writer", e); } } catch (final TableNotFoundException e) { LOGGER.error( "Unable add object", e); } } protected IteratorConfig[] getIteratorConfig() { return null; } protected IteratorSetting[] getScanSettings() { return null; } protected CloseableIterator<T> getAllObjectsWithSecondaryId( final ByteArrayId secondaryId, final String... authorizations ) { try { final BatchScanner scanner = getScanner( null, secondaryId, authorizations); final Iterator<Entry<Key, Value>> it = scanner.iterator(); return new CloseableIteratorWrapper<T>( new ScannerClosableWrapper( scanner), new NativeIteratorWrapper( it)); } catch (final TableNotFoundException e) { LOGGER.info( "Unable to find objects, table '" + getTablename() + "' does not exist", e); } return new CloseableIterator.Empty<T>(); } @SuppressWarnings("unchecked") protected T getObject( final ByteArrayId primaryId, final ByteArrayId secondaryId, final String... authorizations ) { final Object cacheResult = getObjectFromCache( primaryId, secondaryId); if (cacheResult != null) { return (T) cacheResult; } try { final BatchScanner scanner = getScanner( primaryId, secondaryId, authorizations); try { final Iterator<Entry<Key, Value>> it = scanner.iterator(); if (!it.hasNext()) { LOGGER.warn("Object '" + getCombinedId( primaryId, secondaryId).getString() + "' not found"); return null; } final Entry<Key, Value> entry = it.next(); return entryToValue(entry); } finally { scanner.close(); } } catch (final TableNotFoundException e) { LOGGER.error( "Unable to find object '" + getCombinedId( primaryId, secondaryId).getString() + "'", e); } return null; } protected CloseableIterator<T> getObjects( final String... authorizations ) { try { final BatchScanner scanner = getFullScanner(authorizations); final Iterator<Entry<Key, Value>> it = scanner.iterator(); return new CloseableIteratorWrapper<T>( new ScannerClosableWrapper( scanner), new NativeIteratorWrapper( it)); } catch (final TableNotFoundException e) { LOGGER.info( "Unable to find objects, table '" + getTablename() + "' does not exist", e); } return new CloseableIterator.Empty<T>(); } @SuppressWarnings("unchecked") protected T entryToValue( final Entry<Key, Value> entry ) { final T result = (T) PersistenceUtils.fromBinary( entry.getValue().get(), Persistable.class); if (result != null) { addObjectToCache( getPrimaryId(result), getSecondaryId(result), result); } return result; } private BatchScanner getFullScanner( final String... authorizations ) throws TableNotFoundException { return getScanner( null, null, authorizations); } protected BatchScanner getScanner( final ByteArrayId primaryId, final ByteArrayId secondaryId, final String... authorizations ) throws TableNotFoundException { final BatchScanner scanner = accumuloOperations.createBatchScanner( getTablename(), authorizations); final IteratorSetting[] settings = getScanSettings(); if ((settings != null) && (settings.length > 0)) { for (final IteratorSetting setting : settings) { scanner.addScanIterator(setting); } } final String columnFamily = getColumnFamily(); final String columnQualifier = getColumnQualifier(secondaryId); if (columnFamily != null) { if (columnQualifier != null) { scanner.fetchColumn( new Text( columnFamily), new Text( columnQualifier)); } else { scanner.fetchColumnFamily(new Text( columnFamily)); } } final Collection<Range> ranges = new ArrayList<Range>(); if (primaryId != null) { ranges.add(new Range( new Text( primaryId.getBytes()))); } else { ranges.add(new Range()); } scanner.setRanges(ranges); return scanner; } public boolean deleteObjects( final ByteArrayId secondaryId, final String... authorizations ) { return deleteObjects( null, secondaryId, authorizations); } @Override public boolean deleteObjects( final ByteArrayId primaryId, final ByteArrayId secondaryId, final String... authorizations ) { if (primaryId != null) { return accumuloOperations.delete( getTablename(), primaryId, getColumnFamily(), getColumnQualifier(secondaryId), authorizations); } try { final BatchScanner scanner = getScanner( null, secondaryId, authorizations); final Iterator<Entry<Key, Value>> it = scanner.iterator(); try (final CloseableIterator<?> cit = new CloseableIteratorWrapper<T>( new ScannerClosableWrapper( scanner), new DeleteIteratorWrapper( it, authorizations))) { while (cit.hasNext()) { deleteObjectFromCache( getPrimaryId((T) cit.next()), secondaryId); } } catch (final IOException e) { LOGGER.error( "Unable to delete objects", e); } } catch (final TableNotFoundException e) { LOGGER.error( "Unable to find objects, table '" + getTablename() + "' does not exist", e); } return true; } protected boolean objectExists( final ByteArrayId primaryId, final ByteArrayId secondaryId ) { if (getObjectFromCache( primaryId, secondaryId) != null) { return true; } try { final BatchScanner scanner = getScanner( primaryId, secondaryId); try { final Iterator<Entry<Key, Value>> it = scanner.iterator(); if (it.hasNext()) { // may as well cache the result return (entryToValue(it.next()) != null); } else { return false; } } finally { scanner.close(); } } catch (final TableNotFoundException e) { // this is only debug, because if the table doesn't exist, its // essentially empty, but doesn't necessarily indicate an issue LOGGER.debug( "Unable to check existence of object '" + getCombinedId( primaryId, secondaryId) + "'", e); } return false; } private class DeleteIteratorWrapper implements Iterator<T> { String[] authorizations; final private Iterator<Entry<Key, Value>> it; private DeleteIteratorWrapper( final Iterator<Entry<Key, Value>> it, final String[] authorizations ) { this.it = it; this.authorizations = authorizations; } @Override public boolean hasNext() { return it.hasNext(); } @Override public T next() { final Map.Entry<Key, Value> entry = it.next(); accumuloOperations.delete( getTablename(), Arrays.asList(new ByteArrayId( entry.getKey().getRowData().getBackingArray())), entry.getKey().getColumnFamily().toString(), entry.getKey().getColumnQualifier().toString(), authorizations); return entryToValue(entry); } @Override public void remove() { it.remove(); } } private class NativeIteratorWrapper implements Iterator<T> { final private Iterator<Entry<Key, Value>> it; private NativeIteratorWrapper( final Iterator<Entry<Key, Value>> it ) { this.it = it; } @Override public boolean hasNext() { return it.hasNext(); } @Override public T next() { return entryToValue(it.next()); } @Override public void remove() { it.remove(); } } }