/*
* 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.history;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.message.AdviceMessage;
import org.springframework.integration.support.DefaultMessageBuilderFactory;
import org.springframework.integration.support.MessageBuilderFactory;
import org.springframework.integration.support.MutableMessage;
import org.springframework.integration.support.MutableMessageBuilderFactory;
import org.springframework.integration.support.context.NamedComponent;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* @author Mark Fisher
* @author Artem Bilan
* @since 2.0
*/
@SuppressWarnings("serial")
public final class MessageHistory implements List<Properties>, Serializable {
private static final Log logger = LogFactory.getLog(MessageHistory.class);
public static final String HEADER_NAME = "history";
public static final String NAME_PROPERTY = "name";
public static final String TYPE_PROPERTY = "type";
public static final String TIMESTAMP_PROPERTY = "timestamp";
private static final MessageBuilderFactory MESSAGE_BUILDER_FACTORY = new DefaultMessageBuilderFactory();
private final List<Properties> components;
public static MessageHistory read(Message<?> message) {
return message != null ? message.getHeaders().get(HEADER_NAME, MessageHistory.class) : null;
}
public static <T> Message<T> write(Message<T> message, NamedComponent component) {
return write(message, component, MESSAGE_BUILDER_FACTORY);
}
@SuppressWarnings("unchecked")
public static <T> Message<T> write(Message<T> message, NamedComponent component,
MessageBuilderFactory messageBuilderFactory) {
Assert.notNull(message, "Message must not be null");
Assert.notNull(component, "Component must not be null");
Properties metadata = extractMetadata(component);
if (!metadata.isEmpty()) {
MessageHistory previousHistory = message.getHeaders().get(HEADER_NAME, MessageHistory.class);
List<Properties> components = (previousHistory != null) ?
new ArrayList<Properties>(previousHistory) : new ArrayList<Properties>();
components.add(metadata);
MessageHistory history = new MessageHistory(components);
if (message instanceof MutableMessage) {
message.getHeaders().put(HEADER_NAME, history);
}
else if (message instanceof ErrorMessage) {
IntegrationMessageHeaderAccessor headerAccessor = new IntegrationMessageHeaderAccessor(message);
headerAccessor.setHeader(HEADER_NAME, history);
Throwable payload = ((ErrorMessage) message).getPayload();
ErrorMessage errorMessage = new ErrorMessage(payload, headerAccessor.toMessageHeaders());
message = (Message<T>) errorMessage;
}
else if (message instanceof AdviceMessage) {
IntegrationMessageHeaderAccessor headerAccessor = new IntegrationMessageHeaderAccessor(message);
headerAccessor.setHeader(HEADER_NAME, history);
message = new AdviceMessage<T>(message.getPayload(), headerAccessor.toMessageHeaders(),
((AdviceMessage<?>) message).getInputMessage());
}
else {
if (!(message instanceof GenericMessage) &&
(messageBuilderFactory instanceof DefaultMessageBuilderFactory ||
messageBuilderFactory instanceof MutableMessageBuilderFactory)) {
if (logger.isWarnEnabled()) {
logger.warn("MessageHistory rebuilds the message and produces the result of the [" +
messageBuilderFactory + "], not an instance of the provided type [" +
message.getClass() + "]. Consider to supply a custom MessageBuilderFactory " +
"to retain custom messages during MessageHistory tracking.");
}
}
message = messageBuilderFactory.fromMessage(message)
.setHeader(HEADER_NAME, history)
.build();
}
}
return message;
}
private MessageHistory(List<Properties> components) {
Assert.notEmpty(components, "component list must not be empty");
this.components = components;
}
@Override
public int size() {
return this.components.size();
}
@Override
public boolean isEmpty() {
return this.components.isEmpty();
}
@Override
public boolean contains(Object o) {
return this.components.contains(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return this.components.containsAll(c);
}
@Override
public Properties get(int index) {
return this.components.get(index);
}
@Override
public Iterator<Properties> iterator() {
return Collections.unmodifiableList(this.components).iterator();
}
@Override
public ListIterator<Properties> listIterator() {
return Collections.unmodifiableList(this.components).listIterator();
}
@Override
public ListIterator<Properties> listIterator(int index) {
return Collections.unmodifiableList(this.components).listIterator(index);
}
@Override
public List<Properties> subList(int fromIndex, int toIndex) {
return Collections.unmodifiableList(this.components).subList(fromIndex, toIndex);
}
@Override
public Object[] toArray() {
return this.components.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return this.components.toArray(a);
}
@Override
public int indexOf(Object o) {
return this.components.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
return this.components.lastIndexOf(o);
}
@Override
public String toString() {
List<String> names = new ArrayList<String>();
for (Properties p : this.components) {
String name = p.getProperty(NAME_PROPERTY);
if (name != null) {
names.add(name);
}
}
return StringUtils.collectionToCommaDelimitedString(names);
}
/*
* Unsupported Operations
*/
@Override
public boolean add(Properties e) {
throw new UnsupportedOperationException("MessageHistory is immutable.");
}
@Override
public void add(int index, Properties element) {
throw new UnsupportedOperationException("MessageHistory is immutable.");
}
@Override
public boolean addAll(Collection<? extends Properties> c) {
throw new UnsupportedOperationException("MessageHistory is immutable.");
}
@Override
public boolean addAll(int index, Collection<? extends Properties> c) {
throw new UnsupportedOperationException("MessageHistory is immutable.");
}
@Override
public Properties set(int index, Properties element) {
throw new UnsupportedOperationException("MessageHistory is immutable.");
}
@Override
public Properties remove(int index) {
throw new UnsupportedOperationException("MessageHistory is immutable.");
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException("MessageHistory is immutable.");
}
@Override
public boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException("MessageHistory is immutable.");
}
@Override
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException("MessageHistory is immutable.");
}
@Override
public void clear() {
throw new UnsupportedOperationException("MessageHistory is immutable.");
}
private static Properties extractMetadata(NamedComponent component) {
Entry entry = new Entry();
String name = component.getComponentName();
String type = component.getComponentType();
if (name != null && !name.startsWith("org.springframework.integration")) {
entry.setName(name);
if (type != null) {
entry.setType(type);
}
}
if (!entry.isEmpty()) {
entry.setTimestamp(Long.toString(System.currentTimeMillis()));
}
return entry;
}
/**
* Inner class for each Entry in the history.
*/
public static class Entry extends Properties {
public String getName() {
return this.getProperty(NAME_PROPERTY);
}
private void setName(String name) {
this.setProperty(NAME_PROPERTY, name);
}
public String getType() {
return this.getProperty(TYPE_PROPERTY);
}
private void setType(String type) {
this.setProperty(TYPE_PROPERTY, type);
}
public String getTimestamp() {
return this.getProperty(TIMESTAMP_PROPERTY);
}
private void setTimestamp(String timestamp) {
this.setProperty(TIMESTAMP_PROPERTY, timestamp);
}
}
}