/*
* Copyright 2015-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.stomp.support;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.MediaType;
import org.springframework.integration.mapping.HeaderMapper;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.simp.stomp.StompHeaders;
import org.springframework.util.Assert;
import org.springframework.util.MimeType;
import org.springframework.util.MultiValueMap;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;
/**
* The STOMP {@link HeaderMapper} implementation.
*
* @author Artem Bilan
* @since 4.2
* @see StompHeaders
*/
public class StompHeaderMapper implements HeaderMapper<StompHeaders> {
private static final Log logger = LogFactory.getLog(StompHeaderMapper.class);
public static final String STOMP_INBOUND_HEADER_NAME_PATTERN = "STOMP_INBOUND_HEADERS";
public static final String STOMP_OUTBOUND_HEADER_NAME_PATTERN = "STOMP_OUTBOUND_HEADERS";
private static final String[] STOMP_INBOUND_HEADER_NAMES = new String[] {
StompHeaders.CONTENT_LENGTH,
StompHeaders.CONTENT_TYPE,
StompHeaders.MESSAGE_ID,
StompHeaders.RECEIPT_ID,
StompHeaders.SUBSCRIPTION,
};
private final static List<String> STOMP_INBOUND_HEADER_NAMES_LIST =
Arrays.<String>asList(STOMP_INBOUND_HEADER_NAMES);
private static final String[] STOMP_OUTBOUND_HEADER_NAMES = new String[] {
StompHeaders.CONTENT_LENGTH,
StompHeaders.CONTENT_TYPE,
StompHeaders.DESTINATION,
StompHeaders.RECEIPT,
IntegrationStompHeaders.DESTINATION,
IntegrationStompHeaders.RECEIPT
};
private final static List<String> STOMP_OUTBOUND_HEADER_NAMES_LIST =
Arrays.<String>asList(STOMP_OUTBOUND_HEADER_NAMES);
private volatile String[] inboundHeaderNames = STOMP_INBOUND_HEADER_NAMES;
private volatile String[] outboundHeaderNames = STOMP_OUTBOUND_HEADER_NAMES;
public void setInboundHeaderNames(String[] inboundHeaderNames) { //NOSONAR - false positive
Assert.notNull(inboundHeaderNames, "'inboundHeaderNames' must not be null.");
Assert.noNullElements(inboundHeaderNames, "'inboundHeaderNames' must not contains null elements.");
Arrays.sort(inboundHeaderNames);
if (!Arrays.equals(STOMP_INBOUND_HEADER_NAMES, inboundHeaderNames)) {
this.inboundHeaderNames = inboundHeaderNames;
}
}
public void setOutboundHeaderNames(String[] outboundHeaderNames) { //NOSONAR - false positive
Assert.notNull(outboundHeaderNames, "'outboundHeaderNames' must not be null.");
Assert.noNullElements(outboundHeaderNames, "'outboundHeaderNames' must not contains null elements.");
Arrays.sort(outboundHeaderNames);
if (!Arrays.equals(STOMP_OUTBOUND_HEADER_NAMES, outboundHeaderNames)) {
this.outboundHeaderNames = outboundHeaderNames;
}
}
@Override
@SuppressWarnings("unchecked")
public void fromHeaders(MessageHeaders headers, StompHeaders target) {
for (Map.Entry<String, Object> entry : headers.entrySet()) {
String name = entry.getKey();
if (shouldMapHeader(name, this.outboundHeaderNames)) {
Object value = entry.getValue();
if (value != null) {
setStompHeader(target, name, value);
}
}
else if (StompHeaderAccessor.NATIVE_HEADERS.equals(name)) {
MultiValueMap<String, String> multiValueMap =
headers.get(StompHeaderAccessor.NATIVE_HEADERS, MultiValueMap.class);
for (Map.Entry<String, List<String>> entry1 : multiValueMap.entrySet()) {
name = entry1.getKey();
if (shouldMapHeader(name, this.outboundHeaderNames)) {
String value = entry1.getValue().get(0);
if (StringUtils.hasText(value)) {
setStompHeader(target, name, value);
}
}
}
}
}
}
private void setStompHeader(StompHeaders target, String name, Object value) {
if (StompHeaders.CONTENT_LENGTH.equals(name)) {
if (value instanceof Number) {
target.setContentLength(((Number) value).longValue());
}
else if (value instanceof String) {
target.setContentLength(Long.parseLong((String) value));
}
else {
Class<?> clazz = (value != null) ? value.getClass() : null;
throw new IllegalArgumentException(
"Expected Number or String value for 'content-length' header value, but received: " + clazz);
}
}
else if (StompHeaders.CONTENT_TYPE.equals(name) || MessageHeaders.CONTENT_TYPE.equals(name)) {
MimeType contentType = target.getContentType();
if (contentType == null || StompHeaders.CONTENT_TYPE.equals(name)) {
if (value instanceof MediaType) {
target.setContentType((MediaType) value);
}
else if (value instanceof String) {
target.setContentType(MediaType.parseMediaType((String) value));
}
else {
Class<?> clazz = (value != null) ? value.getClass() : null;
throw new IllegalArgumentException(
"Expected MediaType or String value for 'content-type' header value, but received: " + clazz);
}
}
}
else if (StompHeaders.DESTINATION.equals(name) || IntegrationStompHeaders.DESTINATION.equals(name)) {
if (value instanceof String) {
target.setDestination((String) value);
}
else {
Class<?> clazz = (value != null) ? value.getClass() : null;
throw new IllegalArgumentException(
"Expected String value for 'destination' header value, but received: " + clazz);
}
}
else if (StompHeaders.RECEIPT.equals(name) || IntegrationStompHeaders.RECEIPT.equals(name)) {
if (value instanceof String) {
target.setReceipt((String) value);
}
else {
Class<?> clazz = (value != null) ? value.getClass() : null;
throw new IllegalArgumentException(
"Expected String value for 'receipt' header value, but received: " + clazz);
}
}
else {
if (value instanceof String) {
target.set(name, (String) value);
}
else {
Class<?> clazz = (value != null) ? value.getClass() : null;
throw new IllegalArgumentException(
"Expected String value for any generic STOMP header value, but received: " + clazz);
}
}
}
@Override
public Map<String, Object> toHeaders(StompHeaders source) {
Map<String, Object> target = new HashMap<String, Object>();
for (String name : source.keySet()) {
if (shouldMapHeader(name, this.inboundHeaderNames)) {
if (StompHeaders.CONTENT_TYPE.equals(name)) {
target.put(MessageHeaders.CONTENT_TYPE, source.getContentType());
}
else {
String key = name;
if (IntegrationStompHeaders.HEADERS.contains(name)) {
key = IntegrationStompHeaders.PREFIX + name;
}
target.put(key, source.getFirst(name));
}
}
}
return target;
}
private boolean shouldMapHeader(String headerName, String[] patterns) {
if (patterns != null && patterns.length > 0) {
for (String pattern : patterns) {
if (PatternMatchUtils.simpleMatch(pattern, headerName)) {
if (logger.isDebugEnabled()) {
logger.debug(MessageFormat.format("headerName=[{0}] WILL be mapped, matched pattern={1}",
headerName, pattern));
}
return true;
}
else if (STOMP_INBOUND_HEADER_NAME_PATTERN.equals(pattern)
&& STOMP_INBOUND_HEADER_NAMES_LIST.contains(headerName)) {
if (logger.isDebugEnabled()) {
logger.debug(MessageFormat.format("headerName=[{0}] WILL be mapped, matched pattern={1}",
headerName, pattern));
}
return true;
}
else if (STOMP_OUTBOUND_HEADER_NAME_PATTERN.equals(pattern)
&& (STOMP_OUTBOUND_HEADER_NAMES_LIST.contains(headerName)
|| MessageHeaders.CONTENT_TYPE.equals(headerName))) {
if (logger.isDebugEnabled()) {
logger.debug(MessageFormat.format("headerName=[{0}] WILL be mapped, matched pattern={1}",
headerName, pattern));
}
return true;
}
}
}
if (logger.isDebugEnabled()) {
logger.debug(MessageFormat.format("headerName=[{0}] WILL NOT be mapped", headerName));
}
return false;
}
}