com.github.jeroenr.bson.element.BsonObjectId.scala Maven / Gradle / Ivy
The newest version!
package com.github.jeroenr.bson.element
import java.util.concurrent.atomic.AtomicInteger
import com.github.jeroenr.bson.Implicits.BsonValueObjectId
import com.github.jeroenr.bson.util.Converters
case class BsonObjectId(name: String, value: BsonValueObjectId) extends BsonElement {
val code = 0x07.toByte
}
object BsonObjectId {
private val maxCounterValue = 16777216
private val increment = new AtomicInteger(scala.util.Random.nextInt(maxCounterValue))
private def counter = (increment.getAndIncrement + maxCounterValue) % maxCounterValue
/**
* The following implemtation of machineId work around openjdk limitations in
* version 6 and 7
*
* Openjdk fails to parse /proc/net/if_inet6 correctly to determine macaddress
* resulting in SocketException thrown.
*
* Please see:
* * https://github.com/openjdk-mirror/jdk7u-jdk/blob/feeaec0647609a1e6266f902de426f1201f77c55/src/solaris/native/java/net/NetworkInterface.c#L1130
* * http://lxr.free-electrons.com/source/net/ipv6/addrconf.c?v=3.11#L3442
* * http://lxr.free-electrons.com/source/include/linux/netdevice.h?v=3.11#L1130
* * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7078386
*
* and fix in openjdk8:
* * http://hg.openjdk.java.net/jdk8/tl/jdk/rev/b1814b3ea6d3
*/
private val machineId = {
import java.net._
import scala.util.Try
val validPlatform = Try {
val correctVersion = System.getProperty("java.version").substring(0, 3).toFloat >= 1.8
val noIpv6 = System.getProperty("java.net.preferIPv4Stack") == true
val isLinux = System.getProperty("os.name") == "Linux"
!isLinux || correctVersion || noIpv6
}.getOrElse(false)
// Check java policies
val permitted = {
val sec = System.getSecurityManager();
Try {
sec.checkPermission(new NetPermission("getNetworkInformation"))
}.toOption.map(_ => true).getOrElse(false);
}
if (validPlatform && permitted) {
val networkInterfacesEnum = NetworkInterface.getNetworkInterfaces
val networkInterfaces = scala.collection.JavaConverters.enumerationAsScalaIteratorConverter(networkInterfacesEnum).asScala
val ha = networkInterfaces.find(ha => Try(ha.getHardwareAddress).isSuccess && ha.getHardwareAddress != null && ha.getHardwareAddress.length == 6)
.map(_.getHardwareAddress)
.getOrElse(InetAddress.getLocalHost.getHostName.getBytes)
Converters.md5(ha).take(3)
} else {
val threadId = Thread.currentThread.getId.toInt
val arr = new Array[Byte](3)
arr(0) = (threadId & 0xFF).toByte
arr(1) = (threadId >> 8 & 0xFF).toByte
arr(2) = (threadId >> 16 & 0xFF).toByte
arr
}
}
/**
* Generates a new BSON ObjectID.
*
* +------------------------+------------------------+------------------------+------------------------+
* + timestamp (in seconds) + machine identifier + thread identifier + increment +
* + (4 bytes) + (3 bytes) + (2 bytes) + (3 bytes) +
* +------------------------+------------------------+------------------------+------------------------+
*
* The returned BsonObjectId contains a timestamp set to the current time (in seconds),
* with the `machine identifier`, `thread identifier` and `increment` properly set.
*/
def generate: BsonValueObjectId = fromTime(System.currentTimeMillis, false)
/**
* Generates a new BSON ObjectID from the given timestamp in milliseconds.
*
* +------------------------+------------------------+------------------------+------------------------+
* + timestamp (in seconds) + machine identifier + thread identifier + increment +
* + (4 bytes) + (3 bytes) + (2 bytes) + (3 bytes) +
* +------------------------+------------------------+------------------------+------------------------+
*
* The included timestamp is the number of seconds since epoch, so a BsonObjectId time part has only
* a precision up to the second. To get a reasonably unique ID, you _must_ set `onlyTimestamp` to false.
*
* Crafting a BsonObjectId from a timestamp with `fillOnlyTimestamp` set to true is helpful for range queries,
* eg if you want of find documents an _id field which timestamp part is greater than or lesser than
* the one of another id.
*
* If you do not intend to use the produced BsonObjectId for range queries, then you'd rather use
* the `generate` method instead.
*
* @param fillOnlyTimestamp if true, the returned BsonObjectId will only have the timestamp bytes set; the other will be set to zero.
*/
def fromTime(timeMillis: Long, fillOnlyTimestamp: Boolean = true): BsonValueObjectId = {
// n of seconds since epoch. Big endian
val timestamp = (timeMillis / 1000).toInt
val id = new Array[Byte](12)
id(0) = (timestamp >>> 24).toByte
id(1) = (timestamp >> 16 & 0xFF).toByte
id(2) = (timestamp >> 8 & 0xFF).toByte
id(3) = (timestamp & 0xFF).toByte
if (!fillOnlyTimestamp) {
// machine id, 3 first bytes of md5(macadress or hostname)
id(4) = machineId(0)
id(5) = machineId(1)
id(6) = machineId(2)
// 2 bytes of the pid or thread id. Thread id in our case. Low endian
val threadId = Thread.currentThread.getId.toInt
id(7) = (threadId & 0xFF).toByte
id(8) = (threadId >> 8 & 0xFF).toByte
// 3 bytes of counter sequence, which start is randomized. Big endian
val c = counter
id(9) = (c >> 16 & 0xFF).toByte
id(10) = (c >> 8 & 0xFF).toByte
id(11) = (c & 0xFF).toByte
}
BsonValueObjectId(id)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy