/*
 * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/store/StandardStore.java,v 1.23 2004/07/28 09:34:40 ib Exp $
 * $Revision: 1.23 $
 * $Date: 2004/07/28 09:34:40 $
 *
 * ====================================================================
 *
 * Copyright 1999-2002 The Apache Software Foundation 
 *
 * Licensed 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.slide.store;

import java.util.Enumeration;
import java.util.Vector;

import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.Service;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.ServiceInitializationFailedException;
import org.apache.slide.common.Uri;
import org.apache.slide.content.NodeRevisionContent;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.content.NodeRevisionNumber;
import org.apache.slide.content.RevisionAlreadyExistException;
import org.apache.slide.content.RevisionDescriptorNotFoundException;
import org.apache.slide.content.RevisionNotFoundException;
import org.apache.slide.lock.LockTokenNotFoundException;
import org.apache.slide.lock.NodeLock;
import org.apache.slide.security.NodePermission;
import org.apache.slide.structure.ObjectAlreadyExistsException;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.structure.ObjectNotFoundException;
import org.apache.slide.util.HashMapObjectCache;
import org.apache.slide.util.ObjectCache;

/**
 * Abstract implementation of a store. Handles all caching operations.
 *
 * @deprecated This store is deprecated as it has no reasonable transaction support, use ExtendedStore instead.
 * @version $Revision: 1.23 $
 */
public class StandardStore extends AbstractStore {
    
    
    // ----------------------------------------------------------- Constructors
    
    
    /**
     * Constructor. Handles caches initialization.
     */
    public StandardStore() {
        super();
        
        objectsCache = new HashMapObjectCache(1000, 10000, 0.95);
        permissionsCache = new HashMapObjectCache(1000, 10000, 0.95);
        locksCache = new HashMapObjectCache(1000, 10000, 0.95);
        descriptorsCache = new HashMapObjectCache(1000, 10000, 0.95);
        descriptorCache = new HashMapObjectCache(1000, 10000, 0.8);
    }
    
    // ----------------------------------------------------- Instance Variables
    
    
    /**
     * Nodes cache.
     */
    protected ObjectCache objectsCache;
    
    
    /**
     * Permissions cache.
     */
    protected ObjectCache permissionsCache;
    
    
    /**
     * Locks cache.
     */
    protected ObjectCache locksCache;
    
    
    /**
     * Revision descriptors cache.
     */
    protected ObjectCache descriptorsCache;
    
    
    /**
     * Revision descriptor cache.
     */
    protected ObjectCache descriptorCache;
    
    
    // ---------------------------------------------------- ServiceImpl Methods
    
    
    /**
     * Initializes descriptors store.
     *
     * @exception ServiceInitializationFailedException Throws an exception
     * if the descriptors store has already been initialized before
     */
    public void initialize(NamespaceAccessToken token)
        throws ServiceInitializationFailedException {
        super.initialize(token);
    }
    
    
    // ----------------------------------------------- DescriptorsStore Methods
    
    
    /**
     * Retrive an object from the Descriptors Store.
     *
     * @param uri Uri of the object we want to retrieve
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception ObjectNotFoundException The object to retrieve was not found
     */
    public ObjectNode retrieveObject(Uri uri)
        throws ServiceAccessException, ObjectNotFoundException {
        if (nodeStore.cacheResults()) {
            Object tempObject = objectsCache.get(uri.toString());
            if (tempObject != null) {
                return ((ObjectNode) tempObject).cloneObject();
            } else {
                ObjectNode objectNode = nodeStore.retrieveObject(uri);
                objectNode.validate(uri.toString());
                objectsCache.put(uri.toString(), objectNode.cloneObject());
                return objectNode;
            }
        } else {
            return super.retrieveObject(uri);
        }
    }
    
    
    /**
     * Store an object in the Descriptors Store.
     *
     * @param object Object to update
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception ObjectNotFoundException The object to update was not found
     */
    public void storeObject(Uri uri, ObjectNode object)
        throws ServiceAccessException, ObjectNotFoundException {
        super.storeObject(uri, object);
        if (nodeStore.cacheResults()) {
            objectsCache.put(uri.toString(), object.cloneObject());
        }
    }
    
    
    /**
     * Create a new object in the Descriptors Store.
     *
     * @param object SlideObject
     * @param uri Uri of the object we want to create
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception ObjectAlreadyExistsException An object already exists
     * at this Uri
     */
    public void createObject(Uri uri, ObjectNode object)
        throws ServiceAccessException, ObjectAlreadyExistsException {
        super.createObject(uri, object);
        if (nodeStore.cacheResults()) {
            objectsCache.put(uri.toString(), object.cloneObject());
        }
    }
    
    
    /**
     * Remove an object from the Descriptors Store.
     *
     * @param object Object to remove
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception ObjectNotFoundException The object to remove was not found
     */
    public void removeObject(Uri uri, ObjectNode object)
        throws ServiceAccessException, ObjectNotFoundException {
        super.removeObject(uri, object);
        if (nodeStore.cacheResults()) {
            objectsCache.remove(uri.toString());
        }
    }
    
    
    /**
     * Store an object permissions in the Descriptors Store.
     *
     * @param permission Permission we want to create
     * @exception ServiceAccessException Error accessing the Descriptors Store
     */
    public void grantPermission(Uri uri, NodePermission permission)
        throws ServiceAccessException {
        super.grantPermission(uri, permission);
        if (securityStore.cacheResults()) {
            NodePermission tempPermission = permission.cloneObject();
            Object value = permissionsCache.get(uri.toString());
            if (value == null) {
                // populate the cache with the existing entries
                enumeratePermissions(uri);
                // and check if the cache contains a corresponding entry now
                value = permissionsCache.get(uri.toString());
                if (value == null) {
                    // no permissions for the Uri in the cache, so create a new
                    // entry
                    Vector permissionsVector = new Vector();
                    permissionsVector.addElement(tempPermission);
                    permissionsCache.put(uri.toString(), permissionsVector);
                }
            } else {
                Vector permissionsVector = (Vector) value;
                permissionsVector.addElement(tempPermission);
            }
        }
    }
    
    
    /**
     * Store an object permissions in the Descriptors Store.
     *
     * @param permission Permission we want to create
     * @exception ServiceAccessException Error accessing the Descriptors Store
     */
    public void revokePermission(Uri uri, NodePermission permission)
        throws ServiceAccessException {
        super.revokePermission(uri, permission);
        if (securityStore.cacheResults()) {
            Object value = permissionsCache.get(uri.toString());
            Vector permissionsVector = null;
            if (value != null) {
                permissionsVector = (Vector) value;
                permissionsVector.removeElement(permission);
            }
        }
    }
    
    
    /**
     * Revoke all the permissions on the object.
     *
     * @param uri The uri of the object
     * @exception ServiceAccessException Error accessing the Descriptors Store
     */
    public void revokePermissions(Uri uri)
        throws ServiceAccessException {
        super.revokePermissions(uri);
        if (securityStore.cacheResults()) {
            Object value = permissionsCache.get(uri.toString());
            Vector permissionsVector = null;
            if (value != null) {
                permissionsVector = (Vector) value;
                permissionsVector.removeAllElements();
            }
        }
    }
    
    
    /**
     * Enumerate an object permissions.
     *
     * @param uri The uri of the object
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @return A enumeration of all 
     * {@link org.apache.slide.security.NodePermission permissions} on this object.
     */
    public Enumeration enumeratePermissions(Uri uri)
        throws ServiceAccessException {
        // TODO : The vectors elements MUST be cloned for non chached Results
        if (securityStore.cacheResults()) {
            Object value = permissionsCache.get(uri.toString());
            Vector permissionsVector = null;
            if (value != null) {
                permissionsVector = (Vector) value;
            } else {
                permissionsVector = new Vector();
                Enumeration tempEnum = securityStore.enumeratePermissions(uri);
                while (tempEnum.hasMoreElements()) {
                    NodePermission tempPermission =
                        (NodePermission) tempEnum.nextElement();
                    tempPermission.validate(uri.toString());
                    permissionsVector.addElement(tempPermission);
                }
                permissionsCache.put(uri.toString(), permissionsVector);
            }
            return ((Vector) permissionsVector.clone()).elements();
        } else {
            return super.enumeratePermissions(uri);
        }
    }
    
    
    /**
     * Puts a lock on a subject.
     *
     * @param lock Lock token
     * @exception ServiceAccessException Service access error
     */
    public void putLock(Uri uri, NodeLock lock)
        throws ServiceAccessException {
        super.putLock(uri, lock);
        if (lockStore.cacheResults()) {
            Object value = locksCache.get(uri.toString());
            Vector locksVector = null;
            if (value == null) {
                locksVector = new Vector();
                locksCache.put(uri.toString(), locksVector);
            } else {
                locksVector = (Vector) value;
            }
            locksVector.addElement(lock.cloneObject());
        }
    }
    
    
    /**
     * Renews a lock.
     *
     * @param lock Token to renew
     * @exception ServiceAccessException Service access error
     * @exception LockTokenNotFoundException Lock token was not found
     */
    public void renewLock(Uri uri, NodeLock lock)
        throws ServiceAccessException, LockTokenNotFoundException {
        super.renewLock(uri, lock);
        if (lockStore.cacheResults()) {
            Object value = locksCache.get(uri.toString());
            Vector locksVector = null;
            if (value != null) {
                locksVector = (Vector) value;
                boolean wasPresent = locksVector.removeElement(lock);
                if (!wasPresent) {
                    throw new LockTokenNotFoundException(lock);
                }
                locksVector.addElement(lock.cloneObject());
            }
        }
    }
    
    
    /**
     * Removes (cancels) a lock.
     *
     * @param lock Token to remove
     * @exception ServiceAccessException Service access error
     * @exception LockTokenNotFoundException Lock token was not found
     */
    public void removeLock(Uri uri, NodeLock lock)
        throws ServiceAccessException, LockTokenNotFoundException {
        super.removeLock(uri, lock);
        if (lockStore.cacheResults()) {
            Object value = locksCache.get(uri.toString());
            Vector locksVector = null;
            if (value != null) {
                locksVector = (Vector) value;
                boolean wasPresent = locksVector.removeElement(lock);
                if (!wasPresent) {
                    throw new LockTokenNotFoundException(lock);
                }
            }
        }
    }
    
    
    /**
     * Kills a lock.
     *
     * @param lock Token to remove
     * @exception ServiceAccessException Service access error
     * @exception LockTokenNotFoundException Lock token was not found
     */
    public void killLock(Uri uri, NodeLock lock)
        throws ServiceAccessException, LockTokenNotFoundException {
        super.killLock(uri, lock);
        if (lockStore.cacheResults()) {
            Object value = locksCache.get(uri.toString());
            Vector locksVector = null;
            if (value != null) {
                locksVector = (Vector) value;
                boolean wasPresent = locksVector.removeElement(lock);
                if (!wasPresent) {
                    throw new LockTokenNotFoundException(lock);
                }
            }
        }
    }
    
    
    /**
     * Enumerate locks on an object.
     * 
     * @param uri Uri of the subject
     * @return Enumeration List of {@link org.apache.slide.lock.NodeLock locks}
     * which have been put on the subject
     * @exception ServiceAccessException Service access error
     */
    public Enumeration enumerateLocks(Uri uri)
        throws ServiceAccessException {
        if (lockStore.cacheResults()) {
            Object value = locksCache.get(uri.toString());
            Vector locksVector = null;
            if (value == null) {
                locksVector = new Vector();
                Enumeration lockList = lockStore.enumerateLocks(uri);
                while (lockList.hasMoreElements()) {
                    NodeLock tempLock = (NodeLock) lockList.nextElement();
                    tempLock.validate(uri.toString());
                    locksVector.addElement(tempLock);
                }
                locksCache.put(uri.toString(), locksVector);
            } else {
                locksVector = (Vector) value;
            }
            return ((Vector) locksVector.clone()).elements();
        } else {
            return super.enumerateLocks(uri);
        }
    }
    
    
    /**
     * Retrieve a revision descriptors.
     *
     * @param uri Uri
     * @exception ServiceAccessException Service access error
     * @exception RevisionDescriptorNotFoundException Revision descriptor
     * was not found
     */
    public NodeRevisionDescriptors retrieveRevisionDescriptors(Uri uri)
        throws ServiceAccessException, RevisionDescriptorNotFoundException {
        if (revisionDescriptorsStore.cacheResults()) {
            Object tempObject = descriptorsCache.get(uri.toString());
            if (tempObject != null) {
                return ((NodeRevisionDescriptors) tempObject).cloneObject();
            } else {
                NodeRevisionDescriptors revisionDescriptors =
                    revisionDescriptorsStore.retrieveRevisionDescriptors(uri);
                revisionDescriptors = revisionDescriptors.cloneObject();
                descriptorsCache.put(uri.toString(),
                                     revisionDescriptors);
                revisionDescriptors.validate(uri.toString());
                return revisionDescriptors;
            }
        } else {
            return super.retrieveRevisionDescriptors(uri);
        }
    }
    
    
    /**
     * Create new revision descriptors.
     *
     * @param uri Uri
     * @param revisionDescriptors Node revision descriptors
     * @exception ServiceAccessException Service access error
     */
    public void createRevisionDescriptors
        (Uri uri, NodeRevisionDescriptors revisionDescriptors)
        throws ServiceAccessException {
        super.createRevisionDescriptors(uri, revisionDescriptors);
        if (revisionDescriptorsStore.cacheResults()) {
            descriptorsCache.put(uri.toString(),
                                 revisionDescriptors.cloneObject());
        }
    }
    
    
    /**
     * Update revision descriptors.
     *
     * @param uri Uri
     * @param revisionDescriptors Node revision descriptors
     * @exception ServiceAccessException Service access error
     * @exception RevisionDescriptorNotFoundException Revision descriptor
     * was not found
     */
    public void storeRevisionDescriptors
        (Uri uri, NodeRevisionDescriptors revisionDescriptors)
        throws ServiceAccessException, RevisionDescriptorNotFoundException {
        super.storeRevisionDescriptors(uri, revisionDescriptors);
        if (revisionDescriptorsStore.cacheResults()) {
            descriptorsCache.put(uri.toString(),
                                 revisionDescriptors.cloneObject());
        }
    }
    
    
    /**
     * Remove revision descriptors.
     *
     * @param uri Uri
     * @exception ServiceAccessException Service access error
     */
    public void removeRevisionDescriptors(Uri uri)
        throws ServiceAccessException {
        if (revisionDescriptorsStore.cacheResults()) {
            descriptorsCache.remove(uri.toString());
        }
        super.removeRevisionDescriptors(uri);
    }
    
    
    /**
     * Retrieve revision descriptor.
     *
     * @param uri Uri
     * @param revisionNumber Node revision number
     */
    public NodeRevisionDescriptor retrieveRevisionDescriptor
        (Uri uri, NodeRevisionNumber revisionNumber)
        throws ServiceAccessException, RevisionDescriptorNotFoundException {
        if (revisionDescriptorStore.cacheResults()) {
            Object result = descriptorCache.get(uri + "-" + revisionNumber);
            if (result != null) {
                return ((NodeRevisionDescriptor) result).cloneObject();
            } else {
                NodeRevisionDescriptor revisionDescriptor =
                    revisionDescriptorStore
                    .retrieveRevisionDescriptor(uri, revisionNumber)
                    .cloneObject();
                revisionDescriptor.validate();
                descriptorCache.put(uri + "-" + revisionNumber,
                                    revisionDescriptor);
                return revisionDescriptor;
            }
        } else {
            return super.retrieveRevisionDescriptor(uri, revisionNumber);
        }
    }
    
    
    /**
     * Create new revision descriptor.
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @exception ServiceAccessException Service access error
     */
    public void createRevisionDescriptor
        (Uri uri, NodeRevisionDescriptor revisionDescriptor)
        throws ServiceAccessException {
        super.createRevisionDescriptor(uri, revisionDescriptor);
        if (revisionDescriptorStore.cacheResults()) {
            descriptorCache.put(uri + "-"
                                + revisionDescriptor.getRevisionNumber(),
                                revisionDescriptor.cloneObject());
        }
    }
    
    
    /**
     * Update revision descriptor.
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @exception ServiceAccessException Service access error
     * @exception RevisionDescriptorNotFoundException Revision descriptor
     * was not found
     */
    public void storeRevisionDescriptor
        (Uri uri, NodeRevisionDescriptor revisionDescriptor)
        throws ServiceAccessException, RevisionDescriptorNotFoundException {
        super.storeRevisionDescriptor(uri, revisionDescriptor);
        if (revisionDescriptorStore.cacheResults()) {
            String key = uri + "-" + revisionDescriptor.getRevisionNumber();
            descriptorCache.put(key, revisionDescriptor.cloneObject());
        }
    }
    
    
    /**
     * Remove revision descriptor.
     *
     * @param uri Uri
     * @param number Revision number
     * @exception ServiceAccessException Service access error
     */
    public void removeRevisionDescriptor(Uri uri, NodeRevisionNumber number)
        throws ServiceAccessException {
        super.removeRevisionDescriptor(uri, number);
        if (revisionDescriptorStore.cacheResults()) {
            descriptorCache.remove(uri + "-" + number);
        }
    }
    
    
    /**
     * Retrive revision content.
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     */
    public NodeRevisionContent retrieveRevisionContent
        (Uri uri, NodeRevisionDescriptor revisionDescriptor)
        throws ServiceAccessException, RevisionNotFoundException {
        return super.retrieveRevisionContent(uri, revisionDescriptor);
    }
    
    
    /**
     * Create a new revision
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @param revisionContent Node revision content
     */
    public void createRevisionContent
        (Uri uri, NodeRevisionDescriptor revisionDescriptor,
         NodeRevisionContent revisionContent)
        throws ServiceAccessException, RevisionAlreadyExistException {
        super.createRevisionContent(uri, revisionDescriptor, revisionContent);
    }
    
    
    /**
     * Modify the latest revision of an object.
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @param revisionContent Node revision content
     */
    public void storeRevisionContent
        (Uri uri, NodeRevisionDescriptor revisionDescriptor,
         NodeRevisionContent revisionContent)
        throws ServiceAccessException, RevisionNotFoundException {
        super.storeRevisionContent(uri, revisionDescriptor, revisionContent);
    }
    
    
    /**
     * Remove revision.
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     */
    public void removeRevisionContent(Uri uri,
                                      NodeRevisionDescriptor revisionDescriptor)
        throws ServiceAccessException {
        super.removeRevisionContent(uri, revisionDescriptor);
    }
    
    
    /**
     * Return the name of this store
     *
     */
    public String toString() {
        return getName() + "(" + getClass().getName() + ")";
    }


    // ------------------------------------------------------ Protected Methods
    
    
    /**
     * Delist (suspend) the resource manager in the current transaction.
     */
    protected void delist(Service service, boolean success)
        throws ServiceAccessException {
        if (!success) {
            // If there's a failure which will cause the transaction to be 
            // rollbacked, flush the caches
            resetCaches();
        }
        super.delist(service, success);
    }
    
    
    /**
     * Reset the caches.
     */
    protected void resetCaches() {
        objectsCache.clear();
        permissionsCache.clear();
        locksCache.clear();
        descriptorsCache.clear();
        descriptorCache.clear();
    }
    
    
}
