# HG changeset patch # User Tomas Zeman # Date 1328863984 -3600 # Node ID 993582ca8d2e90bb22ec1ab380710f4a36ae683c # Parent bb4478e1cff7cb341b146559844760ef57f98239 Import of tz-lift-misc@14a648a3676f diff -r bb4478e1cff7 -r 993582ca8d2e src/main/scala/net/tz/lift/boot/ProtoBoot.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/net/tz/lift/boot/ProtoBoot.scala Fri Feb 10 09:53:04 2012 +0100 @@ -0,0 +1,83 @@ +/* + * Copyright 2011 Tomas Zeman + * + * 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.tz.lift.boot + +import net.liftweb.common._ +import net.liftweb.http._ +import net.liftweb.mapper._ +import net.liftweb.sitemap._ +import net.liftweb.sitemap.Loc._ +import net.liftweb.util._ +import net.liftweb.widgets.menu.MenuWidget +import net.tz.lift.snippet.ActionLinks +import net.tz.lift.util.YmdDateTimeConverter + +/** + * Prototype boot class to be either extended or copied. + */ +class ProtoBoot extends Logger { + + def boot = { + /* DB stuff */ + /* + S.addAround(DB.buildLoanWrapper()) + */ + + if (Props.mode == Props.RunModes.Development) + DB.addLogFunc { (dbLog, l) => dbLog.statementEntries.foreach { e => + debug("Query: " + e.statement) + }} + + /* Date format */ + LiftRules.dateTimeConverter.default.set { () => YmdDateTimeConverter } + + /* Handle end slash and drop it (except for home page) */ + LiftRules.statelessRewrite.append { + case RewriteRequest(ParsePath(xs,_,_,true),_,_) if (xs.size > 1) && + (xs.lastOption == Some("index")) => + RewriteResponse(xs dropRight 1) + } + + /* Snippet dispatch */ + LiftRules.snippetDispatch.append { + case "Menubar" => new AnyRef with DispatchSnippet { + def dispatch: DispatchIt = { + case _ => { xhtml => MenuWidget() } + } + } + case "action-links" => ActionLinks + } + + /* Sitemap */ + SiteMap.enforceUniqueLinks = false + + /* + LiftRules.setSiteMap(SiteMap( + Menu.i("Home") / "index" >> Hidden + )) + */ + + /* Menu widget */ + MenuWidget.init() + + /* Http conf */ + LiftRules.logServiceRequestTiming = false + LiftRules.early.append(_.setCharacterEncoding("UTF-8")) + } + +} + +// vim: set ts=2 sw=2 et: diff -r bb4478e1cff7 -r 993582ca8d2e src/main/scala/net/tz/lift/snippet/Panel.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/net/tz/lift/snippet/Panel.scala Fri Feb 10 09:53:04 2012 +0100 @@ -0,0 +1,57 @@ +/* + * Copyright 2011 Tomas Zeman + * + * 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.tz.lift.snippet + +import net.liftweb.mapper.MappedField +import scala.xml.{NodeSeq, Text} + +object AttrRow { + def apply(name: => NodeSeq, value: => NodeSeq) = new AttrRow(name, value, + "attr-name", "attr-value") + def apply(f: MappedField[_, _]) = new AttrRow(Text(f.displayName), f.asHtml, + "attr-name", "attr-value") + def formRow(name: => NodeSeq, input: => NodeSeq) = new AttrRow(name, input, + "form-name", "form-value") + def submitRow(button: => NodeSeq) = new SpanRow(button) +} + +class AttrRow(name: => NodeSeq, value: => NodeSeq, nameCss: String, + valueCss: String) extends Function0[NodeSeq] { + def tdN = {name} + def tdV = {value} + def apply(): NodeSeq = {tdN :: tdV :: Nil} +} + +class SpanRow(value: => NodeSeq) extends AttrRow(NodeSeq.Empty, value, "", "") +{ + override def apply(): NodeSeq = {value} +} + +object Panel { + def apply(attrs: Iterable[AttrRow]) = new Panel(attrs) + def fromFields(fields: Iterable[MappedField[_,_]]) = + new Panel(fields.map(AttrRow(_))) +} + +class Panel(attrs: => Iterable[AttrRow]) extends Function1[NodeSeq, NodeSeq] +{ + def apply(in: NodeSeq): NodeSeq = {attrs.map(_())}
+ def &(other: Function1[NodeSeq, NodeSeq]) = new Function1[NodeSeq, NodeSeq] { + def apply(in: NodeSeq): NodeSeq = List(Panel.this, other) flatMap (_(in)) + } +} + +// vim: set ts=2 sw=2 et: diff -r bb4478e1cff7 -r 993582ca8d2e src/main/scala/net/tz/lift/snippet/SnippetHelpers.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/net/tz/lift/snippet/SnippetHelpers.scala Fri Feb 10 09:53:04 2012 +0100 @@ -0,0 +1,74 @@ +/* + * Copyright 2011 Tomas Zeman + * + * 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.tz.lift.snippet + +import net.liftweb.http._ +import scala.xml.{NodeSeq, Text} + +trait SnippetHelpers { + def mkPath(prefix: String, l: String*) = + (prefix :: l.toList) mkString ("/", "/", "") +} + +class A(href: => String) extends Function1[NodeSeq, NodeSeq] { + def apply(in: NodeSeq): NodeSeq = {in} +} + +object A { + def apply(href: String, in: NodeSeq): NodeSeq = (new A(href))(in) + def apply(href: String): A = new A(href) + def apply(href: String, cnt: String): NodeSeq = (new A(href))(Text(cnt)) +} + +object ActionLinks extends DispatchSnippet { + def dispatch: DispatchIt = { + case "are" => { xhtml => if (links.is.isEmpty) NodeSeq.Empty else xhtml } + case _ => render _ + } + + import scala.collection.mutable.{BufferLike, ListBuffer} + + private object links extends RequestVar[ListBuffer[Either[Function0[NodeSeq], Function1[NodeSeq, NodeSeq]]]](new ListBuffer()) + + def render(in: NodeSeq): NodeSeq = links.is.flatMap { _.fold( _(), _(in) ) ++ + Text(" ") } + + def append(l: NodeSeq) = links.is.append(Left( { () => l } )) + def append(l: () => NodeSeq) = links.is.append(Left(l)) + def append(l: NodeSeq => NodeSeq) = links.is.append(Right(l)) +} + +object SimpleForm { + def apply(l: Iterable[AttrRow], submitVal: String, onSubmit: () => Any): + (NodeSeq => NodeSeq) = { + val curS = S.currentSnippet + def doit() = { + onSubmit() + curS foreach { S.mapSnippet(_, loop) } + } + + def loop: (NodeSeq => NodeSeq) = { + Panel(l ++ List(AttrRow.submitRow(SHtml.submit(submitVal, doit)))) + } + + loop + } + + def apply(l: Iterable[AttrRow], submitVal: String): (NodeSeq => NodeSeq) = + apply(l, submitVal, () => ()) +} + +// vim: set ts=2 sw=2 et: diff -r bb4478e1cff7 -r 993582ca8d2e src/main/scala/net/tz/lift/snippet/Table.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/net/tz/lift/snippet/Table.scala Fri Feb 10 09:53:04 2012 +0100 @@ -0,0 +1,78 @@ +/* + * Copyright 2011 Tomas Zeman + * + * 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.tz.lift.snippet + +import net.liftweb.common._ +import net.liftweb.util.Helpers._ +import scala.xml.{Elem, NodeSeq, Text} + +object Column { + def apply[T](name: String, f: T => NodeSeq): Column[T] = + new Column[T](Text(name), f, { _ => Empty}) + def apply[T](name: String, f: T => NodeSeq, tdCss: String): Column[T] = + new Column[T](Text(name), f, { _ => Full(tdCss)}) +} + +class Column[T](name: => NodeSeq, f: T => NodeSeq, tdCss: T => Box[String]) + extends Function1[T, NodeSeq] { + + def th: Elem = {name} + def td(in: T): Elem = { + val r = {apply(in)} + tdCss(in) map { css => r % ("class" -> css) } openOr r + } + def apply(in: T): NodeSeq = f(in) +} + +object Table { + def apply[T](cols: Iterable[Column[T]], rows: Iterable[T]) = new Table(Empty, + cols, rows, Empty) + def apply[T](css: String, cols: Iterable[Column[T]], rows: Iterable[T], + foot: NodeSeq) = new Table(Full(css), cols, rows, Full(foot)) +} + +class Table[T](css: Box[String], cols: Iterable[Column[T]], + rows: => Iterable[T], foot: Box[NodeSeq]) extends + Function1[NodeSeq, NodeSeq] { + + def oddEven(i: Int) = (i % 2) match { + case 1 => "even" + case 0 => "odd" + } + + def thead: Elem = { cols.map(c => c.th) } + def tbody: Elem = { + rows.zipWithIndex.map( r => tr(r._1, r._2)) + } + def tr(in: T, idx: Int): Elem = { + cols.map(_.td(in)) + } + + def tfoot: Elem = { + foot map { f => {f} + } openOr NodeSeq.Empty + } + + def apply(ns: NodeSeq): NodeSeq = { + val t = { + List(thead, tfoot, tbody) + }
+ css map { cl => t % ("class" -> cl) } openOr t + } + +} + +// vim: set ts=2 sw=2 et: diff -r bb4478e1cff7 -r 993582ca8d2e src/main/scala/net/tz/lift/util/DB.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/net/tz/lift/util/DB.scala Fri Feb 10 09:53:04 2012 +0100 @@ -0,0 +1,166 @@ +/* + * Fix for ProtoDBVendor/StandardDBVendor connection leak. + * + * Original code is: + * + * 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. + * + * All fixes are under the same license. + */ +package net.tz.lift.util + +import java.sql.{Connection, DriverManager} +import net.liftweb.common._ +import net.liftweb.db.{ConnectionIdentifier, ConnectionManager} +import net.liftweb.util._ +import net.liftweb.util.Helpers._ + +/** + * The standard DB vendor. + * @param driverName the name of the database driver + * @param dbUrl the URL for the JDBC data connection + * @param dbUser the optional username + * @param dbPassword the optional db password + */ +class StandardDBVendor(driverName: String, + dbUrl: String, + dbUser: Box[String], + dbPassword: Box[String]) extends ProtoDBVendor { + protected def createOne: Box[Connection] = try { + Class.forName(driverName) + + val dm = (dbUser, dbPassword) match { + case (Full(user), Full(pwd)) => + DriverManager.getConnection(dbUrl, user, pwd) + + case _ => DriverManager.getConnection(dbUrl) + } + + Full(dm) + } catch { + case e: Exception => e.printStackTrace; Empty + } +} + +trait ProtoDBVendor extends ConnectionManager { + private val logger = Logger(classOf[ProtoDBVendor]) + private var pool: List[Connection] = Nil + private var poolSize = 0 + private var tempMaxSize = maxPoolSize + + /** + * Override and set to false if the maximum pool size can temporarilly be expanded to avoid pool starvation + */ + protected def allowTemporaryPoolExpansion = true + + /** + * Override this method if you want something other than + * 4 connections in the pool + */ + protected def maxPoolSize = 4 + + /** + * The absolute maximum that this pool can extend to + * The default is 20. Override this method to change. + */ + protected def doNotExpandBeyond = 20 + + /** + * The logic for whether we can expand the pool beyond the current size. By + * default, the logic tests allowTemporaryPoolExpansion && poolSize <= doNotExpandBeyond + */ + protected def canExpand_? : Boolean = allowTemporaryPoolExpansion && poolSize <= doNotExpandBeyond + + /** + * How is a connection created? + */ + protected def createOne: Box[Connection] + + /** + * Test the connection. By default, setAutoCommit(false), + * but you can do a real query on your RDBMS to see if the connection is alive + */ + protected def testConnection(conn: Connection) { + conn.setAutoCommit(false) + } + + def newConnection(name: ConnectionIdentifier): Box[Connection] = + synchronized { + pool match { + case Nil if poolSize < tempMaxSize => + val ret = createOne + ret foreach { c => + c.setAutoCommit(false) + poolSize = poolSize + 1 + logger.debug("Created new pool entry. name=%s, poolSize=%d".format(name, poolSize)) + } + ret + + case Nil => + val curSize = poolSize + logger.trace("No connection left in pool, waiting...") + wait(50L) + // if we've waited 50 ms and the pool is still empty, temporarily expand it + if (pool.isEmpty && poolSize == curSize && canExpand_?) { + tempMaxSize += 1 + logger.debug("Temporarily expanding pool. name=%s, tempMaxSize=%d".format(name, tempMaxSize)) + } + newConnection(name) + + case x :: xs => + logger.trace("Found connection in pool, name=%s".format(name)) + pool = xs + try { + this.testConnection(x) + Full(x) + } catch { + case e => try { + logger.debug("Test connection failed, removing connection from pool, name=%s".format(name)) + poolSize = poolSize - 1 + tryo(x.close) + newConnection(name) + } catch { + case e => newConnection(name) + } + } + } + } + + def releaseConnection(conn: Connection): Unit = synchronized { + if (tempMaxSize > maxPoolSize) { + tryo {conn.close()} + tempMaxSize -= 1 + poolSize -= 1 + } else { + pool = conn :: pool + } + logger.debug("Released connection. poolSize=%d".format(poolSize)) + notifyAll + } + + def closeAllConnections_!(): Unit = synchronized { + logger.info("Closing all connections, poolSize=%d".format(poolSize)) + if (poolSize == 0) () + else { + pool.foreach {c => tryo(c.close); poolSize -= 1} + pool = Nil + + if (poolSize > 0) wait(250) + + closeAllConnections_!() + } + } +} +// vim: set ts=2 sw=2 et: diff -r bb4478e1cff7 -r 993582ca8d2e src/main/scala/net/tz/lift/util/DateExtension.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/net/tz/lift/util/DateExtension.scala Fri Feb 10 09:53:04 2012 +0100 @@ -0,0 +1,44 @@ +/* + * Refactored from net.liftweb.util.TimeHelpers.scala, which is + * + * Copyright 2006-2011 WorldWide Conferencing, LLC and + * + * 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.tz.lift.util + +import java.util.{Calendar, Date} + +/** + * Fixed version of DateExtension (from + * net.liftweb.util.TimeSpan.DateExtension). + */ +class DateExtension(d: Date) { + /** @returns a Date object starting at 00:00 from date */ + def noTime = { + val calendar = Calendar.getInstance + calendar.setTime(d) + calendar.set(Calendar.HOUR_OF_DAY, 0) + calendar.set(Calendar.MINUTE, 0) + calendar.set(Calendar.SECOND, 0) + calendar.set(Calendar.MILLISECOND, 0) + calendar.getTime + } +} + +object DateExtension { + implicit def date2dateExtension(d: Date): DateExtension = + new DateExtension(d) +} + +// vim: set ts=2 sw=2 et: diff -r bb4478e1cff7 -r 993582ca8d2e src/main/scala/net/tz/lift/util/Helpers.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/net/tz/lift/util/Helpers.scala Fri Feb 10 09:53:04 2012 +0100 @@ -0,0 +1,79 @@ +/* + * Copyright 2011 Tomas Zeman + * + * 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.tz.lift.util + +import java.text.SimpleDateFormat +import java.util.Date +import net.liftweb.http.LiftRules +import net.liftweb.util.Helpers.tryo +import org.joda.time.{DateMidnight, DateTime, Period, ReadableInstant} +import org.joda.time.format.{DateTimeFormat, PeriodFormatterBuilder} +import scala.xml.{NodeSeq, Text} + +object AsIsoDateTime { + val df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss") + def unapply(in: String): Option[Date] = tryo { df.parse(in) } + def apply(d: Date): String = df.format(d) +} + +object AsDate { + import DateExtension._ + def unapply(in: String): Option[Date] = LiftRules.dateTimeConverter(). + parseDate(in).map(_.noTime) + def apply(d: Date) = LiftRules.dateTimeConverter().formatDate(d) +} + +object AsDateTime { + def unapply(in: String): Option[Date] = LiftRules.dateTimeConverter(). + parseDateTime(in) + def apply(d: Date) = LiftRules.dateTimeConverter().formatDateTime(d) +} + +object AsDateMidnight { + lazy val fmt = DateTimeFormat.forPattern("yyyy-MM-dd") + def unapply(in: String): Option[DateMidnight] = apply(in) + def apply(in: String): Option[DateMidnight] = tryo { + fmt.parseDateTime(in).toDateMidnight + } + def apply(d: ReadableInstant) = fmt.print(d) + + implicit def dt2d(d: DateTime): Date = d.toDate + implicit def dm2d(d: DateMidnight): Date = d.toDate + implicit def d2dm(d: Date): DateMidnight = new DateMidnight(d) + implicit def dm2dt(d: DateMidnight): DateTime = d.toDateTime +} + +object AsTimePeriod { + lazy val fmt = (new PeriodFormatterBuilder).printZeroAlways. + minimumPrintedDigits(2).appendHours. + appendSeparator(":").appendMinutes.toFormatter + lazy val fmtDt = DateTimeFormat.forPattern("HH:mm") + + /** Parses HH:mm time into period. */ + def apply(in: String): Option[Period] = tryo { fmt.parsePeriod(in) } + def apply(p: Period) = fmt.print(p) + def apply(dt: DateTime) = fmtDt.print(dt) +} + +object Bytes { + implicit def dbl2ns(d: Double): NodeSeq = + Text(String.format(fmt, d.asInstanceOf[AnyRef])) + def fmt = "%.2f" + def kb(v: Long): NodeSeq = v.toDouble / 1024 + def mb(v: Long): NodeSeq = v.toDouble / (1024*1024) +} + +// vim: set ts=2 sw=2 et: diff -r bb4478e1cff7 -r 993582ca8d2e src/main/scala/net/tz/lift/util/YmdDateTimeConverter.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/net/tz/lift/util/YmdDateTimeConverter.scala Fri Feb 10 09:53:04 2012 +0100 @@ -0,0 +1,40 @@ +/* + * Copyright 2011 Tomas Zeman + * + * 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.tz.lift.util + +import java.text.SimpleDateFormat +import java.util.Date +import net.liftweb.common._ +import net.liftweb.util.DateTimeConverter +import net.liftweb.util.TimeHelpers._ + +object YmdDateTimeConverter extends DateTimeConverter { + val df = new SimpleDateFormat("yyyy-MM-dd") + val dtf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") + + def formatDateTime(d: Date) = dtf.format(d) + def formatDate(d: Date) = df.format(d) + /** Uses Helpers.hourFormat which includes seconds but not time zone */ + def formatTime(d: Date) = hourFormat.format(d) + + def parseDateTime(s: String) = tryo { dtf.parse(s) } + def parseDate(s: String) = tryo { df.parse(s) } + /** Tries Helpers.hourFormat and Helpers.timeFormat */ + def parseTime(s: String) = + tryo{hourFormat.parse(s)} or tryo{timeFormatter.parse(s)} +} + +// vim: set ts=2 sw=2 et: