/* * 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.flume.channel; import org.apache.flume.Channel; import org.apache.flume.ChannelException; import org.apache.flume.Event; import org.apache.flume.Transaction; import com.google.common.base.Preconditions; /** * <p> * An implementation of basic {@link Transaction} semantics designed * to work in concert with {@link BasicChannelSemantics} to simplify * creation of robust {@link Channel} implementations. This class * ensures that each transaction implementation method is called only * while the transaction is in the correct state for that method, and * only by the thread that created the transaction. Nested calls to * <code>begin()</code> and <code>close()</code> are supported as long * as they are balanced. * </p> * <p> * Subclasses need only implement <code>doPut</code>, * <code>doTake</code>, <code>doCommit</code>, and * <code>doRollback</code>, and the developer can rest assured that * those methods are called only after transaction state preconditions * have been properly met. <code>doBegin</code> and * <code>doClose</code> may also be implemented if there is work to be * done at those points. * </p> * <p> * All InterruptedException exceptions thrown from the implementations * of the <code>doXXX</code> methods are automatically wrapped to * become ChannelExceptions, but only after restoring the interrupted * status of the thread so that any subsequent blocking method calls * will themselves throw InterruptedException rather than blocking. * The exception to this rule is <code>doTake</code>, which simply * returns null instead of wrapping and propagating the * InterruptedException, though it still first restores the * interrupted status of the thread. * </p> */ public abstract class BasicTransactionSemantics implements Transaction { private State state; private long initialThreadId; protected void doBegin() throws InterruptedException {} protected abstract void doPut(Event event) throws InterruptedException; protected abstract Event doTake() throws InterruptedException; protected abstract void doCommit() throws InterruptedException; protected abstract void doRollback() throws InterruptedException; protected void doClose() {} protected BasicTransactionSemantics() { state = State.NEW; initialThreadId = Thread.currentThread().getId(); } /** * <p> * The method to which {@link BasicChannelSemantics} delegates calls * to <code>put</code>. * </p> */ protected void put(Event event) { Preconditions.checkState(Thread.currentThread().getId() == initialThreadId, "put() called from different thread than getTransaction()!"); Preconditions.checkState(state.equals(State.OPEN), "put() called when transaction is %s!", state); Preconditions.checkArgument(event != null, "put() called with null event!"); try { doPut(event); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new ChannelException(e.toString(), e); } } /** * <p> * The method to which {@link BasicChannelSemantics} delegates calls * to <code>take</code>. * </p> */ protected Event take() { Preconditions.checkState(Thread.currentThread().getId() == initialThreadId, "take() called from different thread than getTransaction()!"); Preconditions.checkState(state.equals(State.OPEN), "take() called when transaction is %s!", state); try { return doTake(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return null; } } /** * @return the current state of the transaction */ protected State getState() { return state; } @Override public void begin() { Preconditions.checkState(Thread.currentThread().getId() == initialThreadId, "begin() called from different thread than getTransaction()!"); Preconditions.checkState(state.equals(State.NEW), "begin() called when transaction is " + state + "!"); try { doBegin(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new ChannelException(e.toString(), e); } state = State.OPEN; } @Override public void commit() { Preconditions.checkState(Thread.currentThread().getId() == initialThreadId, "commit() called from different thread than getTransaction()!"); Preconditions.checkState(state.equals(State.OPEN), "commit() called when transaction is %s!", state); try { doCommit(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new ChannelException(e.toString(), e); } state = State.COMPLETED; } @Override public void rollback() { Preconditions.checkState(Thread.currentThread().getId() == initialThreadId, "rollback() called from different thread than getTransaction()!"); Preconditions.checkState(state.equals(State.OPEN), "rollback() called when transaction is %s!", state); state = State.COMPLETED; try { doRollback(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new ChannelException(e.toString(), e); } } @Override public void close() { Preconditions.checkState(Thread.currentThread().getId() == initialThreadId, "close() called from different thread than getTransaction()!"); Preconditions.checkState( state.equals(State.NEW) || state.equals(State.COMPLETED), "close() called when transaction is %s" + " - you must either commit or rollback first", state); state = State.CLOSED; doClose(); } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("BasicTransactionSemantics: {"); builder.append(" state:").append(state); builder.append(" initialThreadId:").append(initialThreadId); builder.append(" }"); return builder.toString(); } /** * <p> * The state of the {@link Transaction} to which it belongs. * </p> * <dl> * <dt>NEW</dt> * <dd>A newly created transaction that has not yet begun.</dd> * <dt>OPEN</dt> * <dd>A transaction that is open. It is permissible to commit or rollback. * </dd> * <dt>COMPLETED</dt> * <dd>This transaction has been committed or rolled back. It is illegal to * perform any further operations beyond closing it.</dd> * <dt>CLOSED</dt> * <dd>A closed transaction. No further operations are permitted.</dd> * </dl> */ protected static enum State { NEW, OPEN, COMPLETED, CLOSED } }