View Javadoc
1   /*
2    * #%L
3    * ObjectId.java - mongodb-async-driver - Allanbank Consulting, Inc.
4    * %%
5    * Copyright (C) 2011 - 2014 Allanbank Consulting, Inc.
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   * 
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   * 
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package com.allanbank.mongodb.bson.element;
21  
22  import java.io.Serializable;
23  import java.lang.management.ManagementFactory;
24  import java.lang.management.RuntimeMXBean;
25  import java.net.InetAddress;
26  import java.net.NetworkInterface;
27  import java.security.MessageDigest;
28  import java.security.SecureRandom;
29  import java.util.Enumeration;
30  import java.util.concurrent.TimeUnit;
31  import java.util.concurrent.atomic.AtomicLong;
32  
33  import com.allanbank.mongodb.bson.io.EndianUtils;
34  import com.allanbank.mongodb.util.IOUtils;
35  
36  /**
37   * An Object Id.
38   * 
39   * @api.yes This class is part of the driver's API. Public and protected members
40   *          will be deprecated for at least 1 non-bugfix release (version
41   *          numbers are <major>.<minor>.<bugfix>) before being
42   *          removed or modified.
43   * @copyright 2011-2013, Allanbank Consulting, Inc., All Rights Reserved
44   */
45  public class ObjectId implements Serializable, Comparable<ObjectId> {
46  
47      /** The current process's machine id. */
48      public static final long MACHINE_ID;
49  
50      /** The counter to add to the machine id. */
51      private static final AtomicLong COUNTER;
52  
53      /** Serialization version for the class. */
54      private static final long serialVersionUID = -3035334151717895487L;
55  
56      static {
57          long value = 0;
58          final SecureRandom rand = new SecureRandom();
59          try {
60              boolean foundIface = true;
61              final MessageDigest md5 = MessageDigest.getInstance("MD5");
62  
63              try {
64                  final Enumeration<NetworkInterface> ifaces = NetworkInterface
65                          .getNetworkInterfaces();
66                  while (ifaces.hasMoreElements()) {
67                      try {
68                          final NetworkInterface iface = ifaces.nextElement();
69  
70                          if (!iface.isLoopback()) {
71                              md5.update(iface.getHardwareAddress());
72                              foundIface = true;
73                          }
74                      }
75                      catch (final Throwable tryAnotherIface) {
76                          // Noting to do. Try the next one.
77                          tryAnotherIface.hashCode(); // PMD - Shhhh.
78                      }
79                  }
80              }
81              catch (final Throwable tryTheHostName) {
82                  // Nothing to do here. Fall through.
83                  tryTheHostName.hashCode(); // PMD - Shhhh.
84              }
85  
86              if (!foundIface) {
87                  md5.update(InetAddress.getLocalHost().getHostName()
88                          .getBytes("UTF8"));
89              }
90  
91              final byte[] hash = md5.digest();
92              value += (hash[0] & 0xFF);
93              value <<= Byte.SIZE;
94              value += (hash[1] & 0xFF);
95              value <<= Byte.SIZE;
96              value += (hash[2] & 0xFF);
97              value <<= Byte.SIZE;
98          }
99          catch (final Throwable t) {
100             // Degenerate to a random machine id.
101             for (int i = 0; i < 3; ++i) {
102                 value += rand.nextInt(256);
103                 value <<= Byte.SIZE;
104             }
105         }
106 
107         // Try and find the process id from the runtime.
108         int processId;
109         try {
110             final RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
111             final String processName = runtime.getName();
112             final int atLoc = processName.indexOf('@');
113             if (atLoc >= 0) {
114                 final String pidString = processName.substring(0, atLoc);
115                 processId = Integer.parseInt(pidString);
116             }
117             else {
118                 // Degenerate to a random process id.
119                 processId = rand.nextInt();
120             }
121 
122         }
123         catch (final Throwable t) {
124             // Degenerate to a random process id.
125             processId = rand.nextInt();
126         }
127 
128         value += ((processId >> Byte.SIZE) & 0xFF);
129         value <<= Byte.SIZE;
130         value += (processId & 0xFF);
131 
132         MACHINE_ID = (value << 24);
133         COUNTER = new AtomicLong(rand.nextLong() & 0xFFFFFFL);
134     }
135 
136     /**
137      * Generates the current timestamp value. This is the number of
138      * <b>seconds</b> since the Unix Epoch.
139      * 
140      * @return The unique object id value.
141      */
142     private static int now() {
143         return (int) TimeUnit.MILLISECONDS
144                 .toSeconds(System.currentTimeMillis());
145     }
146 
147     /**
148      * Generates the current timestamp value. This is the number of
149      * <b>seconds</b> since the Unix Epoch.
150      * 
151      * @return The unique object id value.
152      */
153     private static long processId() {
154         return MACHINE_ID + (COUNTER.incrementAndGet() & 0xFFFFFFL);
155     }
156 
157     /** The BSON Object Id's machine identifier. */
158     private final long myMachineId;
159 
160     /** The BSON ObjectId's timestamp. */
161     private final int myTimestamp;
162 
163     /**
164      * Constructs a new {@link ObjectId}.
165      */
166     public ObjectId() {
167         this(now(), processId());
168     }
169 
170     /**
171      * Constructs a new {@link ObjectId}.
172      * 
173      * @param timestamp
174      *            The BSON Object Id timestamp.
175      * @param machineId
176      *            The BSON Object Id machine id.
177      */
178     public ObjectId(final int timestamp, final long machineId) {
179         myTimestamp = timestamp;
180         myMachineId = machineId;
181     }
182 
183     /**
184      * Constructs a new {@link ObjectId}.
185      * 
186      * @param hexBytes
187      *            The hex encoded byte value.
188      * @throws IllegalArgumentException
189      *             If the hex encoded string is not 24 characters.
190      */
191     public ObjectId(final String hexBytes) throws IllegalArgumentException {
192 
193         if (hexBytes.length() != 24) {
194             throw new IllegalArgumentException(
195                     "Invalid ObjectId value.  Must be a 24 character hex string.");
196         }
197 
198         final byte[] bytes = IOUtils.hexToBytes(hexBytes);
199         int timestamp = 0;
200         for (int i = 0; i < 4; ++i) {
201             int value = (bytes[i] & 0xFF);
202             value <<= (Byte.SIZE * i);
203             timestamp += value;
204         }
205 
206         long machineId = 0;
207         for (int i = 4; i < 12; ++i) {
208             long value = (bytes[i] & 0xFF);
209             value <<= (Byte.SIZE * (i - 4));
210             machineId += value;
211         }
212 
213         myTimestamp = EndianUtils.swap(timestamp);
214         myMachineId = EndianUtils.swap(machineId);
215     }
216 
217     /**
218      * {@inheritDoc}
219      * <p>
220      * Overridden to compare the object ids based on the tuple (timestamp,
221      * machineId).
222      * </p>
223      */
224     @Override
225     public int compareTo(final ObjectId other) {
226         int result = myTimestamp - other.myTimestamp;
227         if (result == 0) {
228             result = (myMachineId < other.myMachineId) ? -1
229                     : ((myMachineId == other.myMachineId) ? 0 : 1);
230         }
231         return result;
232     }
233 
234     /**
235      * Determines if the passed object is of this same type as this object and
236      * if so that its fields are equal.
237      * 
238      * @param object
239      *            The object to compare to.
240      * 
241      * @see java.lang.Object#equals(java.lang.Object)
242      */
243     @Override
244     public boolean equals(final Object object) {
245         boolean result = false;
246         if (this == object) {
247             result = true;
248         }
249         else if ((object != null) && (getClass() == object.getClass())) {
250             final ObjectId other = (ObjectId) object;
251 
252             result = (myMachineId == other.myMachineId)
253                     && (myTimestamp == other.myTimestamp);
254         }
255         return result;
256     }
257 
258     /**
259      * The low 3 byte value of the machine id.
260      * 
261      * @return The low 3 byte value of the machine id.
262      */
263     public int getCounterField() {
264         return (int) (myMachineId & 0xFFFFFFL);
265     }
266 
267     /**
268      * The lower 8 bytes of the object id. This is the machine identifier field
269      * and counter.
270      * 
271      * @return The lower 8 bytes of the object id.
272      */
273     public long getMachineId() {
274         return myMachineId;
275     }
276 
277     /**
278      * The upper 3 bytes in the machine id.
279      * 
280      * @return The upper 3 bytes of the machine id.
281      */
282     public int getMachineIdentifier() {
283         return (int) ((myMachineId >> 40) & 0xFFFFFFL);
284     }
285 
286     /**
287      * Middle 2 byte process id field from the machine id.
288      * 
289      * @return The middle 2 byte process id field from the machine id.
290      */
291     public int getPidField() {
292         return (int) ((myMachineId >> 24) & 0xFFFFL);
293     }
294 
295     /**
296      * The upper 4 bytes of the object id. This is the <b>seconds</b> since the
297      * UNIX Epoch.
298      * 
299      * @return The upper 4 bytes of the object id.
300      */
301     public int getTimestamp() {
302         return myTimestamp;
303     }
304 
305     /**
306      * Computes a reasonable hash code.
307      * 
308      * @return The hash code value.
309      */
310     @Override
311     public int hashCode() {
312         int result = 0;
313         result += (int) ((myMachineId >> 32) & 0xFFFFFFFF);
314         result += (int) (myMachineId & 0xFFFFFFFF);
315         result += myTimestamp;
316         return result;
317     }
318 
319     /**
320      * Returns the HEX string form of the ObjectId.
321      * 
322      * @return The HEX string form of the ObjectId.
323      */
324     public String toHexString() {
325         final StringBuilder builder = new StringBuilder();
326         String hex = Integer.toHexString(myTimestamp);
327         builder.append("00000000".substring(hex.length()));
328         builder.append(hex);
329 
330         hex = Long.toHexString(myMachineId);
331         builder.append("0000000000000000".substring(hex.length()));
332         builder.append(hex);
333         return builder.toString();
334     }
335 
336     /**
337      * String form of the object.
338      * 
339      * @return A human readable form of the object.
340      * 
341      * @see Object#toString()
342      */
343     @Override
344     public String toString() {
345         final StringBuilder builder = new StringBuilder();
346 
347         builder.append("ObjectId(");
348         builder.append(toHexString());
349         builder.append(")");
350 
351         return builder.toString();
352     }
353 }