.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)
}