/**
* Copyright 2011 The Apache Software Foundation
*
* 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.hadoop.hbase.index.coprocessor.regionserver;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.index.IndexSpecification;
import org.apache.hadoop.hbase.index.manager.IndexManager;
import org.apache.hadoop.hbase.index.util.ByteArrayBuilder;
import org.apache.hadoop.hbase.index.util.IndexUtils;
import org.apache.hadoop.hbase.regionserver.HRegion;
import org.apache.hadoop.hbase.regionserver.HRegionServer;
import org.apache.hadoop.hbase.regionserver.InternalScanner;
import org.apache.hadoop.hbase.regionserver.KeyValueScanner;
import org.apache.hadoop.hbase.regionserver.ScanType;
import org.apache.hadoop.hbase.regionserver.Store;
import org.apache.hadoop.hbase.regionserver.StoreScanner;
import org.apache.hadoop.hbase.util.Bytes;
public class TTLStoreScanner implements InternalScanner {
private InternalScanner delegate;
private Store store;
private long smallestReadPoint;
private long earliestTS;
private ScanType type; // (This should be scan type)
private static final Log LOG = LogFactory.getLog(TTLStoreScanner.class);
private TTLExpiryChecker ttlExpiryChecker;
private String actualTableName;
private HRegionServer rs;
private Boolean userRegionAvailable = null;
public TTLStoreScanner(Store store, long smallestReadPoint, long earliestTS, ScanType type,
List<? extends KeyValueScanner> scanners, TTLExpiryChecker ttlExpiryChecker,
String actualTableName, HRegionServer rs) throws IOException {
this.store = store;
this.smallestReadPoint = smallestReadPoint;
this.earliestTS = earliestTS;
this.type = type;
Scan scan = new Scan();
scan.setMaxVersions(store.getFamily().getMaxVersions());
delegate =
new StoreScanner(store, store.getScanInfo(), scan, scanners, type, this.smallestReadPoint,
this.earliestTS);
this.ttlExpiryChecker = ttlExpiryChecker;
this.actualTableName = actualTableName;
this.rs = rs;
}
@Override
public boolean next(List<KeyValue> results) throws IOException {
return this.next(results, 1);
}
@Override
public boolean next(List<KeyValue> result, int limit) throws IOException {
boolean next = this.delegate.next(result, limit);
// Ideally here i should get only one result(i.e) only one kv
for (Iterator<KeyValue> iterator = result.iterator(); iterator.hasNext();) {
KeyValue kv = (KeyValue) iterator.next();
byte[] indexNameInBytes = formIndexNameFromKV(kv);
// From the indexname get the TTL
IndexSpecification index =
IndexManager.getInstance().getIndex(this.actualTableName, indexNameInBytes);
HRegion hRegion = store.getHRegion();
if (this.type == ScanType.MAJOR_COMPACT) {
if (this.userRegionAvailable == null) {
this.userRegionAvailable =
isUserTableRegionAvailable(hRegion.getTableDesc().getNameAsString(), hRegion, this.rs);
}
// If index is null probably index is been dropped through drop index
// If user region not available it may be due to the reason that the user region has not yet
// opened but
// the index region has opened.
// Its better not to avoid the kv here, and write it during this current compaction.
// Anyway later compaction will avoid it. May lead to false positives but better than
// data loss
if (null == index && userRegionAvailable) {
// Remove the dropped index from the results
LOG.info("The index has been removed for the kv " + kv);
iterator.remove();
continue;
}
}
if (index != null) {
boolean ttlExpired =
this.ttlExpiryChecker.checkIfTTLExpired(index.getTTL(), kv.getTimestamp());
if (ttlExpired) {
result.clear();
LOG.info("The ttl has expired for the kv " + kv);
return false;
}
}
}
return next;
}
@Override
public void close() throws IOException {
this.delegate.close();
}
private byte[] formIndexNameFromKV(KeyValue kv) {
byte[] rowKey = kv.getRow();
// First two bytes are going to be the
ByteArrayBuilder keyBuilder = ByteArrayBuilder.allocate(rowKey.length);
// Start from 2nd offset because the first 2 bytes corresponds to the rowkeylength
keyBuilder.put(rowKey, 0, rowKey.length);
int indexOf = com.google.common.primitives.Bytes.indexOf(keyBuilder.array(), new byte[1]);
return keyBuilder.array(indexOf + 1, IndexUtils.getMaxIndexNameLength());
}
private boolean isUserTableRegionAvailable(String indexTableName, HRegion indexRegion,
HRegionServer rs) {
Collection<HRegion> userRegions = rs.getOnlineRegions(Bytes.toBytes(this.actualTableName));
for (HRegion userRegion : userRegions) {
// TODO start key check is enough? May be we can check for the
// possibility for N-1 Mapping?
if (Bytes.equals(userRegion.getStartKey(), indexRegion.getStartKey())) {
return true;
}
}
return false;
}
@Override
public boolean next(List<KeyValue> results, String metric) throws IOException {
// TODO Implement InternalScanner.next
return false;
}
@Override
public boolean next(List<KeyValue> result, int limit, String metric) throws IOException {
// TODO Implement InternalScanner.next
return false;
}
}