/*
 * ModeShape (http://www.modeshape.org)
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * See the AUTHORS.txt file in the distribution for a full listing of 
 * individual contributors.
 *
 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
 * is licensed to you under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 * 
 * ModeShape is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.modeshape.jcr.cache;

import java.util.Set;
import org.modeshape.jcr.ExecutionContext;
import org.modeshape.jcr.api.value.DateTime;

/**
 * 
 */
public interface SessionCache extends NodeCache {

    /**
     * The context of a save operation, created during each call to {@link #save} and passed to the
     * {@link PreSave#process(MutableCachedNode, SaveContext)} invocations.
     */
    public static interface SaveContext {
        /**
         * Get the instance in time that the save is taking place.
         * 
         * @return the save timestamp; never null
         */
        DateTime getTime();

        /**
         * Get the identifier for the user that is making this save.
         * 
         * @return the user identifier; never null
         */
        String getUserId();
    }

    /**
     * The definition of a callback that can be implemented and passed to {@link SessionCache#save(SessionCache, PreSave)} and
     * {@link SessionCache#save(Set, SessionCache, PreSave)}, allowing the caller to recieve a hook where they can interrogate
     * each of the changed nodes and perform additional logic prior to the actual persisting of the changes. Note that
     * implementations are free to make additional modifications to the supplied nodes, and even create additional nodes or change
     * persistent but unchanged nodes, as long as these operations are done within the same calling thread.
     */
    public static interface PreSave {
        /**
         * Process the supplied node prior to saving the changes. This allows implementations to use the changes to automatically
         * adjust this node or other content.
         * 
         * @param modifiedOrNewNode the mutable node that was changed in this session; never null
         * @param context the context of the save operation; never null
         * @throws Exception if there is a problem during the processing
         */
        void process( MutableCachedNode modifiedOrNewNode,
                      SaveContext context ) throws Exception;

        /**
         * Process the supplied existing node prior to saving the changes but only after the entry corresponding to the key of the
         * node has been locked in Infinispan. Note that locking in Infinispan does not occur always, but only if the
         * {@link org.infinispan.transaction.LockingMode#PESSIMISTIC} flag is enabled. This method should be implemented as
         * optimal as possible and should only be needed in multi-threaded scenarios where concurrent modifications may break
         * consistency.
         * 
         * @param modifiedNode the mutable node that was changed in this session; never null
         * @param context the context of the save operation; never null
         * @param persistentNodeCache the node cache from which the persistent representation of the nodes can be obtained; never
         *        null
         * @throws Exception if there is a problem during the processing
         */
        void processAfterLocking( MutableCachedNode modifiedNode,
                                  SaveContext context,
                                  NodeCache persistentNodeCache ) throws Exception;
    }

    /**
     * Get the context for this session.
     * 
     * @return the session's context; never null
     */
    public ExecutionContext getContext();

    /**
     * Adds a [key,value] data pair for this cache's context
     * 
     * @param key the key for the context data
     * @param value the value for the context data
     */
    public void addContextData( String key,
                                String value );

    /**
     * Saves all changes made within this session.
     * 
     * @throws LockFailureException if a requested lock could not be made
     * @throws DocumentAlreadyExistsException if this session attempts to create a document that has the same key as an existing
     *         document
     * @throws DocumentNotFoundException if one of the modified documents was removed by another session
     * @throws DocumentStoreException if there is a problem storing or retrieving a document
     */
    public void save();

    /**
     * Saves all of this session's changes that were made at or below the specified path. Note that this is not terribly
     * efficient, but is done to implement the deprecated {@link javax.jcr.Item#save()}.
     * 
     * @param toBeSaved the set of keys identifying the nodes whose changes should be saved; may not be null
     * @param otherSession another session whose changes should be saved with this session's changes; may not be null
     * @param preSaveOperation the set of operations to run against the new and changed nodes prior to saving; may be null
     * @throws LockFailureException if a requested lock could not be made
     * @throws DocumentAlreadyExistsException if this session attempts to create a document that has the same key as an existing
     *         document
     * @throws DocumentNotFoundException if one of the modified documents was removed by another session
     * @throws DocumentStoreException if there is a problem storing or retrieving a document
     */
    public void save( Set<NodeKey> toBeSaved,
                      SessionCache otherSession,
                      PreSave preSaveOperation );

    /**
     * Saves all changes made within this session and the supplied session, using a single transaction for both.
     * 
     * @param otherSession another session whose changes should be saved with this session's changes; may not be null
     * @param preSaveOperation the set of operations to run against the new and changed nodes prior to saving; may be null
     * @throws LockFailureException if a requested lock could not be made
     * @throws DocumentAlreadyExistsException if this session attempts to create a document that has the same key as an existing
     *         document
     * @throws DocumentNotFoundException if one of the modified documents was removed by another session
     * @throws DocumentStoreException if there is a problem storing or retrieving a document
     */
    public void save( SessionCache otherSession,
                      PreSave preSaveOperation );

    /**
     * Determine whether this session has any transient, unsaved changes.
     * 
     * @return true if there are unsaved changes, or false otherwise
     */
    public boolean hasChanges();

    /**
     * Returns a set with the {@link NodeKey}s of the transient nodes from this cache. Please note that there may be nodes which
     * have been removed by another session.
     * 
     * @return a <code>Set</code> with the changed keys, or an empty set if
     *         {@link org.modeshape.jcr.cache.SessionCache#hasChanges()} is false. The returned set is a mutable copy of the
     *         underlying set.
     */
    public Set<NodeKey> getChangedNodeKeys();

    /**
     * Returns a set with the {@link NodeKey}s of the transient nodes from this cache which are at or below the path of the given
     * node
     * 
     * @param node a non-null {@link CachedNode} instance
     * @return a <set>Set</set> of nodekeys, or an empty set if no nodes are found
     * @throws NodeNotFoundException if any of changes registered in this cache refer to nodes that have been removed in the
     *         meantime.
     */
    public Set<NodeKey> getChangedNodeKeysAtOrBelow( CachedNode node ) throws NodeNotFoundException;

    /**
     * Returns a set with the {@link NodeKey}s of the existing nodes (persistent not transient & new) which are at and below the
     * path of the node with the given key. Note that this method will attempt to load each node
     * 
     * @param nodeKey the key of node which will be considered the root node
     * @return a <set>Set</set> of nodekeys or an empty set
     */
    public Set<NodeKey> getNodeKeysAtAndBelow( NodeKey nodeKey );

    /**
     * Clears all changes in the cache that are at or below the supplied node.
     * 
     * @param node the node at or below which all changes should be cleared; may not be null
     */
    public void clear( CachedNode node );

    /**
     * Get the cache the reflects the workspace content, without any of the transient, unsaved changes of this session.
     * 
     * @return the workspace cache; never null
     */
    public NodeCache getWorkspace();

    /**
     * Get a mutable form of the node with the supplied key. If this session already has a mutable node in its cache, that
     * existing mutable node is returned; otherwise, a new mutable node is created and added to the session's cache.
     * 
     * @param key the key for the node; may not be null
     * @return the mutable child node
     * @throws NodeNotFoundException if there is no existing node in the session cache or workspace cache
     * @throws UnsupportedOperationException if this session is marked for read-only operations
     */
    public MutableCachedNode mutable( NodeKey key ) throws NodeNotFoundException, UnsupportedOperationException;

    /**
     * Destroy the subgraph with the supplied node as the top node in the subgraph. This method should be called after the node is
     * already {@link MutableCachedNode#removeChild(SessionCache, NodeKey) removed from its parent node}.
     * 
     * @param key the key for the top node in the subgraph; may not be null
     * @throws NodeNotFoundException if there is no existing node in the session cache or workspace cache
     * @throws UnsupportedOperationException if this session is marked for read-only operations
     */
    public void destroy( NodeKey key ) throws NodeNotFoundException, UnsupportedOperationException;

    /**
     * Return whether the node with the supplied key has been removed using this session but not yet persisted.
     * 
     * @param key the for the node; may not be null
     * @return true if the node was removed in this session, or false otherwise
     */
    public boolean isDestroyed( NodeKey key );

    /**
     * Create a new node key for the current source and workspace.
     * 
     * @return a new node key; never null
     */
    public NodeKey createNodeKey();

    /**
     * Create a new node key for the current source and workspace.
     * 
     * @param identifier the unique identifier for the key; if null, a generated identifier will be used
     * @return a new node key; never null
     */
    public NodeKey createNodeKeyWithIdentifier( String identifier );

    /**
     * Returns whether this cache is readonly.
     * 
     * @return true if this cache is readonly, or false otherwise
     */
    public boolean isReadOnly();

    /**
     * Create a new node key for the current source and workspace.
     * 
     * @param sourceName the name (not key) for the source; if null, the key for the current source is used
     * @return a new node key; never null
     */
    public NodeKey createNodeKeyWithSource( String sourceName );

    /**
     * Create a new node key for the current source and workspace.
     * 
     * @param sourceName the name (not key) for the source; if null, the key for the current source is used
     * @param identifier the unique identifier for the key; if null, a generated identifier will be used
     * @return a new node key; never null
     */
    public NodeKey createNodeKey( String sourceName,
                                  String identifier );

    /**
     * Check whether this session is running within a transaction. This is commonly called by components that change persistent
     * state. Such persistent state might not be noticed by this session cache.
     */
    public void checkForTransaction();

    @Override
    public SessionCache unwrap();
}
