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

import java.util.logging.Level;

// All imports
import net.jini.core.transaction.*;
import net.jini.core.transaction.server.*;
import java.rmi.*;
import java.rmi.server.*;
import java.util.Map;
import java.util.HashMap;
import java.util.Collections;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;

import java.util.logging.Logger;
import java.util.logging.Level;

import javax.security.auth.login.LoginContext;
import javax.security.auth.Subject;

import net.jini.export.Exporter;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.security.TrustVerifier;
import net.jini.security.proxytrust.ProxyTrust;
import com.sun.jini.qa.harness.QATest;
import com.sun.jini.qa.harness.QAConfig;

/**
 * This class provides a simple transaction manager that tests can use
 * to test a particpant.  It is not designed to be robust in any way,
 * just to allow a test to drive the participant to various parts of
 * the transaction in a controlled way.  This means that some of the
 * methods are not supported -- anything not needed by a participant
 * may have a simpler local version, since this is always used locally
 * by the test.
 */
public class TesterTransactionManager
        implements TransactionManager, TransactionConstants, Serializable, ProxyTrust {

    private static Logger logger = 
	Logger.getLogger("com.sun.jini.qa.harness");

    private static int serviceID = 100;

    /** Our transaction objects. */
    private Map txns = Collections.synchronizedMap(new HashMap());

    /** The next ID to allocate. */
    private static long nextID = 1;

    LoginContext context;

    private Object proxy;
    private Object myRef;

    public TesterTransactionManager() throws RemoteException {
	Configuration c = QAConfig.getConfig().getConfiguration();
	context = null;
	try {
	    context = (LoginContext) c.getEntry("test", 
						"mahaloLoginContext",
						LoginContext.class, 
						null);
	    if (context != null) {
		logger.log(Level.FINEST, "got a TesterTransactionManager login context");
	    }
	} catch (Throwable e) {
	    throw new RemoteException("Configuration error", e);
	}	
	if (context != null) {
	    doExportWithLogin(context, c);
	} else {
	    doExport(c);
	}
    }

    public TrustVerifier getProxyVerifier() {
	return new TesterTransactionManagerProxyVerifier((TransactionManager) myRef);
    }

    private void doExport(Configuration c) throws RemoteException {
	Exporter exporter = QAConfig.getDefaultExporter();
	if (c instanceof com.sun.jini.qa.harness.QAConfiguration) {
	    try {
		exporter = (Exporter) c.getEntry("test",
						 "testerTransactionManagerExporter",
						 Exporter.class);
	    } catch (ConfigurationException e) {
		throw new RemoteException("Configuration error", e);
	    }
	}
	myRef = exporter.export(this);
	proxy = TesterTransactionManagerProxy.getInstance(
				 (TransactionManager) myRef, serviceID++);
    }
    
    private void doExportWithLogin(LoginContext context, final Configuration c) 
	throws RemoteException
    {
	try {
	    context.login();
	} catch (Throwable e) {
	    throw new RemoteException("Login failed", e);
	}
	try {
	    Subject.doAsPrivileged(context.getSubject(),
				   new PrivilegedExceptionAction() {
					   public Object run() throws RemoteException {
					       doExport(c);
					       return null;
					   }
				       },
				   null);
	} catch (PrivilegedActionException e) {
	    Throwable t = e.getException();
	    throw new RemoteException("doAs failed", t);
	} catch (Throwable e) {
	    throw new RemoteException("doAs failed", e);
	}
    }

    public Object writeReplace() throws ObjectStreamException {
	return proxy;
    }

    /**
     * Return a new <code>ServerTransaction</code> object.
     */
    public TesterTransaction create() {
        TesterTransaction tt = new TesterTransaction(this, nextID());
        txns.put(tt.idObj, tt);
        return tt;
    }

    /**
     * Return the next transaction id.
     */
    private static synchronized long nextID() {
        return nextID++;
    }

    /**
     * This implementation ignores the time -- it is always synchronous.
     */
    public void commit(long id, long timeout)
            throws UnknownTransactionException, CannotCommitException,
            RemoteException {
        commit(id);
    }

    /**
     * This implementation ignores the time -- it is always synchronous.
     */
    public void commit(long id)
            throws UnknownTransactionException, CannotCommitException,
            RemoteException {
        tt(id).commit();
    }

    private TesterTransaction tt(long id) throws UnknownTransactionException {
        try {
            return (TesterTransaction) txns.get(new Long(id));
        } catch (NullPointerException e) {
            throw new UnknownTransactionException("" + id);
        }
    }

    /**
     * This implementation ignores the time -- it is always synchronous.
     */
    public void abort(long id, long timeout)
            throws UnknownTransactionException, CannotAbortException,
            RemoteException {
        abort(id);
    }

    /**
     * This implementation ignores the time -- it is always synchronous.
     */
    public void abort(long id)
            throws UnknownTransactionException, CannotAbortException,
            RemoteException {
        tt(id).sendAbort();
    }

    /**
     * @throws UnsupportedOperationException
     *            Use <code>create()</code>: no leases or other mess
     *            supported or needed in this local case.
     * @see #create()
     */
    public TransactionManager.Created create(long leaseTime) {
        throw new UnsupportedOperationException("don't get fancy: use"
                + " create()");
    }

    /**
     * Return the current state of this transasction.
     */
    public int getState(long id)
            throws UnknownTransactionException, RemoteException {
        return tt(id).getState();
    }

    /**
     * Join the transaction.  Only one participant in each transaction,
     * currently.
     */
    public void join(long id, TransactionParticipant part, long crashCnt)
            throws UnknownTransactionException, CannotJoinException,
            CrashCountException {
        tt(id).join(part, crashCnt);
    }
}
