Lift: MappedInetAddress, Inet networks hierarchy mapper
authorTomas Zeman <tzeman@volny.cz>
Tue, 19 Oct 2010 12:44:08 +0200
changeset 4 5ef63a5d98b2
parent 3 d45ef9f0c1ae
child 5 824fbbce8e65
Lift: MappedInetAddress, Inet networks hierarchy mapper
scala/lift/pgsql-inet-model.scala
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scala/lift/pgsql-inet-model.scala	Tue Oct 19 12:44:08 2010 +0200
@@ -0,0 +1,199 @@
+/*
+ * Copyright ...
+ */
+package example
+
+import java.net.InetAddress
+import net.liftweb.common._
+import net.liftweb.mapper._
+import net.liftweb.util._
+import net.liftweb.util.Helpers._
+
+object InetAddressHelpers extends InetAddressHelpers
+
+trait InetAddressHelpers {
+  def normalize(inet: String): Box[String] = tryo {
+    InetAddress.getByName(inet).getHostAddress
+  }
+
+  def valid_?(inet: String): Boolean = (inet != null) && (tryo {
+    InetAddress.getByName(inet)
+  } isDefined ) && (inet.length > 0)
+
+  def toBigInt(inet: InetAddress): BigInt = BigInt(1, inet.getAddress)
+
+  def toInet(s: String): Box[InetAddress] = s match {
+    case null | "" => Empty
+    case _ => tryo { InetAddress.getByName(s) }
+  }
+}
+
+/**
+ * Postgres specific <code>InetAddress</code> mapping
+ * (maps to ''inet'' data type).
+ * Always use <code>asInet</code> to get <code>InetAddress</code> instance.
+ */
+abstract class MappedInetAddress[T<:Mapper[T]](owner: T) extends
+  MappedString[T](owner, 128) with InetAddressHelpers {
+
+  override def setFilter = normalizeFilter _ :: super.setFilter
+
+  override def validate = (valid_?(i_is_!) match {
+    case true => Nil
+    case false => List(FieldError(this, "Invalid IP address"))
+  }) ::: super.validate
+
+  def normalizeFilter(inet: String): String = inet match {
+    case null | "" => null
+    case s => normalize(s) openOr null
+  }
+
+  def asInet: Box[InetAddress] = toInet(is)
+
+  override def targetSQLType = Types.OTHER
+
+  override def fieldCreatorString(d: DriverType, colName: String) = d match {
+    case _d: BasePostgreSQLDriver => colName + " inet"
+    case _ => super.fieldCreatorString(d, colName)
+  }
+}
+
+/**
+ * Caution: getters may return <code>null</code> value.
+ * Always use <code>asInet</code>.
+ */
+abstract class MappedNullableInetAddress[T<:Mapper[T]](owner: T) extends
+  MappedInetAddress[T](owner) {
+
+  override def defaultValue = null
+
+  override def validate = i_is_! match {
+    case null | "" => Nil
+    case _ => super.validate
+  }
+
+  override def dbNotNull_? = false
+}
+
+/**
+ * IP network mapper via its start/end fields.
+ * Implemented validation of IP overlaps, parent network, start, end.
+ * Maintains network relationships (sub-net/super-net) and updates
+ * parent field accordingly.
+ */
+object ExampleNetwork extends ExampleNetwork with
+  LongKeyedMetaMapper[ExampleNetwork] with LongCRUDify[ExampleNetwork] {
+
+  override def validation: List[ExampleNetwork => List[FieldError]] = 
+    startLtEnd _ :: checkOverlap _ :: super.validation
+
+  def startLtEnd(n: ExampleNetwork): List[FieldError] = (for {
+    s <- n.start.asInet
+    e <- n.end.asInet
+  } yield (toBigInt(s) < toBigInt(e))) match {
+    case Full(true) => Nil
+    case Full(false) =>
+      List(FieldError(n.start, "Start must be below end"))
+    case _ =>
+      List(FieldError(n.start, S ? "Can't compare start/end IP"))
+  }
+
+  def checkOverlap(net: ExampleNetwork): List[FieldError] = {
+    def run(f: MappedInetAddress[ExampleNetwork]) = {
+      findAll(inNet(f):_*) filter {!_.comparePrimaryKeys(net)} filter { n =>
+        net.isSubnetOf(n) match {
+          case f: Failure => true
+          case _ => false
+      }} map { n =>
+        FieldError(f, "Overlaps with: " + n.start + " - " + n.end)
+      }
+    }
+    run(net.start) ::: run(net.end)
+  }
+
+  override def beforeSave = List(n => n.parent(findParent(a)))
+
+  override def afterSave = List(n => children(n.parent.obj) foreach { child =>
+    child.parent(findParent(child)).save
+  })
+
+  override def afterDelete = List(n => children(Full(n)) foreach { child =>
+    child.parent(findParent(child)).save
+  })
+
+  def findParent(net: ExampleNetwork): Box[ExampleNetwork] =
+  findAll(inNet(net):_*) filter {!_.comparePrimaryKeys(net)} sort {
+    (n1, n2) => (for {
+      s1 <- n1.size
+      s2 <- n2.size
+  } yield s1 < s2) openOr true } firstOption
+
+  import OprEnum._
+  def inNet(inet: MappedInetAddress[ExampleNetwork]) = List(
+    Cmp(start, <=, Full(inet.is), Empty, Empty),
+    Cmp(end, >=, Full(inet.is), Empty, Empty)
+  )
+
+  def inNet(net: ExampleNetwork): List[QueryParam[ExampleNetwork]] =
+    inNet(net.start) ::: inNet(net.end)
+
+  def getByNet(s: String, e: String) = Box(findAll(By(start, s), By(end, e)))
+
+  def children(n: Box[ExampleNetwork]) = findAll(By(parent, n))
+}
+
+class ExampleNetwork extends LongKeyedMapper[ExampleNetwork] with IdPK
+  with InetAddressHelpers {
+
+  override def getSingleton = ExampleNetwork
+
+  object start extends MappedInetAddress(this)
+
+  object end extends MappedInetAddress(this)
+
+  object parent extends LongMappedMapper(this, ExampleNetwork) {
+    override def dbIncludeInForm_? = false
+  }
+
+  def size: Box[BigInt] = for {
+    s <- start.asInet
+    e <- end.asInet
+  } yield toBigInt(e) - toBigInt(s) + 1
+
+  /**
+   * Checks if this network is subnet of the <code>other</code>.
+   * @return true if this is subnet of the other
+   *         false if this is supernet
+   *         empty not related at all
+   *         failure if can't compare or network overlap
+   */
+  def isSubnetOf(other: ExampleNetwork): Box[Boolean] = {
+    def inside(what: BigInt, from: BigInt, to: BigInt): Boolean =
+      (from <= what) && (what <= to)
+
+    (for {
+      si <- start.asInet
+      ei <- end.asInet
+      osi <- other.start.asInet
+      oei <- other.end.asInet
+    } yield {
+      val s = toBigInt(si)
+      val e = toBigInt(ei)
+      val os = toBigInt(osi)
+      val oe = toBigInt(oei)
+      val s_inside = inside(s, os, oe)
+      val e_inside = inside(e, os, oe)
+      val os_inside = inside(os, s, e)
+      val oe_inside = inside(oe, s, e)
+      (s_inside, e_inside, os_inside, oe_inside) match {
+        case (true, true, _, _) => Full(true)
+        case (_, _, true, true) => Full(false)
+        case (false, false, false, false) => Empty
+        case (_, _, _, _) => Failure("IP address overlap with: " + other)
+      }
+    }).openOr(Failure("Failed to obtain inet addresses: this: " + this +
+      ", other: " + other))
+  }
+}
+
+// vim: set ts=2 sw=2 et: