/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2000-2010 Oracle and/or its affiliates. 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_1_1.html
 * or packager/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 packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [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.messaging.ums.core;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
//import java.io.PrintWriter;
import java.util.Properties;

import java.util.logging.*;

import javax.xml.soap.Detail;
import javax.xml.soap.Name;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPFault;
import javax.xml.soap.SOAPBody;

import com.sun.messaging.ums.common.MessageUtil;

/**
 * <p>A MQ SOAP Service is a class that implements SOAPService interface and
 * may be used to *register* its service to a MQSOAPServlet as a SOAP
 * service and becomes part of the MQ SOAP Service frame work.
 *
 * <p>A MQ SOAP service consists of the following components:
 *
 * <p>1. Request Handler Chain.  The Request handler can be registered as follows
 * in the web.xml:
 *
 * <p>mq.soap.request.handler.#="MessageHandler class full name"
 *
 * <p>For example,
 *
 * <p>mq.soap.request.handler.1=com.sun.TestMessageListener1
 * <p>mq.soap.request.handler.2=com.sun.TestMessageListener2
 *
 * <p>2. Response Handler Chain.  The Response handler can be registered as
 * follows in the web.xml:
 *
 * <p>mq.soap.response.handler.#=MessageHandler class full name.
 *
 * <p>For example,
 *
 * <p>mq.soap.response.handler.1=com.sun.TestMessageListener1
 * <p>mq.soap.response.handler.2=com.sun.TestMessageListener2
 *
 * <p>3. A service() method to be over ridden by subclass.
 *
 * <p>4. Service lifecycle management methods.  There are four methods defined
 * for life cycle management - init/start/stop/close.  They are used for
 * init/start/stop/close a MQ SOAP Service instance.  Sub class SHOULD
 * implement or over ride the life cycle methods if necessary.
 *
 *
 * @author  chiaming yang
 * 
 * @see     SOAPService
 * @see     MessageHandler
 * @see     MessageHandlerChain
 */
public abstract class UMSService implements SOAPService {

    /**
     * logger category name.
     */
    public static final String LOGGER_CAT_NAME =
    "com.sun.messaging.ums.MQSOAPService";

    /**
     * SOAP context associated with this SOAP Service.
     */
    protected ServiceContext serviceContext = null;

    /**
     * init properties.
     */
    protected Properties props = null;

    /**
     * request handler chain.
     */
    protected MessageHandlerChain reqChain = new MessageHandlerChain();

    /**
     * resp handler chain.
     */
    protected MessageHandlerChain respChain = new MessageHandlerChain();

    /**
     * logger.
     */
    private Logger logger = Logger.getLogger( LOGGER_CAT_NAME );

    /**
     * request handler registration name prefix.
     * for example, define the following in the web.xml:
     *
     * <p>mq.soap.request.handler.1=com.sun.TestMessageListener1
     * <p>mq.soap.request.handler.2=com.sun.TestMessageListener2
     */
    public static final String REQUEST_HANDLER_PREFIX =
    "mq.soap.request.handler.";

    /**
     * response handler registration name prefix.
     * for example, define the following in the web.xml:
     *
     * <p>mq.soap.response.handler.1=com.sun.TestMessageListener3
     * <p>mq.soap.response.handler.2=com.sun.TestMessageListener4
     */
    public static final String RESPONSE_HANDLER_PREFIX =
    "mq.soap.response.handler.";

    /**
     * init this SOAPService with the specified Properties in the parameter.
     */
    public void init (ServiceContext context) throws SOAPException {
        this.serviceContext = context;

        this.props = context.getInitProperties();

        initHandlerChains();
    }

    /**
     * init req/resp handler chains.
     */
    protected void initHandlerChains () {
        initHandlers (REQUEST_HANDLER_PREFIX, this.reqChain);
        initHandlers (RESPONSE_HANDLER_PREFIX, this.respChain);
    }

    /**
     * Load and register message handlers to the handler chain.
     */
    protected void initHandlers (String prefix, MessageHandlerChain chain) {

        boolean moreHandlers = true;

        try {
            int index = 1;
            while ( moreHandlers ) {

                /**
                 * Get handler property name sequentially.
                 */
                String propName = prefix + index;

                /**
                 * get handler class name.
                 */
                String className = props.getProperty(propName);

                /**
                 * if cannot find the next class for the next sequence,
                 * stop right here.
                 */
                if ( className == null ) {
                    moreHandlers = false;
                } else {

                    /**
                     * load the handler with its class name.
                     */
                    MessageHandler handler =
                    (MessageHandler) Class.forName(className).newInstance();

                    /**
                     * init the handler with current service context.
                     */
                    handler.init( this.getServiceContext() );

                    /**
                     * add the handler to the chain.
                     */
                    chain.addMessageHandler(handler);

                    /**
                     * increase index for the next handler property name.
                     */
                    index ++;
                }
            }
        } catch (Exception e) {
            logger.log( Level.SEVERE, e.getMessage(), e );
        }
    }

    /**
     * Get req handler chain.
     */
    public MessageHandlerChain getReqHandlerChain() {
        return this.reqChain;
    }

    /**
     * Get resp handler chain.
     */
    public MessageHandlerChain getRespHandlerChain() {
        return this.respChain;
    }

    /**
     * SOAP service life cycle - start this soap service.
     * To be overriden by sub class.
     */
    public void start() {
        ;
    }

    /**
     * SOAP service life cycle - stop this soap service.
     * To be overriden by sub class.
     */
    public void stop() {
        ;
    }

    /**
     * SOAP service life cycle - close this soap service.
     * Close all message handlers and release resources.
     */
    public void close() {
        closeHandlers(this.reqChain);
        closeHandlers(this.respChain);
    }

    /**
     * Get the ServiceContext object associated with this SOAP service.
     */
    public ServiceContext getServiceContext() {
        return this.serviceContext;
    }


    /**
     * Get this soap service URI.
     */
    public String getServiceName() {
        return "MQSOAPService";
    }

    /**
     * MQ SOAP Service implements this interafce.
     *
     * <p>The request handler chain is processed in sequence before calling
     * the service() method.
     * <p>The respond handler chain is processed in sequence after calling
     * the service() method.
     */
    public SOAPMessage onMessage (SOAPMessage message) {

        SOAPMessage reply = null;

        logger.fine("MQSOAPService received message.");

        try {

            UMSMessageContext context = new UMSMessageContext();
            context.setRequestMessage( message );

            logger.fine("created msg context: " + context);

            logger.fine("*** processing request chain ...");
            processHandlerChain (this.reqChain, context);

            /**
             * To be over ridden by subclass.
             */
            service (context);

            //context.setResponseMessage(reply);

            logger.fine("*** processing response chain ...");
            processHandlerChain(this.respChain, context);

            reply = context.getResponseMessage();

        } catch (MessageHandlerException mhe) {

            mhe.printStackTrace(System.out);

            logger.log(Level.WARNING, mhe.getMessage(), mhe);

            /**
             * if there is a soap fault message in the exception,
             * use it.  Otherwise, construct one and set error
             * code and string to *Client* and *Client Error*.
             */
            reply = mhe.getSOAPFaultMessage();

            if ( reply == null ) {
                reply = createSOAPFaultMessage(mhe, "Client", "Client Error");
            }

        } catch (Throwable throwe) {
            logger.log(Level.WARNING, throwe.getMessage(), throwe);

            /**
             * For the reset of the exception, assume it is the
             * server unable to process the message.  This implies
             * that soap header processing SHOULD be handled in
             * the Message Handler.  Validation of the SOAP headers
             * SHOULD throw MessageHandlerException if unable to
             * process the message.
             */
            reply = createSOAPFaultMessage (throwe, "Server", "Server Error");
        } finally {
            ;
        }

        return reply;

    }

    /**
     * To be over ridden by sub class.
     */
    public abstract void service (MessageContext context) throws SOAPException;

    /**
     * process message handler chain.  Message handles are called in sequesne.
     */
    protected void
    processHandlerChain (MessageHandlerChain chain, MessageContext context)
    throws SOAPException {

        int size = chain.size();
        for ( int index = 0; index < size; index++) {

            MessageHandler handler =
            (MessageHandler) chain.getMessageHandlerAt(index);

            logger.fine("Calling handler: " + handler.getClass().getName());

            handler.processMessage(context);
        }

    }

    /**
     * close message handlers for the specified handler chain.
     */
    protected void closeHandlers (MessageHandlerChain chain) {

        /**
         * get the size of the chain.
         */
        int size = chain.size();

        /**
         * loop through the chain.
         */
        for ( int index = 0; index < size; index ++ ) {

            try {

                /**
                 * get the next message handler.
                 */
                MessageHandler handler =
                (MessageHandler) chain.getMessageHandlerAt(index);

                /**
                 * close the handler.
                 */
                handler.close();

            } catch (Throwable t) {
                logger.log(Level.WARNING, t.getMessage(), t);
            }
        }
    }

    /**
     * Create a soap fault message and set its error code and
     * error string as specified in the parameter.
     */
    public static SOAPMessage
    createSOAPFaultMessage (Throwable t, String faultCode, String faultString) {

        SOAPMessage soapFault = null;

        try {
            MessageFactory mf = MessageFactory.newInstance();

            soapFault = mf.createMessage();

            SOAPEnvelope env =
            soapFault.getSOAPPart().getEnvelope();

            SOAPBody body = env.getBody();
            SOAPFault faultElement = body.addFault();

            String soapNs = env.getElementName().getPrefix();
            String fcode = soapNs + ":" + faultCode;

            faultElement.setFaultCode( fcode );

            faultElement.setFaultString( faultString );
            
            Detail detail = faultElement.getDetail();
            if ( detail == null ) {
            	detail = faultElement.addDetail();
            }
            
            Name stname = MessageUtil.createJMSName("StackTrace");
            SOAPElement entryEle = detail.addDetailEntry(stname);
                      
            //get stack trace
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            PrintStream ps = new PrintStream (baos);
            t.printStackTrace(ps);
            
            ps.close();
            
            String trace = baos.toString("utf8");
         
            entryEle.setValue(trace);
            
            soapFault.saveChanges();

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

        return soapFault;
    }
}

