/*
 * 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 org.apache.axis2.jaxws;

import org.apache.axis2.jaxws.i18n.Messages;
import org.apache.axis2.jaxws.utility.JavaUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Controls error logging of exceptions thrown by the WebService application (provider, impl, etc.)
 * 
 * This class logs errors for all non-checked exceptions.
 * This class logs extensive debug information for all exceptions.
 */
public class WebServiceExceptionLogger {
    private static final Log log = LogFactory.getLog(WebServiceExceptionLogger.class);
    
    /**
     * Logs an error if the exception thrown by @WebMethod m is not a checked exception.
     * If debug logging is enabled, all exceptions are logged.
     * @param method
     * @param throwable
     * @param logFully (if true then the exception is logged, otherwise only the class and stack is logged)
     * @param serviceImplClass class of service implementation
     * @param serviceInstance 
     * @param args Object[] arguments pass to method
     */
    public static void log(Method method, 
                           Throwable throwable, 
                           boolean logFully,
                           Class serviceImplClass,
                           Object serviceInstance,
                           Object[] args) {

        // No matter what happens in this logging method, do not surface that exception.  We don't want a logging
        // failure to mask the real exception we are trying to log or affect subsequent processing.
        try {
                
            // Must have debug or error logging enabled
            if (!log.isDebugEnabled() && !log.isErrorEnabled()) {
                return;
            }
            
            // Get the root of the exception
            Throwable rootT = null;
            if (throwable instanceof InvocationTargetException) {
                rootT = ((InvocationTargetException) throwable).getTargetException();
            } else {
                rootT = throwable;
            }
            
            String name = rootT.getClass().getName();
            log.debug("693210: root Throwable, may cause index error: " + rootT.toString(), rootT);
            String stack = stackToString(rootT);
            
            // Determine if this is a checked exception or non-checked exception
            Class checkedException = JavaUtils.getCheckedException(rootT, method);
            
            if (checkedException == null) {
                // Only log errors for non-checked exceptions
                if (log.isErrorEnabled()) {
                    String text = "";
                    if (logFully) {
                        text = Messages.getMessage("failureLogger", name, rootT.toString());
                       
                    } else {
                        text = Messages.getMessage("failureLogger", name, stack);
                    }
                    log.error(text);
                }
                
            } 
            
            // Full logging if debug is enabled.
            if (log.isDebugEnabled()) {
                log.debug("Exception invoking a method of " + serviceImplClass.toString()
                        + " of instance " + serviceInstance.toString());
                log.debug("Exception type thrown: " + throwable.getClass().getName());
                if (rootT != null) {
                    log.debug("Root Exception type thrown: " + rootT.getClass().getName());
                }
                if (checkedException != null) {
                    log.debug("The exception is an instance of checked exception: " + 
                              checkedException.getName());
                }
                
                // Extra trace if ElementNSImpl incompatibility problem.
                // The incompatibility exception occurs if the JAXB Unmarshaller 
                // unmarshals to a dom element instead of a generated object.  This can 
                // result in class cast exceptions.  The solution is usually a missing
                // @XmlSeeAlso annotation in the jaxws or jaxb classes. 
                if (rootT.toString().contains("org.apache.xerces.dom.ElementNSImpl incompatible")) {
                	log.debug("This exception may be due to a missing @XmlSeeAlso in the client's jaxws or" +
                			" jaxb classes.");
                }
                log.debug("Method = " + method.toGenericString());
                for (int i = 0; i < args.length; i++) {
                    String value =
                            (args[i] == null) ? "null"
                                    : args[i].getClass().toString();
                    log.debug(" Argument[" + i + "] is " + value);
                }
            }
        } catch (Throwable t) {
            if (log.isDebugEnabled()) {
                log.debug("Caught throwable in logger", t);
                log.debug("While attempting to log original exception", throwable);
            }
        }
        return;
        
    }
    
    /**
     * Get a string containing the stack of the specified exception
     *
     * @param e
     * @return
     */
    static String stackToString(Throwable e) {
        java.io.StringWriter sw = new java.io.StringWriter();
        java.io.BufferedWriter bw = new java.io.BufferedWriter(sw);
        java.io.PrintWriter pw = new java.io.PrintWriter(bw);
        e.printStackTrace(pw);
        pw.close();
        String text = sw.getBuffer().toString();
        // Jump past the name of the throwable cause in the stack
        int indexOfThrowable = text.indexOf("at");
        if (indexOfThrowable > 0) {
            text = text.substring(indexOfThrowable);
        }
        return text;
    }
    
    
}
