1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package no.feide.moria.store;
22
23 import java.security.NoSuchAlgorithmException;
24 import java.security.SecureRandom;
25 import java.util.Date;
26
27 /***
28 * Returns an id that is random and unique across a cluster of JVMs. Each JVM
29 * needs to be configured with a unique node id, identifying each node. This is
30 * done by setting the system property <code>no.feide.moria.store.nodeid</code>.
31 * The value must be a ascii string of 3 character length. The returned id is an
32 * encoded String (pseudo Base64, see method documentation for details)
33 * constructed from the node id, the current time and a random string. This
34 * should guarantee unique ids across the cluster and node restarts.
35 * @author Bjørn Ola Smievoll <b.o@smievoll.no>
36 * @version $Revision: 1.11 $
37 */
38
39 public final class RandomId {
40
41 /*** The random generator. */
42 private static SecureRandom random;
43
44 static {
45
46 try {
47 random = SecureRandom.getInstance("SHA1PRNG");
48 } catch (NoSuchAlgorithmException e) {
49 throw new RuntimeException("Unable to get instance of SecureRandom", e);
50 }
51 }
52
53 /*** The characters used in our version of base64. */
54 private static final byte[] CHAR_64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789*-".getBytes();
55
56 /*** The number of random bits to get from the PRNG. */
57 private static final int NO_OF_RANDOM_BITS = 384;
58
59
60 /***
61 * Default private constructor.
62 */
63 private RandomId() {
64
65 }
66
67
68 /***
69 * Generate a new random id.
70 *
71 * @return an encoded random string
72 */
73 public static String newId(final String nodeId) {
74
75 final byte[] nodeIdBytes = nodeIdToByteArray(nodeId);
76
77
78 byte[] now = longToByteArray(new Date().getTime());
79
80
81 int noOfRandomBytes = NO_OF_RANDOM_BITS / 8;
82
83 if ((NO_OF_RANDOM_BITS % 8) != 0)
84 noOfRandomBytes++;
85
86
87 byte[] randomBytes = new byte[noOfRandomBytes];
88 random.nextBytes(randomBytes);
89
90
91 byte[] id = new byte[nodeIdBytes.length + now.length + randomBytes.length];
92 System.arraycopy(nodeIdBytes, 0, id, 0, nodeIdBytes.length);
93 System.arraycopy(now, 0, id, nodeIdBytes.length, now.length);
94 System.arraycopy(randomBytes, 0, id, nodeIdBytes.length + now.length, randomBytes.length);
95
96
97 return pseudoBase64Encode(id);
98 }
99
100
101 /***
102 * Takes a byte array and returns a string encoded with a slightly modified
103 * version of Base64. The difference to standard Base64 is that the
104 * extra two chars, "+" and "/" have been exchanged for the more
105 * url-friendly "-" and "*", and the resulting string is not padded with =
106 * as required by the spec (rfc 2045). Parts of code Copyright (c) 2003,
107 * Sverre H. Huseby <shh@thathost.com>
108 * @param bytes
109 * The data to convert.
110 * @return The encoded version of the input.
111 */
112 static String pseudoBase64Encode(final byte[] bytes) {
113
114
115 StringBuffer finalId = new StringBuffer((bytes.length * 4) / 3);
116
117
118 int byteOffset;
119
120 int currentByte;
121
122 int charOffset;
123
124
125
126
127
128 for (byteOffset = 0;;) {
129
130
131 if (byteOffset >= bytes.length)
132 break;
133 currentByte = ((int) bytes[byteOffset++]) & 255;
134 charOffset = currentByte >> 2;
135 finalId.append((char) CHAR_64[charOffset]);
136
137
138 charOffset = (currentByte & 3) << 4;
139 if (byteOffset < bytes.length) {
140 currentByte = ((int) bytes[byteOffset++]) & 255;
141 charOffset |= currentByte >> 4;
142 finalId.append((char) CHAR_64[charOffset]);
143 } else {
144 finalId.append((char) CHAR_64[charOffset]);
145 break;
146 }
147
148
149 charOffset = (currentByte & 15) << 2;
150 if (byteOffset < bytes.length) {
151 currentByte = ((int) bytes[byteOffset++]) & 255;
152 charOffset |= currentByte >> 6;
153 finalId.append((char) CHAR_64[charOffset]);
154 } else {
155 finalId.append((char) CHAR_64[charOffset]);
156 break;
157 }
158
159
160 charOffset = currentByte & 63;
161 finalId.append((char) CHAR_64[charOffset]);
162 }
163
164 return finalId.toString();
165 }
166
167
168 /***
169 * Takes a long value (64 bit) and returns it as an eight element byte
170 * array.
171 * @param in
172 * The long value to be converted.
173 * @return A byte array representation of the long value given as input.
174 */
175 static byte[] longToByteArray(final long in) {
176
177
178 long local = in;
179
180
181 final int longSize = 8;
182 byte[] out = new byte[longSize];
183
184
185
186
187
188
189
190 for (int i = longSize - 1; i > -1; i--) {
191 out[i] = (byte) (local & 0xffL);
192 local >>= 8;
193 }
194
195 return out;
196 }
197
198 /***
199 * Takes an unsigned short value (16 bit, as an 32 bit int value) and returns it as
200 * an two element byte array.
201 *
202 * @param in
203 * the unsigned short value to be converted.
204 * @return a byte array representation of the short value given as input
205 */
206 static byte[] unsignedShortToByteArray(final int in) {
207
208 if (in < 0 || in > 65535) {
209 throw new IllegalArgumentException("in value must be between 0 and 2^16.");
210 }
211
212
213 int local = in;
214
215
216 final int shortSize = 2;
217 byte[] out = new byte[shortSize];
218
219
220
221 for (int i = shortSize - 1; i > -1; i--) {
222 out[i] = (byte) (local & 0xffL);
223 local >>= 8;
224 }
225
226 return out;
227 }
228
229 static byte[] nodeIdToByteArray(final String nodeId) {
230
231 final String errorMsg = "nodeId has illegal form. Must be <ip-addr>:<port>.";
232
233
234 String[] nodeElements = nodeId.split(":");
235
236 if (nodeElements.length != 2) {
237 throw new IllegalArgumentException(errorMsg);
238 }
239
240
241 String[] addressBytes = nodeElements[0].split("//.");
242
243 if (addressBytes.length != 4) {
244 throw new IllegalArgumentException(errorMsg);
245 }
246
247 byte[] buffer = new byte[6];
248
249 for (int i = 0; i < 4; i++) {
250
251 buffer[i] = Byte.parseByte(Integer.toString(Integer.parseInt(addressBytes[i]) - 128));
252 }
253
254
255 byte[] portBytes = unsignedShortToByteArray(Integer.parseInt(nodeElements[1]));
256
257 if (portBytes.length != 2) {
258 throw new IllegalArgumentException(errorMsg);
259 }
260
261 for (int i = 0; i < 2; i++) {
262 buffer[i + 4] = portBytes[i];
263 }
264
265 return buffer;
266 }
267 }