From 70dfec269b2ea249b2abf3cf252774a6fd578b39 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 14 Oct 2013 21:55:13 -0400 Subject: [PATCH] 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. --- .../messaging/MessageHeaders.java | 49 +++++- .../messaging/MessageHeadersTests.java | 154 ++++++++++++++++++ 2 files changed, 195 insertions(+), 8 deletions(-) create mode 100644 spring-messaging/src/test/java/org/springframework/messaging/MessageHeadersTests.java diff --git a/spring-messaging/src/main/java/org/springframework/messaging/MessageHeaders.java b/spring-messaging/src/main/java/org/springframework/messaging/MessageHeaders.java index 3a6c9bd6c15..ef635e703a1 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/MessageHeaders.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/MessageHeaders.java @@ -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; 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; * @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, 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 except in the @@ -92,13 +97,8 @@ public final class MessageHeaders implements Map, Serializable { public MessageHeaders(Map headers) { this.headers = (headers != null) ? new HashMap(headers) : new HashMap(); - 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, 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); + } + } + } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/MessageHeadersTests.java b/spring-messaging/src/test/java/org/springframework/messaging/MessageHeadersTests.java new file mode 100644 index 00000000000..7b7446caa8c --- /dev/null +++ b/spring-messaging/src/test/java/org/springframework/messaging/MessageHeadersTests.java @@ -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 map = new HashMap(); + map.put("test", value); + MessageHeaders headers = new MessageHeaders(map); + assertEquals(value, headers.get("test")); + } + + @Test + public void testTypedAccessOfHeaderValue() { + Integer value = new Integer(123); + Map map = new HashMap(); + 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 map = new HashMap(); + map.put("test", value); + MessageHeaders headers = new MessageHeaders(map); + assertEquals(value, headers.get("test", String.class)); + } + + @Test + public void testNullHeaderValue() { + Map map = new HashMap(); + MessageHeaders headers = new MessageHeaders(map); + assertNull(headers.get("nosuchattribute")); + } + + @Test + public void testNullHeaderValueWithTypedAccess() { + Map map = new HashMap(); + MessageHeaders headers = new MessageHeaders(map); + assertNull(headers.get("nosuchattribute", String.class)); + } + + @Test + public void testHeaderKeys() { + Map map = new HashMap(); + map.put("key1", "val1"); + map.put("key2", new Integer(123)); + MessageHeaders headers = new MessageHeaders(map); + Set keys = headers.keySet(); + assertTrue(keys.contains("key1")); + assertTrue(keys.contains("key2")); + } + + @Test + public void serializeWithAllSerializableHeaders() throws Exception { + Map map = new HashMap(); + 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 map = new HashMap(); + 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; + } + +}