/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.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://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */
package com.sun.jini.norm;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.jini.config.ConfigurationException;

import com.sun.jini.norm.lookup.SubStore;
import com.sun.jini.reliableLog.LogHandler;
import com.sun.jini.reliableLog.ReliableLog;
import com.sun.jini.system.FileSystem;
import com.sun.jini.thread.ReadersWriter;

/**
 * Class that actually stores a Norm server's state to disk.  Basically
 * a wrapper around ReliableLog with the addition of lock management.
 *
 * @author Sun Microsystems, Inc.
 */
class PersistentStore {
    /** Logger for logging messages for this class */
    private static final Logger logger = Logger.getLogger("com.sun.jini.norm");

    /**
     * Object we use to reliably and persistently log updates to our
     * state, or null if not persistent.
     */
    private ReliableLog log;

    /**
     * No mutation of persistent state can occur during a snapshot,
     * however, we can have multiple mutators, use
     * <code>ReadersWriter</code> object to manage this invariant.  Note
     * as far as the lock is concerned the mutators are the readers and
     * snapshot thread the writer since for us mutation is the
     * non-exclusive operation.
     */
    final private ReadersWriter mutatorLock = new ReadersWriter();

    /**
     * Thread local that tracks if (and how many times) the current thread
     * has acquired a non-exclusive mutator lock.
     */
    final static private ThreadLocal lockState = new ThreadLocal();

    /** Cache a <code>Long</code> object with a zero value */
    final static private Long zero = new Long(0);

    /** Location of the persistent store, or null if not persistent */
    final private File storeLocation;

    /** Object that handles the recovery of logs, or null if not persistent  */
    final private LogHandler logHandler;

    /** The NormServer we are part of */
    final private NormServerBaseImpl server;

    /** Number of updates since last snapshot */
    private int updateCount;

    /** A list of all of the sub-stores */
    private List subStores = new LinkedList();

    /**
     * Construct a store that will persist its data to the specified
     * directory.
     *
     * @param logDir directory where the store should persist its data,
     *        which must exist, unless it is <code>null</code>, in which case
     *	      there is no persistence
     * @param logHandler object that will process the log and last
     *        snapshot to recover the server's state
     * @param server the server is called back after an update so it can
     *        decide whether or not to do a snapshot
     * @throws StoreException if there is a problem setting up the store
     */
    PersistentStore(String logDir, LogHandler logHandler, 
		    NormServerBaseImpl server)
	throws StoreException
    {
	this.logHandler = logHandler;
	this.server = server;
	if (logDir == null) {
	    storeLocation = null;
	} else {
	    storeLocation = new File(logDir);
	    try {
		log = new ReliableLog(
		    storeLocation.getCanonicalPath(), logHandler);
	    } catch (IOException e) {
		throw new CorruptedStoreException(
		    "Failure creating reliable log", e);
	    }

	    try {
		log.recover();
	    } catch (IOException e) {
		throw new CorruptedStoreException(
		    "Failure recovering reliable log", e);	    
	    }
	}
    }

    /**
     * Destroy the store.
     *
     * @throws IOException if it has difficulty removing the log files 
     */
    void destroy() throws IOException {
	// Prep all the sub-stores to be destroyed
	for (Iterator i = subStores.iterator(); i.hasNext(); ) {
	    SubStore subStore = (SubStore) i.next();
	    subStore.prepareDestroy();
	}
	if (log != null) {
	    log.deletePersistentStore();
	    FileSystem.destroy(storeLocation, true);
	}
    }

    /**
     * Inform the store of a sub-store
     */
    void addSubStore(SubStore subStore) throws StoreException {
	try {
	    if (log == null) {
		subStore.setDirectory(null);
	    } else {
		final String subDir = subStore.subDirectory();

		if (subDir == null) {
		    subStore.setDirectory(storeLocation);
		} else {
		    subStore.setDirectory(new File(storeLocation, subDir));
		}
	    }
	    
	    subStores.add(subStore);
	} catch (IOException e) {
	    throw new StoreException("Failure adding substore " + subStore,
				     e);
	} catch (ConfigurationException e) {
	    throw new StoreException("Failure adding substore " + subStore,
				     e);
	}
    }


    /////////////////////////////////////////////////////////////////
    // Methods for obtaining and releasing the locks on the store

    /**
     * Block until we can acquire a non-exclusive mutator lock on the
     * server's persistent state.  This lock should be acquired in a
     * <code>try</code> block and a <code>releaseMutatorLock</code> call
     * should be placed in a <code>finally</code> block.
     */
    void acquireMutatorLock() {
	// Do we already hold a lock?

	Long lockStateVal = (Long) lockState.get();
	if (lockStateVal == null) 
	    lockStateVal = zero;

	final long longVal = lockStateVal.longValue();

	if (longVal == 0) {
	    // No, this thread currently does not hold a lock,
	    // grab non-exclusive lock (which for mutatorLock is a
	    // read lock) 
	    mutatorLock.readLock();
	} 

	// Either way, bump the lock count and update our thread state
	lockState.set(new Long(longVal + 1));
    }

    /**
     * Release one level of mutator locks if this thread holds at least one.
     */
    void releaseMutatorLock() {
	Long lockStateVal = (Long) lockState.get();
	if (lockStateVal == null) 
	    lockStateVal = zero;

	final long longVal = lockStateVal.longValue();

	if (longVal == 0) {
	    // No lock to release, return
	    return;
	}

	if (longVal == 1) {
	    // Last one on stack release lock
	    // Using read lock because we want a non-exclusive lock
	    mutatorLock.readUnlock();
	    lockStateVal = zero;
	} else {
	    lockStateVal = new Long(longVal - 1);
	}

	lockState.set(lockStateVal);
    }

    //////////////////////////////////////////////////////////////////
    // Methods for writing records to the log and taking and
    // coordinating snapshots

    /**
     * Log an update. Will flush to disk before returning.
     *
     * @throws IllegalStateException if the current thread does not hold
     *	       a non-exclusive mutator lock
     * @throws IOException
     * @see ReliableLog#update
     */
    void update(Object o) {
	if (log == null) {
	    return;
	}
	final Long lockStateVal = (Long) lockState.get();
	if (lockStateVal == null || lockStateVal.longValue() == 0) {
	    throw new IllegalStateException("PersistentStore.update:" +
	        "Must acquire mutator lock before calling update()");
	}

	synchronized (this) { 
	    try {
		log.update(o, true);
		updateCount++;
		server.updatePerformed(updateCount);
	    } catch (IOException e) {
		// $$$ should probably be propagating this exception
		logger.log(Level.WARNING, "IOException while updating log", e);
	    }
	}
    }

    /**
     * Generate a snapshot, will perform the necessary locking to ensure no
     * threads are mutating the state of the server before creating the 
     * snapshot.
     *
     * @throws IOException
     * @see ReliableLog#snapshot
     */
    void snapshot() throws IOException {
	if (log == null) {
	    return;
	}
	try {
	    // Using write lock because we want an exclusive lock
	    mutatorLock.writeLock();
	    updateCount = 0;

	    // Don't need to sync on this because
	    // mutatorLock.writeLock() gives us an exclusive lock
	    log.snapshot();
	} finally {
	    // Using write lock because we want an exclusive lock
	    mutatorLock.writeUnlock();
	}
    }
}
