Skip to content

Commit

Permalink
providing support for typesafe where...
Browse files Browse the repository at this point in the history
  • Loading branch information
asouza committed Jun 29, 2011
1 parent 4ea22a0 commit 1aa0eea
Show file tree
Hide file tree
Showing 18 changed files with 1,221 additions and 61 deletions.
@@ -0,0 +1,47 @@
package br.com.caelum.hibernatequerydsl

import scala.collection.JavaConversions._
import net.sf.cglib.proxy.Enhancer
import org.hibernate.criterion.{Criterion, Restrictions}

trait Cond {
def crit:Criterion
}
class EqCond(field:String, value:Any) extends Cond {
def crit = Restrictions.eq(field, value)
}

class ActiveCollection[T](var elements:List[T], query:PimpedCriteria[T,T])(implicit entityType:Manifest[T]) {

private type Myself = ActiveCollection[T]
private def loaded = Option(elements).isDefined

private implicit def listToAC(l:List[T]):Myself = new Myself(elements, null)
private implicit def queryToAC(q:PimpedCriteria[T,T]):Myself = new Myself(null, q)

def grabThem():List[T] = {
if(!loaded) {
elements = query.asList[T].toList
}
elements
}

def take(k: Int):Myself = {
query.using(_.setMaxResults(k))
}

def filter(f: (T) => Cond):Myself = {
query.and(applyRule(f).crit)
}

def find(f: (T) => Cond): Option[T] = {
query.and(applyRule(f).crit).using(_.setMaxResults(1)).headOption
}

def applyRule(f: (T) => Cond):Cond = {
val handler = new ComparisonCallback
val proxy = Enhancer.create(entityType.erasure, handler).asInstanceOf[T]
f(proxy)
}

}
@@ -0,0 +1,26 @@
package br.com.caelum.hibernatequerydsl

import net.sf.cglib.proxy.InvocationHandler
class ComparisonCallback extends InvocationHandler {

def invoke(proxy:AnyRef,method:java.lang.reflect.Method,args:Array[AnyRef]) = {
if(method.getReturnType!=classOf[String]) {
throw new RuntimeException("We are not supporting anything but strings right now, sorry")
}
// TODO to implement others, we will need to use the cutest ThreadLocal ever
// we can also use it with a SINGLE proxy per class by doing a list.an[User].getName

// TODO switch to case or something else
// TODO duplicated code, extract
var _invoked = method.getName
if(_invoked.startsWith("get")) {
_invoked = _invoked.substring(3, _invoked.length)
} else if(_invoked.startsWith("is")) {
_invoked = _invoked.substring(2, _invoked.length)
}
val rest = if (_invoked.length() > 0) _invoked.substring(1,_invoked.length()) else ""
_invoked = Character.toLowerCase(_invoked.charAt(0)) + rest
_invoked
}

}
@@ -0,0 +1,40 @@
package br.com.caelum.hibernatequerydsl

import net.sf.cglib.proxy._
import java.lang.reflect.{ Method, Modifier }
object Expression {
val callback = () => new MethodInterceptor {
implicit def string2WithRubyPowers(str: String) = new {
def withFirstCharLowered = {
str.substring(0, 1).toLowerCase + str.substring(1, str.length)
}
}

val properties = scala.collection.mutable.ListBuffer[String]()
def intercept(proxiedObject: Any, method: Method, params: Array[Object], methodProxy: MethodProxy) = {
val GetterExpression = """(get)?(\w*){1}""".r
method.getName match {
case GetterExpression(_, part2) => {
if (part2 != "toString") {
properties += part2.withFirstCharLowered
}
}
}
if ((method.getReturnType.getModifiers & Modifier.FINAL) == 0) {
proxy(method.getReturnType, this)
} else {
println(properties.mkString("."))
null
}
}
}
private def proxy[T](klass: Class[_], methodInterceptor: MethodInterceptor = callback()): T = {
val enhancer = new Enhancer
enhancer.setSuperclass(klass)
enhancer.setCallback(methodInterceptor)
enhancer.create.asInstanceOf[T]
}
def exp[T](implicit manifest: Manifest[T]): T = {
proxy(manifest.erasure)
}
}
@@ -0,0 +1,37 @@
package br.com.caelum.hibernatequerydsl

import net.sf.cglib.proxy.InvocationHandler
import org.hibernate.criterion.Restrictions
import java.lang.reflect.Method

class InvocationMemorizingCallback extends InvocationHandler {
private var _invoked: String = ""

def invokedPath = _invoked

def invoke(proxy: AnyRef, method: java.lang.reflect.Method, args: Array[AnyRef]) = {
_invoked = method.getName
// TODO switch to case or something else
if (_invoked.startsWith("get")) {
_invoked = _invoked.substring(3, _invoked.length)
} else if (_invoked.startsWith("is")) {
_invoked = _invoked.substring(2, _invoked.length)
}
val rest = if (_invoked.length() > 0) _invoked.substring(1, _invoked.length()) else ""
_invoked = Character.toLowerCase(_invoked.charAt(0)) + rest
null
}
}

object Pimps {
implicit def xxx(qq:Any) = new PimpedCriteriaCondition(target)
}

class PimpedCriteriaCondition(target:Any){
val proxy = target.asInstanceOf[InvocationMemorizingCallback]
def \==(value:Any) = {
println(proxy.invokedPath+"aaaa")
Restrictions.eq(proxy.invokedPath,value)
}
}

@@ -0,0 +1,18 @@
package br.com.caelum.hibernatequerydsl

import org.hibernate.criterion.Order

// TODO if the method to construct this guy received the asc or desc as a second
// parameter thourhg a import, then there would be no need for this extra guy or the
// extra implicit. remove it?
class OrderThis[T,P](path:String, val pimped:PimpedCriteria[T,P]) {

import pimped.criteriaToPimped
def asc():PimpedCriteria[T,P] = {
pimped.criteria.addOrder(Order.asc(path))
}
def desc():PimpedCriteria[T,P] = {
pimped.criteria.addOrder(Order.desc(path))
}

}
@@ -0,0 +1,141 @@
package br.com.caelum.hibernatequerydsl

import org.hibernate.criterion._
import org.hibernate.criterion.Projections._
import org.hibernate.impl.CriteriaImpl
import org.hibernate.transform.Transformers
import org.hibernate.{Session, Criteria}
import net.sf.cglib.proxy.Enhancer
import scala.collection.JavaConversions._
import javax.persistence.criteria.Path

/**
* A criteria that will query on objects of type T, projecting
* on type P. This criteria is backed by a hibernate criteria.
*/
class PimpedCriteria[T,P](prefix:String, val criteria: Criteria) {

import PimpedSession._
type Myself = PimpedCriteria[T,P]
implicit def criteriaToPimped(partial:Criteria) = new PimpedCriteria[T,P](prefix, partial)

val projections = projectionList
val criteriaImpl = criteria.asInstanceOf[CriteriaImpl]

if (criteriaImpl.getProjection != null) {
projections.add(criteriaImpl.getProjection)
}

def unique[Y]: Y = criteria.uniqueResult.asInstanceOf[Y]

def asList[Y]: List[Y] = criteria.list.asInstanceOf[java.util.List[Y]].toList

def using(f:(Criteria) => Criteria):Myself = f(criteria)

def list:List[P] = asList[P]

def orderBy(order: Order):Myself = criteria.addOrder(order)

// TODO use only one class per entity
def orderBy(f:(T) => Unit)(implicit entityType:Manifest[T]) = {
val path = evaluate(f)
new OrderThis[T,P](prefix + path, this)
}

def orderBy2[Proj](f:(Proj) => Unit)(implicit manifest:Manifest[Proj]) = {
val path = evaluate(f)
new OrderThis[T,Proj](path, new PimpedCriteria[T,Proj]("", criteria))
}

def headOption:Option[P] = using(_.setMaxResults(1)).list.toList.asInstanceOf[List[P]].headOption

def join(field: String):Myself = criteria.createAlias(field, field)

def join[Joiner](f:(T) => Joiner)(implicit entityType:Manifest[T]) = {
val field = evaluate(f)
new PimpedCriteria[Joiner, P](prefix + field + ".", criteria.createAlias(field, field))
}

private def evaluate[K,X](f:(K) => X)(implicit entityType:Manifest[K]):String = {
val handler = new InvocationMemorizingCallback
val proxy = Enhancer.create(entityType.erasure, handler).asInstanceOf[K]
f(proxy)
handler.invokedPath
}

def has(toManyField: String):Myself = {
criteria.add(Restrictions.isNotEmpty(toManyField))
}

def includes(toManyField: String):Myself = {
join(toManyField).has(toManyField)
}

def where(condition: Criterion):Myself = {
criteria.add(condition)
}

def where:Myself = { this }

def where(f:(T) => Unit)(implicit entityType:Manifest[T]) {
val field = evaluate(f)
}


def and(condition: Criterion):Myself = {
criteria.add(condition)
}

def count = criteria.setProjection(rowCount).uniqueResult.asInstanceOf[Long].intValue

def first[Y] = criteria.setFirstResult(0).setMaxResults(1).unique[Y]

def last[Y](implicit manifest: Manifest[Y]) = {
val dirtySession = criteria.asInstanceOf[CriteriaImpl].getSession.asInstanceOf[Session]
val size = dirtySession.from[Y].count
criteria.setFirstResult(size.intValue - 1).unique[Y]
}

def groupBy(fields: String*):Myself = {
fields.foreach(field => {
projections.add(Projections.groupProperty(field))
})
criteria.setProjection(projections)
}

def select(fields: String*):Myself = {
fields.foreach(field => {
projections.add(Projections.property(field))
})
criteria.setProjection(projections)
}

def selectWithAliases(fields: Projection*):Myself = {
fields.foreach(projections.add(_))
criteria.setProjection(projections)
}

def avg(field: String):Myself = {
projections.add(Projections.avg(field))
criteria.setProjection(projections)
}

def sum(field: String):Myself = {
projections.add(Projections.sum(field))
criteria.setProjection(projections)
}

def count(field: String):Myself = {
projections.add(Projections.count(field))
criteria.setProjection(projections)
}

def distinct(field:String):Myself = {
projections.add(Projections.distinct(Projections.property(field)))
criteria.setProjection(projections)
}

def transformToBean[Y](implicit manifest: Manifest[Y]) = {
new Transformer[Y,P](criteria.setResultTransformer(Transformers.aliasToBean(manifest.erasure)))
}
}
@@ -0,0 +1,21 @@
package br.com.caelum.hibernatequerydsl

import org.hibernate.Query
import scala.collection.JavaConversions._

class PimpedQuery(query: Query) {
def withParams(params: (String, Any)*) = {
params.foreach((param) => {
query.setParameter(param._1, param._2)
})
query
}

def unique[T]: T = query.uniqueResult.asInstanceOf[T]

def asList[T]: List[T] = query.list.asInstanceOf[java.util.List[T]].toList

def headOption[T]:Option[T] = {
query.setMaxResults(1).list.asInstanceOf[List[T]].headOption
}
}

0 comments on commit 1aa0eea

Please sign in to comment.