1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.id.random;
19
20 import org.apache.commons.id.AbstractStringIdentifierGenerator;
21
22 import java.io.Serializable;
23 import java.util.Random;
24
25 /**
26 * <code>SessionIdGenerator</code> is an identifier generator
27 * that generates an alphanumeric 10+ character identifier.
28 *
29 * <p>The exact length depends on the number of ids requested per time
30 * period. Multiple instances of the class generate still unique ids.</p>
31 *
32 * <p>Originally designed for JServ sessions. Uses synchronized count and
33 * time to ensure uniqueness. Not guaranteed unique across JVMs, but
34 * fairly safe nonetheless.</p>
35
36 * @author Commons-Id team
37 * @version $Id: SessionIdGenerator.java 480488 2006-11-29 08:57:26Z bayard $
38 */
39 public class SessionIdGenerator extends AbstractStringIdentifierGenerator implements Serializable {
40
41 /**
42 * <code>serialVersionUID</code> is the serializable UID for the binary version of the class.
43 */
44 private static final long serialVersionUID = 20060118L;
45 /**
46 * We want to have a random string with a length of 6 characters.
47 * Since we encode it base-36, we modulo the random number with
48 * this value.
49 */
50 private static final long MAX_RANDOM_LEN = 2176782336L; // 36 ** 6
51 /**
52 * <p>The identifier must be unique within the typical lifespan of a
53 * session; the value can roll over after that.</p>3 characters:
54 * (this means a roll over after over a day, which is much larger
55 * than a typical lifespan).
56 */
57 private static final long MAX_TIME_SECTION_LEN = 46656L; // 36 ** 3
58 /**
59 * Milliseconds between different tics. The 3-character time
60 * string has a new value every 2 seconds.
61 */
62 private static final long TIC_DIFFERENCE = 2000;
63 /**
64 * Length of random segment
65 */
66 private static final int RANDOM_LENGTH = 6;
67 /**
68 * Length of time segment
69 */
70 private static final int TIME_LENGTH = 3;
71
72 /** The incrementing counter. */
73 private int counter = 0;
74 /** The last time. */
75 private long lastTimeValue = 0;
76 /** The randmonizer. */
77 private static Random randomizer = new Random();
78
79 /**
80 * Constructor.
81 */
82 public SessionIdGenerator() {
83 super();
84 }
85
86 public long maxLength() {
87 return RANDOM_LENGTH + TIME_LENGTH
88 + AbstractStringIdentifierGenerator.MAX_INT_ALPHANUMERIC_VALUE_LENGTH;
89 }
90
91 public long minLength() {
92 return RANDOM_LENGTH + TIME_LENGTH + 1;
93 }
94
95 /**
96 * Gets the next new identifier.
97 *
98 * <p>Only guaranteed unique within this JVM, but fairly safe
99 * for cross JVM usage as well.</p>
100 *
101 * <p>Format of identifier is
102 * <code>[6 chars random][3 chars time][1+ chars count]</code></p>
103 *
104 * @return the next 10 char String identifier
105 */
106 public String nextStringIdentifier() {
107
108 // Random value
109 //--------------
110 long currentRandom = randomizer.nextLong();
111 if (currentRandom < 0) {
112 currentRandom = -currentRandom;
113 }
114 // force value into 6 char range, and add to pad with zeros
115 // this gives a length of 7, when converted to base 36, and
116 // the first character (always 1 from the add) is dropped
117 currentRandom %= MAX_RANDOM_LEN;
118 currentRandom += MAX_RANDOM_LEN;
119
120 long currentTimeValue = 0;
121 int currentCount = 0;
122
123 synchronized (this) {
124 // Time
125 //--------------
126 currentTimeValue = (System.currentTimeMillis() / TIC_DIFFERENCE);
127
128 // force value into 3 char range, and add to pad with zeros
129 // this gives a length of 4, when converted to base 36, and
130 // the first character (always 1 from the add) is dropped
131 currentTimeValue %= MAX_TIME_SECTION_LEN;
132 currentTimeValue += MAX_TIME_SECTION_LEN;
133
134 // Count
135 //--------------
136 // Make the string unique by appending the count since last
137 // time flip.
138
139 // Count sessions only within tics (so the 'real' counter
140 // isn't exposed to the public).
141 if (lastTimeValue != currentTimeValue) {
142 lastTimeValue = currentTimeValue;
143 counter = 0;
144 }
145 currentCount = counter++;
146 }
147
148 // build string
149 //--------------
150 StringBuffer id = new StringBuffer
151 (AbstractStringIdentifierGenerator.DEFAULT_ALPHANUMERIC_IDENTIFIER_SIZE);
152 id.append(Long.toString(currentRandom,
153 AbstractStringIdentifierGenerator.ALPHA_NUMERIC_CHARSET_SIZE).substring(1)); // 6 chars
154 id.append(Long.toString(currentTimeValue,
155 AbstractStringIdentifierGenerator.ALPHA_NUMERIC_CHARSET_SIZE).substring(1)); // 3 chars
156 id.append(Long.toString(currentCount,
157 AbstractStringIdentifierGenerator.ALPHA_NUMERIC_CHARSET_SIZE)); // 1+ chars
158 return id.toString();
159 }
160 }