/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2009 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */


package org.ow2.jonas.ws.jaxws.handler.builder;

import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.List;

import javax.jws.HandlerChain;
import javax.jws.WebService;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.PortInfo;

import org.ow2.util.ee.metadata.common.api.xml.struct.IHandlerChains;
import org.ow2.util.ee.metadata.common.impl.xml.parsing.HandlerChainsLoader;
import org.ow2.util.ee.metadata.common.impl.xml.parsing.ParsingException;

public class AnnotationHandlerChainBuilder extends HandlerChainBuilder {

    /**
     * HandlerChains structure.
     */
    private IHandlerChains handlerChainsElement;

    /**
     * This constructors has to be used on the endpoint side (@WebService annotated class).
     * @param clazz supporting class
     */
    public AnnotationHandlerChainBuilder(final Class<?> clazz) {

        HandlerChainAnnotation annotation = findHandlerChainAnnotation(clazz, true);

        // If we found an annotation, use it
        if (annotation != null) {
            String file = annotation.getHandlerChain().file().trim();

            if ((file == null) || ("".equals(file))) {
                throw new WebServiceException("@HandlerChain annotation on '" + clazz
                        + "' is missing a valid 'file' value.");
            }

            URL location = getHandlerChainsLocation(file, annotation.getDeclaringClass());

            try {
                handlerChainsElement = HandlerChainsLoader.loadHandlerChains(location, true);
            } catch (ParsingException e) {
                throw new WebServiceException("Cannot parse <handler-chains>", e);
            }

        }
    }

    /**
     * Build the chain.
     * @param info identify the target port for the builded chain
     * @param loader classloader used for Handler class loading
     * @return a Handler's List
     */
    @Override
    public List<Handler> buildHandlerChain(final PortInfo info, final ClassLoader loader) {
        List<Handler> handlers = Collections.emptyList();
        if (handlerChainsElement != null) {
            handlers = buildHandlerChain(handlerChainsElement, info, loader);
        }
        return handlers;
    }

    /**
     * Find the URL of the handler-chains.xml file.
     * @param file filename
     * @param declaringClass relative class
     * @return the file's URL
     */
    private URL getHandlerChainsLocation(final String file, final Class<?> declaringClass) {

        URL location;
        // First try if this is an absolute URL
        try {
            location = new URL(file);
        } catch (IOException mue) {
            // OK, this is not an absolute URL
            // Try to resolve the resource as a Classloader resource
            location = declaringClass.getResource(file);
        }

        return location;
    }

    /**
     * Find a couple HandlerChain/Class
     * @param clazz introspected class
     * @param searchSEI search the serviceEndpointInterface ?
     * @return a HandlerChain/Class couple or null if none found
     */
    private HandlerChainAnnotation findHandlerChainAnnotation(final Class<?> clazz, final boolean searchSEI) {

        HandlerChainAnnotation annotation = null;

        HandlerChain chain = clazz.getAnnotation(HandlerChain.class);

        if (chain == null) {

            if (searchSEI) {
                // Try to look on the SEI
                WebService ws = clazz.getAnnotation(WebService.class);

                if (((ws != null) && (!"".equals(ws.endpointInterface())))) {
                    ClassLoader loader = clazz.getClassLoader();
                    String seClassname = ws.endpointInterface();

                    Class<?> sei;
                    try {
                         sei = Class.forName(seClassname, true, loader);
                    } catch (ClassNotFoundException e) {
                        throw new WebServiceException("Cannot load SEI class '" + seClassname + "'", e);
                    }

                    annotation = findHandlerChainAnnotation(sei, false);

                }
            }

            if (annotation == null) {
                // At this point, the SEI do not have any @HandlerChain
                // Next step: search the interfaces
                for (Class<?> itf : clazz.getInterfaces()) {

                    HandlerChain itfHC = itf.getAnnotation(HandlerChain.class);
                    if (itfHC != null) {
                        annotation = new HandlerChainAnnotation(itfHC, itf);
                        // Got a @HandlerChain, stop the loop
                        break;
                    }
                }
            }
        } else {
            // We have a HandlerChain annotation
            annotation = new HandlerChainAnnotation(chain, clazz);
        }

        return annotation;

    }


    /**
     * The class permits to remember where the @HandlerChain annotation comes from.
     * this have to be used because JAX-WS specify that the 'file' attribute has to
     * be resolved relatively to the declaring class.
     */
    private class HandlerChainAnnotation {

        /**
         * The annotation.
         */
        private HandlerChain handlerChain;

        /**
         * Class declaring the annotation.
         */
        private Class<?> declaringClass;

        public HandlerChainAnnotation(final HandlerChain chain, final Class<?> declaringClass) {
            this.handlerChain = chain;
            this.declaringClass = declaringClass;
        }

        public HandlerChain getHandlerChain() {
            return handlerChain;
        }

        public Class<?> getDeclaringClass() {
            return declaringClass;
        }
    }
}
