scala/lift/pgsql-inet-model.scala
author Tomas Zeman <tzeman@volny.cz>
Wed, 23 May 2018 20:33:08 +0200
changeset 54 21fabe8ab141
parent 4 5ef63a5d98b2
permissions -rw-r--r--
Imported vim-ledger https://github.com/ledger/vim-ledger @ 6eb3bb2

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