| commit 2: | ed6c4d333165 |
| parent 1: | d1ea74f9f4e7 |
| branch: | default |
8 months ago
Changed (Δ12.7 KB):
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 |
} |
