Skip to content

Instantly share code, notes, and snippets.

@voronaam
Last active December 30, 2015 05:09
Show Gist options
  • Save voronaam/7780946 to your computer and use it in GitHub Desktop.
Save voronaam/7780946 to your computer and use it in GitHub Desktop.
Vaadin to Squeryl Container implementation
import java.util.{ Collection => JCollection }
import java.lang.{ Long => JLong }
import java.sql.Timestamp
import scala.collection.JavaConversions._
import org.squeryl.Query
import com.vaadin.data._
import scala.reflect.ClassTag
import org.squeryl.dsl.ast.OrderByArg
import org.squeryl.dsl.ast.ExpressionNode
/**
* Vaadin Container implementation that allows to bind it to arbitrary Squeryl query
* @param R - query result type, the only limitation that it has to have an id of type Long.
* @param query - the query which result to display. It may be used as a sub query: select ... from {query} order by ...
* @param sortList - List of field names by which the data is sorted initially. Can be empty if select {id} from {query} would return ids in the same order as {query} itself.
* It is safer to have the query sorted and pass in the sort field.
*/
abstract class VaadinTableContainer[R](query: Query[R], var sortList: List[(String, Boolean)] = List.empty) extends Container with Container.Sortable {
// Override those two with a helper class to provide metadata
protected def wrap(dbItem: R): WrappedItem
protected def fakeItem: WrappedItem // To provide metadata about field types
private val pageLength = 100; // Has to be more or equal to the table cache size
// TODO: reload on refresh (we do not even have a refresh method at the time of writing)
private var sortedQuery = query
private var fetched = page(0)
var size: Int = inTransaction { from(query)(s => compute(count())).single.measures.toInt }
private var idList = fetchAllIds
private def fetchAllIds = inTransaction { from(query)(r => select(wrap(r).id) orderBy (sortList.map(buildOrderBy(r)))).toList }
// Load page of data into local cache (fetched)
private def page(start: Int) = inTransaction {
val items = queryToIterable(sortedQuery.page(start, pageLength)).map(wrap)
// We do not need to maintain order here, because Vaadin does that based on the full list of ID
items.map(i => (i.id -> i)).toMap
}
// Return list of fields
def getContainerPropertyIds(): JCollection[_] = fakeItem.properties.keys
def getItemIds(): JCollection[_] = idList
def containsId(id: Any): Boolean = idList.contains(id)
// Get an item, load another page of data in cache if necessary
def getItem(id: Any): Item = {
id match {
case id: Long if fetched.contains(id) => fetched(id)
case id: Long => {
// load another page of data if id is not on the current page
val item = idList.indexOf(id)
if (item < 0) throw new Exception(s"Incorrect item id $id")
val start = Math.max(0, item - pageLength / 2)
fetched = page(start)
fetched(id) // If it throws, let it throw...
}
case _ => throw new Exception("Incorrect id type")
}
}
def getType(name: Any): Class[_] = name match {
case name: String => fakeItem.properties(name).getType
case _ => throw new Exception(s"Unexpected name $name")
}
def getContainerProperty(itemId: Any, propertyId: Any): Property[_] = {
val item = getItem(itemId)
item.getItemProperty(propertyId)
}
// All fields are sortable
def getSortableContainerPropertyIds(): JCollection[_] = getContainerPropertyIds
// Implementation of Ordered interface
def firstItemId(): Object = JLong.valueOf(idList.head)
def isFirstId(id: Any): Boolean = id match {
case id: Long => id == idList.head
case _ => false
}
def isLastId(id: Any): Boolean = id match {
case id: Long => id == idList.last
case _ => false
}
def lastItemId(): Object = JLong.valueOf(idList.last)
def nextItemId(id: Any): Object = id match {
case id: Long => {
val index = idList.indexOf(id)
idList.slice(index + 1, index + 2).headOption.map(JLong.valueOf).getOrElse(null)
}
case _ => throw new Exception("Incorrect id type")
}
def prevItemId(id: Any): Object = id match {
case id: Long => {
val index = idList.indexOf(id)
idList.slice(index - 1, index).headOption.map(JLong.valueOf).getOrElse(null)
}
case _ => throw new Exception("Incorrect id type")
}
// Apply sort conditions
def sort(fields: Array[Object], ascending: Array[Boolean]) {
sortList = fields.map(_.asInstanceOf[String]).zip(ascending).toList
sortedQuery = from(query)(r => select(r) orderBy (sortList.map(buildOrderBy(r))))
reload()
}
// Build OrderBy AST clause for one field. We only need to touch it on the row, Squeryl will do the rest.
def buildOrderBy(row: R)(sorter: (String, Boolean)): ExpressionNode = {
val node = wrap(row).getItemProperty(sorter._1).getValue() match {
case v: String => stringToTE(v)
case v: JLong => longToTE(v)
case v: Timestamp => timestampToTE(v)
case _ => throw new Exception("Unexpected type of the field we are tried to sort by")
}
if(sorter._2) node else new OrderByArg(node) {desc}
}
// Reload the cache with the new data (only applies new sort order at the moment)
def reload() {
fetched = page(0)
idList = fetchAllIds
}
// That is autogenerated...
def addContainerProperty(x$1: Any, x$2: Class[_], x$3: Any): Boolean = ???
def addItem(): Object = ???
def addItem(x$1: Any): com.vaadin.data.Item = ???
def removeAllItems(): Boolean = ???
def removeContainerProperty(x$1: Any): Boolean = ???
def removeItem(x$1: Any): Boolean = ???
def addItemAfter(x$1: Any, x$2: Any): com.vaadin.data.Item = ???
def addItemAfter(x$1: Any): Object = ???
}
// Trait to provide metadata about the table's contents
trait WrappedItem extends Item {
// Implement this to provide metadata
def properties: Map[String, Property[_]]
def id: Long
def getItemPropertyIds(): JCollection[_] = properties.keys
def getItemProperty(name: Any): Property[_] = name match {
case name: String => properties(name)
case _ => throw new Exception(s"Incorrect property name: $name")
}
def addItemProperty(x$1: Any, x$2: Property[_]): Boolean = false
def removeItemProperty(x$1: Any): Boolean = false
}
// Has to be an abstract class and not trait to bind to a ClassManifect properly
abstract class VaadinProperty[T <: AnyRef: ClassTag] extends Property[T] {
def setValue(v: T) = throw new Exception("We have immutable objects only")
def isReadOnly = true
def setReadOnly(v: Boolean) { throw new Exception("We have immutable objects only") }
def getType = scala.reflect.classTag[T].runtimeClass.asInstanceOf[Class[_ <: T]] // Terrible, terrible way of getting Class[T]
}
/* ****************************
* SAMPLE USAGE
*/
class UserItem(val user: User) extends WrappedItem {
val properties = Map(
"Id" -> new VaadinProperty[Long] { def getValue = user.id },
"Name" -> new VaadinProperty[String] { def getValue = user.name },
"Address" -> new VaadinProperty[String] { def getValue = user.address }
)
def id = user.id
}
class UserContainer extends VaadinTableContainer(from(Schema.users)(u => where(u.city === "Talinn") select(u)) {
def wrap(user: User) = new UserItem(user)
lazy val fakeItem = new UserItem(null)
}
// That can be later used in a regular way:
// table.setContainerDataSource(new UserContainer())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment