Last active
December 30, 2015 05:09
-
-
Save voronaam/7780946 to your computer and use it in GitHub Desktop.
Vaadin to Squeryl Container implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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