/*
 * 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.openjpa.kernel;

import java.util.BitSet;
import java.util.Collection;
import java.util.List;

import org.apache.openjpa.lib.rop.ResultObjectProvider;
import org.apache.openjpa.lib.util.Closeable;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.ValueStrategies;

/**
 * Interface to be implemented by data store mechanisms to interact with
 * this runtime.
 *
 * @author Abe White
 */
public interface StoreManager
    extends Closeable {

    int VERSION_LATER = 1;
    int VERSION_EARLIER = 2;
    int VERSION_SAME = 3;
    int VERSION_DIFFERENT = 4;

    int FORCE_LOAD_NONE = 0;
    int FORCE_LOAD_DFG = 1;
    int FORCE_LOAD_REFRESH = 3;
    int FORCE_LOAD_ALL = 2;

    /**
     * Set a reference to the corresponding context. This method
     * will be called before the store manager is used. The store manager
     * is responsible for pulling any necessary configuration data from the
     * context, including the transaction mode and connection retain mode.
     */
    void setContext(StoreContext ctx);

    /**
     * Notification that an optimistic transaction has started. This method
     * does not replace the {@link #begin} method, which will still be called
     * when a true data store transaction should begin.
     */
    void beginOptimistic();

    /**
     * Notification that an optimistic transaction was rolled back before
     * a data store transaction ever began.
     */
    void rollbackOptimistic();

    /**
     * Begin a data store transaction. After this method is called,
     * it is assumed that all further operations are operating in a single
     * transaction that can be committed or rolled back. If optimistic
     * transactions are in use, this method will only be called when the
     * system requires a transactionally consistent connection due to a
     * user request to flush or commit the transaction. In this case, it
     * is possible that the optimistic transaction does not have the latest
     * versions of all instances (i.e. another transaction has modified the
     * same instances and committed since the optimistic transaction started).
     * On commit, an exception must be thrown on any attempt to overwrite
     * data for an instance with an older version.
     *
     * @since 0.2.5
     */
    void begin();

    /**
     * Commit the current data store transaction.
     */
    void commit();

    /**
     * Rollback the current data store transaction.
     */
    void rollback();

    /**
     * Verify that the given instance exists in the data store; return false
     * if it does not.
     */
    boolean exists(OpenJPAStateManager sm, Object edata);

    /**
     * Verify that the given instance exists in the data store in memory; return false
     * if it does not. When an object is found in memory the corresponding element of
     * the BitSet is set to 1.
     */
    boolean isCached(List<Object> oids, BitSet edata);

    /**
     * Update the version information in the given state manager to the
     * version stored in the data store.
     *
     * @param sm the instance to check
     * @param edata the current execution data, or null if not
     * given to the calling method of the context
     * @return true if the instance still exists in the
     * datastore and is up-to-date, false otherwise
     */
    boolean syncVersion(OpenJPAStateManager sm, Object edata);

    /**
     * Initialize the given state manager. The object id of the
     * state manager will be set, and the state manager's metadata be set to
     * the class of the instance to load, or possibly one of its superclasses.
     * Initialization involves first calling the
     * {@link OpenJPAStateManager#initialize} method with
     * a new instance of the correct type constructed with the
     * {@link org.apache.openjpa.enhance.PCRegistry#newInstance(Class,
     * org.apache.openjpa.enhance.StateManager, boolean)} method
     * (this will reset the state manager's metadata if the actual type was a
     * subclass). After instance initialization, load any the fields for the
     * given fetch configuration that can be efficiently retrieved. If any of
     * the configured fields are not loaded in this method, they will be
     * loaded with a subsequent call to {@link #load}. If this method is
     * called during a data store transaction, the instance's database record
     * should be locked. Version information can be loaded if desired through
     * the {@link OpenJPAStateManager#setVersion} method.
     *
     * @param sm the instance to initialize
     * @param state the lifecycle state to initialize the state manager with
     * @param fetch configuration for how to load the instance
     * @param edata the current execution data, or null if not
     * given to the calling method of the broker
     * @return true if the matching instance exists in the data
     * store, false otherwise
     */
    boolean initialize(OpenJPAStateManager sm, PCState state,
        FetchConfiguration fetch, Object edata);

    /**
     * Load the given state manager.
     * Note that any collection or map types loaded into the state manager
     * will be proxied with the correct type; therefore the store manager
     * does not have to load the same concrete collection/map types as the
     * instance declares. However, array types must be consistent with the
     * array type stored by the persistence capable instance. If this method
     * is called during a data store transaction, the instance should be
     * locked. If the given state manager does not have its version set
     * already, version information can be loaded if desired through the
     * {@link OpenJPAStateManager#setVersion} method.
     *
     * @param sm the instance to load
     * @param fields set of fields to load; all field indexes in this
     * set must be loaded; this set is mutable
     * @param fetch the fetch configuration to use when loading
     * related objects
     * @param lockLevel attempt to load simple fields at this lock level;
     * relations should be loaded at the read lock level
     * of the fetch configuration
     * @param edata the current execution data, or null if not
     * given to the calling method of the broker
     * @return false if the object no longer exists in the
     * database, true otherwise
     */
    boolean load(OpenJPAStateManager sm, BitSet fields,
        FetchConfiguration fetch, int lockLevel, Object edata);

    /**
     * Initialize, load, or validate the existance of all of the given
     * objects. This method is called from various broker methods that act
     * on multiple objects, such as {@link StoreContext#retrieveAll}. It gives
     * the store manager an opportunity to efficiently batch-load data for
     * several objects. Each of the given state managers will be in one of
     * three states, each requiring a different action:
     * <ul>
     * <li><code>stateManager.getO () == null</code>: An
     * uninitialized state manager. Perform the same actions as in
     * {@link #initialize}.
     * <li><code>load != FORCE_LOAD_NONE || stateManager.getPCState ()
     * == PCState.HOLLOW</code>: A hollow state manager, or one whose
     * fields must be loaded because this is a refresh or retrieve action.
     * Peform the same actions as in {@link #load}, choosing the fields
     * to load based on the fetch configuration, or loading all fields
     * if <code>load == FORCE_LOAD_ALL</code>. Any required fields left
     * unloaded will cause a subsequent invocation of {@link #load} on
     * the individual object in question.</li>
     * <li><code>load == FORCE_LOAD_NONE &amp;&amp;
     * stateManager.getPCState () != PCState.HOLLOW</code>: A non-hollow
     * state manager. Perform the same actions as in {@link #exists},
     * and load additional state if desired. Non-hollow objects will only
     * be included outside of refresh invocations if a user calls
     * <code>findAll</code> with the <code>validate</code>
     * parameter set to <code>true</code>.</li>
     * </ul>
     * Store managers that cannot efficiently batch load can simply test
     * for these conditions and delegate to the proper methods.
     *
     * @param sms the state manager instances to load
     * @param state the lifecycle state to initialize uninitialized
     * state managers with; may be null if no uninitialized
     * instances are included in <code>sms</code>
     * @param load one of the FORCE_LOAD_* constants describing the
     * fields to force-load if this is a refresh or retrieve action
     * @param fetch the current fetch configuration to use when loading
     * related objects
     * @param edata the current execution data, or null if not
     * given to the calling method of the broker
     * @return a collection of the state manager identities for
     * which no data store record exists
     * @see org.apache.openjpa.util.ImplHelper#loadAll
     */
    Collection<Object> loadAll(Collection<OpenJPAStateManager> sms, PCState state, int load,
        FetchConfiguration fetch, Object edata);

    /**
     * Notification that the given state manager is about to change its
     * lifecycle state. The store manager is not required to do anything in
     * this method, but some back ends may need to.
     *
     * @since 0.3.0
     */
    void beforeStateChange(OpenJPAStateManager sm, PCState fromState,
        PCState toState);

    /**
     * Flush the given state manager collection to the datastore, returning
     * a collection of exceptions encountered during flushing.
     * The given collection may include states that do not require data
     * store action, such as persistent-clean instances or persistent-dirty
     * instances that have not been modified since they were last flushed.
     * For datastore updates and inserts, the dirty, non-flushed fields of
     * each state should be flushed. New instances without an assigned object
     * id should be given one via {@link OpenJPAStateManager#setObjectId}. New
     * instances with value-strategy fields that have not been assigned yet
     * should have their fields set. Datastore version information should be
     * updated during flush, and the state manager's version indicator
     * updated through the {@link OpenJPAStateManager#setNextVersion} method.
     * The current version will roll over to this next version upon successful
     * commit.
     *
     * @see org.apache.openjpa.util.ApplicationIds#assign()
     */
    Collection<Exception> flush(Collection<OpenJPAStateManager> sms);

    /**
     * Assign an object id to the given new instance. Return false if the
     * instance cannot be assigned an identity because a flush is required
     * (for example, the identity is determined by the datastore on insert).
     * For application identity instances, the assigned object id should be
     * based on field state. The implementation is responsible for using the
     * proper value strategy according to the instance metadata. This method
     * is called the first time a user requests the oid of a new instance
     * before flush.
     *
     * @param preFlush whether this assignment is being requested by the
     * system as part of pre-flush activities, and can
     * be ignored if it is more efficient to assign within {@link #flush}
     * @see org.apache.openjpa.util.ImplHelper#generateFieldValue
     * @see org.apache.openjpa.util.ImplHelper#generateIdentityValue
     * @see org.apache.openjpa.util.ApplicationIds#assign()
     * @since 0.3.3
     */
    boolean assignObjectId(OpenJPAStateManager sm, boolean preFlush);

    /**
     * Assign a value to the given field. Return false if the value cannot
     * be assigned because a flush is required (for example, the field value
     * is determined by the datastore on insert). This method is called the
     * first time a user requests the value of a field with a value-strategy
     * on a new instance before flush.
     *
     * @param preFlush whether this assignment is being requested by the
     * system as part of pre-flush activities, and can
     * be ignored if it is more efficient to assign within {@link #flush}
     * @see org.apache.openjpa.util.ImplHelper#generateFieldValue
     * @since 0.4.0
     */
    boolean assignField(OpenJPAStateManager sm, int field,
        boolean preFlush);

    /**
     * Return the persistent class for the given data store identity value.
     * If the given value is not a datastore identity object, return null.
     *
     * @since 0.3.0
     */
    Class<?> getManagedType(Object oid);

    /**
     * Return the class used by this StoreManager for datastore identity
     * values. The given metadata may be null, in which case the return
     * value should the common datastore identity class for all classes, or
     * null if this store manager does not use a common identity class.
     */
    Class<?> getDataStoreIdType(ClassMetaData meta);

    /**
     * Copy the given object id value. Use the described type of the given
     * metadata, which may be a subclass of the given oid's described type.
     */
    Object copyDataStoreId(Object oid, ClassMetaData meta);

    /**
     * Create a new unique datastore identity for the given type from
     * the given oid value (presumably pk, stringified oid, or oid instance).
     */
    Object newDataStoreId(Object oidVal, ClassMetaData meta);

    /**
     * Return a connection to the data store suitable for client use. If
     * this method is called during a data store transaction, thie connection
     * must be transactional. If no connection is in use, this method should
     * create one to return.
     */
    Object getClientConnection();

    /**
     * Instruct the store to retain a connection for continued use. This
     * will be invoked automatically based on the user's configured connection
     * retain mode.
     */
    void retainConnection();

    /**
     * Instruct the store to release a retained connection. This
     * will be invoked automatically based on the user's configured connection
     * retain mode.
     */
    void releaseConnection();

    /**
     * Cancel all pending data store statements.
     *
     * @return true if any statements cancelled, false otherwise
     * @since 0.3.1
     */
    boolean cancelAll();

    /**
     * Return a provider for all instances of the given candidate class,
     * optionally including subclasses. The given candidate may be an
     * unmapped type with mapped subclasses. If the provider is iterated
     * within a data store transaction, returned instances should be locked.
     */
    ResultObjectProvider executeExtent(ClassMetaData meta,
        boolean subclasses, FetchConfiguration fetch);

    /**
     * Return a query implementation suitable for this store. If the query
     * is iterated within a data store transaction, returned instances should
     * be locked. Return null if this store does not support native execution
     * of the given language. OpenJPA can execute JPQL in memory even without
     * back end support.
     *
     * @param language the query language
     */
    StoreQuery newQuery(String language);

    /**
     * Return a fetch configuration suitable for this runtime. Typically
     * will be or extend <code>FetchConfigurationImpl</code>.
     */
    FetchConfiguration newFetchConfiguration();

    /**
     * Compare the two version objects.
     *
     * @param state the state manager for the object
     * @param v1 the first version object to compare
     * @param v2 the second version object to compare
     * @return <ul>
     * <li>{@link #VERSION_LATER} if <code>v1</code>
     * is later than <code>v2</code></li>
     * <li>{@link #VERSION_EARLIER} if <code>v1</code>
     * is earlier than <code>v2</code></li>
     * <li>{@link #VERSION_SAME} if <code>v1</code>
     * is the same as <code>v2</code></li>
     * <li>{@link #VERSION_DIFFERENT} if <code>v1</code>
     * is different from <code>v2</code>, but the time
     * difference of the versions cannot be determined</li>
     * </ul>
     */
    int compareVersion(OpenJPAStateManager state, Object v1, Object v2);

    /**
     * Return a sequence that generates datastore identity values for the
     * given class. This method will only be called when the identity strategy
     * for the class is one of:
     * <ul>
     * <li>{@link ValueStrategies#NATIVE}</li>
     * <li>{@link ValueStrategies#AUTOASSIGN}</li>
     * <li>{@link ValueStrategies#INCREMENT}</li>
     * </ul>
     * If the identity strategy cannot be represented as a sequence, return
     * null.
     *
     * @since 0.4.0
     */
    Seq getDataStoreIdSequence(ClassMetaData forClass);

    /**
     * Return a sequence that generates values for the given field. This
     * method will only be called when the value strategy for the field
     * is one of:
     * <ul>
     * <li>{@link ValueStrategies#NATIVE}</li>
     * <li>{@link ValueStrategies#AUTOASSIGN}</li>
     * <li>{@link ValueStrategies#INCREMENT}</li>
     * </ul> If the value strategy cannot be represented as a sequence, return
     * null.
     *
     * @since 0.4.0
     */
    Seq getValueSequence(FieldMetaData forField);

    /**
     * Free any resources this store manager is using.
     *
     * @since 0.2.5
     */
    @Override void close ();
}
