/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2000-2009 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 mq/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 mq/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. 
 */

/*
 * @(#)ExceptionHandler.java	1.31 06/27/07
 */ 

package com.sun.messaging.jmq.jmsclient;

import java.util.logging.*;

import javax.jms.*;

import com.sun.messaging.AdministeredObject;

import com.sun.messaging.jmq.jmsclient.logging.Loggable;
import com.sun.messaging.jmq.jmsclient.resources.ClientResources;

/**
 * The following are the client exception handling basic principles:
 * <p>
 * 1. Use JMS Standard Exceptions whenever appropriate as stated in the
 * specification 1.1 chapter 7.3 "Standard Exceptions".
 * For example,
 * <p>
 * JMSSecurityException: This exception must be thrown when a provider
 * rejects a user name/password submitted by a client. It may also be
 * thrown for any case where a security restriction prevents a method
 * from completing.
 * <p>
 * ResourceAllocationException: This exception is thrown when a provider
 * is unable to allocate the resources required by a method.
 * For example, this exception should be thrown when a call to
 * createTopicConnection fails due to lack of JMS provider resources.
 * <p>
 * 2. Each Exception thrown by MQ client SHOULD have its own unique error
 * code defined in the ClientResources.java whenever appropriate.
 * Each error status returned from the broker SHOULD BE mapped to an unique
 * client error code.
 * <p>
 * 3. Exception messages SHOULD BE as clear as possible.  Each exception
 * message defined in ClientResource.properties MUST BE mapped to a unique
 * error code defined in ClientResource.java.  Information that could help
 * user to identify the cause of exceptions SHOULD BE included in the
 * exception message.
 * <p>
 * 4.  The above principles (#1-#3) are used to construct MQ exceptions.
 * Each MQ JMS exception is identified by its exception type, error code
 * and error message.  Root cause of exception is set to the linked exception
 * of JMS exception.
 *
 * <p>
 * MQ Exception handling may be divided into the following two categories:
 * <p>
 * 1. Exceptions initiated from MQ. The localized error message is obtained
 *    from AdministeredObject.cr (ClientResources bundle) with errorCode
 *    as key.  Additional information may also be included as part of the
 *    error message.
 * <p>
 *    The appropriate JMS standard exception is constructed with "error code"
 *    and "error message" as parameters.
 * <p>
 *    Each JMSException generated by JMQ has an error code defined in
 *    ClientResources.java.
 * <p>
 *
 * 2. Exceptions thrown from JVM and caught by MQ. In general, MQ converts
 *    the caught exception into JMSException type.  MQ choose the error code
 *    for the JMSException in two ways:
 * <p>
 * 2.1 Use generic error code ClientResources.X_CAUGHT_EXCEPTION.
 *
 *     This error code is used for cases that we simply just want to use the
 *     JVM excption message in the JMSException message.  For example,
 *     to handle exceptions thrown during ObjectOutputStream construction.
 * <p>
 *     The API handleException(Exception source, String errorCode)
 *     is used to construct the JMSException instance.  For example,
 *     the caller caught an IOException (ioe) would call the following API
 *     to raise a JMSException:
 * <p>
 *     ExceptionHandler.handleException(ioe, ClientResources.X_CAUGHT_EXCEPTION);
 * <p>
 *     The exception message with ClientResources.X_CAUGHT_EXCEPTION error
 *     code would have the error message format as follows.
 * <p>
 *     "[C4038]: " + RootCauseException.toString();
 *
 *  <p>
 *     The root cause exception is set to the JMSException as linked exception.
 *
 * <p>
 * 2.2 Use an error code defined in ClientResources.java.
 *     The associated error message is defined in ClientResources.properties.
 * <p>
 *    This is basically the same as above except uses an appropriate error
 *    code and message for the exception.  For example,
 * <p>
 *    handleException(ioe, AdministeredObject.cr.X_MESSAGE_SERIALIZE);
 *    message: "[C4016]: Serialize message failed. cause: " + ioe.toString();
 *    errorCode: AdministeredObject.cr.X_MESSAGE_SERIALIZE
 * <p>
 *    handleException(ioe, AdministeredObject.cr.X_MESSAGE_READ, true);
 *    message: "[C4012]: Read message failed. cause: " + ioe.toString();
 *    errorCode: AdministeredObject.cr.X_MESSAGE_READ
 * <p>
 *    The above two cases are the same. Not merged just for backward
 *    compatibility.
 * <p>
 *    The Root cause exception class name and message are always included
 *    in the exception message.
 *
 *
 */
public class ExceptionHandler {

    private static final String cname =
        "com.sun.messaging.jmq.jmsclient.ExceptionHandler";

    public static final Logger rootLogger =
        Logger.getLogger(ConnectionImpl.ROOT_LOGGER_NAME);

    /**
     * All connection related exceptions are handled by connectionException()
     * method.  If the connection has set its listener, it is called.
     *
     * @param source the source exception
     *
     * @exception JMSException the exception thrown
     */
    //public void
    //connectionException(JMSException source) throws JMSException {
    //    connectionException(source, null);
    //}

    /**
     * All connection related exceptions are handled by connectionException()
     * method.  If the connection has set its listener, it is called.
     *
     * @param source the source exception
     * @param errorCode the error code for the target exception
     *
     * @exception JMSException the target exception thrown
     */
    //public void
    //connectionException(Exception source, String errorCode) throws JMSException {
    //    connectionException(source, errorCode, false);
    //}

    /**
     * All connection related exceptions are handled by connectionException()
     * method.  If the connection has set its listener, it is called.
     *
     * @param source the source exception
     * @param errorCode the error code for the target exception
     * @param format if true, use X_CAUGHT_EXCEPTION format
     *               if false, no format
     *
     * @exception JMSException the target exception thrown
     */
    /**public void
    connectionException(Exception source, String errorCode,
                        boolean format) throws JMSException {

        JMSException target;
        if ((source instanceof JMSException)
                && ((JMSException)source).getErrorCode() != null) {
            target = (JMSException)source;
        }
        else {
            target = getJMSException (source, errorCode, format);
            target.setLinkedException(source);
        }

        //this makes readChannel exits!
        throw target;

    }**/

    /**
     * This is called when creating a connection with host and port.
     */
    public static void
    handleConnectException (Exception source,String host, int port)
    throws JMSException {

        String info = "[" + host + ":" + port + "]";

        throwConnectionException (source, info);
    }

    /**
     * This is called when creating a connection with an url.
     */
    public static void
    handleConnectException(Exception source,String url)
    throws JMSException {

        String info = "[" + url + "]";

        throwConnectionException (source, info);
    }

    /**
     * This method handles connection creation exceptions.
     *
     * @param info [host,port] or url string.
     * @throw JMSException when this method is called, it constructs
     * a JMSException with:
     * 1. set info and root cause as exception message.
     * 2. set X_NET_CREATE_CONNECTION as the exception error code.
     * 3. set source exception to its exception link.
     */
    private static void
    throwConnectionException (Exception source, String info) throws JMSException {

        /**
         * in case we caught a JMSException, just propagate.
         */
        if ((source instanceof JMSException)
                && ((JMSException)source).getErrorCode() != null) {
            //throw (JMSException)source;
            throwJMSException((JMSException)source);
        }

        /**
         * include connection information in the string.
         */
        String errorString0 =
        AdministeredObject.cr.getKString
        (AdministeredObject.cr.X_NET_CREATE_CONNECTION, info);

        /**
         * append root cause message.
         */
        String errorString =
        AdministeredObject.cr.getString
        (AdministeredObject.cr.X_CAUGHT_EXCEPTION,errorString0, source.toString() );

        /**
         * construct jms exception with error msg and error code.
         */
        JMSException jmse =
        new com.sun.messaging.jms.JMSException
        (errorString, AdministeredObject.cr.X_NET_CREATE_CONNECTION);
        
        //log connection exception here so that broker host and port are logged.
        if (rootLogger.isLoggable(Level.WARNING)) {
			if (shouldLog(jmse)) {
				rootLogger.log(Level.WARNING, errorString);
			}
		}

        /**
		 * set exception link.
		 */
        jmse.setLinkedException( source );

        /**
         * fire the exception.
         */
        //throw jmse;
        throwJMSException (jmse);
    }

    /**
     * A general method to convert java exception to JMSException.
     *
     * @param source the source exception
     * @param errorCode the error code for the target exception
     *
     * @exception JMSException the target exception thrown
     */
    public static  void
    handleException(Exception source, String errorCode) throws JMSException {
        handleException(source, errorCode, true);
    }

    /**
     * A general method to convert java exception to JMSException.
     *
     * @param source the source exception
     * @param errorCode the error code for the target exception
     * @param format if true, use X_CAUGHT_EXCEPTION format
     *               if false, no format
     *
     * @exception JMSException the target exception thrown
     */
    public static  void
    handleException(Exception source, String errorCode,
                        boolean format) throws JMSException {

        if ((source instanceof JMSException)
                && ((JMSException)source).getErrorCode() != null) {

            //throw (JMSException)source;
            throwJMSException ((JMSException)source);
        }

        handleException(source, getJMSException(source, errorCode, format));

    }

    /**
     * This method is used to link a source exception to a JMSException
     *
     * @param source the exception source.
     * @param target the exception to be thrown.
     *
     * @exception JMSException the target JMSException thrown
     */
    public static void
    handleException (Exception source, JMSException target) throws JMSException {

        target.setLinkedException(source);

        if ( Debug.debug ) {
            printStackTrace(source);
        }

        //throw target;
        throwJMSException (target);
    }

    public static void printStackTrace(Exception e) {
        Debug.printStackTrace(e);
    }

    /**
     * Construct MQ JMSException with an unique format.
     *
     * NOTE: format is no longer in use.  All exception messages use the
     * same format.
     */
    public static JMSException
    getJMSException(Exception source, String errorCode, boolean format) {

        String errorString = null;

        if ( source == null ) {
            /**
             * This should never happen.
             */
            errorString = AdministeredObject.cr.getKString(errorCode);
        } else {
            errorString = getExceptionMessage(source, errorCode);
        }

        return new com.sun.messaging.jms.JMSException(errorString, errorCode);
    }

    /**
     * Get JMS exception message with the root cause exception and MQ
     * error code.
     *
     * @retutn the MQ JMS Exception error string.
     */
    public static String getExceptionMessage (Exception source, String errorCode) {
        String errorString = null;

        /**
         * if no error code, use generic jvm exception message.
         */
        if (errorCode == null) {
            errorCode = AdministeredObject.cr.X_CAUGHT_EXCEPTION;
        }

        /**
         * error code and message for root cause exception.  for exceptions
         * that directly translates into JMSExceptions use this error code.
         */
        if (errorCode == AdministeredObject.cr.X_CAUGHT_EXCEPTION) {
            errorString = "["+AdministeredObject.cr.X_CAUGHT_EXCEPTION+"]: "
                          + source.toString();
        } else {
            /**
             * for exceptions with error codes other than
             * AdministeredObject.cr.X_CAUGHT_EXCEPTION
             * use the following format.
             */
            String errorString0 = AdministeredObject.cr.getKString(errorCode);
            errorString = AdministeredObject.cr.getString(AdministeredObject.cr.X_CAUGHT_EXCEPTION,
                          errorString0, source.toString() );
        }

        return errorString;
    }

    /**
     * Method to log and throw a JMS exception.
     */
    public static void
        throwJMSException (JMSException jmse) throws JMSException {

        try {
            //log exception
            if (shouldLog(jmse)) {

                //get linked exception
                Throwable source = jmse.getLinkedException();

                //log caught an exception
                if ( source != null ) {
                    logCaughtException(source);
                }

                //log throwing an exception
                rootLogger.log(Level.FINER,
                               AdministeredObject.cr.I_THROW_JMS_EXCEPTION,
                               jmse);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        //we are throwing this one
        throw jmse;

    }

    /**
     * If a JMSException is not Loggable, we would still want to log the
     * exception (at least for now). The non-loggable exception maybe logged
     * more than once.
     *
     * @param jmse JMSException the exception to be logged.
     *
     * @return boolean true if the exception should be logged.
     */
    private static boolean shouldLog (JMSException jmse) {

        //default set to true.
        boolean isLoggable = true;

        //if it is a Loggable type, check the log state.  Otherwise,
        //just log the exception.
        if ( jmse instanceof Loggable ) {

            if ( ((Loggable)jmse).getLogState() ) {
                //if logged, set isLoggable to false so that it does not get
                //logged.
                isLoggable = false;
            } else {
                //set log state to true so that it only get log once.
                ((Loggable)jmse).setLogState(true);
            }
        }

        return isLoggable;
    }

    /**
     * This method is used to log an Exception caught by MQ client runtime.
     * @param source Throwable
     */
    public static void logCaughtException (Throwable source) {

        if ( source != null ) {

            if (rootLogger.isLoggable(Level.WARNING)) {

                String msg = AdministeredObject.cr.getKString(
                             AdministeredObject.cr.I_CAUGHT_JVM_EXCEPTION,
                             source.toString());

                rootLogger.log(Level.WARNING, msg);
            }
        }
    }

    public static void logError (Throwable thrown) {

        if ( thrown != null ) {

            if ( rootLogger.isLoggable(Level.SEVERE) ) {
                String msg = thrown.toString();
                //rootLogger.log(Level.SEVERE, msg, thrown);
                rootLogger.log(Level.SEVERE, msg, thrown);
            }
        }
    }
    
    public static void 
    throwRemoteAcknowledgeException (JMSException source, String errorCode) 
    throws JMSException {
    	
    	//get error string
    	String errorString = AdministeredObject.cr.getKString(errorCode);
    	
    	//create exception
    	JMSException newjmse = new com.sun.messaging.jms.JMSException(
		errorString, errorCode);

    	// set exception link if any
    	if (source != null) {
    		newjmse.setLinkedException(source);
    	}
    	
    	// throw the exception
    	throwJMSException(newjmse);
    }

}
