/*
 * 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 com.sun.jini.outrigger;

import java.util.Map;
import java.io.IOException;
import java.rmi.Remote;
import java.rmi.MarshalledObject;
import java.rmi.RemoteException;
import java.rmi.activation.ActivationID;
import java.rmi.activation.ActivationException;
import javax.security.auth.login.LoginException;

import net.jini.core.discovery.LookupLocator;
import net.jini.core.entry.Entry;
import net.jini.core.event.EventRegistration;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.transaction.Transaction;
import net.jini.core.transaction.TransactionException;
import net.jini.core.transaction.UnknownTransactionException;
import net.jini.core.transaction.server.TransactionManager;
import net.jini.core.lease.Lease;
import net.jini.core.lease.LeaseDeniedException;
import net.jini.core.lease.UnknownLeaseException;
import net.jini.export.ProxyAccessor;
import net.jini.space.JavaSpace;
import net.jini.space.InternalSpaceException;

import net.jini.security.TrustVerifier;
import net.jini.security.proxytrust.ServerProxyTrust;
import net.jini.config.ConfigurationException;
import net.jini.id.Uuid;

import com.sun.jini.start.LifeCycle;

/**
 * For various reasons there is code that we would like
 * to run before every incoming remote call. To accomplish
 * this we wrap the server in an object that will run
 * the common code and then delegate to the server to
 * do the actual work. This is a base class for these
 * wrappers.
 *
 * 
 * @since 2.0
 */
class OutriggerServerWrapper 
    implements OutriggerServer, ServerProxyTrust, ProxyAccessor
{
    /** The object being delegated to */     
    private final OutriggerServerImpl delegate;

    /** 
     * If <code>false</code>, hold calls until it becomes
     * <code>true</code> 
     */
    private boolean allowCalls;

    /** 
     * If non-null cause incoming calls to immediately throw this
     * exception. Takes presidents over <code>holdCalls</code>. This
     * field is only set to an <code>Error</code>,
     * <code>RemoteException</code>, or <code>RuntimeException</code>
     * and thus can be thrown by an of <code>OutriggerServer</code>'s
     * methods.  
     */
    private Throwable failCallsWith;

    /**
     * Create an <code>OutriggerServerWrapper</code> that
     * will delegate to a non-activatable <code>OutriggerServerImpl</code>
     * created with the specified configuration and wrapped by
     * <code>this</code>.
     * @param configArgs set of strings to be used to obtain a
     *                   <code>Configuration</code>.
     * @param lifeCycle the object to notify when this
     *                  service is destroyed.
     * @param persistent If <code>true</code> will throw an 
     *                   <code>ConfigurationException</code>
     *                   if there is no persistence directory or
     *                   store specified in the configuration.
     * @throws IOException if there is problem exporting the server.
     * @throws ConfigurationException if the <code>Configuration</code> is 
     *         malformed.
     * @throws LoginException if the <code>loginContext</code> specified
     *         in the configuration is non-null and throws 
     *         an exception when login is attempted.
     */
    OutriggerServerWrapper(String[] configArgs, LifeCycle lifeCycle, 
			   boolean persistent) 
	throws IOException, ConfigurationException, LoginException
    {
	try {
	    delegate = new OutriggerServerImpl(null, lifeCycle, configArgs,
					       persistent, this);
	} catch (ActivationException e) {
	    throw new AssertionError(e);
	}
    }

    /**
     * Create an <code>OutriggerServerWrapper</code> that
     * will delegate to an <code>OutriggerServerImpl</code>
     * created with the specified argument and wrapped by <code>this</code>.
     * @param activationID of the server, must not be <code>null</code>.
     * @param configArgs set of strings to be used to obtain a
     *                   <code>Configuration</code>.
     * @throws IOException if there is problem recovering data
     *         from disk, exporting the server, or unpacking 
     *         <code>data</code>.
     * @throws ConfigurationException if the <code>Configuration</code> is 
     *         malformed.
     * @throws ActivationException if activatable and there
     *         is a problem getting a reference to the activation system.
     * @throws LoginException if the <code>loginContext</code> specified
     *         in the configuration is non-null and throws 
     *         an exception when login is attempted.
     * @throws NullPointerException if <code>activationID</code>
     *         is <code>null</code>.
     */
    OutriggerServerWrapper(ActivationID activationID, String[] configArgs) 
	throws IOException, ConfigurationException, LoginException,
	       ActivationException
    {
	if (activationID == null)
	    throw new NullPointerException("activationID must be non-null");
	delegate = new OutriggerServerImpl(activationID, null, configArgs,
					   true, this);
    }

    /**
     * Cause incoming calls to block until further notice.
     */
    synchronized void holdCalls() {
	failCallsWith = null;
	allowCalls = false;
	notifyAll();
    }

    /**
     * Cause in new or blocked calls to fail with
     * the specified exception.
     * @throws IllegalArgumentException if <code>t</code>
     * is not an <code>Error</code>, <code>RemoteException</code>,
     * or <code>RuntimeException</code>.
     * @throws NullPointerException if <code>t</code> is <code>null</code>.
     */
    synchronized void rejectCalls(Throwable t) {
	if (t == null)
	    throw new NullPointerException("Throwable must not be null");

	if (!((t instanceof Error) || 
	      (t instanceof RuntimeException) ||
	      (t instanceof RemoteException)))
	    throw new IllegalArgumentException("t must be an exception " +
		"that can be thrown from any of OutriggerServer's methods");

	failCallsWith = t;
	allowCalls = true;
	notifyAll();
    }

    /**
     * Allow incoming calls.
     */
    synchronized void allowCalls() {
	failCallsWith = null;
	allowCalls = true;
	notifyAll();
    }
    
    /**
     * Block until calls are allowed, or until calls
     * are to be rejected.
     * @throws RemoteException If calls are being rejected
     *         with <code>RemoteException</code>s.
     * @throws RuntimeException If calls are being rejected
     *         with <code>RuntimeException</code>s.
     * @throws Error If calls are being rejected
     *         with <code>Error</code>s.
     */
    private synchronized void gate() throws RemoteException {
	while (!allowCalls || failCallsWith != null) {
	    if (failCallsWith != null) {
		if (failCallsWith instanceof RemoteException)
		    throw (RemoteException)failCallsWith;
		else if (failCallsWith instanceof Error)
		    throw (Error)failCallsWith;
		else if (failCallsWith instanceof RuntimeException) 
		    throw (RuntimeException)failCallsWith;
		else
		    throw new AssertionError("Wrapper trying to " +
					     "throw " + failCallsWith);
	    }

	    if (!allowCalls) {
		try {
		    wait();
		} catch (InterruptedException e) {
		    throw new 
			InternalSpaceException("gate method interrupted");
		}
	    }
	}
    }
	        

    public long[] write(EntryRep entry, Transaction txn, long lease)
	throws TransactionException, RemoteException 
    {
	gate();
	return delegate.write(entry, txn, lease);
    }

    public Object read(EntryRep tmpl, Transaction txn, long timeout,
		       QueryCookie cookie)
	throws TransactionException, RemoteException, InterruptedException
    {
	gate();
	return delegate.read(tmpl, txn, timeout, cookie);
    }

    public Object readIfExists(EntryRep tmpl, Transaction txn, long timeout,
			       QueryCookie cookie)
	throws TransactionException, RemoteException, InterruptedException
    {
	gate();
	return delegate.readIfExists(tmpl, txn, timeout, cookie);
    }

    public Object take(EntryRep tmpl, Transaction txn, long timeout,
		       QueryCookie cookie)
	throws TransactionException, RemoteException, InterruptedException
    {
	gate();
	return delegate.take(tmpl, txn, timeout, cookie);
    }

    public Object takeIfExists(EntryRep tmpl, Transaction txn, long timeout,
			       QueryCookie cookie)
	throws TransactionException, RemoteException, InterruptedException
    {
	gate();
	return delegate.takeIfExists(tmpl, txn, timeout, cookie);
    }

    public EventRegistration
	notify(EntryRep tmpl, Transaction txn, RemoteEventListener listener,
	       long lease, MarshalledObject handback)
	throws TransactionException, RemoteException 
    {
	gate();
	return delegate.notify(tmpl, txn, listener, lease, handback);
    }

    public EventRegistration registerForAvailabilityEvent(EntryRep[] tmpls,
	    Transaction txn, boolean visibilityOnly, RemoteEventListener listener,
  	    long leaseTime, MarshalledObject handback)
        throws TransactionException, RemoteException
    {
	gate();
	return delegate.registerForAvailabilityEvent(tmpls, txn, visibilityOnly,
	    listener, leaseTime, handback);
    }

    public long[] write(EntryRep[] entries, Transaction txn, long[] leaseTimes)
        throws TransactionException, RemoteException
    {
	gate();
	return delegate.write(entries, txn, leaseTimes);
    }

    public Object take(EntryRep[] tmpls, Transaction tr, long timeout,
		       int limit, QueryCookie cookie)
	throws TransactionException, RemoteException
    {
	gate();
	return delegate.take(tmpls, tr, timeout, limit, cookie);
    }

    public MatchSetData contents(EntryRep[] tmpls, Transaction tr,
				 long leaseTime, long limit)
        throws TransactionException, RemoteException
    {
	gate();
	return delegate.contents(tmpls, tr, leaseTime, limit);
    }

    public EntryRep[] nextBatch(Uuid contentsQueryUuid, Uuid entryUuid)
        throws RemoteException
    {
	gate();
	return delegate.nextBatch(contentsQueryUuid, entryUuid);
    }

    public long renew(Uuid cookie, long extension)
	throws LeaseDeniedException, UnknownLeaseException, RemoteException
    {
	gate();
	return delegate.renew(cookie, extension);
    }

    public void cancel(Uuid cookie)
	throws UnknownLeaseException, RemoteException
    {
	gate();
	delegate.cancel(cookie);
    }

    public Object getAdmin() throws RemoteException {
	gate();
	return delegate.getAdmin();
    }

    public int prepare(TransactionManager mgr, long id)
	throws UnknownTransactionException, RemoteException 
    {
	gate();
	return delegate.prepare(mgr, id);
    }

    public void commit(TransactionManager mgr, long id)
	throws UnknownTransactionException, RemoteException 
    {
	gate();
	delegate.commit(mgr, id);
    }

    public void abort(TransactionManager mgr, long id)
	throws UnknownTransactionException, RemoteException 
    {
	gate();
	delegate.abort(mgr, id);
    }

    public int prepareAndCommit(TransactionManager mgr, long id)
	throws UnknownTransactionException, RemoteException 
    {
	gate();
	return delegate.prepareAndCommit(mgr, id);
    }
    
    public RenewResults renewAll(Uuid[] cookies, long[] durations)
	throws RemoteException
    {
	gate();
	return delegate.renewAll(cookies, durations);
    }

    public Map cancelAll(Uuid[] cookies) throws RemoteException {
	gate();
	return delegate.cancelAll(cookies);
    }

    public Object getServiceProxy() throws RemoteException {
	gate();
	return delegate.getServiceProxy();
    }

    public JavaSpace space() throws RemoteException {
	gate();
	return delegate.space();
    }

    public Uuid contents(EntryRep tmpl, Transaction txn)
        throws TransactionException, RemoteException
    {
	gate();
	return delegate.contents(tmpl, txn);
    }

    public EntryRep[] nextReps(Uuid iterationUuid, int max, 
			       Uuid entryUuid) 
	throws RemoteException
    {
	gate();
	return delegate.nextReps(iterationUuid, max, entryUuid);
    }

    public void delete(Uuid iterationUuid, Uuid entryUuid) 
	throws RemoteException
    {
	gate();
	delegate.delete(iterationUuid, entryUuid);
    }

    public void close(Uuid iterationUuid) throws RemoteException {
	gate();
	delegate.close(iterationUuid);
    }

    public void destroy() throws RemoteException {
	gate();
	delegate.destroy();
    }

    public Entry[] getLookupAttributes() throws RemoteException {
	gate();
	return delegate.getLookupAttributes();
    }

    public void addLookupAttributes(Entry[] attrSets) 
	throws RemoteException 
    {
	gate();
	delegate.addLookupAttributes(attrSets);
    }

    public void modifyLookupAttributes(Entry[] attrSetTemplates,
				       Entry[] attrSets)
       throws RemoteException
    {
	gate();
	delegate.modifyLookupAttributes(attrSetTemplates, attrSets);
    }

    public String[] getLookupGroups() throws RemoteException {
	gate();
	return delegate.getLookupGroups();
    }

    public void addLookupGroups(String[] groups) throws RemoteException {
	gate();
	delegate.addLookupGroups(groups);
    }

    public void removeLookupGroups(String[] groups) throws RemoteException {
	gate();
	delegate.removeLookupGroups(groups);
    }

    public void setLookupGroups(String[] groups) throws RemoteException {
	gate();
	delegate.setLookupGroups(groups);
    }

    public LookupLocator[] getLookupLocators() throws RemoteException {
	gate();
	return delegate.getLookupLocators();
    }

    public void addLookupLocators(LookupLocator[] locators) 
	throws RemoteException 
    {
	gate();
	delegate.addLookupLocators(locators);
    }

    public void removeLookupLocators(LookupLocator[] locators) 
	throws RemoteException 
    {
	gate();
	delegate.removeLookupLocators(locators);
    }

    public void setLookupLocators(LookupLocator[] locators) 
	throws RemoteException 
    {
	gate();
	delegate.setLookupLocators(locators);
    }

    public Object getProxy() {
	// don't need to block or die on this one.
	return delegate.getProxy();
    }

    public TrustVerifier getProxyVerifier() throws RemoteException {
	gate();
	return delegate.getProxyVerifier();
    }
}
