/* * 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; } }