Skip to content

Commit

Permalink
Merge branch 'release/0.0.4'
Browse files Browse the repository at this point in the history
  • Loading branch information
codahale committed May 10, 2011
2 parents 115f805 + a497538 commit 6e1764f
Show file tree
Hide file tree
Showing 33 changed files with 521 additions and 437 deletions.
13 changes: 6 additions & 7 deletions README.md
Expand Up @@ -5,13 +5,12 @@ Dropwizard

It's a little bit of opinionated glue code which bangs together a set of libraries which have historically not sucked:

- [Jetty](http://www.eclipse.org/jetty/) for HTTP servin'.
- [Jersey](http://jersey.java.net/) for REST modelin'.
- [Guice](http://code.google.com/p/google-guice/) for dependency injectin'.
- [Fig](https://github.com/codahale/fig) for JSON configurin'.
- [Jerkson](https://github.com/codahale/jerkson)/[Jackson](http://jackson.codehaus.org) for JSON parsin' and generatin'.
- [Logula](https://github.com/codahale/logula)/[Log4j](http://logging.apache.org/log4j/1.2/) for loggin'.
- [Metrics](https://github.com/codahale/metrics) for figurin' out what your service is doing in production.
* [Jetty](http://www.eclipse.org/jetty/) for HTTP servin'.
* [Jersey](http://jersey.java.net/) for REST modelin'.
* [Fig](https://github.com/codahale/fig) for JSON configurin'.
* [Jerkson](https://github.com/codahale/jerkson)/[Jackson](http://jackson.codehaus.org) for JSON parsin' and generatin'.
* [Logula](https://github.com/codahale/logula)/[Log4j](http://logging.apache.org/log4j/1.2/) for loggin'.
* [Metrics](https://github.com/codahale/metrics) for figurin' out what your service is doing in production.

[Yammer](https://www.yammer.com)'s high-performance, low-latency, Scala services all use Dropwizard. In fact, Dropwizard
is really just a simple extraction of [Yammer](https://www.yammer.com)'s glue code.
Expand Down
4 changes: 2 additions & 2 deletions project/build.properties
Expand Up @@ -2,7 +2,7 @@
#Mon Jan 17 19:25:38 PST 2011
project.organization=com.yammer
project.name=dropwizard
sbt.version=0.7.5.RC0
project.version=0.0.3
sbt.version=0.7.6.RC0
project.version=0.0.4
build.scala.versions=2.8.1
project.initialize=false
20 changes: 8 additions & 12 deletions project/build/DropwizardProject.scala
Expand Up @@ -31,28 +31,24 @@ class DropwizardProject(info: ProjectInfo) extends DefaultProject(info)
val simplespec = "com.codahale" %% "simplespec" % "0.2.0" % "test"
val mockito = "org.mockito" % "mockito-all" % "1.8.4" % "test"

/**
* Guice Dependencies
*/
val guice = "com.google.inject" % "guice" % "3.0"
val guiceServlet = "com.google.inject.extensions" % "guice-servlet" % "3.0"
val guiceMultibindings = "com.google.inject.extensions" % "guice-multibindings" % "3.0"

/**
* Jersey Dependencies
*/
val jerseyScala = "com.codahale" %% "jersey-scala" % "0.1.3"
// TODO: 3/29/11 <coda> -- Change back to regular packaging once
// http://java.net/jira/browse/JERSEY-697 is resolved.
val jerseyGuice = "com.sun.jersey.contribs" % "jersey-guice-nogrizzly" % "1.6"

/**
* Misc Dependencies
*/
val fig = "com.codahale" %% "fig" % "1.1.1"
val jerkson = "com.codahale" %% "jerkson" % "0.1.7"
val metrics = "com.yammer" %% "metrics" % "2.0.0-BETA11"
val jerkson = "com.codahale" %% "jerkson" % "0.1.8"
val metricsVersion = "2.0.0-BETA12"
val metricsCore = "com.yammer.metrics" %% "metrics-core" % metricsVersion
val metricsServlet = "com.yammer.metrics" %% "metrics-servlet" % metricsVersion
val metricsJetty = "com.yammer.metrics" %% "metrics-jetty" % metricsVersion
val metricsLog4j = "com.yammer.metrics" %% "metrics-log4j" % metricsVersion
val commonsCli = "commons-cli" % "commons-cli" % "1.2"
val jacksonCore = "org.codehaus.jackson" % "jackson-core-asl" % "1.7.6"
val jacksonMapper = "org.codehaus.jackson" % "jackson-mapper-asl" % "1.7.6"

/**
* Logging Dependencies
Expand Down
130 changes: 130 additions & 0 deletions src/main/scala/com/yammer/dropwizard/Environment.scala
@@ -0,0 +1,130 @@
package com.yammer.dropwizard

import collection.JavaConversions._
import lifecycle.Managed
import com.sun.jersey.api.core.ResourceConfig._
import com.codahale.logula.Logging
import com.yammer.metrics.core.HealthCheck
import javax.servlet.{Servlet, Filter}
import com.sun.jersey.core.reflection.MethodList
import javax.ws.rs.{Path, HttpMethod}
import org.eclipse.jetty.servlet.{ServletHolder, FilterHolder}

class Environment extends Logging {
private[dropwizard] var resources = Set.empty[Object]
private[dropwizard] var healthChecks = Set.empty[HealthCheck]
private[dropwizard] var providers = Set.empty[Object]
private[dropwizard] var managedObjects = IndexedSeq.empty[Managed]
private[dropwizard] var filters = Map.empty[String, FilterHolder]
private[dropwizard] var servlets = Map.empty[String, ServletHolder]

def addResource(resource: Object) {
if (!isRootResourceClass(resource.getClass)) {
throw new IllegalArgumentException(resource.getClass.getCanonicalName +
" is not a @Path-annotated resource class")
}

if (annotatedMethods(resource).isEmpty) {
throw new IllegalArgumentException(resource.getClass.getCanonicalName +
" has no @GET/@POST/etc-annotated methods")
}

resources += resource
}

def addProvider(provider: Object) {
if (!isProviderClass(provider.getClass)) {
throw new IllegalArgumentException(provider.getClass.getCanonicalName +
" is not a @Provider-annotated provider class")
}
providers += provider
}

def addHealthCheck(healthCheck: HealthCheck) {
healthChecks += healthCheck
}

def manage(managedObject: Managed) {
managedObjects ++= IndexedSeq(managedObject)
}

def addFilter[T <: Filter](filter: T,
pathSpec: String,
params: Map[String, String] = Map.empty) {
val holder = new FilterHolder(filter)
holder.setInitParameters(params)
filters += pathSpec -> holder
}

def addFilterClass[T <: Filter](klass: Class[T],
pathSpec: String,
params: Map[String, String] = Map.empty) {
val holder = new FilterHolder(klass)
holder.setInitParameters(params)
filters += pathSpec -> holder
}

def addServlet[T <: Servlet](servlet: T,
pathSpec: String,
params: Map[String, String] = Map.empty,
initOrder: Int = 0) {
val holder = new ServletHolder(servlet)
holder.setInitParameters(params)
holder.setInitOrder(initOrder)
servlets += pathSpec -> holder
}

def addServletClass[T <: Servlet](klass: Class[T],
pathSpec: String,
params: Map[String, String] = Map.empty,
initOrder: Int = 0) {
val holder = new ServletHolder(klass)
holder.setInitParameters(params)
holder.setInitOrder(initOrder)
servlets += pathSpec -> holder
}

private[dropwizard] def validate() {
def logResources() {
def httpMethods(resource: Object) = annotatedMethods(resource).map {
_.getMetaMethodAnnotations(classOf[HttpMethod]).map { _.value() }
}.flatten.toIndexedSeq.sorted

def paths(resource: Object) =
resource.getClass.getAnnotation(classOf[Path]).value() :: Nil

val out = new StringBuilder("\n\n")
for (resource <- resources;
path <- paths(resource);
method <- httpMethods(resource)) {
out.append(" %s %s (%s)\n".format(method, path, resource.getClass.getCanonicalName))
}
log.info(out.toString())
}

log.debug("resources = %s", resources.mkString("{", ", ", "}"))
log.debug("providers = %s", providers.mkString("{", ", ", "}"))
log.debug("health checks = %s", healthChecks.mkString("{", ", ", "}"))
log.debug("managed objects = %s", managedObjects.mkString("{", ", ", "}"))

logResources()

if (healthChecks.isEmpty) {
log.warn("""
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! THIS SERVICE HAS NO HEALTHCHECKS. THIS MEANS YOU WILL NEVER KNOW IF IT !
! DIES IN PRODUCTION, WHICH MEANS YOU WILL NEVER KNOW IF YOU'RE LETTING !
! YOUR USERS DOWN. YOU SHOULD ADD A HEALTHCHECK FOR EACH DEPENDENCY OF !
! YOUR SERVICE WHICH FULLY (BUT LIGHTLY) TESTS YOUR SERVICE'S ABILITY TO !
! USE THAT SERVICE. THINK OF IT AS A CONTINUOUS INTEGRATION TEST. !
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
""")
}
}

private def annotatedMethods(resource: Object) =
new MethodList(resource.getClass, true).hasMetaAnnotation(classOf[HttpMethod])
}
22 changes: 22 additions & 0 deletions src/main/scala/com/yammer/dropwizard/JerseyConfig.scala
@@ -0,0 +1,22 @@
package com.yammer.dropwizard

import com.sun.jersey.api.core.DefaultResourceConfig
import com.codahale.logula.Logging
import providers.LoggingExceptionMapper
import com.codahale.jersey.inject.ScalaCollectionsQueryParamInjectableProvider
import com.codahale.jersey.providers.{JValueProvider, JsonCaseClassProvider}

class JerseyConfig(env: Environment) extends DefaultResourceConfig with Logging {
(
Set(
new LoggingExceptionMapper,
new JsonCaseClassProvider,
new ScalaCollectionsQueryParamInjectableProvider,
new JValueProvider
) ++ env.resources ++ env.providers
).foreach(getSingletons.add)

override def validate() {
env.validate()
}
}
29 changes: 5 additions & 24 deletions src/main/scala/com/yammer/dropwizard/Service.scala
@@ -1,48 +1,29 @@
package com.yammer.dropwizard

import collection.{immutable, mutable}
import lifecycle.Managed
import modules.{GuiceMultibindingModule, ServerModule, RequestLogHandlerModule}
import collection.immutable
import util.JarAware
import com.codahale.logula.Logging
import com.google.inject.Module
import com.yammer.dropwizard.cli.{ServerCommand, Command}
import com.yammer.metrics.guice.InstrumentationModule
import com.yammer.metrics.core.{DeadlockHealthCheck, HealthCheck}
import com.codahale.fig.Configuration

trait Service extends Logging with JarAware {
private val modules = new mutable.ArrayBuffer[Module]() ++ Seq(
new RequestLogHandlerModule,
new ServerModule,
new InstrumentationModule
)
protected def require(modules: Module*) { this.modules ++= modules }

private var commands = new immutable.TreeMap[String, Command]
protected def provide(commands: Command*) { commands.foreach { c => this.commands += c.name -> c } }
provide(new ServerCommand(this))

protected def healthCheck[A <: HealthCheck](implicit mf: Manifest[A]) {
modules += new GuiceMultibindingModule(classOf[HealthCheck], mf.erasure.asInstanceOf[Class[HealthCheck]])
}
healthCheck[DeadlockHealthCheck]

protected def manage[A <: Managed](implicit mf: Manifest[A]) {
modules += new GuiceMultibindingModule(classOf[Managed], mf.erasure.asInstanceOf[Class[Managed]])
}

def name: String

def banner: Option[String] = None

def configure(implicit config: Configuration, environment: Environment)

private def printUsage(error: Option[String] = None) {
for (msg <- error) {
System.err.printf("%s\n\n", msg)
}

printf("%s <command> [arg1 arg2]\n\n", jarSyntax)


println("Commands")
println("========\n")
for (cmd <- commands.values) {
Expand All @@ -57,7 +38,7 @@ trait Service extends Logging with JarAware {
case command :: args => {
commands.get(command) match {
case Some(cmd) => {
cmd.execute(jarSyntax, modules.toSeq, args)
cmd.execute(this, jarSyntax, args)
}
case None => printUsage(Some("Unrecognized command: " + command))
}
Expand Down
18 changes: 4 additions & 14 deletions src/main/scala/com/yammer/dropwizard/cli/Command.scala
@@ -1,14 +1,9 @@
package com.yammer.dropwizard.cli

import collection.mutable
import com.google.inject._
import org.apache.commons.cli.{ParseException, GnuParser, Options, HelpFormatter, Option => ApacheOption, OptionGroup}
import com.yammer.dropwizard.Service

trait Command {
private val modules = new mutable.ArrayBuffer[Module]

protected def require(modules: Module*) {this.modules ++= modules}

def name: String

def description: Option[String] = None
Expand All @@ -32,10 +27,8 @@ trait Command {
opts
}

def execute(jarSyntax: String, modules: Seq[Module], args: Seq[String]) {
def execute(service: Service, jarSyntax: String, args: Seq[String]) {
try {
this.modules ++= modules

if (args == Seq("-h") || args == Seq("--help")) {
printUsage(jarSyntax, None)
} else {
Expand All @@ -46,28 +39,25 @@ trait Command {
Option(o.getValues).getOrElse(Array.empty[String]).toList
}.toMap

run(opts, cmdLine.getArgs.toList).foreach { e =>
run(service, opts, cmdLine.getArgs.toList).foreach { e =>
printUsage(jarSyntax, Some(e))
}
}
} catch {
case e: ParseException => {
printUsage(jarSyntax, Some(e.getMessage))
}
case e: CreationException => System.err.println(e.getMessage)
case e => {
e.printStackTrace()
}
}
}

def run(opts: Map[String, List[String]], args: List[String]): Option[String]
def run(service: Service, opts: Map[String, List[String]], args: List[String]): Option[String]

protected def commandSyntax(jarSyntax: String) =
"%s %s [options] [arguments]".format(jarSyntax, name)

protected lazy val injector = Guice.createInjector(Stage.PRODUCTION, modules: _*)

def printUsage(jarSyntax: String, error: Option[String] = None) {
for (msg <- error) {
System.err.printf("%s\n\n", msg)
Expand Down
12 changes: 7 additions & 5 deletions src/main/scala/com/yammer/dropwizard/cli/ConfiguredCommand.scala
Expand Up @@ -2,20 +2,22 @@ package com.yammer.dropwizard.cli

import java.io.File
import com.codahale.jerkson.ParsingException
import com.yammer.dropwizard.modules.ConfigurationModule
import com.codahale.logula.Logging
import com.yammer.dropwizard.config.ConfigurationFactory
import com.codahale.fig.Configuration
import com.yammer.dropwizard.Service

trait ConfiguredCommand extends Command with Logging {
override protected def commandSyntax(jarSyntax: String) =
"%s %s [options] <config file> [argumemts]".format(jarSyntax, name)

final def run(opts: Map[String, List[String]], args: List[String]) = args match {
final def run(service: Service, opts: Map[String, List[String]], args: List[String]) = args match {
case filename :: others => {
val f = new File(filename)
if (f.exists && f.isFile) {
try {
require(new ConfigurationModule(filename))
runWithConfigFile(opts, others)
val config = ConfigurationFactory.buildConfiguration(filename)
run(service, config, opts, others)
} catch {
case e: ParsingException => Some("Bad configuration file: " + e.getMessage)
case e => Some("Error: " + e.getMessage)
Expand All @@ -27,5 +29,5 @@ trait ConfiguredCommand extends Command with Logging {
case Nil => Some("no configuration file specified")
}

def runWithConfigFile(opts: Map[String, List[String]], args: List[String]): Option[String]
def run(service: Service, config: Configuration, opts: Map[String, List[String]], args: List[String]): Option[String]
}

0 comments on commit 6e1764f

Please sign in to comment.