/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed 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.springframework.integration.amqp.support;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.mapping.AbstractHeaderMapper;
import org.springframework.integration.mapping.support.JsonHeaders;
import org.springframework.util.MimeType;
import org.springframework.util.StringUtils;
/**
* Default implementation of {@link AmqpHeaderMapper}.
* <p>
* By default this implementation will only copy AMQP properties (e.g. contentType) to and from
* Spring Integration MessageHeaders. Any user-defined headers within the AMQP
* MessageProperties will NOT be copied to or from an AMQP Message unless
* explicitly identified via 'requestHeaderNames' and/or 'replyHeaderNames'
* (see {@link AbstractHeaderMapper#setRequestHeaderNames(String[])}
* and {@link AbstractHeaderMapper#setReplyHeaderNames(String[])}}
* as well as 'mapped-request-headers' and 'mapped-reply-headers' attributes of the AMQP adapters).
* If you need to copy all user-defined headers simply use wild-card character '*'.
* <p>
* Constants for the AMQP header keys are defined in {@link AmqpHeaders}.
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @author Stephane Nicoll
* @since 2.1
*/
public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper<MessageProperties> implements AmqpHeaderMapper {
private static final List<String> STANDARD_HEADER_NAMES = new ArrayList<String>();
static {
STANDARD_HEADER_NAMES.add(AmqpHeaders.APP_ID);
STANDARD_HEADER_NAMES.add(AmqpHeaders.CLUSTER_ID);
STANDARD_HEADER_NAMES.add(AmqpHeaders.CONTENT_ENCODING);
STANDARD_HEADER_NAMES.add(AmqpHeaders.CONTENT_LENGTH);
STANDARD_HEADER_NAMES.add(AmqpHeaders.CONTENT_TYPE);
STANDARD_HEADER_NAMES.add(AmqpHeaders.CORRELATION_ID);
STANDARD_HEADER_NAMES.add(AmqpHeaders.DELAY);
STANDARD_HEADER_NAMES.add(AmqpHeaders.DELIVERY_MODE);
STANDARD_HEADER_NAMES.add(AmqpHeaders.DELIVERY_TAG);
STANDARD_HEADER_NAMES.add(AmqpHeaders.EXPIRATION);
STANDARD_HEADER_NAMES.add(AmqpHeaders.MESSAGE_COUNT);
STANDARD_HEADER_NAMES.add(AmqpHeaders.MESSAGE_ID);
STANDARD_HEADER_NAMES.add(AmqpHeaders.RECEIVED_DELAY);
STANDARD_HEADER_NAMES.add(AmqpHeaders.RECEIVED_DELIVERY_MODE);
STANDARD_HEADER_NAMES.add(AmqpHeaders.RECEIVED_EXCHANGE);
STANDARD_HEADER_NAMES.add(AmqpHeaders.RECEIVED_ROUTING_KEY);
STANDARD_HEADER_NAMES.add(AmqpHeaders.REDELIVERED);
STANDARD_HEADER_NAMES.add(AmqpHeaders.REPLY_TO);
STANDARD_HEADER_NAMES.add(AmqpHeaders.TIMESTAMP);
STANDARD_HEADER_NAMES.add(AmqpHeaders.TYPE);
STANDARD_HEADER_NAMES.add(AmqpHeaders.USER_ID);
STANDARD_HEADER_NAMES.add(JsonHeaders.TYPE_ID);
STANDARD_HEADER_NAMES.add(JsonHeaders.CONTENT_TYPE_ID);
STANDARD_HEADER_NAMES.add(JsonHeaders.KEY_TYPE_ID);
STANDARD_HEADER_NAMES.add(AmqpHeaders.SPRING_REPLY_CORRELATION);
STANDARD_HEADER_NAMES.add(AmqpHeaders.SPRING_REPLY_TO_STACK);
}
protected DefaultAmqpHeaderMapper(String[] requestHeaderNames, String[] replyHeaderNames) {
super(AmqpHeaders.PREFIX, STANDARD_HEADER_NAMES, STANDARD_HEADER_NAMES);
if (requestHeaderNames != null) {
setRequestHeaderNames(requestHeaderNames);
}
if (replyHeaderNames != null) {
setReplyHeaderNames(replyHeaderNames);
}
}
/**
* Extract "standard" headers from an AMQP MessageProperties instance.
*/
@Override
protected Map<String, Object> extractStandardHeaders(MessageProperties amqpMessageProperties) {
Map<String, Object> headers = new HashMap<String, Object>();
try {
String appId = amqpMessageProperties.getAppId();
if (StringUtils.hasText(appId)) {
headers.put(AmqpHeaders.APP_ID, appId);
}
String clusterId = amqpMessageProperties.getClusterId();
if (StringUtils.hasText(clusterId)) {
headers.put(AmqpHeaders.CLUSTER_ID, clusterId);
}
String contentEncoding = amqpMessageProperties.getContentEncoding();
if (StringUtils.hasText(contentEncoding)) {
headers.put(AmqpHeaders.CONTENT_ENCODING, contentEncoding);
}
long contentLength = amqpMessageProperties.getContentLength();
if (contentLength > 0) {
headers.put(AmqpHeaders.CONTENT_LENGTH, contentLength);
}
String contentType = amqpMessageProperties.getContentType();
if (StringUtils.hasText(contentType)) {
headers.put(AmqpHeaders.CONTENT_TYPE, contentType);
}
String correlationId = amqpMessageProperties.getCorrelationId();
if (StringUtils.hasText(contentType)) {
headers.put(AmqpHeaders.CORRELATION_ID, correlationId);
}
MessageDeliveryMode receivedDeliveryMode = amqpMessageProperties.getReceivedDeliveryMode();
if (receivedDeliveryMode != null) {
headers.put(AmqpHeaders.RECEIVED_DELIVERY_MODE, receivedDeliveryMode);
}
long deliveryTag = amqpMessageProperties.getDeliveryTag();
if (deliveryTag > 0) {
headers.put(AmqpHeaders.DELIVERY_TAG, deliveryTag);
}
String expiration = amqpMessageProperties.getExpiration();
if (StringUtils.hasText(expiration)) {
headers.put(AmqpHeaders.EXPIRATION, expiration);
}
Integer messageCount = amqpMessageProperties.getMessageCount();
if (messageCount != null && messageCount > 0) {
headers.put(AmqpHeaders.MESSAGE_COUNT, messageCount);
}
String messageId = amqpMessageProperties.getMessageId();
if (StringUtils.hasText(messageId)) {
headers.put(AmqpHeaders.MESSAGE_ID, messageId);
}
Integer priority = amqpMessageProperties.getPriority();
if (priority != null && priority > 0) {
headers.put(IntegrationMessageHeaderAccessor.PRIORITY, priority);
}
Integer receivedDelay = amqpMessageProperties.getReceivedDelay();
if (receivedDelay != null) {
headers.put(AmqpHeaders.RECEIVED_DELAY, receivedDelay);
}
String receivedExchange = amqpMessageProperties.getReceivedExchange();
if (StringUtils.hasText(receivedExchange)) {
headers.put(AmqpHeaders.RECEIVED_EXCHANGE, receivedExchange);
}
String receivedRoutingKey = amqpMessageProperties.getReceivedRoutingKey();
if (StringUtils.hasText(receivedRoutingKey)) {
headers.put(AmqpHeaders.RECEIVED_ROUTING_KEY, receivedRoutingKey);
}
Boolean redelivered = amqpMessageProperties.isRedelivered();
if (redelivered != null) {
headers.put(AmqpHeaders.REDELIVERED, redelivered);
}
String replyTo = amqpMessageProperties.getReplyTo();
if (replyTo != null) {
headers.put(AmqpHeaders.REPLY_TO, replyTo);
}
Date timestamp = amqpMessageProperties.getTimestamp();
if (timestamp != null) {
headers.put(AmqpHeaders.TIMESTAMP, timestamp);
}
String type = amqpMessageProperties.getType();
if (StringUtils.hasText(type)) {
headers.put(AmqpHeaders.TYPE, type);
}
String userId = amqpMessageProperties.getReceivedUserId();
if (StringUtils.hasText(userId)) {
headers.put(AmqpHeaders.RECEIVED_USER_ID, userId);
}
for (String jsonHeader : JsonHeaders.HEADERS) {
Object value = amqpMessageProperties.getHeaders().get(jsonHeader.replaceFirst(JsonHeaders.PREFIX, ""));
if (value instanceof String && StringUtils.hasText((String) value)) {
headers.put(jsonHeader, value);
}
}
}
catch (Exception e) {
if (logger.isWarnEnabled()) {
logger.warn("error occurred while mapping from AMQP properties to MessageHeaders", e);
}
}
return headers;
}
/**
* Extract user-defined headers from an AMQP MessageProperties instance.
*/
@Override
protected Map<String, Object> extractUserDefinedHeaders(MessageProperties amqpMessageProperties) {
return amqpMessageProperties.getHeaders();
}
/**
* Maps headers from a Spring Integration MessageHeaders instance to the MessageProperties
* of an AMQP Message.
*/
@Override
protected void populateStandardHeaders(Map<String, Object> headers, MessageProperties amqpMessageProperties) {
String appId = getHeaderIfAvailable(headers, AmqpHeaders.APP_ID, String.class);
if (StringUtils.hasText(appId)) {
amqpMessageProperties.setAppId(appId);
}
String clusterId = getHeaderIfAvailable(headers, AmqpHeaders.CLUSTER_ID, String.class);
if (StringUtils.hasText(clusterId)) {
amqpMessageProperties.setClusterId(clusterId);
}
String contentEncoding = getHeaderIfAvailable(headers, AmqpHeaders.CONTENT_ENCODING, String.class);
if (StringUtils.hasText(contentEncoding)) {
amqpMessageProperties.setContentEncoding(contentEncoding);
}
Long contentLength = getHeaderIfAvailable(headers, AmqpHeaders.CONTENT_LENGTH, Long.class);
if (contentLength != null) {
amqpMessageProperties.setContentLength(contentLength);
}
String contentType = this.extractContentTypeAsString(headers);
if (StringUtils.hasText(contentType)) {
amqpMessageProperties.setContentType(contentType);
}
String correlationId = getHeaderIfAvailable(headers, AmqpHeaders.CORRELATION_ID, String.class);
if (StringUtils.hasText(correlationId)) {
amqpMessageProperties.setCorrelationId(correlationId);
}
Integer delay = getHeaderIfAvailable(headers, AmqpHeaders.DELAY, Integer.class);
if (delay != null) {
amqpMessageProperties.setDelay(delay);
}
MessageDeliveryMode deliveryMode = getHeaderIfAvailable(headers, AmqpHeaders.DELIVERY_MODE,
MessageDeliveryMode.class);
if (deliveryMode != null) {
amqpMessageProperties.setDeliveryMode(deliveryMode);
}
Long deliveryTag = getHeaderIfAvailable(headers, AmqpHeaders.DELIVERY_TAG, Long.class);
if (deliveryTag != null) {
amqpMessageProperties.setDeliveryTag(deliveryTag);
}
String expiration = getHeaderIfAvailable(headers, AmqpHeaders.EXPIRATION, String.class);
if (StringUtils.hasText(expiration)) {
amqpMessageProperties.setExpiration(expiration);
}
Integer messageCount = getHeaderIfAvailable(headers, AmqpHeaders.MESSAGE_COUNT, Integer.class);
if (messageCount != null) {
amqpMessageProperties.setMessageCount(messageCount);
}
String messageId = getHeaderIfAvailable(headers, AmqpHeaders.MESSAGE_ID, String.class);
if (StringUtils.hasText(messageId)) {
amqpMessageProperties.setMessageId(messageId);
}
Integer priority = getHeaderIfAvailable(headers, IntegrationMessageHeaderAccessor.PRIORITY, Integer.class);
if (priority != null) {
amqpMessageProperties.setPriority(priority);
}
String receivedExchange = getHeaderIfAvailable(headers, AmqpHeaders.RECEIVED_EXCHANGE, String.class);
if (StringUtils.hasText(receivedExchange)) {
amqpMessageProperties.setReceivedExchange(receivedExchange);
}
String receivedRoutingKey = getHeaderIfAvailable(headers, AmqpHeaders.RECEIVED_ROUTING_KEY, String.class);
if (StringUtils.hasText(receivedRoutingKey)) {
amqpMessageProperties.setReceivedRoutingKey(receivedRoutingKey);
}
Boolean redelivered = getHeaderIfAvailable(headers, AmqpHeaders.REDELIVERED, Boolean.class);
if (redelivered != null) {
amqpMessageProperties.setRedelivered(redelivered);
}
String replyTo = getHeaderIfAvailable(headers, AmqpHeaders.REPLY_TO, String.class);
if (replyTo != null) {
amqpMessageProperties.setReplyTo(replyTo);
}
Date timestamp = getHeaderIfAvailable(headers, AmqpHeaders.TIMESTAMP, Date.class);
if (timestamp != null) {
amqpMessageProperties.setTimestamp(timestamp);
}
String type = getHeaderIfAvailable(headers, AmqpHeaders.TYPE, String.class);
if (type != null) {
amqpMessageProperties.setType(type);
}
String userId = getHeaderIfAvailable(headers, AmqpHeaders.USER_ID, String.class);
if (StringUtils.hasText(userId)) {
amqpMessageProperties.setUserId(userId);
}
Map<String, String> jsonHeaders = new HashMap<String, String>();
for (String jsonHeader : JsonHeaders.HEADERS) {
Object value = getHeaderIfAvailable(headers, jsonHeader, Object.class);
if (value != null) {
headers.remove(jsonHeader);
if (value instanceof Class<?>) {
value = ((Class<?>) value).getName();
}
jsonHeaders.put(jsonHeader.replaceFirst(JsonHeaders.PREFIX, ""), value.toString());
}
}
/*
* If the MessageProperties already contains JsonHeaders, don't overwrite them here because they were
* set up by a message converter.
*/
if (!amqpMessageProperties.getHeaders().containsKey(JsonHeaders.TYPE_ID.replaceFirst(JsonHeaders.PREFIX, ""))) {
amqpMessageProperties.getHeaders().putAll(jsonHeaders);
}
String replyCorrelation = getHeaderIfAvailable(headers, AmqpHeaders.SPRING_REPLY_CORRELATION, String.class);
if (StringUtils.hasLength(replyCorrelation)) {
amqpMessageProperties.setHeader("spring_reply_correlation", replyCorrelation);
}
String replyToStack = getHeaderIfAvailable(headers, AmqpHeaders.SPRING_REPLY_TO_STACK, String.class);
if (StringUtils.hasLength(replyToStack)) {
amqpMessageProperties.setHeader("spring_reply_to", replyToStack);
}
}
@Override
protected void populateUserDefinedHeader(String headerName, Object headerValue,
MessageProperties amqpMessageProperties) {
// do not overwrite an existing header with the same key
// TODO: do we need to expose a boolean 'overwrite' flag?
if (!amqpMessageProperties.getHeaders().containsKey(headerName)) {
amqpMessageProperties.setHeader(headerName, headerValue);
}
}
/**
* Will extract Content-Type from MessageHeaders and convert it to String if possible
* Required since Content-Type can be represented as org.springframework.util.MimeType.
*
*/
private String extractContentTypeAsString(Map<String, Object> headers) {
String contentTypeStringValue = null;
Object contentType = getHeaderIfAvailable(headers, AmqpHeaders.CONTENT_TYPE, Object.class);
if (contentType != null) {
String contentTypeClassName = contentType.getClass().getName();
if (contentType instanceof MimeType) {
contentTypeStringValue = contentType.toString();
}
else if (contentType instanceof String) {
contentTypeStringValue = (String) contentType;
}
else {
if (logger.isWarnEnabled()) {
logger.warn("skipping header '" + AmqpHeaders.CONTENT_TYPE +
"' since it is not of expected type [" + contentTypeClassName + "]");
}
}
}
return contentTypeStringValue;
}
@Override
public Map<String, Object> toHeadersFromRequest(MessageProperties source) {
Map<String, Object> headersFromRequest = super.toHeadersFromRequest(source);
addConsumerMetadata(source, headersFromRequest);
return headersFromRequest;
}
private void addConsumerMetadata(MessageProperties messageProperties, Map<String, Object> headers) {
String consumerTag = messageProperties.getConsumerTag();
if (consumerTag != null) {
headers.put(AmqpHeaders.CONSUMER_TAG, consumerTag);
}
String consumerQueue = messageProperties.getConsumerQueue();
if (consumerQueue != null) {
headers.put(AmqpHeaders.CONSUMER_QUEUE, consumerQueue);
}
}
/**
* Construct a default inbound header mapper.
* @return the mapper.
* @see #inboundRequestHeaders()
* @see #inboundReplyHeaders()
* @since 4.3
*/
public static DefaultAmqpHeaderMapper inboundMapper() {
return new DefaultAmqpHeaderMapper(inboundRequestHeaders(), inboundReplyHeaders());
}
/**
* Construct a default outbound header mapper.
* @return the mapper.
* @see #outboundRequestHeaders()
* @see #outboundReplyHeaders()
* @since 4.3
*/
public static DefaultAmqpHeaderMapper outboundMapper() {
return new DefaultAmqpHeaderMapper(outboundRequestHeaders(), outboundReplyHeaders());
}
/**
* @return the default request headers for an inbound mapper.
* @since 4.3
*/
public static String[] inboundRequestHeaders() {
return new String[] { "*" };
}
/**
* @return the default reply headers for an inbound mapper.
* @since 4.3
*/
public static String[] inboundReplyHeaders() {
return safeOutboundHeaders();
}
/**
* @return the default request headers for an outbound mapper.
* @since 4.3
*/
public static String[] outboundRequestHeaders() {
return safeOutboundHeaders();
}
/**
* @return the default reply headers for an outbound mapper.
* @since 4.3
*/
public static String[] outboundReplyHeaders() {
return new String[] { "*" };
}
private static String[] safeOutboundHeaders() {
return new String[] { "!x-*", "*" };
}
}