Browse Source

Use alternative UUID strategy in MessageHeaders

This change adds an alternative UUID generation strategy to use by
default in MessageHeaders. Instead of using SecureRandom for each
new UUID, SecureRandom is used only for the initial seed to be
provided java.util.Random. Thereafter the same Random instance is
used instead. This provides improved performance while id's are
still random but less securely so.
pull/387/head
Rossen Stoyanchev 11 years ago
parent
commit
70dfec269b
  1. 49
      spring-messaging/src/main/java/org/springframework/messaging/MessageHeaders.java
  2. 154
      spring-messaging/src/test/java/org/springframework/messaging/MessageHeadersTests.java

49
spring-messaging/src/main/java/org/springframework/messaging/MessageHeaders.java

@ -20,6 +20,8 @@ import java.io.IOException; @@ -20,6 +20,8 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -28,6 +30,7 @@ import java.util.HashMap; @@ -28,6 +30,7 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
@ -56,6 +59,7 @@ import org.apache.commons.logging.LogFactory; @@ -56,6 +59,7 @@ import org.apache.commons.logging.LogFactory;
* @author Arjen Poutsma
* @author Mark Fisher
* @author Gary Russell
* @author Rossen Stoyanchev
* @since 4.0
* @see org.springframework.messaging.support.MessageBuilder
*/
@ -65,9 +69,10 @@ public final class MessageHeaders implements Map<String, Object>, Serializable { @@ -65,9 +69,10 @@ public final class MessageHeaders implements Map<String, Object>, Serializable {
private static final Log logger = LogFactory.getLog(MessageHeaders.class);
private static volatile IdGenerator idGenerator = null;
private static volatile IdGenerator defaultIdGenerator = new AlternativeJdkIdGenerator();
/**
* The key for the Message ID. This is an automatically generated UUID and
* should never be explicitly set in the header map <b>except</b> in the
@ -92,13 +97,8 @@ public final class MessageHeaders implements Map<String, Object>, Serializable { @@ -92,13 +97,8 @@ public final class MessageHeaders implements Map<String, Object>, Serializable {
public MessageHeaders(Map<String, Object> headers) {
this.headers = (headers != null) ? new HashMap<String, Object>(headers) : new HashMap<String, Object>();
if (MessageHeaders.idGenerator == null){
this.headers.put(ID, UUID.randomUUID());
}
else {
this.headers.put(ID, MessageHeaders.idGenerator.generateId());
}
IdGenerator generatorToUse = (idGenerator != null) ? idGenerator : defaultIdGenerator;
this.headers.put(ID, generatorToUse.generateId());
this.headers.put(TIMESTAMP, new Long(System.currentTimeMillis()));
}
@ -247,4 +247,37 @@ public final class MessageHeaders implements Map<String, Object>, Serializable { @@ -247,4 +247,37 @@ public final class MessageHeaders implements Map<String, Object>, Serializable {
public static interface IdGenerator {
UUID generateId();
}
/**
* A variation of {@link UUID#randomUUID()} that uses {@link SecureRandom} only for
* the initial seed and {@link Random} thereafter, which provides better performance
* in exchange for less securely random id's.
*/
public static class AlternativeJdkIdGenerator implements IdGenerator {
private final Random random;
public AlternativeJdkIdGenerator() {
byte[] seed = new SecureRandom().generateSeed(8);
this.random = new Random(new BigInteger(seed).longValue());
}
public UUID generateId() {
byte[] randomBytes = new byte[16];
this.random.nextBytes(randomBytes);
long mostSigBits = 0;
for (int i = 0; i < 8; i++) {
mostSigBits = (mostSigBits << 8) | (randomBytes[i] & 0xff);
}
long leastSigBits = 0;
for (int i = 8; i < 16; i++) {
leastSigBits = (leastSigBits << 8) | (randomBytes[i] & 0xff);
}
return new UUID(mostSigBits, leastSigBits);
}
}
}

154
spring-messaging/src/test/java/org/springframework/messaging/MessageHeadersTests.java

@ -0,0 +1,154 @@ @@ -0,0 +1,154 @@
/*
* 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;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Test fixture for {@link MessageHeaders}.
*
* @author Rossen Stoyanchev
*/
public class MessageHeadersTests {
@Test
public void testTimestamp() {
MessageHeaders headers = new MessageHeaders(null);
assertNotNull(headers.getTimestamp());
}
@Test
public void testTimestampOverwritten() throws Exception {
MessageHeaders headers1 = new MessageHeaders(null);
Thread.sleep(50L);
MessageHeaders headers2 = new MessageHeaders(headers1);
assertNotSame(headers1.getTimestamp(), headers2.getTimestamp());
}
@Test
public void testIdOverwritten() throws Exception {
MessageHeaders headers1 = new MessageHeaders(null);
MessageHeaders headers2 = new MessageHeaders(headers1);
assertNotSame(headers1.getId(), headers2.getId());
}
@Test
public void testId() {
MessageHeaders headers = new MessageHeaders(null);
assertNotNull(headers.getId());
}
@Test
public void testNonTypedAccessOfHeaderValue() {
Integer value = new Integer(123);
Map<String, Object> map = new HashMap<String, Object>();
map.put("test", value);
MessageHeaders headers = new MessageHeaders(map);
assertEquals(value, headers.get("test"));
}
@Test
public void testTypedAccessOfHeaderValue() {
Integer value = new Integer(123);
Map<String, Object> map = new HashMap<String, Object>();
map.put("test", value);
MessageHeaders headers = new MessageHeaders(map);
assertEquals(value, headers.get("test", Integer.class));
}
@Test(expected = IllegalArgumentException.class)
public void testHeaderValueAccessWithIncorrectType() {
Integer value = new Integer(123);
Map<String, Object> map = new HashMap<String, Object>();
map.put("test", value);
MessageHeaders headers = new MessageHeaders(map);
assertEquals(value, headers.get("test", String.class));
}
@Test
public void testNullHeaderValue() {
Map<String, Object> map = new HashMap<String, Object>();
MessageHeaders headers = new MessageHeaders(map);
assertNull(headers.get("nosuchattribute"));
}
@Test
public void testNullHeaderValueWithTypedAccess() {
Map<String, Object> map = new HashMap<String, Object>();
MessageHeaders headers = new MessageHeaders(map);
assertNull(headers.get("nosuchattribute", String.class));
}
@Test
public void testHeaderKeys() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("key1", "val1");
map.put("key2", new Integer(123));
MessageHeaders headers = new MessageHeaders(map);
Set<String> keys = headers.keySet();
assertTrue(keys.contains("key1"));
assertTrue(keys.contains("key2"));
}
@Test
public void serializeWithAllSerializableHeaders() throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put("name", "joe");
map.put("age", 42);
MessageHeaders input = new MessageHeaders(map);
MessageHeaders output = (MessageHeaders) serializeAndDeserialize(input);
assertEquals("joe", output.get("name"));
assertEquals(42, output.get("age"));
}
@Test
public void serializeWithNonSerializableHeader() throws Exception {
Object address = new Object();
Map<String, Object> map = new HashMap<String, Object>();
map.put("name", "joe");
map.put("address", address);
MessageHeaders input = new MessageHeaders(map);
MessageHeaders output = (MessageHeaders) serializeAndDeserialize(input);
assertEquals("joe", output.get("name"));
assertNull(output.get("address"));
}
private static Object serializeAndDeserialize(Object object) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
out.writeObject(object);
out.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bais);
Object result = in.readObject();
in.close();
return result;
}
}
Loading…
Cancel
Save