/*
* 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.nifi.remote;
import java.io.IOException;
import java.util.Map;
import org.apache.nifi.remote.protocol.DataPacket;
/**
* <p>
* Provides a transaction for performing site-to-site data transfers.
* </p>
*
* <p>
* A Transaction is created by calling the
* {@link org.apache.nifi.remote.client.SiteToSiteClient#createTransaction(TransferDirection) createTransaction(TransferDirection)}
* method of a
* {@link org.apache.nifi.remote.client.SiteToSiteClient SiteToSiteClient}. The
* resulting Transaction can be used to either send or receive data but not
* both. A new Transaction must be created in order perform the other operation.
* </p>
*
* <p>
* The general flow of execute of a Transaction is as follows:
* <ol>
* <li>Create the transaction as described above.</li>
* <li>Send data via the {@link #send(DataPacket)} method or receive data via
* the {@link #receive()} method. This method will be called 1 or more times. In
* the case of receive, this method should be called until the method returns
* {@code null}, signifying that the remote instance is finished sending data.
* <b>Note:</b> <code>receive()</code> should not be called a second time
* without first fully consuming the stream from the previous Packet that was
* received.</li>
* <li>Confirm the transaction via the {@link #confirm()} method.</li>
* <li>Either complete the transaction via the {@link #complete(boolean)} method
* or cancel the transaction via the {@link #cancel()} method.</li>
* </ol>
* </p>
*
* <p>
* It is important that the Transaction be terminated in order to free the
* resources held by the Transaction. If a Transaction is not terminated, its
* resources will not be freed and if the Transaction holds connections from a
* connection pool, the connections in that pool will eventually become
* exhausted. A Transaction is terminated by calling one of the following
* methods:
* <ul>
* <li>{@link #complete(boolean)}</li>
* <li>{@link #cancel()}</li>
* <li>{@link #error()}</li>
* </ul>
* </p>
*
* <p>
* If at any point an IOException is thrown from one of the methods of the
* Transaction, that Transaction is automatically closed via a call to
* {@link #error()}.
* </p>
*
* <p>
* The Transaction class should not be assumed to be thread-safe.
* </p>
*/
public interface Transaction {
/**
* Sends information to the remote NiFi instance.
*
* @param dataPacket the data packet to send
* @throws IOException if unable to send
*/
void send(DataPacket dataPacket) throws IOException;
/**
* Sends the given byte array as the content of a {@link DataPacket} along
* with the provided attributes
*
* @param content to send
* @param attributes of the content
* @throws IOException if unable to send
*/
void send(byte[] content, Map<String, String> attributes) throws IOException;
/**
* Retrieves information from the remote NiFi instance, if any is available.
* If no data is available, will return {@code null}. It is important to
* consume all data from the remote NiFi instance before attempting to call
* {@link #confirm()}. This is because the sender is always responsible for
* determining when the Transaction has finished. This is done in order to
* prevent the need for a round-trip network request to receive data for
* each data packet.
*
* @return the DataPacket received, or {@code null} if there is no more data
* to receive.
* @throws IOException if unable to receive
*/
DataPacket receive() throws IOException;
/**
* <p>
* Confirms the data that was sent or received by comparing CRC32's of the
* data sent and the data received.
* </p>
*
* <p>
* Even if the protocol being used to send the data is reliable and
* guarantees ordering of packets (such as TCP), it is still required that
* we confirm the transaction before completing the transaction. This is
* done as "safety net" or a defensive programming technique. Mistakes
* happen, and this mechanism helps to ensure that if a bug exists somewhere
* along the line that we do not end up sending or receiving corrupt data.
* If the CRC32 of the sender and the CRC32 of the receiver do not match, an
* IOException will be thrown and both the sender and receiver will cancel
* the transaction automatically.
* </p>
*
* <p>
* If the {@link TransferDirection} of this Transaction is RECEIVE, this
* method will throw an Exception unless all data from the remote instance
* has been consumed (i.e., a call to {@link #receive()} returns
* {@code null}).
* </p>
*
* <p>
* If the {@link TransferDirection} of this Transaction is SEND, calling
* this method dictates that no more data will be sent in this transaction.
* I.e., there will be no more calls to {@link #send(DataPacket)}.
* </p>
*
* @throws IOException if unable to confirm transaction
*/
void confirm() throws IOException;
/**
* <p>
* Completes the transaction and indicates to both the sender and receiver
* that the data transfer was successful.
* </p>
*
* @throws IOException if unable to complete
*
* @return a TransactionCompletion that contains details about the
* Transaction
*/
TransactionCompletion complete() throws IOException;
/**
* <p>
* Cancels this transaction, indicating to the sender that the data has not
* been successfully received so that the sender can retry or handle however
* is appropriate.
* </p>
*
* @param explanation an explanation to tell the other party why the
* transaction was canceled.
* @throws IOException if unable to cancel
*/
void cancel(final String explanation) throws IOException;
/**
* <p>
* Sets the TransactionState of the Transaction to
* {@link TransactionState#ERROR}, and closes the Transaction. The
* underlying connection should not be returned to a connection pool in this
* case.
* </p>
*/
void error();
/**
* @return the current state of the Transaction.
* @throws IOException ioe
*/
TransactionState getState() throws IOException;
/**
* @return a Communicant that represents the other side of this Transaction
* (i.e., the remote NiFi instance)
*/
Communicant getCommunicant();
public enum TransactionState {
/**
* Transaction has been started but no data has been sent or received.
*/
TRANSACTION_STARTED,
/**
* Transaction has been started and data has been sent or received.
*/
DATA_EXCHANGED,
/**
* Data that has been transferred has been confirmed via its CRC.
* Transaction is ready to be completed.
*/
TRANSACTION_CONFIRMED,
/**
* Transaction has been successfully completed.
*/
TRANSACTION_COMPLETED,
/**
* The Transaction has been canceled.
*/
TRANSACTION_CANCELED,
/**
* The Transaction ended in an error.
*/
ERROR;
}
}