nkpart / scapps

scala app tools

Clone this repository (size: 2.5 MB): HTTPS / SSH
$ hg clone http://bitbucket.org/nkpart/scapps/
commit 2: ed6c4d333165
parent 1: d1ea74f9f4e7
branch: default
Slinky and GAE helpers
nkpart
8 months ago

Changed (Δ12.7 KB):

raw changeset »

project/build/ScappsProject.scala (19 lines added, 6 lines removed)

src/main/scala/scapps/gae/DataMapper.scala (28 lines added, 0 lines removed)

src/main/scala/scapps/gae/GoogleServices.scala (18 lines added, 0 lines removed)

src/main/scala/scapps/gae/RichMemcacheService.scala (20 lines added, 0 lines removed)

src/main/scala/scapps/gae/RichUserService.scala (13 lines added, 0 lines removed)

src/main/scala/scapps/gae/State.scala (59 lines added, 0 lines removed)

src/main/scala/scapps/http/BaseApp.scala (37 lines added, 0 lines removed)

src/main/scala/scapps/http/RestfulRoute.scala (59 lines added, 0 lines removed)

src/main/scala/scapps/http/Route.scala (63 lines added, 0 lines removed)

src/main/scala/scapps/http/SlinkyRunner.scala (26 lines added, 0 lines removed)

src/main/scala/scapps/util/Conversions.scala (68 lines added, 0 lines removed)

src/main/scala/scapps/util/StringParser.scala (17 lines added, 0 lines removed)

Up to file-list project/build/ScappsProject.scala:

@@ -3,17 +3,30 @@ import sbt._
3
3
class ScappsProject(info: ProjectInfo) extends DefaultProject(info) {
4
4
//  override def crossScalaVersions = Set("2.7.3", "2.7.4", "2.7.5")
5
5
//  override def parallelExecution = true
6
//  lazy val twitter = project("twitter", "Dispatch Twitter", new ScappsDefault(_), http, json, oauth)
6
7
7
//  lazy val twitter = project("twitter", "Dispatch Twitter", new ScappsDefault(_), http, json, oauth)
8
  
9
8
  override def managedStyle = ManagedStyle.Maven    
10
9
  
11
10
  val dispatchJson = "net.databinder" % "dispatch-json_2.7.5" % "0.5.0"
11
  
12
12
  val st = "org.scala-tools.testing" % "scalatest" % "0.9.5" % "test->default"
13
  // class HttpProject(info: ProjectInfo) extends ScappsDefault(info) {
14
  //     val httpclient = "org.apache.httpcomponents" % "httpclient" % "4.0-beta2"
13
  
14
  val servlet = "javax.servlet" % "servlet-api" % "2.5"
15
  
16
  val jettyServlet = "org.eclipse.jetty" % "jetty-servlet" % "7.0.0.RC2"
17
  val jettyServer = "org.eclipse.jetty" % "jetty-server" % "7.0.0.RC2"
18
  
19
  val gae = "com.google.appengine" % "appengine-api-1.0-sdk" % "1.2.1"
20
  
21
  val dnAppEngine = "com.google.appengine.orm" % "datanucleus-appengine" % "1.0.0"
22
  
23
  override def ivyXML =
24
        <dependencies>
25
            <dependency org="org.datanucleus" name="datanucleus-core" rev="1.1.0">
26
                <exclude org="javax.transaction" />
27
            </dependency>
28
        </dependencies>
29
  
15
30
  //     val lag_net = "lag.net repository" at "http://www.lag.net/repo"
16
31
  //     val configgy = "net.lag" % "configgy" % "1.3" % "provided->default"
17
  //     val st = "org.scala-tools.testing" % "scalatest" % "0.9.5" % "test->default"
18
  //   }
19
32
}

Up to file-list src/main/scala/scapps/gae/DataMapper.scala:

1
package scapps.gae
2
3
import scalaz.Scalaz._
4
import reflect.Manifest
5
6
abstract class DataMapper[T](implicit m : Manifest[T]) {
7
  import scapps.util.IterableConversions._
8
9
  private val cls : Class[T] = m.erasure.asInstanceOf[Class[T]]
10
11
  def findById(id: Long): Option[T] = State.runDB(pm => pm.getObjectById(cls, id).asInstanceOf[T].onull)
12
  
13
  def findAll() = State.runDB(pm => pm.getExtent(cls).iterator.toList)
14
15
  def findFirst(filter: String, params: String, args: String*): Option[T] = State.runDB(pm => {
16
    val q = pm.newQuery(cls, filter)
17
    q.declareParameters(params)
18
    val iterator = q.executeWithArray(args : _*).asInstanceOf[java.lang.Iterable[T]].iterator
19
    (iterator.hasNext).option(iterator.next)
20
  })
21
22
  def save(t : T) = State.runDB(pm => pm.makePersistent(t))
23
}
24
25
// TODO If this gets any bigger, split it out into a new file
26
trait Mappable {
27
  def save()(implicit mapper : DataMapper[this.type]) = mapper.save(this)
28
}

Up to file-list src/main/scala/scapps/gae/GoogleServices.scala:

1
package scapps.gae
2
3
import com.google.appengine.api.memcache._
4
import com.google.appengine.api.users._
5
6
trait GoogleServices {
7
  def memcacheService : MemcacheService
8
  
9
  def userService : UserService
10
}
11
12
trait DefaultServices extends GoogleServices {
13
  def memcacheService : MemcacheService = MemcacheServiceFactory.getMemcacheService
14
  
15
  def userService : UserService = UserServiceFactory.getUserService
16
}
17
18
object G extends DefaultServices

Up to file-list src/main/scala/scapps/gae/RichMemcacheService.scala:

1
package scapps.gae
2
3
import scalaz.Scalaz._
4
import com.google.appengine.api.memcache.MemcacheService
5
6
case class RichMemcacheService(memcache: MemcacheService) {
7
  def lookup[A](key: String): Option[A] = memcache.get(key).asInstanceOf[A].onull
8
9
  def getOrElsePut[A](key: String, a: => A) : A = {
10
    lookup(key) getOrElse {
11
      val v: A = a
12
      memcache.put(key, v)
13
      v
14
    }
15
  }
16
}
17
18
object RichMemcacheService {
19
  implicit def MemcacheTo(s: MemcacheService) = RichMemcacheService(s)
20
}

Up to file-list src/main/scala/scapps/gae/RichUserService.scala:

1
package scapps.gae
2
3
import com.google.appengine.api.users._
4
import scalaz.Scalaz._
5
6
case class RichUserService(service : UserService) {
7
  def user: Option[User] = service.getCurrentUser.onull
8
}
9
10
object RichUserService {
11
  implicit def UserServiceTo(s: UserService) = RichUserService(s)
12
}
13

Up to file-list src/main/scala/scapps/gae/State.scala:

1
package scapps.gae
2
3
import scalaz.Scalaz._
4
import javax.jdo.{JDOHelper, PersistenceManager}
5
6
trait Database {
7
  def runDB[B](f : PersistenceManager => B)
8
  
9
  // TODO provide implementation of this in terms of runDb
10
  def transaction[B](thunk : => B)
11
}
12
13
object State {
14
  private val pmf = JDOHelper.getPersistenceManagerFactory("transactions-optional")
15
  
16
  trait PM {
17
    def acquire() : PersistenceManager
18
    def release(pm : PersistenceManager)
19
  }
20
21
  case class NormalPM() extends PM {
22
    def acquire() : PersistenceManager = pmf.getPersistenceManager
23
    def release(pm : PersistenceManager) = pm.close
24
  }
25
26
  case class TransactionalPM(pm : PersistenceManager) extends PM {
27
    def acquire() : PersistenceManager = pm
28
    def release(pm : PersistenceManager) = () // no op
29
  }
30
31
  private val managerMaker = new scala.util.DynamicVariable[PM](NormalPM())
32
33
  def runDB[B](f : PersistenceManager => B) = {
34
    val thePm = managerMaker.value.acquire()
35
    try {
36
      f(thePm)
37
    } finally {
38
      managerMaker.value.release(thePm)
39
    }
40
  }
41
42
  def transaction[B](thunk : => B) = runDB(pm => {
43
    val pm = managerMaker.value.acquire();
44
    
45
    val t = pm.currentTransaction()
46
    try {
47
      t.begin()
48
      val r = managerMaker.withValue(TransactionalPM(pm)) { thunk }
49
      t.commit()
50
      r
51
    } finally {
52
      if (t.isActive()) {
53
        t.rollback()
54
      }
55
      managerMaker.value.release(pm)
56
    }
57
  }
58
  )
59
}

Up to file-list src/main/scala/scapps/http/BaseApp.scala:

1
package scapps.http
2
3
4
// TODO clean up these imports if possible
5
6
import javax.servlet.ServletContext
7
import scalaz.Scalaz._
8
import scalaz.http.request.{Method, Request}
9
import scalaz.http.servlet.{HttpServlet, HttpServletRequest, ServletApplication, StreamStreamServletApplication}
10
import scalaz.http.servlet.HttpServlet._
11
import scalaz.http.StreamStreamApplication._
12
import scalaz.http.{Application}
13
import scalaz.http.response._
14
import scalaz.http.response.Body._
15
16
/*
17
Slinky base application that provides Optional handling of request.
18
Looks up resources if None.
19
*/
20
abstract class BaseApp extends StreamStreamServletApplication {
21
  def route(implicit request : Request[Stream], servletRequest: HttpServletRequest) : Option[Response[Stream]]
22
23
  def log(request: Request[Stream], response: Response[Stream]) {
24
    //"0.0.0.0 - localhost [%s] \"%s %s HTTP/1.0\" %s 0000" format ("", request.method.asString, request.path.list.mkString, response.status.toInt.toString)
25
    //request
26
    // TODO use apache log format
27
    // 127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326
28
  }
29
  
30
  val application = new ServletApplication[Stream, Stream] {
31
    def application(implicit servlet: HttpServlet, servletRequest: HttpServletRequest, request: Request[Stream]) = {
32
      val response = route getOrElse resource(x => OK << Stream.fromIterator(x), NotFound.xhtml)
33
      log(request, response)
34
      response
35
    }
36
  }
37
}

Up to file-list src/main/scala/scapps/http/RestfulRoute.scala:

1
package scapps.htpp
2
3
import scalaz.Scalaz._
4
import scalaz.{NonEmptyList, Kleisli}
5
import scalaz.http.request._
6
import scalaz.http.response.Response
7
8
case class ItemRequest(request: Request[Stream], key: NonEmptyList[Char]) {
9
  import scapps.util.StringParser._
10
  lazy val longKey: Option[Long] = key.list.mkString.asLong
11
}
12
13
final class RequestW(val request : Request[Stream]) {
14
  def asItemRequest : Option[ItemRequest] = {
15
    val sid: Option[NonEmptyList[Char]] = request.path.list.mkString.split("/")(0).toList.nel
16
    (sid |> (ItemRequest(request, _)))
17
  }
18
}
19
20
object RequestW {
21
  implicit def RequestTo(request : Request[Stream]) = new RequestW(request)
22
  implicit def RequestFrom(w : RequestW) = w.request
23
}
24
25
trait RestfulRoute extends Kleisli[Option, Request[Stream], Response[Stream]] {
26
  import scapps.http.Route._
27
  import scapps.http.OptionKleisli.OptionKleisli
28
  import RequestW._
29
30
  val base: String
31
32
  def index(r: Request[Stream]): Option[Response[Stream]]
33
34
  def formForCreate(r: Request[Stream]): Option[Response[Stream]]
35
36
  def createItem(r: Request[Stream]): Option[Response[Stream]]
37
38
  def formForEdit(request: ItemRequest): Option[Response[Stream]]
39
40
  def deleteItem(r: ItemRequest): Option[Response[Stream]]
41
42
  def updateItem(r: ItemRequest): Option[Response[Stream]]
43
44
  def item(r: ItemRequest): Option[Response[Stream]]
45
46
  def routes : Kleisli[Option, Request[Stream], Response[Stream]]  = {
47
    methodHax >=> dir("/" + base) >=> List(
48
      GET >=> List(
49
        "/" >=> index _,
50
        "/new" >=> formForCreate _,
51
        dir("/") >=> withParam("edit") >=> (_.asItemRequest) >=> formForEdit _,
52
        dir("/") >=> (_.asItemRequest) >=> item _
53
        ),
54
      PUT >=> dir("/") >=> (_.asItemRequest) >=> updateItem _,
55
      POST >=> "/" >=> createItem _,
56
      DELETE >=> dir("/") >=> (_.asItemRequest) >=> deleteItem _
57
      )
58
  }
59
}

Up to file-list src/main/scala/scapps/http/Route.scala:

1
package scapps.http
2
3
import scalaz.Kleisli
4
import scalaz.{OptionW, StringW}
5
import scalaz.http.request._
6
7
import scalaz.http.response.Response
8
import scalaz.NonEmptyList._
9
import scalaz.NonEmptyList
10
import scalaz.Scalaz._
11
12
object OptionKleisli {
13
  // Scalaz doesn't supply this. Yet.
14
  implicit def OptionKleisli[A, B](f: A => Option[B]): Kleisli[Option, A, B] = Kleisli.kleisli[Option](f)  
15
}
16
17
object Route {
18
  def firstSome[A, B](fs: List[Kleisli[Option, A, B]])(a: A): Option[B] = (fs.elements.map(_(a)).find(_.isDefined)).join
19
20
  implicit def ListKleisliKleisli[A, B](fs: List[Kleisli[Option, A, B]]): Kleisli[Option, A, B] = Kleisli.kleisli[Option](firstSome(fs) _)
21
22
  implicit def PathKliesli(s: String) = Kleisli.kleisli[Option](exactdir(s))
23
24
  implicit def MethodKliesli(m: Method) = Kleisli.kleisli[Option](methodM(m))
25
26
  implicit def PathMethodKliesli(t: Tuple2[Method, String]) = MethodKliesli(t._1) >=> PathKliesli(t._2)
27
28
  def stringToNel(s : String, n : NonEmptyList[Char]) = {
29
    if (s.length == 0) n else {
30
      val l = s.toList
31
      nel(l.head, l.tail)
32
    }
33
  }
34
  // returns a new request with the prefix stripped if it matches, else none
35
  def dir(prefix: String) = {
36
    def f(prefix: String)(r: Request[Stream]): Option[Request[Stream]] = {
37
      (r.pathStartsWith(prefix)).option(
38
        r(r.uri(stringToNel(r.path.list.mkString.replaceFirst(prefix, ""), NonEmptyList.nel('/'))))
39
      )
40
    }
41
    f(prefix) _
42
  }
43
44
45
  // returns a new request with the uri stripped to '/', if it matches exactly, else none
46
  def exactdir(s: String) : (Request[Stream] => Option[Request[Stream]]) = {
47
    def g(s: String)(r: Request[Stream]): Option[Request[Stream]] = (r.pathEquals(s)).option(r(r.uri(NonEmptyList.nel('/'))))
48
    g(s) _
49
  }
50
51
  def withParam(key: String) = (r: Request[Stream]) => (r !? key).option(r)
52
53
  def methodM(m: Method) : (Request[Stream] => Option[Request[Stream]]) = (r: Request[Stream]) => { (r.method.equals(m)).option(r) }
54
55
  // interprets the value of _method in a post as an HTTP method
56
  def methodHax = (r: Request[Stream]) => {
57
    import scalaz.http.request.Method._
58
    val mbMeth: Option[Method] = (r | "_method") >>= (_.mkString : Option[Method])
59
    Some((mbMeth |> (r(_))) getOrElse (r))
60
  }
61
}
62
63

Up to file-list src/main/scala/scapps/http/SlinkyRunner.scala:

1
package scapps.http
2
3
import org.eclipse.jetty.server.bio.SocketConnector
4
import org.eclipse.jetty.server.handler.ContextHandler
5
import org.eclipse.jetty.server.{Connector, Handler, Server}
6
import org.eclipse.jetty.servlet.{ServletHolder, ServletContextHandler}
7
8
object SlinkyRunner {
9
  def main(args: Array[String]) {
10
    val server: Server = new Server(8081)
11
    val handler: ServletContextHandler = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS |
12
            ServletContextHandler.NO_SECURITY)
13
14
    val holder = new ServletHolder(classOf[scalaz.http.servlet.StreamStreamServlet])
15
    holder.setInitParameter("application", "com.scapps.ScappsApplication")
16
    
17
    // TODO Improve
18
    holder.setInitParameter("scapps-application", args(0))
19
20
    handler.addServlet(holder, "/*")
21
    server.setHandler(handler);
22
23
    server.start();
24
    server.join();
25
  }
26
}

Up to file-list src/main/scala/scapps/util/Conversions.scala:

1
package scapps.util
2
3
/**
4
 * General Java Iterable converters, allowing for comprehensions.
5
 *
6
 * Based on suggestions from Jamie Webb (http://scala.sygneca.com/)
7
 */
8
object IterableConversions {
9
  case class JavaIteratorIterator[A](itr: java.util.Iterator[A]) extends Iterator[A] {
10
    def hasNext = itr.hasNext
11
12
    def next() = itr.next
13
  }
14
15
  case class JavaIteratableIterable[A](iterable: java.lang.Iterable[A]) extends Iterable[A] {
16
    def elements = JavaIteratorIterator(iterable.iterator)
17
  }
18
19
  implicit def javaIteratorTo[A](iterator : java.util.Iterator[A]) = JavaIteratorIterator(iterator)
20
21
  implicit def implicitJavaIterableToScalaIterable[A](iterable: java.lang.Iterable[A]): Iterable[A] =
22
    JavaIteratableIterable(iterable)
23
24
  case class JavaEnumerationIterator[A](itr: java.util.Enumeration[A]) extends Iterator[A] {
25
    def hasNext = itr.hasMoreElements
26
27
    def next() = itr.nextElement
28
  }
29
30
  case class JavaEnumerationIterable[A](iterable: java.util.Enumeration[A]) extends Iterable[A] {
31
    def elements = JavaEnumerationIterator(iterable)
32
  }
33
34
  implicit def implicitJavaEnumerationToScalaIterable[A](iterable: java.util.Enumeration[A]): Iterable[A] =
35
    JavaEnumerationIterable(iterable)
36
37
}
38
39
object JavaEnumerationConversions {
40
  implicit def toEnumeration[A](iterator: java.util.Iterator[A]): java.util.Enumeration[A] =
41
    new java.util.Enumeration[A] {
42
      def hasMoreElements = iterator.hasNext
43
44
      def nextElement = iterator.next
45
    }
46
}
47
48
/**
49
 * General Scala to Iterable converters, allowing for java use.
50
 *
51
 */
52
object JavaIterableConversions {
53
  class JavaIterator[T](itr: Iterator[T]) extends java.util.Iterator[T] {
54
    def hasNext() = itr.hasNext
55
56
    def next() = itr.next
57
58
    def remove() = throw new java.lang.UnsupportedOperationException("Remove is not implemented in JavaIterableConversions:JavaIterator")
59
  }
60
61
  class JavaIterable[T](iterable: Iterable[T]) extends java.lang.Iterable[T] {
62
    def iterator() = new JavaIterator[T](iterable.elements)
63
  }
64
65
  implicit def implicitScalaIterableToJavaIterable[T](iterable: Iterable[T]): java.lang.Iterable[T] =
66
    new JavaIterable[T](iterable)
67
68
}

Up to file-list src/main/scala/scapps/util/StringParser.scala:

1
package scapps.util
2
3
import scalaz.Scalaz._
4
import scalaz.{Validation, Success, Failure}
5
6
case class StringParser(s : String) {
7
  def parseLong: Validation[NumberFormatException, Long] = {
8
    (() => java.lang.Long.parseLong(s)).throws.left.map(_.asInstanceOf[NumberFormatException])
9
  }.fold(Failure(_), Success(_))
10
  
11
  def asLong: Option[Long] = parseLong.either.right.toOption
12
}
13
14
object StringParser {
15
  implicit def StringTo(s : String) = StringParser(s)
16
  implicit def StringFrom(p : StringParser) = p.s
17
}