/**
 * 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 org.ow2.util.ee.metadata.common.api.xml.struct.IHandlerChains;
import org.ow2.util.ee.metadata.common.api.xml.struct.IHandlerChain;
import org.ow2.util.ee.metadata.common.api.xml.struct.IHandler;

import javax.xml.namespace.QName;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.LogicalHandler;
import javax.xml.ws.handler.PortInfo;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

/**
 * The HandlerChainBuilder is ...
 *
 * @author Guillaume Sauthier
 */
public abstract class HandlerChainBuilder {

    /**
     * Build a Handler chain for the given PortInfo.
     * @param info identify the target port for the builded chain
     * @param loader classloader used for Handler class loading
     * @return a new HandlerChain for this port
     */
    public abstract List<Handler> buildHandlerChain(PortInfo info,
                                                    ClassLoader loader);
    /**
     * Build a Handler chain for the given PortInfo.
     * @param handlerChains represents the XML structure
     * @param info identify the target port for the builded chain
     * @param loader classloader used for Handler class loading
     * @return a new HandlerChain for this port
     */
    public List<Handler> buildHandlerChain(final IHandlerChains handlerChains,
                                           final PortInfo info,
                                           final ClassLoader loader) {
        List<Handler> handlers = new ArrayList<Handler>();

        List<IHandlerChain> chains = handlerChains.getHandlerChains();
        for (IHandlerChain chain : chains) {

            // Before creating the chain, check that the current chain
            // really target the current port
            QName portPattern = chain.getPortNamePattern();
            if (!matchesPattern(info.getPortName(), portPattern)) {
                // No matches
                // Try next chain
                continue;
            }
            QName servicePattern = chain.getServiceNamePattern();
            if (!matchesPattern(info.getServiceName(), servicePattern)) {
                // No matches
                // Try next chain
                continue;
            }
            List<String> bindings = chain.getProtocolBindings();
            if (!matchesBinding(info.getBindingID(), bindings)) {
                // No matches
                // Try next chain
                continue;
            }

            // If we are here, this is because we match the PortInfo
            // So we can continue and build the chain

            List<IHandler> handlerList = chain.getHandlers();
            for (IHandler pcHandler : handlerList) {

                HandlerBuilder builder = new HandlerBuilder(pcHandler, loader);
                Handler handler = builder.buildHandler();
                handlers.add(handler);
            }

        }

        return sortHandlers(handlers);
    }

    private boolean matchesBinding(final String bindingID, final List<String> bindings) {

        // Quick tries
        if (bindingID == null) {
            return true;
        }

        if ((bindings == null) || (bindings.isEmpty())) {
            return true;
        }

        // Resolve the bindings
        List<String> resolvedBindings = new ArrayList<String>();
        for (String binding : bindings) {
            resolvedBindings.add(BindingUtils.resolve(binding));
        }

        // Check if the Port's bindings is contained is the list
        return (resolvedBindings.contains(bindingID));
    }

    /**
     * Naive pattern matching method (namespaces are not checked).
     * @param name target QName
     * @param pattern checked pattern (of the form [namespace-prefix:]local-part)
     * @return true if the pattern is matching the current QName
     */
    private boolean matchesPattern(final QName name, final QName pattern) {
        if (pattern == null) {
            // null matches everything
            return true;
        }

        if (("".equals(pattern.getNamespaceURI()))
             && ("*".equals(pattern.getLocalPart()))) {
            // having the QName {""}* matches everything
            return true;
        }

        // Namespaces cannot have wildwards, so just check strict equality
        if (!pattern.getNamespaceURI().equals(name.getNamespaceURI())) {
            // different namespaces, quickly exit
            return false;
        }

        // Try pattern matching
        return Pattern.matches(pattern.getLocalPart(), name.getLocalPart());
    }

    protected List<Handler> sortHandlers(final List<Handler> handlers) {

        List<Handler> logicalHandlers = new ArrayList<Handler>();
        List<Handler> protocolHandlers = new ArrayList<Handler>();

        if (handlers != null) {
            for (Handler handler : handlers) {

                if (handler instanceof LogicalHandler) {
                    logicalHandlers.add(handler);
                } else {
                    protocolHandlers.add(handler);
                }
            }
        }

        // LogicalHandlers first, then ProtocolHandlers
        List<Handler> sortedHandlers = new ArrayList<Handler>();
        sortedHandlers.addAll(logicalHandlers);
        sortedHandlers.addAll(protocolHandlers);

        return sortedHandlers;
    }

}
