/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.services.soap.api.message.dispatcher; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toMap; import static org.mule.runtime.api.metadata.DataType.INPUT_STREAM; import static org.mule.runtime.extension.api.client.DefaultOperationParameters.builder; import static org.mule.service.http.api.HttpConstants.Method.POST; import static org.mule.service.http.api.HttpHeaders.Names.CONTENT_TYPE; import org.mule.runtime.api.lifecycle.InitialisationException; import org.mule.runtime.api.metadata.TypedValue; import org.mule.runtime.api.streaming.bytes.CursorStreamProvider; import org.mule.runtime.extension.api.client.DefaultOperationParameters; import org.mule.runtime.extension.api.client.ExtensionsClient; import org.mule.runtime.extension.api.runtime.operation.Result; import org.mule.runtime.extension.api.soap.message.DispatchingResponse; import org.mule.runtime.extension.api.soap.message.DispatchingRequest; import org.mule.runtime.extension.api.soap.message.MessageDispatcher; import org.mule.services.soap.api.exception.DispatchingException; import com.google.common.collect.ImmutableMap; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Map; import java.util.Optional; /** * A {@link MessageDispatcher} that dispatches the SOAP request via HTTP using an HTTP connector provided configuration. * <p> * As the this lives in mule and it cannot depend on the HTTP extension, reflection is used to access the returned headers. * * @since 4.0 */ public final class HttpConfigBasedMessageDispatcher implements MessageDispatcher { private final String configName; private final ExtensionsClient client; public HttpConfigBasedMessageDispatcher(String configName, ExtensionsClient client) { this.configName = configName; this.client = client; } /** * {@inheritDoc} * <p> * Dispatches the message using the {@link ExtensionsClient} executing the {@code request} operation of the HTTP extension. * <p> * This method executes the request operation with the http {@code parseResponse} flag set to false in order to receive always * the complete response stream and to pass it to the service to process it. */ @Override public DispatchingResponse dispatch(DispatchingRequest context) { ImmutableMap<Object, Object> headers = ImmutableMap.builder() .putAll(context.getHeaders()) // It's important that content type is bundled with the headers .put(CONTENT_TYPE, context.getContentType()) .build(); DefaultOperationParameters params = builder().configName(configName) .addParameter("method", POST.toString()) .addParameter("url", context.getAddress()) .addParameter("headers", headers) .addParameter("parseResponse", false) .addParameter("body", new TypedValue<>(context.getContent(), INPUT_STREAM)) .build(); try { Result<Object, Object> result = client.executeAsync("HTTP", "request", params).get(); Map<String, String> httpHeaders = getHttpHeaders(result); InputStream content = getContent(result); return new DispatchingResponse(content, httpHeaders.get(CONTENT_TYPE.toLowerCase()), httpHeaders); } catch (Exception e) { throw new DispatchingException("Could not dispatch soap message using the [" + configName + "] HTTP configuration", e); } } /** * Reflectively introspects the result to find the HTTP Headers. * * @param result the {@link Result} returned by the http request operation */ private Map<String, String> getHttpHeaders(Result<Object, Object> result) { try { Optional httpAttributes = result.getAttributes(); if (!httpAttributes.isPresent()) { throw new IllegalStateException("No Http Attributes found on the response, cannot get response headers."); } Object headers = httpAttributes.get().getClass().getMethod("getHeaders").invoke(httpAttributes.get()); Map<String, List<String>> map = (Map<String, List<String>>) headers.getClass().getMethod("toListValuesMap").invoke(headers); return map.entrySet().stream().collect(toMap(e -> e.getKey(), e -> e.getValue().stream().collect(joining(" ")))); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new IllegalStateException("Something went wrong when introspecting the http response attributes.", e); } } /** * Retrieves the content as an input stream validating it. */ private InputStream getContent(Result<Object, Object> result) { Object output = result.getOutput(); if (output instanceof CursorStreamProvider) { return ((CursorStreamProvider) output).openCursor(); } else if (output instanceof InputStream) { return (InputStream) output; } throw new IllegalStateException("Content was expected to be an stream but got a [" + output.getClass().getName() + "]"); } @Override public void dispose() { // do nothing } @Override public void initialise() throws InitialisationException { // do nothing } }