/**
* 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.component.cxf.common.header;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.camel.Exchange;
import org.apache.camel.component.cxf.common.message.CxfConstants;
import org.apache.camel.spi.HeaderFilterStrategy;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.helpers.CastUtils;
import org.apache.cxf.message.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility class to propagate headers to and from CXF message.
*
* @version
*/
public final class CxfHeaderHelper {
private static final Logger LOG = LoggerFactory.getLogger(CxfHeaderHelper.class);
private static final Map<String, String> CAMEL_TO_CXF_HEADERS = new HashMap<>();
private static final Map<String, String> CXF_TO_CAMEL_HEADERS = new HashMap<>();
static {
// initialize mappings between Camel and CXF header names
defineMapping(Exchange.HTTP_URI, Message.REQUEST_URI);
defineMapping(Exchange.HTTP_METHOD, Message.HTTP_REQUEST_METHOD);
defineMapping(Exchange.HTTP_PATH, Message.PATH_INFO);
defineMapping(Exchange.CONTENT_TYPE, Message.CONTENT_TYPE);
defineMapping(Exchange.HTTP_CHARACTER_ENCODING, Message.ENCODING);
defineMapping(Exchange.HTTP_QUERY, Message.QUERY_STRING);
defineMapping(Exchange.ACCEPT_CONTENT_TYPE, Message.ACCEPT_CONTENT_TYPE);
defineMapping(Exchange.HTTP_RESPONSE_CODE, Message.RESPONSE_CODE);
}
/**
* Utility class does not have public constructor
*/
private CxfHeaderHelper() {
}
private static void defineMapping(String camelHeader, String cxfHeader) {
CAMEL_TO_CXF_HEADERS.put(camelHeader, cxfHeader);
CXF_TO_CAMEL_HEADERS.put(cxfHeader, camelHeader);
}
/**
* Propagates Camel headers to CXF headers.
*
* @param strategy header filter strategy
* @param camelHeaders Camel headers
* @param requestHeaders CXF request headers
* @param camelExchange provides context for filtering
*/
public static void propagateCamelHeadersToCxfHeaders(HeaderFilterStrategy strategy,
Map<String, Object> camelHeaders, Map<String, List<String>> requestHeaders,
Exchange camelExchange) throws Exception {
if (strategy == null) {
return;
}
camelHeaders.entrySet().forEach(entry -> {
// Need to make sure the cxf needed header will not be filtered
if (strategy.applyFilterToCamelHeaders(entry.getKey(), entry.getValue(), camelExchange)
&& CAMEL_TO_CXF_HEADERS.get(entry.getKey()) == null) {
LOG.trace("Drop Camel header: {}={}", entry.getKey(), entry.getValue());
return;
}
// we need to make sure the entry value is not null
if (entry.getValue() == null) {
LOG.trace("Drop Camel header: {}={}", entry.getKey(), entry.getValue());
return;
}
String cxfHeaderName = CAMEL_TO_CXF_HEADERS.getOrDefault(entry.getKey(), entry.getKey());
LOG.trace("Propagate Camel header: {}={} as {}", entry.getKey(), entry.getValue(), cxfHeaderName);
requestHeaders.put(cxfHeaderName, Arrays.asList(entry.getValue().toString()));
});
}
/**
* Propagates Camel headers to CXF message.
*
* @param strategy header filter strategy
* @param camelHeaders Camel header
* @param cxfMessage CXF message
* @param exchange provides context for filtering
*/
public static void propagateCamelToCxf(HeaderFilterStrategy strategy,
Map<String, Object> camelHeaders, Message cxfMessage, Exchange exchange) {
// use copyProtocolHeadersFromCxfToCamel treemap to keep ordering and ignore key case
cxfMessage.putIfAbsent(Message.PROTOCOL_HEADERS, new TreeMap<>(String.CASE_INSENSITIVE_ORDER));
final Map<String, List<String>> cxfHeaders =
CastUtils.cast((Map<?, ?>) cxfMessage.get(Message.PROTOCOL_HEADERS));
if (strategy == null) {
return;
}
camelHeaders.entrySet().forEach(entry -> {
// Need to make sure the cxf needed header will not be filtered
if (strategy.applyFilterToCamelHeaders(entry.getKey(), entry.getValue(), exchange)) {
LOG.trace("Drop external header: {}={}", entry.getKey(), entry.getValue());
return;
}
// we need to make sure the entry value is not null
if (entry.getValue() == null) {
LOG.trace("Drop Camel header: {}={}", entry.getKey(), entry.getValue());
return;
}
String cxfHeaderName = CAMEL_TO_CXF_HEADERS.getOrDefault(entry.getKey(), entry.getKey());
LOG.trace("Propagate Camel header: {}={} as {}", entry.getKey(), entry.getValue(), cxfHeaderName);
if (Exchange.CONTENT_TYPE.equals(entry.getKey())) {
cxfMessage.put(cxfHeaderName, entry.getValue());
}
if (Exchange.HTTP_RESPONSE_CODE.equals(entry.getKey())
|| Client.REQUEST_CONTEXT.equals(entry.getKey())
|| Client.RESPONSE_CONTEXT.equals(entry.getKey())) {
cxfMessage.put(cxfHeaderName, entry.getValue());
} else {
Object values = entry.getValue();
if (values instanceof List<?>) {
cxfHeaders.put(cxfHeaderName, CastUtils.cast((List<?>) values, String.class));
} else {
List<String> listValue = new ArrayList<>();
listValue.add(entry.getValue().toString());
cxfHeaders.put(cxfHeaderName, listValue);
}
}
});
}
/**
* Propagates CXF headers to Camel headers.
*
* @param strategy header filter strategy
* @param responseHeaders CXF response headers
* @param camelHeaders Camel headers
* @param camelExchange provides context for filtering
*/
public static void propagateCxfHeadersToCamelHeaders(HeaderFilterStrategy strategy,
Map<String, List<Object>> responseHeaders, Map<String, Object> camelHeaders,
Exchange camelExchange) throws Exception {
if (strategy == null) {
return;
}
responseHeaders.entrySet().forEach(entry -> {
if (strategy.applyFilterToExternalHeaders(entry.getKey(), entry.getValue(), camelExchange)) {
LOG.trace("Drop external header: {}={}", entry.getKey(), entry.getValue());
return;
}
String camelHeaderName = CXF_TO_CAMEL_HEADERS.getOrDefault(entry.getKey(), entry.getKey());
LOG.trace("Populate external header: {}={} as {}", entry.getKey(), entry.getValue(), camelHeaderName);
camelHeaders.put(camelHeaderName, entry.getValue().get(0));
});
}
/**
* Propagates CXF headers to Camel message.
*
* @param strategy header filter strategy
* @param cxfMessage CXF message
* @param camelMessage Camel message
* @param exchange provides context for filtering
*/
public static void propagateCxfToCamel(HeaderFilterStrategy strategy, Message cxfMessage,
org.apache.camel.Message camelMessage, Exchange exchange) {
if (strategy == null) {
return;
}
// Copy the CXF protocol headers to the camel headers
copyProtocolHeadersFromCxfToCamel(strategy, exchange, cxfMessage, camelMessage);
// Copy the CXF HTTP headers to the camel headers
copyHttpHeadersFromCxfToCamel(strategy, cxfMessage, camelMessage, exchange);
// propagate request context
copyCxfHeaderToCamel(strategy, exchange, cxfMessage, camelMessage, Client.REQUEST_CONTEXT);
// propagate response context
copyCxfHeaderToCamel(strategy, exchange, cxfMessage, camelMessage, Client.RESPONSE_CONTEXT);
}
private static void copyProtocolHeadersFromCxfToCamel(HeaderFilterStrategy strategy, Exchange exchange,
Message cxfMessage, org.apache.camel.Message camelMessage) {
Map<String, List<String>> cxfHeaders =
CastUtils.cast((Map<?, ?>) cxfMessage.getOrDefault(Message.PROTOCOL_HEADERS, Collections.emptyMap()));
cxfHeaders.entrySet().forEach(cxfHeader -> {
String camelHeaderName = CXF_TO_CAMEL_HEADERS.getOrDefault(cxfHeader.getKey(), cxfHeader.getKey());
Object value = convertCxfProtocolHeaderValues(cxfHeader.getValue(), exchange);
copyCxfHeaderToCamel(strategy, exchange, cxfMessage, camelMessage, cxfHeader.getKey(), camelHeaderName, value);
});
}
private static Object convertCxfProtocolHeaderValues(List<String> values, Exchange exchange) {
if (values.size() == 1) {
return values.get(0);
}
if (exchange.getProperty(CxfConstants.CAMEL_CXF_PROTOCOL_HEADERS_MERGED, Boolean.FALSE, Boolean.class)) {
return String.join(", ", values);
}
return values;
}
public static void copyHttpHeadersFromCxfToCamel(HeaderFilterStrategy strategy, Message cxfMessage,
org.apache.camel.Message camelMessage, Exchange exchange) {
CXF_TO_CAMEL_HEADERS.entrySet().forEach(entry ->
copyCxfHeaderToCamel(strategy, exchange, cxfMessage, camelMessage, entry.getKey(), entry.getValue()));
}
private static void copyCxfHeaderToCamel(HeaderFilterStrategy strategy, Exchange exchange,
Message cxfMessage, org.apache.camel.Message camelMessage, String key) {
copyCxfHeaderToCamel(strategy, exchange, cxfMessage, camelMessage, key, key);
}
private static void copyCxfHeaderToCamel(HeaderFilterStrategy strategy, Exchange exchange,
Message cxfMessage, org.apache.camel.Message camelMessage, String cxfKey, String camelKey) {
copyCxfHeaderToCamel(strategy, exchange, cxfMessage, camelMessage, cxfKey, camelKey, cxfMessage.get(cxfKey));
}
private static void copyCxfHeaderToCamel(HeaderFilterStrategy strategy, Exchange exchange,
Message cxfMessage, org.apache.camel.Message camelMessage, String cxfKey, String camelKey,
Object initialValue) {
Object value = initialValue;
if (Message.PATH_INFO.equals(cxfKey)) {
// We need remove the BASE_PATH from the PATH_INFO
value = convertPathInfo(cxfMessage);
} else if (Message.CONTENT_TYPE.equals(cxfKey)) {
// propagate content type with the encoding information
// We need to do it as the CXF does this kind of thing in transport level
value = determineContentType(cxfMessage);
}
if (value != null && !strategy.applyFilterToExternalHeaders(cxfKey, value, exchange)) {
camelMessage.setHeader(camelKey, value);
}
}
private static String convertPathInfo(Message message) {
String pathInfo = findHeaderValue(message, Message.PATH_INFO);
String basePath = findHeaderValue(message, Message.BASE_PATH);
if (pathInfo != null && basePath != null && pathInfo.startsWith(basePath)) {
return pathInfo.substring(basePath.length());
}
return pathInfo;
}
private static String determineContentType(Message message) {
String ct = findHeaderValue(message, Message.CONTENT_TYPE);
String enc = findHeaderValue(message, Message.ENCODING);
if (null != ct) {
if (enc != null
&& !ct.contains("charset=")
&& !ct.toLowerCase().contains("multipart/related")) {
ct = ct + "; charset=" + enc;
}
} else if (enc != null) {
ct = "text/xml; charset=" + enc;
} else {
ct = "text/xml";
}
// update the content_type value in the message
message.put(Message.CONTENT_TYPE, ct);
return ct;
}
private static String findHeaderValue(Message message, String key) {
String value = (String) message.get(key);
if (value != null) {
return value;
}
Map<String, List<String>> protocolHeaders =
CastUtils.cast((Map<?, ?>) message.getOrDefault(Message.PROTOCOL_HEADERS, Collections.emptyMap()));
return protocolHeaders.getOrDefault(key, Collections.singletonList(null)).get(0);
}
}