|
Lift Framework example source code file (LoggingStatementWrappers.scala)
The Lift Framework LoggingStatementWrappers.scala source code/* * Copyright 2006-2011 WorldWide Conferencing, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.liftweb package db import java.lang.reflect.{InvocationHandler,Method,Proxy} import java.sql.{Array => SqlArray, _} import net.liftweb.util._ import net.liftweb.common.{Box,Loggable} trait DBLogEntry { def statement : String def duration : Long } object DBLogEntry { def unapply(obj : Any) = obj match { case entry : DBLogEntry => Some(entry.statement,entry.duration) case _ => None } } case class DBStatementEntry(statement : String, duration : Long) extends DBLogEntry case class DBMetaEntry(statement : String, duration : Long) extends DBLogEntry /** * This trait is applied to JDBC statements and similar constructs that can log operations. * * To enable logging of DB operations, use DB.addLogFunc */ trait DBLog { protected var executedStatements = List[DBLogEntry]() /* Some convenience methods to simplify the statements. We defined methods that can either take a raw description, or a function that can use the result of the operation to construct a description. */ protected def logStatement[T](description : String)(f : => T) : T = logStatement({ignore : T => description})(f) protected def logStatement[T](description : T => String)(f : => T) : T = Helpers.calcTime(f) match { case (duration, result) => executedStatements ::= DBStatementEntry(description(result), duration); result } protected def logMeta[T](description : String)(f : => T) : T = logMeta({ignore : T => description})(f) protected def logMeta[T](description : T => String)(f : => T) : T = Helpers.calcTime(f) match { case (duration, result) => executedStatements ::= DBMetaEntry(description(result), duration); result } /** Return a list of all of the DBStatementEntry instances in the log buffer */ def statementEntries : List[DBStatementEntry] = executedStatements.filter(_.isInstanceOf[DBStatementEntry]).reverse.asInstanceOf[List[DBStatementEntry]] /** Return a list of all of the DBMetaEntry instances in the log buffer */ def metaEntries : List[DBMetaEntry] = executedStatements.filter(_.isInstanceOf[DBMetaEntry]).reverse.asInstanceOf[List[DBMetaEntry]] /** Return all log buffer entries */ def allEntries : List[DBLogEntry] = executedStatements.reverse } object DBLog { def createStatement (conn : Connection) = { val stmt = conn.createStatement Proxy.newProxyInstance(this.getClass.getClassLoader, Array(classOf[java.sql.Statement], classOf[DBLog]), new LoggedStatementHandler(stmt)).asInstanceOf[Statement] } def prepareStatement (conn : Connection, query : String) = proxyPreparedStatement(conn.prepareStatement(query), query) def prepareStatement (conn : Connection, query : String, autoKeys : Int) = proxyPreparedStatement(conn.prepareStatement(query, autoKeys), query) def prepareStatement (conn : Connection, query : String, autoKeys : Array[Int]) = proxyPreparedStatement(conn.prepareStatement(query, autoKeys), query) def prepareStatement (conn : Connection, query : String, autoKeys : Array[String]) = proxyPreparedStatement(conn.prepareStatement(query, autoKeys), query) private def proxyPreparedStatement(stmt : => PreparedStatement, query : String) = { try { Proxy.newProxyInstance(this.getClass.getClassLoader, Array(classOf[java.sql.PreparedStatement], classOf[DBLog]), new LoggedPreparedStatementHandler(query, stmt)).asInstanceOf[PreparedStatement] } catch { case sqle : SQLException => throw new SQLException("Error preparing statement: \"%s\"".format(query), sqle) } } /** * This class corresponds to a logged version of java.sql.Statement. All operations * are supported via dynamic dispatch. This is done so that we can support both * JDBC3 and JDBC4 without having two code trees. * * To enable logging of DB operations, use DB.addLogFunc */ sealed private[DBLog] class LoggedStatementHandler(underlying : Statement) extends InvocationHandler with DBLog with Loggable { def underlyingClassname = "java.sql.Statement" lazy val representative : Class[_] = Class.forName(underlyingClassname) def invoke (proxy : Object, method : Method, args : Array[Object]) : Object = method.getName match { // Handle DBLog methods first. We have to do this since the end user expects a DBLog interface // via the proxy. case "statementEntries" => this.statementEntries case "metaEntries" => this.metaEntries case "allEntries" => this.allEntries // The rest are from Statement case "addBatch" => { logStatement("Batched: \"%s\"".format(args(0))) { chain(method, args) } } case "cancel" => { logMeta("Cancelled Statement") { chain(method, Array()) } } case "clearBatch" => { logMeta("Cleared Batch") { chain(method, Array()) } } case "clearWarnings" => { logMeta("Cleared Warnings") { chain(method, Array()) } } case "close" => { logMeta("Closed Statement") { chain(method, Array()) } } case "execute" if args.length == 1 => { logStatement({ret : Object => "\"%s\" : result = %s".format(args(0), ret)}) { chain(method, args) } } case "execute" if args(1).getClass == classOf[Int] => { logStatement({ret : Object => "Exec \"%s\", Auto-gen keys = %s : result = %s".format(args(0), StatementConstantDescriptions.genKeyDescriptions(args(1).asInstanceOf[Int]), ret)}) { chain(method, args) } } case "execute" => { logStatement({ret : Object => "Exec \"%s\", Auto-gen keys for columns %s".format(args(0), args(1).asInstanceOf[Array[_]].mkString(", "), ret)}) { chain(method, args) } } case "executeBatch" => { logStatement({result : Object => "Exec batch, counts = " + result.asInstanceOf[Array[Int]].mkString("(", ", ", ")")}) { chain(method, Array()) } } case "executeQuery" => { logStatement({rs : Object => "Exec query \"%s\" : rs = %s".format(args(0),rs)}) { chain(method, args) } } case "executeUpdate" if args.length == 1 => { logStatement({count : Object => "Exec update \"%s\" : count = %d".format(args(0),count)}) { chain(method, args) } } case "executeUpdate" if args(1).getClass == classOf[Int] => { logStatement({count : Object => "Exec update \"%s\", Auto-gen keys = %s".format(args(0), StatementConstantDescriptions.genKeyDescriptions(args(1).asInstanceOf[Int]), count)}) { chain(method, args) } } case "executeUpdate" => { logStatement({count : Object => "Exec update \"%s\", Auto-gen keys for columns %s".format(args(0), args(1).asInstanceOf[Array[_]].mkString(", "), count)}) { chain(method, args) } } case "getConnection" => { logMeta("Get underlying Connection") { chain(method, Array()) } } case "getFetchDirection" => { logMeta({ret : Object => "Get fetch direction : " + StatementConstantDescriptions.fetchDirDescriptions(ret.asInstanceOf[Int])}) { chain(method, Array()) } } case "getFetchSize" => { logMeta({size : Object => "Get fetch size : " + size}) { chain(method, Array()) } } case "getGeneratedKeys" => { logMeta({rs : Object => "Get generated keys : rs = " + rs}) { chain(method, Array()) } } case "getMaxFieldSize" => { logMeta({size : Object => "Get max field size : " + size}) { chain(method, Array()) } } case "getMaxRows" => { logMeta({maxRows : Object => "Get max rows : " + maxRows}) { chain(method, Array()) } } case "getMoreResults" if args.length == 0 => { logMeta({hasMore : Object => "Get more results : " + hasMore}) { chain(method, Array()) } } case "getMoreResults" => { logMeta({ret : Object => "Get more results (%s) : %s".format(StatementConstantDescriptions.getMoreResultsDescriptions(args(0).asInstanceOf[Int]), ret)}) { chain(method, args) } } case "getQueryTimeout" => { logMeta({timeout : Object => "Get query timeout : %d seconds ".format(timeout)}) { chain(method, Array()) } } case "getResultSet" => { logMeta({rs : Object => "Get result set : " + rs}) { chain(method, Array()) } } case "getResultSetConcurrency" => { logMeta({ret : Object => "Get result set concurrency : " + StatementConstantDescriptions.resultSetConcurrencyDescs(ret.asInstanceOf[Int])}) { chain(method, Array()) } } case "getResultSetHoldability" => { logMeta({ret : Object => "Get ResultSet holdability : " + StatementConstantDescriptions.resultSetHoldabilityDescs(ret.asInstanceOf[Int])}) { chain(method, Array()) } } case "getResultSetType" => { logMeta({ret : Object => "Get ResultSet type : " + StatementConstantDescriptions.resultSetTypeDescs(ret.asInstanceOf[Int])}) { chain(method, Array()) } } case "getUpdateCount" => { logMeta({count : Object => "Get update count : " + count}) { chain(method, Array()) } } case "getWarnings" => { logMeta({ret : Object => "Get SQL Warnings: " + Box.!!(ret).map(_.toString).openOr("None")}) { chain(method, Array()) } } case "isClosed" => { logMeta({ret : Object => "Check isClosed : " + ret}) { chain(method, Array()) } } case "isPoolable" => { logMeta({ret : Object => "Check isPoolable : " + ret}) { chain(method, Array()) } } case "setCursorName" => { logMeta("Set cursor name = %s" + args(0)) { chain(method, args) } } case "setEscapeProcessing" => { logMeta("Set escape processing = " + args(0)) { chain(method, args) } } case "setFetchDirection" => { logMeta("Set fetch direction = " + StatementConstantDescriptions.fetchDirDescriptions(args(0).asInstanceOf[Int])) { chain(method, args) } } case "setFetchSize" => { logMeta("Set fetch size = " + args(0)) { chain(method, args) } } case "setMaxFieldSize" => { logMeta("Set max field size = " + args(0)) { chain(method, args) } } case "setMaxRows" => { logMeta("Set max rows = " + args(0)) { chain(method, args) } } case "setPoolable" => { logMeta("Set poolable = " + args(0)) { chain(method, args) } } case "setQueryTimeout" => { logMeta("Set query timeout = " + args(0)) { chain(method, args) } } case "toString" => { // We'll call into our own representation here this.toString } // These are from wrapper and are required case "isWrapperFor" => args(0).getClass match { case `representative` => Boolean.box(true) case _ => chain(method, args) } case "unwrap" => args(0).getClass match { case `representative` => underlying case _ => chain(method, args) } case methodName => throw new NoSuchMethodException(methodName + " is not implemented here") } protected def chain(method : Method, args : Array[Object]) : Object = try { val m = representative.getMethod(method.getName, method.getParameterTypes : _*) m.invoke(underlying, args : _*) } catch { case ite: java.lang.reflect.InvocationTargetException => throw ite.getCause case nsme : NoSuchMethodException => logger.warn("Could not locate method %s for %s : %s".format(method.getName, underlyingClassname, nsme.getMessage)) throw nsme } /* This toString only gets invoked if we target this instance as a * LoggedStatementHandler directly, or via the proxied "toString" above. */ override def toString = "Logged Statements =\n" + executedStatements.reverse.map(" " + _).mkString("\n") } /** * This class corresponds to a logged version of java.sql.PreparedStatement. All operations * should be supported. * * To enable logging of DB operations, use DB.addLogFunc */ sealed private[DBLog] class LoggedPreparedStatementHandler (stmt : String, underlying : PreparedStatement) extends LoggedStatementHandler(underlying) { override def underlyingClassname = "java.sql.PreparedStatement" private var paramMap = Map.empty[Int,Any] // utility method to fill in params private def paramified : String = { val sb = new StringBuilder(500) def substitute (in : String, index : Int): Unit = in.indexOf('?') match { case -1 => sb.append(in) case j => sb.append(in.substring(0,j)) sb.append(paramMap(index)) substitute(in.substring(j + 1), index + 1) } substitute(stmt, 1) sb.toString } override def invoke (proxy : Object, method : Method, args : Array[Object]) : Object = { method.getName match { // All of the simple cases can be handled in one spot case "setArray" | "setBigDecimal" | "setBoolean" | "setByte" | "setBytes" | "setDouble" | "setFloat" | "setInt" | "setLong" | "setNString" | "setRef" | "setRowId" | "setShort" | "setSQLXML" => { paramMap += args(0).asInstanceOf[Int] -> args(1) chain(method, args) } // Everything else gets special treatment case "addBatch" => { logStatement("Batching \"%s\"".format(paramified)) { chain(method, Array()) } } case "clearParameters" => { paramMap = Map.empty[Int,Any] logMeta("Clear parameters") { chain(method, Array()) } } case "execute" => { logStatement({ret : Object => "Exec \"%s\" : %s".format(paramified, ret)}) { chain(method, Array()) } } case "executeQuery" => { logStatement({rs : Object => "Exec query \"%s\" : %s".format(paramified, rs)}) { chain(method, Array()) } } case "executeUpdate" => { logStatement({ret : Object => "Exec update \"%s\" : updated %d rows".format(paramified, ret)}) { chain(method, Array()) } } case "getMetaData" => { logMeta({ret : Object => "Get metadata : " + ret}) { chain(method, Array()) } } case "getParameterMetaData" => { logMeta({ret : Object => "Get param metadata : " + ret}) { chain(method, Array()) } } case "setAsciiStream" if args.length == 2 => { paramMap += args(0).asInstanceOf[Int] -> "(Ascii Stream: %s)".format(args(1)) chain(method, args) } case "setAsciiStream" => { paramMap += args(0).asInstanceOf[Int] -> "(Ascii Stream: %s (%d bytes))".format(args(1), args(2)) chain(method, args) } case "setBinaryStream" if args.length == 2 => { paramMap += args(0).asInstanceOf[Int] -> "(Binary Stream: %s)".format(args(1)) chain(method, args) } case "setBinaryStream" => { paramMap += args(0).asInstanceOf[Int] -> "(Binary Stream: %s (%d bytes))".format(args(1), args(2)) chain(method, args) } case "setBlob" if args.length == 2 => { paramMap += args(0).asInstanceOf[Int] -> "(Blob : %s)".format(args(1)) chain(method, args) } case "setBlob" => { paramMap += args(0).asInstanceOf[Int] -> "(Blob : %s (%d bytes))".format(args(1), args(2)) chain(method, args) } case "setCharacterStream" if args.length == 2 => { paramMap += args(0).asInstanceOf[Int] -> "(Char stream : %s)".format(args(1)) chain(method, args) } case "setCharacterStream" => { paramMap += args(0).asInstanceOf[Int] -> "(Char stream : %s (%d bytes))".format(args(1), args(2)) chain(method, args) } case "setClob" if args.length == 2 => { paramMap += args(0).asInstanceOf[Int] -> "(Clob : %s)".format(args(1)) chain(method, args) } case "setClob" => { paramMap += args(0).asInstanceOf[Int] -> "(Clob : %s (%d bytes))".format(args(1), args(2)) chain(method, args) } case "setDate" if args.length == 2 => { paramMap += args(0).asInstanceOf[Int] -> args(1) chain(method, args) } case "setDate" => { paramMap += args(0).asInstanceOf[Int] -> (args(1) + ":" + args(2)) chain(method, args) } case "setNCharacterStream" if args.length == 2 => { paramMap += args(0).asInstanceOf[Int] -> "(NChar Stream : %s)".format(args(1)) chain(method, args) } case "setNCharacterStream" => { paramMap += args(0).asInstanceOf[Int] -> "(NChar Stream : %s (%d bytes))".format(args(1), args(2)) chain(method, args) } case "setNClob" if args.length == 2 => { paramMap += args(0).asInstanceOf[Int] -> "(NClob : %s)".format(args(1)) chain(method, args) } case "setNClob" => { paramMap += args(0).asInstanceOf[Int] -> "(NClob : %s (%d bytes))".format(args(1), args(2)) chain(method, args) } case "setNull" => { paramMap += args(0).asInstanceOf[Int] -> "NULL" chain(method, args) } case "setObject" if (args.length >= 2 && args.length < 4) => { paramMap += args(0).asInstanceOf[Int] -> args(1) chain(method, args) } case "setObject" if args.length == 4 => { paramMap += args(0).asInstanceOf[Int] -> "%s (scale %d)".format(args(1), args(3)) chain(method, args) } case "setString" => { paramMap += args(0).asInstanceOf[Int] -> "\"%s\"".format(args(1)) chain(method, args) } case "setTime" if args.length == 2 => { paramMap += args(0).asInstanceOf[Int] -> args(1) chain(method, args) } case "setTime" => { paramMap += args(0).asInstanceOf[Int] -> (args(1) + ":" + args(2)) chain(method, args) } case "setTimestamp" if args.length == 2 => { paramMap += args(0).asInstanceOf[Int] -> args(1) chain(method, args) } case "setTimestamp" => { paramMap += args(0).asInstanceOf[Int] -> (args(1) + ":" + args(2)) chain(method, args) } case "setUnicodeStream" => { paramMap += args(0).asInstanceOf[Int] -> "(Unicode Stream : %s (%d bytes))".format(args(1), args(2)) chain(method, args) } case "setURL" => { paramMap += args(0).asInstanceOf[Int] -> "\"%s\"".format(args(1)) chain(method, args) } // Chain up to LoggedStatement if we don't handle it here case _ => super.invoke(proxy, method, args) } } } } /** * This object defines some conversions from Int JDBC constants to * descriptive strings */ object StatementConstantDescriptions { def genKeyDescriptions (in : Int) = in match { case Statement.NO_GENERATED_KEYS => "NO_GENERATED_KEYS" case Statement.RETURN_GENERATED_KEYS => "RETURN_GENERATED_KEYS" case x => "Invalid Generated Keys Constant: " + x } def fetchDirDescriptions (in : Int) = in match { case ResultSet.FETCH_FORWARD => "FETCH_FORWARD" case ResultSet.FETCH_REVERSE => "FETCH_REVERSE" case ResultSet.FETCH_UNKNOWN => "FETCH_UNKNOWN" case x => "Invalid Fetch Direction Constant: " + x } def getMoreResultsDescriptions (in : Int) = in match { case Statement.CLOSE_CURRENT_RESULT => "CLOSE_CURRENT_RESULT" case Statement.KEEP_CURRENT_RESULT => "KEEP_CURRENT_RESULT" case Statement.CLOSE_ALL_RESULTS => "CLOSE_ALL_RESULTS" case x => "Invalid getMoreResults constant: " + x } def resultSetConcurrencyDescs (in : Int) = in match { case ResultSet.CONCUR_READ_ONLY => "CONCUR_READ_ONLY" case ResultSet.CONCUR_UPDATABLE => "CONCUR_UPDATABLE" case x => "Invalid ResultSet concurrency constant: " + x } def resultSetHoldabilityDescs (in : Int) = in match { case ResultSet.HOLD_CURSORS_OVER_COMMIT => "HOLD_CURSORS_OVER_COMMIT" case ResultSet.CLOSE_CURSORS_AT_COMMIT => "CLOSE_CURSORS_AT_COMMIT" case x => "Invalid ResultSet holdability constant: " + x } def resultSetTypeDescs (in : Int) = in match { case ResultSet.TYPE_FORWARD_ONLY => "TYPE_FORWARD_ONLY" case ResultSet.TYPE_SCROLL_INSENSITIVE => "TYPE_SCROLL_INSENSITIVE" case ResultSet.TYPE_SCROLL_SENSITIVE => "TYPE_SCROLL_SENSITIVE" case x => "Invalid ResultSet type constant: " + x } } Other Lift Framework examples (source code examples)Here is a short list of links related to this Lift Framework LoggingStatementWrappers.scala source code file: |
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.