/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package com.sun.ejb.containers;

import java.util.Date;
import java.io.Serializable;

import javax.ejb.EJBLocalObject;
import javax.ejb.NoSuchObjectLocalException;
import javax.ejb.EJBException;
import javax.ejb.Timer;
import javax.ejb.TimerHandle;
import javax.ejb.EJBException;
import javax.ejb.FinderException;


import com.sun.enterprise.Switch;
import com.sun.enterprise.InvocationManager;
import com.sun.enterprise.InvocationException;
import com.sun.enterprise.ComponentInvocation;

import com.sun.ejb.Invocation;
import com.sun.ejb.ComponentContext;

import java.io.IOException;

import com.sun.ejb.spi.io.IndirectlySerializable;
import com.sun.ejb.spi.io.SerializableObjectFactory;

/*
 * TimerWrapper is the application-level representation
 * of a timer.  Note that this class is not Serializable.
 * Timer instances are not intended to be directly persisted
 * by the application.  TimerHandle should be used instead.
 * 
 * @author Kenneth Saks
 */
public class TimerWrapper
    implements Timer, IndirectlySerializable
{
    
    private TimerPrimaryKey timerId_;
    private EJBTimerService timerService_;

    TimerWrapper(TimerPrimaryKey timerId, EJBTimerService timerService) {
        timerId_      = timerId;
        timerService_ = timerService;   //TimerService passed in could be null
    }

    /*
     * Implementations of javax.ejb.Timer methods
     */
    public void cancel() 
        throws IllegalStateException, NoSuchObjectLocalException, EJBException {

        checkCallPermission();

        try {
            timerService_.cancelTimer(timerId_);
        } catch(FinderException fe) {
            throw new NoSuchObjectLocalException("timer no longer exists", fe);
        } catch(Exception e) {
            throw new EJBException(e);
        }

    }
    
    public long getTimeRemaining() 
        throws IllegalStateException, NoSuchObjectLocalException {

        Date nextTimeout = getNextTimeout();

        Date now = new Date();

        long timeRemaining = nextTimeout.getTime() - now.getTime();

        return (timeRemaining > 0) ? timeRemaining : 0;
    }
    
    public Date getNextTimeout() 
        throws IllegalStateException, NoSuchObjectLocalException {

        checkCallPermission();

        Date nextTimeout;

        try {
            nextTimeout = timerService_.getNextTimeout(timerId_);
        } catch(FinderException fe) {
            throw new NoSuchObjectLocalException("timer no longer exists", fe);
        } 

        return nextTimeout;
    }
    
    public Serializable getInfo() 
        throws IllegalStateException, NoSuchObjectLocalException {

        checkCallPermission();

        Serializable info;

        try {
            info = timerService_.getInfo(timerId_);
        } catch(FinderException fe) {
            throw new NoSuchObjectLocalException("timer no longer exists", fe);
        } 

        return info;
    }
    
    public TimerHandle getHandle() 
        throws IllegalStateException, NoSuchObjectLocalException {

        checkCallPermission();

        if( !timerService_.timerExists(timerId_) ) {
            throw new NoSuchObjectLocalException("timer no longer exists");
        } 

        return new TimerHandleImpl(timerId_);
    }

    public boolean equals(Object o) {
        boolean equal = false;
        if(o instanceof TimerWrapper) {
            TimerWrapper other = (TimerWrapper) o;
            equal = other.timerId_.equals(this.timerId_);
        }
        return equal;
    }

    public int hashCode() {
        return timerId_.hashCode();
    }

    public String toString() {
        return "Timer " + timerId_;
    }

    /**
     * Verify that Timer method access is allowed from this context.
     * This method is static so that TimerHandle can call it even
     * before it has created a TimerWrapper instance.
     */
    private static void checkCallPermission() throws IllegalStateException {

        boolean allowed = false;

        ContainerFactoryImpl cf = (ContainerFactoryImpl) 
            Switch.getSwitch().getContainerFactory();
        EJBTimerService timerService = cf.getEJBTimerService();
        if( timerService == null ) {
            throw new IllegalStateException 
                ("EJBTimerService is not available");
        }

        try {
            InvocationManager invManager = 
                Switch.getSwitch().getInvocationManager();

            ComponentInvocation inv = invManager.getCurrentInvocation();
            if (inv == null)
                throw new IllegalStateException
                    ("Invocation cannot be null");

            int invType = inv.getInvocationType();
            if( invType == ComponentInvocation.EJB_INVOCATION ) { 
                if ( inv instanceof Invocation ) {
                    ComponentContext context = ((Invocation) inv).context;
                    // Delegate check to EJB context.  Let any 
                    // IllegalStateException bubble up.
                    context.checkTimerServiceMethodAccess();
                    allowed = true;
                } else {
                    // NOTE : There shouldn't be any cases where an EJB
                    // container uses com.sun.enterprise.ComponentInvocation
                    // instead of com.sun.ejb.Invocation and timer method
                    // access is allowed. No EJBContextImpl is available
                    // to perform checks.
                    allowed = false;
                }
            } else if( invType == ComponentInvocation.SERVLET_INVOCATION ) {
                throw new IllegalStateException
                    ("Web tier access to EJB timers through local " +
                     "interfaces is not portable");
            }
        } catch(InvocationException ie) {
            IllegalStateException ise = new IllegalStateException
                ("Operation not allowed");
            ise.initCause(ie);
            throw ise;
        }

        if( !allowed ) {
            throw new IllegalStateException("Operation not allowed");
        }
        
    }

    public SerializableObjectFactory getSerializableObjectFactory() {
        return new SerializedTimerWrapper(timerId_);
    }

    /**
     * Used by serialization code to serialize a TimerWrapper.  We
     * need a separate type that TimerHandle so that on deserialization
     * we know it started as a TimerWrapper instead of a TimerHandle.
     */
    public static class SerializedTimerWrapper
        implements SerializableObjectFactory
    {
        private TimerPrimaryKey timerId_;

        SerializedTimerWrapper() {}

        SerializedTimerWrapper(TimerPrimaryKey timerId) {
            timerId_ = timerId;
        }

        /**
         * When deserializing the timer wrapper create a TimerWrapper object.
         * Check if the record is valid only when making calls on the object.
         */
        public Object createObject()
            throws EJBException
        {
            ContainerFactoryImpl cf = (ContainerFactoryImpl) 
                Switch.getSwitch().getContainerFactory();
            EJBTimerService timerService = cf.getEJBTimerService();
            TimerWrapper timer = new TimerWrapper(timerId_, timerService);

            return timer;
        }
    }

    private static class TimerHandleImpl implements TimerHandle {

        private TimerPrimaryKey timerId_;
        
        public TimerHandleImpl(TimerPrimaryKey timerId) {
            timerId_ = timerId;
        }

        /**
         * Materialize Timer from handle.  This must be coded
         * very defensively, since handle use might be attempted from 
         * outside of EJB container.  
         */
        public Timer getTimer() throws IllegalStateException, 
            NoSuchObjectLocalException, EJBException {
            TimerWrapper timer = null;
            Switch theSwitch   = Switch.getSwitch();
            if( theSwitch != null ) {
                
                // Make sure use of timer service methods are allowed
                TimerWrapper.checkCallPermission();

                timer = getTimerInternal(timerId_);

            } else {
                throw new IllegalStateException
                    ("Attempt to use EJB timer from outside a container");
            }

            return timer;
        }

        public static TimerWrapper getTimerInternal(TimerPrimaryKey timerId) 
            throws NoSuchObjectLocalException, EJBException {

            TimerWrapper timer = null;
            ContainerFactoryImpl cf = (ContainerFactoryImpl) 
                Switch.getSwitch().getContainerFactory();
            EJBTimerService timerService = cf.getEJBTimerService();

            if( timerService != null ) {
                if( timerService.timerExists(timerId) ) {
                    timer = new TimerWrapper(timerId, timerService);
                } else {
                    throw new NoSuchObjectLocalException
                        ("timer is no longer active");
                }
            } else {
                throw new EJBException("EJB Timer Service not available");
            }
            
            return timer;
        }
    }
}
