--- /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: