/**
 * 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.camel.util;

import java.util.HashMap;
import java.util.Map;

import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePattern;
import org.apache.camel.InvalidPayloadException;
import org.apache.camel.InvalidTypeException;
import org.apache.camel.Message;
import org.apache.camel.NoSuchBeanException;
import org.apache.camel.NoSuchEndpointException;
import org.apache.camel.NoSuchHeaderException;
import org.apache.camel.NoSuchPropertyException;
import org.apache.camel.NoTypeConversionAvailableException;

/**
 * Some helper methods for working with {@link Exchange} objects
 *
 * @version $Revision: 65776 $
 */
public final class ExchangeHelper {

    /**
     * Utility classes should not have a public constructor.
     */
    private ExchangeHelper() {
    }

    /**
     * Extracts the exchange property of the given name and type; if it is not present then the
     * default value will be used
     *
     * @param exchange the message exchange
     * @param propertyName the name of the property on the exchange
     * @param type the expected type of the property
     * @param defaultValue the default value to be used if the property name does not exist or could not be
     * converted to the given type
     * @return the property value as the given type or the defaultValue if it could not be found or converted
     */
    public static <T> T getExchangeProperty(Exchange exchange, String propertyName, Class<T> type, T defaultValue) {
        T answer = exchange.getProperty(propertyName, type);
        if (answer == null) {
            return defaultValue;
        }
        return answer;
    }


    /**
     * Attempts to resolve the endpoint for the given value
     *
     * @param exchange the message exchange being processed
     * @param value the value which can be an {@link Endpoint} or an object
     *                which provides a String representation of an endpoint via
     *                {@link #toString()}
     *
     * @return the endpoint
     * @throws NoSuchEndpointException if the endpoint cannot be resolved
     */
    @SuppressWarnings({"unchecked" })
    public static <E extends Exchange> Endpoint<E> resolveEndpoint(E exchange, Object value)
        throws NoSuchEndpointException {
        Endpoint<E> endpoint;
        if (value instanceof Endpoint) {
            endpoint = (Endpoint<E>)value;
        } else {
            String uri = value.toString();
            endpoint = CamelContextHelper.getMandatoryEndpoint(exchange.getContext(), uri);
        }
        return endpoint;
    }

    public static <T> T getMandatoryProperty(Exchange exchange, String propertyName, Class<T> type)
        throws NoSuchPropertyException {
        try {
            T result = exchange.getProperty(propertyName, type);
            if (result != null) {
                return result;
            }
        } catch (NoTypeConversionAvailableException ex) {
            // will throw NoSuchPropertyException below
        }
        throw new NoSuchPropertyException(exchange, propertyName, type);
    }

    public static <T> T getMandatoryHeader(Exchange exchange, String propertyName, Class<T> type)
        throws NoSuchHeaderException {
        T answer = exchange.getIn().getHeader(propertyName, type);
        if (answer == null) {
            throw new NoSuchHeaderException(exchange, propertyName, type);
        }
        return answer;
    }

    /**
     * Returns the mandatory inbound message body of the correct type or throws
     * an exception if it is not present
     */
    public static Object getMandatoryInBody(Exchange exchange) throws InvalidPayloadException {
        Object answer = exchange.getIn().getBody();
        if (answer == null) {
            throw new InvalidPayloadException(exchange, Object.class);
        }
        return answer;
    }

    /**
     * Returns the mandatory inbound message body of the correct type or throws
     * an exception if it is not present
     */
    public static <T> T getMandatoryInBody(Exchange exchange, Class<T> type) throws InvalidPayloadException {
        T answer = exchange.getIn().getBody(type);
        if (answer == null) {
            throw new InvalidPayloadException(exchange, type);
        }
        return answer;
    }

    /**
     * Returns the mandatory outbound message body of the correct type or throws
     * an exception if it is not present
     */
    public static Object getMandatoryOutBody(Exchange exchange) throws InvalidPayloadException {
        Message out = exchange.getOut();
        Object answer = out.getBody();
        if (answer == null) {
            throw new InvalidPayloadException(exchange, Object.class, out);
        }
        return answer;
    }

    /**
     * Returns the mandatory outbound message body of the correct type or throws
     * an exception if it is not present
     */
    public static <T> T getMandatoryOutBody(Exchange exchange, Class<T> type) throws InvalidPayloadException {
        Message out = exchange.getOut();
        T answer = out.getBody(type);
        if (answer == null) {
            throw new InvalidPayloadException(exchange, type, out);
        }
        return answer;
    }

    /**
     * Converts the value to the given expected type or throws an exception
     */
    public static <T> T convertToMandatoryType(Exchange exchange, Class<T> type, Object value)
        throws InvalidTypeException {
        T answer = convertToType(exchange, type, value);
        if (answer == null) {
            throw new InvalidTypeException(exchange, value, type);
        }
        return answer;
    }

    /**
     * Converts the value to the given expected type returning null if it could
     * not be converted
     */
    public static <T> T convertToType(Exchange exchange, Class<T> type, Object value) {
        return exchange.getContext().getTypeConverter().convertTo(type, exchange, value);
    }

    /**
     * Copies the results of a message exchange from the source exchange to the result exchange
     * which will copy the out and fault message contents and the exception
     *
     * @param result the result exchange which will have the output and error state added
     * @param source the source exchange which is not modified
     */
    public static void copyResults(Exchange result, Exchange source) {
        if (result != source) {
            result.setException(source.getException());
            Message fault = source.getFault(false);
            if (fault != null) {
                result.getFault(true).copyFrom(fault);
            }

            Message out = source.getOut(false);
            if (out != null) {
                result.getOut(true).copyFrom(out);
            } else {
                // no results so lets copy the last input
                // as the final processor on a pipeline might not
                // have created any OUT; such as a mock:endpoint
                // so lets assume the last IN is the OUT
                if (result.getPattern().isOutCapable()) {
                    // only set OUT if its OUT capable
                    result.getOut(true).copyFrom(source.getIn());
                } else {
                    // if not replace IN instead to keep the MEP
                    result.getIn().copyFrom(source.getIn());
                }
            }
            result.getProperties().clear();
            result.getProperties().putAll(source.getProperties());
        }
    }

    /**
     * Returns true if the given exchange pattern (if defined) can support IN messagea
     *
     * @param exchange the exchange to interrogate
     * @return true if the exchange is defined as an {@link ExchangePattern} which supports
     * IN messages
     */
    public static boolean isInCapable(Exchange exchange) {
        ExchangePattern pattern = exchange.getPattern();
        return pattern != null && pattern.isInCapable();
    }

    /**
     * Returns true if the given exchange pattern (if defined) can support OUT messagea
     *
     * @param exchange the exchange to interrogate
     * @return true if the exchange is defined as an {@link ExchangePattern} which supports
     * OUT messages
     */
    public static boolean isOutCapable(Exchange exchange) {
        ExchangePattern pattern = exchange.getPattern();
        return pattern != null && pattern.isOutCapable();
    }

    /**
     * Creates a new instance of the given type from the injector
     */
    public static <T> T newInstance(Exchange exchange, Class<T> type) {
        return exchange.getContext().getInjector().newInstance(type);
    }

    /**
     * Creates a Map of the variables which are made available to a script or template
     *
     * @param exchange the exchange to make available
     * @return a Map populated with the require dvariables
     */
    public static Map createVariableMap(Exchange exchange) {
        Map answer = new HashMap();
        populateVariableMap(exchange, answer);
        return answer;
    }

    /**
     * Populates the Map with the variables which are made available to a script or template
     *
     * @param exchange the exchange to make available
     * @param map      the map to populate
     */
    public static void populateVariableMap(Exchange exchange, Map map) {
        map.put("exchange", exchange);
        Message in = exchange.getIn();
        map.put("in", in);
        map.put("request", in);
        map.put("headers", in.getHeaders());
        map.put("body", in.getBody());
        if (isOutCapable(exchange)) {
            Message out = exchange.getOut(true);
            map.put("out", out);
            map.put("response", out);
        }
        map.put("camelContext", exchange.getContext());
    }

    /**
     * Returns the MIME content type on the input message or null if one is not defined
     */
    public static String getContentType(Exchange exchange) {
        return exchange.getIn().getHeader("Content-Type", String.class);
    }

    /**
     * Performs a lookup in the registry of the mandatory bean name and throws an exception if it could not be found
     */
    public static Object lookupMandatoryBean(Exchange exchange, String name) {
        Object value = lookupBean(exchange, name);
        if (value == null) {
            throw new NoSuchBeanException(name);
        }
        return value;
    }

    /**
     * Performs a lookup in the registry of the mandatory bean name and throws an exception if it could not be found
     */
    public static <T> T lookupMandatoryBean(Exchange exchange, String name, Class<T> type) {
        T value = lookupBean(exchange, name, type);
        if (value == null) {
            throw new NoSuchBeanException(name);
        }
        return value;
    }

    /**
     * Performs a lookup in the registry of the bean name
     */
    public static Object lookupBean(Exchange exchange, String name) {
        return exchange.getContext().getRegistry().lookup(name);
    }

    /**
     * Performs a lookup in the registry of the bean name and type
     */
    public static <T> T lookupBean(Exchange exchange, String name, Class<T> type) {
        return exchange.getContext().getRegistry().lookup(name, type);
    }
}
