All Downloads are FREE. Search and download functionalities are using the official Maven repository.

.circumflex-orm.2.5.source-code.ddl.scala Maven / Gradle / Ivy

The newest version!
package ru.circumflex
package orm

import core._
import java.io.File
import java.lang.IllegalStateException
import org.apache.commons.io.FileUtils
import collection.JavaConversions._
import java.lang.reflect.Modifier

/*!# Exporting Database Schema

The `DDLUnit` class provides API for creating and dropping database schema.
It features arranging database objects in correct order (preliminary
auxiliary objects, tables, constraints, auxiliary database objects) and
configurable logging.
*/
class DDLUnit {

  protected var _schemata: Seq[Schema] = Nil
  def schemata = _schemata
  protected var _tables: Seq[Table[_, _]] = Nil
  def tables = _tables
  protected var _views: Seq[View[_, _]] = Nil
  def views = _views
  protected var _constraints: Seq[Constraint] = Nil
  def constraints = _constraints
  protected var _preAux: Seq[SchemaObject] = Nil
  def preAux = _preAux
  protected var _postAux: Seq[SchemaObject] = Nil
  def postAux = _postAux

  protected var _msgs: Seq[Msg] = Nil
  def messages = _msgs
  def msgsArray: Array[Msg] = messages.toArray

  def this(objects: SchemaObject*) = {
    this()
    add(objects: _*)
  }

  def resetMsgs(): this.type = {
    _msgs = Nil
    this
  }

  def clear() = {
    _schemata = Nil
    _tables = Nil
    _views = Nil
    _constraints = Nil
    _preAux = Nil
    _postAux = Nil
    resetMsgs()
  }

  def add(objects: SchemaObject*): this.type = {
    objects.foreach(addObject(_))
    this
  }

  def addObject(obj: SchemaObject): this.type = {
    def processRelation(r: Relation[_, _]) {
      addObject(r.schema)
      r.preAux.foreach(o =>
        if (!_preAux.contains(o)) _preAux ++= List(o))
      r.postAux.foreach(o => addObject(o))
    }
    obj match {
      case t: Table[_, _] => if (!_tables.contains(t)) {
        _tables ++= List(t)
        t.constraints.foreach(c => addObject(c))
        t.indexes.foreach(i => addObject(i))
        processRelation(t)
      }
      case v: View[_, _] => if (!_views.contains(v)) {
        _views ++= List(v)
        processRelation(v)
      }
      case c: Constraint => if (!_constraints.contains(c))
        _constraints ++= List(c)
      case s: Schema => if (!_schemata.contains(s))
        _schemata ++= List(s)
      case o => if (!_postAux.contains(o))
        _postAux ++= List(o)
    }
    this
  }

  protected def dropObjects(objects: Seq[SchemaObject]) {
    if (ormConf.readOnly)
      throw new CircumflexException(
        "Read-only configuration does not allow DDL statements.")
    objects.reverse.foreach { o =>
      tx.execute(o.sqlDrop, { st =>
        st.executeUpdate()
        _msgs ++= List(new Msg(
          "orm.ddl.info",
          "status" -> ("DROP " + o.objectName + ": OK"),
          "sql" -> o.sqlDrop))
      }, { e =>
        _msgs ++= List(new Msg(
          "orm.ddl.info",
          "status" -> ("DROP " + o.objectName + ": " + e.getMessage),
          "sql" -> o.sqlDrop,
          "error" -> e.getMessage))
      })
    }
  }

  protected def createObjects(objects: Seq[SchemaObject]) {
    if (ormConf.readOnly)
      throw new CircumflexException(
        "Read-only configuration does not allow DDL statements.")
    objects.foreach { o =>
      tx.execute(o.sqlCreate, { st =>
        st.executeUpdate()
        _msgs ++= List(new Msg(
          "orm.ddl.info",
          "status" -> ("CREATE " + o.objectName + ": OK"),
          "sql" -> o.sqlCreate))
      }, { e =>
        _msgs ++= List(new Msg(
          "orm.ddl.error",
          "status" -> ("CREATE " + o.objectName + ": " + e.getMessage),
          "sql" -> o.sqlCreate,
          "error" -> e.getMessage))
      })
    }
  }

  def DROP(): this.type = {
    resetMsgs()
    _drop()
    this
  }

  def _drop() {
    tx.execute({ conn =>
    // We will commit every successfull statement.
      val autoCommit = conn.getAutoCommit
      conn.setAutoCommit(true)
      // Execute a script.
      dropObjects(postAux)
      dropObjects(views)
      if (ormConf.dialect.supportsDropConstraints)
        dropObjects(constraints)
      dropObjects(tables)
      dropObjects(preAux)
      if (ormConf.dialect.supportsSchema)
        dropObjects(schemata)
      // Restore auto-commit.
      conn.setAutoCommit(autoCommit)
    }, { throw _ })
  }

  def CREATE(): this.type = {
    resetMsgs()
    _create()
    this
  }

  def _create() {
    tx.execute({ conn =>
    // We will commit every successfull statement.
      val autoCommit = conn.getAutoCommit
      conn.setAutoCommit(true)
      // Execute a script.
      if (ormConf.dialect.supportsSchema)
        createObjects(schemata)
      createObjects(preAux)
      createObjects(tables)
      createObjects(constraints)
      createObjects(views)
      createObjects(postAux)
      // Restore auto-commit.
      conn.setAutoCommit(autoCommit)
    }, { throw _ })
  }

  def DROP_CREATE(): this.type = {
    resetMsgs()
    _drop()
    _create()
    this
  }

  def close() {
    tx.close()
    ormConf.connectionProvider.close()
  }

  def objectsCount: Int = schemata.size +
    tables.size +
    constraints.size +
    views.size +
    preAux.size +
    postAux.size

  override def toString: String = {
    var result = "Circumflex DDL Unit: "
    if (messages.size == 0) {
      result += objectsCount + " objects in queue."
    } else {
      val infoCount = messages.filter(_.key == "orm.ddl.info").size
      val errorsCount = messages.filter(_.key == "orm.ddl.error").size
      result += infoCount + " successful statements, " + errorsCount + " errors."
    }
    result
  }
}

/*!# Building Schema from Sources

The `DDLUnit` singleton can inspect your compiled classes to find the relations and build
schema from them. The usage is pretty simple:

    DDLUnit.fromClasspath().CREATE()

You can also specify package prefix for searching (using either slashes or dots as delimiters):

    DDLUnit.fromClasspath("com.myapp.model").CREATE()

By default, the compiled classes are being searched in `target/classes` and `target/test-classes`
directories relative to your project's root. You can override this setting using `cx.build.outputDirs`
configuration parameter (paths are split from String using `File.pathSeparator`, i.e. colon ":" in UNIX
and ";" in Windows).
*/
object DDLUnit {

  def outputDirs: Iterable[File] = cx.get("cx.build.outputDirs") match {
    case Some(i: Iterable[File]) => i
    case Some(s: String) => s.split(File.pathSeparator).map(s => new File(s))
    case _ => List(new File("target/classes"), new File("target/test-classes"))
  }

  def fromClasspath(pkgPrefix: String = ""): DDLUnit = {
    val loader = this.getClass.getClassLoader
    val ddl = new DDLUnit()
    for (dir <- outputDirs) {
      try {
        // Resolve directories and paths
        val pkgPath = pkgPrefix.replaceAll("\\.", "/")
        val outUrl = dir.toURI.toURL
        val pkgUrl = loader.getResource(pkgPath)
        val classDir = new File(outUrl.getFile, pkgPath)
        ORM_LOG.debug("Looking for schema objects in " + classDir.getAbsolutePath)
        // Make sure that requisite paths exist
        if (pkgUrl == null)
          throw new IllegalStateException("Could not resolve package '" + pkgPath + "'")
        if (!classDir.isDirectory)
          throw new IllegalStateException("Class directory " + classDir.getAbsolutePath + " does not exist.")
        // Iterate class files
        val files = FileUtils.listFiles(classDir, Array("class"), true).asInstanceOf[java.util.Collection[File]]
        for (file <- collectionAsScalaIterable(files)) {
          val relPath = file.getCanonicalPath.substring(dir.getCanonicalPath.length + 1)
          val className = relPath.substring(0, relPath.length - ".class".length).replaceAll(File.separator, ".")
          // Ensure that anonymous objects are not processed separately.
          if (className.matches("[^\\$]+(?:\\$$)?"))
            try {
              val c = loader.loadClass(className)
              var so: SchemaObject = null
              // try to treat it as a singleton
              try {
                val module = c.getField("MODULE$")
                if (isSchemaObjectType(module.getType))
                  so = module.get(null).asInstanceOf[SchemaObject]
              } catch {
                case e: NoSuchFieldException =>
                  // Try to instantiate it as a POJO.
                  if (isSchemaObjectType(c))
                    so = c.newInstance.asInstanceOf[SchemaObject]
              }
              if (so != null) {
                ddl.addObject(so)
                ORM_LOG.debug("Found schema object: " + c.getName)
              }
            } catch {
              case e: Exception =>
              // Omit non-schema classes silently
            }
        }
      } catch {
        case e: Exception => ORM_LOG.warn(e.getMessage)
      }
    }
    ORM_LOG.debug("Lookup complete, " + ddl.objectsCount + " objects found.")
    ddl
  }

  protected def isSchemaObjectType(c: Class[_]): Boolean =
    classOf[SchemaObject].isAssignableFrom(c) &&
      !Modifier.isAbstract(c.getModifiers) &&
      !Modifier.isInterface(c.getModifiers)

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy