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

import com.sun.jini.landlord.LeasedResource;
import net.jini.id.Uuid;
import net.jini.security.ProxyPreparer;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.rmi.MarshalledObject;
import java.rmi.RemoteException;
import java.util.Date;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Map;

import net.jini.core.event.RemoteEventListener;

/**
 * The <tt>ServiceRegistration</tt> class serves as the server-side abstraction
 * that maintains client registration state information. It implements the
 * <tt>LeasedResource</tt> interface to allow it to be used with the
 * <tt>Landlord</tt> framework. It implements the <tt>Comparable</tt> 
 * interface so that it can be used in <tt>SortedMap</tt> collections.
 *
 * @author Sun Microsystems, Inc.
 *
 * @since 1.1
 */
class ServiceRegistration implements LeasedResource, Comparable, Serializable {

    private static final long serialVersionUID = 2L;

    /** Unique identifier object */
    private final Uuid cookie;

    /** The current expiration for this registration */
    private long expiration = 0;
    
    /** The prepared, client-provided notification target. */
    // This field is transient in order to allow readObject/writeObject
    // to un/wrap the the listener to/from a MarshalledObject. 
    // This is done to prevent the corruption of the input stream.
    // For example, if the associated codebase of the listener object
    // is no longer valid, then the stream is corrupted since the object
    // can't be reconstructed.  Wrapping the reference in a 
    // MarshalledObject always allows us to read from the stream.
    // We might not be able to reconstruct the listener field, but
    // the rest of the objects in the stream will be fine.
    private transient RemoteEventListener preparedEventTarget = null; 

    /** The marshalled form of the client-provided notification target. */
    private MarshalledObject marshalledEventTarget = null; 

    /** Event log iterator. */
    // This field is transient because event state info is persisted
    // separately from the registration state info. It is (re)constructed
    // upon service initialization.
    private transient EventLogIterator eventIterator = null;

    /** 
     * Map of collected <tt>EventID</tt>'s that resulted in an 
     * UnknownEventException for the current <code>
     * eventTarget</code>. This <code>Map</code> is checked
     * upon each mailbox notification for this registration. 
     * If the received event has the same <tt>EventID</tt> 
     * as the one contained in this <code>Map</code> then an 
     * <code>UnknownEventException</code>
     * is propagated back to the sender and the event is not logged.
     * This structure is also consulted before each event delivery request.
     * If the event to be delivered has an <tt>EventID</tt> that is 
     * contained in this <tt>Map</tt>, then event delivery is cancelled
     * for that particular event. Note that this structure gets cleared
     * whenever a target listener is (re)set on the assumption that an
     * active (re)set will provide a new/better target listener that might
     * be able to handle these events.
     */
    private Map unknownEvents = new HashMap();

    /** Convenience constructor */
    public ServiceRegistration(Uuid cookie, EventLogIterator eventIterator) {
        this.cookie = cookie;
        this.eventIterator = eventIterator;
    }

    // inherit javadoc from parent
    public void setExpiration(long newExpiration) {
	 expiration = newExpiration;
    }

    // inherit javadoc from parent
    public long getExpiration() { 
	return expiration;
    }

    // inherit javadoc from parent
    public Uuid getCookie() {
	return cookie;
    }

    /** 
     * Return the identity map of EventIDs that caused an 
     * UnknownEventException to be generated for the current
     * notification target. A Map is used instead of a generic
     * Collection since the space/time tradeoff for searches
     * seems worth it.
     */
    public Map getUnknownEvents() {
	return unknownEvents;
    }

    /** 
     * Get the reference to the prepared, 
     * client-supplied notification target 
     */
    public RemoteEventListener getEventTarget() {
	return preparedEventTarget;

    }

    /** Set the reference to the client-supplied notification target */
    public void setEventTarget(RemoteEventListener preparedTarget) 
	throws IOException
    {
        if (preparedTarget == null) {
	    preparedEventTarget = null;
	    marshalledEventTarget = null;
	} else {
	    preparedEventTarget = preparedTarget;
	    marshalledEventTarget = new MarshalledObject(preparedTarget);
	}
    }
    
    /**
     * Returns <code>true</code> if an event target is currently set and
     * false otherwise. 
     */
    public boolean hasEventTarget() { 
        return (marshalledEventTarget != null);
    }
     
    /*
     * Method that restores internal, transient state.
     * Note:
     * This method attempts to prepare the target listener object, which 
     * might involve a remote invocation. Therefore, calling this method
     * while holding a lock should be avoided.
     */
    public void restoreTransientState(ProxyPreparer targetPreparer) 
        throws IOException, ClassNotFoundException 
    {
        if (targetPreparer == null) {
            throw new NullPointerException(
	        "targetPreparer cannot be null");
        }
        if (marshalledEventTarget != null) {
            RemoteEventListener unprepared = 
	        (RemoteEventListener)marshalledEventTarget.get();
            preparedEventTarget = (RemoteEventListener)
                targetPreparer.prepareProxy(unprepared);
        }
	/* 
	 * Note: Would like to defer preparation until listener
	 * is needed (i.e. in getEventTarget()). Unfortunately, 
	 * listeners are obtained within "locks" and proxy
	 * preparation (i.e. possible remote calls) should
	 * not be done while holding those locks.
	 */
    }

    /** 
     * Get the  reference to the registration's associated 
     * <tt>EventLogIterator</tt>
     */
    public EventLogIterator iterator() {
        return eventIterator; 
    }

    /** 
     * Set the reference for this registration's 
     * <tt>EventLogIterator</tt> 
     */
    public void setIterator(EventLogIterator iter) {
        eventIterator = iter; 
    }

    /**
     * Primary sort by leaseExpiration, secondary by leaseID.  The
     * secondary sort is immaterial, except to ensure a total order
     * (required by TreeMap).
     */
    public int compareTo(Object obj) {
        ServiceRegistration reg = (ServiceRegistration)obj;
        if (this == reg)
            return 0;
        if ( expiration < reg.expiration ||
             (expiration == reg.expiration &&
             (   (cookie.getMostSignificantBits() < 
		     reg.cookie.getMostSignificantBits())
              && (cookie.getLeastSignificantBits() < 
		     reg.cookie.getLeastSignificantBits()) ) ))
            return -1;
        return 1;
    }

    // inherit documentation from supertype
    public String toString () {
        return getClass().getName() + ":" + cookie.toString();
    }

//TODO - implement readObject() for structural checks

    /**
     * Utility method to display debugging information to the
     * provided <tt>Logger</tt>
     */
    void dumpInfo(Logger logger) {
            logger.log(Level.FINEST, "{0}", this.toString());
            logger.log(Level.FINEST, "Expires at: {0}", new Date(expiration));
            logger.log(Level.FINEST, "Prepared target is: {0}", preparedEventTarget);
            logger.log(Level.FINEST, "Marshalled target is: {0}", marshalledEventTarget);
            logger.log(Level.FINEST, "Unknowns: {0}", 
	        new Integer(unknownEvents.size()));
	    // eventIterator is null upon recovery phase  which
	    // is before the transient state gets rebuilt.
	    if (eventIterator != null) {
	        try { 
                    logger.log(Level.FINEST, "hasNext: {0}",
		        new Boolean(eventIterator.hasNext()));
	        } catch (IOException ioe) {
                    logger.log(Level.FINEST, "hasNext exception.", ioe);
	        }
	    }
    }
}
