diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..0e1ccc0 --- /dev/null +++ b/build.sbt @@ -0,0 +1,96 @@ +import scala.xml.Group + +val manifestSetting = packageOptions += { + Package.ManifestAttributes( + "Created-By" -> "Simple Build Tool", + "Built-By" -> System.getProperty("user.name"), + "Build-Jdk" -> System.getProperty("java.version"), + "Specification-Title" -> name.value, + "Specification-Version" -> version.value, + "Specification-Vendor" -> organization.value, + "Implementation-Title" -> name.value, + "Implementation-Version" -> version.value, + "Implementation-Vendor-Id" -> organization.value, + "Implementation-Vendor" -> organization.value + ) +} + +val publishSettings: Seq[Setting[_]] = Seq( + publishTo := { + val res = + if (version.value.trim.endsWith("SNAPSHOT")) + Opts.resolver.sonatypeSnapshots + else + Opts.resolver.sonatypeStaging + Some(res) + }, + publishMavenStyle := true, + publishArtifact in Test := false, + pomIncludeRepository := { x => false } +) + +val mavenCentralFrouFrou = Seq( + homepage := Some(new URL("http://swagger.io")), + startYear := Some(2009), + licenses := Seq(("ASL", new URL("http://github.com/swagger-api/swagger-async-httpclient/raw/HEAD/LICENSE"))), + pomExtra := pomExtra.value ++ Group( + + http://github.com/swagger-api/swagger-async-httpclient + scm:git:git://github.com/swagger-api/swagger-async-httpclient.git + + + + casualjim + Ivan Porto Carrero + http://flanders.co.nz/ + + + ) +) + +val projectSettings = Seq( + organization := "io.swagger", + name := "swagger-async-httpclient", + scalaVersion := "2.11.12", + scalacOptions ++= Seq("-unchecked", "-deprecation", "-optimize", "-Xcheckinit", "-encoding", "utf8"), + javacOptions in compile ++= Seq("-Xlint:deprecation"), + manifestSetting, + autoCompilerPlugins := true, + parallelExecution in Test := false, + commands += Command.args("s", "") { (state, args) => + args.mkString(" ") ! state.log + state + }, + TaskKey[Unit]("gc", "runs garbage collector") := { + streams.value.log.info("requesting garbage collection") + System.gc() + } +) + +val buildInfoConfig: Seq[Setting[_]] = Seq( + buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion), + buildInfoPackage := organization(_ + ".client.async").value +) + +val defaultSettings = + Defaults.coreDefaultSettings ++ buildInfoConfig ++ projectSettings ++ publishSettings ++ mavenCentralFrouFrou + + +lazy val root = Project( + id = "swagger-async-httpclient", + base = file("."), + settings = defaultSettings ++ Seq( + libraryDependencies ++= Seq( + "org.scalatra.rl" %% "rl" % "0.4.10", + "org.slf4j" % "slf4j-api" % "1.7.25", + "ch.qos.logback" % "logback-classic" % "1.2.3" % "provided", + "org.json4s" %% "json4s-jackson" % "3.5.3", + "com.googlecode.juniversalchardet" % "juniversalchardet" % "1.0.3", + "eu.medsea.mimeutil" % "mime-util" % "2.1.3" exclude("org.slf4j", "slf4j-log4j12") exclude("log4j", "log4j"), + "com.ning" % "async-http-client" % "1.8.17", + "com.typesafe.scala-logging" %% "scala-logging" % "3.7.2" + ) + ) +) + +enablePlugins(BuildInfoPlugin) diff --git a/project/build.properties b/project/build.properties index 5075289..8b697bb 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.5 \ No newline at end of file +sbt.version=1.1.0 diff --git a/project/build.scala b/project/build.scala deleted file mode 100644 index 51bca07..0000000 --- a/project/build.scala +++ /dev/null @@ -1,136 +0,0 @@ -import sbt._ -import Keys._ -import sbtrelease.ReleasePlugin._ -import sbtbuildinfo.Plugin._ -import scala.xml.Group - -object build extends Build { - - val manifestSetting = packageOptions <+= (name, version, organization) map { - (title, version, vendor) => - Package.ManifestAttributes( - "Created-By" -> "Simple Build Tool", - "Built-By" -> System.getProperty("user.name"), - "Build-Jdk" -> System.getProperty("java.version"), - "Specification-Title" -> title, - "Specification-Version" -> version, - "Specification-Vendor" -> vendor, - "Implementation-Title" -> title, - "Implementation-Version" -> version, - "Implementation-Vendor-Id" -> vendor, - "Implementation-Vendor" -> vendor) - } - - val publishSettings: Seq[Setting[_]] = Seq( - publishTo <<= (version) { version: String => - val res = - if (version.trim.endsWith("SNAPSHOT")) - Opts.resolver.sonatypeSnapshots - else - Opts.resolver.sonatypeStaging - Some(res) - }, - publishMavenStyle := true, - publishArtifact in Test := false, - pomIncludeRepository := { x => false } - ) - - val mavenCentralFrouFrou = Seq( - homepage := Some(new URL("http://swagger.io")), - startYear := Some(2009), - licenses := Seq(("ASL", new URL("http://github.com/swagger-api/swagger-async-httpclient/raw/HEAD/LICENSE"))), - pomExtra <<= (pomExtra, name, description) {(pom, name, desc) => pom ++ Group( - - http://github.com/swagger-api/swagger-async-httpclient - scm:git:git://github.com/swagger-api/swagger-async-httpclient.git - - - - casualjim - Ivan Porto Carrero - http://flanders.co.nz/ - - - )} - ) - - def versionSpecificSourcesIn(c: Configuration) = - unmanagedSourceDirectories in c <+= (scalaVersion, sourceDirectory in c) { - case (v, dir) if v startsWith "2.9" => dir / "scala_2.9" - case (v, dir) if v startsWith "2.10" => dir / "scala_2.10" - case (v, dir) if v startsWith "2.11" => dir / "scala_2.11" - } - - val projectSettings = Seq( - organization := "io.swagger", - name := "swagger-async-httpclient", - scalaVersion := "2.11.7", - crossScalaVersions := Seq("2.10.4", "2.11.7"), - scalacOptions ++= Seq("-unchecked", "-deprecation", "-optimize", "-Xcheckinit", "-encoding", "utf8"), - scalacOptions <++= scalaVersion map { - case v if v.startsWith("2.9") || v.startsWith("2.10") => Seq("-P:continuations:enable") - case _ => Seq.empty - }, - scalacOptions in Compile <++= scalaVersion map ({ - case v if v startsWith "2.10" => Seq("-language:implicitConversions", "-language:reflectiveCalls") - case _ => Seq.empty - }), - javacOptions in compile ++= Seq("-target", "1.6", "-source", "1.6", "-Xlint:deprecation"), - manifestSetting, - autoCompilerPlugins := true, - libraryDependencies <++= scalaVersion { - case v if v.startsWith("2.9") || v.startsWith("2.10") => Seq(compilerPlugin("org.scala-lang.plugins" % "continuations" % v)) - case _ => Seq.empty - }, - parallelExecution in Test := false, - commands += Command.args("s", "") { (state, args) => - args.mkString(" ") ! state.log - state - }, - TaskKey[Unit]("gc", "runs garbage collector") <<= streams map { s => - s.log.info("requesting garbage collection") - System.gc() - } - ) - - val buildInfoConfig: Seq[Setting[_]] = buildInfoSettings ++ Seq( - sourceGenerators in Compile <+= buildInfo, - buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion), - buildInfoPackage <<= organization(_ + ".client.async") - ) - - val defaultSettings = - Defaults.defaultSettings ++ releaseSettings ++ buildInfoConfig ++ projectSettings ++ publishSettings ++ mavenCentralFrouFrou - - - lazy val root = Project( - id = "swagger-async-httpclient", - base = file("."), - settings = defaultSettings ++ Seq( - libraryDependencies ++= Seq( - "org.scalatra.rl" %% "rl" % "0.4.10", - "org.slf4j" % "slf4j-api" % "1.7.7", - "ch.qos.logback" % "logback-classic" % "1.1.2" % "provided", - "org.json4s" %% "json4s-jackson" % "3.2.10", - "com.googlecode.juniversalchardet" % "juniversalchardet" % "1.0.3", - "eu.medsea.mimeutil" % "mime-util" % "2.1.3" exclude("org.slf4j", "slf4j-log4j12") exclude("log4j", "log4j"), - "com.ning" % "async-http-client" % "1.8.14" - ), - libraryDependencies <+= scalaVersion { - case "2.9.3" => "org.clapper" % "grizzled-slf4j_2.9.2" % "0.6.10" exclude("org.scala-lang", "scala-library") - case v if v startsWith "2.9" => "org.clapper" %% "grizzled-slf4j" % "0.6.10" - case v if v startsWith "2.10" => "com.typesafe.scala-logging" %% "scala-logging-slf4j" % "2.1.2" - case v => "com.typesafe.scala-logging" %% "scala-logging" % "3.1.0" - }, - libraryDependencies <++= scalaVersion { - case v if v startsWith "2.9" => Seq("com.typesafe.akka" % "akka-actor" % "2.0.5") - case v => Seq.empty - }, - resolvers <++= scalaVersion { - case v if v startsWith "2.9" => Seq("Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/") - case v => Seq.empty - }, - versionSpecificSourcesIn(Compile) - ) - ) -} diff --git a/project/plugins.sbt b/project/plugins.sbt index 825fd38..7747411 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,3 @@ -addSbtPlugin("com.github.gseitz" % "sbt-release" % "0.8.5") - -addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.3.2") - -addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.6") +addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.7") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.7.0") +addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.3.4") diff --git a/sbt b/sbt index 08e5882..218f371 100755 --- a/sbt +++ b/sbt @@ -1,25 +1,100 @@ #!/usr/bin/env bash # # A more capable sbt runner, coincidentally also called sbt. -# Author: Paul Phillips +# Author: Paul Phillips + +set -o pipefail + +declare -r sbt_release_version="0.13.16" +declare -r sbt_unreleased_version="0.13.16" + +declare -r latest_213="2.13.0-M2" +declare -r latest_212="2.12.4" +declare -r latest_211="2.11.12" +declare -r latest_210="2.10.7" +declare -r latest_29="2.9.3" +declare -r latest_28="2.8.2" -# todo - make this dynamic -declare -r sbt_release_version="0.13.6" -declare -r sbt_unreleased_version="0.13.6" declare -r buildProps="project/build.properties" -declare sbt_jar sbt_dir sbt_create sbt_version -declare scala_version sbt_explicit_version -declare verbose noshare batch trace_level log_level -declare sbt_saved_stty debugUs +declare -r sbt_launch_ivy_release_repo="http://repo.typesafe.com/typesafe/ivy-releases" +declare -r sbt_launch_ivy_snapshot_repo="https://repo.scala-sbt.org/scalasbt/ivy-snapshots" +declare -r sbt_launch_mvn_release_repo="http://repo.scala-sbt.org/scalasbt/maven-releases" +declare -r sbt_launch_mvn_snapshot_repo="http://repo.scala-sbt.org/scalasbt/maven-snapshots" + +declare -r default_jvm_opts_common="-Xms512m -Xmx1536m -Xss2m" +declare -r noshare_opts="-Dsbt.global.base=project/.sbtboot -Dsbt.boot.directory=project/.boot -Dsbt.ivy.home=project/.ivy" + +declare sbt_jar sbt_dir sbt_create sbt_version sbt_script sbt_new +declare sbt_explicit_version +declare verbose noshare batch trace_level +declare debugUs + +declare java_cmd="java" +declare sbt_launch_dir="$HOME/.sbt/launchers" +declare sbt_launch_repo + +# pull -J and -D options to give to java. +declare -a java_args scalac_args sbt_commands residual_args + +# args to jvm/sbt via files or environment variables +declare -a extra_jvm_opts extra_sbt_opts echoerr () { echo >&2 "$@"; } vlog () { [[ -n "$verbose" ]] && echoerr "$@"; } +die () { echo "Aborting: $@" ; exit 1; } + +setTrapExit () { + # save stty and trap exit, to ensure echo is re-enabled if we are interrupted. + export SBT_STTY="$(stty -g 2>/dev/null)" + + # restore stty settings (echo in particular) + onSbtRunnerExit() { + [ -t 0 ] || return + vlog "" + vlog "restoring stty: $SBT_STTY" + stty "$SBT_STTY" + } + + vlog "saving stty: $SBT_STTY" + trap onSbtRunnerExit EXIT +} + +# this seems to cover the bases on OSX, and someone will +# have to tell me about the others. +get_script_path () { + local path="$1" + [[ -L "$path" ]] || { echo "$path" ; return; } + + local target="$(readlink "$path")" + if [[ "${target:0:1}" == "/" ]]; then + echo "$target" + else + echo "${path%/*}/$target" + fi +} + +declare -r script_path="$(get_script_path "$BASH_SOURCE")" +declare -r script_name="${script_path##*/}" + +init_default_option_file () { + local overriding_var="${!1}" + local default_file="$2" + if [[ ! -r "$default_file" && "$overriding_var" =~ ^@(.*)$ ]]; then + local envvar_file="${BASH_REMATCH[1]}" + if [[ -r "$envvar_file" ]]; then + default_file="$envvar_file" + fi + fi + echo "$default_file" +} + +declare sbt_opts_file="$(init_default_option_file SBT_OPTS .sbtopts)" +declare jvm_opts_file="$(init_default_option_file JVM_OPTS .jvmopts)" -# spaces are possible, e.g. sbt.version = 0.13.0 build_props_sbt () { [[ -r "$buildProps" ]] && \ - grep '^sbt\.version' "$buildProps" | tr '=' ' ' | awk '{ print $2; }' + grep '^sbt\.version' "$buildProps" | tr '=\r' ' ' | awk '{ print $2; }' } update_build_props_sbt () { @@ -43,104 +118,87 @@ set_sbt_version () { export sbt_version } -# restore stty settings (echo in particular) -onSbtRunnerExit() { - [[ -n "$sbt_saved_stty" ]] || return - vlog "" - vlog "restoring stty: $sbt_saved_stty" - stty "$sbt_saved_stty" - unset sbt_saved_stty -} - -# save stty and trap exit, to ensure echo is reenabled if we are interrupted. -trap onSbtRunnerExit EXIT -sbt_saved_stty="$(stty -g 2>/dev/null)" -vlog "Saved stty: $sbt_saved_stty" +url_base () { + local version="$1" -# this seems to cover the bases on OSX, and someone will -# have to tell me about the others. -get_script_path () { - local path="$1" - [[ -L "$path" ]] || { echo "$path" ; return; } - - local target="$(readlink "$path")" - if [[ "${target:0:1}" == "/" ]]; then - echo "$target" - else - echo "${path%/*}/$target" - fi -} - -die() { - echo "Aborting: $@" - exit 1 + case "$version" in + 0.7.*) echo "http://simple-build-tool.googlecode.com" ;; + 0.10.* ) echo "$sbt_launch_ivy_release_repo" ;; + 0.11.[12]) echo "$sbt_launch_ivy_release_repo" ;; + 0.*-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]) # ie "*-yyyymmdd-hhMMss" + echo "$sbt_launch_ivy_snapshot_repo" ;; + 0.*) echo "$sbt_launch_ivy_release_repo" ;; + *-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]) # ie "*-yyyymmdd-hhMMss" + echo "$sbt_launch_mvn_snapshot_repo" ;; + *) echo "$sbt_launch_mvn_release_repo" ;; + esac } make_url () { - version="$1" + local version="$1" + + local base="${sbt_launch_repo:-$(url_base "$version")}" case "$version" in - 0.7.*) echo "http://simple-build-tool.googlecode.com/files/sbt-launch-0.7.7.jar" ;; - 0.10.* ) echo "$sbt_launch_repo/org.scala-tools.sbt/sbt-launch/$version/sbt-launch.jar" ;; - 0.11.[12]) echo "$sbt_launch_repo/org.scala-tools.sbt/sbt-launch/$version/sbt-launch.jar" ;; - *) echo "$sbt_launch_repo/org.scala-sbt/sbt-launch/$version/sbt-launch.jar" ;; + 0.7.*) echo "$base/files/sbt-launch-0.7.7.jar" ;; + 0.10.* ) echo "$base/org.scala-tools.sbt/sbt-launch/$version/sbt-launch.jar" ;; + 0.11.[12]) echo "$base/org.scala-tools.sbt/sbt-launch/$version/sbt-launch.jar" ;; + 0.*) echo "$base/org.scala-sbt/sbt-launch/$version/sbt-launch.jar" ;; + *) echo "$base/org/scala-sbt/sbt-launch/$version/sbt-launch.jar" ;; esac } -init_default_option_file () { - local overriding_var="${!1}" - local default_file="$2" - if [[ ! -r "$default_file" && "$overriding_var" =~ ^@(.*)$ ]]; then - local envvar_file="${BASH_REMATCH[1]}" - if [[ -r "$envvar_file" ]]; then - default_file="$envvar_file" - fi - fi - echo "$default_file" -} - -declare -r cms_opts="-XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC" -declare -r jit_opts="-XX:ReservedCodeCacheSize=256m -XX:+TieredCompilation" -declare -r default_jvm_opts_common="-Xms512m -Xmx1536m -Xss2m $jit_opts $cms_opts" -declare -r noshare_opts="-Dsbt.global.base=project/.sbtboot -Dsbt.boot.directory=project/.boot -Dsbt.ivy.home=project/.ivy" -declare -r latest_28="2.8.2" -declare -r latest_29="2.9.3" -declare -r latest_210="2.10.4" -declare -r latest_211="2.11.2" +addJava () { vlog "[addJava] arg = '$1'" ; java_args+=("$1"); } +addSbt () { vlog "[addSbt] arg = '$1'" ; sbt_commands+=("$1"); } +addScalac () { vlog "[addScalac] arg = '$1'" ; scalac_args+=("$1"); } +addResidual () { vlog "[residual] arg = '$1'" ; residual_args+=("$1"); } -declare -r script_path="$(get_script_path "$BASH_SOURCE")" -declare -r script_name="${script_path##*/}" - -# some non-read-onlies set with defaults -declare java_cmd="java" -declare sbt_opts_file="$(init_default_option_file SBT_OPTS .sbtopts)" -declare jvm_opts_file="$(init_default_option_file JVM_OPTS .jvmopts)" -declare sbt_launch_repo="http://typesafe.artifactoryonline.com/typesafe/ivy-releases" +addResolver () { addSbt "set resolvers += $1"; } +addDebugger () { addJava "-Xdebug" ; addJava "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=$1"; } +setThisBuild () { + vlog "[addBuild] args = '$@'" + local key="$1" && shift + addSbt "set $key in ThisBuild := $@" +} +setScalaVersion () { + [[ "$1" == *"-SNAPSHOT" ]] && addResolver 'Resolver.sonatypeRepo("snapshots")' + addSbt "++ $1" +} +setJavaHome () { + java_cmd="$1/bin/java" + setThisBuild javaHome "_root_.scala.Some(file(\"$1\"))" + export JAVA_HOME="$1" + export JDK_HOME="$1" + export PATH="$JAVA_HOME/bin:$PATH" +} -# pull -J and -D options to give to java. -declare -a residual_args -declare -a java_args -declare -a scalac_args -declare -a sbt_commands +getJavaVersion() { "$1" -version 2>&1 | grep -E -e '(java|openjdk) version' | awk '{ print $3 }' | tr -d \"; } -# args to jvm/sbt via files or environment variables -declare -a extra_jvm_opts extra_sbt_opts +checkJava() { + # Warn if there is a Java version mismatch between PATH and JAVA_HOME/JDK_HOME -# if set, use JAVA_HOME over java found in path -[[ -e "$JAVA_HOME/bin/java" ]] && java_cmd="$JAVA_HOME/bin/java" + [[ -n "$JAVA_HOME" && -e "$JAVA_HOME/bin/java" ]] && java="$JAVA_HOME/bin/java" + [[ -n "$JDK_HOME" && -e "$JDK_HOME/lib/tools.jar" ]] && java="$JDK_HOME/bin/java" -# directory to store sbt launchers -declare sbt_launch_dir="$HOME/.sbt/launchers" -[[ -d "$sbt_launch_dir" ]] || mkdir -p "$sbt_launch_dir" -[[ -w "$sbt_launch_dir" ]] || sbt_launch_dir="$(mktemp -d -t sbt_extras_launchers.XXXXXX)" + if [[ -n "$java" ]]; then + pathJavaVersion=$(getJavaVersion java) + homeJavaVersion=$(getJavaVersion "$java") + if [[ "$pathJavaVersion" != "$homeJavaVersion" ]]; then + echoerr "Warning: Java version mismatch between PATH and JAVA_HOME/JDK_HOME, sbt will use the one in PATH" + echoerr " Either: fix your PATH, remove JAVA_HOME/JDK_HOME or use -java-home" + echoerr " java version from PATH: $pathJavaVersion" + echoerr " java version from JAVA_HOME/JDK_HOME: $homeJavaVersion" + fi + fi +} java_version () { - local version=$("$java_cmd" -version 2>&1 | grep -e 'java version' | awk '{ print $3 }' | tr -d \") + local version=$(getJavaVersion "$java_cmd") vlog "Detected Java version: $version" echo "${version:2:1}" } -# MaxPermSize critical on pre-8 jvms but incurs noisy warning on 8+ +# MaxPermSize critical on pre-8 JVMs but incurs noisy warning on 8+ default_jvm_opts () { local v="$(java_version)" if [[ $v -ge 8 ]]; then @@ -173,16 +231,23 @@ execRunner () { vlog "" } - [[ -n "$batch" ]] && exec /dev/null; then - curl --fail --silent "$url" --output "$jar" + curl --fail --silent --location "$url" --output "$jar" elif which wget >/dev/null; then - wget --quiet -O "$jar" "$url" + wget -q -O "$jar" "$url" fi } && [[ -r "$jar" ]] } acquire_sbt_jar () { - sbt_url="$(jar_url "$sbt_version")" - sbt_jar="$(jar_file "$sbt_version")" - - [[ -r "$sbt_jar" ]] || download_url "$sbt_url" "$sbt_jar" + { + sbt_jar="$(jar_file "$sbt_version")" + [[ -r "$sbt_jar" ]] + } || { + sbt_jar="$HOME/.ivy2/local/org.scala-sbt/sbt-launch/$sbt_version/jars/sbt-launch.jar" + [[ -r "$sbt_jar" ]] + } || { + sbt_jar="$(jar_file "$sbt_version")" + download_url "$(make_url "$sbt_version")" "$sbt_jar" + } } usage () { + set_sbt_version cat < Turn on JVM debugging, open at the given port. -batch Disable interactive mode -prompt Set the sbt prompt; in expr, 's' is the State and 'e' is Extracted + -script Run the specified file as a scala script # sbt version (default: sbt.version from $buildProps if present, otherwise $sbt_release_version) -sbt-force-latest force the use of the latest release of sbt: $sbt_release_version -sbt-version use the specified version of sbt (default: $sbt_release_version) -sbt-dev use the latest pre-release version of sbt: $sbt_unreleased_version -sbt-jar use the specified jar as the sbt launcher - -sbt-launch-dir directory to hold sbt launchers (default: ~/.sbt/launchers) - -sbt-launch-repo repo url for downloading sbt launcher jar (default: $sbt_launch_repo) + -sbt-launch-dir directory to hold sbt launchers (default: $sbt_launch_dir) + -sbt-launch-repo repo url for downloading sbt launcher jar (default: $(url_base "$sbt_version")) # scala version (default: as chosen by sbt) -28 use $latest_28 -29 use $latest_29 -210 use $latest_210 -211 use $latest_211 + -212 use $latest_212 + -213 use $latest_213 -scala-home use the scala build at the specified directory -scala-version use the specified version of scala -binary-version use the specified scala version when searching for dependencies @@ -280,42 +355,7 @@ runner with the -x option. EOM } -addJava () { - vlog "[addJava] arg = '$1'" - java_args=( "${java_args[@]}" "$1" ) -} -addSbt () { - vlog "[addSbt] arg = '$1'" - sbt_commands=( "${sbt_commands[@]}" "$1" ) -} -setThisBuild () { - vlog "[addBuild] args = '$@'" - local key="$1" && shift - addSbt "set $key in ThisBuild := $@" -} - -addScalac () { - vlog "[addScalac] arg = '$1'" - scalac_args=( "${scalac_args[@]}" "$1" ) -} -addResidual () { - vlog "[residual] arg = '$1'" - residual_args=( "${residual_args[@]}" "$1" ) -} -addResolver () { - addSbt "set resolvers += $1" -} -addDebugger () { - addJava "-Xdebug" - addJava "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=$1" -} -setScalaVersion () { - [[ "$1" == *"-SNAPSHOT" ]] && addResolver 'Resolver.sonatypeRepo("snapshots")' - addSbt "++ $1" -} - -process_args () -{ +process_args () { require_arg () { local type="$1" local opt="$2" @@ -327,10 +367,10 @@ process_args () } while [[ $# -gt 0 ]]; do case "$1" in - -h|-help) usage; exit 1 ;; + -h|-help) usage; exit 0 ;; -v) verbose=true && shift ;; -d) addSbt "--debug" && shift ;; - -w) addSbt "--warn" && shift ;; + -w) addSbt "--warn" && shift ;; -q) addSbt "--error" && shift ;; -x) debugUs=true && shift ;; -trace) require_arg integer "$1" "$2" && trace_level="$2" && shift 2 ;; @@ -340,10 +380,11 @@ process_args () -sbt-boot) require_arg path "$1" "$2" && addJava "-Dsbt.boot.directory=$2" && shift 2 ;; -sbt-dir) require_arg path "$1" "$2" && sbt_dir="$2" && shift 2 ;; -debug-inc) addJava "-Dxsbt.inc.debug=true" && shift ;; - -offline) addSbt "set offline := true" && shift ;; + -offline) addSbt "set offline in Global := true" && shift ;; -jvm-debug) require_arg port "$1" "$2" && addDebugger "$2" && shift 2 ;; -batch) batch=true && shift ;; -prompt) require_arg "expr" "$1" "$2" && setThisBuild shellPrompt "(s => { val e = Project.extract(s) ; $2 })" && shift 2 ;; + -script) require_arg file "$1" "$2" && sbt_script="$2" && addJava "-Dsbt.main.class=sbt.ScriptMain" && shift 2 ;; -sbt-create) sbt_create=true && shift ;; -sbt-jar) require_arg path "$1" "$2" && sbt_jar="$2" && shift 2 ;; @@ -354,8 +395,8 @@ process_args () -sbt-launch-repo) require_arg path "$1" "$2" && sbt_launch_repo="$2" && shift 2 ;; -scala-version) require_arg version "$1" "$2" && setScalaVersion "$2" && shift 2 ;; -binary-version) require_arg version "$1" "$2" && setThisBuild scalaBinaryVersion "\"$2\"" && shift 2 ;; - -scala-home) require_arg path "$1" "$2" && setThisBuild scalaHome "Some(file(\"$2\"))" && shift 2 ;; - -java-home) require_arg path "$1" "$2" && java_cmd="$2/bin/java" && shift 2 ;; + -scala-home) require_arg path "$1" "$2" && setThisBuild scalaHome "_root_.scala.Some(file(\"$2\"))" && shift 2 ;; + -java-home) require_arg path "$1" "$2" && setJavaHome "$2" && shift 2 ;; -sbt-opts) require_arg path "$1" "$2" && sbt_opts_file="$2" && shift 2 ;; -jvm-opts) require_arg path "$1" "$2" && jvm_opts_file="$2" && shift 2 ;; @@ -366,7 +407,9 @@ process_args () -29) setScalaVersion "$latest_29" && shift ;; -210) setScalaVersion "$latest_210" && shift ;; -211) setScalaVersion "$latest_211" && shift ;; - + -212) setScalaVersion "$latest_212" && shift ;; + -213) setScalaVersion "$latest_213" && shift ;; + new) sbt_new=true && : ${sbt_explicit_version:=$sbt_release_version} && addResidual "$1" && shift ;; *) addResidual "$1" && shift ;; esac done @@ -377,8 +420,10 @@ process_args "$@" # skip #-styled comments and blank lines readConfigFile() { - while read line; do - [[ $line =~ ^# ]] || [[ -z $line ]] || echo "$line" + local end=false + until $end; do + read || end=true + [[ $REPLY =~ ^# ]] || [[ -z $REPLY ]] || echo "$REPLY" done < "$1" } @@ -403,6 +448,8 @@ argumentCount=$# # set sbt version set_sbt_version +checkJava + # only exists in 0.12+ setTraceLevel() { case "$sbt_version" in @@ -415,19 +462,21 @@ setTraceLevel() { [[ ${#scalac_args[@]} -eq 0 ]] || addSbt "set scalacOptions in ThisBuild += \"${scalac_args[@]}\"" # Update build.properties on disk to set explicit version - sbt gives us no choice -[[ -n "$sbt_explicit_version" ]] && update_build_props_sbt "$sbt_explicit_version" +[[ -n "$sbt_explicit_version" && -z "$sbt_new" ]] && update_build_props_sbt "$sbt_explicit_version" vlog "Detected sbt version $sbt_version" -[[ -n "$scala_version" ]] && vlog "Overriding scala version to $scala_version" - -# no args - alert them there's stuff in here -(( argumentCount > 0 )) || { - vlog "Starting $script_name: invoke with -help for other options" - residual_args=( shell ) -} +if [[ -n "$sbt_script" ]]; then + residual_args=( $sbt_script ${residual_args[@]} ) +else + # no args - alert them there's stuff in here + (( argumentCount > 0 )) || { + vlog "Starting $script_name: invoke with -help for other options" + residual_args=( shell ) + } +fi -# verify this is an sbt dir or -create was given -[[ -r ./build.sbt || -d ./project || -n "$sbt_create" ]] || { +# verify this is an sbt dir, -create was given or user attempts to run a scala script +[[ -r ./build.sbt || -d ./project || -n "$sbt_create" || -n "$sbt_script" || -n "$sbt_new" ]] || { cat < T): Future[T] = { - val fut = Promise[T]() - try { - val r = fn - r match { - case t: Throwable => fut.complete(Failure(t)) - case s => fut.complete(Success(s)) - } - } catch { - case t: Throwable => fut.complete(Failure(t)) - } - fut.future - } -} diff --git a/src/main/scala_2.11/io/swagger/client/RestClient.scala b/src/main/scala_2.11/io/swagger/client/RestClient.scala deleted file mode 100644 index 464f085..0000000 --- a/src/main/scala_2.11/io/swagger/client/RestClient.scala +++ /dev/null @@ -1,446 +0,0 @@ -package io.swagger.client - -import java.io.File -import java.net.URI -import java.nio.charset.Charset -import java.text.SimpleDateFormat -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicLong -import java.util.{Date, Locale, TimeZone, concurrent => juc} - -import com.ning.http.client._ -import com.ning.http.client.cookie.{Cookie => AhcCookie} -import com.ning.http.client.providers.netty.NettyAsyncHttpProviderConfig -import io.swagger.client.async.BuildInfo -import org.jboss.netty.channel.socket.nio.{NioClientSocketChannelFactory, NioWorkerPool} -import org.jboss.netty.util.{HashedWheelTimer, Timer} -import rl.Imports._ -import rl.{MapQueryString, UrlCodingUtils} - -import scala.collection.JavaConverters._ -import scala.concurrent.duration._ -import scala.concurrent.{ExecutionContext, Future, Promise} -import scala.io.Codec -import scala.util.Failure - -object RestClient { - - private val threadIds = new AtomicLong() - - private lazy val factory = new juc.ThreadFactory { - def newThread(runnable: Runnable): Thread = { - val thread = new Thread(runnable) - thread.setName("swagger-client-thread-" + threadIds.incrementAndGet()) - thread.setDaemon(true) - thread - } - } - - val DefaultUserAgent = s"Reverb SwaggerClient / ${BuildInfo.version}" - - private implicit def stringWithExt(s: String) = new { - def isBlank = s == null || s.trim.isEmpty - def nonBlank = !isBlank - def blankOption = if (isBlank) None else Option(s) - } - - case class CookieOptions( domain : String = "", - path : String = "", - maxAge : Int = -1, - secure : Boolean = false, - comment : String = "", - httpOnly: Boolean = false, - version : Int = 0, - encoding: String = "UTF-8") - - trait HttpCookie { - implicit def cookieOptions: CookieOptions - def name: String - def value: String - - } - - case class RequestCookie(name: String, value: String, cookieOptions: CookieOptions = CookieOptions()) extends HttpCookie - - object DateUtil { - @volatile private[this] var _currentTimeMillis: Option[Long] = None - def currentTimeMillis = _currentTimeMillis getOrElse System.currentTimeMillis - def currentTimeMillis_=(ct: Long) = _currentTimeMillis = Some(ct) - def freezeTime() = _currentTimeMillis = Some(System.currentTimeMillis()) - def unfreezeTime() = _currentTimeMillis = None - def formatDate(date: Date, format: String, timeZone: TimeZone = TimeZone.getTimeZone("GMT")) = { - val df = new SimpleDateFormat(format) - df.setTimeZone(timeZone) - df.format(date) - } - } - - - case class Cookie(name: String, value: String)(implicit val cookieOptions: CookieOptions = CookieOptions()) extends HttpCookie { - - private def ensureDotDomain = - (if (!cookieOptions.domain.startsWith(".")) "." + cookieOptions.domain else cookieOptions.domain).toLowerCase(Locale.ENGLISH) - - def toCookieString = { - val sb = new StringBuffer - sb append name append "=" - sb append value - - if(cookieOptions.domain.nonBlank && cookieOptions.domain != "localhost") - sb.append("; Domain=").append(ensureDotDomain) - - val pth = cookieOptions.path - if(pth.nonBlank) sb append "; Path=" append (if(!pth.startsWith("/")) { - "/" + pth - } else { pth }) - - if(cookieOptions.comment.nonBlank) sb append ("; Comment=") append cookieOptions.comment - - appendMaxAge(sb, cookieOptions.maxAge, cookieOptions.version) - - if (cookieOptions.secure) sb append "; Secure" - if (cookieOptions.httpOnly) sb append "; HttpOnly" - sb.toString - } - private[this] def appendMaxAge(sb: StringBuffer, maxAge: Int, version: Int) = { - val dateInMillis = maxAge match { - case a if a < 0 => None // we don't do anything for max-age when it's < 0 then it becomes a session cookie - case 0 => Some(0L) // Set the date to the min date for the system - case a => Some(DateUtil.currentTimeMillis + a * 1000) - } - - // This used to be Max-Age but IE is not always very happy with that - // see: http://mrcoles.com/blog/cookies-max-age-vs-expires/ - // see Q1: http://blogs.msdn.com/b/ieinternals/archive/2009/08/20/wininet-ie-cookie-internals-faq.aspx - val bOpt = dateInMillis map (ms => appendExpires(sb, new Date(ms))) - val agedOpt = if (version > 0) bOpt map (_.append("; Max-Age=").append(maxAge)) else bOpt - agedOpt getOrElse sb - } - - private[this] def appendExpires(sb: StringBuffer, expires: Date) = - sb append "; Expires=" append formatExpires(expires) - - private[this] def formatExpires(date: Date) = DateUtil.formatDate(date, "EEE, dd MMM yyyy HH:mm:ss zzz") - - } - - - class CookieJar(private val reqCookies: Map[String, RequestCookie]) { - private val cookies = new ConcurrentHashMap[String, HttpCookie].asScala ++ reqCookies - - def get(key: String) = cookies.get(key) filter (_.cookieOptions.maxAge != 0) map (_.value) - - def apply(key: String) = get(key) getOrElse (throw new Exception("No cookie could be found for the specified key [%s]" format key)) - - def update(name: String, value: String)(implicit cookieOptions: CookieOptions=CookieOptions()) = { - cookies += name -> Cookie(name, value)(cookieOptions) - } - - def set(name: String, value: String)(implicit cookieOptions: CookieOptions=CookieOptions()) = { - this.update(name, value)(cookieOptions) - } - - def delete(name: String)(implicit cookieOptions: CookieOptions = CookieOptions(maxAge = 0)) { - this.update(name, "")(cookieOptions.copy(maxAge = 0)) - } - - def +=(keyValuePair: (String, String))(implicit cookieOptions: CookieOptions = CookieOptions()) = { - this.update(keyValuePair._1, keyValuePair._2)(cookieOptions) - } - - def -=(key: String)(implicit cookieOptions: CookieOptions = CookieOptions(maxAge = 0)) { - delete(key)(cookieOptions) - } - - def size = cookies.size - - def foreach[U](fn: (HttpCookie) => U) = cookies foreach { case (_, v) => fn(v) } - - private[client] def responseCookies = cookies.values collect { case c: Cookie => c } - - override def toString: String = cookies.toString() - } - - - class RestClientResponse(response: Response) extends ClientResponse { - val cookies = (response.getCookies.asScala map { cookie => - val cko = CookieOptions(cookie.getDomain, cookie.getPath, cookie.getMaxAge) - cookie.getName -> Cookie(cookie.getName, cookie.getValue)(cko) - }).toMap - - val headers = (response.getHeaders.keySet().asScala map { k => k -> response.getHeaders(k).asScala.toSeq}).toMap - - val status = ResponseStatus(response.getStatusCode, response.getStatusText) - - val contentType = response.getContentType - - val inputStream = response.getResponseBodyAsStream - - val uri = response.getUri - - def body = response.getResponseBody(charset getOrElse "UTF-8") - - def mediaType: Option[String] = headers.get("Content-Type") flatMap { _.headOption } - - def charset: Option[String] = - for { - ct <- mediaType - charset <- ct.split(";").drop(1).headOption - } yield charset.toUpperCase.replace("CHARSET=", "").trim - } - - trait Defaults { - def builder: AsyncHttpClientConfig.Builder - def timer: Timer - } - - private object InternalDefaults { - /** true if we think we're runing un-forked in an sbt-interactive session */ - val inTrapExit = ( - for (group ← Option(Thread.currentThread.getThreadGroup)) - yield group.getName == "trap.exit").getOrElse(false) - - /** Sets a user agent, no timeout for requests */ - object BasicDefaults extends Defaults { - lazy val timer = new HashedWheelTimer() - def builder = (new AsyncHttpClientConfig.Builder() - setAllowPoolingConnection true - setRequestTimeoutInMs 45000 - setCompressionEnabled true - setFollowRedirects false - setMaximumConnectionsPerHost 200 - setUserAgent DefaultUserAgent - setMaxRequestRetry 0) - } - - /** Uses daemon threads and tries to exit cleanly when running in sbt */ - object SbtProcessDefaults extends Defaults { - def builder = { - val shuttingDown = new juc.atomic.AtomicBoolean(false) - /** daemon threads that also shut down everything when interrupted! */ - lazy val interruptThreadFactory = new juc.ThreadFactory { - def newThread(runnable: Runnable) = { - new Thread(runnable) { - setDaemon(true) - setName("bragi-client-thread-" + threadIds.incrementAndGet()) - override def interrupt() { - shutdown() - super.interrupt() - } - } - } - } - lazy val nioClientSocketChannelFactory = { - val workerCount = 2 * Runtime.getRuntime().availableProcessors() - new NioClientSocketChannelFactory( - juc.Executors.newCachedThreadPool(interruptThreadFactory), - 1, - new NioWorkerPool( - juc.Executors.newCachedThreadPool(interruptThreadFactory), - workerCount), - timer) - } - - def shutdown() { - if (shuttingDown.compareAndSet(false, true)) { - nioClientSocketChannelFactory.releaseExternalResources() - } - } - - BasicDefaults.builder.setAsyncHttpClientProviderConfig( - new NettyAsyncHttpProviderConfig().addProperty( - NettyAsyncHttpProviderConfig.SOCKET_CHANNEL_FACTORY, - nioClientSocketChannelFactory)) - } - lazy val timer = new HashedWheelTimer(factory) - } - } -} - -class RestClient(config: SwaggerConfig) extends TransportClient with Logging { - - import io.swagger.client.RestClient._ - protected def underlying: Defaults = { - if (InternalDefaults.inTrapExit) InternalDefaults.SbtProcessDefaults - else InternalDefaults.BasicDefaults - } - protected val locator: ServiceLocator = config.locator - protected val clientConfig: AsyncHttpClientConfig = (underlying.builder - setUserAgent config.userAgent - setRequestTimeoutInMs config.idleTimeout.toMillis.toInt - setConnectionTimeoutInMs config.connectTimeout.toMillis.toInt - setCompressionEnabled config.enableCompression // enable content-compression - setAllowPoolingConnection true // enable http keep-alive - setFollowRedirects config.followRedirects).build() - - import io.swagger.client.StringHttpMethod._ - implicit val execContext = ExecutionContext.fromExecutorService(clientConfig.executorService()) - - private[this] val mimes = new Mimes with Logging { - protected def warn(message: String) = logger.warn(message) - } - - private[this] val cookies = new CookieJar(Map.empty) - - protected def createClient() = new AsyncHttpClient(clientConfig) - - private[this] val client = createClient() - - private[this] def createRequest(method: String): String ⇒ AsyncHttpClient#BoundRequestBuilder = { - method.toUpperCase(Locale.ENGLISH) match { - case `GET` ⇒ client.prepareGet _ - case `POST` ⇒ client.preparePost _ - case `PUT` ⇒ client.preparePut _ - case `DELETE` ⇒ client.prepareDelete _ - case `HEAD` ⇒ client.prepareHead _ - case `OPTIONS` ⇒ client.prepareOptions _ - case `CONNECT` ⇒ client.prepareConnect _ - } - } - - private[this] def addTimeout(timeout: Duration)(req: AsyncHttpClient#BoundRequestBuilder) = { - if (timeout.isFinite()) { - val prc = new PerRequestConfig() - prc.setRequestTimeoutInMs(timeout.toMillis.toInt) - req.setPerRequestConfig(prc) - } - req - } - - private[this] def addParameters(method: String, params: Iterable[(String, String)], isMultipart: Boolean = false, charset: Charset = Codec.UTF8.charSet)(req: AsyncHttpClient#BoundRequestBuilder) = { - method.toUpperCase(Locale.ENGLISH) match { - case `GET` | `DELETE` | `HEAD` | `OPTIONS` ⇒ params foreach { case (k, v) ⇒ req addQueryParameter (k, v) } - case `PUT` | `POST` | `PATCH` ⇒ { - if (!isMultipart) - if (req.build().getHeaders.getFirstValue("Content-Type").startsWith("application/x-www-form-urlencoded")) - params foreach { case (k, v) ⇒ req addParameter (k, v) } - else - params foreach { case (k, v) => req addQueryParameter(k, v) } - else { - params foreach { case (k, v) => req addBodyPart new StringPart(k, v, charset.name)} - } - } - case _ ⇒ // we don't care, carry on - } - req - } - - private[this] def addHeaders(headers: Iterable[(String, String)], files: Iterable[(String, File)])(req: AsyncHttpClient#BoundRequestBuilder) = { - headers foreach { case (k, v) => req.addHeader(k, v) } - if (!Map(headers.map(kv => kv._1.toUpperCase -> kv._2).toSeq:_*).contains("CONTENT-TYPE")) - req.setHeader("Content-Type", defaultWriteContentType(files)("Content-Type")) - req - } - - private[this] def addFiles(files: Iterable[(String, File)], isMultipart: Boolean)(req: AsyncHttpClient#BoundRequestBuilder) = { - if (isMultipart) { - files foreach { case (nm, file) => - req.addBodyPart(new FilePart(nm, file, mimes(file), FileCharset(file).name)) - } - } - req - } - - private[this] def addCookies(req: AsyncHttpClient#BoundRequestBuilder) = { - cookies foreach { cookie => - val ahcCookie = AhcCookie.newValidCookie( - cookie.name, - cookie.value, - cookie.cookieOptions.domain, - cookie.value, - cookie.cookieOptions.path, - -1, - cookie.cookieOptions.maxAge, - cookie.cookieOptions.secure, - cookie.cookieOptions.httpOnly) - req.addCookie(ahcCookie) - } - req - } - - private[this] def addQuery(u: URI)(req: AsyncHttpClient#BoundRequestBuilder) = { - u.getQuery.blankOption foreach { uu => - rl.QueryString(uu) match { - case m: MapQueryString => m.value foreach { case (k, v) => v foreach { req.addQueryParameter(k, _) } } - case _ => - } - } - req - } - - private[this] val allowsBody = Vector(PUT, POST, PATCH) - - - private[this] def addBody(method: String, body: String)(req: AsyncHttpClient#BoundRequestBuilder) = { - if (allowsBody.contains(method.toUpperCase(Locale.ENGLISH)) && body.nonBlank) req.setBody(body) - req - } - - - private[this] def requestFiles(params: Iterable[(String, Any)]) = params collect { case (k, v: File) => k -> v } - private[this] def paramsFrom(params: Iterable[(String, Any)]) = params collect { - case (k, v: String) => k -> v - case (k, null) => k -> "" - case (k, v) => k -> v.toString - } - private[this] def isMultipartRequest(method: String, headers: Iterable[(String, String)], files: Iterable[(String, File)]) = { - allowsBody.contains(method.toUpperCase(Locale.ENGLISH)) && { - val ct = (defaultWriteContentType(files) ++ headers)("Content-Type") - ct.toLowerCase(Locale.ENGLISH).startsWith("multipart/form-data") - } - } - - private[this] def requestUri(base: URI, u: URI) = if (u.isAbsolute) u else { - // There is no constructor on java.net.URI that will not encode the path - // except for the one where you pass in a uri as string so we're concatenating ourselves - val prt = base.getPort - val b = - if (prt > 0 && prt != 80 && prt != 443) "%s://%s:%d".format(base.getScheme, base.getHost, prt) - else "%s://%s".format(base.getScheme, base.getHost) - val p = base.getRawPath + u.getRawPath.blankOption.getOrElse("/") - val q = u.getRawQuery.blankOption.map("?"+_).getOrElse("") - val f = u.getRawFragment.blankOption.map("#"+_).getOrElse("") - URI.create(b+p+q+f) - } - - def submit(method: String, uri: String, params: Iterable[(String, Any)], headers: Iterable[(String, String)], body: String = "", timeout: Duration = 90.seconds): Future[RestClientResponse] = { - val u = URI.create(if (UrlCodingUtils.needsUrlEncoding(uri)) uri.urlEncode else uri).normalize() - val files = requestFiles(params) - val isMultipart = isMultipartRequest(method, headers, files) - locator.pickOneAsUri(config.name, "") flatMap { opt => - if (opt.isEmpty) sys.error("No host could be found for %s".format(config.name)) - else { - val baseUrl = opt.get - (createRequest(method) - andThen addTimeout(timeout) - andThen addHeaders(headers, files) - andThen addCookies - andThen addParameters(method, paramsFrom(params), isMultipart) - andThen addQuery(u) - andThen addBody(method, body) - andThen addFiles(files, isMultipart) - andThen executeRequest)(requestUri(URI.create(baseUrl).normalize(), u).toASCIIString) - } - } - } - - private[this] def executeRequest(req: AsyncHttpClient#BoundRequestBuilder): Future[RestClientResponse] = { - logger.debug("Requesting:\n" + req.build()) - val promise = Promise[RestClientResponse]() - req.execute(new AsyncCompletionHandler[Promise[RestClientResponse]] { - override def onThrowable(t: Throwable) = promise.complete(Failure(t)) - def onCompleted(response: Response) = { - logger.debug(s"Got response [${response.getStatusCode} ${response.getStatusText}}] for request to ${req.build().getUrl}.\n$response") - promise.success(new RestClientResponse(response)) - } - }) - promise.future - } - - private[this] def defaultWriteContentType(files: Iterable[(String, File)]) = { - val value = if (files.nonEmpty) "multipart/form-data" else config.contentType.headerValue - Map("Content-Type" -> value) - } - - def close() = client.closeAsynchronously() -} diff --git a/src/main/scala_2.11/io/swagger/client/ServiceLocator.scala b/src/main/scala_2.11/io/swagger/client/ServiceLocator.scala deleted file mode 100644 index 9e1912e..0000000 --- a/src/main/scala_2.11/io/swagger/client/ServiceLocator.scala +++ /dev/null @@ -1,126 +0,0 @@ -package io.swagger.client - -import scala.concurrent.{Await, Future, ExecutionContext} -import scala.concurrent.duration._ -import scala.collection.concurrent.TrieMap -import language.postfixOps -import java.net.URI -import java.util.concurrent.atomic.AtomicLong - -/** - * A trait for a load balancing strategy. - * It takes a set of hosts and returns a single host - * from the Set - */ -trait HostPicker { - /** - * Pick a host from the provided list of services - * @param hosts The hosts to pick from - * @return A Future with an Option that contains the host if there was one. - */ - def apply(hosts: Set[String], serviceName: Option[String])(implicit executionContext: ExecutionContext): Future[Option[String]] -} - -object HeadHostPicker extends HostPicker { - def apply(hosts: Set[String], serviceName: Option[String] = None)(implicit executionContext: ExecutionContext): Future[Option[String]] = { - Future.successful(hosts.headOption) - } -} - -object RandomHostPicker extends HostPicker { - private[this] val rand = new util.Random() - def apply(hosts: Set[String], serviceName: Option[String] = None)(implicit executionContext: ExecutionContext): Future[Option[String]] = { - Future.successful(if (hosts.nonEmpty) Some(hosts.toList(rand.nextInt(hosts.size))) else None) - } -} - -object GlobalRoundRobinHostPicker extends HostPicker { - private[this] val pickers = TrieMap.empty[String, RoundRobinHostPicker] - private[this] val default = new RoundRobinHostPicker - - def apply( - hosts: Set[String], - serviceName: Option[String])( - implicit executionContext: ExecutionContext): Future[Option[String]] = { - - val roundRobinPicker = serviceName map { name => - pickers.getOrElseUpdate(name, new RoundRobinHostPicker) - } getOrElse(default) - - roundRobinPicker(hosts, None) - } -} - -final class RoundRobinHostPicker extends HostPicker { - // Start at -1 so that the first call to incrementAndGet returns 0 - private[this] val counter = new AtomicLong(-1) - - def apply(hosts: Set[String], serviceName: Option[String] = None)(implicit executionContext: ExecutionContext): Future[Option[String]] = { - Future.successful { - val sortedHosts = hosts.toVector.sorted - - if (sortedHosts.length == 0) { - None - } else { - // This cast is okay because vector.length is an Int. - val index = (counter.incrementAndGet() % sortedHosts.length).toInt - Some(sortedHosts(index)) - } - } - } -} - -trait ServiceLocator { - implicit protected def executionContext: ExecutionContext - def locate(name: String): Future[Set[String]] - - def locateBlocking(name: String, atMost: FiniteDuration = 20 seconds): Set[String] = { - Await.result(locate(name), atMost) - } - - def pickOne( - name: String, - picker: HostPicker = GlobalRoundRobinHostPicker): Future[Option[String]] - - def pickOneBlocking( - name: String, - picker: HostPicker = GlobalRoundRobinHostPicker, - atMost: FiniteDuration = 20 seconds): Option[String] = { - - Await.result(pickOne(name, picker), atMost) - } - - def locateAsUris(name: String, path: String): Future[Set[String]] - - def locateAsUrisBlocking( - name: String, - path: String, - atMost: FiniteDuration = 20 seconds): Set[String] = { - - Await.result(locateAsUris(name, path), atMost) - } - - def pickOneAsUri( - name: String, - path: String, - picker: HostPicker = GlobalRoundRobinHostPicker): Future[Option[String]] - - def pickOneAsUriBlocking( - name: String, - path: String, - picker: HostPicker = GlobalRoundRobinHostPicker, - atMost: FiniteDuration = 20 seconds): Option[String] = { - - Await.result(pickOneAsUri(name, path, picker), atMost) - } -} - -case class BaseUrl(url: URI)(implicit protected val executionContext: ExecutionContext = ExecutionContext.global) extends ServiceLocator { - private[this] val withoutScheme = url.getHost - private[this] val withScheme = url.getScheme + "://" + url.getAuthority + stripTrailingSlash(url.getPath) - def locate(name: String): Future[Set[String]] = Future.successful(Set(withoutScheme)) - def pickOne(name: String, picker: HostPicker): Future[Option[String]] = Future.successful(Some(withoutScheme)) - def locateAsUris(name: String, path: String): Future[Set[String]] = Future.successful(Set(withScheme)) - def pickOneAsUri(name: String, path: String, picker: HostPicker): Future[Option[String]] = Future.successful(Some(withScheme)) - private def stripTrailingSlash(s: String): String = if (s endsWith "/") s.substring(0, s.length - 1) else s -} diff --git a/src/main/scala_2.11/io/swagger/client/SwaggerConfig.scala b/src/main/scala_2.11/io/swagger/client/SwaggerConfig.scala deleted file mode 100644 index 8f7d5d2..0000000 --- a/src/main/scala_2.11/io/swagger/client/SwaggerConfig.scala +++ /dev/null @@ -1,63 +0,0 @@ -package io.swagger.client - -import scala.concurrent.duration._ -import java.io.StringWriter -import org.json4s.jackson.JsonMethods -import org.json4s._ -import org.json4s.Xml._ -import java.net.URI - -object SwaggerConfig { - - - private case class DefaultSwaggerConfig( - locator: ServiceLocator, - override val userAgent: String = RestClient.DefaultUserAgent, - override val idleTimeout: Duration = 5.minutes, - override val connectTimeout: Duration = 5.seconds, - override val maxMessageSize: Int = 8912, - override val enableCompression: Boolean = true, - override val followRedirects: Boolean = true, - override val identity: String = "0", - override val name: String = "no-name", - override val contentType: ContentType = ContentType("json", "application/json;charset=utf-8")) extends SwaggerConfig - - def forUrl( - baseUrl: URI, - userAgent: String = RestClient.DefaultUserAgent, - idleTimeout: Duration = 5.minutes, - connectTimeout: Duration = 5.seconds, - maxMessageSize: Int = 8912, - enableCompression: Boolean = true, - followRedirects: Boolean = true, - identity: String = "0"): SwaggerConfig = - new DefaultSwaggerConfig(BaseUrl(baseUrl), userAgent, idleTimeout, connectTimeout, maxMessageSize, enableCompression, followRedirects, identity) - - def forLocator( - locator: ServiceLocator, - name: String, - userAgent: String = RestClient.DefaultUserAgent, - idleTimeout: Duration = 5.minutes, - connectTimeout: Duration = 5.seconds, - maxMessageSize: Int = 8912, - enableCompression: Boolean = true, - followRedirects: Boolean = true, - identity: String = "0"): SwaggerConfig = - new DefaultSwaggerConfig(locator, userAgent, idleTimeout, connectTimeout, maxMessageSize, enableCompression, followRedirects, identity, name) - } - - case class ContentType(name: String, headerValue: String) - trait SwaggerConfig { - def locator: ServiceLocator - def userAgent: String - def idleTimeout: Duration - def connectTimeout: Duration - def maxMessageSize: Int - def enableCompression: Boolean - def followRedirects: Boolean - def identity: String - def name: String - def contentType: ContentType - } - - diff --git a/src/main/scala_2.11/io/swagger/client/TransportClient.scala b/src/main/scala_2.11/io/swagger/client/TransportClient.scala deleted file mode 100644 index 7d65c2e..0000000 --- a/src/main/scala_2.11/io/swagger/client/TransportClient.scala +++ /dev/null @@ -1,107 +0,0 @@ -package io.swagger.client - -import com.ning.http._ -import client._ -import scala.concurrent.{Promise, ExecutionContext, Future} -import scala.concurrent.duration._ -import java.net.URI -import org.json4s._ -import scala.util.Try -import scala.annotation.implicitNotFound - - -trait ClientResponse { - def cookies: Map[String, RestClient.Cookie] - def headers: Map[String, Seq[String]] - def status: ResponseStatus - def contentType: String - def mediaType: Option[String] - def charset: Option[String] - def uri: URI - def statusCode: Int = status.code - def statusText: String = status.line - def body: String -} - -@implicitNotFound( - "No ClientResponseReader found for type ${T}. Try to implement an implicit ClientResponseReader for this type, or perhaps you're just missing an import like ClientResponseReader._." -) -trait ClientResponseReader[T] { - def read(resp: ClientResponse): T -} - -object ClientResponseReaders { - - private def rdr[T](fn: ClientResponse => T): ClientResponseReader[T] = new ClientResponseReader[T] { - def read(resp: ClientResponse): T = fn(resp) - } - - implicit val StringReader: ClientResponseReader[String] = rdr(_.body) - implicit val JValueReader: ClientResponseReader[JValue] = rdr(r => jackson.parseJson(r.body)) - implicit val UnitReader: ClientResponseReader[Unit] = rdr(_ => ()) - implicit def OptionReader[T](implicit reader: ClientResponseReader[T]): ClientResponseReader[Option[T]] = rdr { resp => - try { - if (resp.statusCode / 100 == 2) Option(reader.read(resp)) else None - } catch { - case _: Throwable => None - } - } - implicit def TryReader[T](implicit reader: ClientResponseReader[T]): ClientResponseReader[Try[T]] = - rdr { resp => - try { - if (resp.statusCode / 100 == 2) Try(reader.read(resp)) - else scala.util.Failure(new ApiException(resp)) - } catch { - case t: Throwable => scala.util.Failure(t) - } - } - - - object JsonTypeClassReader { - implicit def JsonFormatsReader[T](implicit jsonReader: org.json4s.Reader[T]): ClientResponseReader[T] = - rdr(resp => jsonReader.read(JValueReader.read(resp))) - } - - object Json4sFormatsReader { - implicit def JsonFormatsReader[T](implicit formats: org.json4s.Formats, mf: Manifest[T]): ClientResponseReader[T] = - rdr(resp => jackson.parseJson(resp.body).extract[T]) - } - -} - -@implicitNotFound( - "No RequestWriter found for type ${T}. Try to implement an implicit RequestWriter for this type, or perhaps you're just missing an import like RequestWriters._." -) -trait RequestWriter[T] { - def write(body: T): String -} -object RequestWriters { - private def wrtr[T](fn: T => String) = new RequestWriter[T] { - def write(body: T): String = fn(body) - } - - implicit val StringWriter: RequestWriter[String] = wrtr(identity) - implicit val JValueWriter: RequestWriter[JValue] = wrtr(jackson.compactJson) - implicit def OptionWriter[T](implicit writer: RequestWriter[T]): RequestWriter[Option[T]] = - wrtr(o => o map writer.write getOrElse "") - - object JsonTypeClassWriter { - implicit def JsonFormatsWriter[T](implicit jsonWriter: org.json4s.Writer[T]): RequestWriter[T] = - wrtr(bd => JValueWriter write jsonWriter.write(bd)) - } - - object Json4sFormatsWriter { - implicit def JsonFormatsWriter[T](implicit formats: Formats, mf: Manifest[T]): RequestWriter[T] = - wrtr(bd => JValueWriter write Extraction.decompose(bd)) - } -} - -trait TransportClient { - protected def locator: ServiceLocator - protected def clientConfig: AsyncHttpClientConfig - protected def createClient(): AsyncHttpClient - implicit def execContext: ExecutionContext - def open(): Future[Unit] = Promise.successful(()).future - def submit(method: String, uri: String, params: Iterable[(String, Any)], headers: Iterable[(String, String)], body: String, timeout: Duration = 90.seconds): Future[ClientResponse] - def close(): Unit -} diff --git a/src/main/scala_2.9/io/swagger/client/ApiClient.scala b/src/main/scala_2.9/io/swagger/client/ApiClient.scala deleted file mode 100644 index 783166d..0000000 --- a/src/main/scala_2.9/io/swagger/client/ApiClient.scala +++ /dev/null @@ -1,25 +0,0 @@ -package io.swagger.client - -import akka.dispatch.{Future, Promise} -import org.json4s.jackson.JsonMethods - -abstract class ApiClient(client: TransportClient, config: SwaggerConfig) extends JsonMethods { - protected implicit val execContext = client.execContext - protected val ser = config.dataFormat - - protected def addFmt(pth: String) = pth.replace("{format}", ser.name) - - protected def process[T](fn: => T): Future[T] = { - val fut = Promise[T] - try { - val r = fn - r match { - case t: Throwable => fut.complete(Left(t)) - case s => fut.complete(Right(r)) - } - } catch { - case t: Throwable => fut.complete(Left(t)) - } - fut - } -} diff --git a/src/main/scala_2.9/io/swagger/client/Logging.scala b/src/main/scala_2.9/io/swagger/client/Logging.scala deleted file mode 100644 index d9cafcd..0000000 --- a/src/main/scala_2.9/io/swagger/client/Logging.scala +++ /dev/null @@ -1,7 +0,0 @@ -package io.swagger.client - -import grizzled.slf4j.Logger - -trait Logging { - @transient lazy val logger: Logger = Logger(getClass) -} \ No newline at end of file diff --git a/src/main/scala_2.9/io/swagger/client/RestClient.scala b/src/main/scala_2.9/io/swagger/client/RestClient.scala deleted file mode 100644 index 3bd8c40..0000000 --- a/src/main/scala_2.9/io/swagger/client/RestClient.scala +++ /dev/null @@ -1,456 +0,0 @@ -package io.swagger.client - -import com.ning.http._ -import client._ -import client.{ Cookie => AhcCookie } -import collection.JavaConverters._ -import java.util.{TimeZone, Date, Locale} -import java.util.concurrent.ConcurrentHashMap -import io.Codec -import java.nio.charset.Charset -import java.io.File -import java.net.URI -import rl.MapQueryString -import akka.dispatch.{Promise, ExecutionContext, Future} -import akka.util.Duration -import akka.util.duration._ -import org.json4s._ -import org.json4s.jackson.JsonMethods -import java.text.SimpleDateFormat -import io.swagger.client.async.BuildInfo -import java.util.concurrent.atomic.AtomicLong -import java.util.{ concurrent => juc } -import org.jboss.netty.util.{HashedWheelTimer, Timer} -import org.jboss.netty.channel.socket.nio.{NioWorkerPool, NioClientSocketChannelFactory} -import com.ning.http.client.providers.netty.NettyAsyncHttpProviderConfig - - -object RestClient { - private val threadIds = new AtomicLong() - - private lazy val factory = new juc.ThreadFactory { - def newThread(runnable: Runnable): Thread = { - val thread = new Thread(runnable) - thread.setName("bragi-client-thread-" + threadIds.incrementAndGet()) - thread.setDaemon(true) - thread - } - } - - val DefaultUserAgent = "Reverb SwaggerClient / " + BuildInfo.version - - private implicit def stringWithExt(s: String) = new { - def isBlank = s == null || s.trim.isEmpty - def nonBlank = !isBlank - def blankOption = if (isBlank) None else Option(s) - } - - case class CookieOptions( domain : String = "", - path : String = "", - maxAge : Int = -1, - secure : Boolean = false, - comment : String = "", - httpOnly: Boolean = false, - version : Int = 0, - encoding: String = "UTF-8") - - trait HttpCookie { - implicit def cookieOptions: CookieOptions - def name: String - def value: String - - } - - case class RequestCookie(name: String, value: String, cookieOptions: CookieOptions = CookieOptions()) extends HttpCookie - object DateUtil { - @volatile private[this] var _currentTimeMillis: Option[Long] = None - def currentTimeMillis = _currentTimeMillis getOrElse System.currentTimeMillis - def currentTimeMillis_=(ct: Long) = _currentTimeMillis = Some(ct) - def freezeTime() = _currentTimeMillis = Some(System.currentTimeMillis()) - def unfreezeTime() = _currentTimeMillis = None - def formatDate(date: Date, format: String, timeZone: TimeZone = TimeZone.getTimeZone("GMT")) = { - val df = new SimpleDateFormat(format) - df.setTimeZone(timeZone) - df.format(date) - } - } - - - case class Cookie(name: String, value: String)(implicit val cookieOptions: CookieOptions = CookieOptions()) extends HttpCookie { - - private def ensureDotDomain = - (if (!cookieOptions.domain.startsWith(".")) "." + cookieOptions.domain else cookieOptions.domain).toLowerCase(Locale.ENGLISH) - - def toCookieString = { - val sb = new StringBuffer - sb append name append "=" - sb append value - - if(cookieOptions.domain.nonBlank && cookieOptions.domain != "localhost") - sb.append("; Domain=").append(ensureDotDomain) - - val pth = cookieOptions.path - if(pth.nonBlank) sb append "; Path=" append (if(!pth.startsWith("/")) { - "/" + pth - } else { pth }) - - if(cookieOptions.comment.nonBlank) sb append ("; Comment=") append cookieOptions.comment - - appendMaxAge(sb, cookieOptions.maxAge, cookieOptions.version) - - if (cookieOptions.secure) sb append "; Secure" - if (cookieOptions.httpOnly) sb append "; HttpOnly" - sb.toString - } - private[this] def appendMaxAge(sb: StringBuffer, maxAge: Int, version: Int) = { - val dateInMillis = maxAge match { - case a if a < 0 => None // we don't do anything for max-age when it's < 0 then it becomes a session cookie - case 0 => Some(0L) // Set the date to the min date for the system - case a => Some(DateUtil.currentTimeMillis + a * 1000) - } - - // This used to be Max-Age but IE is not always very happy with that - // see: http://mrcoles.com/blog/cookies-max-age-vs-expires/ - // see Q1: http://blogs.msdn.com/b/ieinternals/archive/2009/08/20/wininet-ie-cookie-internals-faq.aspx - val bOpt = dateInMillis map (ms => appendExpires(sb, new Date(ms))) - val agedOpt = if (version > 0) bOpt map (_.append("; Max-Age=").append(maxAge)) else bOpt - agedOpt getOrElse sb - } - - private[this] def appendExpires(sb: StringBuffer, expires: Date) = - sb append "; Expires=" append formatExpires(expires) - - private[this] def formatExpires(date: Date) = DateUtil.formatDate(date, "EEE, dd MMM yyyy HH:mm:ss zzz") - - } - - - class CookieJar(private val reqCookies: Map[String, RequestCookie]) { - private val cookies = new ConcurrentHashMap[String, HttpCookie].asScala ++ reqCookies - - def get(key: String) = cookies.get(key) filter (_.cookieOptions.maxAge != 0) map (_.value) - - def apply(key: String) = get(key) getOrElse (throw new Exception("No cookie could be found for the specified key [%s]" format key)) - - def update(name: String, value: String)(implicit cookieOptions: CookieOptions=CookieOptions()) = { - cookies += name -> Cookie(name, value)(cookieOptions) - } - - def set(name: String, value: String)(implicit cookieOptions: CookieOptions=CookieOptions()) = { - this.update(name, value)(cookieOptions) - } - - def delete(name: String)(implicit cookieOptions: CookieOptions = CookieOptions(maxAge = 0)) { - this.update(name, "")(cookieOptions.copy(maxAge = 0)) - } - - def +=(keyValuePair: (String, String))(implicit cookieOptions: CookieOptions = CookieOptions()) = { - this.update(keyValuePair._1, keyValuePair._2)(cookieOptions) - } - - def -=(key: String)(implicit cookieOptions: CookieOptions = CookieOptions(maxAge = 0)) { - delete(key)(cookieOptions) - } - - def size = cookies.size - - def foreach[U](fn: (HttpCookie) => U) = cookies foreach { case (_, v) => fn(v) } - - private[client] def responseCookies = cookies.values collect { case c: Cookie => c } - - override def toString: String = cookies.toString() - } - - - class RestClientResponse(response: Response) extends ClientResponse { - val cookies = (response.getCookies.asScala map { cookie => - val cko = CookieOptions(cookie.getDomain, cookie.getPath, cookie.getMaxAge) - cookie.getName -> Cookie(cookie.getName, cookie.getValue)(cko) - }).toMap - - val headers = (response.getHeaders.keySet().asScala map { k => k -> response.getHeaders(k).asScala.toSeq}).toMap - - val status = ResponseStatus(response.getStatusCode, response.getStatusText) - - val contentType = response.getContentType - - val inputStream = response.getResponseBodyAsStream - - val uri = response.getUri - - def body = response.getResponseBody(charset getOrElse "UTF-8") - - def mediaType: Option[String] = headers.get("Content-Type") flatMap { _.headOption } - - def charset: Option[String] = - for { - ct <- mediaType - charset <- ct.split(";").drop(1).headOption - } yield charset.toUpperCase.replace("CHARSET=", "").trim - } - - trait Defaults { - def builder: AsyncHttpClientConfig.Builder - def timer: Timer - } - - private object InternalDefaults { - /** true if we think we're runing un-forked in an sbt-interactive session */ - val inTrapExit = ( - for (group ← Option(Thread.currentThread.getThreadGroup)) - yield group.getName == "trap.exit").getOrElse(false) - - /** Sets a user agent, no timeout for requests */ - object BasicDefaults extends Defaults { - lazy val timer = new HashedWheelTimer() - def builder = (new AsyncHttpClientConfig.Builder() - setAllowPoolingConnection true - setRequestTimeoutInMs 45000 - setCompressionEnabled true - setFollowRedirects false - setMaximumConnectionsPerHost 200 - setUserAgent DefaultUserAgent - setMaxRequestRetry 0) - } - - /** Uses daemon threads and tries to exit cleanly when running in sbt */ - object SbtProcessDefaults extends Defaults { - def builder = { - val shuttingDown = new juc.atomic.AtomicBoolean(false) - /** daemon threads that also shut down everything when interrupted! */ - lazy val interruptThreadFactory = new juc.ThreadFactory { - def newThread(runnable: Runnable) = { - new Thread(runnable) { - setDaemon(true) - setName("bragi-client-thread-" + threadIds.incrementAndGet()) - override def interrupt() { - shutdown() - super.interrupt() - } - } - } - } - lazy val nioClientSocketChannelFactory = { - val workerCount = 2 * Runtime.getRuntime().availableProcessors() - new NioClientSocketChannelFactory( - juc.Executors.newCachedThreadPool(interruptThreadFactory), - 1, - new NioWorkerPool( - juc.Executors.newCachedThreadPool(interruptThreadFactory), - workerCount), - timer) - } - - def shutdown() { - if (shuttingDown.compareAndSet(false, true)) { - nioClientSocketChannelFactory.releaseExternalResources() - } - } - - BasicDefaults.builder.setAsyncHttpClientProviderConfig( - new NettyAsyncHttpProviderConfig().addProperty( - NettyAsyncHttpProviderConfig.SOCKET_CHANNEL_FACTORY, - nioClientSocketChannelFactory)) - } - lazy val timer = new HashedWheelTimer(factory) - } - } -} - -class RestClient(config: SwaggerConfig) extends TransportClient with Logging { - import RestClient._ - protected def underlying: Defaults = { - if (InternalDefaults.inTrapExit) InternalDefaults.SbtProcessDefaults - else InternalDefaults.BasicDefaults - } - protected val locator: ServiceLocator = config.locator - protected val clientConfig: AsyncHttpClientConfig = (underlying.builder - setUserAgent config.userAgent - setRequestTimeoutInMs config.idleTimeout.toMillis.toInt - setConnectionTimeoutInMs config.connectTimeout.toMillis.toInt - setCompressionEnabled config.enableCompression // enable content-compression - setAllowPoolingConnection true // enable http keep-alive - setFollowRedirects config.followRedirects).build() - - import StringHttpMethod._ - implicit val execContext = ExecutionContext.fromExecutorService(clientConfig.executorService()) - - private[this] val mimes = new Mimes { - protected def warn(message: String) = logger.warn(message) - } - - private[this] val cookies = new CookieJar(Map.empty) - - protected def createClient() = new AsyncHttpClient(clientConfig) { - def preparePatch(uri: String): AsyncHttpClient#BoundRequestBuilder = requestBuilder(PATCH, uri) - def prepareTrace(uri: String): AsyncHttpClient#BoundRequestBuilder = requestBuilder(TRACE, uri) - } - - private[this] val client = createClient() - - private[this] def createRequest(method: String): String ⇒ AsyncHttpClient#BoundRequestBuilder = { - method.toUpperCase(Locale.ENGLISH) match { - case `GET` ⇒ client.prepareGet _ - case `POST` ⇒ client.preparePost _ - case `PUT` ⇒ client.preparePut _ - case `DELETE` ⇒ client.prepareDelete _ - case `HEAD` ⇒ client.prepareHead _ - case `OPTIONS` ⇒ client.prepareOptions _ - case `CONNECT` ⇒ client.prepareConnect _ - case `PATCH` ⇒ client.preparePatch _ - case `TRACE` ⇒ client.prepareTrace _ - } - } - - private[this] def addTimeout(timeout: Duration)(req: AsyncHttpClient#BoundRequestBuilder) = { - if (timeout.isFinite()) { - val prc = new PerRequestConfig() - prc.setRequestTimeoutInMs(timeout.toMillis.toInt) - req.setPerRequestConfig(prc) - } - req - } - - private[this] def addParameters(method: String, params: Iterable[(String, String)], isMultipart: Boolean = false, charset: Charset = Codec.UTF8)(req: AsyncHttpClient#BoundRequestBuilder) = { - method.toUpperCase(Locale.ENGLISH) match { - case `GET` | `DELETE` | `HEAD` | `OPTIONS` ⇒ params foreach { case (k, v) ⇒ req addQueryParameter (k, v) } - case `PUT` | `POST` | `PATCH` ⇒ { - if (!isMultipart) - if (req.build().getHeaders.getFirstValue("Content-Type").startsWith("application/x-www-form-urlencoded")) - params foreach { case (k, v) ⇒ req addParameter (k, v) } - else - params foreach { case (k, v) => req addQueryParameter(k, v) } - else { - params foreach { case (k, v) => req addBodyPart new StringPart(k, v, charset.name)} - } - } - case _ ⇒ // we don't care, carry on - } - req - } - - private[this] def addHeaders(headers: Iterable[(String, String)], files: Iterable[(String, File)])(req: AsyncHttpClient#BoundRequestBuilder) = { - headers foreach { case (k, v) => req.addHeader(k, v) } - if (!Map(headers.map(kv => kv._1.toUpperCase -> kv._2).toSeq:_*).contains("CONTENT-TYPE")) - req.setHeader("Content-Type", defaultWriteContentType(files)("Content-Type")) - req - } - - private[this] def addFiles(files: Iterable[(String, File)], isMultipart: Boolean)(req: AsyncHttpClient#BoundRequestBuilder) = { - if (isMultipart) { - files foreach { case (nm, file) => - req.addBodyPart(new FilePart(nm, file, mimes(file), FileCharset(file).name)) - } - } - req - } - - private[this] def addCookies(req: AsyncHttpClient#BoundRequestBuilder) = { - cookies foreach { cookie => - val ahcCookie = new AhcCookie( - cookie.cookieOptions.domain, - cookie.name, - cookie.value, - cookie.cookieOptions.path, - cookie.cookieOptions.maxAge, - cookie.cookieOptions.secure, - cookie.cookieOptions.version) - req.addCookie(ahcCookie) - } - req - } - - private[this] def addQuery(u: URI)(req: AsyncHttpClient#BoundRequestBuilder) = { - u.getQuery.blankOption foreach { uu => - rl.QueryString(uu) match { - case m: MapQueryString => m.value foreach { case (k, v) => v foreach { req.addQueryParameter(k, _) } } - case _ => - } - } - req - } - - private[this] val allowsBody = Vector(PUT, POST, PATCH) - - - private[this] def addBody(method: String, body: String)(req: AsyncHttpClient#BoundRequestBuilder) = { - if (allowsBody.contains(method.toUpperCase(Locale.ENGLISH)) && body.nonBlank) { - req.setBody(body) - } - req - } - - - private[this] def requestFiles(params: Iterable[(String, Any)]) = params collect { case (k, v: File) => k -> v } - private[this] def paramsFrom(params: Iterable[(String, Any)]) = params collect { - case (k, v: String) => k -> v - case (k, null) => k -> "" - case (k, v) => k -> v.toString - } - private[this] def isMultipartRequest(method: String, headers: Iterable[(String, String)], files: Iterable[(String, File)]) = { - allowsBody.contains(method.toUpperCase(Locale.ENGLISH)) && { - val ct = (defaultWriteContentType(files) ++ headers)("Content-Type") - ct.toLowerCase(Locale.ENGLISH).startsWith("multipart/form-data") - } - } - - private[this] def requestUri(base: URI, u: URI) = if (u.isAbsolute) u else { - // There is no constructor on java.net.URI that will not encode the path - // except for the one where you pass in a uri as string so we're concatenating ourselves - val prt = base.getPort - val b = - if (prt > 0 && prt != 80 && prt != 443) "%s://%s:%d".format(base.getScheme, base.getHost, prt) - else "%s://%s".format(base.getScheme, base.getHost) - val p = base.getRawPath + u.getRawPath.blankOption.getOrElse("/") - val q = u.getRawQuery.blankOption.map("?"+_).getOrElse("") - val f = u.getRawFragment.blankOption.map("#"+_).getOrElse("") - URI.create(b+p+q+f) - } - - def submit(method: String, uri: String, params: Iterable[(String, Any)], headers: Iterable[(String, String)], body: String = "", timeout: Duration = 90.seconds): Future[RestClientResponse] = { - val u = URI.create(uri).normalize() - val files = requestFiles(params) - val isMultipart = isMultipartRequest(method, headers, files) - - locator.pickOneAsUri(config.name, "") flatMap { opt => - if (opt.isEmpty) sys.error("No host could be found for %s".format(config.name)) - else { - val baseUrl = opt.get - (createRequest(method) - andThen addTimeout(timeout) - andThen addHeaders(headers, files) - andThen addCookies - andThen addParameters(method, paramsFrom(params), isMultipart) - andThen addQuery(u) - andThen addBody(method, body) - andThen addFiles(files, isMultipart) - andThen executeRequest)(requestUri(URI.create(baseUrl).normalize(), u).toASCIIString) - } - } - } - - private[this] def executeRequest(req: AsyncHttpClient#BoundRequestBuilder) = { - logger.debug("Requesting:\n" + req.build()) - val promise = Promise[RestClientResponse]() - req.execute(new AsyncCompletionHandler[Future[ClientResponse]] { - override def onThrowable(t: Throwable) = promise.complete(Left(t)) - def onCompleted(response: Response) = { - logger.debug("Got response ["+response.getStatusCode+" "+response.getStatusText+"] for request to "+req.build().getUrl+".\n"+response) - if (response.getStatusCode / 100 == 2) - promise.complete(Right(new RestClientResponse(response))) - else { - promise.complete(Left(new ApiException(response.getStatusCode, response.getStatusText, response.getResponseBody(Codec.UTF8.name())))) - } - } - }) - promise - } - - private[this] def defaultWriteContentType(files: Iterable[(String, File)]) = { - val value = if (files.nonEmpty) "multipart/form-data" else config.dataFormat.contentType - Map("Content-Type" -> value) - } - - def close() = Future { client.close() } -} - diff --git a/src/main/scala_2.9/io/swagger/client/ServiceLocator.scala b/src/main/scala_2.9/io/swagger/client/ServiceLocator.scala deleted file mode 100644 index 5e93184..0000000 --- a/src/main/scala_2.9/io/swagger/client/ServiceLocator.scala +++ /dev/null @@ -1,78 +0,0 @@ -package io.swagger.client - -import akka.dispatch.{Promise, Await, Future, ExecutionContext} -import akka.util.duration._ -import akka.util.FiniteDuration -import java.net.URI -import java.util.concurrent.Executors - -/** - * A trait for a load balancing strategy. - * It takes a set of hosts and returns a single host - * from the Set - */ -trait HostPicker { - /** - * Pick a host from the provided list of services - * @param hosts The hosts to pick from - * @return A Future with an Option that contains the host if there was one. - */ - def apply(hosts: Set[String])(implicit executionContext: ExecutionContext): Future[Option[String]] -} - -object HeadHostPicker extends HostPicker { - def apply(hosts: Set[String])(implicit executionContext: ExecutionContext): Future[Option[String]] = { - Future(hosts.headOption) - } -} - -object RandomHostPicker extends HostPicker { - private[this] val rand = new util.Random() - def apply(hosts: Set[String])(implicit executionContext: ExecutionContext): Future[Option[String]] = { - Future(if (hosts.nonEmpty) Some(hosts.toList(rand.nextInt(hosts.size))) else None) - } -} - -trait ServiceLocator { - implicit protected def executionContext: ExecutionContext - def locate(name: String): Future[Set[String]] - def locateBlocking(name: String, atMost: FiniteDuration = 20 seconds): Set[String] = - Await.result(locate(name), atMost) - - def pickOne(name: String, picker: HostPicker = RandomHostPicker): Future[Option[String]] - - def pickOneBlocking(name: String, picker: HostPicker = RandomHostPicker, atMost: FiniteDuration = 20 seconds): Option[String] = - Await.result(pickOne(name, picker), atMost) - - def locateAsUris(name: String, path: String): Future[Set[String]] - def locateAsUrisBlocking(name: String, path: String, atMost: FiniteDuration = 20 seconds): Set[String] = - Await.result(locateAsUris(name, path), atMost) - - def pickOneAsUri(name: String, path: String, picker: HostPicker = RandomHostPicker): Future[Option[String]] - - def pickOneAsUriBlocking(name: String, path: String, picker: HostPicker = RandomHostPicker, atMost: FiniteDuration = 20 seconds): Option[String] = - Await.result(pickOneAsUri(name, path, picker), atMost) - -} - -object GlobalContext { - lazy val executionContext: ExecutionContext = ExecutionContext.fromExecutor(Executors.newCachedThreadPool()) - -} - -case class BaseUrl(url: String)(implicit protected val executionContext: ExecutionContext = GlobalContext.executionContext) extends ServiceLocator { - private[this] val uri = URI.create(url) - private[this] val withoutScheme = uri.getHost - private[this] val withScheme = uri.getScheme + "://" + uri.getAuthority + stripTrailingSlash(uri.getPath) - - - def locate(name: String): Future[Set[String]] = Promise.successful(Set(withoutScheme)).future - - def pickOne(name: String, picker: HostPicker): Future[Option[String]] = Promise.successful(Some(withoutScheme)).future - - def locateAsUris(name: String, path: String): Future[Set[String]] = Promise.successful(Set(withScheme)).future - - def pickOneAsUri(name: String, path: String, picker: HostPicker): Future[Option[String]] = Promise.successful(Some(withScheme)).future - - private[this] def stripTrailingSlash(s: String): String = if (s endsWith "/") s.dropRight(1) else s -} diff --git a/src/main/scala_2.9/io/swagger/client/SwaggerConfig.scala b/src/main/scala_2.9/io/swagger/client/SwaggerConfig.scala deleted file mode 100644 index 164b7af..0000000 --- a/src/main/scala_2.9/io/swagger/client/SwaggerConfig.scala +++ /dev/null @@ -1,108 +0,0 @@ -package io.swagger.client - -import akka.util.Duration -import akka.util.duration._ -import java.io.StringWriter -import org.json4s.jackson.JsonMethods -import SwaggerConfig.DataFormat -import org.json4s._ -import org.json4s.Xml._ -import io.Codec - -object SwaggerConfig { - sealed trait DataFormat { - - def name: String - def contentType: String - - def serialize[T](obj: T): String - def deserialize[T:Manifest](json: String): T - } - object DataFormat { - object Json { - def apply(fmts: Formats) = new Json(fmts) - } - class Json(fmts: Formats) extends DataFormat { - implicit protected val jsonFormats: Formats = fmts - val contentType: String = "application/json;charset=utf-8" - val name: String = "json" - def deserialize[T: Manifest](json: String): T = JsonMethods.parse(json, fmts.wantsBigDecimal).extract[T] - def serialize[T](obj: T): String = JsonMethods.compact(JsonMethods.render(Extraction.decompose(obj))) - } - - object XML { - def apply(fmts: Formats) = new XML(fmts) - } - class XML(fmts: Formats) extends DataFormat { - implicit protected val jsonFormats: Formats = fmts - val contentType: String = "application/xml" - - val name: String = "xml" - - def deserialize[T: Manifest](json: String): T = { - val JObject(JField(_, jv) :: Nil) = toJson(scala.xml.XML.loadString(json)) - jv.extract[T] - } - - protected lazy val xmlRootNode = - - def serialize[T](obj: T): String = { - val json = Extraction.decompose(obj) - val sw = new StringWriter() - scala.xml.XML.write(sw, xmlRootNode.copy(child = toXml(json)), Codec.UTF8.name, true, null) - sw.toString - } - } - } - - private case class DefaultSwaggerConfig( - locator: ServiceLocator, - override val userAgent: String = RestClient.DefaultUserAgent, - override val dataFormat: SwaggerConfig.DataFormat = DataFormat.Json(DefaultFormats), - override val idleTimeout: Duration = 5.minutes, - override val connectTimeout: Duration = 5.seconds, - override val maxMessageSize: Int = 8912, - override val enableCompression: Boolean = true, - override val followRedirects: Boolean = true, - override val identity: String = "0", - override val name: String = "no-name") extends SwaggerConfig - - def forUrl( - baseUrl: String, - userAgent: String = RestClient.DefaultUserAgent, - dataFormat: SwaggerConfig.DataFormat = DataFormat.Json(DefaultFormats), - idleTimeout: Duration = 5.minutes, - connectTimeout: Duration = 5.seconds, - maxMessageSize: Int = 8912, - enableCompression: Boolean = true, - followRedirects: Boolean = true, - identity: String = "0"): SwaggerConfig = - new DefaultSwaggerConfig(BaseUrl(baseUrl), userAgent, dataFormat, idleTimeout, connectTimeout, maxMessageSize, enableCompression, followRedirects, identity) - - def forLocator( - locator: ServiceLocator, - name: String, - userAgent: String = RestClient.DefaultUserAgent, - dataFormat: SwaggerConfig.DataFormat = DataFormat.Json(DefaultFormats), - idleTimeout: Duration = 5.minutes, - connectTimeout: Duration = 5.seconds, - maxMessageSize: Int = 8912, - enableCompression: Boolean = true, - followRedirects: Boolean = true, - identity: String = "0"): SwaggerConfig = - new DefaultSwaggerConfig(locator, userAgent, dataFormat, idleTimeout, connectTimeout, maxMessageSize, enableCompression, followRedirects, identity, name) -} - -trait SwaggerConfig { - def locator: ServiceLocator - def userAgent: String = RestClient.DefaultUserAgent - def dataFormat: SwaggerConfig.DataFormat = DataFormat.Json(DefaultFormats) - def idleTimeout: Duration = 5.minutes - def connectTimeout: Duration = 5.seconds - def maxMessageSize: Int = 8912 - def enableCompression: Boolean = true - def followRedirects: Boolean = true - def identity: String = "0" - def name: String = "no-name" -} - diff --git a/src/main/scala_2.9/io/swagger/client/TransportClient.scala b/src/main/scala_2.9/io/swagger/client/TransportClient.scala deleted file mode 100644 index 999e144..0000000 --- a/src/main/scala_2.9/io/swagger/client/TransportClient.scala +++ /dev/null @@ -1,33 +0,0 @@ -package io.swagger.client - -import com.ning.http._ -import client._ -import akka.dispatch.{Promise, ExecutionContext, Future} -import akka.util.Duration -import akka.util.duration._ -import java.net.URI -import org.json4s._ - - -trait ClientResponse { - def cookies: Map[String, RestClient.Cookie] - def headers: Map[String, Seq[String]] - def status: ResponseStatus - def contentType: String - def mediaType: Option[String] - def charset: Option[String] - def uri: URI - def statusCode: Int = status.code - def statusText: String = status.line - def body: String - -} - -trait TransportClient { - protected def locator: ServiceLocator - protected def clientConfig: AsyncHttpClientConfig - implicit def execContext: ExecutionContext - def open(): Future[Unit] = Promise.successful(()).future - def submit(method: String, uri: String, params: Iterable[(String, Any)], headers: Iterable[(String, String)], body: String, timeout: Duration = 90.seconds): Future[ClientResponse] - def close(): Future[Unit] -}