/*
 * 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.Collection;
import java.util.Map;
import java.util.Set;

/**
 * <p>
 * The StateManager is responsible for providing NiFi components a mechanism for storing
 * and retrieving "simple" state. Simple, in this case, refers to the notion that state is
 * stored only as key/value pairs, both of which are String values. If more complex state is
 * needed, components are encourage to "encode" their state using a structured data format,
 * such as JSON or XML and marshal and unmarshal the 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 in all cases. 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 behavior in the same
 * manner, as a standalone node could be thought of as a "cluster of 1."
 * </p>
 */
public interface StateManager {

    /**
     * Updates the value of the component's state for the given key, setting it to the
     * given <code>value</code>.
     *
     * @param key the key of the state to update
     * @param value the value to change the state to
     * @param scope the scope to use when storing the state
     * @param persistence the level of persistence for this key
     *
     * @throws IOException if unable to communicate with the underlying storage mechanism
     */
    void set(String key, String value, Scope scope) throws IOException;

    /**
     * Returns <code>true</code> if a value is set for the given key and scope, <code>false</code> otherwise.
     *
     * @param key the key to check
     * @param scope the scope to check
     * @return <code>true</code> if a value is set for the given key and scope, <code>false</code> otherwise
     * @throws IOException if unable to communicate with the underlying storage mechanism
     */
    boolean containsKey(String key, Scope scope) throws IOException;

    /**
     * Returns a Set of all Keys that exist in the State Manager for the given Scope.
     *
     * @param scope the scope from which to retrieve the keys
     *
     * @return a Set of all Keys that exist in the State Manager for the given Scope.
     * @throws IOException if unable to communicate with the underlying storage mechanism
     */
    Set<String> keySet(Scope scope) throws IOException;

    /**
     * Returns a Collection of all state values that exist in the State Manager for the given Scope
     *
     * @param scope the scope from which to retrieve values
     *
     * @return a Collection of all state values that exist in the State Manager for the given Scope
     * @throws IOException if unable to communicate with the underlying storage mechanism
     */
    Collection<String> values(Scope scope) throws IOException;

    /**
     * Returns a Map that holds the same key/value pairs as the State Manager
     *
     * @param scope the scope from which to retrieve keys and values
     * @return a Map that holds the same key/value pairs as the State Manager
     * @throws IOException if unable to communicate with the underlying storage mechanism
     */
    Map<String, String> toMap(Scope scope) throws IOException;

    /**
     * Returns the currently configured entry for the component's state for the given key, or <code>null</code> if no
     * state has been configured for the given key
     *
     * @param key the key of the state to retrieve
     * @param scope the scope from which to retrieve the state
     * @return the currently configured value for the component's state for the given key, or <code>null</code> if no
     *         state has been configured for the given key
     *
     * @throws IOException if unable to communicate with the underlying storage mechanism
     */
    StateEntry get(String key, Scope scope) throws IOException;


    /**
     * Updates the value of the component's state for the given key to the new value if and only if the value currently
     * is the same as the given oldValue. The Persistence that is used will be the same as the persistence level used when
     * the state was previously set.
     *
     * @param key the key of the state to update
     * @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 IOException if unable to communicate with the underlying storage mechanism
     */
    boolean replace(String key, StateEntry oldValue, String newValue, Scope scope) throws IOException;


    /**
     * Returns the value of the currently configured entry for the component's state for the given key, or <code>null</code> if
     * no state has been configured for the given key
     *
     * @param key the key of the state to retrieve
     * @param scope the scope from which to retrieve the state
     * @return the currently configured value for the component's state for the given key, or <code>null</code> if no
     *         state has been configured for the given key
     *
     * @throws IOException if unable to communicate with the underlying storage mechanism
     */
    String getValue(String key, Scope scope) throws IOException;


    /**
     * Removes the currently configured value for the component's state for the given key
     *
     * @param key the key to remove from the component's state
     * @param scope the scope from which the state should be removed
     * @return <code>true</code> if the value was removed from the component's state, <code>false</code> if no state existed for the
     *         component with the given key
     *
     * @throws IOException if unable to communicate with the underlying storage mechanism or other nodes in the cluster
     */
    boolean remove(String key, Scope scope) throws IOException;


    /**
     * Removes all values from the component's state that is stored using the given scope
     *
     * @param scope the scope of the state to clear
     *
     * @throws IOException if unable to communicate with the underlying storage mechanism
     */
    void clear(Scope scope) throws IOException;
}
