/*
 * 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;
    }
}
