/*
 * 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.components.state;

import java.io.IOException;
import java.util.Map;
import org.apache.nifi.annotation.behavior.Stateful;
import org.apache.nifi.components.state.exception.StateTooLargeException;

/**
 * <p>
 * The StateManager is responsible for providing NiFi components a mechanism for storing
 * and retrieving state.
 * </p>
 *
 * <p>
 * When calling methods in this class, the {@link Scope} is used in order to specify whether
 * state should be stored/retrieved from the local state or the clustered state. However, if
 * any instance of NiFi is not clustered (or is disconnected from its cluster), the Scope is
 * not really relevant and the local state will be used. This allows component
 * developers to not concern themselves with whether or not a particular instance of NiFi is
 * clustered. Instead, developers should assume that the instance is indeed clustered and write
 * the component accordingly. If not clustered, the component will still behave in the same
 * manner, as a standalone node could be thought of as a "cluster of 1."
 * </p>
 *
 * <p>
 * This mechanism is designed to allow developers to easily store and retrieve small amounts of state.
 * The storage mechanism is implementation-specific, but is typically either stored on a remote system
 * or on disk. For that reason, one should consider the cost of storing and retrieving this data, and the
 * amount of data should be kept to the minimum required.
 * </p>
 *
 * <p>
 * Any component that wishes to use the StateManager should also use the {@link Stateful} annotation to provide
 * a description of what state is being stored and what Scope is used. If this annotation is not present, the UI
 * will not expose such information or allow DFMs to clear the state.
 * </p>
 */
public interface StateManager {

    /**
     * Updates the value of the component's state, setting it to given value
     *
     * @param state the value to change the state to
     * @param scope the scope to use when storing the state
     *
     * @throws StateTooLargeException if attempting to store more state than is allowed by the backing storage mechanism
     * @throws IOException if unable to communicate with the underlying storage mechanism
     */
    void setState(Map<String, String> state, Scope scope) throws IOException;

    /**
     * Returns the current state for the component. This return value will never be <code>null</code>.
     * If the state has not yet been set, the StateMap's version will be -1, and the map of values will be empty.
     *
     * @param scope the scope to use when fetching the state
     * @return the current state for the component
     * @throws IOException if unable to communicate with the underlying storage mechanism
     */
    StateMap getState(Scope scope) throws IOException;

    /**
     * Updates the value of the component's state to the new value if and only if the value currently
     * is the same as the given oldValue.
     *
     * @param oldValue the old value to compare against
     * @param newValue the new value to use if and only if the state's value is the same as the given oldValue
     * @param scope the scope to use for storing the new state
     * @return <code>true</code> if the state was updated to the new value, <code>false</code> if the state's value was not
     *         equal to oldValue
     *
     * @throws StateTooLargeException if attempting to store more state than is allowed by the backing storage mechanism
     * @throws IOException if unable to communicate with the underlying storage mechanism
     */
    boolean replace(StateMap oldValue, Map<String, String> newValue, Scope scope) throws IOException;

    /**
     * Clears all keys and values from the component's state
     *
     * @param scope the scope whose values should be cleared
     *
     * @throws IOException if unable to communicate with the underlying storage mechanism
     */
    void clear(Scope scope) throws IOException;
}
