# HG changeset patch # User Tomas Zeman # Date 1287485048 -7200 # Node ID 5ef63a5d98b2a41381f55889f05ef123ba737970 # Parent d45ef9f0c1ae5e27e5a1883673defe6ce438b103 Lift: MappedInetAddress, Inet networks hierarchy mapper diff -r d45ef9f0c1ae -r 5ef63a5d98b2 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 InetAddress mapping + * (maps to ''inet'' data type). + * Always use asInet to get InetAddress 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 null value. + * Always use asInet. + */ +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 other. + * @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: