Skip to content

Commit

Permalink
[breaking][trivially] ScalatraTests support for Jetty7 and Jetty8. [f…
Browse files Browse the repository at this point in the history
…ixes #129]

The signature of addFilter changed incompatibly from Jetty7 to Jetty8.
This is a reprehensible hack to support both with the same version of
Scalatra.  We don't need to add any more Cross Build Hell.™

If this broke your code, replace:
    import org.eclipse.jetty.server.DispatcherType
with
    import org.scalatra.test.DispatcherType
  • Loading branch information
rossabaker committed Sep 12, 2011
1 parent 5e5aa82 commit bd2a038
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 9 deletions.
4 changes: 3 additions & 1 deletion notes/2.0.0.markdown
@@ -1,11 +1,13 @@
### scalatra
* Alternate build for Jetty 8 / Servlet 3.0 (use "servlet30" classifier). [(GH-129)](http://github.com/scalatra/scalatra/issues/129)
* Fix `ApiFormats.acceptHeader` and sort by `q` parameter
* Add crossbuild for Scala 2.9.1

### scalatra-fileupload
* Support implicit `[]`-suffix convention in `fileMultiParams` as with `multiParams`. [(GH-128)](http://github.com/scalatra/scalatra/issues/128)

### scalatra-test
* Fix NoSuchMethodError in Jetty8. [(GH-129)](http://github.com/scalatra/scalatra/issues/129)

### scalatra-scalate
* Upgrade to Scalate-1.5.2

Expand Down
22 changes: 18 additions & 4 deletions project/build.scala
Expand Up @@ -105,11 +105,13 @@ object ScalatraBuild extends Build {
val base64 = "net.iharder" % "base64" % "2.3.8"

val commonsFileupload = "commons-fileupload" % "commons-fileupload" % "1.2.1"

val commonsIo = "commons-io" % "commons-io" % "2.0.1"
val commonsLang3 = "org.apache.commons" % "commons-lang3" % "3.0.1"

private def jettyDep(name: String) = "org.eclipse.jetty" % name % "7.4.5.v20110725"
private def jettyDep(name: String, version: String = "7.4.5.v20110725") =
"org.eclipse.jetty" % name % version
val testJettyServlet = jettyDep("test-jetty-servlet")
val testJettyServlet_8 = jettyDep("test-jetty-servlet", "8.0.1.v20110908")
val jettyWebsocket = jettyDep("jetty-websocket")
val jettyWebapp = jettyDep("jetty-webapp")

Expand Down Expand Up @@ -158,6 +160,7 @@ object ScalatraBuild extends Build {
}

val servletApi = "javax.servlet" % "servlet-api" % "2.5" % "provided"
val servletApi_3_0 = "javax.servlet" % "javax.servlet-api" % "3.0.1" % "provided"

def socketioCore(version: String) = "org.scalatra.socketio-java" % "socketio-core" % version

Expand All @@ -176,7 +179,7 @@ object ScalatraBuild extends Build {
.aggregate(scalatraCore, scalatraAuth, scalatraFileupload,
scalatraScalate, scalatraSocketio, scalatraLiftJson, scalatraAntiXml,
scalatraTest, scalatraScalatest, scalatraSpecs, scalatraSpecs2,
scalatraExample)
scalatraExample, scalatraJetty8Tests)

lazy val scalatraCore = Project("scalatra", file("core"),
settings = scalatraSettings)
Expand Down Expand Up @@ -247,7 +250,9 @@ object ScalatraBuild extends Build {
lazy val scalatraTest = Project("scalatra-test", file("test"),
settings = scalatraSettings)
.settings(
libraryDependencies ++= Seq(testJettyServlet, mockitoAll),
libraryDependencies <++= (scalaVersion) { sv =>
Seq(testJettyServlet, mockitoAll, commonsLang3, specs2(sv) % "test")
},
description := "The abstract Scalatra test framework")

lazy val scalatraScalatest = Project("scalatra-scalatest", file("scalatest"),
Expand Down Expand Up @@ -289,6 +294,15 @@ object ScalatraBuild extends Build {
.dependsOn(scalatraCore, scalatraScalate, scalatraAuth, scalatraFileupload,
scalatraSocketio)

lazy val scalatraJetty8Tests = Project("scalatra-jetty8-tests", file("test/jetty8"),
settings = scalatraSettings)
.settings(
libraryDependencies ++= Seq(servletApi_3_0, testJettyServlet_8 % "test"),
description := "Compatibility tests for Jetty 8",
publish := {},
publishLocal := {})
.testWithScalatraTest

class RichProject(project: Project) {
def testWithScalatraTest = {
val testProjects = Seq(scalatraScalatest, scalatraSpecs, scalatraSpecs2)
Expand Down
@@ -0,0 +1,48 @@
package org.scalatra
package test.jetty8

import java.util.EnumSet
import javax.servlet.{DispatcherType => ServletDispatcherType, _}
import org.specs2._
import test.DispatcherType
import DispatcherType._
import test.specs2.ScalatraSpec

class AddFilterSpecFilter extends Filter {
def init(config: FilterConfig) = {}
def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain) = {
res.getWriter.write(req.getDispatcherType.name)
}
def destroy() = {}
}

class AddFilterSpec extends ScalatraSpec { def is =
"Adding a filter instance should" ^
"dispatch on the specified dispatcher type" ! e1^
"not dispatch on unspecified dispatcher types" ! e2^
p^
"Adding a filter by class should" ^
"dispatch on the specified dispatcher type" ! e3^
"not dispatch on unspecified dispatcher types" ! e4

addFilter(new AddFilterSpecFilter, "/instance/request", EnumSet.of(REQUEST))
addFilter(new AddFilterSpecFilter, "/instance/not-request", EnumSet.complementOf(EnumSet.of(REQUEST)))
addFilter(classOf[AddFilterSpecFilter], "/class/request", EnumSet.of(REQUEST))
addFilter(classOf[AddFilterSpecFilter], "/class/not-request", EnumSet.complementOf(EnumSet.of(REQUEST)))

def e1 = get("/instance/request") {
body must_== "REQUEST"
}

def e2 = get("/instance/not-request") {
status must_== 404
}

def e3 = get("/class/request") {
body must_== "REQUEST"
}

def e4 = get("/class/not-request") {
status must_== 404
}
}
44 changes: 44 additions & 0 deletions test/src/main/java/org/scalatra/test/DispatcherType.java
@@ -0,0 +1,44 @@
package org.scalatra.test;

import java.util.*;
import org.apache.commons.lang3.reflect.MethodUtils;

public enum DispatcherType {
REQUEST(1),
FORWARD(2),
INCLUDE(4),
ERROR(8),
ASYNC(16);

private int intValue;

private DispatcherType(int intValue) {
this.intValue = intValue;
}

public int intValue() {
return intValue;
}

public static int intValue(Set<DispatcherType> dispatcherTypes) {
int value = 0;
for (DispatcherType dispatcherType : dispatcherTypes) {
value |= dispatcherType.intValue();
}
return value;
}

@SuppressWarnings(value = "unchecked") // yeah... I know
public static <E extends Enum<E>> EnumSet<E> convert(
Set<DispatcherType> dispatcherTypes, String className)
throws Exception
{
Set<E> result = new HashSet<E>();
Class<?> enumClass = Class.forName(className);
for (DispatcherType dispatcherType : dispatcherTypes) {
result.add((E) MethodUtils.invokeStaticMethod(Enum.class, "valueOf",
enumClass, dispatcherType.name()));
}
return EnumSet.copyOf(result);
}
}
13 changes: 13 additions & 0 deletions test/src/main/scala/org/scalatra/test/Reflection.scala
@@ -0,0 +1,13 @@
package org.scalatra.test

import org.apache.commons.lang3.reflect.MethodUtils

object Reflection {
def invokeMethod(target: AnyRef, methodName: String, args: AnyRef*): Either[Throwable, AnyRef] =
try {
Right(MethodUtils.invokeMethod(target, methodName, args :_*))
}
catch {
case e => Left(e)
}
}
25 changes: 21 additions & 4 deletions test/src/main/scala/org/scalatra/test/ScalatraTests.scala
Expand Up @@ -5,7 +5,6 @@ import scala.util.DynamicVariable
import java.net.URLEncoder.encode
import org.eclipse.jetty.testing.HttpTester
import org.eclipse.jetty.testing.ServletTester
import org.eclipse.jetty.server.DispatcherType
import org.eclipse.jetty.servlet.{FilterHolder, DefaultServlet, ServletHolder}
import java.nio.charset.Charset
import javax.servlet.http.HttpServlet
Expand Down Expand Up @@ -98,15 +97,33 @@ trait ScalatraTests {

def addFilter(filter: Filter, path: String): FilterHolder =
addFilter(filter, path, DefaultDispatcherTypes)

def addFilter(filter: Filter, path: String, dispatches: EnumSet[DispatcherType]): FilterHolder = {
val holder = new FilterHolder(filter)
tester.getContext.addFilter(holder, path, dispatches)
def tryToAddFilter(dispatches: AnyRef) = Reflection.invokeMethod(
tester.getContext, "addFilter", holder, path, dispatches)
// HACK: Jetty7 and Jetty8 have incompatible interfaces. Call it reflectively
// so we support both.
for {
_ <- tryToAddFilter(DispatcherType.intValue(dispatches): java.lang.Integer).left
result <- tryToAddFilter(DispatcherType.convert(dispatches, "javax.servlet.DispatcherType")).left
} yield (throw result)
holder
}

def addFilter(filter: Class[_ <: Filter], path: String): FilterHolder =
addFilter(filter, path, DefaultDispatcherTypes)
def addFilter(filter: Class[_ <: Filter], path: String, dispatches: EnumSet[DispatcherType]): FilterHolder =
tester.getContext.addFilter(filter, path, dispatches)

def addFilter(filter: Class[_ <: Filter], path: String, dispatches: EnumSet[DispatcherType]): FilterHolder = {
def tryToAddFilter(dispatches: AnyRef): Either[Throwable, AnyRef] =
Reflection.invokeMethod(tester.getContext, "addFilter",
filter, path, dispatches)
// HACK: Jetty7 and Jetty8 have incompatible interfaces. Call it reflectively
// so we support both.
(tryToAddFilter(DispatcherType.intValue(dispatches): java.lang.Integer).left map {
t: Throwable => tryToAddFilter(DispatcherType.convert(dispatches, "javax.servlet.DispatcherType"))
}).joinLeft fold ({ throw _ }, { x => x.asInstanceOf[FilterHolder] })
}

@deprecated("renamed to addFilter", "2.0")
def routeFilter(filter: Class[_ <: Filter], path: String) =
Expand Down
13 changes: 13 additions & 0 deletions test/src/test/scala/org/scalatra/test/DispatcherTypeSpec.scala
@@ -0,0 +1,13 @@
package org.scalatra.test

import java.util.EnumSet
import org.specs2.mutable._
import DispatcherType._

class DispatcherTypeSpec extends Specification {
"A set of dispatcher types" should {
"have an int value equal to the bitwise or of its members" in {
intValue(EnumSet.of(REQUEST, INCLUDE, ASYNC)) must_== 21
}
}
}

0 comments on commit bd2a038

Please sign in to comment.