/* * Copyright 2002-2017 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.support; import java.util.Date; import java.util.List; import java.util.Map; import org.springframework.integration.IntegrationMessageHeaderAccessor; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.ErrorMessage; import org.springframework.messaging.support.GenericMessage; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** * The default message builder; creates immutable {@link GenericMessage}s. * Named MessageBuilder instead of DefaultMessageBuilder for backwards * compatibility. * * @author Arjen Poutsma * @author Mark Fisher * @author Oleg Zhurakousky * @author Dave Syer * @author Gary Russell * @author Artem Bilan */ public final class MessageBuilder<T> extends AbstractIntegrationMessageBuilder<T> { private final T payload; private final IntegrationMessageHeaderAccessor headerAccessor; private final Message<T> originalMessage; private volatile boolean modified; private String[] readOnlyHeaders; /** * Private constructor to be invoked from the static factory methods only. */ private MessageBuilder(T payload, Message<T> originalMessage) { Assert.notNull(payload, "payload must not be null"); this.payload = payload; this.originalMessage = originalMessage; this.headerAccessor = new IntegrationMessageHeaderAccessor(originalMessage); if (originalMessage != null) { this.modified = (!this.payload.equals(originalMessage.getPayload())); } } @Override public T getPayload() { return this.payload; } @Override public Map<String, Object> getHeaders() { return this.headerAccessor.toMap(); } /** * Create a builder for a new {@link Message} instance pre-populated with all of the headers copied from the * provided message. The payload of the provided Message will also be used as the payload for the new message. * * @param message the Message from which the payload and all headers will be copied * @param <T> The type of the payload. * @return A MessageBuilder. */ public static <T> MessageBuilder<T> fromMessage(Message<T> message) { Assert.notNull(message, "message must not be null"); return new MessageBuilder<T>(message.getPayload(), message); } /** * Create a builder for a new {@link Message} instance with the provided payload. * * @param payload the payload for the new message * @param <T> The type of the payload. * @return A MessageBuilder. */ public static <T> MessageBuilder<T> withPayload(T payload) { return new MessageBuilder<T>(payload, null); } /** * Set the value for the given header name. If the provided value is <code>null</code>, the header will be removed. * * @param headerName The header name. * @param headerValue The header value. * @return this MessageBuilder. */ @Override public MessageBuilder<T> setHeader(String headerName, Object headerValue) { this.headerAccessor.setHeader(headerName, headerValue); return this; } /** * Set the value for the given header name only if the header name is not already associated with a value. * * @param headerName The header name. * @param headerValue The header value. * @return this MessageBuilder. */ @Override public MessageBuilder<T> setHeaderIfAbsent(String headerName, Object headerValue) { this.headerAccessor.setHeaderIfAbsent(headerName, headerValue); return this; } /** * Removes all headers provided via array of 'headerPatterns'. As the name suggests the array * may contain simple matching patterns for header names. Supported pattern styles are: * "xxx*", "*xxx", "*xxx*" and "xxx*yyy". * * @param headerPatterns The header patterns. * @return this MessageBuilder. */ @Override public MessageBuilder<T> removeHeaders(String... headerPatterns) { this.headerAccessor.removeHeaders(headerPatterns); return this; } /** * Remove the value for the given header name. * @param headerName The header name. * @return this MessageBuilder. */ @Override public MessageBuilder<T> removeHeader(String headerName) { this.headerAccessor.removeHeader(headerName); return this; } /** * Copy the name-value pairs from the provided Map. This operation will overwrite any existing values. Use { * {@link #copyHeadersIfAbsent(Map)} to avoid overwriting values. Note that the 'id' and 'timestamp' header values * will never be overwritten. * * @param headersToCopy The headers to copy. * @return this MessageBuilder. * * @see MessageHeaders#ID * @see MessageHeaders#TIMESTAMP */ @Override public MessageBuilder<T> copyHeaders(Map<String, ?> headersToCopy) { this.headerAccessor.copyHeaders(headersToCopy); return this; } /** * Copy the name-value pairs from the provided Map. This operation will <em>not</em> overwrite any existing values. * * @param headersToCopy The headers to copy. * @return this MessageBuilder. */ @Override public MessageBuilder<T> copyHeadersIfAbsent(Map<String, ?> headersToCopy) { this.headerAccessor.copyHeadersIfAbsent(headersToCopy); return this; } @SuppressWarnings("unchecked") @Override protected List<List<Object>> getSequenceDetails() { return (List<List<Object>>) this.headerAccessor.getHeader(IntegrationMessageHeaderAccessor.SEQUENCE_DETAILS); } @Override protected Object getCorrelationId() { return this.headerAccessor.getCorrelationId(); } @Override protected Object getSequenceNumber() { return this.headerAccessor.getSequenceNumber(); } @Override protected Object getSequenceSize() { return this.headerAccessor.getSequenceSize(); } /* * The following overrides (delegating to super) are provided to ease the * pain for existing applications that use the builder API and expect * a MessageBuilder to be returned. */ @Override public MessageBuilder<T> pushSequenceDetails(Object correlationId, int sequenceNumber, int sequenceSize) { super.pushSequenceDetails(correlationId, sequenceNumber, sequenceSize); return this; } @Override public MessageBuilder<T> popSequenceDetails() { super.popSequenceDetails(); return this; } @Override public MessageBuilder<T> setExpirationDate(Long expirationDate) { super.setExpirationDate(expirationDate); return this; } @Override public MessageBuilder<T> setExpirationDate(Date expirationDate) { super.setExpirationDate(expirationDate); return this; } @Override public MessageBuilder<T> setCorrelationId(Object correlationId) { super.setCorrelationId(correlationId); return this; } @Override public MessageBuilder<T> setReplyChannel(MessageChannel replyChannel) { super.setReplyChannel(replyChannel); return this; } @Override public MessageBuilder<T> setReplyChannelName(String replyChannelName) { super.setReplyChannelName(replyChannelName); return this; } @Override public MessageBuilder<T> setErrorChannel(MessageChannel errorChannel) { super.setErrorChannel(errorChannel); return this; } @Override public MessageBuilder<T> setErrorChannelName(String errorChannelName) { super.setErrorChannelName(errorChannelName); return this; } @Override public MessageBuilder<T> setSequenceNumber(Integer sequenceNumber) { super.setSequenceNumber(sequenceNumber); return this; } @Override public MessageBuilder<T> setSequenceSize(Integer sequenceSize) { super.setSequenceSize(sequenceSize); return this; } @Override public MessageBuilder<T> setPriority(Integer priority) { super.setPriority(priority); return this; } /** * Specify a list of headers which should be considered as read only * and prohibited from being populated in the message. * @param readOnlyHeaders the list of headers for {@code readOnly} mode. * Defaults to {@link MessageHeaders#ID} and {@link MessageHeaders#TIMESTAMP}. * @return the current {@link MessageBuilder} * @since 4.3.2 * @see IntegrationMessageHeaderAccessor#isReadOnly(String) */ public MessageBuilder<T> readOnlyHeaders(String... readOnlyHeaders) { this.readOnlyHeaders = readOnlyHeaders; this.headerAccessor.setReadOnlyHeaders(readOnlyHeaders); return this; } @Override @SuppressWarnings("unchecked") public Message<T> build() { if (!this.modified && !this.headerAccessor.isModified() && this.originalMessage != null && !containsReadOnly(this.originalMessage.getHeaders())) { return this.originalMessage; } if (this.payload instanceof Throwable) { return (Message<T>) new ErrorMessage((Throwable) this.payload, this.headerAccessor.toMap()); } return new GenericMessage<T>(this.payload, this.headerAccessor.toMap()); } private boolean containsReadOnly(MessageHeaders headers) { if (!ObjectUtils.isEmpty(this.readOnlyHeaders)) { for (String readOnly : this.readOnlyHeaders) { if (headers.containsKey(readOnly)) { return true; } } } return false; } }