/** * 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.hive.hcatalog.api.repl; import org.apache.hadoop.hive.conf.HiveConf; import org.apache.hadoop.hive.metastore.IMetaStoreClient; import org.apache.hadoop.hive.metastore.api.NotificationEvent; import org.apache.hive.hcatalog.api.HCatClient; import org.apache.hive.hcatalog.api.HCatNotificationEvent; import org.apache.hive.hcatalog.common.HCatException; import java.util.Iterator; import java.util.List; public class HCatReplicationTaskIterator implements Iterator<ReplicationTask>{ private class HCatReplicationTaskIteratorNotificationFilter implements IMetaStoreClient.NotificationFilter { private String dbName; private String tableName; public HCatReplicationTaskIteratorNotificationFilter(String dbName, String tableName){ this.dbName = dbName; this.tableName = tableName; } @Override public boolean accept(NotificationEvent event) { if (event == null){ return false; // get rid of trivial case first, so that we can safely assume non-null } if (this.dbName == null){ return true; // if our dbName is null, we're interested in all wh events } if (this.dbName.equalsIgnoreCase(event.getDbName())){ if ( (this.tableName == null) // if our dbName is equal, but tableName is blank, we're interested in this db-level event || (this.tableName.equalsIgnoreCase(event.getTableName())) // table level event that matches us ){ return true; } } return false; } } private HCatClient hcatClient; private IMetaStoreClient.NotificationFilter filter; private int maxEvents; private int batchSize; private Iterator<HCatNotificationEvent> batchIter = null; private List<HCatNotificationEvent> batch = null; private long pos; private long maxPos; private int eventCount; public HCatReplicationTaskIterator( HCatClient hcatClient, long eventFrom, int maxEvents, String dbName, String tableName) throws HCatException { init(hcatClient,eventFrom,maxEvents, new HCatReplicationTaskIteratorNotificationFilter(dbName,tableName)); // using init(..) instead of this(..) because the new HCatReplicationTaskIteratorNotificationFilter // is an operation that needs to run before delegating to the other ctor, and this messes up chaining // ctors } public HCatReplicationTaskIterator( HCatClient hcatClient, long eventFrom, int maxEvents, IMetaStoreClient.NotificationFilter filter) throws HCatException{ init(hcatClient,eventFrom,maxEvents,filter); } private void init( HCatClient hcatClient, long eventFrom, int maxEvents, IMetaStoreClient.NotificationFilter filter) throws HCatException { // Simple implementation for now, this will later expand to do DAG evaluation. this.hcatClient = hcatClient; this.filter = filter; this.pos = eventFrom; if (maxEvents < 1){ // 0 or -1 implies fetch everything this.maxEvents = Integer.MAX_VALUE; } else { this.maxEvents = maxEvents; } batchSize = Integer.parseInt( hcatClient.getConfVal(HiveConf.ConfVars.METASTORE_BATCH_RETRIEVE_MAX.varname,"50")); this.eventCount = 0; this.maxPos = hcatClient.getCurrentNotificationEventId(); } private void fetchNextBatch() throws HCatException { batch = hcatClient.getNextNotification(pos, batchSize, filter); batchIter = batch.iterator(); if (batch.isEmpty()){ pos += batchSize; if (pos < maxPos){ fetchNextBatch(); // This way, the only way the recursive stack of fetchNextBatch returns is if: // a) We got a nonempty result, and we can consume // b) We reached the end of the queue, and there are no more events. // So, when we return from the fetchNextBatch() stack, if we have no more // results in batch, we're done. } } } @Override public boolean hasNext() { if (eventCount >= maxEvents){ // If we've already satisfied the number of events we were supposed to deliver, we end it. return false; } if ((batchIter != null) && (batchIter.hasNext())){ // If we have a valid batchIter and it has more elements, return them. return true; } // If we're here, we want more events, and either batchIter is null, or batchIter // has reached the end of the current batch. Let's fetch the next batch. try { fetchNextBatch(); } catch (HCatException e) { // Regrettable that we have to wrap the HCatException into a RuntimeException, // but throwing the exception is the appropriate result here, and hasNext() // signature will only allow RuntimeExceptions. Iterator.hasNext() really // should have allowed IOExceptions throw new RuntimeException(e); } // New batch has been fetched. If it's not empty, we have more elements to process. return !batch.isEmpty(); } @Override public ReplicationTask next() { eventCount++; HCatNotificationEvent ev = batchIter.next(); pos = ev.getEventId(); return ReplicationTask.create(hcatClient,ev); } @Override public void remove() { throw new UnsupportedOperationException("remove() not supported on HCatReplicationTaskIterator"); } }