Add strategy for customizing ID/TIMESTAMP headers

Introduce MessageHeaderInitializer strategy for initializing a
MessageHeaderAccessor.

Add IdTimestampMessageHeaderInitializer implementation that provides
control over ID and timestamp header generation.

Disable ID and timestamp by default in SimpMessageHeaderAccessor and
therefore its sub-class StompHeaderAccessor.

Issue: SPR-11468
This commit is contained in:
Rossen Stoyanchev
2014-04-14 13:46:32 -04:00
parent 09248a0b37
commit 95e25d4e6f
11 changed files with 168 additions and 349 deletions

View File

@@ -1,70 +0,0 @@
/*
* Copyright 2002-2014 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.messaging.simp;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageHeaderAccessorFactorySupport;
import org.springframework.util.IdGenerator;
import java.util.UUID;
/**
* A default implementation of
* {@link org.springframework.messaging.simp.SimpMessageHeaderAccessorFactory}.
*
* @author Rossen Stoyanchev
* @since 4.1
*/
public class DefaultSimpMessageHeaderAccessorFactory extends MessageHeaderAccessorFactorySupport
implements SimpMessageHeaderAccessorFactory {
public DefaultSimpMessageHeaderAccessorFactory() {
super.setIdGenerator(ID_VALUE_NONE_GENERATOR);
}
@Override
public SimpMessageHeaderAccessor create() {
SimpMessageHeaderAccessor accessor = new SimpMessageHeaderAccessor(SimpMessageType.MESSAGE, null);
updateMessageHeaderAccessor(accessor);
return accessor;
}
@Override
public SimpMessageHeaderAccessor create(SimpMessageType messageType) {
SimpMessageHeaderAccessor accessor = new SimpMessageHeaderAccessor(messageType, null);
updateMessageHeaderAccessor(accessor);
return accessor;
}
@Override
public SimpMessageHeaderAccessor wrap(Message<?> message) {
SimpMessageHeaderAccessor accessor = new SimpMessageHeaderAccessor(message);
updateMessageHeaderAccessor(accessor);
return accessor;
}
private static final IdGenerator ID_VALUE_NONE_GENERATOR = new IdGenerator() {
@Override
public UUID generateId() {
return MessageHeaders.ID_VALUE_NONE;
}
};
}

View File

@@ -21,7 +21,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import org.springframework.messaging.Message; import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.IdTimestampMessageHeaderInitializer;
import org.springframework.messaging.support.MessageHeaderAccessor; import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.messaging.support.NativeMessageHeaderAccessor; import org.springframework.messaging.support.NativeMessageHeaderAccessor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@@ -40,7 +40,13 @@ import org.springframework.util.Assert;
*/ */
public class SimpMessageHeaderAccessor extends NativeMessageHeaderAccessor { public class SimpMessageHeaderAccessor extends NativeMessageHeaderAccessor {
private static final SimpMessageHeaderAccessorFactory factory = new DefaultSimpMessageHeaderAccessorFactory(); private static final IdTimestampMessageHeaderInitializer headerInitializer;
static {
headerInitializer = new IdTimestampMessageHeaderInitializer();
headerInitializer.setDisableIdGeneration();
headerInitializer.setEnableTimestamp(false);
}
// SiMP header names // SiMP header names
@@ -77,6 +83,7 @@ public class SimpMessageHeaderAccessor extends NativeMessageHeaderAccessor {
super(externalSourceHeaders); super(externalSourceHeaders);
Assert.notNull(messageType, "MessageType must not be null"); Assert.notNull(messageType, "MessageType must not be null");
setHeader(MESSAGE_TYPE_HEADER, messageType); setHeader(MESSAGE_TYPE_HEADER, messageType);
headerInitializer.initHeaders(this);
} }
/** /**
@@ -85,6 +92,7 @@ public class SimpMessageHeaderAccessor extends NativeMessageHeaderAccessor {
*/ */
protected SimpMessageHeaderAccessor(Message<?> message) { protected SimpMessageHeaderAccessor(Message<?> message) {
super(message); super(message);
headerInitializer.initHeaders(this);
} }
@@ -93,7 +101,7 @@ public class SimpMessageHeaderAccessor extends NativeMessageHeaderAccessor {
* {@link org.springframework.messaging.simp.SimpMessageType} {@code MESSAGE}. * {@link org.springframework.messaging.simp.SimpMessageType} {@code MESSAGE}.
*/ */
public static SimpMessageHeaderAccessor create() { public static SimpMessageHeaderAccessor create() {
return factory.create(); return new SimpMessageHeaderAccessor(SimpMessageType.MESSAGE, null);
} }
/** /**
@@ -101,20 +109,20 @@ public class SimpMessageHeaderAccessor extends NativeMessageHeaderAccessor {
* {@link org.springframework.messaging.simp.SimpMessageType}. * {@link org.springframework.messaging.simp.SimpMessageType}.
*/ */
public static SimpMessageHeaderAccessor create(SimpMessageType messageType) { public static SimpMessageHeaderAccessor create(SimpMessageType messageType) {
return factory.create(messageType); return new SimpMessageHeaderAccessor(messageType, null);
} }
/** /**
* Create an instance from the payload and headers of the given Message. * Create an instance from the payload and headers of the given Message.
*/ */
public static SimpMessageHeaderAccessor wrap(Message<?> message) { public static SimpMessageHeaderAccessor wrap(Message<?> message) {
return factory.wrap(message); return new SimpMessageHeaderAccessor(message);
} }
@Override @Override
protected MessageHeaderAccessor createAccessor(Message<?> message) { protected MessageHeaderAccessor createAccessor(Message<?> message) {
return factory.wrap(message); return wrap(message);
} }
public void setMessageTypeIfNotSet(SimpMessageType messageType) { public void setMessageTypeIfNotSet(SimpMessageType messageType) {

View File

@@ -1,48 +0,0 @@
/*
* Copyright 2002-2014 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.messaging.simp;
import org.springframework.messaging.Message;
/**
* A factory for creating pre-configured instances of type
* {@link org.springframework.messaging.simp.SimpMessageHeaderAccessor}.
*
* @author Rossen Stoyanchev
* @since 4.1
*/
public interface SimpMessageHeaderAccessorFactory {
/**
* Create an instance with
* {@link org.springframework.messaging.simp.SimpMessageType} {@code MESSAGE}.
*/
SimpMessageHeaderAccessor create();
/**
* Create an instance with the given
* {@link org.springframework.messaging.simp.SimpMessageType}.
*/
SimpMessageHeaderAccessor create(SimpMessageType messageType);
/**
* Create an instance from the payload and headers of the given Message.
*/
SimpMessageHeaderAccessor wrap(Message<?> message);
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2002-2014 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.messaging.simp.stomp;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageHeaderAccessorFactorySupport;
import org.springframework.util.IdGenerator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* A default implementation of
* {@link org.springframework.messaging.simp.stomp.StompHeaderAccessorFactory}.
*
* @author Rossen Stoyanchev
* @since 4.1
*/
public class DefaultStompHeaderAccessorFactory extends MessageHeaderAccessorFactorySupport
implements StompHeaderAccessorFactory {
public DefaultStompHeaderAccessorFactory() {
super.setIdGenerator(ID_VALUE_NONE_GENERATOR);
}
@Override
public StompHeaderAccessor create(StompCommand command) {
StompHeaderAccessor accessor = new StompHeaderAccessor(command, null);
updateMessageHeaderAccessor(accessor);
return accessor;
}
@Override
public StompHeaderAccessor create(StompCommand command, Map<String, List<String>> headers) {
StompHeaderAccessor accessor = new StompHeaderAccessor(command, headers);
updateMessageHeaderAccessor(accessor);
return accessor;
}
@Override
public StompHeaderAccessor createForHeartbeat() {
StompHeaderAccessor accessor = new StompHeaderAccessor();
updateMessageHeaderAccessor(accessor);
return accessor;
}
@Override
public StompHeaderAccessor wrap(Message<?> message) {
StompHeaderAccessor accessor = new StompHeaderAccessor(message);
updateMessageHeaderAccessor(accessor);
return accessor;
}
private static final IdGenerator ID_VALUE_NONE_GENERATOR = new IdGenerator() {
@Override
public UUID generateId() {
return MessageHeaders.ID_VALUE_NONE;
}
};
}

View File

@@ -30,7 +30,6 @@ import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.MimeType; import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils; import org.springframework.util.MimeTypeUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@@ -59,8 +58,6 @@ import org.springframework.util.StringUtils;
*/ */
public class StompHeaderAccessor extends SimpMessageHeaderAccessor { public class StompHeaderAccessor extends SimpMessageHeaderAccessor {
private static final StompHeaderAccessorFactory factory = new DefaultStompHeaderAccessorFactory();
private static final AtomicLong messageIdCounter = new AtomicLong(); private static final AtomicLong messageIdCounter = new AtomicLong();
private static final long[] DEFAULT_HEARTBEAT = new long[] {0, 0}; private static final long[] DEFAULT_HEARTBEAT = new long[] {0, 0};
@@ -176,14 +173,14 @@ public class StompHeaderAccessor extends SimpMessageHeaderAccessor {
* Create an instance for the given STOMP command. * Create an instance for the given STOMP command.
*/ */
public static StompHeaderAccessor create(StompCommand command) { public static StompHeaderAccessor create(StompCommand command) {
return factory.create(command); return new StompHeaderAccessor(command, null);
} }
/** /**
* Create an instance for the given STOMP command and headers. * Create an instance for the given STOMP command and headers.
*/ */
public static StompHeaderAccessor create(StompCommand command, Map<String, List<String>> headers) { public static StompHeaderAccessor create(StompCommand command, Map<String, List<String>> headers) {
return factory.create(command, headers); return new StompHeaderAccessor(command, headers);
} }
/** /**
@@ -191,20 +188,20 @@ public class StompHeaderAccessor extends SimpMessageHeaderAccessor {
* have headers, a session id is needed for processing purposes at a minimum. * have headers, a session id is needed for processing purposes at a minimum.
*/ */
public static StompHeaderAccessor createForHeartbeat() { public static StompHeaderAccessor createForHeartbeat() {
return factory.createForHeartbeat(); return new StompHeaderAccessor();
} }
/** /**
* Create an instance from the payload and headers of the given Message. * Create an instance from the payload and headers of the given Message.
*/ */
public static StompHeaderAccessor wrap(Message<?> message) { public static StompHeaderAccessor wrap(Message<?> message) {
return factory.wrap(message); return new StompHeaderAccessor(message);
} }
@Override @Override
protected MessageHeaderAccessor createAccessor(Message<?> message) { protected MessageHeaderAccessor createAccessor(Message<?> message) {
return factory.wrap(message); return wrap(message);
} }
Map<String, List<String>> getNativeHeaders() { Map<String, List<String>> getNativeHeaders() {

View File

@@ -1,55 +0,0 @@
/*
* Copyright 2002-2014 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.messaging.simp.stomp;
import org.springframework.messaging.Message;
import java.util.List;
import java.util.Map;
/**
* A factory for creating pre-configured instances of type
* {@link org.springframework.messaging.simp.stomp.StompHeaderAccessor}.
*
* @author Rossen Stoyanchev
* @since 4.1
*/
public interface StompHeaderAccessorFactory {
/**
* Create an instance for the given STOMP command.
*/
StompHeaderAccessor create(StompCommand command);
/**
* Create an instance for the given STOMP command and headers.
*/
StompHeaderAccessor create(StompCommand command, Map<String, List<String>> headers);
/**
* Create headers for a heartbeat. While a STOMP heartbeat frame does not
* have headers, a session id is needed for processing purposes at a minimum.
*/
StompHeaderAccessor createForHeartbeat();
/**
* Create an instance from the payload and headers of the given Message.
*/
StompHeaderAccessor wrap(Message<?> message);
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2002-2013 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.messaging.support;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.IdGenerator;
import java.util.UUID;
/**
* A {@link org.springframework.messaging.support.MessageHeaderInitializer MessageHeaderInitializer}
* to customize the strategy for ID and TIMESTAMP message header generation.
*
* @author Rossen Stoyanchev
* @since 4.1
*/
public class IdTimestampMessageHeaderInitializer implements MessageHeaderInitializer {
private IdGenerator idGenerator;
private boolean enableTimestamp;
/**
* Configure the IdGenerator strategy to initialize {@code MessageHeaderAccessor}
* instances with.
*
* <p>By default this property is set to {@code null} in which case the default
* IdGenerator of {@link org.springframework.messaging.MessageHeaders} is used.
*
* <p>To have no id's generated at all, see {@@link #setDisableIdGeneration()}.
*/
public void setIdGenerator(IdGenerator idGenerator) {
this.idGenerator = idGenerator;
}
/**
* A shortcut for calling
* {@link #setIdGenerator(org.springframework.util.IdGenerator)} with an
* id generation strategy to
*
*/
public void setDisableIdGeneration() {
this.idGenerator = ID_VALUE_NONE_GENERATOR;
}
/**
* @return the configured {@code IdGenerator} if any.
*/
public IdGenerator getIdGenerator() {
return this.idGenerator;
}
/**
* Whether to enable the automatic addition of the
* {@link org.springframework.messaging.MessageHeaders#TIMESTAMP} header on
* {@code MessageHeaderAccessor} instances being initialized.
*
* <p>By default this property is set to false.
*/
public void setEnableTimestamp(boolean enableTimestamp) {
this.enableTimestamp = enableTimestamp;
}
/**
* @return Whether the timestamp header is enabled or not.
*/
public boolean isEnableTimestamp() {
return this.enableTimestamp;
}
@Override
public void initHeaders(MessageHeaderAccessor headerAccessor) {
headerAccessor.setIdGenerator(getIdGenerator());
headerAccessor.setEnableTimestamp(isEnableTimestamp());
}
private static final IdGenerator ID_VALUE_NONE_GENERATOR = new IdGenerator() {
@Override
public UUID generateId() {
return MessageHeaders.ID_VALUE_NONE;
}
};
}

View File

@@ -129,11 +129,6 @@ public class MessageHeaderAccessor {
public MessageHeaderAccessor(Message<?> message) { public MessageHeaderAccessor(Message<?> message) {
if (message != null) { if (message != null) {
this.headers = new MutableMessageHeaders(message.getHeaders()); this.headers = new MutableMessageHeaders(message.getHeaders());
MessageHeaderAccessor accessor = getAccessor(message, MessageHeaderAccessor.class);
if (accessor != null) {
this.idGenerator = accessor.idGenerator;
this.enableTimestamp = accessor.enableTimestamp;
}
} }
else { else {
this.headers = new MutableMessageHeaders(); this.headers = new MutableMessageHeaders();
@@ -276,23 +271,24 @@ public class MessageHeaderAccessor {
} }
/** /**
* A private mechanism for providing an alternate IdGenerator strategy. * A package private mechanism to configure the IdGenerator strategy to use.
* *
* <p>By default this property is not set in which case the default IdGenerator * <p>By default this property is not set in which case the default IdGenerator
* of {@link org.springframework.messaging.MessageHeaders} is used. * in {@link org.springframework.messaging.MessageHeaders} is used.
* *
* @see org.springframework.messaging.support.MessageHeaderAccessorFactorySupport * @see org.springframework.messaging.support.IdTimestampMessageHeaderInitializer
*/ */
void setIdGenerator(IdGenerator idGenerator) { void setIdGenerator(IdGenerator idGenerator) {
this.idGenerator = idGenerator; this.idGenerator = idGenerator;
} }
/** /**
* A private mechanism to enable having a timestamp added to every message. * A package private mechanism to enables the automatic addition of the
* {@link org.springframework.messaging.MessageHeaders#TIMESTAMP} header.
* *
* <p>By default this property is set to false. * <p>By default this property is set to false.
* *
* @see org.springframework.messaging.support.MessageHeaderAccessorFactorySupport * @see org.springframework.messaging.support.IdTimestampMessageHeaderInitializer
*/ */
void setEnableTimestamp(boolean enableTimestamp) { void setEnableTimestamp(boolean enableTimestamp) {
this.enableTimestamp = enableTimestamp; this.enableTimestamp = enableTimestamp;

View File

@@ -1,67 +0,0 @@
/*
* Copyright 2002-2014 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.messaging.support;
import org.springframework.util.IdGenerator;
/**
* A support class for factories creating pre-configured instances of type
* {@link org.springframework.messaging.support.MessageHeaderAccessor}.
*
* @author Rossen Stoyanchev
* @since 4.1
*/
public class MessageHeaderAccessorFactorySupport {
private IdGenerator idGenerator;
private Boolean enableTimestamp;
protected MessageHeaderAccessorFactorySupport() {
}
/**
*
* @param idGenerator
*/
public void setIdGenerator(IdGenerator idGenerator) {
this.idGenerator = idGenerator;
}
public IdGenerator getIdGenerator() {
return this.idGenerator;
}
public void setEnableTimestamp(boolean enableTimestamp) {
this.enableTimestamp = enableTimestamp;
}
public boolean isEnableTimestamp() {
return this.enableTimestamp;
}
protected void updateMessageHeaderAccessor(MessageHeaderAccessor headerAccessor) {
if (this.idGenerator != null) {
headerAccessor.setIdGenerator(this.idGenerator);
}
if (this.enableTimestamp != null) {
headerAccessor.setEnableTimestamp(this.enableTimestamp);
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2002-2014 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.messaging.support;
/**
* Callback interface for initializing a
* {@link org.springframework.messaging.support.MessageHeaderAccessor MessageHeaderAccessor}.
*
* @author Rossen Stoyanchev
* @since 4.1
*/
public interface MessageHeaderInitializer {
/**
* Initialize the given {@code MessageHeaderAccessor}.
*
* @param headerAccessor the instance to initialize
*/
void initHeaders(MessageHeaderAccessor headerAccessor);
}

View File

@@ -26,6 +26,7 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType; import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.support.IdTimestampMessageHeaderInitializer;
import org.springframework.messaging.support.MessageBuilder; import org.springframework.messaging.support.MessageBuilder;
import org.springframework.messaging.support.MessageHeaderAccessor; import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.util.AlternativeJdkIdGenerator; import org.springframework.util.AlternativeJdkIdGenerator;
@@ -221,15 +222,15 @@ public class StompHeaderAccessorTests {
@Test @Test
public void messageIdAndTimestampEnabled() { public void messageIdAndTimestampEnabled() {
DefaultStompHeaderAccessorFactory factory = new DefaultStompHeaderAccessorFactory(); IdTimestampMessageHeaderInitializer headerInitializer = new IdTimestampMessageHeaderInitializer();
factory.setIdGenerator(new AlternativeJdkIdGenerator()); headerInitializer.setIdGenerator(new AlternativeJdkIdGenerator());
factory.setEnableTimestamp(true); headerInitializer.setEnableTimestamp(true);
StompHeaderAccessor headerAccessor = factory.create(StompCommand.SEND); StompHeaderAccessor headerAccessor = StompHeaderAccessor.create(StompCommand.SEND);
MessageHeaders headers = headerAccessor.getMessageHeaders(); headerInitializer.initHeaders(headerAccessor);
assertNotNull(headers.getId()); assertNotNull(headerAccessor.getMessageHeaders().getId());
assertNotNull(headers.getTimestamp()); assertNotNull(headerAccessor.getMessageHeaders().getTimestamp());
} }
@Test @Test