Skip to content
This repository has been archived by the owner on Oct 6, 2022. It is now read-only.

Commit

Permalink
changed the way how the matching of URIs is done; the code that needs…
Browse files Browse the repository at this point in the history
… to be written is now less verbose and should be far easier to understand/maintain

pushed to version 0.0.1
  • Loading branch information
Delors committed Jun 1, 2012
1 parent daaa8ad commit d7e0f94
Show file tree
Hide file tree
Showing 35 changed files with 500 additions and 856 deletions.
2 changes: 1 addition & 1 deletion core/pom.xml
Expand Up @@ -21,7 +21,7 @@
<parent>
<artifactId>org.dorest</artifactId>
<groupId>org.dorest</groupId>
<version>0.0.0-SNAPSHOT</version>
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>org.dorest.core</artifactId>
Expand Down
17 changes: 9 additions & 8 deletions core/src/main/scala/org/dorest/server/DoRestApp.scala
Expand Up @@ -17,19 +17,20 @@ package org.dorest.server

import collection.mutable.Buffer

/** Enables the registration of [[org.dorest.server.HandlerFactory]] objects.
*
* This trait is implemented by DoRest servers.
*
* @author Michael Eichberg
*/
/**
* Enables the registration of [[org.dorest.server.HandlerFactory]] objects.
*
* This trait is to be implemented by DoRest servers.
*
* @author Michael Eichberg
*/
trait DoRestApp {

private var _factories: Buffer[HandlerCreator] = Buffer()
private var _factories: Buffer[HandlerFactory] = Buffer()

def factories = _factories

def register(handlerFactory: HandlerCreator) {
def register(handlerFactory: HandlerFactory) {
_factories += handlerFactory
}

Expand Down
232 changes: 11 additions & 221 deletions core/src/main/scala/org/dorest/server/HandlerFactory.scala
Expand Up @@ -17,229 +17,19 @@ package org.dorest.server

import java.lang.Long

/** HandlerCreators are responsible for matching URIs and – if the URI matches – to create a new Handler
* that will then handle the request.
*
* '''Thread Safety'''
* Handler factories have to be thread safe. I.e., handler factories have to support the simultaneous
* matching of URIs; the DoRest framework use a single Handler factory for matching URIs.
*
* @author Michael Eichberg
*/
trait HandlerCreator {
/**
* HandlerCreators are responsible for matching URIs and – if the URI matches – to create a new Handler
* that will then handle the request.
*
* '''Thread Safety'''
* Handler factories have to be thread safe. I.e., handler factories have to support the simultaneous
* matching of URIs; the DoRest framework use a single Handler factory for matching URIs.
*
* @author Michael Eichberg
*/
trait HandlerFactory {

def matchURI(path: String, query: String): Option[Handler]

}

/** @author Michael Eichberg
*/
abstract class HandlerFactory[T <: Handler] extends HandlerCreator {

protected implicit def namedSegmentToPathSegment(name: String): Named = new Named(name)

/** A function that does nothing.
*/
// Used to avoid that we have to deal with Option objects or have to deal with null values.
final def DoNothing(t: T): Unit = {
/*do nothing*/
}

// TODO merge path and pathelement; i.e., make pathelement itself a list
/** The (URI) path of this resource.
*/
trait PathMatcher {

/** Prepends the given path element to this path and returns a new path object.
*/
def ::(p: PathElement): PathMatcher

/** Tries to match the path. If the path matches, a list of functions is returned which
* will then be called; e.g., to complete the initialization of the resource's parameters.
*/
def matchSegment(path: String): Option[List[(T) Unit]]
}

class ComplexPath(val pe: PathElement, val tail: PathMatcher) extends PathMatcher {

def ::(pe: PathElement): PathMatcher = new ComplexPath(pe, this)

def matchSegment(path: String): Option[List[(T) Unit]] = {
pe.matchSegment(path) match {
case Some((pathRest, f)) {
// this segment matched, but what about the rest of the path?
tail.matchSegment(pathRest) map (fs f :: fs)
}
case _ None
}
}
}

object EmptyPath extends PathMatcher {

def ::(p: PathElement): PathMatcher = new ComplexPath(p, this)

def matchSegment(path: String): Option[List[(T) Unit]] =
if (path.size == 0) Some(Nil) else None
}

trait PathElement {

/** Constructs a new path that consists of the given path element and this element.
*/
def ::(ps: PathElement): PathMatcher = {
new ComplexPath(ps, new ComplexPath(this, EmptyPath))
}

/** Tries to match a maximum length segment of the given path. If the match is successful
* the rest of the path and a function is returned that – if the whole path can be matched –
* is called.
*
* The primary purpose of the function is to enable the initialization of a resource's variable
* path parameters.
*/
def matchSegment(path: String): Option[(String, (T) Unit)]

}

/** Can be used to match a path (segment) that is optional and which extends until the end of a given
* concrete path.
*
* If the given path is not empty and does not match the match is considered to have failed
* unless {link #failOnMatchError} is set to false.
*
* Cannot be used to match an optional sub-part of a path. E.g., matching something like
* {{{"/user"::{"/"userid}::"/tag"}}} where {{{"/"userid"}}} is optional is not possible.
*/
class Optional(val p: PathMatcher, val failOnMatchError: Boolean) extends PathElement {

def matchSegment(path: String): Option[(String, (T) Unit)] = {
if (path.size == 0) {
return Some(("", DoNothing))
}

p.matchSegment(path) match {
case Some(fs) Some(("", (t: T) {
fs.foreach(_(t))
}))
case _ if (failOnMatchError) None else Some(("", DoNothing))
}
}

}

object Optional {
def apply(pe: PathElement, failOnMatchError: Boolean = true) = {
new Optional(new ComplexPath(pe, EmptyPath), failOnMatchError)
}
}

/** Matches a segment that defines a long value.
*/
class LongValue(val set: Long T Unit) extends PathElement {

def matchSegment(path: String): Option[(String, (T) Unit)] = {
LongValue.matcher.findFirstIn(path).map(s {
(path.substring(s.length), set(s.toLong))
}
)
}
}

object LongValue {

private val matcher = """^-?\d+""".r

def apply(set: Long T Unit): PathElement = new LongValue(set)

def apply(set: (T, Long) Unit): PathElement = apply((v: Long) (t: T) set(t, v.toLong))

}

/** Matches a string segment that contains a word character or "@".
*/
class StringValue(set: String T Unit) extends PathElement {

def matchSegment(path: String): Option[(String, (T) Unit)] = {
StringValue.matcher.findFirstIn(path).map((s) {
(path.substring(s.length), set(s))
}
)
}
}

object StringValue {

private val matcher = """^(\w|@|-|\.)+""".r

def apply(set: (String) (T) Unit) = new StringValue(set)

def apply(set: (T, String) Unit): PathElement = apply((v: String) (t: T) set(t, v))

}

class AnyPath(set: String T Unit) extends PathElement {
def matchSegment(path: String): Option[(String, T Unit)] = {
Some("" /* The (empty) rest of the path. */ , set(path))
}
}

object AnyPath {
def apply(set: (String) (T) Unit) = new AnyPath(set)
}

class Named(val name: String) extends PathElement {

def matchSegment(path: String): Option[(String, (T) Unit)] = {
if (path.startsWith(name)) {
val pathRest = path.substring(name.length)
Some((pathRest, DoNothing))
}
else {
None
}
}
}

def matchURI(path: String, query: String): Option[T] = {
pathMatcher matchSegment (path) map { create(_) }
/*this.pathMatcher.matchSegment(path) match {
case Some(fs) ⇒ {
Some(create(fs))
}
case _ ⇒ None
}*/
}

private def create(fs: List[(T) Unit]): T = {
val t = create()
fs.foreach(_(t))
t
}

trait QueryMatcher

object NoQuery extends QueryMatcher

private var pathMatcher: PathMatcher = EmptyPath

def path(f: PathMatcher) {
pathMatcher = f
}

def path(staticPath: String) {
path {
staticPath :: EmptyPath
}
}

private[this] var queryMatcher: QueryMatcher = NoQuery

def query(f: QueryMatcher) {
queryMatcher = f
}

/** Creates a new handler object that will be further initialized using the segments extracted by the path matchers.
*/
def create(): T
}
20 changes: 4 additions & 16 deletions core/src/main/scala/org/dorest/server/MappedDirectory.scala
Expand Up @@ -23,13 +23,11 @@ import java.lang.Boolean
*
* @author Michael Eichberg (mail at michael-eichberg.de)
*/
class MappedDirectory(val baseDirectory: String, enableIndexHTMLDeliveryOnDirectoryAccess: Boolean = false) extends Handler {
class MappedDirectory(val baseDirectory: String, val path: String, enableIndexHTMLDeliveryOnDirectoryAccess: Boolean = false) extends Handler {

import java.io._

var path: String = _

def processRequest(requestBody: => InputStream): Response = {
def processRequest(requestBody: InputStream): Response = {
if (method != GET) {
return new SupportedMethodsResponse(GET)
}
Expand All @@ -53,7 +51,7 @@ class MappedDirectory(val baseDirectory: String, enableIndexHTMLDeliveryOnDirect
val fileName = file.getName
val fileType = {
val fileSuffix = fileName.substring(fileName.lastIndexOf('.') + 1)
Some((
Some((// TODO move to some extensible table
fileSuffix match {
case "css" MediaType.TEXT_CSS
case "javascript" MediaType.APPLICATION_JAVASCRIPT
Expand Down Expand Up @@ -87,20 +85,10 @@ class MappedDirectory(val baseDirectory: String, enableIndexHTMLDeliveryOnDirect
def length = file.length.asInstanceOf[Int]

def write(responseBody: OutputStream) {
// TODO Read blocks and not just single bytes.
// TODO Think about caching files.
/*val in = new FileInputStream(file)
try {
while (in.available > 0)
responseBody.write(in.read)
} finally {
if (in != null)
in.close
}*/
responseBody.write(readFully(file))
}

def readFully(file: File) : Array[Byte] = {
def readFully(file: File): Array[Byte] = {
val in = new FileInputStream(file)
try {
val length = file.length.asInstanceOf[Int];
Expand Down

0 comments on commit d7e0f94

Please sign in to comment.