/* * Copyright © 2012-2014 Cask Data, Inc. * * 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 co.cask.tephra.distributed; import co.cask.tephra.InvalidTruncateTimeException; import co.cask.tephra.Transaction; import co.cask.tephra.TransactionCouldNotTakeSnapshotException; import co.cask.tephra.TransactionNotInProgressException; import co.cask.tephra.distributed.thrift.TInvalidTruncateTimeException; import co.cask.tephra.distributed.thrift.TTransactionCouldNotTakeSnapshotException; import co.cask.tephra.distributed.thrift.TTransactionNotInProgressException; import co.cask.tephra.distributed.thrift.TTransactionServer; import com.google.common.base.Function; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import org.apache.thrift.TException; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.TTransport; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.Collection; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; /** * This class is a wrapper around the thrift tx service client, it takes * Operations, converts them into thrift objects, calls the thrift * client, and converts the results back to data fabric classes. * This class also instruments the thrift calls with metrics. */ public class TransactionServiceThriftClient { private static final Function<byte[], ByteBuffer> BYTES_WRAPPER = new Function<byte[], ByteBuffer>() { @Override public ByteBuffer apply(byte[] input) { return ByteBuffer.wrap(input); } }; /** * The thrift transport layer. We need this when we close the connection. */ TTransport transport; /** * The actual thrift client. */ TTransactionServer.Client client; /** * Whether this client is valid for use. */ private final AtomicBoolean isValid = new AtomicBoolean(true); /** * Constructor from an existing, connected thrift transport. * * @param transport the thrift transport layer. It must already be connected */ public TransactionServiceThriftClient(TTransport transport) { this.transport = transport; // thrift protocol layer, we use binary because so does the service TProtocol protocol = new TBinaryProtocol(transport); // and create a thrift client this.client = new TTransactionServer.Client(protocol); } /** * close this client. may be called multiple times */ public void close() { if (this.transport.isOpen()) { this.transport.close(); } } public Transaction startLong() throws TException { try { return TransactionConverterUtils.unwrap(client.startLong()); } catch (TException e) { isValid.set(false); throw e; } } public Transaction startShort() throws TException { try { return TransactionConverterUtils.unwrap(client.startShort()); } catch (TException e) { isValid.set(false); throw e; } } public Transaction startShort(int timeout) throws TException { try { return TransactionConverterUtils.unwrap(client.startShortTimeout(timeout)); } catch (TException e) { isValid.set(false); throw e; } } public boolean canCommit(Transaction tx, Collection<byte[]> changeIds) throws TException, TransactionNotInProgressException { try { return client.canCommitTx(TransactionConverterUtils.wrap(tx), ImmutableSet.copyOf(Iterables.transform(changeIds, BYTES_WRAPPER))).isValue(); } catch (TTransactionNotInProgressException e) { throw new TransactionNotInProgressException(e.getMessage()); } catch (TException e) { isValid.set(false); throw e; } } public boolean commit(Transaction tx) throws TException, TransactionNotInProgressException { try { return client.commitTx(TransactionConverterUtils.wrap(tx)).isValue(); } catch (TTransactionNotInProgressException e) { throw new TransactionNotInProgressException(e.getMessage()); } catch (TException e) { isValid.set(false); throw e; } } public void abort(Transaction tx) throws TException { try { client.abortTx(TransactionConverterUtils.wrap(tx)); } catch (TException e) { isValid.set(false); throw e; } } public boolean invalidate(long tx) throws TException { try { return client.invalidateTx(tx); } catch (TException e) { isValid.set(false); throw e; } } public Transaction checkpoint(Transaction tx) throws TException { try { return TransactionConverterUtils.unwrap(client.checkpoint(TransactionConverterUtils.wrap(tx))); } catch (TException e) { isValid.set(false); throw e; } } public InputStream getSnapshotStream() throws TException, TransactionCouldNotTakeSnapshotException { try { ByteBuffer buffer = client.getSnapshot(); if (buffer.hasArray()) { return new ByteArrayInputStream(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); } // The ByteBuffer is not backed by array. Read the content to a new byte array and return an InputStream of that. byte[] snapshot = new byte[buffer.remaining()]; buffer.get(snapshot); return new ByteArrayInputStream(snapshot); } catch (TTransactionCouldNotTakeSnapshotException e) { throw new TransactionCouldNotTakeSnapshotException(e.getMessage()); } catch (TException e) { isValid.set(false); throw e; } } public String status() throws TException { try { return client.status(); } catch (TException e) { isValid.set(false); throw e; } } public void resetState() throws TException { try { client.resetState(); } catch (TException e) { isValid.set(false); throw e; } } public boolean truncateInvalidTx(Set<Long> invalidTxIds) throws TException { try { return client.truncateInvalidTx(invalidTxIds).isValue(); } catch (TException e) { isValid.set(false); throw e; } } public boolean truncateInvalidTxBefore(long time) throws TException, InvalidTruncateTimeException { try { return client.truncateInvalidTxBefore(time).isValue(); } catch (TInvalidTruncateTimeException e) { throw new InvalidTruncateTimeException(e.getMessage()); } catch (TException e) { isValid.set(false); throw e; } } public int getInvalidSize() throws TException { try { return client.invalidTxSize(); } catch (TException e) { isValid.set(false); throw e; } } public boolean isValid() { return isValid.get(); } }