[
  {
    "path": ".gitignore",
    "content": ".idea/\nproject/target\nproject/project\ntarget\n*.log"
  },
  {
    "path": ".travis.yml",
    "content": "language: scala\nsudo: true\nscala:\n   - 2.11.7\n\nscript:\n   - sbt ++$TRAVIS_SCALA_VERSION clean coverage test &&\n     sbt coverageAggregate\n\nbefore_install:\n  - pip install --user codecov\nafter_success:\n  - codecov\n"
  },
  {
    "path": "Dockerfile",
    "content": "# Neutrino Docker file\n\nFROM ubuntu:14.04\n\nRUN apt-get -y update\nRUN apt-get install -y nano openssh-server python supervisor unzip curl wget vim\nRUN mkdir -p /var/run/sshd ; mkdir -p /var/log/supervisor\nRUN apt-get install -y jq telnet\n\nADD supervisord.conf    /etc/supervisor/conf.d/supervisord.conf\nRUN apt-get -y update\n\n# Install Open JDK 7\nRUN apt-get install -y openjdk-7-jre\n\n# Define commonly used JAVA_HOME variable\nENV JAVA_HOME /usr/lib/jvm/java-7-openjdk-amd64\n\n\n\nRUN mkdir -p /neutrino/logs && cd /neutrino \\\n    && mkdir -p /etc/neutrino\n\nADD run.conf    /etc/supervisor/conf.d/run.conf\n\nRUN mkdir -p /ebay/software/packages/neutrino\nCOPY target/pack/lib /neutrino/lib \nCOPY target/pack/bin /neutrino/bin\nCOPY src/pack/run.sh /neutrino/run.sh\n\nRUN chmod 777 /neutrino/run.sh\n\n# Expose all ports\nEXPOSE 1-65535\n\nCMD [\"/usr/bin/supervisord\"]\n# default cmd to run \n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n"
  },
  {
    "path": "README.md",
    "content": "<a href=\"http://neutrinoslb.github.io/\"><img alt=\"Neutrino-logo\" src=\"http://neutrinoslb.github.io/images/neutrino-logo.png\" width=\"325\"></a>\n\n [![Build Status](https://travis-ci.org/eBay/Neutrino.svg?branch=master)](https://travis-ci.org/eBay/Neutrino) [![Apache V2.0 License](http://www.parallec.io/images/apache2.svg) ](https://github.com/eBay/Neutrino/blob/master/LICENSE) \n\nNeutrino is a software load balancer(SLB) is used by eBay to do L7 Switching and Load Balancing for eBay’s test infrastructure. It is build using Scala and Netty and it uses the Java Virtual Machine (JVM) as run-time environment.\n\nLink to the [website](http://neutrinoslb.github.io/)\n\n## Why another SLB\nEbay was looking for options to replace their hardware load balancers which are expensive and unable to keep up with the demand. There were two options, either take an open source product like HAProxy or build an in-house one. \n\nFrom a high level, SLB has to satisfy following requirements\n- L7 Switching using canonical names\n- L7 Switching using canonical names and url context\n- L7 Switching based on Rules. For eg. Traffic might need to route based on HTTP header, based on authentication header value etc\n- Layer 4 Switching\n- Should be able to send the traffic logs to API endpoints\n- Cluster management is automated using eBay PaaS and Network Topology is stored in a DB and can be accessed through API. SLB should be able to read the topology and reconfigure itself\n- Load Balancing should support most common algorithms like Least Connection and Round Robin. The framework should be extensible to add more algorithms in the future.\n- Should be able to run on a Bare Metal, VM or a container\n- No traffic loss during reconfiguration\n\nHAProxy is the most commonly used SLB across the industry. It is written in C and has a reputation for being fast and efficient (in terms of processor and memory usage).  It can do L4 Switching and  L7 Switching using canonical names and url context. But L7 Switching based on rules, sending log to API end point or adding new load balancing algorithms cannot be satisfied using HAProxy. Reading the configuration from a DB or a API can be achieved through another application, but not optimal. Extending HAProxy to support these features found to be tough. Adding additional load balancing algorithms is also tough in HAProxy. Those are the reasons forced eBay to think about developing a SLB in-house.\n\nNeutrino was built keeping the above requirements in mind. It is build in Scala language using Netty Server. It can do L7 routing using canonical names, url context and rule based. It has highly extensible pipeline architecture so that, new modules can be hooked into the pipeline without much work. Developers can add new switching rules and load balancing options easily. New modules can be added to send the log to API end point or load the configuration file from a DB or API. It is using JVM runtime environment, so developers can use either Java or Scala to add modules.\n\n## Prerequisites\n\nBuilding the neutrino requires:\n- JDK 1.7+\n- SBT 0.13.7\n- Scala 2.11+\n- IntelliJ IDEA 13 (recommended)\n\n## How to Build\n- Checkout the Git Repo GitHub\n- Goto neutrino-opensource dir\n- Run the command \"sbt pack\"\n- \"sbt pack\" command will build Neutrino and create the jar files. You can find all the final jar files in \"target/pack/lib\"\n\n## How to run in a server\n- Create the slb.conf in \"/etc/neutrino\". Please refer to a sample slb.conf for reference\n-   [slb.conf](https://github.com/eBay/Neutrino/blob/master/src/main/resources/slb.conf)\n- Run \"target/pack/sl-b\"\n- Open hosts file and add canonical name(cname) entries\n  - 127.0.0.1 cname1.com\n  - 127.0.0.1 cnamewildcard.com\n- Run a webserver at 9999 port and/or 9998 port. Least Connection balancer will make sure that to send the traffic only if the server is up\n- Hit the URL curl cname1.com:8080\n- Hit the URL curl cnamewildcard.com:8080/website\n- Hit the URL http://localhost:8079 to access pool information\n\n# How to run in Docker container\n- Create the slb.conf in \"/etc/neutrino\". Please refer to a sample slb.conf for reference\n- [slb.conf](https://github.com/eBay/Neutrino/blob/master/src/main/resources/slb.conf)\n- Run the docker command\n  - docker run -d --net=host  -v /etc/neutrino:/etc/neutrino  -t neutrinoslb/latest\n  - --net=host : use the host network stack inside the container\n  - -v /etc/neutrino:/etc/neutrino : Mount the volume inside the container\n  - -t neutrinoslb/latest : Get the latest docker image from docker hub\n\n## Jenkins Build\n\n[Jenkins](https://travis-ci.org/eBay/Neutrino/)\n\n## Code Coverage\n\nCurrent code coverage is 64%. Code Coverage Link :\n\n[Code Coverage](https://codecov.io/github/eBay/Neutrino)\n\n\n## Contributions\n\nNeutrino is served to you by Chris Brown, Blesson Paul and eBay PaaS Team.\n\n## Questions\n\nPlease post your questions in [google forum](https://groups.google.com/forum/#!forum/neutrinoslb)\n\n"
  },
  {
    "path": "build.sbt",
    "content": "import sbt.Keys._\nimport sbt._\n\nname := \"neutrino-core\"\n\nversion := \"1.0.0-SNAPSHOT\"\n\norganization := \"com.ebay.neutrino\"\n\nscalaVersion := \"2.11.7\"\n\nscalacOptions ++= Seq(\"-unchecked\", \"-deprecation\", \"-feature\", \"-encoding\", \"utf8\", \"-language:postfixOps\")\n\ncrossPaths := false\n\nfork in ThisBuild := true\n\nparallelExecution in ThisBuild := false\n\n// Library dependencies\nlibraryDependencies ++= Seq(\n  // Basic dependencies\n  \"org.slf4j\"                 %  \"slf4j-api\"                  % Version.slf4J,\n  \"com.typesafe.scala-logging\"      %% \"scala-logging-slf4j\"         % Version.scalalogging,\n  \"com.lihaoyi\"               %% \"scalatags\"                  % Version.scalatags,\n  \"com.typesafe\"              %  \"config\"                     % Version.config,\n  \"com.google.guava\"          %  \"guava\"                      % Version.guava,\n  \"com.google.code.findbugs\"  %  \"jsr305\"                     % Version.jsr305,\n  //\n  // Functionality Frameworks\n  \"io.netty\"                  %  \"netty-all\"                  % Version.netty,\n  \"com.codahale.metrics\"      %  \"metrics-annotation\"         % Version.metrics,\n  \"com.codahale.metrics\"      %  \"metrics-graphite\"           % Version.metrics,\n  \"nl.grons\"                  %% \"metrics-scala\"              % Version.metricsScala,\n  // For API\n  \"io.spray\"              %%  \"spray-can\"                  % Version.spray,\n  \"io.spray\"              %%  \"spray-client\"               % Version.spray,\n  \"io.spray\"              %%  \"spray-routing\"              % Version.spray,\n  \"io.spray\"              %% \"spray-json\"                 % Version.sprayJson,\n  // Test resources\n  \"org.scalatest\"             %% \"scalatest\"                  % Version.scalatest % \"test\",\n  \"com.typesafe.akka\"         %% \"akka-actor\"                 % Version.akka  % \"test\",\n  \"org.slf4j\"                 %  \"slf4j-simple\"               % Version.slf4J\n)\n\ncoverageExcludedPackages := \"com\\\\.twitter.*;com\\\\.ebay\\\\.neutrino\\\\.www.*\"\n\npackAutoSettings\n"
  },
  {
    "path": "project/Dependencies.scala",
    "content": "import sbt._\n\nobject Version {\n  val scala         = \"2.10.4\"\n  val slf4J         = \"1.7.5\"\n  val logback       = \"1.0.11\"\n  val scalalogging  = \"2.1.1\"\n  val scalatags     = \"0.4.5\"\n  val config        = \"1.2.1\"\n  val guava         = \"18.0\"\n  val jsr305        = \"1.3.+\"\n  val metrics       = \"3.0.2\"\n  val metricsScala  = \"3.2.1_a2.3\"\n  val netty         = \"4.1.0.Beta5\"\n  val akka          = \"2.3.4\"\n  val spray         = \"1.3.1\"\n  val sprayJson     = \"1.3.2\"\n  val json4s        = \"3.2.10\"\n  val scalatest     = \"2.2.1\"\n  val junit         = \"0.8\"\n}\n\n\nobject Resolvers {\n  val mavenrepo = \"Maven Repo\" at \"http://repo1.maven.org/maven2\"\n}\n"
  },
  {
    "path": "project/Testing.scala",
    "content": ""
  },
  {
    "path": "project/assembly.sbt",
    "content": "addSbtPlugin(\"com.eed3si9n\" % \"sbt-assembly\" % \"0.11.2\")"
  },
  {
    "path": "project/build.properties",
    "content": "sbt.version=0.13.7\n#sbt.version=0.13.1"
  },
  {
    "path": "project/plugins.sbt",
    "content": "import sbt._\n\n// Add eBay-specific resolvers (TODO move this to CI)\nresolvers ++= Seq(\n  \"Maven Repo\" at \"http://repo1.maven.org/maven2\"\n)\n\n\n// Expensive resolver; Add instead in your local dev ~/.sbtconfig file\n// addSbtPlugin(\"net.virtual-void\" % \"sbt-dependency-graph\" % \"0.7.4\")\n\naddSbtPlugin(\"org.scoverage\" %% \"sbt-scoverage\" % \"1.3.3\")\n\naddSbtPlugin(\"org.scalastyle\" %% \"scalastyle-sbt-plugin\" % \"0.6.0\")\n\naddSbtPlugin(\"de.johoop\" % \"findbugs4sbt\" % \"1.3.0\")\n\naddSbtPlugin(\"org.xerial.sbt\" % \"sbt-pack\" % \"0.7.2\")\n\nchecksums in update := Nil\n"
  },
  {
    "path": "project/release_notes.txt",
    "content": "V0.5.6\n- Added additional fields to VirtualServer, VirtualPool, renamed settings in NeutrinoNode\n- Health aware load balancing\n    - Added HealthNodes data-structure to RoundRobin and LeastConnection\n- Configuration Changes\n  - Modified CanonicalAddress: added remotePort, removed handler and pipeline\n- NeutrinoChannel: replaced core/settings params with service\n- NeutrinoService\n  - now contains NeutrinoPools, settings, addresses PER incoming connection (ListenerSettings)\n  - absorbed poolResolver support from NeutrinoChannel\n  - Removed NeutrinoCore from NeutrinoNode/NeutrinoPools; rolled up dependencies\n- Deprecated\n  - FirstResolver\n  - Moved NeutrinoPools::getNamed() to NamedResolver::get()\n- TODO Deprecate HasConfiguration\n\n\nV0.5.5\n- TimeoutSupport\n  - Added read-idle, write-idle, write-commit, request and session timeout\n- Connection Management\n  - Moved request lifecycle into NeutrinoRequest\n  - Proper cleanup of both session and request between close-listener and NeutrinoRequest release\n  - Added connection-timeout and force-keepalive settings to reference.conf\n- Diagnostics\n  - Added audit diagnostic-handler support with audit-threshold configuration to reference.conf\n\n\n- NeutrinoRequest and AttributeClassSupport\n  - added support for request-content; this allows you to store arbitrary data at the NeutrinoRequest level.\n  - previously request-scoped content (i.e.: node) was stored indirectly at in the channel/session context\n  - this allows you generic map access without having to worry about cleanup/reset by other requests\n  - provided through a generic AttributeClassSupport trait and get/set methods which take the class requested as the key.\n  - is mixed into NeutrinoRequest but can be mixed into any scala class/trait/object\n  - helper attributes are also defined in the AttributeSupport (currently, .balancer and .node)\n\n- The NeutrinoPool is now the entry-point for both allocation and release of the request and channel\n  - added release() method which is symmetric to existing assign method, and moved release code from various other classes (Balancer and Node) into it\n  - introduced request-parameter to both assign() and release()\n  - this allows the balancer to take advantage of request-specifics\n  - along with 1) this provides a way for the balancer to extract request-context information\n\n## V0.5.4\n- Bugfix: Bucket 8080 and 8082 pools by transport\n- Feature: Support both CNAME- and WildcardCNAME- resolvers (default to CNAME for full domain match)\n...\n\n## V0.5\n- Refactor of the engine into NeutrinoSession pipeline and transport-only I/O channels"
  },
  {
    "path": "run.conf",
    "content": "[program:daemon]\ncommand=/neutrino/run.sh > /neutrino/logs/startup.log 2>&1\n"
  },
  {
    "path": "scalastyle-config.xml",
    "content": "<scalastyle>\n    <name>Scalastyle standard configuration</name>\n    <check level=\"warning\" class=\"org.scalastyle.file.FileTabChecker\" enabled=\"true\"></check>\n    <check level=\"warning\" class=\"org.scalastyle.file.FileLengthChecker\" enabled=\"true\">\n        <parameters>\n            <parameter name=\"maxFileLength\"><![CDATA[800]]></parameter>\n        </parameters>\n    </check>\n    <check level=\"warning\" class=\"org.scalastyle.file.HeaderMatchesChecker\" enabled=\"true\">\n        <parameters>\n            <parameter name=\"header\"><![CDATA[// Copyright (C) 2011-2012 the original author or authors.\n// See the LICENCE.txt file distributed with this work for additional\n// information regarding copyright ownership.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.]]></parameter>\n        </parameters>\n    </check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.SpacesAfterPlusChecker\" enabled=\"true\"></check>\n    <check level=\"warning\" class=\"org.scalastyle.file.WhitespaceEndOfLineChecker\" enabled=\"true\"></check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.SpacesBeforePlusChecker\" enabled=\"true\"></check>\n    <check level=\"warning\" class=\"org.scalastyle.file.FileLineLengthChecker\" enabled=\"true\">\n        <parameters>\n            <parameter name=\"maxLineLength\"><![CDATA[160]]></parameter>\n            <parameter name=\"tabSize\"><![CDATA[4]]></parameter>\n        </parameters>\n    </check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.ClassNamesChecker\" enabled=\"true\">\n        <parameters>\n            <parameter name=\"regex\"><![CDATA[[A-Z][A-Za-z]*]]></parameter>\n        </parameters>\n    </check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.ObjectNamesChecker\" enabled=\"true\">\n        <parameters>\n            <parameter name=\"regex\"><![CDATA[[A-Z][A-Za-z]*]]></parameter>\n        </parameters>\n    </check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.PackageObjectNamesChecker\" enabled=\"true\">\n        <parameters>\n            <parameter name=\"regex\"><![CDATA[^[a-z][A-Za-z]*$]]></parameter>\n        </parameters>\n    </check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.EqualsHashCodeChecker\" enabled=\"true\"></check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.IllegalImportsChecker\" enabled=\"true\">\n        <parameters>\n            <parameter name=\"illegalImports\"><![CDATA[sun._,java.awt._]]></parameter>\n        </parameters>\n    </check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.ParameterNumberChecker\" enabled=\"true\">\n        <parameters>\n            <parameter name=\"maxParameters\"><![CDATA[8]]></parameter>\n        </parameters>\n    </check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.MagicNumberChecker\" enabled=\"true\">\n        <parameters>\n            <parameter name=\"ignore\"><![CDATA[-1,0,1,2,3]]></parameter>\n        </parameters>\n    </check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.NoWhitespaceBeforeLeftBracketChecker\" enabled=\"true\"></check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.NoWhitespaceAfterLeftBracketChecker\" enabled=\"true\"></check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.ReturnChecker\" enabled=\"true\"></check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.NullChecker\" enabled=\"true\"></check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.NoCloneChecker\" enabled=\"true\"></check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.NoFinalizeChecker\" enabled=\"true\"></check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.CovariantEqualsChecker\" enabled=\"true\"></check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.StructuralTypeChecker\" enabled=\"true\"></check>\n    <check level=\"warning\" class=\"org.scalastyle.file.RegexChecker\" enabled=\"true\">\n        <parameters>\n            <parameter name=\"regex\"><![CDATA[println]]></parameter>\n        </parameters>\n    </check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.NumberOfTypesChecker\" enabled=\"true\">\n        <parameters>\n            <parameter name=\"maxTypes\"><![CDATA[30]]></parameter>\n        </parameters>\n    </check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.CyclomaticComplexityChecker\" enabled=\"true\">\n        <parameters>\n            <parameter name=\"maximum\"><![CDATA[10]]></parameter>\n        </parameters>\n    </check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.UppercaseLChecker\" enabled=\"true\"></check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.SimplifyBooleanExpressionChecker\" enabled=\"true\"></check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.IfBraceChecker\" enabled=\"true\">\n        <parameters>\n            <parameter name=\"singleLineAllowed\"><![CDATA[true]]></parameter>\n            <parameter name=\"doubleLineAllowed\"><![CDATA[false]]></parameter>\n        </parameters>\n    </check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.MethodLengthChecker\" enabled=\"true\">\n        <parameters>\n            <parameter name=\"maxLength\"><![CDATA[50]]></parameter>\n        </parameters>\n    </check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.MethodNamesChecker\" enabled=\"true\">\n        <parameters>\n            <parameter name=\"regex\"><![CDATA[^[a-z][A-Za-z0-9]*$]]></parameter>\n        </parameters>\n    </check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.NumberOfMethodsInTypeChecker\" enabled=\"true\">\n        <parameters>\n            <parameter name=\"maxMethods\"><![CDATA[30]]></parameter>\n        </parameters>\n    </check>\n    <check level=\"warning\" class=\"org.scalastyle.scalariform.PublicMethodsHaveTypeChecker\" enabled=\"true\"></check>\n    <check level=\"warning\" class=\"org.scalastyle.file.NewLineAtEofChecker\" enabled=\"true\"></check>\n    <check level=\"warning\" class=\"org.scalastyle.file.NoNewLineAtEofChecker\" enabled=\"false\"></check>\n</scalastyle>"
  },
  {
    "path": "slb.ctmpl",
    "content": "neutrino {\n  # Use this to enable/disable the direct SLB api\n  enable-api = true,\n}\nneutrino.datasource {\n  #Refresh time for file\n\n  refresh-period = 30s\n  datasource-reader = \"com.ebay.neutrino.datasource.FileReader\"\n}\n\nresolvers.neutrino {\n  listeners = [\n    {\n      # pool resolvers configured\n      pool-resolver = [\"cname\", \"layerseven\"],\n      # listening port of SLB\n      port = 8983,\n      protocol = \"http\" \n    },\n   {\n      # pool resolvers configured\n      pool-resolver = [\"cname\", \"layerseven\"],\n      # listening port of SLB\n      port = 8984,\n      protocol = \"http\"\n    }\n  ]\n  \n  initializers = [\n    \"com.ebay.neutrino.metrics.MetricsLifecycle\"\n  ]\n  metrics = [\n    # Support for console logging\n    { type = \"console\",  publish-period = 1m }\n  ]\n  pools = [\n     {\n      id = \"Solr\", protocol = \"http\", balancer = \"weighted-round-robin\", port = \"8983\",\n      servers = [\n\n         {{range service \"Solr\"}}\n          {{ if .Tags | contains \"blue\" }}\n         { id=\"{{.Node}}\" , host=\"{{.Address}}\", port=\"{{.Port}}\", weight = {{ key \"solr/config/blue/weight\"}} },\n          {{end}}{{end}}\n           {{range service \"Solr\"}} \n          {{ if .Tags | contains \"green\" }}\n         { id=\"{{.Node}}\" , host=\"{{.Address}}\", port=\"{{.Port}}\", weight = {{ key \"solr/config/green/weight\"}}},\n          {{end}}{{end}}\n\n]\naddresses = [\n        {host=\"10.142.0.25\" }\n      ]\n\n   }\n#Direct-Component Testing Pool\n   {\n      id = \"DirectTest\", protocol = \"http\", balancer = \"lc\", port = \"8984\",\n      servers = [\n\n           {{range service \"Solr\"}}\n          {{$keyname := key \"solr/config/testing/env\"}}    \n           {{ if .Tags  | contains $keyname }}\n         { id=\"{{.Node}}\" , host=\"{{.Address}}\", port=\"{{.Port}}\"},\n          {{end}}{{end}}\n\n]\naddresses = [\n        {host=\"10.142.0.25\" }\n      ]\n\n   } \n\n  ] }\t\t\t\t\t\t\t\t\t\n"
  },
  {
    "path": "sonar-project.properties",
    "content": "sonar.projectKey=com.ebay.neutrino\nsonar.projectName=neutrino-core\nsonar.projectVersion=0.5.5\nsonar.language=scala\nsonar.dynamicAnalysis=reuseReports\n\nsonar.scoverage.reportPath=target/scala-2.10/scoverage-report/scoverage.xml\n\n# Some properties that will be inherited by the modules\nsonar.sources=src/main/scala\n\n# List of the module identifiers\n#sonar.modules=core"
  },
  {
    "path": "src/main/java/com/ebay/neutrino/channel/DefaultChannelUtil.java",
    "content": "package com.ebay.neutrino.channel;\n\nimport com.google.common.primitives.Ints;\nimport com.google.common.primitives.Longs;\nimport io.netty.util.internal.EmptyArrays;\nimport io.netty.util.internal.PlatformDependent;\nimport io.netty.util.internal.SystemPropertyUtil;\nimport io.netty.util.internal.ThreadLocalRandom;\nimport io.netty.util.internal.logging.InternalLogger;\nimport io.netty.util.internal.logging.InternalLoggerFactory;\n\nimport java.lang.reflect.Method;\nimport java.net.InetAddress;\nimport java.net.NetworkInterface;\nimport java.net.SocketException;\nimport java.net.UnknownHostException;\nimport java.util.Arrays;\nimport java.util.Enumeration;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.regex.Pattern;\n\n/**\n * Gross shim to allow us to package-access DefaultChannelId.\n *\n */\npublic abstract class DefaultChannelUtil {\n    // Prevent instantiation\n    private DefaultChannelUtil() {}\n\n    static final long serialVersionUID = 3884076183504074063L;\n    static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelUtil.class);\n\n    // Maximal value for 64bit systems is 2^22.  See man 5 proc.\n    // See https://github.com/netty/netty/issues/2706\n    private static final int MAX_PROCESS_ID = 4194304;\n    private static final Pattern MACHINE_ID_PATTERN = Pattern.compile(\"^(?:[0-9a-fA-F][:-]?){6,8}$\");\n    private static final byte[] MACHINE_ID;\n    private static final int PROCESS_ID;\n\n    static final int MACHINE_ID_LEN = 8;\n    static final int PROCESS_ID_LEN = 4;\n    static final int SEQUENCE_LEN = 4;\n    static final int TIMESTAMP_LEN = 8;\n    static final int RANDOM_LEN = 4;\n\n    private static final AtomicInteger nextSequence = new AtomicInteger();\n\n    public static final int DATA_LEN = MACHINE_ID_LEN + PROCESS_ID_LEN + SEQUENCE_LEN + TIMESTAMP_LEN + RANDOM_LEN;\n\n\n    static {\n        int processId = -1;\n        String customProcessId = SystemPropertyUtil.get(\"io.netty.processId\");\n        if (customProcessId != null) {\n            try {\n                processId = Integer.parseInt(customProcessId);\n            } catch (NumberFormatException e) {\n                // Malformed input.\n            }\n\n            if (processId < 0 || processId > MAX_PROCESS_ID) {\n                processId = -1;\n                logger.warn(\"-Dio.netty.processId: {} (malformed)\", customProcessId);\n            } else if (logger.isDebugEnabled()) {\n                logger.debug(\"-Dio.netty.processId: {} (user-set)\", processId);\n            }\n        }\n\n        if (processId < 0) {\n            processId = defaultProcessId();\n            if (logger.isDebugEnabled()) {\n                logger.debug(\"-Dio.netty.processId: {} (auto-detected)\", processId);\n            }\n        }\n\n        PROCESS_ID = processId;\n\n        byte[] machineId = null;\n        String customMachineId = SystemPropertyUtil.get(\"io.netty.machineId\");\n        if (customMachineId != null) {\n            if (MACHINE_ID_PATTERN.matcher(customMachineId).matches()) {\n                machineId = parseMachineId(customMachineId);\n                logger.debug(\"-Dio.netty.machineId: {} (user-set)\", customMachineId);\n            } else {\n                logger.warn(\"-Dio.netty.machineId: {} (malformed)\", customMachineId);\n            }\n        }\n\n        if (machineId == null) {\n            machineId = defaultMachineId();\n            if (logger.isDebugEnabled()) {\n                logger.debug(\"-Dio.netty.machineId: {} (auto-detected)\", formatAddress(machineId));\n            }\n        }\n\n        MACHINE_ID = machineId;\n    }\n\n    @SuppressWarnings(\"DynamicRegexReplaceableByCompiledPattern\")\n    private static byte[] parseMachineId(String value) {\n        // Strip separators.\n        value = value.replaceAll(\"[:-]\", \"\");\n\n        byte[] machineId = new byte[MACHINE_ID_LEN];\n        for (int i = 0; i < value.length(); i += 2) {\n            machineId[i] = (byte) Integer.parseInt(value.substring(i, i + 2), 16);\n        }\n\n        return machineId;\n    }\n\n    private static byte[] defaultMachineId() {\n        // Find the best MAC address available.\n        final byte[] NOT_FOUND = { -1 };\n        byte[] bestMacAddr = NOT_FOUND;\n        InetAddress bestInetAddr = null;\n        try {\n            bestInetAddr = InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 });\n        } catch (UnknownHostException e) {\n            // Never happens.\n            PlatformDependent.throwException(e);\n        }\n\n        // Retrieve the list of available network interfaces.\n        Map<NetworkInterface, InetAddress> ifaces = new LinkedHashMap<>();\n        try {\n            for (Enumeration<NetworkInterface> i = NetworkInterface.getNetworkInterfaces(); i.hasMoreElements();) {\n                NetworkInterface iface = i.nextElement();\n                // Use the interface with proper INET addresses only.\n                Enumeration<InetAddress> addrs = iface.getInetAddresses();\n                if (addrs.hasMoreElements()) {\n                    InetAddress a = addrs.nextElement();\n                    if (!a.isLoopbackAddress()) {\n                        ifaces.put(iface, a);\n                    }\n                }\n            }\n        } catch (SocketException e) {\n            logger.warn(\"Failed to retrieve the list of available network interfaces\", e);\n        }\n\n        for (Entry<NetworkInterface, InetAddress> entry: ifaces.entrySet()) {\n            NetworkInterface iface = entry.getKey();\n            InetAddress inetAddr = entry.getValue();\n            if (iface.isVirtual()) {\n                continue;\n            }\n\n            byte[] macAddr;\n            try {\n                macAddr = iface.getHardwareAddress();\n            } catch (SocketException e) {\n                logger.debug(\"Failed to get the hardware address of a network interface: {}\", iface, e);\n                continue;\n            }\n\n            boolean replace = false;\n            int res = compareAddresses(bestMacAddr, macAddr);\n            if (res < 0) {\n                // Found a better MAC address.\n                replace = true;\n            } else if (res == 0) {\n                // Two MAC addresses are of pretty much same quality.\n                res = compareAddresses(bestInetAddr, inetAddr);\n                if (res < 0) {\n                    // Found a MAC address with better INET address.\n                    replace = true;\n                } else if (res == 0) {\n                    // Cannot tell the difference.  Choose the longer one.\n                    if (bestMacAddr.length < macAddr.length) {\n                        replace = true;\n                    }\n                }\n            }\n\n            if (replace) {\n                bestMacAddr = macAddr;\n                bestInetAddr = inetAddr;\n            }\n        }\n\n        if (bestMacAddr == NOT_FOUND) {\n            bestMacAddr = new byte[MACHINE_ID_LEN];\n            ThreadLocalRandom.current().nextBytes(bestMacAddr);\n            logger.warn(\n                    \"Failed to find a usable hardware address from the network interfaces; using random bytes: {}\",\n                    formatAddress(bestMacAddr));\n        }\n\n        switch (bestMacAddr.length) {\n            case 6: // EUI-48 - convert to EUI-64\n                byte[] newAddr = new byte[MACHINE_ID_LEN];\n                System.arraycopy(bestMacAddr, 0, newAddr, 0, 3);\n                newAddr[3] = (byte) 0xFF;\n                newAddr[4] = (byte) 0xFE;\n                System.arraycopy(bestMacAddr, 3, newAddr, 5, 3);\n                bestMacAddr = newAddr;\n                break;\n            default: // Unknown\n                bestMacAddr = Arrays.copyOf(bestMacAddr, MACHINE_ID_LEN);\n        }\n\n        return bestMacAddr;\n    }\n\n    /**\n     * @return positive - current is better, 0 - cannot tell from MAC addr, negative - candidate is better.\n     */\n    private static int compareAddresses(byte[] current, byte[] candidate) {\n        if (candidate == null) {\n            return 1;\n        }\n\n        // Must be EUI-48 or longer.\n        if (candidate.length < 6) {\n            return 1;\n        }\n\n        // Must not be filled with only 0 and 1.\n        boolean onlyZeroAndOne = true;\n        for (byte b: candidate) {\n            if (b != 0 && b != 1) {\n                onlyZeroAndOne = false;\n                break;\n            }\n        }\n\n        if (onlyZeroAndOne) {\n            return 1;\n        }\n\n        // Must not be a multicast address\n        if ((candidate[0] & 1) != 0) {\n            return 1;\n        }\n\n        // Prefer globally unique address.\n        if ((current[0] & 2) == 0) {\n            if ((candidate[0] & 2) == 0) {\n                // Both current and candidate are globally unique addresses.\n                return 0;\n            } else {\n                // Only current is globally unique.\n                return 1;\n            }\n        } else {\n            if ((candidate[0] & 2) == 0) {\n                // Only candidate is globally unique.\n                return -1;\n            } else {\n                // Both current and candidate are non-unique.\n                return 0;\n            }\n        }\n    }\n\n    /**\n     * @return positive - current is better, 0 - cannot tell, negative - candidate is better\n     */\n    private static int compareAddresses(InetAddress current, InetAddress candidate) {\n        return scoreAddress(current) - scoreAddress(candidate);\n    }\n\n    private static int scoreAddress(InetAddress addr) {\n        if (addr.isAnyLocalAddress()) {\n            return 0;\n        }\n        if (addr.isMulticastAddress()) {\n            return 1;\n        }\n        if (addr.isLinkLocalAddress()) {\n            return 2;\n        }\n        if (addr.isSiteLocalAddress()) {\n            return 3;\n        }\n\n        return 4;\n    }\n\n    private static String formatAddress(byte[] addr) {\n        StringBuilder buf = new StringBuilder(24);\n        for (byte b: addr) {\n            buf.append(String.format(\"%02x:\", b & 0xff));\n        }\n        return buf.substring(0, buf.length() - 1);\n    }\n\n    private static int defaultProcessId() {\n        final ClassLoader loader = PlatformDependent.getSystemClassLoader();\n        String value;\n        try {\n            // Invoke java.lang.management.ManagementFactory.getRuntimeMXBean().getName()\n            Class<?> mgmtFactoryType = Class.forName(\"java.lang.management.ManagementFactory\", true, loader);\n            Class<?> runtimeMxBeanType = Class.forName(\"java.lang.management.RuntimeMXBean\", true, loader);\n\n            Method getRuntimeMXBean = mgmtFactoryType.getMethod(\"getRuntimeMXBean\", EmptyArrays.EMPTY_CLASSES);\n            Object bean = getRuntimeMXBean.invoke(null, EmptyArrays.EMPTY_OBJECTS);\n            Method getName = runtimeMxBeanType.getDeclaredMethod(\"getName\", EmptyArrays.EMPTY_CLASSES);\n            value = (String) getName.invoke(bean, EmptyArrays.EMPTY_OBJECTS);\n        } catch (Exception e) {\n            logger.debug(\"Could not invoke ManagementFactory.getRuntimeMXBean().getName(); Android?\", e);\n            try {\n                // Invoke android.os.Process.myPid()\n                Class<?> processType = Class.forName(\"android.os.Process\", true, loader);\n                Method myPid = processType.getMethod(\"myPid\", EmptyArrays.EMPTY_CLASSES);\n                value = myPid.invoke(null, EmptyArrays.EMPTY_OBJECTS).toString();\n            } catch (Exception e2) {\n                logger.debug(\"Could not invoke Process.myPid(); not Android?\", e2);\n                value = \"\";\n            }\n        }\n\n        int atIndex = value.indexOf('@');\n        if (atIndex >= 0) {\n            value = value.substring(0, atIndex);\n        }\n\n        int pid;\n        try {\n            pid = Integer.parseInt(value);\n        } catch (NumberFormatException e) {\n            // value did not contain an integer.\n            pid = -1;\n        }\n\n        if (pid < 0 || pid > MAX_PROCESS_ID) {\n            pid = ThreadLocalRandom.current().nextInt(MAX_PROCESS_ID + 1);\n            logger.warn(\"Failed to find the current process ID from '{}'; using a random value: {}\",  value, pid);\n        }\n\n        return pid;\n    }\n\n\n    static final int writeInt(byte[] data, int i, int value) {\n        data[i ++] = (byte) (value >>> 24);\n        data[i ++] = (byte) (value >>> 16);\n        data[i ++] = (byte) (value >>> 8);\n        data[i ++] = (byte) value;\n        return i;\n    }\n\n    static final int writeLong(byte[] data, int i, long value) {\n        data[i ++] = (byte) (value >>> 56);\n        data[i ++] = (byte) (value >>> 48);\n        data[i ++] = (byte) (value >>> 40);\n        data[i ++] = (byte) (value >>> 32);\n        data[i ++] = (byte) (value >>> 24);\n        data[i ++] = (byte) (value >>> 16);\n        data[i ++] = (byte) (value >>> 8);\n        data[i ++] = (byte) value;\n        return i;\n    }\n\n    static final int readInt(byte[] data, int i) {\n        return Ints.fromBytes(data[i], data[i+1], data[i+2], data[i+3]);\n    }\n\n    static final long readLong(byte[] data, int i) {\n        return Longs.fromBytes(data[i], data[i+1], data[i+2], data[i+3], data[i+4], data[i+5], data[i+6], data[i+7]);\n    }\n\n\n    /**\n     *\n     * @param randomHashCode\n     * @return\n     */\n    public static final byte[] newInstanceBytes() {\n        // Create a new instance-buffer\n        final byte[] data = new byte[MACHINE_ID_LEN + PROCESS_ID_LEN + SEQUENCE_LEN + TIMESTAMP_LEN + RANDOM_LEN];\n        int i = 0;\n\n        // machineId\n        System.arraycopy(MACHINE_ID, 0, data, i, MACHINE_ID_LEN);\n        i += MACHINE_ID_LEN;\n\n        // processId\n        i = writeInt(data, i, PROCESS_ID);\n\n        // sequence\n        i = writeInt(data, i, nextSequence.getAndIncrement());\n\n        // timestamp (kind of)\n        i = writeLong(data, i, Long.reverse(System.nanoTime()) ^ System.currentTimeMillis());\n\n        // random\n        // random\n        int random = ThreadLocalRandom.current().nextInt();\n        i = writeInt(data, i, random);\n\n        assert i == data.length;\n        return data;\n    }\n}"
  },
  {
    "path": "src/main/java/io/netty/handler/codec/http/NettyHttpHeaders.java",
    "content": "package io.netty.handler.codec.http;\n\nimport io.netty.buffer.ByteBuf;\n\n/**\n * A copy-and-paste reimpleemntation of the Netty HttpHeaders, to decouple\n * it from the HTTP handler.\n *\n * We want to be able to multiplex it on the channel. Note that we would much\n * rather just reuse the existing Netty handler functionality, but it is not\n * easily extensible.\n *\n * @see HttpHeaders\n *\n *\n * Provides the constants for the standard HTTP header names and values and\n * commonly used utility methods that accesses an {@link HttpMessage}.\n */\npublic abstract class NettyHttpHeaders {\n    // Prevent instantiation\n    private NettyHttpHeaders() {}\n\n\n    // Just delegate to the package Netty version (dumb)\n    public static void encode(HttpHeaders headers, ByteBuf buf) throws Exception {\n        HttpHeaders.encode(headers, buf);\n    }\n\n\n    // Just delegate to the package Netty version (dumb)\n    public static void encodeAscii0(CharSequence seq, ByteBuf buf) {\n        HttpHeaders.encodeAscii0(seq, buf);\n    }\n}\n"
  },
  {
    "path": "src/main/resources/css/bootstrap-theme.css",
    "content": "/*!\n * Bootstrap v3.3.2 (http://getbootstrap.com)\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n  text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);\n}\n.btn-default:active,\n.btn-primary:active,\n.btn-success:active,\n.btn-info:active,\n.btn-warning:active,\n.btn-danger:active,\n.btn-default.active,\n.btn-primary.active,\n.btn-success.active,\n.btn-info.active,\n.btn-warning.active,\n.btn-danger.active {\n  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n          box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n}\n.btn-default .badge,\n.btn-primary .badge,\n.btn-success .badge,\n.btn-info .badge,\n.btn-warning .badge,\n.btn-danger .badge {\n  text-shadow: none;\n}\n.btn:active,\n.btn.active {\n  background-image: none;\n}\n.btn-default {\n  text-shadow: 0 1px 0 #fff;\n  background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);\n  background-image:      -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));\n  background-image:         linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-color: #dbdbdb;\n  border-color: #ccc;\n}\n.btn-default:hover,\n.btn-default:focus {\n  background-color: #e0e0e0;\n  background-position: 0 -15px;\n}\n.btn-default:active,\n.btn-default.active {\n  background-color: #e0e0e0;\n  border-color: #dbdbdb;\n}\n.btn-default.disabled,\n.btn-default:disabled,\n.btn-default[disabled] {\n  background-color: #e0e0e0;\n  background-image: none;\n}\n.btn-primary {\n  background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);\n  background-image:      -o-linear-gradient(top, #337ab7 0%, #265a88 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));\n  background-image:         linear-gradient(to bottom, #337ab7 0%, #265a88 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-color: #245580;\n}\n.btn-primary:hover,\n.btn-primary:focus {\n  background-color: #265a88;\n  background-position: 0 -15px;\n}\n.btn-primary:active,\n.btn-primary.active {\n  background-color: #265a88;\n  border-color: #245580;\n}\n.btn-primary.disabled,\n.btn-primary:disabled,\n.btn-primary[disabled] {\n  background-color: #265a88;\n  background-image: none;\n}\n.btn-success {\n  background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);\n  background-image:      -o-linear-gradient(top, #5cb85c 0%, #419641 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));\n  background-image:         linear-gradient(to bottom, #5cb85c 0%, #419641 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-color: #3e8f3e;\n}\n.btn-success:hover,\n.btn-success:focus {\n  background-color: #419641;\n  background-position: 0 -15px;\n}\n.btn-success:active,\n.btn-success.active {\n  background-color: #419641;\n  border-color: #3e8f3e;\n}\n.btn-success.disabled,\n.btn-success:disabled,\n.btn-success[disabled] {\n  background-color: #419641;\n  background-image: none;\n}\n.btn-info {\n  background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n  background-image:      -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));\n  background-image:         linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-color: #28a4c9;\n}\n.btn-info:hover,\n.btn-info:focus {\n  background-color: #2aabd2;\n  background-position: 0 -15px;\n}\n.btn-info:active,\n.btn-info.active {\n  background-color: #2aabd2;\n  border-color: #28a4c9;\n}\n.btn-info.disabled,\n.btn-info:disabled,\n.btn-info[disabled] {\n  background-color: #2aabd2;\n  background-image: none;\n}\n.btn-warning {\n  background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n  background-image:      -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));\n  background-image:         linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-color: #e38d13;\n}\n.btn-warning:hover,\n.btn-warning:focus {\n  background-color: #eb9316;\n  background-position: 0 -15px;\n}\n.btn-warning:active,\n.btn-warning.active {\n  background-color: #eb9316;\n  border-color: #e38d13;\n}\n.btn-warning.disabled,\n.btn-warning:disabled,\n.btn-warning[disabled] {\n  background-color: #eb9316;\n  background-image: none;\n}\n.btn-danger {\n  background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n  background-image:      -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));\n  background-image:         linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-color: #b92c28;\n}\n.btn-danger:hover,\n.btn-danger:focus {\n  background-color: #c12e2a;\n  background-position: 0 -15px;\n}\n.btn-danger:active,\n.btn-danger.active {\n  background-color: #c12e2a;\n  border-color: #b92c28;\n}\n.btn-danger.disabled,\n.btn-danger:disabled,\n.btn-danger[disabled] {\n  background-color: #c12e2a;\n  background-image: none;\n}\n.thumbnail,\n.img-thumbnail {\n  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);\n          box-shadow: 0 1px 2px rgba(0, 0, 0, .075);\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n  background-color: #e8e8e8;\n  background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n  background-image:      -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));\n  background-image:         linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n  background-repeat: repeat-x;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n  background-color: #2e6da4;\n  background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n  background-image:      -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));\n  background-image:         linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n  background-repeat: repeat-x;\n}\n.navbar-default {\n  background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);\n  background-image:      -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));\n  background-image:         linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .active > a {\n  background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n  background-image:      -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));\n  background-image:         linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);\n  background-repeat: repeat-x;\n  -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);\n}\n.navbar-brand,\n.navbar-nav > li > a {\n  text-shadow: 0 1px 0 rgba(255, 255, 255, .25);\n}\n.navbar-inverse {\n  background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);\n  background-image:      -o-linear-gradient(top, #3c3c3c 0%, #222 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));\n  background-image:         linear-gradient(to bottom, #3c3c3c 0%, #222 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .active > a {\n  background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n  background-image:      -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));\n  background-image:         linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);\n  background-repeat: repeat-x;\n  -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);\n          box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);\n}\n.navbar-inverse .navbar-brand,\n.navbar-inverse .navbar-nav > li > a {\n  text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);\n}\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n  border-radius: 0;\n}\n@media (max-width: 767px) {\n  .navbar .navbar-nav .open .dropdown-menu > .active > a,\n  .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,\n  .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {\n    color: #fff;\n    background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n    background-image:      -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n    background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));\n    background-image:         linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n    background-repeat: repeat-x;\n  }\n}\n.alert {\n  text-shadow: 0 1px 0 rgba(255, 255, 255, .2);\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);\n          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);\n}\n.alert-success {\n  background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n  background-image:      -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));\n  background-image:         linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);\n  background-repeat: repeat-x;\n  border-color: #b2dba1;\n}\n.alert-info {\n  background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n  background-image:      -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));\n  background-image:         linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);\n  background-repeat: repeat-x;\n  border-color: #9acfea;\n}\n.alert-warning {\n  background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n  background-image:      -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));\n  background-image:         linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);\n  background-repeat: repeat-x;\n  border-color: #f5e79e;\n}\n.alert-danger {\n  background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n  background-image:      -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));\n  background-image:         linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);\n  background-repeat: repeat-x;\n  border-color: #dca7a7;\n}\n.progress {\n  background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n  background-image:      -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));\n  background-image:         linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);\n  background-repeat: repeat-x;\n}\n.progress-bar {\n  background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);\n  background-image:      -o-linear-gradient(top, #337ab7 0%, #286090 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));\n  background-image:         linear-gradient(to bottom, #337ab7 0%, #286090 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);\n  background-repeat: repeat-x;\n}\n.progress-bar-success {\n  background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n  background-image:      -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));\n  background-image:         linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);\n  background-repeat: repeat-x;\n}\n.progress-bar-info {\n  background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n  background-image:      -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));\n  background-image:         linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);\n  background-repeat: repeat-x;\n}\n.progress-bar-warning {\n  background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n  background-image:      -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));\n  background-image:         linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);\n  background-repeat: repeat-x;\n}\n.progress-bar-danger {\n  background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n  background-image:      -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));\n  background-image:         linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);\n  background-repeat: repeat-x;\n}\n.progress-bar-striped {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n}\n.list-group {\n  border-radius: 4px;\n  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);\n          box-shadow: 0 1px 2px rgba(0, 0, 0, .075);\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n  text-shadow: 0 -1px 0 #286090;\n  background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n  background-image:      -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));\n  background-image:         linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);\n  background-repeat: repeat-x;\n  border-color: #2b669a;\n}\n.list-group-item.active .badge,\n.list-group-item.active:hover .badge,\n.list-group-item.active:focus .badge {\n  text-shadow: none;\n}\n.panel {\n  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);\n          box-shadow: 0 1px 2px rgba(0, 0, 0, .05);\n}\n.panel-default > .panel-heading {\n  background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n  background-image:      -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));\n  background-image:         linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n  background-repeat: repeat-x;\n}\n.panel-primary > .panel-heading {\n  background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n  background-image:      -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));\n  background-image:         linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n  background-repeat: repeat-x;\n}\n.panel-success > .panel-heading {\n  background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n  background-image:      -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));\n  background-image:         linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);\n  background-repeat: repeat-x;\n}\n.panel-info > .panel-heading {\n  background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n  background-image:      -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));\n  background-image:         linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);\n  background-repeat: repeat-x;\n}\n.panel-warning > .panel-heading {\n  background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n  background-image:      -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));\n  background-image:         linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);\n  background-repeat: repeat-x;\n}\n.panel-danger > .panel-heading {\n  background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n  background-image:      -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));\n  background-image:         linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);\n  background-repeat: repeat-x;\n}\n.well {\n  background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n  background-image:      -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));\n  background-image:         linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);\n  background-repeat: repeat-x;\n  border-color: #dcdcdc;\n  -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);\n          box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);\n}\n/*# sourceMappingURL=bootstrap-theme.css.map */\n"
  },
  {
    "path": "src/main/resources/css/bootstrap.css",
    "content": "/*!\n * Bootstrap v3.3.2 (http://getbootstrap.com)\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n/*! normalize.css v3.0.2 | MIT License | git.io/normalize */\nhtml {\n  font-family: sans-serif;\n  -webkit-text-size-adjust: 100%;\n      -ms-text-size-adjust: 100%;\n}\nbody {\n  margin: 0;\n}\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n  display: block;\n}\naudio,\ncanvas,\nprogress,\nvideo {\n  display: inline-block;\n  vertical-align: baseline;\n}\naudio:not([controls]) {\n  display: none;\n  height: 0;\n}\n[hidden],\ntemplate {\n  display: none;\n}\na {\n  background-color: transparent;\n}\na:active,\na:hover {\n  outline: 0;\n}\nabbr[title] {\n  border-bottom: 1px dotted;\n}\nb,\nstrong {\n  font-weight: bold;\n}\ndfn {\n  font-style: italic;\n}\nh1 {\n  margin: .67em 0;\n  font-size: 2em;\n}\nmark {\n  color: #000;\n  background: #ff0;\n}\nsmall {\n  font-size: 80%;\n}\nsub,\nsup {\n  position: relative;\n  font-size: 75%;\n  line-height: 0;\n  vertical-align: baseline;\n}\nsup {\n  top: -.5em;\n}\nsub {\n  bottom: -.25em;\n}\nimg {\n  border: 0;\n}\nsvg:not(:root) {\n  overflow: hidden;\n}\nfigure {\n  margin: 1em 40px;\n}\nhr {\n  height: 0;\n  -webkit-box-sizing: content-box;\n     -moz-box-sizing: content-box;\n          box-sizing: content-box;\n}\npre {\n  overflow: auto;\n}\ncode,\nkbd,\npre,\nsamp {\n  font-family: monospace, monospace;\n  font-size: 1em;\n}\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  margin: 0;\n  font: inherit;\n  color: inherit;\n}\nbutton {\n  overflow: visible;\n}\nbutton,\nselect {\n  text-transform: none;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n  -webkit-appearance: button;\n  cursor: pointer;\n}\nbutton[disabled],\nhtml input[disabled] {\n  cursor: default;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n  padding: 0;\n  border: 0;\n}\ninput {\n  line-height: normal;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n  -webkit-box-sizing: border-box;\n     -moz-box-sizing: border-box;\n          box-sizing: border-box;\n  padding: 0;\n}\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n  height: auto;\n}\ninput[type=\"search\"] {\n  -webkit-box-sizing: content-box;\n     -moz-box-sizing: content-box;\n          box-sizing: content-box;\n  -webkit-appearance: textfield;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\nfieldset {\n  padding: .35em .625em .75em;\n  margin: 0 2px;\n  border: 1px solid #c0c0c0;\n}\nlegend {\n  padding: 0;\n  border: 0;\n}\ntextarea {\n  overflow: auto;\n}\noptgroup {\n  font-weight: bold;\n}\ntable {\n  border-spacing: 0;\n  border-collapse: collapse;\n}\ntd,\nth {\n  padding: 0;\n}\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n@media print {\n  *,\n  *:before,\n  *:after {\n    color: #000 !important;\n    text-shadow: none !important;\n    background: transparent !important;\n    -webkit-box-shadow: none !important;\n            box-shadow: none !important;\n  }\n  a,\n  a:visited {\n    text-decoration: underline;\n  }\n  a[href]:after {\n    content: \" (\" attr(href) \")\";\n  }\n  abbr[title]:after {\n    content: \" (\" attr(title) \")\";\n  }\n  a[href^=\"#\"]:after,\n  a[href^=\"javascript:\"]:after {\n    content: \"\";\n  }\n  pre,\n  blockquote {\n    border: 1px solid #999;\n\n    page-break-inside: avoid;\n  }\n  thead {\n    display: table-header-group;\n  }\n  tr,\n  img {\n    page-break-inside: avoid;\n  }\n  img {\n    max-width: 100% !important;\n  }\n  p,\n  h2,\n  h3 {\n    orphans: 3;\n    widows: 3;\n  }\n  h2,\n  h3 {\n    page-break-after: avoid;\n  }\n  select {\n    background: #fff !important;\n  }\n  .navbar {\n    display: none;\n  }\n  .btn > .caret,\n  .dropup > .btn > .caret {\n    border-top-color: #000 !important;\n  }\n  .label {\n    border: 1px solid #000;\n  }\n  .table {\n    border-collapse: collapse !important;\n  }\n  .table td,\n  .table th {\n    background-color: #fff !important;\n  }\n  .table-bordered th,\n  .table-bordered td {\n    border: 1px solid #ddd !important;\n  }\n}\n@font-face {\n  font-family: 'Glyphicons Halflings';\n\n  src: url('../fonts/glyphicons-halflings-regular.eot');\n  src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');\n}\n.glyphicon {\n  position: relative;\n  top: 1px;\n  display: inline-block;\n  font-family: 'Glyphicons Halflings';\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1;\n\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n.glyphicon-asterisk:before {\n  content: \"\\2a\";\n}\n.glyphicon-plus:before {\n  content: \"\\2b\";\n}\n.glyphicon-euro:before,\n.glyphicon-eur:before {\n  content: \"\\20ac\";\n}\n.glyphicon-minus:before {\n  content: \"\\2212\";\n}\n.glyphicon-cloud:before {\n  content: \"\\2601\";\n}\n.glyphicon-envelope:before {\n  content: \"\\2709\";\n}\n.glyphicon-pencil:before {\n  content: \"\\270f\";\n}\n.glyphicon-glass:before {\n  content: \"\\e001\";\n}\n.glyphicon-music:before {\n  content: \"\\e002\";\n}\n.glyphicon-search:before {\n  content: \"\\e003\";\n}\n.glyphicon-heart:before {\n  content: \"\\e005\";\n}\n.glyphicon-star:before {\n  content: \"\\e006\";\n}\n.glyphicon-star-empty:before {\n  content: \"\\e007\";\n}\n.glyphicon-user:before {\n  content: \"\\e008\";\n}\n.glyphicon-film:before {\n  content: \"\\e009\";\n}\n.glyphicon-th-large:before {\n  content: \"\\e010\";\n}\n.glyphicon-th:before {\n  content: \"\\e011\";\n}\n.glyphicon-th-list:before {\n  content: \"\\e012\";\n}\n.glyphicon-ok:before {\n  content: \"\\e013\";\n}\n.glyphicon-remove:before {\n  content: \"\\e014\";\n}\n.glyphicon-zoom-in:before {\n  content: \"\\e015\";\n}\n.glyphicon-zoom-out:before {\n  content: \"\\e016\";\n}\n.glyphicon-off:before {\n  content: \"\\e017\";\n}\n.glyphicon-signal:before {\n  content: \"\\e018\";\n}\n.glyphicon-cog:before {\n  content: \"\\e019\";\n}\n.glyphicon-trash:before {\n  content: \"\\e020\";\n}\n.glyphicon-home:before {\n  content: \"\\e021\";\n}\n.glyphicon-file:before {\n  content: \"\\e022\";\n}\n.glyphicon-time:before {\n  content: \"\\e023\";\n}\n.glyphicon-road:before {\n  content: \"\\e024\";\n}\n.glyphicon-download-alt:before {\n  content: \"\\e025\";\n}\n.glyphicon-download:before {\n  content: \"\\e026\";\n}\n.glyphicon-upload:before {\n  content: \"\\e027\";\n}\n.glyphicon-inbox:before {\n  content: \"\\e028\";\n}\n.glyphicon-play-circle:before {\n  content: \"\\e029\";\n}\n.glyphicon-repeat:before {\n  content: \"\\e030\";\n}\n.glyphicon-refresh:before {\n  content: \"\\e031\";\n}\n.glyphicon-list-alt:before {\n  content: \"\\e032\";\n}\n.glyphicon-lock:before {\n  content: \"\\e033\";\n}\n.glyphicon-flag:before {\n  content: \"\\e034\";\n}\n.glyphicon-headphones:before {\n  content: \"\\e035\";\n}\n.glyphicon-volume-off:before {\n  content: \"\\e036\";\n}\n.glyphicon-volume-down:before {\n  content: \"\\e037\";\n}\n.glyphicon-volume-up:before {\n  content: \"\\e038\";\n}\n.glyphicon-qrcode:before {\n  content: \"\\e039\";\n}\n.glyphicon-barcode:before {\n  content: \"\\e040\";\n}\n.glyphicon-tag:before {\n  content: \"\\e041\";\n}\n.glyphicon-tags:before {\n  content: \"\\e042\";\n}\n.glyphicon-book:before {\n  content: \"\\e043\";\n}\n.glyphicon-bookmark:before {\n  content: \"\\e044\";\n}\n.glyphicon-print:before {\n  content: \"\\e045\";\n}\n.glyphicon-camera:before {\n  content: \"\\e046\";\n}\n.glyphicon-font:before {\n  content: \"\\e047\";\n}\n.glyphicon-bold:before {\n  content: \"\\e048\";\n}\n.glyphicon-italic:before {\n  content: \"\\e049\";\n}\n.glyphicon-text-height:before {\n  content: \"\\e050\";\n}\n.glyphicon-text-width:before {\n  content: \"\\e051\";\n}\n.glyphicon-align-left:before {\n  content: \"\\e052\";\n}\n.glyphicon-align-center:before {\n  content: \"\\e053\";\n}\n.glyphicon-align-right:before {\n  content: \"\\e054\";\n}\n.glyphicon-align-justify:before {\n  content: \"\\e055\";\n}\n.glyphicon-list:before {\n  content: \"\\e056\";\n}\n.glyphicon-indent-left:before {\n  content: \"\\e057\";\n}\n.glyphicon-indent-right:before {\n  content: \"\\e058\";\n}\n.glyphicon-facetime-video:before {\n  content: \"\\e059\";\n}\n.glyphicon-picture:before {\n  content: \"\\e060\";\n}\n.glyphicon-map-marker:before {\n  content: \"\\e062\";\n}\n.glyphicon-adjust:before {\n  content: \"\\e063\";\n}\n.glyphicon-tint:before {\n  content: \"\\e064\";\n}\n.glyphicon-edit:before {\n  content: \"\\e065\";\n}\n.glyphicon-share:before {\n  content: \"\\e066\";\n}\n.glyphicon-check:before {\n  content: \"\\e067\";\n}\n.glyphicon-move:before {\n  content: \"\\e068\";\n}\n.glyphicon-step-backward:before {\n  content: \"\\e069\";\n}\n.glyphicon-fast-backward:before {\n  content: \"\\e070\";\n}\n.glyphicon-backward:before {\n  content: \"\\e071\";\n}\n.glyphicon-play:before {\n  content: \"\\e072\";\n}\n.glyphicon-pause:before {\n  content: \"\\e073\";\n}\n.glyphicon-stop:before {\n  content: \"\\e074\";\n}\n.glyphicon-forward:before {\n  content: \"\\e075\";\n}\n.glyphicon-fast-forward:before {\n  content: \"\\e076\";\n}\n.glyphicon-step-forward:before {\n  content: \"\\e077\";\n}\n.glyphicon-eject:before {\n  content: \"\\e078\";\n}\n.glyphicon-chevron-left:before {\n  content: \"\\e079\";\n}\n.glyphicon-chevron-right:before {\n  content: \"\\e080\";\n}\n.glyphicon-plus-sign:before {\n  content: \"\\e081\";\n}\n.glyphicon-minus-sign:before {\n  content: \"\\e082\";\n}\n.glyphicon-remove-sign:before {\n  content: \"\\e083\";\n}\n.glyphicon-ok-sign:before {\n  content: \"\\e084\";\n}\n.glyphicon-question-sign:before {\n  content: \"\\e085\";\n}\n.glyphicon-info-sign:before {\n  content: \"\\e086\";\n}\n.glyphicon-screenshot:before {\n  content: \"\\e087\";\n}\n.glyphicon-remove-circle:before {\n  content: \"\\e088\";\n}\n.glyphicon-ok-circle:before {\n  content: \"\\e089\";\n}\n.glyphicon-ban-circle:before {\n  content: \"\\e090\";\n}\n.glyphicon-arrow-left:before {\n  content: \"\\e091\";\n}\n.glyphicon-arrow-right:before {\n  content: \"\\e092\";\n}\n.glyphicon-arrow-up:before {\n  content: \"\\e093\";\n}\n.glyphicon-arrow-down:before {\n  content: \"\\e094\";\n}\n.glyphicon-share-alt:before {\n  content: \"\\e095\";\n}\n.glyphicon-resize-full:before {\n  content: \"\\e096\";\n}\n.glyphicon-resize-small:before {\n  content: \"\\e097\";\n}\n.glyphicon-exclamation-sign:before {\n  content: \"\\e101\";\n}\n.glyphicon-gift:before {\n  content: \"\\e102\";\n}\n.glyphicon-leaf:before {\n  content: \"\\e103\";\n}\n.glyphicon-fire:before {\n  content: \"\\e104\";\n}\n.glyphicon-eye-open:before {\n  content: \"\\e105\";\n}\n.glyphicon-eye-close:before {\n  content: \"\\e106\";\n}\n.glyphicon-warning-sign:before {\n  content: \"\\e107\";\n}\n.glyphicon-plane:before {\n  content: \"\\e108\";\n}\n.glyphicon-calendar:before {\n  content: \"\\e109\";\n}\n.glyphicon-random:before {\n  content: \"\\e110\";\n}\n.glyphicon-comment:before {\n  content: \"\\e111\";\n}\n.glyphicon-magnet:before {\n  content: \"\\e112\";\n}\n.glyphicon-chevron-up:before {\n  content: \"\\e113\";\n}\n.glyphicon-chevron-down:before {\n  content: \"\\e114\";\n}\n.glyphicon-retweet:before {\n  content: \"\\e115\";\n}\n.glyphicon-shopping-cart:before {\n  content: \"\\e116\";\n}\n.glyphicon-folder-close:before {\n  content: \"\\e117\";\n}\n.glyphicon-folder-open:before {\n  content: \"\\e118\";\n}\n.glyphicon-resize-vertical:before {\n  content: \"\\e119\";\n}\n.glyphicon-resize-horizontal:before {\n  content: \"\\e120\";\n}\n.glyphicon-hdd:before {\n  content: \"\\e121\";\n}\n.glyphicon-bullhorn:before {\n  content: \"\\e122\";\n}\n.glyphicon-bell:before {\n  content: \"\\e123\";\n}\n.glyphicon-certificate:before {\n  content: \"\\e124\";\n}\n.glyphicon-thumbs-up:before {\n  content: \"\\e125\";\n}\n.glyphicon-thumbs-down:before {\n  content: \"\\e126\";\n}\n.glyphicon-hand-right:before {\n  content: \"\\e127\";\n}\n.glyphicon-hand-left:before {\n  content: \"\\e128\";\n}\n.glyphicon-hand-up:before {\n  content: \"\\e129\";\n}\n.glyphicon-hand-down:before {\n  content: \"\\e130\";\n}\n.glyphicon-circle-arrow-right:before {\n  content: \"\\e131\";\n}\n.glyphicon-circle-arrow-left:before {\n  content: \"\\e132\";\n}\n.glyphicon-circle-arrow-up:before {\n  content: \"\\e133\";\n}\n.glyphicon-circle-arrow-down:before {\n  content: \"\\e134\";\n}\n.glyphicon-globe:before {\n  content: \"\\e135\";\n}\n.glyphicon-wrench:before {\n  content: \"\\e136\";\n}\n.glyphicon-tasks:before {\n  content: \"\\e137\";\n}\n.glyphicon-filter:before {\n  content: \"\\e138\";\n}\n.glyphicon-briefcase:before {\n  content: \"\\e139\";\n}\n.glyphicon-fullscreen:before {\n  content: \"\\e140\";\n}\n.glyphicon-dashboard:before {\n  content: \"\\e141\";\n}\n.glyphicon-paperclip:before {\n  content: \"\\e142\";\n}\n.glyphicon-heart-empty:before {\n  content: \"\\e143\";\n}\n.glyphicon-link:before {\n  content: \"\\e144\";\n}\n.glyphicon-phone:before {\n  content: \"\\e145\";\n}\n.glyphicon-pushpin:before {\n  content: \"\\e146\";\n}\n.glyphicon-usd:before {\n  content: \"\\e148\";\n}\n.glyphicon-gbp:before {\n  content: \"\\e149\";\n}\n.glyphicon-sort:before {\n  content: \"\\e150\";\n}\n.glyphicon-sort-by-alphabet:before {\n  content: \"\\e151\";\n}\n.glyphicon-sort-by-alphabet-alt:before {\n  content: \"\\e152\";\n}\n.glyphicon-sort-by-order:before {\n  content: \"\\e153\";\n}\n.glyphicon-sort-by-order-alt:before {\n  content: \"\\e154\";\n}\n.glyphicon-sort-by-attributes:before {\n  content: \"\\e155\";\n}\n.glyphicon-sort-by-attributes-alt:before {\n  content: \"\\e156\";\n}\n.glyphicon-unchecked:before {\n  content: \"\\e157\";\n}\n.glyphicon-expand:before {\n  content: \"\\e158\";\n}\n.glyphicon-collapse-down:before {\n  content: \"\\e159\";\n}\n.glyphicon-collapse-up:before {\n  content: \"\\e160\";\n}\n.glyphicon-log-in:before {\n  content: \"\\e161\";\n}\n.glyphicon-flash:before {\n  content: \"\\e162\";\n}\n.glyphicon-log-out:before {\n  content: \"\\e163\";\n}\n.glyphicon-new-window:before {\n  content: \"\\e164\";\n}\n.glyphicon-record:before {\n  content: \"\\e165\";\n}\n.glyphicon-save:before {\n  content: \"\\e166\";\n}\n.glyphicon-open:before {\n  content: \"\\e167\";\n}\n.glyphicon-saved:before {\n  content: \"\\e168\";\n}\n.glyphicon-import:before {\n  content: \"\\e169\";\n}\n.glyphicon-export:before {\n  content: \"\\e170\";\n}\n.glyphicon-send:before {\n  content: \"\\e171\";\n}\n.glyphicon-floppy-disk:before {\n  content: \"\\e172\";\n}\n.glyphicon-floppy-saved:before {\n  content: \"\\e173\";\n}\n.glyphicon-floppy-remove:before {\n  content: \"\\e174\";\n}\n.glyphicon-floppy-save:before {\n  content: \"\\e175\";\n}\n.glyphicon-floppy-open:before {\n  content: \"\\e176\";\n}\n.glyphicon-credit-card:before {\n  content: \"\\e177\";\n}\n.glyphicon-transfer:before {\n  content: \"\\e178\";\n}\n.glyphicon-cutlery:before {\n  content: \"\\e179\";\n}\n.glyphicon-header:before {\n  content: \"\\e180\";\n}\n.glyphicon-compressed:before {\n  content: \"\\e181\";\n}\n.glyphicon-earphone:before {\n  content: \"\\e182\";\n}\n.glyphicon-phone-alt:before {\n  content: \"\\e183\";\n}\n.glyphicon-tower:before {\n  content: \"\\e184\";\n}\n.glyphicon-stats:before {\n  content: \"\\e185\";\n}\n.glyphicon-sd-video:before {\n  content: \"\\e186\";\n}\n.glyphicon-hd-video:before {\n  content: \"\\e187\";\n}\n.glyphicon-subtitles:before {\n  content: \"\\e188\";\n}\n.glyphicon-sound-stereo:before {\n  content: \"\\e189\";\n}\n.glyphicon-sound-dolby:before {\n  content: \"\\e190\";\n}\n.glyphicon-sound-5-1:before {\n  content: \"\\e191\";\n}\n.glyphicon-sound-6-1:before {\n  content: \"\\e192\";\n}\n.glyphicon-sound-7-1:before {\n  content: \"\\e193\";\n}\n.glyphicon-copyright-mark:before {\n  content: \"\\e194\";\n}\n.glyphicon-registration-mark:before {\n  content: \"\\e195\";\n}\n.glyphicon-cloud-download:before {\n  content: \"\\e197\";\n}\n.glyphicon-cloud-upload:before {\n  content: \"\\e198\";\n}\n.glyphicon-tree-conifer:before {\n  content: \"\\e199\";\n}\n.glyphicon-tree-deciduous:before {\n  content: \"\\e200\";\n}\n.glyphicon-cd:before {\n  content: \"\\e201\";\n}\n.glyphicon-save-file:before {\n  content: \"\\e202\";\n}\n.glyphicon-open-file:before {\n  content: \"\\e203\";\n}\n.glyphicon-level-up:before {\n  content: \"\\e204\";\n}\n.glyphicon-copy:before {\n  content: \"\\e205\";\n}\n.glyphicon-paste:before {\n  content: \"\\e206\";\n}\n.glyphicon-alert:before {\n  content: \"\\e209\";\n}\n.glyphicon-equalizer:before {\n  content: \"\\e210\";\n}\n.glyphicon-king:before {\n  content: \"\\e211\";\n}\n.glyphicon-queen:before {\n  content: \"\\e212\";\n}\n.glyphicon-pawn:before {\n  content: \"\\e213\";\n}\n.glyphicon-bishop:before {\n  content: \"\\e214\";\n}\n.glyphicon-knight:before {\n  content: \"\\e215\";\n}\n.glyphicon-baby-formula:before {\n  content: \"\\e216\";\n}\n.glyphicon-tent:before {\n  content: \"\\26fa\";\n}\n.glyphicon-blackboard:before {\n  content: \"\\e218\";\n}\n.glyphicon-bed:before {\n  content: \"\\e219\";\n}\n.glyphicon-apple:before {\n  content: \"\\f8ff\";\n}\n.glyphicon-erase:before {\n  content: \"\\e221\";\n}\n.glyphicon-hourglass:before {\n  content: \"\\231b\";\n}\n.glyphicon-lamp:before {\n  content: \"\\e223\";\n}\n.glyphicon-duplicate:before {\n  content: \"\\e224\";\n}\n.glyphicon-piggy-bank:before {\n  content: \"\\e225\";\n}\n.glyphicon-scissors:before {\n  content: \"\\e226\";\n}\n.glyphicon-bitcoin:before {\n  content: \"\\e227\";\n}\n.glyphicon-yen:before {\n  content: \"\\00a5\";\n}\n.glyphicon-ruble:before {\n  content: \"\\20bd\";\n}\n.glyphicon-scale:before {\n  content: \"\\e230\";\n}\n.glyphicon-ice-lolly:before {\n  content: \"\\e231\";\n}\n.glyphicon-ice-lolly-tasted:before {\n  content: \"\\e232\";\n}\n.glyphicon-education:before {\n  content: \"\\e233\";\n}\n.glyphicon-option-horizontal:before {\n  content: \"\\e234\";\n}\n.glyphicon-option-vertical:before {\n  content: \"\\e235\";\n}\n.glyphicon-menu-hamburger:before {\n  content: \"\\e236\";\n}\n.glyphicon-modal-window:before {\n  content: \"\\e237\";\n}\n.glyphicon-oil:before {\n  content: \"\\e238\";\n}\n.glyphicon-grain:before {\n  content: \"\\e239\";\n}\n.glyphicon-sunglasses:before {\n  content: \"\\e240\";\n}\n.glyphicon-text-size:before {\n  content: \"\\e241\";\n}\n.glyphicon-text-color:before {\n  content: \"\\e242\";\n}\n.glyphicon-text-background:before {\n  content: \"\\e243\";\n}\n.glyphicon-object-align-top:before {\n  content: \"\\e244\";\n}\n.glyphicon-object-align-bottom:before {\n  content: \"\\e245\";\n}\n.glyphicon-object-align-horizontal:before {\n  content: \"\\e246\";\n}\n.glyphicon-object-align-left:before {\n  content: \"\\e247\";\n}\n.glyphicon-object-align-vertical:before {\n  content: \"\\e248\";\n}\n.glyphicon-object-align-right:before {\n  content: \"\\e249\";\n}\n.glyphicon-triangle-right:before {\n  content: \"\\e250\";\n}\n.glyphicon-triangle-left:before {\n  content: \"\\e251\";\n}\n.glyphicon-triangle-bottom:before {\n  content: \"\\e252\";\n}\n.glyphicon-triangle-top:before {\n  content: \"\\e253\";\n}\n.glyphicon-console:before {\n  content: \"\\e254\";\n}\n.glyphicon-superscript:before {\n  content: \"\\e255\";\n}\n.glyphicon-subscript:before {\n  content: \"\\e256\";\n}\n.glyphicon-menu-left:before {\n  content: \"\\e257\";\n}\n.glyphicon-menu-right:before {\n  content: \"\\e258\";\n}\n.glyphicon-menu-down:before {\n  content: \"\\e259\";\n}\n.glyphicon-menu-up:before {\n  content: \"\\e260\";\n}\n* {\n  -webkit-box-sizing: border-box;\n     -moz-box-sizing: border-box;\n          box-sizing: border-box;\n}\n*:before,\n*:after {\n  -webkit-box-sizing: border-box;\n     -moz-box-sizing: border-box;\n          box-sizing: border-box;\n}\nhtml {\n  font-size: 10px;\n\n  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\nbody {\n  font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-size: 14px;\n  line-height: 1.42857143;\n  color: #333;\n  background-color: #fff;\n}\ninput,\nbutton,\nselect,\ntextarea {\n  font-family: inherit;\n  font-size: inherit;\n  line-height: inherit;\n}\na {\n  color: #337ab7;\n  text-decoration: none;\n}\na:hover,\na:focus {\n  color: #23527c;\n  text-decoration: underline;\n}\na:focus {\n  outline: thin dotted;\n  outline: 5px auto -webkit-focus-ring-color;\n  outline-offset: -2px;\n}\nfigure {\n  margin: 0;\n}\nimg {\n  vertical-align: middle;\n}\n.img-responsive,\n.thumbnail > img,\n.thumbnail a > img,\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n  display: block;\n  max-width: 100%;\n  height: auto;\n}\n.img-rounded {\n  border-radius: 6px;\n}\n.img-thumbnail {\n  display: inline-block;\n  max-width: 100%;\n  height: auto;\n  padding: 4px;\n  line-height: 1.42857143;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-radius: 4px;\n  -webkit-transition: all .2s ease-in-out;\n       -o-transition: all .2s ease-in-out;\n          transition: all .2s ease-in-out;\n}\n.img-circle {\n  border-radius: 50%;\n}\nhr {\n  margin-top: 20px;\n  margin-bottom: 20px;\n  border: 0;\n  border-top: 1px solid #eee;\n}\n.sr-only {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  overflow: hidden;\n  clip: rect(0, 0, 0, 0);\n  border: 0;\n}\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n  position: static;\n  width: auto;\n  height: auto;\n  margin: 0;\n  overflow: visible;\n  clip: auto;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n  font-family: inherit;\n  font-weight: 500;\n  line-height: 1.1;\n  color: inherit;\n}\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small,\n.h1 small,\n.h2 small,\n.h3 small,\n.h4 small,\n.h5 small,\n.h6 small,\nh1 .small,\nh2 .small,\nh3 .small,\nh4 .small,\nh5 .small,\nh6 .small,\n.h1 .small,\n.h2 .small,\n.h3 .small,\n.h4 .small,\n.h5 .small,\n.h6 .small {\n  font-weight: normal;\n  line-height: 1;\n  color: #777;\n}\nh1,\n.h1,\nh2,\n.h2,\nh3,\n.h3 {\n  margin-top: 20px;\n  margin-bottom: 10px;\n}\nh1 small,\n.h1 small,\nh2 small,\n.h2 small,\nh3 small,\n.h3 small,\nh1 .small,\n.h1 .small,\nh2 .small,\n.h2 .small,\nh3 .small,\n.h3 .small {\n  font-size: 65%;\n}\nh4,\n.h4,\nh5,\n.h5,\nh6,\n.h6 {\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\nh4 small,\n.h4 small,\nh5 small,\n.h5 small,\nh6 small,\n.h6 small,\nh4 .small,\n.h4 .small,\nh5 .small,\n.h5 .small,\nh6 .small,\n.h6 .small {\n  font-size: 75%;\n}\nh1,\n.h1 {\n  font-size: 36px;\n}\nh2,\n.h2 {\n  font-size: 30px;\n}\nh3,\n.h3 {\n  font-size: 24px;\n}\nh4,\n.h4 {\n  font-size: 18px;\n}\nh5,\n.h5 {\n  font-size: 14px;\n}\nh6,\n.h6 {\n  font-size: 12px;\n}\np {\n  margin: 0 0 10px;\n}\n.lead {\n  margin-bottom: 20px;\n  font-size: 16px;\n  font-weight: 300;\n  line-height: 1.4;\n}\n@media (min-width: 768px) {\n  .lead {\n    font-size: 21px;\n  }\n}\nsmall,\n.small {\n  font-size: 85%;\n}\nmark,\n.mark {\n  padding: .2em;\n  background-color: #fcf8e3;\n}\n.text-left {\n  text-align: left;\n}\n.text-right {\n  text-align: right;\n}\n.text-center {\n  text-align: center;\n}\n.text-justify {\n  text-align: justify;\n}\n.text-nowrap {\n  white-space: nowrap;\n}\n.text-lowercase {\n  text-transform: lowercase;\n}\n.text-uppercase {\n  text-transform: uppercase;\n}\n.text-capitalize {\n  text-transform: capitalize;\n}\n.text-muted {\n  color: #777;\n}\n.text-primary {\n  color: #337ab7;\n}\na.text-primary:hover {\n  color: #286090;\n}\n.text-success {\n  color: #3c763d;\n}\na.text-success:hover {\n  color: #2b542c;\n}\n.text-info {\n  color: #31708f;\n}\na.text-info:hover {\n  color: #245269;\n}\n.text-warning {\n  color: #8a6d3b;\n}\na.text-warning:hover {\n  color: #66512c;\n}\n.text-danger {\n  color: #a94442;\n}\na.text-danger:hover {\n  color: #843534;\n}\n.bg-primary {\n  color: #fff;\n  background-color: #337ab7;\n}\na.bg-primary:hover {\n  background-color: #286090;\n}\n.bg-success {\n  background-color: #dff0d8;\n}\na.bg-success:hover {\n  background-color: #c1e2b3;\n}\n.bg-info {\n  background-color: #d9edf7;\n}\na.bg-info:hover {\n  background-color: #afd9ee;\n}\n.bg-warning {\n  background-color: #fcf8e3;\n}\na.bg-warning:hover {\n  background-color: #f7ecb5;\n}\n.bg-danger {\n  background-color: #f2dede;\n}\na.bg-danger:hover {\n  background-color: #e4b9b9;\n}\n.page-header {\n  padding-bottom: 9px;\n  margin: 40px 0 20px;\n  border-bottom: 1px solid #eee;\n}\nul,\nol {\n  margin-top: 0;\n  margin-bottom: 10px;\n}\nul ul,\nol ul,\nul ol,\nol ol {\n  margin-bottom: 0;\n}\n.list-unstyled {\n  padding-left: 0;\n  list-style: none;\n}\n.list-inline {\n  padding-left: 0;\n  margin-left: -5px;\n  list-style: none;\n}\n.list-inline > li {\n  display: inline-block;\n  padding-right: 5px;\n  padding-left: 5px;\n}\ndl {\n  margin-top: 0;\n  margin-bottom: 20px;\n}\ndt,\ndd {\n  line-height: 1.42857143;\n}\ndt {\n  font-weight: bold;\n}\ndd {\n  margin-left: 0;\n}\n@media (min-width: 768px) {\n  .dl-horizontal dt {\n    float: left;\n    width: 160px;\n    overflow: hidden;\n    clear: left;\n    text-align: right;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n  .dl-horizontal dd {\n    margin-left: 180px;\n  }\n}\nabbr[title],\nabbr[data-original-title] {\n  cursor: help;\n  border-bottom: 1px dotted #777;\n}\n.initialism {\n  font-size: 90%;\n  text-transform: uppercase;\n}\nblockquote {\n  padding: 10px 20px;\n  margin: 0 0 20px;\n  font-size: 17.5px;\n  border-left: 5px solid #eee;\n}\nblockquote p:last-child,\nblockquote ul:last-child,\nblockquote ol:last-child {\n  margin-bottom: 0;\n}\nblockquote footer,\nblockquote small,\nblockquote .small {\n  display: block;\n  font-size: 80%;\n  line-height: 1.42857143;\n  color: #777;\n}\nblockquote footer:before,\nblockquote small:before,\nblockquote .small:before {\n  content: '\\2014 \\00A0';\n}\n.blockquote-reverse,\nblockquote.pull-right {\n  padding-right: 15px;\n  padding-left: 0;\n  text-align: right;\n  border-right: 5px solid #eee;\n  border-left: 0;\n}\n.blockquote-reverse footer:before,\nblockquote.pull-right footer:before,\n.blockquote-reverse small:before,\nblockquote.pull-right small:before,\n.blockquote-reverse .small:before,\nblockquote.pull-right .small:before {\n  content: '';\n}\n.blockquote-reverse footer:after,\nblockquote.pull-right footer:after,\n.blockquote-reverse small:after,\nblockquote.pull-right small:after,\n.blockquote-reverse .small:after,\nblockquote.pull-right .small:after {\n  content: '\\00A0 \\2014';\n}\naddress {\n  margin-bottom: 20px;\n  font-style: normal;\n  line-height: 1.42857143;\n}\ncode,\nkbd,\npre,\nsamp {\n  font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\ncode {\n  padding: 2px 4px;\n  font-size: 90%;\n  color: #c7254e;\n  background-color: #f9f2f4;\n  border-radius: 4px;\n}\nkbd {\n  padding: 2px 4px;\n  font-size: 90%;\n  color: #fff;\n  background-color: #333;\n  border-radius: 3px;\n  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);\n          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);\n}\nkbd kbd {\n  padding: 0;\n  font-size: 100%;\n  font-weight: bold;\n  -webkit-box-shadow: none;\n          box-shadow: none;\n}\npre {\n  display: block;\n  padding: 9.5px;\n  margin: 0 0 10px;\n  font-size: 13px;\n  line-height: 1.42857143;\n  color: #333;\n  word-break: break-all;\n  word-wrap: break-word;\n  background-color: #f5f5f5;\n  border: 1px solid #ccc;\n  border-radius: 4px;\n}\npre code {\n  padding: 0;\n  font-size: inherit;\n  color: inherit;\n  white-space: pre-wrap;\n  background-color: transparent;\n  border-radius: 0;\n}\n.pre-scrollable {\n  max-height: 340px;\n  overflow-y: scroll;\n}\n.container {\n  padding-right: 15px;\n  padding-left: 15px;\n  margin-right: auto;\n  margin-left: auto;\n}\n@media (min-width: 768px) {\n  .container {\n    width: 750px;\n  }\n}\n@media (min-width: 992px) {\n  .container {\n    width: 970px;\n  }\n}\n@media (min-width: 1200px) {\n  .container {\n    width: 1170px;\n  }\n}\n.container-fluid {\n  padding-right: 15px;\n  padding-left: 15px;\n  margin-right: auto;\n  margin-left: auto;\n}\n.row {\n  margin-right: -15px;\n  margin-left: -15px;\n}\n.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {\n  position: relative;\n  min-height: 1px;\n  padding-right: 15px;\n  padding-left: 15px;\n}\n.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {\n  float: left;\n}\n.col-xs-12 {\n  width: 100%;\n}\n.col-xs-11 {\n  width: 91.66666667%;\n}\n.col-xs-10 {\n  width: 83.33333333%;\n}\n.col-xs-9 {\n  width: 75%;\n}\n.col-xs-8 {\n  width: 66.66666667%;\n}\n.col-xs-7 {\n  width: 58.33333333%;\n}\n.col-xs-6 {\n  width: 50%;\n}\n.col-xs-5 {\n  width: 41.66666667%;\n}\n.col-xs-4 {\n  width: 33.33333333%;\n}\n.col-xs-3 {\n  width: 25%;\n}\n.col-xs-2 {\n  width: 16.66666667%;\n}\n.col-xs-1 {\n  width: 8.33333333%;\n}\n.col-xs-pull-12 {\n  right: 100%;\n}\n.col-xs-pull-11 {\n  right: 91.66666667%;\n}\n.col-xs-pull-10 {\n  right: 83.33333333%;\n}\n.col-xs-pull-9 {\n  right: 75%;\n}\n.col-xs-pull-8 {\n  right: 66.66666667%;\n}\n.col-xs-pull-7 {\n  right: 58.33333333%;\n}\n.col-xs-pull-6 {\n  right: 50%;\n}\n.col-xs-pull-5 {\n  right: 41.66666667%;\n}\n.col-xs-pull-4 {\n  right: 33.33333333%;\n}\n.col-xs-pull-3 {\n  right: 25%;\n}\n.col-xs-pull-2 {\n  right: 16.66666667%;\n}\n.col-xs-pull-1 {\n  right: 8.33333333%;\n}\n.col-xs-pull-0 {\n  right: auto;\n}\n.col-xs-push-12 {\n  left: 100%;\n}\n.col-xs-push-11 {\n  left: 91.66666667%;\n}\n.col-xs-push-10 {\n  left: 83.33333333%;\n}\n.col-xs-push-9 {\n  left: 75%;\n}\n.col-xs-push-8 {\n  left: 66.66666667%;\n}\n.col-xs-push-7 {\n  left: 58.33333333%;\n}\n.col-xs-push-6 {\n  left: 50%;\n}\n.col-xs-push-5 {\n  left: 41.66666667%;\n}\n.col-xs-push-4 {\n  left: 33.33333333%;\n}\n.col-xs-push-3 {\n  left: 25%;\n}\n.col-xs-push-2 {\n  left: 16.66666667%;\n}\n.col-xs-push-1 {\n  left: 8.33333333%;\n}\n.col-xs-push-0 {\n  left: auto;\n}\n.col-xs-offset-12 {\n  margin-left: 100%;\n}\n.col-xs-offset-11 {\n  margin-left: 91.66666667%;\n}\n.col-xs-offset-10 {\n  margin-left: 83.33333333%;\n}\n.col-xs-offset-9 {\n  margin-left: 75%;\n}\n.col-xs-offset-8 {\n  margin-left: 66.66666667%;\n}\n.col-xs-offset-7 {\n  margin-left: 58.33333333%;\n}\n.col-xs-offset-6 {\n  margin-left: 50%;\n}\n.col-xs-offset-5 {\n  margin-left: 41.66666667%;\n}\n.col-xs-offset-4 {\n  margin-left: 33.33333333%;\n}\n.col-xs-offset-3 {\n  margin-left: 25%;\n}\n.col-xs-offset-2 {\n  margin-left: 16.66666667%;\n}\n.col-xs-offset-1 {\n  margin-left: 8.33333333%;\n}\n.col-xs-offset-0 {\n  margin-left: 0;\n}\n@media (min-width: 768px) {\n  .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {\n    float: left;\n  }\n  .col-sm-12 {\n    width: 100%;\n  }\n  .col-sm-11 {\n    width: 91.66666667%;\n  }\n  .col-sm-10 {\n    width: 83.33333333%;\n  }\n  .col-sm-9 {\n    width: 75%;\n  }\n  .col-sm-8 {\n    width: 66.66666667%;\n  }\n  .col-sm-7 {\n    width: 58.33333333%;\n  }\n  .col-sm-6 {\n    width: 50%;\n  }\n  .col-sm-5 {\n    width: 41.66666667%;\n  }\n  .col-sm-4 {\n    width: 33.33333333%;\n  }\n  .col-sm-3 {\n    width: 25%;\n  }\n  .col-sm-2 {\n    width: 16.66666667%;\n  }\n  .col-sm-1 {\n    width: 8.33333333%;\n  }\n  .col-sm-pull-12 {\n    right: 100%;\n  }\n  .col-sm-pull-11 {\n    right: 91.66666667%;\n  }\n  .col-sm-pull-10 {\n    right: 83.33333333%;\n  }\n  .col-sm-pull-9 {\n    right: 75%;\n  }\n  .col-sm-pull-8 {\n    right: 66.66666667%;\n  }\n  .col-sm-pull-7 {\n    right: 58.33333333%;\n  }\n  .col-sm-pull-6 {\n    right: 50%;\n  }\n  .col-sm-pull-5 {\n    right: 41.66666667%;\n  }\n  .col-sm-pull-4 {\n    right: 33.33333333%;\n  }\n  .col-sm-pull-3 {\n    right: 25%;\n  }\n  .col-sm-pull-2 {\n    right: 16.66666667%;\n  }\n  .col-sm-pull-1 {\n    right: 8.33333333%;\n  }\n  .col-sm-pull-0 {\n    right: auto;\n  }\n  .col-sm-push-12 {\n    left: 100%;\n  }\n  .col-sm-push-11 {\n    left: 91.66666667%;\n  }\n  .col-sm-push-10 {\n    left: 83.33333333%;\n  }\n  .col-sm-push-9 {\n    left: 75%;\n  }\n  .col-sm-push-8 {\n    left: 66.66666667%;\n  }\n  .col-sm-push-7 {\n    left: 58.33333333%;\n  }\n  .col-sm-push-6 {\n    left: 50%;\n  }\n  .col-sm-push-5 {\n    left: 41.66666667%;\n  }\n  .col-sm-push-4 {\n    left: 33.33333333%;\n  }\n  .col-sm-push-3 {\n    left: 25%;\n  }\n  .col-sm-push-2 {\n    left: 16.66666667%;\n  }\n  .col-sm-push-1 {\n    left: 8.33333333%;\n  }\n  .col-sm-push-0 {\n    left: auto;\n  }\n  .col-sm-offset-12 {\n    margin-left: 100%;\n  }\n  .col-sm-offset-11 {\n    margin-left: 91.66666667%;\n  }\n  .col-sm-offset-10 {\n    margin-left: 83.33333333%;\n  }\n  .col-sm-offset-9 {\n    margin-left: 75%;\n  }\n  .col-sm-offset-8 {\n    margin-left: 66.66666667%;\n  }\n  .col-sm-offset-7 {\n    margin-left: 58.33333333%;\n  }\n  .col-sm-offset-6 {\n    margin-left: 50%;\n  }\n  .col-sm-offset-5 {\n    margin-left: 41.66666667%;\n  }\n  .col-sm-offset-4 {\n    margin-left: 33.33333333%;\n  }\n  .col-sm-offset-3 {\n    margin-left: 25%;\n  }\n  .col-sm-offset-2 {\n    margin-left: 16.66666667%;\n  }\n  .col-sm-offset-1 {\n    margin-left: 8.33333333%;\n  }\n  .col-sm-offset-0 {\n    margin-left: 0;\n  }\n}\n@media (min-width: 992px) {\n  .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {\n    float: left;\n  }\n  .col-md-12 {\n    width: 100%;\n  }\n  .col-md-11 {\n    width: 91.66666667%;\n  }\n  .col-md-10 {\n    width: 83.33333333%;\n  }\n  .col-md-9 {\n    width: 75%;\n  }\n  .col-md-8 {\n    width: 66.66666667%;\n  }\n  .col-md-7 {\n    width: 58.33333333%;\n  }\n  .col-md-6 {\n    width: 50%;\n  }\n  .col-md-5 {\n    width: 41.66666667%;\n  }\n  .col-md-4 {\n    width: 33.33333333%;\n  }\n  .col-md-3 {\n    width: 25%;\n  }\n  .col-md-2 {\n    width: 16.66666667%;\n  }\n  .col-md-1 {\n    width: 8.33333333%;\n  }\n  .col-md-pull-12 {\n    right: 100%;\n  }\n  .col-md-pull-11 {\n    right: 91.66666667%;\n  }\n  .col-md-pull-10 {\n    right: 83.33333333%;\n  }\n  .col-md-pull-9 {\n    right: 75%;\n  }\n  .col-md-pull-8 {\n    right: 66.66666667%;\n  }\n  .col-md-pull-7 {\n    right: 58.33333333%;\n  }\n  .col-md-pull-6 {\n    right: 50%;\n  }\n  .col-md-pull-5 {\n    right: 41.66666667%;\n  }\n  .col-md-pull-4 {\n    right: 33.33333333%;\n  }\n  .col-md-pull-3 {\n    right: 25%;\n  }\n  .col-md-pull-2 {\n    right: 16.66666667%;\n  }\n  .col-md-pull-1 {\n    right: 8.33333333%;\n  }\n  .col-md-pull-0 {\n    right: auto;\n  }\n  .col-md-push-12 {\n    left: 100%;\n  }\n  .col-md-push-11 {\n    left: 91.66666667%;\n  }\n  .col-md-push-10 {\n    left: 83.33333333%;\n  }\n  .col-md-push-9 {\n    left: 75%;\n  }\n  .col-md-push-8 {\n    left: 66.66666667%;\n  }\n  .col-md-push-7 {\n    left: 58.33333333%;\n  }\n  .col-md-push-6 {\n    left: 50%;\n  }\n  .col-md-push-5 {\n    left: 41.66666667%;\n  }\n  .col-md-push-4 {\n    left: 33.33333333%;\n  }\n  .col-md-push-3 {\n    left: 25%;\n  }\n  .col-md-push-2 {\n    left: 16.66666667%;\n  }\n  .col-md-push-1 {\n    left: 8.33333333%;\n  }\n  .col-md-push-0 {\n    left: auto;\n  }\n  .col-md-offset-12 {\n    margin-left: 100%;\n  }\n  .col-md-offset-11 {\n    margin-left: 91.66666667%;\n  }\n  .col-md-offset-10 {\n    margin-left: 83.33333333%;\n  }\n  .col-md-offset-9 {\n    margin-left: 75%;\n  }\n  .col-md-offset-8 {\n    margin-left: 66.66666667%;\n  }\n  .col-md-offset-7 {\n    margin-left: 58.33333333%;\n  }\n  .col-md-offset-6 {\n    margin-left: 50%;\n  }\n  .col-md-offset-5 {\n    margin-left: 41.66666667%;\n  }\n  .col-md-offset-4 {\n    margin-left: 33.33333333%;\n  }\n  .col-md-offset-3 {\n    margin-left: 25%;\n  }\n  .col-md-offset-2 {\n    margin-left: 16.66666667%;\n  }\n  .col-md-offset-1 {\n    margin-left: 8.33333333%;\n  }\n  .col-md-offset-0 {\n    margin-left: 0;\n  }\n}\n@media (min-width: 1200px) {\n  .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {\n    float: left;\n  }\n  .col-lg-12 {\n    width: 100%;\n  }\n  .col-lg-11 {\n    width: 91.66666667%;\n  }\n  .col-lg-10 {\n    width: 83.33333333%;\n  }\n  .col-lg-9 {\n    width: 75%;\n  }\n  .col-lg-8 {\n    width: 66.66666667%;\n  }\n  .col-lg-7 {\n    width: 58.33333333%;\n  }\n  .col-lg-6 {\n    width: 50%;\n  }\n  .col-lg-5 {\n    width: 41.66666667%;\n  }\n  .col-lg-4 {\n    width: 33.33333333%;\n  }\n  .col-lg-3 {\n    width: 25%;\n  }\n  .col-lg-2 {\n    width: 16.66666667%;\n  }\n  .col-lg-1 {\n    width: 8.33333333%;\n  }\n  .col-lg-pull-12 {\n    right: 100%;\n  }\n  .col-lg-pull-11 {\n    right: 91.66666667%;\n  }\n  .col-lg-pull-10 {\n    right: 83.33333333%;\n  }\n  .col-lg-pull-9 {\n    right: 75%;\n  }\n  .col-lg-pull-8 {\n    right: 66.66666667%;\n  }\n  .col-lg-pull-7 {\n    right: 58.33333333%;\n  }\n  .col-lg-pull-6 {\n    right: 50%;\n  }\n  .col-lg-pull-5 {\n    right: 41.66666667%;\n  }\n  .col-lg-pull-4 {\n    right: 33.33333333%;\n  }\n  .col-lg-pull-3 {\n    right: 25%;\n  }\n  .col-lg-pull-2 {\n    right: 16.66666667%;\n  }\n  .col-lg-pull-1 {\n    right: 8.33333333%;\n  }\n  .col-lg-pull-0 {\n    right: auto;\n  }\n  .col-lg-push-12 {\n    left: 100%;\n  }\n  .col-lg-push-11 {\n    left: 91.66666667%;\n  }\n  .col-lg-push-10 {\n    left: 83.33333333%;\n  }\n  .col-lg-push-9 {\n    left: 75%;\n  }\n  .col-lg-push-8 {\n    left: 66.66666667%;\n  }\n  .col-lg-push-7 {\n    left: 58.33333333%;\n  }\n  .col-lg-push-6 {\n    left: 50%;\n  }\n  .col-lg-push-5 {\n    left: 41.66666667%;\n  }\n  .col-lg-push-4 {\n    left: 33.33333333%;\n  }\n  .col-lg-push-3 {\n    left: 25%;\n  }\n  .col-lg-push-2 {\n    left: 16.66666667%;\n  }\n  .col-lg-push-1 {\n    left: 8.33333333%;\n  }\n  .col-lg-push-0 {\n    left: auto;\n  }\n  .col-lg-offset-12 {\n    margin-left: 100%;\n  }\n  .col-lg-offset-11 {\n    margin-left: 91.66666667%;\n  }\n  .col-lg-offset-10 {\n    margin-left: 83.33333333%;\n  }\n  .col-lg-offset-9 {\n    margin-left: 75%;\n  }\n  .col-lg-offset-8 {\n    margin-left: 66.66666667%;\n  }\n  .col-lg-offset-7 {\n    margin-left: 58.33333333%;\n  }\n  .col-lg-offset-6 {\n    margin-left: 50%;\n  }\n  .col-lg-offset-5 {\n    margin-left: 41.66666667%;\n  }\n  .col-lg-offset-4 {\n    margin-left: 33.33333333%;\n  }\n  .col-lg-offset-3 {\n    margin-left: 25%;\n  }\n  .col-lg-offset-2 {\n    margin-left: 16.66666667%;\n  }\n  .col-lg-offset-1 {\n    margin-left: 8.33333333%;\n  }\n  .col-lg-offset-0 {\n    margin-left: 0;\n  }\n}\ntable {\n  background-color: transparent;\n}\ncaption {\n  padding-top: 8px;\n  padding-bottom: 8px;\n  color: #777;\n  text-align: left;\n}\nth {\n  text-align: left;\n}\n.table {\n  width: 100%;\n  max-width: 100%;\n  margin-bottom: 20px;\n}\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n  padding: 8px;\n  line-height: 1.42857143;\n  vertical-align: top;\n  border-top: 1px solid #ddd;\n}\n.table > thead > tr > th {\n  vertical-align: bottom;\n  border-bottom: 2px solid #ddd;\n}\n.table > caption + thead > tr:first-child > th,\n.table > colgroup + thead > tr:first-child > th,\n.table > thead:first-child > tr:first-child > th,\n.table > caption + thead > tr:first-child > td,\n.table > colgroup + thead > tr:first-child > td,\n.table > thead:first-child > tr:first-child > td {\n  border-top: 0;\n}\n.table > tbody + tbody {\n  border-top: 2px solid #ddd;\n}\n.table .table {\n  background-color: #fff;\n}\n.table-condensed > thead > tr > th,\n.table-condensed > tbody > tr > th,\n.table-condensed > tfoot > tr > th,\n.table-condensed > thead > tr > td,\n.table-condensed > tbody > tr > td,\n.table-condensed > tfoot > tr > td {\n  padding: 5px;\n}\n.table-bordered {\n  border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > tbody > tr > th,\n.table-bordered > tfoot > tr > th,\n.table-bordered > thead > tr > td,\n.table-bordered > tbody > tr > td,\n.table-bordered > tfoot > tr > td {\n  border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n  border-bottom-width: 2px;\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n  background-color: #f9f9f9;\n}\n.table-hover > tbody > tr:hover {\n  background-color: #f5f5f5;\n}\ntable col[class*=\"col-\"] {\n  position: static;\n  display: table-column;\n  float: none;\n}\ntable td[class*=\"col-\"],\ntable th[class*=\"col-\"] {\n  position: static;\n  display: table-cell;\n  float: none;\n}\n.table > thead > tr > td.active,\n.table > tbody > tr > td.active,\n.table > tfoot > tr > td.active,\n.table > thead > tr > th.active,\n.table > tbody > tr > th.active,\n.table > tfoot > tr > th.active,\n.table > thead > tr.active > td,\n.table > tbody > tr.active > td,\n.table > tfoot > tr.active > td,\n.table > thead > tr.active > th,\n.table > tbody > tr.active > th,\n.table > tfoot > tr.active > th {\n  background-color: #f5f5f5;\n}\n.table-hover > tbody > tr > td.active:hover,\n.table-hover > tbody > tr > th.active:hover,\n.table-hover > tbody > tr.active:hover > td,\n.table-hover > tbody > tr:hover > .active,\n.table-hover > tbody > tr.active:hover > th {\n  background-color: #e8e8e8;\n}\n.table > thead > tr > td.success,\n.table > tbody > tr > td.success,\n.table > tfoot > tr > td.success,\n.table > thead > tr > th.success,\n.table > tbody > tr > th.success,\n.table > tfoot > tr > th.success,\n.table > thead > tr.success > td,\n.table > tbody > tr.success > td,\n.table > tfoot > tr.success > td,\n.table > thead > tr.success > th,\n.table > tbody > tr.success > th,\n.table > tfoot > tr.success > th {\n  background-color: #dff0d8;\n}\n.table-hover > tbody > tr > td.success:hover,\n.table-hover > tbody > tr > th.success:hover,\n.table-hover > tbody > tr.success:hover > td,\n.table-hover > tbody > tr:hover > .success,\n.table-hover > tbody > tr.success:hover > th {\n  background-color: #d0e9c6;\n}\n.table > thead > tr > td.info,\n.table > tbody > tr > td.info,\n.table > tfoot > tr > td.info,\n.table > thead > tr > th.info,\n.table > tbody > tr > th.info,\n.table > tfoot > tr > th.info,\n.table > thead > tr.info > td,\n.table > tbody > tr.info > td,\n.table > tfoot > tr.info > td,\n.table > thead > tr.info > th,\n.table > tbody > tr.info > th,\n.table > tfoot > tr.info > th {\n  background-color: #d9edf7;\n}\n.table-hover > tbody > tr > td.info:hover,\n.table-hover > tbody > tr > th.info:hover,\n.table-hover > tbody > tr.info:hover > td,\n.table-hover > tbody > tr:hover > .info,\n.table-hover > tbody > tr.info:hover > th {\n  background-color: #c4e3f3;\n}\n.table > thead > tr > td.warning,\n.table > tbody > tr > td.warning,\n.table > tfoot > tr > td.warning,\n.table > thead > tr > th.warning,\n.table > tbody > tr > th.warning,\n.table > tfoot > tr > th.warning,\n.table > thead > tr.warning > td,\n.table > tbody > tr.warning > td,\n.table > tfoot > tr.warning > td,\n.table > thead > tr.warning > th,\n.table > tbody > tr.warning > th,\n.table > tfoot > tr.warning > th {\n  background-color: #fcf8e3;\n}\n.table-hover > tbody > tr > td.warning:hover,\n.table-hover > tbody > tr > th.warning:hover,\n.table-hover > tbody > tr.warning:hover > td,\n.table-hover > tbody > tr:hover > .warning,\n.table-hover > tbody > tr.warning:hover > th {\n  background-color: #faf2cc;\n}\n.table > thead > tr > td.danger,\n.table > tbody > tr > td.danger,\n.table > tfoot > tr > td.danger,\n.table > thead > tr > th.danger,\n.table > tbody > tr > th.danger,\n.table > tfoot > tr > th.danger,\n.table > thead > tr.danger > td,\n.table > tbody > tr.danger > td,\n.table > tfoot > tr.danger > td,\n.table > thead > tr.danger > th,\n.table > tbody > tr.danger > th,\n.table > tfoot > tr.danger > th {\n  background-color: #f2dede;\n}\n.table-hover > tbody > tr > td.danger:hover,\n.table-hover > tbody > tr > th.danger:hover,\n.table-hover > tbody > tr.danger:hover > td,\n.table-hover > tbody > tr:hover > .danger,\n.table-hover > tbody > tr.danger:hover > th {\n  background-color: #ebcccc;\n}\n.table-responsive {\n  min-height: .01%;\n  overflow-x: auto;\n}\n@media screen and (max-width: 767px) {\n  .table-responsive {\n    width: 100%;\n    margin-bottom: 15px;\n    overflow-y: hidden;\n    -ms-overflow-style: -ms-autohiding-scrollbar;\n    border: 1px solid #ddd;\n  }\n  .table-responsive > .table {\n    margin-bottom: 0;\n  }\n  .table-responsive > .table > thead > tr > th,\n  .table-responsive > .table > tbody > tr > th,\n  .table-responsive > .table > tfoot > tr > th,\n  .table-responsive > .table > thead > tr > td,\n  .table-responsive > .table > tbody > tr > td,\n  .table-responsive > .table > tfoot > tr > td {\n    white-space: nowrap;\n  }\n  .table-responsive > .table-bordered {\n    border: 0;\n  }\n  .table-responsive > .table-bordered > thead > tr > th:first-child,\n  .table-responsive > .table-bordered > tbody > tr > th:first-child,\n  .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n  .table-responsive > .table-bordered > thead > tr > td:first-child,\n  .table-responsive > .table-bordered > tbody > tr > td:first-child,\n  .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n    border-left: 0;\n  }\n  .table-responsive > .table-bordered > thead > tr > th:last-child,\n  .table-responsive > .table-bordered > tbody > tr > th:last-child,\n  .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n  .table-responsive > .table-bordered > thead > tr > td:last-child,\n  .table-responsive > .table-bordered > tbody > tr > td:last-child,\n  .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n    border-right: 0;\n  }\n  .table-responsive > .table-bordered > tbody > tr:last-child > th,\n  .table-responsive > .table-bordered > tfoot > tr:last-child > th,\n  .table-responsive > .table-bordered > tbody > tr:last-child > td,\n  .table-responsive > .table-bordered > tfoot > tr:last-child > td {\n    border-bottom: 0;\n  }\n}\nfieldset {\n  min-width: 0;\n  padding: 0;\n  margin: 0;\n  border: 0;\n}\nlegend {\n  display: block;\n  width: 100%;\n  padding: 0;\n  margin-bottom: 20px;\n  font-size: 21px;\n  line-height: inherit;\n  color: #333;\n  border: 0;\n  border-bottom: 1px solid #e5e5e5;\n}\nlabel {\n  display: inline-block;\n  max-width: 100%;\n  margin-bottom: 5px;\n  font-weight: bold;\n}\ninput[type=\"search\"] {\n  -webkit-box-sizing: border-box;\n     -moz-box-sizing: border-box;\n          box-sizing: border-box;\n}\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n  margin: 4px 0 0;\n  margin-top: 1px \\9;\n  line-height: normal;\n}\ninput[type=\"file\"] {\n  display: block;\n}\ninput[type=\"range\"] {\n  display: block;\n  width: 100%;\n}\nselect[multiple],\nselect[size] {\n  height: auto;\n}\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n  outline: thin dotted;\n  outline: 5px auto -webkit-focus-ring-color;\n  outline-offset: -2px;\n}\noutput {\n  display: block;\n  padding-top: 7px;\n  font-size: 14px;\n  line-height: 1.42857143;\n  color: #555;\n}\n.form-control {\n  display: block;\n  width: 100%;\n  height: 34px;\n  padding: 6px 12px;\n  font-size: 14px;\n  line-height: 1.42857143;\n  color: #555;\n  background-color: #fff;\n  background-image: none;\n  border: 1px solid #ccc;\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n  -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;\n       -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n          transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n}\n.form-control:focus {\n  border-color: #66afe9;\n  outline: 0;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);\n          box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);\n}\n.form-control::-moz-placeholder {\n  color: #999;\n  opacity: 1;\n}\n.form-control:-ms-input-placeholder {\n  color: #999;\n}\n.form-control::-webkit-input-placeholder {\n  color: #999;\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n  cursor: not-allowed;\n  background-color: #eee;\n  opacity: 1;\n}\ntextarea.form-control {\n  height: auto;\n}\ninput[type=\"search\"] {\n  -webkit-appearance: none;\n}\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n  input[type=\"date\"],\n  input[type=\"time\"],\n  input[type=\"datetime-local\"],\n  input[type=\"month\"] {\n    line-height: 34px;\n  }\n  input[type=\"date\"].input-sm,\n  input[type=\"time\"].input-sm,\n  input[type=\"datetime-local\"].input-sm,\n  input[type=\"month\"].input-sm,\n  .input-group-sm input[type=\"date\"],\n  .input-group-sm input[type=\"time\"],\n  .input-group-sm input[type=\"datetime-local\"],\n  .input-group-sm input[type=\"month\"] {\n    line-height: 30px;\n  }\n  input[type=\"date\"].input-lg,\n  input[type=\"time\"].input-lg,\n  input[type=\"datetime-local\"].input-lg,\n  input[type=\"month\"].input-lg,\n  .input-group-lg input[type=\"date\"],\n  .input-group-lg input[type=\"time\"],\n  .input-group-lg input[type=\"datetime-local\"],\n  .input-group-lg input[type=\"month\"] {\n    line-height: 46px;\n  }\n}\n.form-group {\n  margin-bottom: 15px;\n}\n.radio,\n.checkbox {\n  position: relative;\n  display: block;\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\n.radio label,\n.checkbox label {\n  min-height: 20px;\n  padding-left: 20px;\n  margin-bottom: 0;\n  font-weight: normal;\n  cursor: pointer;\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n  position: absolute;\n  margin-top: 4px \\9;\n  margin-left: -20px;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n  margin-top: -5px;\n}\n.radio-inline,\n.checkbox-inline {\n  display: inline-block;\n  padding-left: 20px;\n  margin-bottom: 0;\n  font-weight: normal;\n  vertical-align: middle;\n  cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n  margin-top: 0;\n  margin-left: 10px;\n}\ninput[type=\"radio\"][disabled],\ninput[type=\"checkbox\"][disabled],\ninput[type=\"radio\"].disabled,\ninput[type=\"checkbox\"].disabled,\nfieldset[disabled] input[type=\"radio\"],\nfieldset[disabled] input[type=\"checkbox\"] {\n  cursor: not-allowed;\n}\n.radio-inline.disabled,\n.checkbox-inline.disabled,\nfieldset[disabled] .radio-inline,\nfieldset[disabled] .checkbox-inline {\n  cursor: not-allowed;\n}\n.radio.disabled label,\n.checkbox.disabled label,\nfieldset[disabled] .radio label,\nfieldset[disabled] .checkbox label {\n  cursor: not-allowed;\n}\n.form-control-static {\n  padding-top: 7px;\n  padding-bottom: 7px;\n  margin-bottom: 0;\n}\n.form-control-static.input-lg,\n.form-control-static.input-sm {\n  padding-right: 0;\n  padding-left: 0;\n}\n.input-sm {\n  height: 30px;\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\nselect.input-sm {\n  height: 30px;\n  line-height: 30px;\n}\ntextarea.input-sm,\nselect[multiple].input-sm {\n  height: auto;\n}\n.form-group-sm .form-control {\n  height: 30px;\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\nselect.form-group-sm .form-control {\n  height: 30px;\n  line-height: 30px;\n}\ntextarea.form-group-sm .form-control,\nselect[multiple].form-group-sm .form-control {\n  height: auto;\n}\n.form-group-sm .form-control-static {\n  height: 30px;\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n}\n.input-lg {\n  height: 46px;\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\nselect.input-lg {\n  height: 46px;\n  line-height: 46px;\n}\ntextarea.input-lg,\nselect[multiple].input-lg {\n  height: auto;\n}\n.form-group-lg .form-control {\n  height: 46px;\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\nselect.form-group-lg .form-control {\n  height: 46px;\n  line-height: 46px;\n}\ntextarea.form-group-lg .form-control,\nselect[multiple].form-group-lg .form-control {\n  height: auto;\n}\n.form-group-lg .form-control-static {\n  height: 46px;\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n}\n.has-feedback {\n  position: relative;\n}\n.has-feedback .form-control {\n  padding-right: 42.5px;\n}\n.form-control-feedback {\n  position: absolute;\n  top: 0;\n  right: 0;\n  z-index: 2;\n  display: block;\n  width: 34px;\n  height: 34px;\n  line-height: 34px;\n  text-align: center;\n  pointer-events: none;\n}\n.input-lg + .form-control-feedback {\n  width: 46px;\n  height: 46px;\n  line-height: 46px;\n}\n.input-sm + .form-control-feedback {\n  width: 30px;\n  height: 30px;\n  line-height: 30px;\n}\n.has-success .help-block,\n.has-success .control-label,\n.has-success .radio,\n.has-success .checkbox,\n.has-success .radio-inline,\n.has-success .checkbox-inline,\n.has-success.radio label,\n.has-success.checkbox label,\n.has-success.radio-inline label,\n.has-success.checkbox-inline label {\n  color: #3c763d;\n}\n.has-success .form-control {\n  border-color: #3c763d;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n}\n.has-success .form-control:focus {\n  border-color: #2b542c;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;\n}\n.has-success .input-group-addon {\n  color: #3c763d;\n  background-color: #dff0d8;\n  border-color: #3c763d;\n}\n.has-success .form-control-feedback {\n  color: #3c763d;\n}\n.has-warning .help-block,\n.has-warning .control-label,\n.has-warning .radio,\n.has-warning .checkbox,\n.has-warning .radio-inline,\n.has-warning .checkbox-inline,\n.has-warning.radio label,\n.has-warning.checkbox label,\n.has-warning.radio-inline label,\n.has-warning.checkbox-inline label {\n  color: #8a6d3b;\n}\n.has-warning .form-control {\n  border-color: #8a6d3b;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n}\n.has-warning .form-control:focus {\n  border-color: #66512c;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;\n}\n.has-warning .input-group-addon {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n  border-color: #8a6d3b;\n}\n.has-warning .form-control-feedback {\n  color: #8a6d3b;\n}\n.has-error .help-block,\n.has-error .control-label,\n.has-error .radio,\n.has-error .checkbox,\n.has-error .radio-inline,\n.has-error .checkbox-inline,\n.has-error.radio label,\n.has-error.checkbox label,\n.has-error.radio-inline label,\n.has-error.checkbox-inline label {\n  color: #a94442;\n}\n.has-error .form-control {\n  border-color: #a94442;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n}\n.has-error .form-control:focus {\n  border-color: #843534;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;\n}\n.has-error .input-group-addon {\n  color: #a94442;\n  background-color: #f2dede;\n  border-color: #a94442;\n}\n.has-error .form-control-feedback {\n  color: #a94442;\n}\n.has-feedback label ~ .form-control-feedback {\n  top: 25px;\n}\n.has-feedback label.sr-only ~ .form-control-feedback {\n  top: 0;\n}\n.help-block {\n  display: block;\n  margin-top: 5px;\n  margin-bottom: 10px;\n  color: #737373;\n}\n@media (min-width: 768px) {\n  .form-inline .form-group {\n    display: inline-block;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .form-inline .form-control {\n    display: inline-block;\n    width: auto;\n    vertical-align: middle;\n  }\n  .form-inline .form-control-static {\n    display: inline-block;\n  }\n  .form-inline .input-group {\n    display: inline-table;\n    vertical-align: middle;\n  }\n  .form-inline .input-group .input-group-addon,\n  .form-inline .input-group .input-group-btn,\n  .form-inline .input-group .form-control {\n    width: auto;\n  }\n  .form-inline .input-group > .form-control {\n    width: 100%;\n  }\n  .form-inline .control-label {\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .form-inline .radio,\n  .form-inline .checkbox {\n    display: inline-block;\n    margin-top: 0;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .form-inline .radio label,\n  .form-inline .checkbox label {\n    padding-left: 0;\n  }\n  .form-inline .radio input[type=\"radio\"],\n  .form-inline .checkbox input[type=\"checkbox\"] {\n    position: relative;\n    margin-left: 0;\n  }\n  .form-inline .has-feedback .form-control-feedback {\n    top: 0;\n  }\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox,\n.form-horizontal .radio-inline,\n.form-horizontal .checkbox-inline {\n  padding-top: 7px;\n  margin-top: 0;\n  margin-bottom: 0;\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox {\n  min-height: 27px;\n}\n.form-horizontal .form-group {\n  margin-right: -15px;\n  margin-left: -15px;\n}\n@media (min-width: 768px) {\n  .form-horizontal .control-label {\n    padding-top: 7px;\n    margin-bottom: 0;\n    text-align: right;\n  }\n}\n.form-horizontal .has-feedback .form-control-feedback {\n  right: 15px;\n}\n@media (min-width: 768px) {\n  .form-horizontal .form-group-lg .control-label {\n    padding-top: 14.333333px;\n  }\n}\n@media (min-width: 768px) {\n  .form-horizontal .form-group-sm .control-label {\n    padding-top: 6px;\n  }\n}\n.btn {\n  display: inline-block;\n  padding: 6px 12px;\n  margin-bottom: 0;\n  font-size: 14px;\n  font-weight: normal;\n  line-height: 1.42857143;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: middle;\n  -ms-touch-action: manipulation;\n      touch-action: manipulation;\n  cursor: pointer;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n      -ms-user-select: none;\n          user-select: none;\n  background-image: none;\n  border: 1px solid transparent;\n  border-radius: 4px;\n}\n.btn:focus,\n.btn:active:focus,\n.btn.active:focus,\n.btn.focus,\n.btn:active.focus,\n.btn.active.focus {\n  outline: thin dotted;\n  outline: 5px auto -webkit-focus-ring-color;\n  outline-offset: -2px;\n}\n.btn:hover,\n.btn:focus,\n.btn.focus {\n  color: #333;\n  text-decoration: none;\n}\n.btn:active,\n.btn.active {\n  background-image: none;\n  outline: 0;\n  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n          box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n}\n.btn.disabled,\n.btn[disabled],\nfieldset[disabled] .btn {\n  pointer-events: none;\n  cursor: not-allowed;\n  filter: alpha(opacity=65);\n  -webkit-box-shadow: none;\n          box-shadow: none;\n  opacity: .65;\n}\n.btn-default {\n  color: #333;\n  background-color: #fff;\n  border-color: #ccc;\n}\n.btn-default:hover,\n.btn-default:focus,\n.btn-default.focus,\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n  color: #333;\n  background-color: #e6e6e6;\n  border-color: #adadad;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n  background-image: none;\n}\n.btn-default.disabled,\n.btn-default[disabled],\nfieldset[disabled] .btn-default,\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus,\n.btn-default.disabled:active,\n.btn-default[disabled]:active,\nfieldset[disabled] .btn-default:active,\n.btn-default.disabled.active,\n.btn-default[disabled].active,\nfieldset[disabled] .btn-default.active {\n  background-color: #fff;\n  border-color: #ccc;\n}\n.btn-default .badge {\n  color: #fff;\n  background-color: #333;\n}\n.btn-primary {\n  color: #fff;\n  background-color: #337ab7;\n  border-color: #2e6da4;\n}\n.btn-primary:hover,\n.btn-primary:focus,\n.btn-primary.focus,\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n  color: #fff;\n  background-color: #286090;\n  border-color: #204d74;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n  background-image: none;\n}\n.btn-primary.disabled,\n.btn-primary[disabled],\nfieldset[disabled] .btn-primary,\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus,\n.btn-primary.disabled:active,\n.btn-primary[disabled]:active,\nfieldset[disabled] .btn-primary:active,\n.btn-primary.disabled.active,\n.btn-primary[disabled].active,\nfieldset[disabled] .btn-primary.active {\n  background-color: #337ab7;\n  border-color: #2e6da4;\n}\n.btn-primary .badge {\n  color: #337ab7;\n  background-color: #fff;\n}\n.btn-success {\n  color: #fff;\n  background-color: #5cb85c;\n  border-color: #4cae4c;\n}\n.btn-success:hover,\n.btn-success:focus,\n.btn-success.focus,\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n  color: #fff;\n  background-color: #449d44;\n  border-color: #398439;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n  background-image: none;\n}\n.btn-success.disabled,\n.btn-success[disabled],\nfieldset[disabled] .btn-success,\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus,\n.btn-success.disabled:active,\n.btn-success[disabled]:active,\nfieldset[disabled] .btn-success:active,\n.btn-success.disabled.active,\n.btn-success[disabled].active,\nfieldset[disabled] .btn-success.active {\n  background-color: #5cb85c;\n  border-color: #4cae4c;\n}\n.btn-success .badge {\n  color: #5cb85c;\n  background-color: #fff;\n}\n.btn-info {\n  color: #fff;\n  background-color: #5bc0de;\n  border-color: #46b8da;\n}\n.btn-info:hover,\n.btn-info:focus,\n.btn-info.focus,\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n  color: #fff;\n  background-color: #31b0d5;\n  border-color: #269abc;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n  background-image: none;\n}\n.btn-info.disabled,\n.btn-info[disabled],\nfieldset[disabled] .btn-info,\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus,\n.btn-info.disabled:active,\n.btn-info[disabled]:active,\nfieldset[disabled] .btn-info:active,\n.btn-info.disabled.active,\n.btn-info[disabled].active,\nfieldset[disabled] .btn-info.active {\n  background-color: #5bc0de;\n  border-color: #46b8da;\n}\n.btn-info .badge {\n  color: #5bc0de;\n  background-color: #fff;\n}\n.btn-warning {\n  color: #fff;\n  background-color: #f0ad4e;\n  border-color: #eea236;\n}\n.btn-warning:hover,\n.btn-warning:focus,\n.btn-warning.focus,\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n  color: #fff;\n  background-color: #ec971f;\n  border-color: #d58512;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n  background-image: none;\n}\n.btn-warning.disabled,\n.btn-warning[disabled],\nfieldset[disabled] .btn-warning,\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus,\n.btn-warning.disabled:active,\n.btn-warning[disabled]:active,\nfieldset[disabled] .btn-warning:active,\n.btn-warning.disabled.active,\n.btn-warning[disabled].active,\nfieldset[disabled] .btn-warning.active {\n  background-color: #f0ad4e;\n  border-color: #eea236;\n}\n.btn-warning .badge {\n  color: #f0ad4e;\n  background-color: #fff;\n}\n.btn-danger {\n  color: #fff;\n  background-color: #d9534f;\n  border-color: #d43f3a;\n}\n.btn-danger:hover,\n.btn-danger:focus,\n.btn-danger.focus,\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n  color: #fff;\n  background-color: #c9302c;\n  border-color: #ac2925;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n  background-image: none;\n}\n.btn-danger.disabled,\n.btn-danger[disabled],\nfieldset[disabled] .btn-danger,\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus,\n.btn-danger.disabled:active,\n.btn-danger[disabled]:active,\nfieldset[disabled] .btn-danger:active,\n.btn-danger.disabled.active,\n.btn-danger[disabled].active,\nfieldset[disabled] .btn-danger.active {\n  background-color: #d9534f;\n  border-color: #d43f3a;\n}\n.btn-danger .badge {\n  color: #d9534f;\n  background-color: #fff;\n}\n.btn-link {\n  font-weight: normal;\n  color: #337ab7;\n  border-radius: 0;\n}\n.btn-link,\n.btn-link:active,\n.btn-link.active,\n.btn-link[disabled],\nfieldset[disabled] .btn-link {\n  background-color: transparent;\n  -webkit-box-shadow: none;\n          box-shadow: none;\n}\n.btn-link,\n.btn-link:hover,\n.btn-link:focus,\n.btn-link:active {\n  border-color: transparent;\n}\n.btn-link:hover,\n.btn-link:focus {\n  color: #23527c;\n  text-decoration: underline;\n  background-color: transparent;\n}\n.btn-link[disabled]:hover,\nfieldset[disabled] .btn-link:hover,\n.btn-link[disabled]:focus,\nfieldset[disabled] .btn-link:focus {\n  color: #777;\n  text-decoration: none;\n}\n.btn-lg,\n.btn-group-lg > .btn {\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\n.btn-sm,\n.btn-group-sm > .btn {\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\n.btn-xs,\n.btn-group-xs > .btn {\n  padding: 1px 5px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\n.btn-block {\n  display: block;\n  width: 100%;\n}\n.btn-block + .btn-block {\n  margin-top: 5px;\n}\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n  width: 100%;\n}\n.fade {\n  opacity: 0;\n  -webkit-transition: opacity .15s linear;\n       -o-transition: opacity .15s linear;\n          transition: opacity .15s linear;\n}\n.fade.in {\n  opacity: 1;\n}\n.collapse {\n  display: none;\n  visibility: hidden;\n}\n.collapse.in {\n  display: block;\n  visibility: visible;\n}\ntr.collapse.in {\n  display: table-row;\n}\ntbody.collapse.in {\n  display: table-row-group;\n}\n.collapsing {\n  position: relative;\n  height: 0;\n  overflow: hidden;\n  -webkit-transition-timing-function: ease;\n       -o-transition-timing-function: ease;\n          transition-timing-function: ease;\n  -webkit-transition-duration: .35s;\n       -o-transition-duration: .35s;\n          transition-duration: .35s;\n  -webkit-transition-property: height, visibility;\n       -o-transition-property: height, visibility;\n          transition-property: height, visibility;\n}\n.caret {\n  display: inline-block;\n  width: 0;\n  height: 0;\n  margin-left: 2px;\n  vertical-align: middle;\n  border-top: 4px solid;\n  border-right: 4px solid transparent;\n  border-left: 4px solid transparent;\n}\n.dropup,\n.dropdown {\n  position: relative;\n}\n.dropdown-toggle:focus {\n  outline: 0;\n}\n.dropdown-menu {\n  position: absolute;\n  top: 100%;\n  left: 0;\n  z-index: 1000;\n  display: none;\n  float: left;\n  min-width: 160px;\n  padding: 5px 0;\n  margin: 2px 0 0;\n  font-size: 14px;\n  text-align: left;\n  list-style: none;\n  background-color: #fff;\n  -webkit-background-clip: padding-box;\n          background-clip: padding-box;\n  border: 1px solid #ccc;\n  border: 1px solid rgba(0, 0, 0, .15);\n  border-radius: 4px;\n  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);\n          box-shadow: 0 6px 12px rgba(0, 0, 0, .175);\n}\n.dropdown-menu.pull-right {\n  right: 0;\n  left: auto;\n}\n.dropdown-menu .divider {\n  height: 1px;\n  margin: 9px 0;\n  overflow: hidden;\n  background-color: #e5e5e5;\n}\n.dropdown-menu > li > a {\n  display: block;\n  padding: 3px 20px;\n  clear: both;\n  font-weight: normal;\n  line-height: 1.42857143;\n  color: #333;\n  white-space: nowrap;\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n  color: #262626;\n  text-decoration: none;\n  background-color: #f5f5f5;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n  color: #fff;\n  text-decoration: none;\n  background-color: #337ab7;\n  outline: 0;\n}\n.dropdown-menu > .disabled > a,\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n  color: #777;\n}\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n  text-decoration: none;\n  cursor: not-allowed;\n  background-color: transparent;\n  background-image: none;\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n}\n.open > .dropdown-menu {\n  display: block;\n}\n.open > a {\n  outline: 0;\n}\n.dropdown-menu-right {\n  right: 0;\n  left: auto;\n}\n.dropdown-menu-left {\n  right: auto;\n  left: 0;\n}\n.dropdown-header {\n  display: block;\n  padding: 3px 20px;\n  font-size: 12px;\n  line-height: 1.42857143;\n  color: #777;\n  white-space: nowrap;\n}\n.dropdown-backdrop {\n  position: fixed;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 990;\n}\n.pull-right > .dropdown-menu {\n  right: 0;\n  left: auto;\n}\n.dropup .caret,\n.navbar-fixed-bottom .dropdown .caret {\n  content: \"\";\n  border-top: 0;\n  border-bottom: 4px solid;\n}\n.dropup .dropdown-menu,\n.navbar-fixed-bottom .dropdown .dropdown-menu {\n  top: auto;\n  bottom: 100%;\n  margin-bottom: 2px;\n}\n@media (min-width: 768px) {\n  .navbar-right .dropdown-menu {\n    right: 0;\n    left: auto;\n  }\n  .navbar-right .dropdown-menu-left {\n    right: auto;\n    left: 0;\n  }\n}\n.btn-group,\n.btn-group-vertical {\n  position: relative;\n  display: inline-block;\n  vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n  position: relative;\n  float: left;\n}\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group-vertical > .btn:focus,\n.btn-group > .btn:active,\n.btn-group-vertical > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn.active {\n  z-index: 2;\n}\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group {\n  margin-left: -1px;\n}\n.btn-toolbar {\n  margin-left: -5px;\n}\n.btn-toolbar .btn-group,\n.btn-toolbar .input-group {\n  float: left;\n}\n.btn-toolbar > .btn,\n.btn-toolbar > .btn-group,\n.btn-toolbar > .input-group {\n  margin-left: 5px;\n}\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n  border-radius: 0;\n}\n.btn-group > .btn:first-child {\n  margin-left: 0;\n}\n.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group > .btn-group {\n  float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n  border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n  outline: 0;\n}\n.btn-group > .btn + .dropdown-toggle {\n  padding-right: 8px;\n  padding-left: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n  padding-right: 12px;\n  padding-left: 12px;\n}\n.btn-group.open .dropdown-toggle {\n  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n          box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n}\n.btn-group.open .dropdown-toggle.btn-link {\n  -webkit-box-shadow: none;\n          box-shadow: none;\n}\n.btn .caret {\n  margin-left: 0;\n}\n.btn-lg .caret {\n  border-width: 5px 5px 0;\n  border-bottom-width: 0;\n}\n.dropup .btn-lg .caret {\n  border-width: 0 5px 5px;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group,\n.btn-group-vertical > .btn-group > .btn {\n  display: block;\n  float: none;\n  width: 100%;\n  max-width: 100%;\n}\n.btn-group-vertical > .btn-group > .btn {\n  float: none;\n}\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n  margin-top: -1px;\n  margin-left: 0;\n}\n.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\n  border-radius: 0;\n}\n.btn-group-vertical > .btn:first-child:not(:last-child) {\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn:last-child:not(:first-child) {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n  border-bottom-left-radius: 4px;\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n  border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n  border-bottom-right-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.btn-group-justified {\n  display: table;\n  width: 100%;\n  table-layout: fixed;\n  border-collapse: separate;\n}\n.btn-group-justified > .btn,\n.btn-group-justified > .btn-group {\n  display: table-cell;\n  float: none;\n  width: 1%;\n}\n.btn-group-justified > .btn-group .btn {\n  width: 100%;\n}\n.btn-group-justified > .btn-group .dropdown-menu {\n  left: auto;\n}\n[data-toggle=\"buttons\"] > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn input[type=\"checkbox\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"checkbox\"] {\n  position: absolute;\n  clip: rect(0, 0, 0, 0);\n  pointer-events: none;\n}\n.input-group {\n  position: relative;\n  display: table;\n  border-collapse: separate;\n}\n.input-group[class*=\"col-\"] {\n  float: none;\n  padding-right: 0;\n  padding-left: 0;\n}\n.input-group .form-control {\n  position: relative;\n  z-index: 2;\n  float: left;\n  width: 100%;\n  margin-bottom: 0;\n}\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n  height: 46px;\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\nselect.input-group-lg > .form-control,\nselect.input-group-lg > .input-group-addon,\nselect.input-group-lg > .input-group-btn > .btn {\n  height: 46px;\n  line-height: 46px;\n}\ntextarea.input-group-lg > .form-control,\ntextarea.input-group-lg > .input-group-addon,\ntextarea.input-group-lg > .input-group-btn > .btn,\nselect[multiple].input-group-lg > .form-control,\nselect[multiple].input-group-lg > .input-group-addon,\nselect[multiple].input-group-lg > .input-group-btn > .btn {\n  height: auto;\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n  height: 30px;\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\nselect.input-group-sm > .form-control,\nselect.input-group-sm > .input-group-addon,\nselect.input-group-sm > .input-group-btn > .btn {\n  height: 30px;\n  line-height: 30px;\n}\ntextarea.input-group-sm > .form-control,\ntextarea.input-group-sm > .input-group-addon,\ntextarea.input-group-sm > .input-group-btn > .btn,\nselect[multiple].input-group-sm > .form-control,\nselect[multiple].input-group-sm > .input-group-addon,\nselect[multiple].input-group-sm > .input-group-btn > .btn {\n  height: auto;\n}\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n  display: table-cell;\n}\n.input-group-addon:not(:first-child):not(:last-child),\n.input-group-btn:not(:first-child):not(:last-child),\n.input-group .form-control:not(:first-child):not(:last-child) {\n  border-radius: 0;\n}\n.input-group-addon,\n.input-group-btn {\n  width: 1%;\n  white-space: nowrap;\n  vertical-align: middle;\n}\n.input-group-addon {\n  padding: 6px 12px;\n  font-size: 14px;\n  font-weight: normal;\n  line-height: 1;\n  color: #555;\n  text-align: center;\n  background-color: #eee;\n  border: 1px solid #ccc;\n  border-radius: 4px;\n}\n.input-group-addon.input-sm {\n  padding: 5px 10px;\n  font-size: 12px;\n  border-radius: 3px;\n}\n.input-group-addon.input-lg {\n  padding: 10px 16px;\n  font-size: 18px;\n  border-radius: 6px;\n}\n.input-group-addon input[type=\"radio\"],\n.input-group-addon input[type=\"checkbox\"] {\n  margin-top: 0;\n}\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.input-group-addon:first-child {\n  border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.input-group-addon:last-child {\n  border-left: 0;\n}\n.input-group-btn {\n  position: relative;\n  font-size: 0;\n  white-space: nowrap;\n}\n.input-group-btn > .btn {\n  position: relative;\n}\n.input-group-btn > .btn + .btn {\n  margin-left: -1px;\n}\n.input-group-btn > .btn:hover,\n.input-group-btn > .btn:focus,\n.input-group-btn > .btn:active {\n  z-index: 2;\n}\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group {\n  margin-right: -1px;\n}\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group {\n  margin-left: -1px;\n}\n.nav {\n  padding-left: 0;\n  margin-bottom: 0;\n  list-style: none;\n}\n.nav > li {\n  position: relative;\n  display: block;\n}\n.nav > li > a {\n  position: relative;\n  display: block;\n  padding: 10px 15px;\n}\n.nav > li > a:hover,\n.nav > li > a:focus {\n  text-decoration: none;\n  background-color: #eee;\n}\n.nav > li.disabled > a {\n  color: #777;\n}\n.nav > li.disabled > a:hover,\n.nav > li.disabled > a:focus {\n  color: #777;\n  text-decoration: none;\n  cursor: not-allowed;\n  background-color: transparent;\n}\n.nav .open > a,\n.nav .open > a:hover,\n.nav .open > a:focus {\n  background-color: #eee;\n  border-color: #337ab7;\n}\n.nav .nav-divider {\n  height: 1px;\n  margin: 9px 0;\n  overflow: hidden;\n  background-color: #e5e5e5;\n}\n.nav > li > a > img {\n  max-width: none;\n}\n.nav-tabs {\n  border-bottom: 1px solid #ddd;\n}\n.nav-tabs > li {\n  float: left;\n  margin-bottom: -1px;\n}\n.nav-tabs > li > a {\n  margin-right: 2px;\n  line-height: 1.42857143;\n  border: 1px solid transparent;\n  border-radius: 4px 4px 0 0;\n}\n.nav-tabs > li > a:hover {\n  border-color: #eee #eee #ddd;\n}\n.nav-tabs > li.active > a,\n.nav-tabs > li.active > a:hover,\n.nav-tabs > li.active > a:focus {\n  color: #555;\n  cursor: default;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-bottom-color: transparent;\n}\n.nav-tabs.nav-justified {\n  width: 100%;\n  border-bottom: 0;\n}\n.nav-tabs.nav-justified > li {\n  float: none;\n}\n.nav-tabs.nav-justified > li > a {\n  margin-bottom: 5px;\n  text-align: center;\n}\n.nav-tabs.nav-justified > .dropdown .dropdown-menu {\n  top: auto;\n  left: auto;\n}\n@media (min-width: 768px) {\n  .nav-tabs.nav-justified > li {\n    display: table-cell;\n    width: 1%;\n  }\n  .nav-tabs.nav-justified > li > a {\n    margin-bottom: 0;\n  }\n}\n.nav-tabs.nav-justified > li > a {\n  margin-right: 0;\n  border-radius: 4px;\n}\n.nav-tabs.nav-justified > .active > a,\n.nav-tabs.nav-justified > .active > a:hover,\n.nav-tabs.nav-justified > .active > a:focus {\n  border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n  .nav-tabs.nav-justified > li > a {\n    border-bottom: 1px solid #ddd;\n    border-radius: 4px 4px 0 0;\n  }\n  .nav-tabs.nav-justified > .active > a,\n  .nav-tabs.nav-justified > .active > a:hover,\n  .nav-tabs.nav-justified > .active > a:focus {\n    border-bottom-color: #fff;\n  }\n}\n.nav-pills > li {\n  float: left;\n}\n.nav-pills > li > a {\n  border-radius: 4px;\n}\n.nav-pills > li + li {\n  margin-left: 2px;\n}\n.nav-pills > li.active > a,\n.nav-pills > li.active > a:hover,\n.nav-pills > li.active > a:focus {\n  color: #fff;\n  background-color: #337ab7;\n}\n.nav-stacked > li {\n  float: none;\n}\n.nav-stacked > li + li {\n  margin-top: 2px;\n  margin-left: 0;\n}\n.nav-justified {\n  width: 100%;\n}\n.nav-justified > li {\n  float: none;\n}\n.nav-justified > li > a {\n  margin-bottom: 5px;\n  text-align: center;\n}\n.nav-justified > .dropdown .dropdown-menu {\n  top: auto;\n  left: auto;\n}\n@media (min-width: 768px) {\n  .nav-justified > li {\n    display: table-cell;\n    width: 1%;\n  }\n  .nav-justified > li > a {\n    margin-bottom: 0;\n  }\n}\n.nav-tabs-justified {\n  border-bottom: 0;\n}\n.nav-tabs-justified > li > a {\n  margin-right: 0;\n  border-radius: 4px;\n}\n.nav-tabs-justified > .active > a,\n.nav-tabs-justified > .active > a:hover,\n.nav-tabs-justified > .active > a:focus {\n  border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n  .nav-tabs-justified > li > a {\n    border-bottom: 1px solid #ddd;\n    border-radius: 4px 4px 0 0;\n  }\n  .nav-tabs-justified > .active > a,\n  .nav-tabs-justified > .active > a:hover,\n  .nav-tabs-justified > .active > a:focus {\n    border-bottom-color: #fff;\n  }\n}\n.tab-content > .tab-pane {\n  display: none;\n  visibility: hidden;\n}\n.tab-content > .active {\n  display: block;\n  visibility: visible;\n}\n.nav-tabs .dropdown-menu {\n  margin-top: -1px;\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.navbar {\n  position: relative;\n  min-height: 50px;\n  margin-bottom: 20px;\n  border: 1px solid transparent;\n}\n@media (min-width: 768px) {\n  .navbar {\n    border-radius: 4px;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-header {\n    float: left;\n  }\n}\n.navbar-collapse {\n  padding-right: 15px;\n  padding-left: 15px;\n  overflow-x: visible;\n  -webkit-overflow-scrolling: touch;\n  border-top: 1px solid transparent;\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);\n          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);\n}\n.navbar-collapse.in {\n  overflow-y: auto;\n}\n@media (min-width: 768px) {\n  .navbar-collapse {\n    width: auto;\n    border-top: 0;\n    -webkit-box-shadow: none;\n            box-shadow: none;\n  }\n  .navbar-collapse.collapse {\n    display: block !important;\n    height: auto !important;\n    padding-bottom: 0;\n    overflow: visible !important;\n    visibility: visible !important;\n  }\n  .navbar-collapse.in {\n    overflow-y: visible;\n  }\n  .navbar-fixed-top .navbar-collapse,\n  .navbar-static-top .navbar-collapse,\n  .navbar-fixed-bottom .navbar-collapse {\n    padding-right: 0;\n    padding-left: 0;\n  }\n}\n.navbar-fixed-top .navbar-collapse,\n.navbar-fixed-bottom .navbar-collapse {\n  max-height: 340px;\n}\n@media (max-device-width: 480px) and (orientation: landscape) {\n  .navbar-fixed-top .navbar-collapse,\n  .navbar-fixed-bottom .navbar-collapse {\n    max-height: 200px;\n  }\n}\n.container > .navbar-header,\n.container-fluid > .navbar-header,\n.container > .navbar-collapse,\n.container-fluid > .navbar-collapse {\n  margin-right: -15px;\n  margin-left: -15px;\n}\n@media (min-width: 768px) {\n  .container > .navbar-header,\n  .container-fluid > .navbar-header,\n  .container > .navbar-collapse,\n  .container-fluid > .navbar-collapse {\n    margin-right: 0;\n    margin-left: 0;\n  }\n}\n.navbar-static-top {\n  z-index: 1000;\n  border-width: 0 0 1px;\n}\n@media (min-width: 768px) {\n  .navbar-static-top {\n    border-radius: 0;\n  }\n}\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n  position: fixed;\n  right: 0;\n  left: 0;\n  z-index: 1030;\n}\n@media (min-width: 768px) {\n  .navbar-fixed-top,\n  .navbar-fixed-bottom {\n    border-radius: 0;\n  }\n}\n.navbar-fixed-top {\n  top: 0;\n  border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n  bottom: 0;\n  margin-bottom: 0;\n  border-width: 1px 0 0;\n}\n.navbar-brand {\n  float: left;\n  height: 50px;\n  padding: 15px 15px;\n  font-size: 18px;\n  line-height: 20px;\n}\n.navbar-brand:hover,\n.navbar-brand:focus {\n  text-decoration: none;\n}\n.navbar-brand > img {\n  display: block;\n}\n@media (min-width: 768px) {\n  .navbar > .container .navbar-brand,\n  .navbar > .container-fluid .navbar-brand {\n    margin-left: -15px;\n  }\n}\n.navbar-toggle {\n  position: relative;\n  float: right;\n  padding: 9px 10px;\n  margin-top: 8px;\n  margin-right: 15px;\n  margin-bottom: 8px;\n  background-color: transparent;\n  background-image: none;\n  border: 1px solid transparent;\n  border-radius: 4px;\n}\n.navbar-toggle:focus {\n  outline: 0;\n}\n.navbar-toggle .icon-bar {\n  display: block;\n  width: 22px;\n  height: 2px;\n  border-radius: 1px;\n}\n.navbar-toggle .icon-bar + .icon-bar {\n  margin-top: 4px;\n}\n@media (min-width: 768px) {\n  .navbar-toggle {\n    display: none;\n  }\n}\n.navbar-nav {\n  margin: 7.5px -15px;\n}\n.navbar-nav > li > a {\n  padding-top: 10px;\n  padding-bottom: 10px;\n  line-height: 20px;\n}\n@media (max-width: 767px) {\n  .navbar-nav .open .dropdown-menu {\n    position: static;\n    float: none;\n    width: auto;\n    margin-top: 0;\n    background-color: transparent;\n    border: 0;\n    -webkit-box-shadow: none;\n            box-shadow: none;\n  }\n  .navbar-nav .open .dropdown-menu > li > a,\n  .navbar-nav .open .dropdown-menu .dropdown-header {\n    padding: 5px 15px 5px 25px;\n  }\n  .navbar-nav .open .dropdown-menu > li > a {\n    line-height: 20px;\n  }\n  .navbar-nav .open .dropdown-menu > li > a:hover,\n  .navbar-nav .open .dropdown-menu > li > a:focus {\n    background-image: none;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-nav {\n    float: left;\n    margin: 0;\n  }\n  .navbar-nav > li {\n    float: left;\n  }\n  .navbar-nav > li > a {\n    padding-top: 15px;\n    padding-bottom: 15px;\n  }\n}\n.navbar-form {\n  padding: 10px 15px;\n  margin-top: 8px;\n  margin-right: -15px;\n  margin-bottom: 8px;\n  margin-left: -15px;\n  border-top: 1px solid transparent;\n  border-bottom: 1px solid transparent;\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);\n          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);\n}\n@media (min-width: 768px) {\n  .navbar-form .form-group {\n    display: inline-block;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .navbar-form .form-control {\n    display: inline-block;\n    width: auto;\n    vertical-align: middle;\n  }\n  .navbar-form .form-control-static {\n    display: inline-block;\n  }\n  .navbar-form .input-group {\n    display: inline-table;\n    vertical-align: middle;\n  }\n  .navbar-form .input-group .input-group-addon,\n  .navbar-form .input-group .input-group-btn,\n  .navbar-form .input-group .form-control {\n    width: auto;\n  }\n  .navbar-form .input-group > .form-control {\n    width: 100%;\n  }\n  .navbar-form .control-label {\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .navbar-form .radio,\n  .navbar-form .checkbox {\n    display: inline-block;\n    margin-top: 0;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .navbar-form .radio label,\n  .navbar-form .checkbox label {\n    padding-left: 0;\n  }\n  .navbar-form .radio input[type=\"radio\"],\n  .navbar-form .checkbox input[type=\"checkbox\"] {\n    position: relative;\n    margin-left: 0;\n  }\n  .navbar-form .has-feedback .form-control-feedback {\n    top: 0;\n  }\n}\n@media (max-width: 767px) {\n  .navbar-form .form-group {\n    margin-bottom: 5px;\n  }\n  .navbar-form .form-group:last-child {\n    margin-bottom: 0;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-form {\n    width: auto;\n    padding-top: 0;\n    padding-bottom: 0;\n    margin-right: 0;\n    margin-left: 0;\n    border: 0;\n    -webkit-box-shadow: none;\n            box-shadow: none;\n  }\n}\n.navbar-nav > li > .dropdown-menu {\n  margin-top: 0;\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n  margin-bottom: 0;\n  border-top-left-radius: 4px;\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.navbar-btn {\n  margin-top: 8px;\n  margin-bottom: 8px;\n}\n.navbar-btn.btn-sm {\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\n.navbar-btn.btn-xs {\n  margin-top: 14px;\n  margin-bottom: 14px;\n}\n.navbar-text {\n  margin-top: 15px;\n  margin-bottom: 15px;\n}\n@media (min-width: 768px) {\n  .navbar-text {\n    float: left;\n    margin-right: 15px;\n    margin-left: 15px;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-left {\n    float: left !important;\n  }\n  .navbar-right {\n    float: right !important;\n    margin-right: -15px;\n  }\n  .navbar-right ~ .navbar-right {\n    margin-right: 0;\n  }\n}\n.navbar-default {\n  background-color: #f8f8f8;\n  border-color: #e7e7e7;\n}\n.navbar-default .navbar-brand {\n  color: #777;\n}\n.navbar-default .navbar-brand:hover,\n.navbar-default .navbar-brand:focus {\n  color: #5e5e5e;\n  background-color: transparent;\n}\n.navbar-default .navbar-text {\n  color: #777;\n}\n.navbar-default .navbar-nav > li > a {\n  color: #777;\n}\n.navbar-default .navbar-nav > li > a:hover,\n.navbar-default .navbar-nav > li > a:focus {\n  color: #333;\n  background-color: transparent;\n}\n.navbar-default .navbar-nav > .active > a,\n.navbar-default .navbar-nav > .active > a:hover,\n.navbar-default .navbar-nav > .active > a:focus {\n  color: #555;\n  background-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .disabled > a,\n.navbar-default .navbar-nav > .disabled > a:hover,\n.navbar-default .navbar-nav > .disabled > a:focus {\n  color: #ccc;\n  background-color: transparent;\n}\n.navbar-default .navbar-toggle {\n  border-color: #ddd;\n}\n.navbar-default .navbar-toggle:hover,\n.navbar-default .navbar-toggle:focus {\n  background-color: #ddd;\n}\n.navbar-default .navbar-toggle .icon-bar {\n  background-color: #888;\n}\n.navbar-default .navbar-collapse,\n.navbar-default .navbar-form {\n  border-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .open > a:hover,\n.navbar-default .navbar-nav > .open > a:focus {\n  color: #555;\n  background-color: #e7e7e7;\n}\n@media (max-width: 767px) {\n  .navbar-default .navbar-nav .open .dropdown-menu > li > a {\n    color: #777;\n  }\n  .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,\n  .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {\n    color: #333;\n    background-color: transparent;\n  }\n  .navbar-default .navbar-nav .open .dropdown-menu > .active > a,\n  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,\n  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {\n    color: #555;\n    background-color: #e7e7e7;\n  }\n  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,\n  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n    color: #ccc;\n    background-color: transparent;\n  }\n}\n.navbar-default .navbar-link {\n  color: #777;\n}\n.navbar-default .navbar-link:hover {\n  color: #333;\n}\n.navbar-default .btn-link {\n  color: #777;\n}\n.navbar-default .btn-link:hover,\n.navbar-default .btn-link:focus {\n  color: #333;\n}\n.navbar-default .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-default .btn-link:hover,\n.navbar-default .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-default .btn-link:focus {\n  color: #ccc;\n}\n.navbar-inverse {\n  background-color: #222;\n  border-color: #080808;\n}\n.navbar-inverse .navbar-brand {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-brand:hover,\n.navbar-inverse .navbar-brand:focus {\n  color: #fff;\n  background-color: transparent;\n}\n.navbar-inverse .navbar-text {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a:hover,\n.navbar-inverse .navbar-nav > li > a:focus {\n  color: #fff;\n  background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .active > a,\n.navbar-inverse .navbar-nav > .active > a:hover,\n.navbar-inverse .navbar-nav > .active > a:focus {\n  color: #fff;\n  background-color: #080808;\n}\n.navbar-inverse .navbar-nav > .disabled > a,\n.navbar-inverse .navbar-nav > .disabled > a:hover,\n.navbar-inverse .navbar-nav > .disabled > a:focus {\n  color: #444;\n  background-color: transparent;\n}\n.navbar-inverse .navbar-toggle {\n  border-color: #333;\n}\n.navbar-inverse .navbar-toggle:hover,\n.navbar-inverse .navbar-toggle:focus {\n  background-color: #333;\n}\n.navbar-inverse .navbar-toggle .icon-bar {\n  background-color: #fff;\n}\n.navbar-inverse .navbar-collapse,\n.navbar-inverse .navbar-form {\n  border-color: #101010;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .open > a:hover,\n.navbar-inverse .navbar-nav > .open > a:focus {\n  color: #fff;\n  background-color: #080808;\n}\n@media (max-width: 767px) {\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {\n    border-color: #080808;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu .divider {\n    background-color: #080808;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {\n    color: #9d9d9d;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {\n    color: #fff;\n    background-color: transparent;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {\n    color: #fff;\n    background-color: #080808;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n    color: #444;\n    background-color: transparent;\n  }\n}\n.navbar-inverse .navbar-link {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-link:hover {\n  color: #fff;\n}\n.navbar-inverse .btn-link {\n  color: #9d9d9d;\n}\n.navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link:focus {\n  color: #fff;\n}\n.navbar-inverse .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-inverse .btn-link:focus {\n  color: #444;\n}\n.breadcrumb {\n  padding: 8px 15px;\n  margin-bottom: 20px;\n  list-style: none;\n  background-color: #f5f5f5;\n  border-radius: 4px;\n}\n.breadcrumb > li {\n  display: inline-block;\n}\n.breadcrumb > li + li:before {\n  padding: 0 5px;\n  color: #ccc;\n  content: \"/\\00a0\";\n}\n.breadcrumb > .active {\n  color: #777;\n}\n.pagination {\n  display: inline-block;\n  padding-left: 0;\n  margin: 20px 0;\n  border-radius: 4px;\n}\n.pagination > li {\n  display: inline;\n}\n.pagination > li > a,\n.pagination > li > span {\n  position: relative;\n  float: left;\n  padding: 6px 12px;\n  margin-left: -1px;\n  line-height: 1.42857143;\n  color: #337ab7;\n  text-decoration: none;\n  background-color: #fff;\n  border: 1px solid #ddd;\n}\n.pagination > li:first-child > a,\n.pagination > li:first-child > span {\n  margin-left: 0;\n  border-top-left-radius: 4px;\n  border-bottom-left-radius: 4px;\n}\n.pagination > li:last-child > a,\n.pagination > li:last-child > span {\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 4px;\n}\n.pagination > li > a:hover,\n.pagination > li > span:hover,\n.pagination > li > a:focus,\n.pagination > li > span:focus {\n  color: #23527c;\n  background-color: #eee;\n  border-color: #ddd;\n}\n.pagination > .active > a,\n.pagination > .active > span,\n.pagination > .active > a:hover,\n.pagination > .active > span:hover,\n.pagination > .active > a:focus,\n.pagination > .active > span:focus {\n  z-index: 2;\n  color: #fff;\n  cursor: default;\n  background-color: #337ab7;\n  border-color: #337ab7;\n}\n.pagination > .disabled > span,\n.pagination > .disabled > span:hover,\n.pagination > .disabled > span:focus,\n.pagination > .disabled > a,\n.pagination > .disabled > a:hover,\n.pagination > .disabled > a:focus {\n  color: #777;\n  cursor: not-allowed;\n  background-color: #fff;\n  border-color: #ddd;\n}\n.pagination-lg > li > a,\n.pagination-lg > li > span {\n  padding: 10px 16px;\n  font-size: 18px;\n}\n.pagination-lg > li:first-child > a,\n.pagination-lg > li:first-child > span {\n  border-top-left-radius: 6px;\n  border-bottom-left-radius: 6px;\n}\n.pagination-lg > li:last-child > a,\n.pagination-lg > li:last-child > span {\n  border-top-right-radius: 6px;\n  border-bottom-right-radius: 6px;\n}\n.pagination-sm > li > a,\n.pagination-sm > li > span {\n  padding: 5px 10px;\n  font-size: 12px;\n}\n.pagination-sm > li:first-child > a,\n.pagination-sm > li:first-child > span {\n  border-top-left-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.pagination-sm > li:last-child > a,\n.pagination-sm > li:last-child > span {\n  border-top-right-radius: 3px;\n  border-bottom-right-radius: 3px;\n}\n.pager {\n  padding-left: 0;\n  margin: 20px 0;\n  text-align: center;\n  list-style: none;\n}\n.pager li {\n  display: inline;\n}\n.pager li > a,\n.pager li > span {\n  display: inline-block;\n  padding: 5px 14px;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-radius: 15px;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n  text-decoration: none;\n  background-color: #eee;\n}\n.pager .next > a,\n.pager .next > span {\n  float: right;\n}\n.pager .previous > a,\n.pager .previous > span {\n  float: left;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n  color: #777;\n  cursor: not-allowed;\n  background-color: #fff;\n}\n.label {\n  display: inline;\n  padding: .2em .6em .3em;\n  font-size: 75%;\n  font-weight: bold;\n  line-height: 1;\n  color: #fff;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: baseline;\n  border-radius: .25em;\n}\na.label:hover,\na.label:focus {\n  color: #fff;\n  text-decoration: none;\n  cursor: pointer;\n}\n.label:empty {\n  display: none;\n}\n.btn .label {\n  position: relative;\n  top: -1px;\n}\n.label-default {\n  background-color: #777;\n}\n.label-default[href]:hover,\n.label-default[href]:focus {\n  background-color: #5e5e5e;\n}\n.label-primary {\n  background-color: #337ab7;\n}\n.label-primary[href]:hover,\n.label-primary[href]:focus {\n  background-color: #286090;\n}\n.label-success {\n  background-color: #5cb85c;\n}\n.label-success[href]:hover,\n.label-success[href]:focus {\n  background-color: #449d44;\n}\n.label-info {\n  background-color: #5bc0de;\n}\n.label-info[href]:hover,\n.label-info[href]:focus {\n  background-color: #31b0d5;\n}\n.label-warning {\n  background-color: #f0ad4e;\n}\n.label-warning[href]:hover,\n.label-warning[href]:focus {\n  background-color: #ec971f;\n}\n.label-danger {\n  background-color: #d9534f;\n}\n.label-danger[href]:hover,\n.label-danger[href]:focus {\n  background-color: #c9302c;\n}\n.badge {\n  display: inline-block;\n  min-width: 10px;\n  padding: 3px 7px;\n  font-size: 12px;\n  font-weight: bold;\n  line-height: 1;\n  color: #fff;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: baseline;\n  background-color: #777;\n  border-radius: 10px;\n}\n.badge:empty {\n  display: none;\n}\n.btn .badge {\n  position: relative;\n  top: -1px;\n}\n.btn-xs .badge {\n  top: 0;\n  padding: 1px 5px;\n}\na.badge:hover,\na.badge:focus {\n  color: #fff;\n  text-decoration: none;\n  cursor: pointer;\n}\n.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n  color: #337ab7;\n  background-color: #fff;\n}\n.list-group-item > .badge {\n  float: right;\n}\n.list-group-item > .badge + .badge {\n  margin-right: 5px;\n}\n.nav-pills > li > a > .badge {\n  margin-left: 3px;\n}\n.jumbotron {\n  padding: 30px 15px;\n  margin-bottom: 30px;\n  color: inherit;\n  background-color: #eee;\n}\n.jumbotron h1,\n.jumbotron .h1 {\n  color: inherit;\n}\n.jumbotron p {\n  margin-bottom: 15px;\n  font-size: 21px;\n  font-weight: 200;\n}\n.jumbotron > hr {\n  border-top-color: #d5d5d5;\n}\n.container .jumbotron,\n.container-fluid .jumbotron {\n  border-radius: 6px;\n}\n.jumbotron .container {\n  max-width: 100%;\n}\n@media screen and (min-width: 768px) {\n  .jumbotron {\n    padding: 48px 0;\n  }\n  .container .jumbotron,\n  .container-fluid .jumbotron {\n    padding-right: 60px;\n    padding-left: 60px;\n  }\n  .jumbotron h1,\n  .jumbotron .h1 {\n    font-size: 63px;\n  }\n}\n.thumbnail {\n  display: block;\n  padding: 4px;\n  margin-bottom: 20px;\n  line-height: 1.42857143;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-radius: 4px;\n  -webkit-transition: border .2s ease-in-out;\n       -o-transition: border .2s ease-in-out;\n          transition: border .2s ease-in-out;\n}\n.thumbnail > img,\n.thumbnail a > img {\n  margin-right: auto;\n  margin-left: auto;\n}\na.thumbnail:hover,\na.thumbnail:focus,\na.thumbnail.active {\n  border-color: #337ab7;\n}\n.thumbnail .caption {\n  padding: 9px;\n  color: #333;\n}\n.alert {\n  padding: 15px;\n  margin-bottom: 20px;\n  border: 1px solid transparent;\n  border-radius: 4px;\n}\n.alert h4 {\n  margin-top: 0;\n  color: inherit;\n}\n.alert .alert-link {\n  font-weight: bold;\n}\n.alert > p,\n.alert > ul {\n  margin-bottom: 0;\n}\n.alert > p + p {\n  margin-top: 5px;\n}\n.alert-dismissable,\n.alert-dismissible {\n  padding-right: 35px;\n}\n.alert-dismissable .close,\n.alert-dismissible .close {\n  position: relative;\n  top: -2px;\n  right: -21px;\n  color: inherit;\n}\n.alert-success {\n  color: #3c763d;\n  background-color: #dff0d8;\n  border-color: #d6e9c6;\n}\n.alert-success hr {\n  border-top-color: #c9e2b3;\n}\n.alert-success .alert-link {\n  color: #2b542c;\n}\n.alert-info {\n  color: #31708f;\n  background-color: #d9edf7;\n  border-color: #bce8f1;\n}\n.alert-info hr {\n  border-top-color: #a6e1ec;\n}\n.alert-info .alert-link {\n  color: #245269;\n}\n.alert-warning {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n  border-color: #faebcc;\n}\n.alert-warning hr {\n  border-top-color: #f7e1b5;\n}\n.alert-warning .alert-link {\n  color: #66512c;\n}\n.alert-danger {\n  color: #a94442;\n  background-color: #f2dede;\n  border-color: #ebccd1;\n}\n.alert-danger hr {\n  border-top-color: #e4b9c0;\n}\n.alert-danger .alert-link {\n  color: #843534;\n}\n@-webkit-keyframes progress-bar-stripes {\n  from {\n    background-position: 40px 0;\n  }\n  to {\n    background-position: 0 0;\n  }\n}\n@-o-keyframes progress-bar-stripes {\n  from {\n    background-position: 40px 0;\n  }\n  to {\n    background-position: 0 0;\n  }\n}\n@keyframes progress-bar-stripes {\n  from {\n    background-position: 40px 0;\n  }\n  to {\n    background-position: 0 0;\n  }\n}\n.progress {\n  height: 20px;\n  margin-bottom: 20px;\n  overflow: hidden;\n  background-color: #f5f5f5;\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);\n          box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);\n}\n.progress-bar {\n  float: left;\n  width: 0;\n  height: 100%;\n  font-size: 12px;\n  line-height: 20px;\n  color: #fff;\n  text-align: center;\n  background-color: #337ab7;\n  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);\n          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);\n  -webkit-transition: width .6s ease;\n       -o-transition: width .6s ease;\n          transition: width .6s ease;\n}\n.progress-striped .progress-bar,\n.progress-bar-striped {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  -webkit-background-size: 40px 40px;\n          background-size: 40px 40px;\n}\n.progress.active .progress-bar,\n.progress-bar.active {\n  -webkit-animation: progress-bar-stripes 2s linear infinite;\n       -o-animation: progress-bar-stripes 2s linear infinite;\n          animation: progress-bar-stripes 2s linear infinite;\n}\n.progress-bar-success {\n  background-color: #5cb85c;\n}\n.progress-striped .progress-bar-success {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n}\n.progress-bar-info {\n  background-color: #5bc0de;\n}\n.progress-striped .progress-bar-info {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n}\n.progress-bar-warning {\n  background-color: #f0ad4e;\n}\n.progress-striped .progress-bar-warning {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n}\n.progress-bar-danger {\n  background-color: #d9534f;\n}\n.progress-striped .progress-bar-danger {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n}\n.media {\n  margin-top: 15px;\n}\n.media:first-child {\n  margin-top: 0;\n}\n.media,\n.media-body {\n  overflow: hidden;\n  zoom: 1;\n}\n.media-body {\n  width: 10000px;\n}\n.media-object {\n  display: block;\n}\n.media-right,\n.media > .pull-right {\n  padding-left: 10px;\n}\n.media-left,\n.media > .pull-left {\n  padding-right: 10px;\n}\n.media-left,\n.media-right,\n.media-body {\n  display: table-cell;\n  vertical-align: top;\n}\n.media-middle {\n  vertical-align: middle;\n}\n.media-bottom {\n  vertical-align: bottom;\n}\n.media-heading {\n  margin-top: 0;\n  margin-bottom: 5px;\n}\n.media-list {\n  padding-left: 0;\n  list-style: none;\n}\n.list-group {\n  padding-left: 0;\n  margin-bottom: 20px;\n}\n.list-group-item {\n  position: relative;\n  display: block;\n  padding: 10px 15px;\n  margin-bottom: -1px;\n  background-color: #fff;\n  border: 1px solid #ddd;\n}\n.list-group-item:first-child {\n  border-top-left-radius: 4px;\n  border-top-right-radius: 4px;\n}\n.list-group-item:last-child {\n  margin-bottom: 0;\n  border-bottom-right-radius: 4px;\n  border-bottom-left-radius: 4px;\n}\na.list-group-item {\n  color: #555;\n}\na.list-group-item .list-group-item-heading {\n  color: #333;\n}\na.list-group-item:hover,\na.list-group-item:focus {\n  color: #555;\n  text-decoration: none;\n  background-color: #f5f5f5;\n}\n.list-group-item.disabled,\n.list-group-item.disabled:hover,\n.list-group-item.disabled:focus {\n  color: #777;\n  cursor: not-allowed;\n  background-color: #eee;\n}\n.list-group-item.disabled .list-group-item-heading,\n.list-group-item.disabled:hover .list-group-item-heading,\n.list-group-item.disabled:focus .list-group-item-heading {\n  color: inherit;\n}\n.list-group-item.disabled .list-group-item-text,\n.list-group-item.disabled:hover .list-group-item-text,\n.list-group-item.disabled:focus .list-group-item-text {\n  color: #777;\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n  z-index: 2;\n  color: #fff;\n  background-color: #337ab7;\n  border-color: #337ab7;\n}\n.list-group-item.active .list-group-item-heading,\n.list-group-item.active:hover .list-group-item-heading,\n.list-group-item.active:focus .list-group-item-heading,\n.list-group-item.active .list-group-item-heading > small,\n.list-group-item.active:hover .list-group-item-heading > small,\n.list-group-item.active:focus .list-group-item-heading > small,\n.list-group-item.active .list-group-item-heading > .small,\n.list-group-item.active:hover .list-group-item-heading > .small,\n.list-group-item.active:focus .list-group-item-heading > .small {\n  color: inherit;\n}\n.list-group-item.active .list-group-item-text,\n.list-group-item.active:hover .list-group-item-text,\n.list-group-item.active:focus .list-group-item-text {\n  color: #c7ddef;\n}\n.list-group-item-success {\n  color: #3c763d;\n  background-color: #dff0d8;\n}\na.list-group-item-success {\n  color: #3c763d;\n}\na.list-group-item-success .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-success:hover,\na.list-group-item-success:focus {\n  color: #3c763d;\n  background-color: #d0e9c6;\n}\na.list-group-item-success.active,\na.list-group-item-success.active:hover,\na.list-group-item-success.active:focus {\n  color: #fff;\n  background-color: #3c763d;\n  border-color: #3c763d;\n}\n.list-group-item-info {\n  color: #31708f;\n  background-color: #d9edf7;\n}\na.list-group-item-info {\n  color: #31708f;\n}\na.list-group-item-info .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-info:hover,\na.list-group-item-info:focus {\n  color: #31708f;\n  background-color: #c4e3f3;\n}\na.list-group-item-info.active,\na.list-group-item-info.active:hover,\na.list-group-item-info.active:focus {\n  color: #fff;\n  background-color: #31708f;\n  border-color: #31708f;\n}\n.list-group-item-warning {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n}\na.list-group-item-warning {\n  color: #8a6d3b;\n}\na.list-group-item-warning .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-warning:hover,\na.list-group-item-warning:focus {\n  color: #8a6d3b;\n  background-color: #faf2cc;\n}\na.list-group-item-warning.active,\na.list-group-item-warning.active:hover,\na.list-group-item-warning.active:focus {\n  color: #fff;\n  background-color: #8a6d3b;\n  border-color: #8a6d3b;\n}\n.list-group-item-danger {\n  color: #a94442;\n  background-color: #f2dede;\n}\na.list-group-item-danger {\n  color: #a94442;\n}\na.list-group-item-danger .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-danger:hover,\na.list-group-item-danger:focus {\n  color: #a94442;\n  background-color: #ebcccc;\n}\na.list-group-item-danger.active,\na.list-group-item-danger.active:hover,\na.list-group-item-danger.active:focus {\n  color: #fff;\n  background-color: #a94442;\n  border-color: #a94442;\n}\n.list-group-item-heading {\n  margin-top: 0;\n  margin-bottom: 5px;\n}\n.list-group-item-text {\n  margin-bottom: 0;\n  line-height: 1.3;\n}\n.panel {\n  margin-bottom: 20px;\n  background-color: #fff;\n  border: 1px solid transparent;\n  border-radius: 4px;\n  -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05);\n          box-shadow: 0 1px 1px rgba(0, 0, 0, .05);\n}\n.panel-body {\n  padding: 15px;\n}\n.panel-heading {\n  padding: 10px 15px;\n  border-bottom: 1px solid transparent;\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel-heading > .dropdown .dropdown-toggle {\n  color: inherit;\n}\n.panel-title {\n  margin-top: 0;\n  margin-bottom: 0;\n  font-size: 16px;\n  color: inherit;\n}\n.panel-title > a,\n.panel-title > small,\n.panel-title > .small,\n.panel-title > small > a,\n.panel-title > .small > a {\n  color: inherit;\n}\n.panel-footer {\n  padding: 10px 15px;\n  background-color: #f5f5f5;\n  border-top: 1px solid #ddd;\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel > .list-group,\n.panel > .panel-collapse > .list-group {\n  margin-bottom: 0;\n}\n.panel > .list-group .list-group-item,\n.panel > .panel-collapse > .list-group .list-group-item {\n  border-width: 1px 0;\n  border-radius: 0;\n}\n.panel > .list-group:first-child .list-group-item:first-child,\n.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {\n  border-top: 0;\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel > .list-group:last-child .list-group-item:last-child,\n.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {\n  border-bottom: 0;\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel-heading + .list-group .list-group-item:first-child {\n  border-top-width: 0;\n}\n.list-group + .panel-footer {\n  border-top-width: 0;\n}\n.panel > .table,\n.panel > .table-responsive > .table,\n.panel > .panel-collapse > .table {\n  margin-bottom: 0;\n}\n.panel > .table caption,\n.panel > .table-responsive > .table caption,\n.panel > .panel-collapse > .table caption {\n  padding-right: 15px;\n  padding-left: 15px;\n}\n.panel > .table:first-child,\n.panel > .table-responsive:first-child > .table:first-child {\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {\n  border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {\n  border-top-right-radius: 3px;\n}\n.panel > .table:last-child,\n.panel > .table-responsive:last-child > .table:last-child {\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {\n  border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {\n  border-bottom-right-radius: 3px;\n}\n.panel > .panel-body + .table,\n.panel > .panel-body + .table-responsive,\n.panel > .table + .panel-body,\n.panel > .table-responsive + .panel-body {\n  border-top: 1px solid #ddd;\n}\n.panel > .table > tbody:first-child > tr:first-child th,\n.panel > .table > tbody:first-child > tr:first-child td {\n  border-top: 0;\n}\n.panel > .table-bordered,\n.panel > .table-responsive > .table-bordered {\n  border: 0;\n}\n.panel > .table-bordered > thead > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,\n.panel > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-bordered > thead > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,\n.panel > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-bordered > tfoot > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n  border-left: 0;\n}\n.panel > .table-bordered > thead > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,\n.panel > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-bordered > thead > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,\n.panel > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-bordered > tfoot > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n  border-right: 0;\n}\n.panel > .table-bordered > thead > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,\n.panel > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-bordered > thead > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,\n.panel > .table-bordered > tbody > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {\n  border-bottom: 0;\n}\n.panel > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-bordered > tfoot > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {\n  border-bottom: 0;\n}\n.panel > .table-responsive {\n  margin-bottom: 0;\n  border: 0;\n}\n.panel-group {\n  margin-bottom: 20px;\n}\n.panel-group .panel {\n  margin-bottom: 0;\n  border-radius: 4px;\n}\n.panel-group .panel + .panel {\n  margin-top: 5px;\n}\n.panel-group .panel-heading {\n  border-bottom: 0;\n}\n.panel-group .panel-heading + .panel-collapse > .panel-body,\n.panel-group .panel-heading + .panel-collapse > .list-group {\n  border-top: 1px solid #ddd;\n}\n.panel-group .panel-footer {\n  border-top: 0;\n}\n.panel-group .panel-footer + .panel-collapse .panel-body {\n  border-bottom: 1px solid #ddd;\n}\n.panel-default {\n  border-color: #ddd;\n}\n.panel-default > .panel-heading {\n  color: #333;\n  background-color: #f5f5f5;\n  border-color: #ddd;\n}\n.panel-default > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #ddd;\n}\n.panel-default > .panel-heading .badge {\n  color: #f5f5f5;\n  background-color: #333;\n}\n.panel-default > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #ddd;\n}\n.panel-primary {\n  border-color: #337ab7;\n}\n.panel-primary > .panel-heading {\n  color: #fff;\n  background-color: #337ab7;\n  border-color: #337ab7;\n}\n.panel-primary > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #337ab7;\n}\n.panel-primary > .panel-heading .badge {\n  color: #337ab7;\n  background-color: #fff;\n}\n.panel-primary > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #337ab7;\n}\n.panel-success {\n  border-color: #d6e9c6;\n}\n.panel-success > .panel-heading {\n  color: #3c763d;\n  background-color: #dff0d8;\n  border-color: #d6e9c6;\n}\n.panel-success > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #d6e9c6;\n}\n.panel-success > .panel-heading .badge {\n  color: #dff0d8;\n  background-color: #3c763d;\n}\n.panel-success > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #d6e9c6;\n}\n.panel-info {\n  border-color: #bce8f1;\n}\n.panel-info > .panel-heading {\n  color: #31708f;\n  background-color: #d9edf7;\n  border-color: #bce8f1;\n}\n.panel-info > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #bce8f1;\n}\n.panel-info > .panel-heading .badge {\n  color: #d9edf7;\n  background-color: #31708f;\n}\n.panel-info > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #bce8f1;\n}\n.panel-warning {\n  border-color: #faebcc;\n}\n.panel-warning > .panel-heading {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n  border-color: #faebcc;\n}\n.panel-warning > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #faebcc;\n}\n.panel-warning > .panel-heading .badge {\n  color: #fcf8e3;\n  background-color: #8a6d3b;\n}\n.panel-warning > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #faebcc;\n}\n.panel-danger {\n  border-color: #ebccd1;\n}\n.panel-danger > .panel-heading {\n  color: #a94442;\n  background-color: #f2dede;\n  border-color: #ebccd1;\n}\n.panel-danger > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #ebccd1;\n}\n.panel-danger > .panel-heading .badge {\n  color: #f2dede;\n  background-color: #a94442;\n}\n.panel-danger > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #ebccd1;\n}\n.embed-responsive {\n  position: relative;\n  display: block;\n  height: 0;\n  padding: 0;\n  overflow: hidden;\n}\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  border: 0;\n}\n.embed-responsive.embed-responsive-16by9 {\n  padding-bottom: 56.25%;\n}\n.embed-responsive.embed-responsive-4by3 {\n  padding-bottom: 75%;\n}\n.well {\n  min-height: 20px;\n  padding: 19px;\n  margin-bottom: 20px;\n  background-color: #f5f5f5;\n  border: 1px solid #e3e3e3;\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);\n}\n.well blockquote {\n  border-color: #ddd;\n  border-color: rgba(0, 0, 0, .15);\n}\n.well-lg {\n  padding: 24px;\n  border-radius: 6px;\n}\n.well-sm {\n  padding: 9px;\n  border-radius: 3px;\n}\n.close {\n  float: right;\n  font-size: 21px;\n  font-weight: bold;\n  line-height: 1;\n  color: #000;\n  text-shadow: 0 1px 0 #fff;\n  filter: alpha(opacity=20);\n  opacity: .2;\n}\n.close:hover,\n.close:focus {\n  color: #000;\n  text-decoration: none;\n  cursor: pointer;\n  filter: alpha(opacity=50);\n  opacity: .5;\n}\nbutton.close {\n  -webkit-appearance: none;\n  padding: 0;\n  cursor: pointer;\n  background: transparent;\n  border: 0;\n}\n.modal-open {\n  overflow: hidden;\n}\n.modal {\n  position: fixed;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 1040;\n  display: none;\n  overflow: hidden;\n  -webkit-overflow-scrolling: touch;\n  outline: 0;\n}\n.modal.fade .modal-dialog {\n  -webkit-transition: -webkit-transform .3s ease-out;\n       -o-transition:      -o-transform .3s ease-out;\n          transition:         transform .3s ease-out;\n  -webkit-transform: translate(0, -25%);\n      -ms-transform: translate(0, -25%);\n       -o-transform: translate(0, -25%);\n          transform: translate(0, -25%);\n}\n.modal.in .modal-dialog {\n  -webkit-transform: translate(0, 0);\n      -ms-transform: translate(0, 0);\n       -o-transform: translate(0, 0);\n          transform: translate(0, 0);\n}\n.modal-open .modal {\n  overflow-x: hidden;\n  overflow-y: auto;\n}\n.modal-dialog {\n  position: relative;\n  width: auto;\n  margin: 10px;\n}\n.modal-content {\n  position: relative;\n  background-color: #fff;\n  -webkit-background-clip: padding-box;\n          background-clip: padding-box;\n  border: 1px solid #999;\n  border: 1px solid rgba(0, 0, 0, .2);\n  border-radius: 6px;\n  outline: 0;\n  -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5);\n          box-shadow: 0 3px 9px rgba(0, 0, 0, .5);\n}\n.modal-backdrop {\n  position: absolute;\n  top: 0;\n  right: 0;\n  left: 0;\n  background-color: #000;\n}\n.modal-backdrop.fade {\n  filter: alpha(opacity=0);\n  opacity: 0;\n}\n.modal-backdrop.in {\n  filter: alpha(opacity=50);\n  opacity: .5;\n}\n.modal-header {\n  min-height: 16.42857143px;\n  padding: 15px;\n  border-bottom: 1px solid #e5e5e5;\n}\n.modal-header .close {\n  margin-top: -2px;\n}\n.modal-title {\n  margin: 0;\n  line-height: 1.42857143;\n}\n.modal-body {\n  position: relative;\n  padding: 15px;\n}\n.modal-footer {\n  padding: 15px;\n  text-align: right;\n  border-top: 1px solid #e5e5e5;\n}\n.modal-footer .btn + .btn {\n  margin-bottom: 0;\n  margin-left: 5px;\n}\n.modal-footer .btn-group .btn + .btn {\n  margin-left: -1px;\n}\n.modal-footer .btn-block + .btn-block {\n  margin-left: 0;\n}\n.modal-scrollbar-measure {\n  position: absolute;\n  top: -9999px;\n  width: 50px;\n  height: 50px;\n  overflow: scroll;\n}\n@media (min-width: 768px) {\n  .modal-dialog {\n    width: 600px;\n    margin: 30px auto;\n  }\n  .modal-content {\n    -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5);\n            box-shadow: 0 5px 15px rgba(0, 0, 0, .5);\n  }\n  .modal-sm {\n    width: 300px;\n  }\n}\n@media (min-width: 992px) {\n  .modal-lg {\n    width: 900px;\n  }\n}\n.tooltip {\n  position: absolute;\n  z-index: 1070;\n  display: block;\n  font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-size: 12px;\n  font-weight: normal;\n  line-height: 1.4;\n  visibility: visible;\n  filter: alpha(opacity=0);\n  opacity: 0;\n}\n.tooltip.in {\n  filter: alpha(opacity=90);\n  opacity: .9;\n}\n.tooltip.top {\n  padding: 5px 0;\n  margin-top: -3px;\n}\n.tooltip.right {\n  padding: 0 5px;\n  margin-left: 3px;\n}\n.tooltip.bottom {\n  padding: 5px 0;\n  margin-top: 3px;\n}\n.tooltip.left {\n  padding: 0 5px;\n  margin-left: -3px;\n}\n.tooltip-inner {\n  max-width: 200px;\n  padding: 3px 8px;\n  color: #fff;\n  text-align: center;\n  text-decoration: none;\n  background-color: #000;\n  border-radius: 4px;\n}\n.tooltip-arrow {\n  position: absolute;\n  width: 0;\n  height: 0;\n  border-color: transparent;\n  border-style: solid;\n}\n.tooltip.top .tooltip-arrow {\n  bottom: 0;\n  left: 50%;\n  margin-left: -5px;\n  border-width: 5px 5px 0;\n  border-top-color: #000;\n}\n.tooltip.top-left .tooltip-arrow {\n  right: 5px;\n  bottom: 0;\n  margin-bottom: -5px;\n  border-width: 5px 5px 0;\n  border-top-color: #000;\n}\n.tooltip.top-right .tooltip-arrow {\n  bottom: 0;\n  left: 5px;\n  margin-bottom: -5px;\n  border-width: 5px 5px 0;\n  border-top-color: #000;\n}\n.tooltip.right .tooltip-arrow {\n  top: 50%;\n  left: 0;\n  margin-top: -5px;\n  border-width: 5px 5px 5px 0;\n  border-right-color: #000;\n}\n.tooltip.left .tooltip-arrow {\n  top: 50%;\n  right: 0;\n  margin-top: -5px;\n  border-width: 5px 0 5px 5px;\n  border-left-color: #000;\n}\n.tooltip.bottom .tooltip-arrow {\n  top: 0;\n  left: 50%;\n  margin-left: -5px;\n  border-width: 0 5px 5px;\n  border-bottom-color: #000;\n}\n.tooltip.bottom-left .tooltip-arrow {\n  top: 0;\n  right: 5px;\n  margin-top: -5px;\n  border-width: 0 5px 5px;\n  border-bottom-color: #000;\n}\n.tooltip.bottom-right .tooltip-arrow {\n  top: 0;\n  left: 5px;\n  margin-top: -5px;\n  border-width: 0 5px 5px;\n  border-bottom-color: #000;\n}\n.popover {\n  position: absolute;\n  top: 0;\n  left: 0;\n  z-index: 1060;\n  display: none;\n  max-width: 276px;\n  padding: 1px;\n  font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-size: 14px;\n  font-weight: normal;\n  line-height: 1.42857143;\n  text-align: left;\n  white-space: normal;\n  background-color: #fff;\n  -webkit-background-clip: padding-box;\n          background-clip: padding-box;\n  border: 1px solid #ccc;\n  border: 1px solid rgba(0, 0, 0, .2);\n  border-radius: 6px;\n  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);\n          box-shadow: 0 5px 10px rgba(0, 0, 0, .2);\n}\n.popover.top {\n  margin-top: -10px;\n}\n.popover.right {\n  margin-left: 10px;\n}\n.popover.bottom {\n  margin-top: 10px;\n}\n.popover.left {\n  margin-left: -10px;\n}\n.popover-title {\n  padding: 8px 14px;\n  margin: 0;\n  font-size: 14px;\n  background-color: #f7f7f7;\n  border-bottom: 1px solid #ebebeb;\n  border-radius: 5px 5px 0 0;\n}\n.popover-content {\n  padding: 9px 14px;\n}\n.popover > .arrow,\n.popover > .arrow:after {\n  position: absolute;\n  display: block;\n  width: 0;\n  height: 0;\n  border-color: transparent;\n  border-style: solid;\n}\n.popover > .arrow {\n  border-width: 11px;\n}\n.popover > .arrow:after {\n  content: \"\";\n  border-width: 10px;\n}\n.popover.top > .arrow {\n  bottom: -11px;\n  left: 50%;\n  margin-left: -11px;\n  border-top-color: #999;\n  border-top-color: rgba(0, 0, 0, .25);\n  border-bottom-width: 0;\n}\n.popover.top > .arrow:after {\n  bottom: 1px;\n  margin-left: -10px;\n  content: \" \";\n  border-top-color: #fff;\n  border-bottom-width: 0;\n}\n.popover.right > .arrow {\n  top: 50%;\n  left: -11px;\n  margin-top: -11px;\n  border-right-color: #999;\n  border-right-color: rgba(0, 0, 0, .25);\n  border-left-width: 0;\n}\n.popover.right > .arrow:after {\n  bottom: -10px;\n  left: 1px;\n  content: \" \";\n  border-right-color: #fff;\n  border-left-width: 0;\n}\n.popover.bottom > .arrow {\n  top: -11px;\n  left: 50%;\n  margin-left: -11px;\n  border-top-width: 0;\n  border-bottom-color: #999;\n  border-bottom-color: rgba(0, 0, 0, .25);\n}\n.popover.bottom > .arrow:after {\n  top: 1px;\n  margin-left: -10px;\n  content: \" \";\n  border-top-width: 0;\n  border-bottom-color: #fff;\n}\n.popover.left > .arrow {\n  top: 50%;\n  right: -11px;\n  margin-top: -11px;\n  border-right-width: 0;\n  border-left-color: #999;\n  border-left-color: rgba(0, 0, 0, .25);\n}\n.popover.left > .arrow:after {\n  right: 1px;\n  bottom: -10px;\n  content: \" \";\n  border-right-width: 0;\n  border-left-color: #fff;\n}\n.carousel {\n  position: relative;\n}\n.carousel-inner {\n  position: relative;\n  width: 100%;\n  overflow: hidden;\n}\n.carousel-inner > .item {\n  position: relative;\n  display: none;\n  -webkit-transition: .6s ease-in-out left;\n       -o-transition: .6s ease-in-out left;\n          transition: .6s ease-in-out left;\n}\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n  line-height: 1;\n}\n@media all and (transform-3d), (-webkit-transform-3d) {\n  .carousel-inner > .item {\n    -webkit-transition: -webkit-transform .6s ease-in-out;\n         -o-transition:      -o-transform .6s ease-in-out;\n            transition:         transform .6s ease-in-out;\n\n    -webkit-backface-visibility: hidden;\n            backface-visibility: hidden;\n    -webkit-perspective: 1000;\n            perspective: 1000;\n  }\n  .carousel-inner > .item.next,\n  .carousel-inner > .item.active.right {\n    left: 0;\n    -webkit-transform: translate3d(100%, 0, 0);\n            transform: translate3d(100%, 0, 0);\n  }\n  .carousel-inner > .item.prev,\n  .carousel-inner > .item.active.left {\n    left: 0;\n    -webkit-transform: translate3d(-100%, 0, 0);\n            transform: translate3d(-100%, 0, 0);\n  }\n  .carousel-inner > .item.next.left,\n  .carousel-inner > .item.prev.right,\n  .carousel-inner > .item.active {\n    left: 0;\n    -webkit-transform: translate3d(0, 0, 0);\n            transform: translate3d(0, 0, 0);\n  }\n}\n.carousel-inner > .active,\n.carousel-inner > .next,\n.carousel-inner > .prev {\n  display: block;\n}\n.carousel-inner > .active {\n  left: 0;\n}\n.carousel-inner > .next,\n.carousel-inner > .prev {\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.carousel-inner > .next {\n  left: 100%;\n}\n.carousel-inner > .prev {\n  left: -100%;\n}\n.carousel-inner > .next.left,\n.carousel-inner > .prev.right {\n  left: 0;\n}\n.carousel-inner > .active.left {\n  left: -100%;\n}\n.carousel-inner > .active.right {\n  left: 100%;\n}\n.carousel-control {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  width: 15%;\n  font-size: 20px;\n  color: #fff;\n  text-align: center;\n  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);\n  filter: alpha(opacity=50);\n  opacity: .5;\n}\n.carousel-control.left {\n  background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);\n  background-image:      -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);\n  background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001)));\n  background-image:         linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\n  background-repeat: repeat-x;\n}\n.carousel-control.right {\n  right: 0;\n  left: auto;\n  background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);\n  background-image:      -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);\n  background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5)));\n  background-image:         linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\n  background-repeat: repeat-x;\n}\n.carousel-control:hover,\n.carousel-control:focus {\n  color: #fff;\n  text-decoration: none;\n  filter: alpha(opacity=90);\n  outline: 0;\n  opacity: .9;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-left,\n.carousel-control .glyphicon-chevron-right {\n  position: absolute;\n  top: 50%;\n  z-index: 5;\n  display: inline-block;\n}\n.carousel-control .icon-prev,\n.carousel-control .glyphicon-chevron-left {\n  left: 50%;\n  margin-left: -10px;\n}\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-right {\n  right: 50%;\n  margin-right: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next {\n  width: 20px;\n  height: 20px;\n  margin-top: -10px;\n  font-family: serif;\n  line-height: 1;\n}\n.carousel-control .icon-prev:before {\n  content: '\\2039';\n}\n.carousel-control .icon-next:before {\n  content: '\\203a';\n}\n.carousel-indicators {\n  position: absolute;\n  bottom: 10px;\n  left: 50%;\n  z-index: 15;\n  width: 60%;\n  padding-left: 0;\n  margin-left: -30%;\n  text-align: center;\n  list-style: none;\n}\n.carousel-indicators li {\n  display: inline-block;\n  width: 10px;\n  height: 10px;\n  margin: 1px;\n  text-indent: -999px;\n  cursor: pointer;\n  background-color: #000 \\9;\n  background-color: rgba(0, 0, 0, 0);\n  border: 1px solid #fff;\n  border-radius: 10px;\n}\n.carousel-indicators .active {\n  width: 12px;\n  height: 12px;\n  margin: 0;\n  background-color: #fff;\n}\n.carousel-caption {\n  position: absolute;\n  right: 15%;\n  bottom: 20px;\n  left: 15%;\n  z-index: 10;\n  padding-top: 20px;\n  padding-bottom: 20px;\n  color: #fff;\n  text-align: center;\n  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);\n}\n.carousel-caption .btn {\n  text-shadow: none;\n}\n@media screen and (min-width: 768px) {\n  .carousel-control .glyphicon-chevron-left,\n  .carousel-control .glyphicon-chevron-right,\n  .carousel-control .icon-prev,\n  .carousel-control .icon-next {\n    width: 30px;\n    height: 30px;\n    margin-top: -15px;\n    font-size: 30px;\n  }\n  .carousel-control .glyphicon-chevron-left,\n  .carousel-control .icon-prev {\n    margin-left: -15px;\n  }\n  .carousel-control .glyphicon-chevron-right,\n  .carousel-control .icon-next {\n    margin-right: -15px;\n  }\n  .carousel-caption {\n    right: 20%;\n    left: 20%;\n    padding-bottom: 30px;\n  }\n  .carousel-indicators {\n    bottom: 20px;\n  }\n}\n.clearfix:before,\n.clearfix:after,\n.dl-horizontal dd:before,\n.dl-horizontal dd:after,\n.container:before,\n.container:after,\n.container-fluid:before,\n.container-fluid:after,\n.row:before,\n.row:after,\n.form-horizontal .form-group:before,\n.form-horizontal .form-group:after,\n.btn-toolbar:before,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:before,\n.btn-group-vertical > .btn-group:after,\n.nav:before,\n.nav:after,\n.navbar:before,\n.navbar:after,\n.navbar-header:before,\n.navbar-header:after,\n.navbar-collapse:before,\n.navbar-collapse:after,\n.pager:before,\n.pager:after,\n.panel-body:before,\n.panel-body:after,\n.modal-footer:before,\n.modal-footer:after {\n  display: table;\n  content: \" \";\n}\n.clearfix:after,\n.dl-horizontal dd:after,\n.container:after,\n.container-fluid:after,\n.row:after,\n.form-horizontal .form-group:after,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:after,\n.nav:after,\n.navbar:after,\n.navbar-header:after,\n.navbar-collapse:after,\n.pager:after,\n.panel-body:after,\n.modal-footer:after {\n  clear: both;\n}\n.center-block {\n  display: block;\n  margin-right: auto;\n  margin-left: auto;\n}\n.pull-right {\n  float: right !important;\n}\n.pull-left {\n  float: left !important;\n}\n.hide {\n  display: none !important;\n}\n.show {\n  display: block !important;\n}\n.invisible {\n  visibility: hidden;\n}\n.text-hide {\n  font: 0/0 a;\n  color: transparent;\n  text-shadow: none;\n  background-color: transparent;\n  border: 0;\n}\n.hidden {\n  display: none !important;\n  visibility: hidden !important;\n}\n.affix {\n  position: fixed;\n}\n@-ms-viewport {\n  width: device-width;\n}\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n  display: none !important;\n}\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n  display: none !important;\n}\n@media (max-width: 767px) {\n  .visible-xs {\n    display: block !important;\n  }\n  table.visible-xs {\n    display: table;\n  }\n  tr.visible-xs {\n    display: table-row !important;\n  }\n  th.visible-xs,\n  td.visible-xs {\n    display: table-cell !important;\n  }\n}\n@media (max-width: 767px) {\n  .visible-xs-block {\n    display: block !important;\n  }\n}\n@media (max-width: 767px) {\n  .visible-xs-inline {\n    display: inline !important;\n  }\n}\n@media (max-width: 767px) {\n  .visible-xs-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm {\n    display: block !important;\n  }\n  table.visible-sm {\n    display: table;\n  }\n  tr.visible-sm {\n    display: table-row !important;\n  }\n  th.visible-sm,\n  td.visible-sm {\n    display: table-cell !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm-block {\n    display: block !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm-inline {\n    display: inline !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md {\n    display: block !important;\n  }\n  table.visible-md {\n    display: table;\n  }\n  tr.visible-md {\n    display: table-row !important;\n  }\n  th.visible-md,\n  td.visible-md {\n    display: table-cell !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md-block {\n    display: block !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md-inline {\n    display: inline !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg {\n    display: block !important;\n  }\n  table.visible-lg {\n    display: table;\n  }\n  tr.visible-lg {\n    display: table-row !important;\n  }\n  th.visible-lg,\n  td.visible-lg {\n    display: table-cell !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg-block {\n    display: block !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg-inline {\n    display: inline !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (max-width: 767px) {\n  .hidden-xs {\n    display: none !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .hidden-sm {\n    display: none !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .hidden-md {\n    display: none !important;\n  }\n}\n@media (min-width: 1200px) {\n  .hidden-lg {\n    display: none !important;\n  }\n}\n.visible-print {\n  display: none !important;\n}\n@media print {\n  .visible-print {\n    display: block !important;\n  }\n  table.visible-print {\n    display: table;\n  }\n  tr.visible-print {\n    display: table-row !important;\n  }\n  th.visible-print,\n  td.visible-print {\n    display: table-cell !important;\n  }\n}\n.visible-print-block {\n  display: none !important;\n}\n@media print {\n  .visible-print-block {\n    display: block !important;\n  }\n}\n.visible-print-inline {\n  display: none !important;\n}\n@media print {\n  .visible-print-inline {\n    display: inline !important;\n  }\n}\n.visible-print-inline-block {\n  display: none !important;\n}\n@media print {\n  .visible-print-inline-block {\n    display: inline-block !important;\n  }\n}\n@media print {\n  .hidden-print {\n    display: none !important;\n  }\n}\n/*# sourceMappingURL=bootstrap.css.map */\n"
  },
  {
    "path": "src/main/resources/css/dashboard.css",
    "content": "/*\n * Base structure\n */\n\n/* Move down content because we have a fixed navbar that is 50px tall */\nbody {\n  padding-top: 50px;\n}\n\n\n/*\n * Global add-ons\n */\n\n.sub-header {\n  padding-bottom: 10px;\n  border-bottom: 1px solid #eee;\n}\n\n/*\n * Top navigation\n * Hide default border to remove 1px line.\n */\n.navbar-fixed-top {\n  border: 0;\n}\n\n/*\n * Sidebar\n */\n\n/* Hide for mobile, show later */\n.sidebar {\n  display: none;\n}\n@media (min-width: 768px) {\n  .sidebar {\n    position: fixed;\n    top: 51px;\n    bottom: 0;\n    left: 0;\n    z-index: 1000;\n    display: block;\n    padding: 20px;\n    overflow-x: hidden;\n    overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */\n    background-color: #f5f5f5;\n    border-right: 1px solid #eee;\n  }\n}\n\n/* Sidebar navigation */\n.nav-sidebar {\n  margin-right: -21px; /* 20px padding + 1px border */\n  margin-bottom: 20px;\n  margin-left: -20px;\n}\n.nav-sidebar > li > a {\n  padding-right: 20px;\n  padding-left: 20px;\n}\n.nav-sidebar > .active > a,\n.nav-sidebar > .active > a:hover,\n.nav-sidebar > .active > a:focus {\n  color: #fff;\n  background-color: #428bca;\n}\n\n\n/*\n * Main content\n */\n\n.main {\n  padding: 20px;\n}\n@media (min-width: 768px) {\n  .main {\n    padding-right: 40px;\n    padding-left: 40px;\n  }\n}\n.main .page-header {\n  margin-top: 0;\n}\n\n\n/*\n * Placeholder dashboard ideas\n */\n\n.placeholders {\n  margin-bottom: 30px;\n  text-align: center;\n}\n.placeholders h4 {\n  margin-bottom: 0;\n}\n.placeholder {\n  margin-bottom: 20px;\n}\n.placeholder img {\n  display: inline-block;\n  border-radius: 50%;\n}\n"
  },
  {
    "path": "src/main/resources/js/bootstrap.js",
    "content": "/*!\n * Bootstrap v3.3.2 (http://getbootstrap.com)\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\nif (typeof jQuery === 'undefined') {\n  throw new Error('Bootstrap\\'s JavaScript requires jQuery')\n}\n\n+function ($) {\n  'use strict';\n  var version = $.fn.jquery.split(' ')[0].split('.')\n  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) {\n    throw new Error('Bootstrap\\'s JavaScript requires jQuery version 1.9.1 or higher')\n  }\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: transition.js v3.3.2\n * http://getbootstrap.com/javascript/#transitions\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)\n  // ============================================================\n\n  function transitionEnd() {\n    var el = document.createElement('bootstrap')\n\n    var transEndEventNames = {\n      WebkitTransition : 'webkitTransitionEnd',\n      MozTransition    : 'transitionend',\n      OTransition      : 'oTransitionEnd otransitionend',\n      transition       : 'transitionend'\n    }\n\n    for (var name in transEndEventNames) {\n      if (el.style[name] !== undefined) {\n        return { end: transEndEventNames[name] }\n      }\n    }\n\n    return false // explicit for ie8 (  ._.)\n  }\n\n  // http://blog.alexmaccaw.com/css-transitions\n  $.fn.emulateTransitionEnd = function (duration) {\n    var called = false\n    var $el = this\n    $(this).one('bsTransitionEnd', function () { called = true })\n    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }\n    setTimeout(callback, duration)\n    return this\n  }\n\n  $(function () {\n    $.support.transition = transitionEnd()\n\n    if (!$.support.transition) return\n\n    $.event.special.bsTransitionEnd = {\n      bindType: $.support.transition.end,\n      delegateType: $.support.transition.end,\n      handle: function (e) {\n        if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)\n      }\n    }\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: alert.js v3.3.2\n * http://getbootstrap.com/javascript/#alerts\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // ALERT CLASS DEFINITION\n  // ======================\n\n  var dismiss = '[data-dismiss=\"alert\"]'\n  var Alert   = function (el) {\n    $(el).on('click', dismiss, this.close)\n  }\n\n  Alert.VERSION = '3.3.2'\n\n  Alert.TRANSITION_DURATION = 150\n\n  Alert.prototype.close = function (e) {\n    var $this    = $(this)\n    var selector = $this.attr('data-target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    var $parent = $(selector)\n\n    if (e) e.preventDefault()\n\n    if (!$parent.length) {\n      $parent = $this.closest('.alert')\n    }\n\n    $parent.trigger(e = $.Event('close.bs.alert'))\n\n    if (e.isDefaultPrevented()) return\n\n    $parent.removeClass('in')\n\n    function removeElement() {\n      // detach from parent, fire event then clean up data\n      $parent.detach().trigger('closed.bs.alert').remove()\n    }\n\n    $.support.transition && $parent.hasClass('fade') ?\n      $parent\n        .one('bsTransitionEnd', removeElement)\n        .emulateTransitionEnd(Alert.TRANSITION_DURATION) :\n      removeElement()\n  }\n\n\n  // ALERT PLUGIN DEFINITION\n  // =======================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.alert')\n\n      if (!data) $this.data('bs.alert', (data = new Alert(this)))\n      if (typeof option == 'string') data[option].call($this)\n    })\n  }\n\n  var old = $.fn.alert\n\n  $.fn.alert             = Plugin\n  $.fn.alert.Constructor = Alert\n\n\n  // ALERT NO CONFLICT\n  // =================\n\n  $.fn.alert.noConflict = function () {\n    $.fn.alert = old\n    return this\n  }\n\n\n  // ALERT DATA-API\n  // ==============\n\n  $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: button.js v3.3.2\n * http://getbootstrap.com/javascript/#buttons\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // BUTTON PUBLIC CLASS DEFINITION\n  // ==============================\n\n  var Button = function (element, options) {\n    this.$element  = $(element)\n    this.options   = $.extend({}, Button.DEFAULTS, options)\n    this.isLoading = false\n  }\n\n  Button.VERSION  = '3.3.2'\n\n  Button.DEFAULTS = {\n    loadingText: 'loading...'\n  }\n\n  Button.prototype.setState = function (state) {\n    var d    = 'disabled'\n    var $el  = this.$element\n    var val  = $el.is('input') ? 'val' : 'html'\n    var data = $el.data()\n\n    state = state + 'Text'\n\n    if (data.resetText == null) $el.data('resetText', $el[val]())\n\n    // push to event loop to allow forms to submit\n    setTimeout($.proxy(function () {\n      $el[val](data[state] == null ? this.options[state] : data[state])\n\n      if (state == 'loadingText') {\n        this.isLoading = true\n        $el.addClass(d).attr(d, d)\n      } else if (this.isLoading) {\n        this.isLoading = false\n        $el.removeClass(d).removeAttr(d)\n      }\n    }, this), 0)\n  }\n\n  Button.prototype.toggle = function () {\n    var changed = true\n    var $parent = this.$element.closest('[data-toggle=\"buttons\"]')\n\n    if ($parent.length) {\n      var $input = this.$element.find('input')\n      if ($input.prop('type') == 'radio') {\n        if ($input.prop('checked') && this.$element.hasClass('active')) changed = false\n        else $parent.find('.active').removeClass('active')\n      }\n      if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change')\n    } else {\n      this.$element.attr('aria-pressed', !this.$element.hasClass('active'))\n    }\n\n    if (changed) this.$element.toggleClass('active')\n  }\n\n\n  // BUTTON PLUGIN DEFINITION\n  // ========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.button')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.button', (data = new Button(this, options)))\n\n      if (option == 'toggle') data.toggle()\n      else if (option) data.setState(option)\n    })\n  }\n\n  var old = $.fn.button\n\n  $.fn.button             = Plugin\n  $.fn.button.Constructor = Button\n\n\n  // BUTTON NO CONFLICT\n  // ==================\n\n  $.fn.button.noConflict = function () {\n    $.fn.button = old\n    return this\n  }\n\n\n  // BUTTON DATA-API\n  // ===============\n\n  $(document)\n    .on('click.bs.button.data-api', '[data-toggle^=\"button\"]', function (e) {\n      var $btn = $(e.target)\n      if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')\n      Plugin.call($btn, 'toggle')\n      e.preventDefault()\n    })\n    .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^=\"button\"]', function (e) {\n      $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))\n    })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: carousel.js v3.3.2\n * http://getbootstrap.com/javascript/#carousel\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // CAROUSEL CLASS DEFINITION\n  // =========================\n\n  var Carousel = function (element, options) {\n    this.$element    = $(element)\n    this.$indicators = this.$element.find('.carousel-indicators')\n    this.options     = options\n    this.paused      =\n    this.sliding     =\n    this.interval    =\n    this.$active     =\n    this.$items      = null\n\n    this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))\n\n    this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element\n      .on('mouseenter.bs.carousel', $.proxy(this.pause, this))\n      .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))\n  }\n\n  Carousel.VERSION  = '3.3.2'\n\n  Carousel.TRANSITION_DURATION = 600\n\n  Carousel.DEFAULTS = {\n    interval: 5000,\n    pause: 'hover',\n    wrap: true,\n    keyboard: true\n  }\n\n  Carousel.prototype.keydown = function (e) {\n    if (/input|textarea/i.test(e.target.tagName)) return\n    switch (e.which) {\n      case 37: this.prev(); break\n      case 39: this.next(); break\n      default: return\n    }\n\n    e.preventDefault()\n  }\n\n  Carousel.prototype.cycle = function (e) {\n    e || (this.paused = false)\n\n    this.interval && clearInterval(this.interval)\n\n    this.options.interval\n      && !this.paused\n      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))\n\n    return this\n  }\n\n  Carousel.prototype.getItemIndex = function (item) {\n    this.$items = item.parent().children('.item')\n    return this.$items.index(item || this.$active)\n  }\n\n  Carousel.prototype.getItemForDirection = function (direction, active) {\n    var activeIndex = this.getItemIndex(active)\n    var willWrap = (direction == 'prev' && activeIndex === 0)\n                || (direction == 'next' && activeIndex == (this.$items.length - 1))\n    if (willWrap && !this.options.wrap) return active\n    var delta = direction == 'prev' ? -1 : 1\n    var itemIndex = (activeIndex + delta) % this.$items.length\n    return this.$items.eq(itemIndex)\n  }\n\n  Carousel.prototype.to = function (pos) {\n    var that        = this\n    var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))\n\n    if (pos > (this.$items.length - 1) || pos < 0) return\n\n    if (this.sliding)       return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, \"slid\"\n    if (activeIndex == pos) return this.pause().cycle()\n\n    return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))\n  }\n\n  Carousel.prototype.pause = function (e) {\n    e || (this.paused = true)\n\n    if (this.$element.find('.next, .prev').length && $.support.transition) {\n      this.$element.trigger($.support.transition.end)\n      this.cycle(true)\n    }\n\n    this.interval = clearInterval(this.interval)\n\n    return this\n  }\n\n  Carousel.prototype.next = function () {\n    if (this.sliding) return\n    return this.slide('next')\n  }\n\n  Carousel.prototype.prev = function () {\n    if (this.sliding) return\n    return this.slide('prev')\n  }\n\n  Carousel.prototype.slide = function (type, next) {\n    var $active   = this.$element.find('.item.active')\n    var $next     = next || this.getItemForDirection(type, $active)\n    var isCycling = this.interval\n    var direction = type == 'next' ? 'left' : 'right'\n    var that      = this\n\n    if ($next.hasClass('active')) return (this.sliding = false)\n\n    var relatedTarget = $next[0]\n    var slideEvent = $.Event('slide.bs.carousel', {\n      relatedTarget: relatedTarget,\n      direction: direction\n    })\n    this.$element.trigger(slideEvent)\n    if (slideEvent.isDefaultPrevented()) return\n\n    this.sliding = true\n\n    isCycling && this.pause()\n\n    if (this.$indicators.length) {\n      this.$indicators.find('.active').removeClass('active')\n      var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])\n      $nextIndicator && $nextIndicator.addClass('active')\n    }\n\n    var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, \"slid\"\n    if ($.support.transition && this.$element.hasClass('slide')) {\n      $next.addClass(type)\n      $next[0].offsetWidth // force reflow\n      $active.addClass(direction)\n      $next.addClass(direction)\n      $active\n        .one('bsTransitionEnd', function () {\n          $next.removeClass([type, direction].join(' ')).addClass('active')\n          $active.removeClass(['active', direction].join(' '))\n          that.sliding = false\n          setTimeout(function () {\n            that.$element.trigger(slidEvent)\n          }, 0)\n        })\n        .emulateTransitionEnd(Carousel.TRANSITION_DURATION)\n    } else {\n      $active.removeClass('active')\n      $next.addClass('active')\n      this.sliding = false\n      this.$element.trigger(slidEvent)\n    }\n\n    isCycling && this.cycle()\n\n    return this\n  }\n\n\n  // CAROUSEL PLUGIN DEFINITION\n  // ==========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.carousel')\n      var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)\n      var action  = typeof option == 'string' ? option : options.slide\n\n      if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))\n      if (typeof option == 'number') data.to(option)\n      else if (action) data[action]()\n      else if (options.interval) data.pause().cycle()\n    })\n  }\n\n  var old = $.fn.carousel\n\n  $.fn.carousel             = Plugin\n  $.fn.carousel.Constructor = Carousel\n\n\n  // CAROUSEL NO CONFLICT\n  // ====================\n\n  $.fn.carousel.noConflict = function () {\n    $.fn.carousel = old\n    return this\n  }\n\n\n  // CAROUSEL DATA-API\n  // =================\n\n  var clickHandler = function (e) {\n    var href\n    var $this   = $(this)\n    var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\\s]+$)/, '')) // strip for ie7\n    if (!$target.hasClass('carousel')) return\n    var options = $.extend({}, $target.data(), $this.data())\n    var slideIndex = $this.attr('data-slide-to')\n    if (slideIndex) options.interval = false\n\n    Plugin.call($target, options)\n\n    if (slideIndex) {\n      $target.data('bs.carousel').to(slideIndex)\n    }\n\n    e.preventDefault()\n  }\n\n  $(document)\n    .on('click.bs.carousel.data-api', '[data-slide]', clickHandler)\n    .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)\n\n  $(window).on('load', function () {\n    $('[data-ride=\"carousel\"]').each(function () {\n      var $carousel = $(this)\n      Plugin.call($carousel, $carousel.data())\n    })\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: collapse.js v3.3.2\n * http://getbootstrap.com/javascript/#collapse\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // COLLAPSE PUBLIC CLASS DEFINITION\n  // ================================\n\n  var Collapse = function (element, options) {\n    this.$element      = $(element)\n    this.options       = $.extend({}, Collapse.DEFAULTS, options)\n    this.$trigger      = $(this.options.trigger).filter('[href=\"#' + element.id + '\"], [data-target=\"#' + element.id + '\"]')\n    this.transitioning = null\n\n    if (this.options.parent) {\n      this.$parent = this.getParent()\n    } else {\n      this.addAriaAndCollapsedClass(this.$element, this.$trigger)\n    }\n\n    if (this.options.toggle) this.toggle()\n  }\n\n  Collapse.VERSION  = '3.3.2'\n\n  Collapse.TRANSITION_DURATION = 350\n\n  Collapse.DEFAULTS = {\n    toggle: true,\n    trigger: '[data-toggle=\"collapse\"]'\n  }\n\n  Collapse.prototype.dimension = function () {\n    var hasWidth = this.$element.hasClass('width')\n    return hasWidth ? 'width' : 'height'\n  }\n\n  Collapse.prototype.show = function () {\n    if (this.transitioning || this.$element.hasClass('in')) return\n\n    var activesData\n    var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')\n\n    if (actives && actives.length) {\n      activesData = actives.data('bs.collapse')\n      if (activesData && activesData.transitioning) return\n    }\n\n    var startEvent = $.Event('show.bs.collapse')\n    this.$element.trigger(startEvent)\n    if (startEvent.isDefaultPrevented()) return\n\n    if (actives && actives.length) {\n      Plugin.call(actives, 'hide')\n      activesData || actives.data('bs.collapse', null)\n    }\n\n    var dimension = this.dimension()\n\n    this.$element\n      .removeClass('collapse')\n      .addClass('collapsing')[dimension](0)\n      .attr('aria-expanded', true)\n\n    this.$trigger\n      .removeClass('collapsed')\n      .attr('aria-expanded', true)\n\n    this.transitioning = 1\n\n    var complete = function () {\n      this.$element\n        .removeClass('collapsing')\n        .addClass('collapse in')[dimension]('')\n      this.transitioning = 0\n      this.$element\n        .trigger('shown.bs.collapse')\n    }\n\n    if (!$.support.transition) return complete.call(this)\n\n    var scrollSize = $.camelCase(['scroll', dimension].join('-'))\n\n    this.$element\n      .one('bsTransitionEnd', $.proxy(complete, this))\n      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])\n  }\n\n  Collapse.prototype.hide = function () {\n    if (this.transitioning || !this.$element.hasClass('in')) return\n\n    var startEvent = $.Event('hide.bs.collapse')\n    this.$element.trigger(startEvent)\n    if (startEvent.isDefaultPrevented()) return\n\n    var dimension = this.dimension()\n\n    this.$element[dimension](this.$element[dimension]())[0].offsetHeight\n\n    this.$element\n      .addClass('collapsing')\n      .removeClass('collapse in')\n      .attr('aria-expanded', false)\n\n    this.$trigger\n      .addClass('collapsed')\n      .attr('aria-expanded', false)\n\n    this.transitioning = 1\n\n    var complete = function () {\n      this.transitioning = 0\n      this.$element\n        .removeClass('collapsing')\n        .addClass('collapse')\n        .trigger('hidden.bs.collapse')\n    }\n\n    if (!$.support.transition) return complete.call(this)\n\n    this.$element\n      [dimension](0)\n      .one('bsTransitionEnd', $.proxy(complete, this))\n      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)\n  }\n\n  Collapse.prototype.toggle = function () {\n    this[this.$element.hasClass('in') ? 'hide' : 'show']()\n  }\n\n  Collapse.prototype.getParent = function () {\n    return $(this.options.parent)\n      .find('[data-toggle=\"collapse\"][data-parent=\"' + this.options.parent + '\"]')\n      .each($.proxy(function (i, element) {\n        var $element = $(element)\n        this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)\n      }, this))\n      .end()\n  }\n\n  Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {\n    var isOpen = $element.hasClass('in')\n\n    $element.attr('aria-expanded', isOpen)\n    $trigger\n      .toggleClass('collapsed', !isOpen)\n      .attr('aria-expanded', isOpen)\n  }\n\n  function getTargetFromTrigger($trigger) {\n    var href\n    var target = $trigger.attr('data-target')\n      || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\\s]+$)/, '') // strip for ie7\n\n    return $(target)\n  }\n\n\n  // COLLAPSE PLUGIN DEFINITION\n  // ==========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.collapse')\n      var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)\n\n      if (!data && options.toggle && option == 'show') options.toggle = false\n      if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.collapse\n\n  $.fn.collapse             = Plugin\n  $.fn.collapse.Constructor = Collapse\n\n\n  // COLLAPSE NO CONFLICT\n  // ====================\n\n  $.fn.collapse.noConflict = function () {\n    $.fn.collapse = old\n    return this\n  }\n\n\n  // COLLAPSE DATA-API\n  // =================\n\n  $(document).on('click.bs.collapse.data-api', '[data-toggle=\"collapse\"]', function (e) {\n    var $this   = $(this)\n\n    if (!$this.attr('data-target')) e.preventDefault()\n\n    var $target = getTargetFromTrigger($this)\n    var data    = $target.data('bs.collapse')\n    var option  = data ? 'toggle' : $.extend({}, $this.data(), { trigger: this })\n\n    Plugin.call($target, option)\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: dropdown.js v3.3.2\n * http://getbootstrap.com/javascript/#dropdowns\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // DROPDOWN CLASS DEFINITION\n  // =========================\n\n  var backdrop = '.dropdown-backdrop'\n  var toggle   = '[data-toggle=\"dropdown\"]'\n  var Dropdown = function (element) {\n    $(element).on('click.bs.dropdown', this.toggle)\n  }\n\n  Dropdown.VERSION = '3.3.2'\n\n  Dropdown.prototype.toggle = function (e) {\n    var $this = $(this)\n\n    if ($this.is('.disabled, :disabled')) return\n\n    var $parent  = getParent($this)\n    var isActive = $parent.hasClass('open')\n\n    clearMenus()\n\n    if (!isActive) {\n      if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {\n        // if mobile we use a backdrop because click events don't delegate\n        $('<div class=\"dropdown-backdrop\"/>').insertAfter($(this)).on('click', clearMenus)\n      }\n\n      var relatedTarget = { relatedTarget: this }\n      $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))\n\n      if (e.isDefaultPrevented()) return\n\n      $this\n        .trigger('focus')\n        .attr('aria-expanded', 'true')\n\n      $parent\n        .toggleClass('open')\n        .trigger('shown.bs.dropdown', relatedTarget)\n    }\n\n    return false\n  }\n\n  Dropdown.prototype.keydown = function (e) {\n    if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return\n\n    var $this = $(this)\n\n    e.preventDefault()\n    e.stopPropagation()\n\n    if ($this.is('.disabled, :disabled')) return\n\n    var $parent  = getParent($this)\n    var isActive = $parent.hasClass('open')\n\n    if ((!isActive && e.which != 27) || (isActive && e.which == 27)) {\n      if (e.which == 27) $parent.find(toggle).trigger('focus')\n      return $this.trigger('click')\n    }\n\n    var desc = ' li:not(.divider):visible a'\n    var $items = $parent.find('[role=\"menu\"]' + desc + ', [role=\"listbox\"]' + desc)\n\n    if (!$items.length) return\n\n    var index = $items.index(e.target)\n\n    if (e.which == 38 && index > 0)                 index--                        // up\n    if (e.which == 40 && index < $items.length - 1) index++                        // down\n    if (!~index)                                      index = 0\n\n    $items.eq(index).trigger('focus')\n  }\n\n  function clearMenus(e) {\n    if (e && e.which === 3) return\n    $(backdrop).remove()\n    $(toggle).each(function () {\n      var $this         = $(this)\n      var $parent       = getParent($this)\n      var relatedTarget = { relatedTarget: this }\n\n      if (!$parent.hasClass('open')) return\n\n      $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))\n\n      if (e.isDefaultPrevented()) return\n\n      $this.attr('aria-expanded', 'false')\n      $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)\n    })\n  }\n\n  function getParent($this) {\n    var selector = $this.attr('data-target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    var $parent = selector && $(selector)\n\n    return $parent && $parent.length ? $parent : $this.parent()\n  }\n\n\n  // DROPDOWN PLUGIN DEFINITION\n  // ==========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.dropdown')\n\n      if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))\n      if (typeof option == 'string') data[option].call($this)\n    })\n  }\n\n  var old = $.fn.dropdown\n\n  $.fn.dropdown             = Plugin\n  $.fn.dropdown.Constructor = Dropdown\n\n\n  // DROPDOWN NO CONFLICT\n  // ====================\n\n  $.fn.dropdown.noConflict = function () {\n    $.fn.dropdown = old\n    return this\n  }\n\n\n  // APPLY TO STANDARD DROPDOWN ELEMENTS\n  // ===================================\n\n  $(document)\n    .on('click.bs.dropdown.data-api', clearMenus)\n    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })\n    .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)\n    .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)\n    .on('keydown.bs.dropdown.data-api', '[role=\"menu\"]', Dropdown.prototype.keydown)\n    .on('keydown.bs.dropdown.data-api', '[role=\"listbox\"]', Dropdown.prototype.keydown)\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: modal.js v3.3.2\n * http://getbootstrap.com/javascript/#modals\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // MODAL CLASS DEFINITION\n  // ======================\n\n  var Modal = function (element, options) {\n    this.options        = options\n    this.$body          = $(document.body)\n    this.$element       = $(element)\n    this.$backdrop      =\n    this.isShown        = null\n    this.scrollbarWidth = 0\n\n    if (this.options.remote) {\n      this.$element\n        .find('.modal-content')\n        .load(this.options.remote, $.proxy(function () {\n          this.$element.trigger('loaded.bs.modal')\n        }, this))\n    }\n  }\n\n  Modal.VERSION  = '3.3.2'\n\n  Modal.TRANSITION_DURATION = 300\n  Modal.BACKDROP_TRANSITION_DURATION = 150\n\n  Modal.DEFAULTS = {\n    backdrop: true,\n    keyboard: true,\n    show: true\n  }\n\n  Modal.prototype.toggle = function (_relatedTarget) {\n    return this.isShown ? this.hide() : this.show(_relatedTarget)\n  }\n\n  Modal.prototype.show = function (_relatedTarget) {\n    var that = this\n    var e    = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })\n\n    this.$element.trigger(e)\n\n    if (this.isShown || e.isDefaultPrevented()) return\n\n    this.isShown = true\n\n    this.checkScrollbar()\n    this.setScrollbar()\n    this.$body.addClass('modal-open')\n\n    this.escape()\n    this.resize()\n\n    this.$element.on('click.dismiss.bs.modal', '[data-dismiss=\"modal\"]', $.proxy(this.hide, this))\n\n    this.backdrop(function () {\n      var transition = $.support.transition && that.$element.hasClass('fade')\n\n      if (!that.$element.parent().length) {\n        that.$element.appendTo(that.$body) // don't move modals dom position\n      }\n\n      that.$element\n        .show()\n        .scrollTop(0)\n\n      if (that.options.backdrop) that.adjustBackdrop()\n      that.adjustDialog()\n\n      if (transition) {\n        that.$element[0].offsetWidth // force reflow\n      }\n\n      that.$element\n        .addClass('in')\n        .attr('aria-hidden', false)\n\n      that.enforceFocus()\n\n      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })\n\n      transition ?\n        that.$element.find('.modal-dialog') // wait for modal to slide in\n          .one('bsTransitionEnd', function () {\n            that.$element.trigger('focus').trigger(e)\n          })\n          .emulateTransitionEnd(Modal.TRANSITION_DURATION) :\n        that.$element.trigger('focus').trigger(e)\n    })\n  }\n\n  Modal.prototype.hide = function (e) {\n    if (e) e.preventDefault()\n\n    e = $.Event('hide.bs.modal')\n\n    this.$element.trigger(e)\n\n    if (!this.isShown || e.isDefaultPrevented()) return\n\n    this.isShown = false\n\n    this.escape()\n    this.resize()\n\n    $(document).off('focusin.bs.modal')\n\n    this.$element\n      .removeClass('in')\n      .attr('aria-hidden', true)\n      .off('click.dismiss.bs.modal')\n\n    $.support.transition && this.$element.hasClass('fade') ?\n      this.$element\n        .one('bsTransitionEnd', $.proxy(this.hideModal, this))\n        .emulateTransitionEnd(Modal.TRANSITION_DURATION) :\n      this.hideModal()\n  }\n\n  Modal.prototype.enforceFocus = function () {\n    $(document)\n      .off('focusin.bs.modal') // guard against infinite focus loop\n      .on('focusin.bs.modal', $.proxy(function (e) {\n        if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {\n          this.$element.trigger('focus')\n        }\n      }, this))\n  }\n\n  Modal.prototype.escape = function () {\n    if (this.isShown && this.options.keyboard) {\n      this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {\n        e.which == 27 && this.hide()\n      }, this))\n    } else if (!this.isShown) {\n      this.$element.off('keydown.dismiss.bs.modal')\n    }\n  }\n\n  Modal.prototype.resize = function () {\n    if (this.isShown) {\n      $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))\n    } else {\n      $(window).off('resize.bs.modal')\n    }\n  }\n\n  Modal.prototype.hideModal = function () {\n    var that = this\n    this.$element.hide()\n    this.backdrop(function () {\n      that.$body.removeClass('modal-open')\n      that.resetAdjustments()\n      that.resetScrollbar()\n      that.$element.trigger('hidden.bs.modal')\n    })\n  }\n\n  Modal.prototype.removeBackdrop = function () {\n    this.$backdrop && this.$backdrop.remove()\n    this.$backdrop = null\n  }\n\n  Modal.prototype.backdrop = function (callback) {\n    var that = this\n    var animate = this.$element.hasClass('fade') ? 'fade' : ''\n\n    if (this.isShown && this.options.backdrop) {\n      var doAnimate = $.support.transition && animate\n\n      this.$backdrop = $('<div class=\"modal-backdrop ' + animate + '\" />')\n        .prependTo(this.$element)\n        .on('click.dismiss.bs.modal', $.proxy(function (e) {\n          if (e.target !== e.currentTarget) return\n          this.options.backdrop == 'static'\n            ? this.$element[0].focus.call(this.$element[0])\n            : this.hide.call(this)\n        }, this))\n\n      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow\n\n      this.$backdrop.addClass('in')\n\n      if (!callback) return\n\n      doAnimate ?\n        this.$backdrop\n          .one('bsTransitionEnd', callback)\n          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :\n        callback()\n\n    } else if (!this.isShown && this.$backdrop) {\n      this.$backdrop.removeClass('in')\n\n      var callbackRemove = function () {\n        that.removeBackdrop()\n        callback && callback()\n      }\n      $.support.transition && this.$element.hasClass('fade') ?\n        this.$backdrop\n          .one('bsTransitionEnd', callbackRemove)\n          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :\n        callbackRemove()\n\n    } else if (callback) {\n      callback()\n    }\n  }\n\n  // these following methods are used to handle overflowing modals\n\n  Modal.prototype.handleUpdate = function () {\n    if (this.options.backdrop) this.adjustBackdrop()\n    this.adjustDialog()\n  }\n\n  Modal.prototype.adjustBackdrop = function () {\n    this.$backdrop\n      .css('height', 0)\n      .css('height', this.$element[0].scrollHeight)\n  }\n\n  Modal.prototype.adjustDialog = function () {\n    var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight\n\n    this.$element.css({\n      paddingLeft:  !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',\n      paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''\n    })\n  }\n\n  Modal.prototype.resetAdjustments = function () {\n    this.$element.css({\n      paddingLeft: '',\n      paddingRight: ''\n    })\n  }\n\n  Modal.prototype.checkScrollbar = function () {\n    this.bodyIsOverflowing = document.body.scrollHeight > document.documentElement.clientHeight\n    this.scrollbarWidth = this.measureScrollbar()\n  }\n\n  Modal.prototype.setScrollbar = function () {\n    var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)\n    if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)\n  }\n\n  Modal.prototype.resetScrollbar = function () {\n    this.$body.css('padding-right', '')\n  }\n\n  Modal.prototype.measureScrollbar = function () { // thx walsh\n    var scrollDiv = document.createElement('div')\n    scrollDiv.className = 'modal-scrollbar-measure'\n    this.$body.append(scrollDiv)\n    var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth\n    this.$body[0].removeChild(scrollDiv)\n    return scrollbarWidth\n  }\n\n\n  // MODAL PLUGIN DEFINITION\n  // =======================\n\n  function Plugin(option, _relatedTarget) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.modal')\n      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)\n\n      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))\n      if (typeof option == 'string') data[option](_relatedTarget)\n      else if (options.show) data.show(_relatedTarget)\n    })\n  }\n\n  var old = $.fn.modal\n\n  $.fn.modal             = Plugin\n  $.fn.modal.Constructor = Modal\n\n\n  // MODAL NO CONFLICT\n  // =================\n\n  $.fn.modal.noConflict = function () {\n    $.fn.modal = old\n    return this\n  }\n\n\n  // MODAL DATA-API\n  // ==============\n\n  $(document).on('click.bs.modal.data-api', '[data-toggle=\"modal\"]', function (e) {\n    var $this   = $(this)\n    var href    = $this.attr('href')\n    var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\\s]+$)/, ''))) // strip for ie7\n    var option  = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())\n\n    if ($this.is('a')) e.preventDefault()\n\n    $target.one('show.bs.modal', function (showEvent) {\n      if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown\n      $target.one('hidden.bs.modal', function () {\n        $this.is(':visible') && $this.trigger('focus')\n      })\n    })\n    Plugin.call($target, option, this)\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: tooltip.js v3.3.2\n * http://getbootstrap.com/javascript/#tooltip\n * Inspired by the original jQuery.tipsy by Jason Frame\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // TOOLTIP PUBLIC CLASS DEFINITION\n  // ===============================\n\n  var Tooltip = function (element, options) {\n    this.type       =\n    this.options    =\n    this.enabled    =\n    this.timeout    =\n    this.hoverState =\n    this.$element   = null\n\n    this.init('tooltip', element, options)\n  }\n\n  Tooltip.VERSION  = '3.3.2'\n\n  Tooltip.TRANSITION_DURATION = 150\n\n  Tooltip.DEFAULTS = {\n    animation: true,\n    placement: 'top',\n    selector: false,\n    template: '<div class=\"tooltip\" role=\"tooltip\"><div class=\"tooltip-arrow\"></div><div class=\"tooltip-inner\"></div></div>',\n    trigger: 'hover focus',\n    title: '',\n    delay: 0,\n    html: false,\n    container: false,\n    viewport: {\n      selector: 'body',\n      padding: 0\n    }\n  }\n\n  Tooltip.prototype.init = function (type, element, options) {\n    this.enabled   = true\n    this.type      = type\n    this.$element  = $(element)\n    this.options   = this.getOptions(options)\n    this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)\n\n    var triggers = this.options.trigger.split(' ')\n\n    for (var i = triggers.length; i--;) {\n      var trigger = triggers[i]\n\n      if (trigger == 'click') {\n        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))\n      } else if (trigger != 'manual') {\n        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'\n        var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'\n\n        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))\n        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))\n      }\n    }\n\n    this.options.selector ?\n      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :\n      this.fixTitle()\n  }\n\n  Tooltip.prototype.getDefaults = function () {\n    return Tooltip.DEFAULTS\n  }\n\n  Tooltip.prototype.getOptions = function (options) {\n    options = $.extend({}, this.getDefaults(), this.$element.data(), options)\n\n    if (options.delay && typeof options.delay == 'number') {\n      options.delay = {\n        show: options.delay,\n        hide: options.delay\n      }\n    }\n\n    return options\n  }\n\n  Tooltip.prototype.getDelegateOptions = function () {\n    var options  = {}\n    var defaults = this.getDefaults()\n\n    this._options && $.each(this._options, function (key, value) {\n      if (defaults[key] != value) options[key] = value\n    })\n\n    return options\n  }\n\n  Tooltip.prototype.enter = function (obj) {\n    var self = obj instanceof this.constructor ?\n      obj : $(obj.currentTarget).data('bs.' + this.type)\n\n    if (self && self.$tip && self.$tip.is(':visible')) {\n      self.hoverState = 'in'\n      return\n    }\n\n    if (!self) {\n      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())\n      $(obj.currentTarget).data('bs.' + this.type, self)\n    }\n\n    clearTimeout(self.timeout)\n\n    self.hoverState = 'in'\n\n    if (!self.options.delay || !self.options.delay.show) return self.show()\n\n    self.timeout = setTimeout(function () {\n      if (self.hoverState == 'in') self.show()\n    }, self.options.delay.show)\n  }\n\n  Tooltip.prototype.leave = function (obj) {\n    var self = obj instanceof this.constructor ?\n      obj : $(obj.currentTarget).data('bs.' + this.type)\n\n    if (!self) {\n      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())\n      $(obj.currentTarget).data('bs.' + this.type, self)\n    }\n\n    clearTimeout(self.timeout)\n\n    self.hoverState = 'out'\n\n    if (!self.options.delay || !self.options.delay.hide) return self.hide()\n\n    self.timeout = setTimeout(function () {\n      if (self.hoverState == 'out') self.hide()\n    }, self.options.delay.hide)\n  }\n\n  Tooltip.prototype.show = function () {\n    var e = $.Event('show.bs.' + this.type)\n\n    if (this.hasContent() && this.enabled) {\n      this.$element.trigger(e)\n\n      var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])\n      if (e.isDefaultPrevented() || !inDom) return\n      var that = this\n\n      var $tip = this.tip()\n\n      var tipId = this.getUID(this.type)\n\n      this.setContent()\n      $tip.attr('id', tipId)\n      this.$element.attr('aria-describedby', tipId)\n\n      if (this.options.animation) $tip.addClass('fade')\n\n      var placement = typeof this.options.placement == 'function' ?\n        this.options.placement.call(this, $tip[0], this.$element[0]) :\n        this.options.placement\n\n      var autoToken = /\\s?auto?\\s?/i\n      var autoPlace = autoToken.test(placement)\n      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'\n\n      $tip\n        .detach()\n        .css({ top: 0, left: 0, display: 'block' })\n        .addClass(placement)\n        .data('bs.' + this.type, this)\n\n      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)\n\n      var pos          = this.getPosition()\n      var actualWidth  = $tip[0].offsetWidth\n      var actualHeight = $tip[0].offsetHeight\n\n      if (autoPlace) {\n        var orgPlacement = placement\n        var $container   = this.options.container ? $(this.options.container) : this.$element.parent()\n        var containerDim = this.getPosition($container)\n\n        placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top'    :\n                    placement == 'top'    && pos.top    - actualHeight < containerDim.top    ? 'bottom' :\n                    placement == 'right'  && pos.right  + actualWidth  > containerDim.width  ? 'left'   :\n                    placement == 'left'   && pos.left   - actualWidth  < containerDim.left   ? 'right'  :\n                    placement\n\n        $tip\n          .removeClass(orgPlacement)\n          .addClass(placement)\n      }\n\n      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)\n\n      this.applyPlacement(calculatedOffset, placement)\n\n      var complete = function () {\n        var prevHoverState = that.hoverState\n        that.$element.trigger('shown.bs.' + that.type)\n        that.hoverState = null\n\n        if (prevHoverState == 'out') that.leave(that)\n      }\n\n      $.support.transition && this.$tip.hasClass('fade') ?\n        $tip\n          .one('bsTransitionEnd', complete)\n          .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :\n        complete()\n    }\n  }\n\n  Tooltip.prototype.applyPlacement = function (offset, placement) {\n    var $tip   = this.tip()\n    var width  = $tip[0].offsetWidth\n    var height = $tip[0].offsetHeight\n\n    // manually read margins because getBoundingClientRect includes difference\n    var marginTop = parseInt($tip.css('margin-top'), 10)\n    var marginLeft = parseInt($tip.css('margin-left'), 10)\n\n    // we must check for NaN for ie 8/9\n    if (isNaN(marginTop))  marginTop  = 0\n    if (isNaN(marginLeft)) marginLeft = 0\n\n    offset.top  = offset.top  + marginTop\n    offset.left = offset.left + marginLeft\n\n    // $.fn.offset doesn't round pixel values\n    // so we use setOffset directly with our own function B-0\n    $.offset.setOffset($tip[0], $.extend({\n      using: function (props) {\n        $tip.css({\n          top: Math.round(props.top),\n          left: Math.round(props.left)\n        })\n      }\n    }, offset), 0)\n\n    $tip.addClass('in')\n\n    // check to see if placing tip in new offset caused the tip to resize itself\n    var actualWidth  = $tip[0].offsetWidth\n    var actualHeight = $tip[0].offsetHeight\n\n    if (placement == 'top' && actualHeight != height) {\n      offset.top = offset.top + height - actualHeight\n    }\n\n    var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)\n\n    if (delta.left) offset.left += delta.left\n    else offset.top += delta.top\n\n    var isVertical          = /top|bottom/.test(placement)\n    var arrowDelta          = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight\n    var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'\n\n    $tip.offset(offset)\n    this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)\n  }\n\n  Tooltip.prototype.replaceArrow = function (delta, dimension, isHorizontal) {\n    this.arrow()\n      .css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')\n      .css(isHorizontal ? 'top' : 'left', '')\n  }\n\n  Tooltip.prototype.setContent = function () {\n    var $tip  = this.tip()\n    var title = this.getTitle()\n\n    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)\n    $tip.removeClass('fade in top bottom left right')\n  }\n\n  Tooltip.prototype.hide = function (callback) {\n    var that = this\n    var $tip = this.tip()\n    var e    = $.Event('hide.bs.' + this.type)\n\n    function complete() {\n      if (that.hoverState != 'in') $tip.detach()\n      that.$element\n        .removeAttr('aria-describedby')\n        .trigger('hidden.bs.' + that.type)\n      callback && callback()\n    }\n\n    this.$element.trigger(e)\n\n    if (e.isDefaultPrevented()) return\n\n    $tip.removeClass('in')\n\n    $.support.transition && this.$tip.hasClass('fade') ?\n      $tip\n        .one('bsTransitionEnd', complete)\n        .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :\n      complete()\n\n    this.hoverState = null\n\n    return this\n  }\n\n  Tooltip.prototype.fixTitle = function () {\n    var $e = this.$element\n    if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {\n      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')\n    }\n  }\n\n  Tooltip.prototype.hasContent = function () {\n    return this.getTitle()\n  }\n\n  Tooltip.prototype.getPosition = function ($element) {\n    $element   = $element || this.$element\n\n    var el     = $element[0]\n    var isBody = el.tagName == 'BODY'\n\n    var elRect    = el.getBoundingClientRect()\n    if (elRect.width == null) {\n      // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093\n      elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })\n    }\n    var elOffset  = isBody ? { top: 0, left: 0 } : $element.offset()\n    var scroll    = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }\n    var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null\n\n    return $.extend({}, elRect, scroll, outerDims, elOffset)\n  }\n\n  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {\n    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2 } :\n           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :\n           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :\n        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }\n\n  }\n\n  Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {\n    var delta = { top: 0, left: 0 }\n    if (!this.$viewport) return delta\n\n    var viewportPadding = this.options.viewport && this.options.viewport.padding || 0\n    var viewportDimensions = this.getPosition(this.$viewport)\n\n    if (/right|left/.test(placement)) {\n      var topEdgeOffset    = pos.top - viewportPadding - viewportDimensions.scroll\n      var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight\n      if (topEdgeOffset < viewportDimensions.top) { // top overflow\n        delta.top = viewportDimensions.top - topEdgeOffset\n      } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow\n        delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset\n      }\n    } else {\n      var leftEdgeOffset  = pos.left - viewportPadding\n      var rightEdgeOffset = pos.left + viewportPadding + actualWidth\n      if (leftEdgeOffset < viewportDimensions.left) { // left overflow\n        delta.left = viewportDimensions.left - leftEdgeOffset\n      } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow\n        delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset\n      }\n    }\n\n    return delta\n  }\n\n  Tooltip.prototype.getTitle = function () {\n    var title\n    var $e = this.$element\n    var o  = this.options\n\n    title = $e.attr('data-original-title')\n      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)\n\n    return title\n  }\n\n  Tooltip.prototype.getUID = function (prefix) {\n    do prefix += ~~(Math.random() * 1000000)\n    while (document.getElementById(prefix))\n    return prefix\n  }\n\n  Tooltip.prototype.tip = function () {\n    return (this.$tip = this.$tip || $(this.options.template))\n  }\n\n  Tooltip.prototype.arrow = function () {\n    return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))\n  }\n\n  Tooltip.prototype.enable = function () {\n    this.enabled = true\n  }\n\n  Tooltip.prototype.disable = function () {\n    this.enabled = false\n  }\n\n  Tooltip.prototype.toggleEnabled = function () {\n    this.enabled = !this.enabled\n  }\n\n  Tooltip.prototype.toggle = function (e) {\n    var self = this\n    if (e) {\n      self = $(e.currentTarget).data('bs.' + this.type)\n      if (!self) {\n        self = new this.constructor(e.currentTarget, this.getDelegateOptions())\n        $(e.currentTarget).data('bs.' + this.type, self)\n      }\n    }\n\n    self.tip().hasClass('in') ? self.leave(self) : self.enter(self)\n  }\n\n  Tooltip.prototype.destroy = function () {\n    var that = this\n    clearTimeout(this.timeout)\n    this.hide(function () {\n      that.$element.off('.' + that.type).removeData('bs.' + that.type)\n    })\n  }\n\n\n  // TOOLTIP PLUGIN DEFINITION\n  // =========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.tooltip')\n      var options = typeof option == 'object' && option\n\n      if (!data && option == 'destroy') return\n      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.tooltip\n\n  $.fn.tooltip             = Plugin\n  $.fn.tooltip.Constructor = Tooltip\n\n\n  // TOOLTIP NO CONFLICT\n  // ===================\n\n  $.fn.tooltip.noConflict = function () {\n    $.fn.tooltip = old\n    return this\n  }\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: popover.js v3.3.2\n * http://getbootstrap.com/javascript/#popovers\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // POPOVER PUBLIC CLASS DEFINITION\n  // ===============================\n\n  var Popover = function (element, options) {\n    this.init('popover', element, options)\n  }\n\n  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')\n\n  Popover.VERSION  = '3.3.2'\n\n  Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {\n    placement: 'right',\n    trigger: 'click',\n    content: '',\n    template: '<div class=\"popover\" role=\"tooltip\"><div class=\"arrow\"></div><h3 class=\"popover-title\"></h3><div class=\"popover-content\"></div></div>'\n  })\n\n\n  // NOTE: POPOVER EXTENDS tooltip.js\n  // ================================\n\n  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)\n\n  Popover.prototype.constructor = Popover\n\n  Popover.prototype.getDefaults = function () {\n    return Popover.DEFAULTS\n  }\n\n  Popover.prototype.setContent = function () {\n    var $tip    = this.tip()\n    var title   = this.getTitle()\n    var content = this.getContent()\n\n    $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)\n    $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events\n      this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'\n    ](content)\n\n    $tip.removeClass('fade top bottom left right in')\n\n    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do\n    // this manually by checking the contents.\n    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()\n  }\n\n  Popover.prototype.hasContent = function () {\n    return this.getTitle() || this.getContent()\n  }\n\n  Popover.prototype.getContent = function () {\n    var $e = this.$element\n    var o  = this.options\n\n    return $e.attr('data-content')\n      || (typeof o.content == 'function' ?\n            o.content.call($e[0]) :\n            o.content)\n  }\n\n  Popover.prototype.arrow = function () {\n    return (this.$arrow = this.$arrow || this.tip().find('.arrow'))\n  }\n\n  Popover.prototype.tip = function () {\n    if (!this.$tip) this.$tip = $(this.options.template)\n    return this.$tip\n  }\n\n\n  // POPOVER PLUGIN DEFINITION\n  // =========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.popover')\n      var options = typeof option == 'object' && option\n\n      if (!data && option == 'destroy') return\n      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.popover\n\n  $.fn.popover             = Plugin\n  $.fn.popover.Constructor = Popover\n\n\n  // POPOVER NO CONFLICT\n  // ===================\n\n  $.fn.popover.noConflict = function () {\n    $.fn.popover = old\n    return this\n  }\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: scrollspy.js v3.3.2\n * http://getbootstrap.com/javascript/#scrollspy\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // SCROLLSPY CLASS DEFINITION\n  // ==========================\n\n  function ScrollSpy(element, options) {\n    var process  = $.proxy(this.process, this)\n\n    this.$body          = $('body')\n    this.$scrollElement = $(element).is('body') ? $(window) : $(element)\n    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)\n    this.selector       = (this.options.target || '') + ' .nav li > a'\n    this.offsets        = []\n    this.targets        = []\n    this.activeTarget   = null\n    this.scrollHeight   = 0\n\n    this.$scrollElement.on('scroll.bs.scrollspy', process)\n    this.refresh()\n    this.process()\n  }\n\n  ScrollSpy.VERSION  = '3.3.2'\n\n  ScrollSpy.DEFAULTS = {\n    offset: 10\n  }\n\n  ScrollSpy.prototype.getScrollHeight = function () {\n    return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)\n  }\n\n  ScrollSpy.prototype.refresh = function () {\n    var offsetMethod = 'offset'\n    var offsetBase   = 0\n\n    if (!$.isWindow(this.$scrollElement[0])) {\n      offsetMethod = 'position'\n      offsetBase   = this.$scrollElement.scrollTop()\n    }\n\n    this.offsets = []\n    this.targets = []\n    this.scrollHeight = this.getScrollHeight()\n\n    var self     = this\n\n    this.$body\n      .find(this.selector)\n      .map(function () {\n        var $el   = $(this)\n        var href  = $el.data('target') || $el.attr('href')\n        var $href = /^#./.test(href) && $(href)\n\n        return ($href\n          && $href.length\n          && $href.is(':visible')\n          && [[$href[offsetMethod]().top + offsetBase, href]]) || null\n      })\n      .sort(function (a, b) { return a[0] - b[0] })\n      .each(function () {\n        self.offsets.push(this[0])\n        self.targets.push(this[1])\n      })\n  }\n\n  ScrollSpy.prototype.process = function () {\n    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset\n    var scrollHeight = this.getScrollHeight()\n    var maxScroll    = this.options.offset + scrollHeight - this.$scrollElement.height()\n    var offsets      = this.offsets\n    var targets      = this.targets\n    var activeTarget = this.activeTarget\n    var i\n\n    if (this.scrollHeight != scrollHeight) {\n      this.refresh()\n    }\n\n    if (scrollTop >= maxScroll) {\n      return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)\n    }\n\n    if (activeTarget && scrollTop < offsets[0]) {\n      this.activeTarget = null\n      return this.clear()\n    }\n\n    for (i = offsets.length; i--;) {\n      activeTarget != targets[i]\n        && scrollTop >= offsets[i]\n        && (!offsets[i + 1] || scrollTop <= offsets[i + 1])\n        && this.activate(targets[i])\n    }\n  }\n\n  ScrollSpy.prototype.activate = function (target) {\n    this.activeTarget = target\n\n    this.clear()\n\n    var selector = this.selector +\n        '[data-target=\"' + target + '\"],' +\n        this.selector + '[href=\"' + target + '\"]'\n\n    var active = $(selector)\n      .parents('li')\n      .addClass('active')\n\n    if (active.parent('.dropdown-menu').length) {\n      active = active\n        .closest('li.dropdown')\n        .addClass('active')\n    }\n\n    active.trigger('activate.bs.scrollspy')\n  }\n\n  ScrollSpy.prototype.clear = function () {\n    $(this.selector)\n      .parentsUntil(this.options.target, '.active')\n      .removeClass('active')\n  }\n\n\n  // SCROLLSPY PLUGIN DEFINITION\n  // ===========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.scrollspy')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.scrollspy\n\n  $.fn.scrollspy             = Plugin\n  $.fn.scrollspy.Constructor = ScrollSpy\n\n\n  // SCROLLSPY NO CONFLICT\n  // =====================\n\n  $.fn.scrollspy.noConflict = function () {\n    $.fn.scrollspy = old\n    return this\n  }\n\n\n  // SCROLLSPY DATA-API\n  // ==================\n\n  $(window).on('load.bs.scrollspy.data-api', function () {\n    $('[data-spy=\"scroll\"]').each(function () {\n      var $spy = $(this)\n      Plugin.call($spy, $spy.data())\n    })\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: tab.js v3.3.2\n * http://getbootstrap.com/javascript/#tabs\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // TAB CLASS DEFINITION\n  // ====================\n\n  var Tab = function (element) {\n    this.element = $(element)\n  }\n\n  Tab.VERSION = '3.3.2'\n\n  Tab.TRANSITION_DURATION = 150\n\n  Tab.prototype.show = function () {\n    var $this    = this.element\n    var $ul      = $this.closest('ul:not(.dropdown-menu)')\n    var selector = $this.data('target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    if ($this.parent('li').hasClass('active')) return\n\n    var $previous = $ul.find('.active:last a')\n    var hideEvent = $.Event('hide.bs.tab', {\n      relatedTarget: $this[0]\n    })\n    var showEvent = $.Event('show.bs.tab', {\n      relatedTarget: $previous[0]\n    })\n\n    $previous.trigger(hideEvent)\n    $this.trigger(showEvent)\n\n    if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return\n\n    var $target = $(selector)\n\n    this.activate($this.closest('li'), $ul)\n    this.activate($target, $target.parent(), function () {\n      $previous.trigger({\n        type: 'hidden.bs.tab',\n        relatedTarget: $this[0]\n      })\n      $this.trigger({\n        type: 'shown.bs.tab',\n        relatedTarget: $previous[0]\n      })\n    })\n  }\n\n  Tab.prototype.activate = function (element, container, callback) {\n    var $active    = container.find('> .active')\n    var transition = callback\n      && $.support.transition\n      && (($active.length && $active.hasClass('fade')) || !!container.find('> .fade').length)\n\n    function next() {\n      $active\n        .removeClass('active')\n        .find('> .dropdown-menu > .active')\n          .removeClass('active')\n        .end()\n        .find('[data-toggle=\"tab\"]')\n          .attr('aria-expanded', false)\n\n      element\n        .addClass('active')\n        .find('[data-toggle=\"tab\"]')\n          .attr('aria-expanded', true)\n\n      if (transition) {\n        element[0].offsetWidth // reflow for transition\n        element.addClass('in')\n      } else {\n        element.removeClass('fade')\n      }\n\n      if (element.parent('.dropdown-menu')) {\n        element\n          .closest('li.dropdown')\n            .addClass('active')\n          .end()\n          .find('[data-toggle=\"tab\"]')\n            .attr('aria-expanded', true)\n      }\n\n      callback && callback()\n    }\n\n    $active.length && transition ?\n      $active\n        .one('bsTransitionEnd', next)\n        .emulateTransitionEnd(Tab.TRANSITION_DURATION) :\n      next()\n\n    $active.removeClass('in')\n  }\n\n\n  // TAB PLUGIN DEFINITION\n  // =====================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.tab')\n\n      if (!data) $this.data('bs.tab', (data = new Tab(this)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.tab\n\n  $.fn.tab             = Plugin\n  $.fn.tab.Constructor = Tab\n\n\n  // TAB NO CONFLICT\n  // ===============\n\n  $.fn.tab.noConflict = function () {\n    $.fn.tab = old\n    return this\n  }\n\n\n  // TAB DATA-API\n  // ============\n\n  var clickHandler = function (e) {\n    e.preventDefault()\n    Plugin.call($(this), 'show')\n  }\n\n  $(document)\n    .on('click.bs.tab.data-api', '[data-toggle=\"tab\"]', clickHandler)\n    .on('click.bs.tab.data-api', '[data-toggle=\"pill\"]', clickHandler)\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: affix.js v3.3.2\n * http://getbootstrap.com/javascript/#affix\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // AFFIX CLASS DEFINITION\n  // ======================\n\n  var Affix = function (element, options) {\n    this.options = $.extend({}, Affix.DEFAULTS, options)\n\n    this.$target = $(this.options.target)\n      .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))\n      .on('click.bs.affix.data-api',  $.proxy(this.checkPositionWithEventLoop, this))\n\n    this.$element     = $(element)\n    this.affixed      =\n    this.unpin        =\n    this.pinnedOffset = null\n\n    this.checkPosition()\n  }\n\n  Affix.VERSION  = '3.3.2'\n\n  Affix.RESET    = 'affix affix-top affix-bottom'\n\n  Affix.DEFAULTS = {\n    offset: 0,\n    target: window\n  }\n\n  Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {\n    var scrollTop    = this.$target.scrollTop()\n    var position     = this.$element.offset()\n    var targetHeight = this.$target.height()\n\n    if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false\n\n    if (this.affixed == 'bottom') {\n      if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'\n      return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'\n    }\n\n    var initializing   = this.affixed == null\n    var colliderTop    = initializing ? scrollTop : position.top\n    var colliderHeight = initializing ? targetHeight : height\n\n    if (offsetTop != null && scrollTop <= offsetTop) return 'top'\n    if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'\n\n    return false\n  }\n\n  Affix.prototype.getPinnedOffset = function () {\n    if (this.pinnedOffset) return this.pinnedOffset\n    this.$element.removeClass(Affix.RESET).addClass('affix')\n    var scrollTop = this.$target.scrollTop()\n    var position  = this.$element.offset()\n    return (this.pinnedOffset = position.top - scrollTop)\n  }\n\n  Affix.prototype.checkPositionWithEventLoop = function () {\n    setTimeout($.proxy(this.checkPosition, this), 1)\n  }\n\n  Affix.prototype.checkPosition = function () {\n    if (!this.$element.is(':visible')) return\n\n    var height       = this.$element.height()\n    var offset       = this.options.offset\n    var offsetTop    = offset.top\n    var offsetBottom = offset.bottom\n    var scrollHeight = $('body').height()\n\n    if (typeof offset != 'object')         offsetBottom = offsetTop = offset\n    if (typeof offsetTop == 'function')    offsetTop    = offset.top(this.$element)\n    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)\n\n    var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)\n\n    if (this.affixed != affix) {\n      if (this.unpin != null) this.$element.css('top', '')\n\n      var affixType = 'affix' + (affix ? '-' + affix : '')\n      var e         = $.Event(affixType + '.bs.affix')\n\n      this.$element.trigger(e)\n\n      if (e.isDefaultPrevented()) return\n\n      this.affixed = affix\n      this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null\n\n      this.$element\n        .removeClass(Affix.RESET)\n        .addClass(affixType)\n        .trigger(affixType.replace('affix', 'affixed') + '.bs.affix')\n    }\n\n    if (affix == 'bottom') {\n      this.$element.offset({\n        top: scrollHeight - height - offsetBottom\n      })\n    }\n  }\n\n\n  // AFFIX PLUGIN DEFINITION\n  // =======================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.affix')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.affix', (data = new Affix(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.affix\n\n  $.fn.affix             = Plugin\n  $.fn.affix.Constructor = Affix\n\n\n  // AFFIX NO CONFLICT\n  // =================\n\n  $.fn.affix.noConflict = function () {\n    $.fn.affix = old\n    return this\n  }\n\n\n  // AFFIX DATA-API\n  // ==============\n\n  $(window).on('load', function () {\n    $('[data-spy=\"affix\"]').each(function () {\n      var $spy = $(this)\n      var data = $spy.data()\n\n      data.offset = data.offset || {}\n\n      if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom\n      if (data.offsetTop    != null) data.offset.top    = data.offsetTop\n\n      Plugin.call($spy, data)\n    })\n  })\n\n}(jQuery);\n"
  },
  {
    "path": "src/main/resources/reference.conf",
    "content": "# Configuration file for SLB Reference Implementation\n#\n# This configuration document is divided into three components. Note that (in this simplified\n# model) each component shares the same base configuration.\n#  - http-server\n#  - load-balancer\n#  - daemon\n#\n#\nneutrino {\n\n  # Core engine settings\n  core {}\n\n  # Example initializers\n  initializers = []\n\n\n  # Configured channel listeners (interfaces)\n  listeners = [\n    # By default, we listen for HTTP port 8080\n    {}\n  ]\n\n\n  # Pool List.\n  # A a user should configure one or more pools for use by populating pool items\n  pools = []\n\n\n  # Default Listener Settings\n  # These will be merged into individual listeners[]\n  listener = {\n    # Hostname or IP of interface to listen on.\n    host = \"0.0.0.0\"\n\n    # Port to listen on (required).\n    # Must be greater than 1024 if not running as root (recommended)\n    port = [8080]\n\n    # Port alias\n    # Used as backup in matching portmaps to downstream. This is not listened on.\n    # If you want this port to be listened on as well as the primary, add it to the primary\n    # 'port' list instead.\n    port-alias = [80]\n\n    # Protocol\n    # One of [\"http\", \"zmq\"]\n    protocol = \"http\"\n\n    # Default Pipeline Class for interface\n    pipeline-class = null\n\n    # Swappable pool-selection mechanisim\n    # One of [none, first, default, cname, or full.class.Name]\n    pool-resolver = \"none\"\n\n    # Interface-specific timeouts\n    timeout = ${neutrino.timeout}\n\n    # Channel Options\n    #\n    channel-options {\n\n      # Force downstream channel to keep-alive or use client's keep-alive default\n      force-keepalive = true\n\n      # Event audit support\n      #\n      # Enabling this will capture timestamps of events within the pipeline and output\n      # them when the threshold is passed.\n      #  > audit-threshold = 5s\n      audit-threshold = null\n    }\n\n  }\n\n\n  # Default channel timeout settings\n  #\n  # These will be inherited (and can be overridden) in either VIP or Pool settings. This means the following\n  # defaults are provided:\n  #   ebay.neutrino.timeout\n  #   ebay.neutrino.pool.timeout\n  #   ebay.neutrino.pools[x].timeout\n  #\n  timeout = {\n\n    # Timeout for read-idle; if a read has not been been made on this VIP's socket within this duration,\n    # fail and close channel\n    #   Default (30 seconds) closes channel if no reads or writes within 2 seconds\n    #   Zero (0 seconds) disables timeout\n    read-idle-timeout = 30s\n\n    # Timeout for write-idle; if a read has not been been made on this VIP's socket within this duration,\n    # fail and close channel\n    #   Default (30 seconds) closes channel if no reads or writes within 2 seconds\n    #   Zero (0 seconds) disables timeout\n    write-idle-timeout = 30s\n\n    # Timeout for individual request; if the request is open for longer than this duration, fail and close\n    #   Default (30 seconds) closes request's channel after 30 seconds, regardless of usage\n    #   Zero (0 seconds) disables timeout\n    request-timeout = 30s\n\n    # Timeout for HTTP session (VIP connection); if the channel is open for longer than this duration,\n    # fail and close.\n    #   Default (2 minutes) closes channel after 2 mins, regardless of usage\n    #   Zero (0 seconds) disables timeout\n    session-timeout = 2m\n\n    # Timeout for writes; if a write has not been completed within this duration, fail and close channel\n    #   Default (0 seconds) disables timeout\n    write-timeout = 5s\n\n    # Connection timeout; if applied to an outbound connection, will fail the connect() attempt if\n    # not completed within this time\n    #   Default (5 seconds) fails attempt after 5 seconds\n    connection-timeout = 5s\n  }\n\n\n  # Default Pool Settings\n  # These will be merged into individual pool settings\n  pool {\n    # Users should ensure they provide their own valid ID if more than one pool is configured\n    id = \"default\"\n\n    # Virtual Address (VIP) List\n    # A user should configure one or more VIPs for use by populating pool items.\n    addresses = [\n      # Address Settings\n      # These\n    ]\n\n    # Configured protocol\n    protocol = \"http\"\n\n    # Servers\n    servers = []\n\n    # Load balancer configurationbal\n    #  One of [rr/round-robin, lc/least-connection, class-name]\n    balancer = \"round-robin\"\n\n    # Health monitoring\n    health {\n      # Default health monitoring is a status-200 monitor on root URL\n      #monitor-class = \"com.ebay.neutrino.health.Status200Monitor\"\n      url = \"/\"\n    }\n\n\n    # Pool-specific timeouts\n    timeout = ${neutrino.timeout}\n  }\n\n\n  # Reporting/Metrics settings\n  # These can be enabled by including the MetricsLifecycle component in the initializers\n  metrics = [\n    # Support for console logging\n    { type = \"console\",  publish-period = 20m },\n\n    # Support for graphite publishing\n    # { type = \"graphite\", publish-period = 1m, host = \"10.65.255.15\", port = 2003 }\n  ]\n\n  supervisorThreadCount = 1\n\n  workerThreadCount = 4\n\n}"
  },
  {
    "path": "src/main/resources/simplelogger.properties",
    "content": "org.slf4j.simpleLogger.defaultLogLevel=warn\n\n# Overall logging tools (simple and full event logging)\n#org.slf4j.simpleLogger.log.com.ebay.neutrino.handler.ops.HttpDiagnosticsHandler=info\n#org.slf4j.simpleLogger.log.com.ebay.neutrino.handler.ops.HeaderDiagnosticsHandler=info\n#org.slf4j.simpleLogger.log.com.ebay.neutrino.handler.ops.NeutrinoAuditHandler$=debug\norg.slf4j.simpleLogger.log.io.netty.handler.logging.LoggingHandler=info\n\n\n# Noisy handler diagnostics\norg.slf4j.simpleLogger.log.com.ebay.neutrino=info\n#org.slf4j.simpleLogger.log.com.ebay.neutrino.PooledService=info\n#org.slf4j.simpleLogger.log.com.ebay.neutrino.pipeline=info\n#org.slf4j.simpleLogger.log.com.ebay.neutrino.handler=info\n#org.slf4j.simpleLogger.log.com.ebay.neutrino.handler.PipelineHandler=info\n#org.slf4j.simpleLogger.log.com.ebay.neutrino.handler.NeutrinoDownstreamHandler=info\n#org.slf4j.simpleLogger.log.com.ebay.neutrino.handler.NeutrinoDownstreamListener=info\n#org.slf4j.simpleLogger.log.com.ebay.neutrino.handler.NeutrinoDownstreamAttempt=info\n#org.slf4j.simpleLogger.log.com.ebay.neutrino.handler.NeutrinoRequestHandler=info\n#org.slf4j.simpleLogger.log.com.ebay.neutrino.handler.NeutrinoEmbeddedHandler=info\n\n# Operational handlers\n#org.slf4j.simpleLogger.log.com.ebay.neutrino.handler.ops.ChannelTimeout=debug\n#org.slf4j.simpleLogger.log.com.ebay.neutrino.handler.ops.ChannelTimeoutHandler=debug\n\n#org.slf4j.simpleLogger.log.com.ebay.neutrino.handler.neutrino.NeutrinoEncodingHandler=info\n#org.slf4j.simpleLogger.log.com.ebay.neutrino.handler.EndpointHandler=info\n#org.slf4j.simpleLogger.log.com.ebay.neutrino.pipeline.HttpMessageUtils$=info\n"
  },
  {
    "path": "src/main/resources/slb.conf",
    "content": "neutrino {\n  # Use this to enable/disable the direct SLB api\n  enable-api = true,\n}\nneutrino.datasource {\n  #Refresh time for file\n\n  refresh-period = 30s\n  datasource-reader = \"com.ebay.neutrino.datasource.FileReader\"\n}\n\nresolvers.neutrino {\n\n\n  listeners = [\n    {\n      # pool resolvers configured\n      pool-resolver = [\"cname\", \"layerseven\"],\n      # listening port of SLB\n      port = 8080,\n      protocol = \"http\"\n    }\n\n  ]\n\n  initializers = [\n    \"com.ebay.neutrino.metrics.MetricsLifecycle\"\n  ]\n  metrics = [\n    # Support for console logging\n    { type = \"console\",  publish-period = 1m }\n\n  ]\n\n  # pools configuration\n  pools = [\n    # pool 1, lc = least connection\n    { id = \"cname1_id\", protocol = \"http\", balancer = \"lc\", port = \"8080\",\n      # servers are the VM/Servers to which traffic will be routed\n      servers = [\n        { id=\"server10\", host = \"localhost\", port = \"9999\" }\n        { id=\"server2\", host = \"localhost\", port = \"9998\" }\n      ]\n      # All traffic which comes with header Host=cname1.com will be routed to localhost.\n      # cname1.com should uniquely to idenitfy a pool\n      addresses = [\n        { host=\"cname1.com\" }\n        { host=\"cname5.com\" }\n        { host=\"d-sjc-00541471.corp.ebay.com\" }\n\n      ]\n      # wildcard shares the balancer and protocol with addresses\n      # All traffic which comes with header host=cnamewildcard.com amd path=local will be routed to localhost.\n      # host + path should be unique to identify the pool\n      wildcard = [\n        {host=\"cnamewildcard.com\", path=\"/website\" }\n      ]\n      timeout= {\n        read-idle-timeout = 1s,\n        write-idle-timeout = 1s,\n        write-timeout = 1s,\n        session-timeout = 1s,\n        request-timeout = 1s,\n        connection-timeout = 1s\n\n      }\n    },\n    # pool 2\n//    { id = \"cname2_id\", protocol = \"http\", balancer = \"round-robin\", port = \"8080\",\n//      servers = [\n//        { id=\"server3\", host = \"www.ebay1.com\", port = \"80\", weight = 5 }\n//      ]\n//      addresses = [\n//        {host=\"cname2.com\" }\n//      ]\n//      wildcard = [\n//        {host=\"cnamewildcard.com\", path=\"/ebay\" }\n//      ]\n//    }\n\n\n    # pool 2\n    { id = \"cname2_id\", protocol = \"http\", balancer = \"weighted-round-robin\", port = \"8080\",\n      servers = [\n        { id=\"server3\", host = \"www.ebay1.com\", port = \"80\", weight = 5 }\n      ]\n      addresses = [\n        {host=\"cname2.com\" }\n      ]\n      wildcard = [\n        {host=\"cnamewildcard.com\", path=\"/ebay\" }\n      ]\n    }\n\n  ]\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/NeutrinoCore.scala",
    "content": "package com.ebay.neutrino\n\nimport com.ebay.neutrino.channel.NeutrinoServices\nimport com.ebay.neutrino.config._\nimport com.ebay.neutrino.metrics.{Instrumented, MetricsKey}\nimport com.ebay.neutrino.util.Utilities\nimport com.typesafe.config.Config\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.channel.nio.NioEventLoopGroup\n\nimport scala.concurrent.Future\n\n\n/**\n * Core Balancer Component.\n *\n * Good Netty design practices:\n * @see http://normanmaurer.me/presentations/2014-facebook-eng-netty/slides.html#28.0\n *\n * Netty Read throttling:\n * @see https://groups.google.com/forum/?fromgroups=#!topic/netty/Zz4enelRwYE\n */\nclass NeutrinoCore(private[neutrino] val settings: NeutrinoSettings) extends StrictLogging with Instrumented {\n\n  //implicit val ec = ExecutionContext.fromExecutor(Executors.newCachedThreadPool()newFixedThreadPool(10))\n  implicit val context = scala.concurrent.ExecutionContext.Implicits.global\n\n  // Connections (this should be externalized? We're sort of bleeding impl here)\n  val services    = new NeutrinoServices(this)\n  val supervisor  = new NioEventLoopGroup(settings.supervisorThreadCount)\n  val workers     = new NioEventLoopGroup(settings.workerThreadCount)\n\n  // Add some simple diagnostic metrics\n  metrics.safegauge(MetricsKey.BossThreads) { supervisor.executorCount }\n  metrics.safegauge(MetricsKey.IOThreads)   { workers.executorCount }\n\n\n  /**\n   * Configure the balancer with the settings provided.\n   *\n   * Note - this method is depricated. Callers should (ideally) configure the underlying\n   * service directly.\n   *\n   * @param settings\n   */\n  @Deprecated\n  def configure(settings: LoadBalancer) =\n    // Defer the configuration to the available services; they will split/remove relevant configurations\n    services map (_.update(settings.pools:_*))\n\n\n  /**\n   * Start the balancer core's lifecycle.\n   *\n   * @return a future indicating success or failure of startup ports. Either all will be successful, or all will fail.\n   */\n  def start(): Future[_] = {\n    import Utilities._\n\n    // Before starting listeners, run the startup lifecycle event\n    settings.lifecycleListeners foreach (_.start(this))\n\n    // Register worker-completion activities on the work-groups, including Shutdown lifecycle events\n    workers.terminationFuture().future() onComplete {\n      // TODO make sure this is consistent with the core.shutdown()\n      case result => settings.lifecycleListeners foreach (_.shutdown(NeutrinoCore.this))\n    }\n\n    // Initialize individual ports\n    val listeners = services flatMap (_.listen())\n\n    // Convert listeners to a group-future\n    val future = Future.sequence(listeners.toMap.values)\n\n    // If any of these guys fail, clean up them all\n    future onFailure { case ex =>\n      listeners foreach {\n        case (address, channel) => channel.onFailure {\n          case ex => logger.warn(s\"Unable to successfully start listener on address: $address\", ex)\n        }\n      }\n      shutdown()\n    }\n\n    future\n  }\n\n  /**\n   * Stop the balancer's execution, if running.\n   *\n   * @return a completion future, to notify completion of execution\n   */\n  def shutdown(): Future[_] = {\n    import Utilities._\n\n    // Shutdown services\n    services map (_.shutdown())\n\n    // Wrap Netty futures in scala futures\n    val groups = Seq(supervisor.shutdownGracefully().future(), workers.shutdownGracefully().future())\n\n    // Return when both are complete\n    Future.sequence(groups)\n  }\n\n  /**\n   * Resolve registered components.\n   */\n  def component[T <: NeutrinoLifecycle](clazz: Class[T]): Option[T] =\n    settings.lifecycleListeners.find(_.getClass == clazz).asInstanceOf[Option[T]]\n\n}\n\n\nobject NeutrinoCore {\n\n  // Default constructor of balancer\n  def apply(): NeutrinoCore = NeutrinoCore(Configuration.load())\n\n  // Default constructor of balancer\n  def apply(config: Config): NeutrinoCore = new NeutrinoCore(NeutrinoSettings(config))\n\n  // Default constructor of balancer\n  def apply(settings: NeutrinoSettings, lbconfig: LoadBalancer): NeutrinoCore = {\n    val core = new NeutrinoCore(settings)\n    core.configure(lbconfig)\n    core\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/NeutrinoLifecycle.scala",
    "content": "package com.ebay.neutrino\n\nimport java.lang.reflect.Constructor\n\nimport com.typesafe.config.Config\n\nimport scala.reflect.ClassTag\nimport scala.util.{Failure, Success, Try}\n\n\n/**\n * Marker interface for BalancerLifecycle.\n *\n * This will be created reflectively if provided in dot-delimited class format in the\n * application.conf/reference.conf configuration file.\n *\n * It supports one of two reflective constructors:\n *  .newInstance()\n *  .newInstance(config: Config)\n */\ntrait NeutrinoLifecycle {\n\n  // Component lifecycle starting\n  def start(balancer: NeutrinoCore)\n\n  // Component lifecycle stopping\n  def shutdown(balancer: NeutrinoCore)\n}\n\n\n/**\n * Helper utility class for resolving initializers from the active balancer core.\n *\n * TODO rename to something akin to component\n * TODO rename class for easier Java API\n */\nobject NeutrinoLifecycle {\n\n  // Try a one-parameter ctor for settings\n  def create[T](clazz: Class[_ <: T], c: Config): Option[T] =\n    constructor(clazz.getConstructor(classOf[Config])) map (_.newInstance(c))\n\n  // Try a default (no-param) setting\n  def create[T](clazz: Class[_ <: T]): Option[T] =\n    constructor(clazz.getConstructor()) map (_.newInstance())\n\n  // Resolve the constructor provided, skipping unavailable ones\n  def constructor[T](ctor: => Constructor[T]): Option[Constructor[T]] =\n    Try(ctor) match {\n      case Success(ctor) => Option(ctor)\n      case Failure(x: NoSuchMethodException) => None\n      case Failure(x) => throw x\n    }\n\n  def hasConstructor[T](clazz: Class[_ <: T]): Boolean =\n    constructor(clazz.getConstructor()).isDefined ||\n      constructor(clazz.getConstructor(classOf[Config])).isDefined\n\n\n  // Java-compatible version\n  def getInitializer[T <: NeutrinoLifecycle](core: NeutrinoCore, clazz: Class[T]): Option[T] =\n    core.component[T](clazz)\n\n  // Java-compatible version\n  def getInitializer[T <: NeutrinoLifecycle](request: NeutrinoRequest, clazz: Class[T]): Option[T] =\n    getInitializer(request.session.service.core, clazz)\n\n  // Convenience method; resolve an initializer/component by class\n  def getInitializer[T <: NeutrinoLifecycle](connection: NeutrinoRequest)(implicit ct: ClassTag[T]): Option[T] =\n    connection.session.service.core.component[T](ct.runtimeClass.asInstanceOf[Class[T]])\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/NeutrinoNode.scala",
    "content": "package com.ebay.neutrino\n\nimport com.ebay.neutrino.config.{HealthState, TimeoutSettings, VirtualServer}\nimport com.ebay.neutrino.handler.ops.{ChannelStatisticsHandler, ChannelTimeoutHandler, NeutrinoAuditHandler}\nimport com.ebay.neutrino.handler.{NeutrinoClientDecoder, NeutrinoClientHandler}\nimport com.ebay.neutrino.metrics.{Instrumented, Metrics, MetricsKey}\nimport com.ebay.neutrino.util.{DifferentialStateSupport, Utilities}\nimport com.typesafe.scalalogging.slf4j._\nimport io.netty.bootstrap.Bootstrap\nimport io.netty.buffer.Unpooled\nimport io.netty.channel._\nimport io.netty.channel.socket.SocketChannel\nimport io.netty.channel.socket.nio.NioSocketChannel\nimport io.netty.handler.codec.http.HttpRequestEncoder\n\nimport scala.collection.concurrent.TrieMap\nimport scala.concurrent.Future\nimport scala.concurrent.duration.Duration\nimport scala.util.{Failure, Success}\n\n/**\n * A stateful runtime wrapper around a VirtualServer, encapsulating all the state\n * necessary to manage a downstream node (server).\n *\n * Note that we have an asymmetric relationship with the LoadBalancer component;\n *  - The Pool delegates allocate() calls to the Balancer\n *  - The Balancer is responsible for selecting and calling connect() on the node to\n *    establish the connection\n *  - The Channel's close-listener calls release() on the node on completion\n *  - The Node notifies the balancer of the release.\n *\n * Otherwise, the channel has to maintain an unnecessary link back to the balancer\n * that assigned it, and the load-balancer in turn has to maintain channel->node\n * syncronized state.\n *\n * TODO convert to cached InetAddress to speed up resolution\n * TODO implement available this stack using a mature concurrent manager\n */\ncase class NeutrinoNode(pool: NeutrinoPool, settings: VirtualServer)\n  extends ChannelFutureListener\n  with StrictLogging\n  with Instrumented\n{\n  import Utilities._\n  import com.ebay.neutrino.handler.NeutrinoClientHandler._\n  import com.ebay.neutrino.util.AttributeSupport._\n\n  // Member datum\n  val initializer = new NeutrinoNodeInitializer(this)\n  val available   = new java.util.concurrent.ConcurrentLinkedDeque[Channel]()\n  val allocated   = new TrieMap[ChannelId, Channel]()\n\n\n  /**\n   * Update the node settings.\n   */\n  def update(setting: VirtualServer) = {\n    // Update current health\n    if (settings.healthState != HealthState.Unknown) settings.healthState = settings.healthState\n  }\n\n  /**\n   * Make the connection attempt.\n   *\n   * The challenge here is that we want to inject a fully-formed EndpointHandler\n   * into the initial pipeline, which requires an EndpointConnection object.\n   * Unfortunately, this object is created by the caller only after the\n   * connect has completed.\n   */\n  import pool.service.context\n  def connect(): Future[Channel] =\n    Option(available.poll()) match\n    {\n      case None =>\n        logger.info(\"Initializing fresh endpoint\")\n        Metrics.PooledCreated.mark()\n        initializer.connect()\n\n      case Some(endpoint) if (endpoint.isAvailable()) =>\n        logger.info(\"Assigning existing endpoint\")\n        Metrics.PooledAssigned.mark()\n        Future.successful(endpoint)\n\n      case Some(endpoint) if (endpoint.isActive()) =>\n        logger.info(\"Existing endpoint unavailable (inconsistent state); initializing fresh endpoint\")\n        Metrics.PooledRecreated.mark()\n        endpoint.close()\n        connect()\n\n      case Some(endpoint) =>\n        logger.info(\"Existing endpoint inactive; initializing fresh endpoint\")\n        Metrics.PooledRecreated.mark()\n        endpoint.close()\n        connect()\n    }\n\n\n  /**\n   * Make the connection attempt, using pooled-connection support.\n   *\n   * The challenge here is that we want to inject a fully-formed EndpointHandler\n   * into the initial pipeline, which requires an EndpointConnection object.\n   * Unfortunately, this object is created by the caller only after the\n   * connect has completed.\n   *\n   * TODO convert to cached InetAddress to speed up resolution\n   */\n  def resolve(): Future[Channel] = {\n    // Attempt to resolve, rejecting inactive/closed connections\n    // Do we need to explicitly clean them up?\n    val channel = connect()\n\n    // Cache the channel's allocation\n    channel andThen {\n      case Success(channel) =>\n        require(!allocated.contains(channel.id), s\"Attempt to assign an already-allocated endpoint $channel\")\n        allocated(channel.id) = channel\n        channel.statistics.allocations.incrementAndGet()\n\n      case Failure(ex) =>\n        Metrics.PooledFailed += 1\n        logger.info(\"Channel connection failed with {}\", ex.getMessage)\n    }\n  }\n\n\n  /**\n   * Release the allocated endpoint back to the 'allocated' list.\n   *\n   * TODO support closing connection if too many cached\n   * TODO Retire overused connections\n   */\n  def release(channel: Channel) =\n    // Try and resolve existing\n    allocated remove(channel.id) match\n    {\n      case Some(ch) if (channel.isReusable()) =>\n        // We want to make sure this is a valid; push through an empty write to force any pending closes\n        channel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(this)\n\n      case Some(ch) if (channel.isActive()) =>\n        // Downstream channel has requested a close (ie: keep-alive false). Close and release now.\n        channel.close()\n        Metrics.PooledClosed += 1\n        logger.info(\"Releasing and closing endpoint {}\", channel)\n\n      case Some(ch) =>\n        Metrics.PooledClosed += 1\n        logger.info(\"Releasing closed endpoint {}\", channel)\n\n      case None =>\n        metrics.counter(MetricsKey.PoolReleased,  \"mismatch\") += 1\n        logger.warn(\"Attempted to return a channel which was not allocated: {}\", channel.toStringExt)\n    }\n\n\n  /**\n   * Handle retry/release priming operations.\n   * @param future\n   */\n  override def operationComplete(future: ChannelFuture): Unit =\n    if (future.isSuccess) {\n      Metrics.PooledRetained += 1\n      available.push(future.channel)\n      logger.info(\"Releasing endpoint {}\", future.channel)\n    }\n    else {\n      Metrics.PooledClosed += 1\n      logger.info(\"Flush failed; closing endpoint {}\", future.channel)\n    }\n\n\n\n  // TODO - implement correct/custom shutdown behavior\n  def shutdown() = {}\n\n}\n\n\n/**\n * Downstream connection initializer.\n *\n * @param node\n */\nclass NeutrinoNodeInitializer(node: NeutrinoNode)\n  extends ChannelInitializer[SocketChannel]\n  with ChannelFutureListener\n  with Instrumented\n{\n  import com.ebay.neutrino.metrics.Metrics._\n  import com.ebay.neutrino.util.AttributeSupport._\n  import com.ebay.neutrino.util.Utilities._\n\n  // Customized timeouts for server-clients; use only the channel-specific values and ensure a\n  // valid connection-timeout\n  val timeouts = {\n    val defaults = node.pool.settings.timeouts\n    val connect  = defaults.connectionTimeout\n\n    defaults.copy(\n      requestCompletion = Duration.Undefined,\n      sessionCompletion = Duration.Undefined,\n      connectionTimeout = if (connect.isFinite) connect else TimeoutSettings.Default.connectionTimeout\n    )\n  }\n\n  val bootstrap = new Bootstrap()\n    .channel(classOf[NioSocketChannel])\n    .group(node.pool.service.core.workers)\n    .option[java.lang.Boolean](ChannelOption.TCP_NODELAY, true)\n    .option[java.lang.Integer](ChannelOption.CONNECT_TIMEOUT_MILLIS, timeouts.connectionTimeout.toMillis.toInt)\n    .handler(this)\n\n  // Static handlers\n  val timeout = new ChannelTimeoutHandler(timeouts)\n  val stats   = new ChannelStatisticsHandler(false)\n  val audit   = new NeutrinoAuditHandler()\n\n\n  // Framing decoders; extract initial HTTP connect event from HTTP stream\n  protected override def initChannel(channel: SocketChannel): Unit = {\n    val client = new NeutrinoClientHandler()\n\n    // Configure our codecs\n    channel.pipeline.addLast(timeout)\n    channel.pipeline.addLast(stats)\n    channel.pipeline.addLast(new HttpRequestEncoder())\n    channel.pipeline.addLast(new NeutrinoClientDecoder(client.queue))\n    channel.pipeline.addLast(audit)\n    channel.pipeline.addLast(client)\n\n    // Update our downstream metrics\n    Metrics.DownstreamTotal.mark\n    Metrics.DownstreamOpen += 1\n\n    // Prime the statistics object and hook channel close for proper cleanup\n    channel.statistics\n    channel.closeFuture().addListener(this)\n  }\n\n\n  /**\n   * Connect to the downstream.\n   * @return\n   */\n  def connect() = bootstrap.connect(node.settings.socketAddress).future\n\n  /**\n   * Customized channel-close listener for Neutrino IO channels.\n   * Capture and output IO data.\n   */\n  override def operationComplete(future: ChannelFuture) = {\n    DownstreamOpen -= 1\n  }\n}\n\n\n/**\n * Implementation of a state wrapper around a NeutrinoNode\n * (and its contained VirtualServer settings).\n *\n * We implement state in a TrieMap to provide concurrent access support.\n * An alternative would be to restrict access to this class and mediate concurrent\n * access externally.\n */\nclass NeutrinoNodes(pool: NeutrinoPool)\n  extends DifferentialStateSupport[String, VirtualServer] with StrictLogging\n{\n  // Cache our nodes here (it should make it easier to cleanup on lifecycle here)\n  val nodes = new TrieMap[String, NeutrinoNode]()\n\n  // Extract just the node-values, in read-only mode\n  def apply() = nodes.readOnlySnapshot.values\n\n  // Required methods\n  override protected def key(v: VirtualServer): String = v.id\n\n  override protected def addState(settings: VirtualServer) =\n    nodes put (key(settings), new NeutrinoNode(pool, settings))\n\n  override protected def removeState(settings: VirtualServer) =\n    nodes remove(key(settings)) map { node => node.shutdown() }\n\n  // Update the server's status\n  override protected def updateState(pre: VirtualServer, post: VirtualServer) =\n    nodes get (key(pre)) match {\n      case Some(node) => node.update(post)\n      case None => logger.warn(\"Unable to resolve a node for key {}\", key(pre).toString)\n    }\n\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/NeutrinoPool.scala",
    "content": "package com.ebay.neutrino\n\nimport java.net.NoRouteToHostException\n\nimport com.ebay.neutrino.balancer.Balancer\nimport com.ebay.neutrino.channel.{NeutrinoSession, NeutrinoService}\nimport com.ebay.neutrino.config.{Transport, VirtualPool}\nimport com.ebay.neutrino.util.DifferentialStateSupport\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.channel.Channel\n\nimport scala.collection.concurrent.TrieMap\nimport scala.concurrent.Future\n\n/**\n * An extremely simple shim between a pool 'implementation' and the underlying\n * configuration as imported from our settings.\n *\n * Good model for this...\n * @see https://github.com/twitter/commons/tree/master/src/java/com/twitter/common/net/loadbalancing\n *\n *\n * State management:\n * - This object requires a set of 'default' settings, both mutable and immutable\n *\n * - A caller can update the mutable settings using the update() method, which will\n *   replace the current settings.\n *\n * - The following properties are immutable and attempts to update will cause an\n *   exception:\n *    - id\n *    - protocol\n *    - balancer settings (in particular, balancer-type currently can't be changed)\n *\n * - The following properties are mutable and may be changed with update()\n *    - servers\n *    - addresses\n *    - others\n */\nclass NeutrinoPool(val service: NeutrinoService, defaultSettings: VirtualPool) extends StrictLogging\n{\n  import com.ebay.neutrino.util.AttributeSupport._\n  import service.context\n\n  // Active settings; prevent external change\n  private var currentSettings = defaultSettings\n\n  // Create our load-balancing algorithm for this pool\n  val balancer = Balancer(settings.balancer)\n\n  // Cache our nodes here (it should make it easier to cleanup on lifecycle here)\n  val nodes = new NeutrinoNodes(this)\n\n  // Complete initialization of settings\n  update(defaultSettings)\n\n\n  // Settings accessor; prevent callers from changing the active-settings\n  def settings: VirtualPool = currentSettings\n\n  /**\n   * HTTP EndpointResolver support\n   * Register a new incoming request, and attempt to aøllocate a new peer endpoint\n   *\n   * TODO resolve an endpoint-service for this connect...\n   * TODO implement load-balancing ...\n   */\n  def resolve(request: NeutrinoRequest): Future[Channel] =\n  {\n    def failed()  = Future.failed(new NoRouteToHostException(s\"Unable to connect to ${request.uri}\"))\n    val assigned  = balancer.assign(request)\n    val allocated = assigned map (node => node.resolve map ((node, _)))\n    val channel   = allocated getOrElse failed()\n\n    // On success, store our components in the request\n    channel map { case (node, channel) =>\n      request.node = node\n      request.balancer = balancer\n      request.downstream = channel\n      channel.session = request.session\n      channel\n    }\n  }\n\n  def release(request: NeutrinoRequest) = {\n    require(request.balancer.isEmpty || request.balancer.get == balancer, \"Request balancer belongs to different pool\")\n\n    (request.node, request.downstream) match {\n      case (Some(node), Some(channel)) =>\n        logger.info(\"Response completed for {} - releasing downstream.\", request)\n\n        // Clear out downstream and request\n        channel.session = None\n        request.node = None\n        request.balancer = None\n        request.downstream = None\n\n        // Release node first (balancer release can trigger additional pull from node)\n        node.release(channel)\n        balancer.release(request, node)\n\n      case (None, None) =>\n        // Channel/Node was never allocated (connection was never successful). Nothing to return.\n\n      case (node, channel) =>\n        logger.warn(\"Attempted to release a request with invalid node and/or downstream channel set: {}, {}\", node, channel)\n    }\n  }\n\n\n  /**\n   * Update the pool's settings with the provided.\n   */\n  def update(settings: VirtualPool) =\n  {\n    // Validate incoming settings against our default\n    require(settings.id == defaultSettings.id)\n    require(settings.protocol == defaultSettings.protocol)\n    require(settings.balancer == defaultSettings.balancer)\n\n    // Update the cached settings\n    currentSettings = settings\n\n    // Diff the current nodes and updated\n    nodes.update(settings.servers:_*)\n\n    // After everything is updated, push the new config to the load-balancer\n    // TODO can this call be more efficient? We already know the size\n    balancer.rebuild(nodes().toArray)\n  }\n\n\n  /**\n   * Handle pool shutdown; peform required quiescence.\n   * TODO; make better\n   */\n  def shutdown() = {}\n\n\n  /**\n   *\n   */\n  override def toString() = super.toString()\n}\n\n\ncase class NeutrinoPoolId(id: String, transport: Transport) {\n  require(!id.contains(\"::\"), \"ID should not contain the :: characters\")\n  override val toString = id+\"::\"+transport\n}\n\nobject NeutrinoPoolId {\n\n  // Create a neutrino pool ID out of a serialized string representation\n  def apply(id: String): NeutrinoPoolId =\n    id.split(\"::\") match {\n      case Array(poolid, trans) => NeutrinoPoolId(poolid, Transport(trans))\n      case _ => throw new IllegalArgumentException(s\"Unable to extract NeutrinoPoolId from $id\")\n    }\n\n  // Create a neutrino pool ID out of id/session\n  def apply(id: String, session: NeutrinoSession): NeutrinoPoolId =\n    NeutrinoPoolId(id, session.service.settings.protocol)\n\n}\n\n\n/**\n * Implementation of a state wrapper around a NeutrinoPool\n * (and its contained VirtualPool settings).\n *\n * We implement state in a TrieMap to provide concurrent access support.\n * An alternative would be to restrict access to this class and mediate concurrent\n * access externally.\n */\nclass NeutrinoPools(val service: NeutrinoService)\n  extends DifferentialStateSupport[NeutrinoPoolId, VirtualPool]\n  with Iterable[VirtualPool]\n  with StrictLogging\n{\n  val pools = new TrieMap[NeutrinoPoolId, NeutrinoPool]()\n\n\n  // Required methods\n  @inline override protected def key(pool: VirtualPool): NeutrinoPoolId =\n    NeutrinoPoolId(pool.id, pool.protocol)\n\n  override protected def addState(settings: VirtualPool) = {\n    // Initialize the startup nodes and configure the balancer\n    val pool = new NeutrinoPool(service, settings)\n    logger.info(\"Configuring new pool {}\", pool)\n    pools.put(key(settings), pool)\n  }\n\n  override protected def removeState(settings: VirtualPool) =\n    pools.remove(key(settings)) map { pool =>\n      logger.info(\"Removing existing pool {}\", pool)\n      pool.shutdown()\n    }\n\n  override protected def updateState(pre: VirtualPool, post: VirtualPool) = {\n    pools.get(key(pre)) match {\n      case Some(pool) =>\n        logger.info(\"Updating existing pool {}\", pool)\n        pool.update(post)\n\n      case None =>\n        // Pool was removed in the time between calculation of the state and this\n        logger.error(\"Error upsting existing pool {} - was removed concurrently.\", pre.id)\n    }\n  }\n\n\n  /**\n   * Public accessor for NeutrinoPool.\n   * Callers should use iterator() unless the underlying NeutrinoPools are required.\n   * This is immutable so changes to the underlying collection will not be visible.\n   */\n  def apply() = pools.readOnlySnapshot().values\n\n  /**\n   * Public accessor for VirtualPool settings.\n   * This is immutable so changes to the underlying collection will not be visible.\n   */\n  override def iterator: Iterator[VirtualPool] =\n    pools.readOnlySnapshot().values.iterator map (_.settings)\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/NeutrinoPoolResolver.scala",
    "content": "package com.ebay.neutrino\n\nimport com.ebay.neutrino.balancer.{L7AddressResolver, CNameWildcardResolver, CNameResolver}\nimport com.typesafe.scalalogging.slf4j.StrictLogging\n\n\n/**\n * A plugin interface for Pool resolution.\n *\n * This provides a way to configure default pool resolution for incoming request processing.\n *\n * Some common variants are:\n * - Default pool for interface\n * - First configured pool\n * - By request name\n */\ntrait PoolResolver {\n\n  /**\n   * Attempt to resolve a pool using this request.\n   *\n   * @param pools the source pool-set to resolve on\n   * @param request the source of request\n   * @return a valid pool, or None if unable to resolve\n   */\n  def resolve(pools: NeutrinoPools, request: NeutrinoRequest): Option[NeutrinoPool]\n}\n\n\nobject PoolResolver {\n  /**\n   * Create a new PoolResolver based on the type/description provided.\n   *\n   * @return a valid resolver, or Unavailable if not provided\n   */\n  def apply(name: String): PoolResolver =\n    name.toLowerCase match {\n      case \"none\" | \"\" => NoResolver\n      case \"default\" => DefaultResolver\n      case \"cname\"   => new CNameResolver\n      case \"layerseven\"   => new L7AddressResolver\n      case classname => Class.forName(name).newInstance().asInstanceOf[PoolResolver]\n    }\n}\n\n\n// Supported resolvers\n//\nobject DefaultResolver extends NamedResolver(\"default\")\n\n\n/**\n * Resolve the provided pool\n */\ncase class StaticResolver(pool: NeutrinoPool) extends PoolResolver {\n  // Attempt to resolve a pool using this request.\n  override def resolve(pools: NeutrinoPools, request: NeutrinoRequest): Option[NeutrinoPool] = Option(pool)\n}\n\n/**\n * No-op pool resolver; this will never resolve a pool.\n */\nobject NoResolver extends PoolResolver {\n  // Attempt to resolve a pool using this request.\n  override def resolve(pools: NeutrinoPools, request: NeutrinoRequest): Option[NeutrinoPool] = None\n}\n\n\n/**\n * Resolve a pool by its ID (and transport).\n */\nclass NamedResolver(poolname: String) extends PoolResolver {\n\n  // Attempt to resolve a pool using this request.\n  override def resolve(pools: NeutrinoPools, request: NeutrinoRequest): Option[NeutrinoPool] =\n    pools.pools get (NeutrinoPoolId(poolname, request.session))\n}\n\nobject NamedResolver extends StrictLogging {\n\n  // Retrieve the pool by name/id.\n  def get(pools: NeutrinoPools, poolid: String): Option[NeutrinoPool] =\n    pools() find (_.settings.id == poolid)\n\n  // Retrieve the pool for this request, by poolid\n  def get(request: NeutrinoRequest, poolid: String): Option[NeutrinoPool] =\n    get(request.session.service.pools, poolid)\n}\n"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/NeutrinoRequest.scala",
    "content": "package com.ebay.neutrino\n\nimport java.net.UnknownServiceException\nimport java.util.concurrent.TimeUnit\n\nimport com.ebay.neutrino.channel.NeutrinoSession\nimport com.ebay.neutrino.config.{CompletionStatus, Host}\nimport com.ebay.neutrino.metrics.Metrics\nimport com.ebay.neutrino.util.AttributeClassMap\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.buffer.ByteBuf\nimport io.netty.channel.{Channel, ChannelFuture}\nimport io.netty.handler.codec.DecoderResult\nimport io.netty.handler.codec.http._\nimport io.netty.util.ReferenceCounted\nimport io.netty.util.concurrent.{Future => NFuture, GenericFutureListener}\n\nimport scala.concurrent.Future\nimport scala.concurrent.duration._\nimport scala.util.Try\n\n\n/**\n * Connection specifics.\n * HttpRequest-specific fields separated from the connection.\n *\n * This class is responsible for maintaining:\n * - the high-level request/response connection (ie: connected endpoints)\n * - the low-level state (ie: negotiated state, options)\n *\n *\n * Note that request-handling within this object is not immutable; making changes to\n * the enclosed request may/may not apply changes to the underlying request object.\n *\n * In deference to heap management, we elect to not make a copy on initial use.\n *\n * TODO also, HTTP Spec supposed to parse connection:: token and remove all matching connection-token headers\n */\nclass NeutrinoRequest(val session: NeutrinoSession, private[neutrino] val delegate: HttpRequest)\n  extends AttributeClassMap\n  with CachedPoolSupport\n  with HttpRequest\n  with GenericFutureListener[ChannelFuture]\n  with StrictLogging\n{\n  import com.ebay.neutrino.util.AttributeSupport._\n  import com.ebay.neutrino.util.HttpRequestUtils._\n\n  // Cached pool; we cache it here so it's not exposed to cross-request\n  val start = System.currentTimeMillis()\n  val pool  = new PoolCache()\n  val requestUri = delegate.URI()\n  val requestKeepalive = HttpHeaderUtil.isKeepAlive(delegate)\n\n  // Deferred resolvers\n  lazy val host: Option[Host] = delegate.host(requestUri)\n\n  // Cached response-data\n  var response: Option[HttpResponse] = None\n\n\n  /** Request completion future support.\n    *\n    */\n  private val completePromise = session.channel.newPromise().addListener(this)\n\n  def addListener(listener: GenericFutureListener[_ <: NFuture[_ >: Void]]) =\n    completePromise.addListener(listener)\n\n  def addListeners(listeners: GenericFutureListener[_ <: NFuture[_ >: Void]]*) =\n    completePromise.addListeners(listeners:_*)\n\n\n  /**\n   * Process the request-completion.\n   * @param future\n   */\n  override def operationComplete(future: ChannelFuture): Unit =\n  {\n    // Connection Pool/Node release\n    pool.release()\n\n    val micros = elapsed.toMicros\n    Metrics.RequestsOpen.dec()\n    Metrics.RequestsCompleted.update(micros, TimeUnit.MICROSECONDS)\n    Metrics.RequestsCompletedType(CompletionStatus(response)).update(micros, TimeUnit.MICROSECONDS)\n\n    logger.info(\"Request completed: {}\", this)\n  }\n\n  /**\n   * Register a new incoming request, and attempt to allocate a new peer endpoint.\n   * If not established, or force is provided, renegotiate the endpoint.\n   *\n   * Since we can't assume that a connect attempt will be successful or will fail,\n   * we actually need to defer the connection-setup to the future success.\n   * In the short-term, however, we want to preserve the same deterministic method\n   * return value, so we'll use the future but wait on the response (which is a\n   * SORT OF BAD THING)\n   *\n   * TODO convert the return type to a Future[EndpointConnection]\n   * TODO convert the whole typing to Scala-specific typing\n   */\n  def connect() = {\n    // Ensure we're not already-established\n    require(this.downstream.isEmpty, \"Currently, downstream is reestablished for each request. Shouldn't be already set\")\n\n    // Store pool selection (we'll need to return the node back to the selected pool)\n    pool() match {\n      case None =>\n        Future.failed(new UnknownServiceException(s\"Unable to resolve pool for request.\"))\n\n      case Some(pool) =>\n        // If not established, or force is provided, renegotiate the endpoint\n        // Grab the connection's current pool, and see if we have a resolver available\n        // If downstream not available, attempt to connect one here\n        logger.info(\"Establishing downstream to {}\", pool.settings.id)\n        pool.resolve(this)\n    }\n  }\n\n  /**\n   * Close the outstanding request.\n   *\n   * This is intended to be idempotic; it can be called more than once and should\n   * guarantee that the close-events are only executed once.\n   */\n  def complete() =\n    completePromise.synchronized {\n      // Complete the request promise\n      if (!completePromise.isDone) completePromise.setSuccess\n    }\n\n\n  // Calculate elapsed duration of the current request\n  def elapsed = (System.currentTimeMillis-start) millis\n\n\n  /** Delegate HttpRequest methods\n    *\n    */\n  @Deprecated\n  def getMethod(): HttpMethod = delegate.method()\n  def method(): HttpMethod = delegate.method()\n  def setMethod(method: HttpMethod): HttpRequest = { delegate.setMethod(method); this }\n\n  @Deprecated\n  def getUri(): String = delegate.uri()\n  def uri(): String = delegate.uri()\n  def setUri(uri: String): HttpRequest = { delegate.setUri(uri); this }\n\n  @Deprecated\n  def getProtocolVersion(): HttpVersion = delegate.protocolVersion()\n  def protocolVersion(): HttpVersion = delegate.protocolVersion()\n  def setProtocolVersion(version: HttpVersion): HttpRequest = { delegate.setProtocolVersion(version); this }\n\n  @Deprecated\n  def getDecoderResult(): DecoderResult = delegate.decoderResult()\n  def decoderResult(): DecoderResult = delegate.decoderResult()\n  def setDecoderResult(result: DecoderResult): Unit = delegate.setDecoderResult(result)\n\n  def headers(): HttpHeaders = delegate.headers()\n}\n\n\n/**\n * Neutrino Pipeline only: endpoint pool for request.\n *\n * Note that this class is not synchronized; callers should provide their own\n * synchronization as required.\n */\nsealed trait CachedPoolSupport { self: NeutrinoRequest =>\n  import com.ebay.neutrino.util.AttributeSupport.RequestAttributeSupport\n\n\n  class PoolCache {\n\n    // Cached pool-resolver; this supports the notion of a per-request resolver\n    private var pool: Option[NeutrinoPool] = None\n\n    // Is the pool current cached?\n    def isEmpty() = pool.isEmpty\n\n    /** Delegate the resolve() request to the connection's pools */\n    def resolve() = session.service.resolvePool(self)\n\n    // Return the cached pool, resolving if necessary\n    def apply(): Option[NeutrinoPool] = {\n      if (pool.isEmpty) pool = resolve()\n      pool\n    }\n\n    // Set pool by name\n    def set(poolname: String): Option[NeutrinoPool] = {\n      // If replacing exisiting pool, we need to release any outstanding connections to it's node\n      release()\n\n      // Attempt to set a pool with the name provided\n      pool = Option(poolname) flatMap (NamedResolver.get(self, _))\n      pool\n    }\n\n    // Release the endpoint.\n    // Note that we don't actually need to return to the right pool; it'll take care of getting\n    // it to the right place.\n    def release(clearPool: Boolean=true) = {\n      require(self.asInstanceOf[NeutrinoRequest].downstream.isEmpty || pool.isDefined, \"If downstream is set, must have a valid pool\")\n      pool map (_.release(self))\n      if (clearPool) pool = None\n    }\n  }\n}\n\n\n/**\n * Static request methods.\n *\n */\nobject NeutrinoRequest extends StrictLogging {\n\n  import com.ebay.neutrino.util.AttributeSupport._\n\n\n  def apply(channel: Channel, request: HttpRequest): NeutrinoRequest = {\n    // Grab the session out of the channel's attributes\n    val session = channel.session.get\n\n    request match {\n      case request: FullHttpRequest  => new NeutrinoRequest(session, request) with NeutrinoFullRequest\n      case request: LastHttpContent  => new NeutrinoRequest(session, request) with NeutrinoLastHttpContent\n      case request: ReferenceCounted => new NeutrinoRequest(session, request) with NeutrinoReferenceCounted\n      case request                   => new NeutrinoRequest(session, request)\n    }\n  }\n\n\n  /**\n   * Create the connection-object and attempt to associate it with a valid pool.\n   * @param channel\n   * @param httprequest\n   */\n  def create(channel: Channel, httprequest: HttpRequest): Try[NeutrinoRequest] = {\n    // Attempt to create request (catching any URI format exceptions)\n    val request = Try(NeutrinoRequest(channel, httprequest))\n    val statistics = channel.statistics\n\n    // Track request-session statistics\n    request map { request =>\n      // Update our metrics\n      Metrics.RequestsOpen.inc()\n      Metrics.RequestsTotal.mark()\n\n      // Update session metrics\n      if (statistics.requestCount == 0) {\n        Metrics.SessionActive += 1\n        Metrics.SessionTotal.mark\n        logger.info(\"Starting user-session {}\", this)\n      }\n\n      // Resolve by host (if available) or fallback to the incoming VIP's default pool\n      // Register the request-level connection entity on the new channel with the Balancer\n      statistics.requestCount.incrementAndGet()\n    }\n\n    request\n  }\n\n\n  trait NeutrinoReferenceCounted extends ReferenceCounted { self: NeutrinoRequest =>\n    @inline val typed = delegate.asInstanceOf[ReferenceCounted]\n    override def refCnt(): Int = typed.refCnt()\n    override def retain(): ReferenceCounted = typed.retain()\n    override def retain(increment: Int): ReferenceCounted = typed.retain(increment)\n    override def touch(): ReferenceCounted = typed.touch()\n    override def touch(hint: AnyRef): ReferenceCounted = typed.touch(hint)\n    override def release(): Boolean = typed.release()\n    override def release(decrement: Int): Boolean = typed.release(decrement)\n  }\n\n  trait NeutrinoLastHttpContent extends NeutrinoReferenceCounted with LastHttpContent { self: NeutrinoRequest =>\n    @inline override val typed = delegate.asInstanceOf[LastHttpContent]\n    override def content(): ByteBuf = typed.content()\n    override def copy(): LastHttpContent = typed.copy()\n    override def duplicate(): LastHttpContent = typed.duplicate()\n    override def retain(): LastHttpContent = typed.retain()\n    override def retain(increment: Int): LastHttpContent = typed.retain(increment)\n    override def touch(): LastHttpContent = typed.touch()\n    override def touch(hint: AnyRef): LastHttpContent = typed.touch(hint)\n    override def trailingHeaders(): HttpHeaders = typed.trailingHeaders()\n  }\n\n  trait NeutrinoFullRequest extends NeutrinoLastHttpContent with FullHttpRequest { self: NeutrinoRequest =>\n    @inline override val typed = delegate.asInstanceOf[FullHttpRequest]\n    override def copy(content: ByteBuf): FullHttpRequest = typed.copy(content)\n    override def copy(): FullHttpRequest = typed.copy()\n    override def retain(increment: Int): FullHttpRequest = typed.retain(increment)\n    override def retain(): FullHttpRequest = typed.retain()\n    override def touch(): FullHttpRequest = typed.touch()\n    override def touch(hint: AnyRef): FullHttpRequest = typed.touch(hint)\n    override def duplicate(): FullHttpRequest = typed.duplicate()\n    override def setProtocolVersion(version: HttpVersion): FullHttpRequest = typed.setProtocolVersion(version)\n    override def setMethod(method: HttpMethod): FullHttpRequest = typed.setMethod(method)\n    override def setUri(uri: String): FullHttpRequest = typed.setUri(uri)\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/SLB.scala",
    "content": "package com.ebay.neutrino\n\n/**\n * Created by blpaul on 11/13/2015.\n */\n\nimport com.ebay.neutrino.api.SLBApi\nimport com.ebay.neutrino.config.{Configuration, LoadBalancer}\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport akka.actor.{ActorSystem, Props}\nimport com.ebay.neutrino.cluster.SLBLoader\nimport com.ebay.neutrino.cluster.SystemConfiguration\n\nimport scala.concurrent.Future\n\n/**\n * Create an HTTP SLB application\n *\n *\n */\nclass SLB(system: ActorSystem) extends StrictLogging\n{\n  // Start a configuration-loader\n  val config   = SystemConfiguration(system)\n  val api      = system.actorOf(Props(classOf[SLBApi]), \"api\")\n  val reloader = system.actorOf(Props(classOf[SLBLoader]), \"loader\")\n\n\n  // Start running. This will run until the process is interrupted or stop is called\n  def start(): Future[_] = {\n    logger.warn(\"Starting SLB...\")\n    config.core.start()\n  }\n\n  def shutdown(): Future[_] = {\n    logger.info(s\"Stopping SLB Service\")\n    system.shutdown()\n    config.core.shutdown()\n  }\n}\n\n\nobject SLB extends StrictLogging {\n\n  /**\n   * Initialize a new SLB instance, using the configuration file provided.\n   *\n   * @param filename\n   * @return\n   */\n  def apply(filename: String = \"/etc/neutrino/slb.conf\"): SLB =\n    new SLB(SystemConfiguration.system(filename))\n\n\n  def main(args: Array[String]): Unit = {\n    // If running as a stand-alone application, start\n    //new SLBLifecycle().start(args)\n    SLB().start()\n  }\n}\n\n\n"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/api/ApiData.scala",
    "content": "package com.ebay.neutrino.api\n\nimport java.util.Date\n\n\nimport com.ebay.neutrino.config.CompletionStatus\nimport com.ebay.neutrino.metrics.Metrics\nimport spray.json.DefaultJsonProtocol\n\n\ntrait ApiData {\n\n  import com.ebay.neutrino.api.ApiData._\n\n  // Faked start time (should be moved to core)\n  val apiStartTime = new Date()\n\n\n  def generateStatus(): ApiData.Status = {\n    import Metrics._\n    import nl.grons.metrics.scala.{Timer => TimerData}\n\n    def toTimer(data: TimerData) = {\n      val snapshot = data.snapshot\n      Timer(\n        data.count,\n        snapshot.getMin,\n        snapshot.getMax,\n        snapshot.getMean,\n        snapshot.get95thPercentile,\n        data.oneMinuteRate,\n        data.fiveMinuteRate,\n        data.fifteenMinuteRate\n      )\n    }\n\n\n    Status(\n      HostInfo(\n        \"localhost\",\n        \"127.0.0.1\",\n        apiStartTime.toString\n      ),\n      Traffic(\n        UpstreamBytesRead.count, UpstreamBytesWrite.count, UpstreamPacketsRead.count, UpstreamPacketsWrite.count, UpstreamOpen.count, UpstreamTotal.count\n      ),\n      Traffic(\n        DownstreamBytesRead.count, DownstreamBytesWrite.count, DownstreamPacketsRead.count, DownstreamPacketsWrite.count, DownstreamOpen.count, DownstreamTotal.count\n      ),\n      Requests(     // Sessions\n        SessionActive.count,\n        toTimer(Metrics.SessionDuration)\n      ),\n      Requests(     // Requests\n        RequestsOpen.count,\n        toTimer(Metrics.RequestsCompleted)\n      ),\n      Seq(\n        Responses(\"2xx\", toTimer(Metrics.RequestsCompletedType(CompletionStatus.Status2xx))),\n        Responses(\"4xx\", toTimer(Metrics.RequestsCompletedType(CompletionStatus.Status4xx))),\n        Responses(\"5xx\", toTimer(Metrics.RequestsCompletedType(CompletionStatus.Status5xx))),\n        Responses(\"incomplete\", toTimer(Metrics.RequestsCompletedType(CompletionStatus.Incomplete))),\n        Responses(\"other\", toTimer(Metrics.RequestsCompletedType(CompletionStatus.Other)))\n      )\n    )\n  }\n}\n\n\nobject ApiData {\n\n  // Supported metric types\n  sealed trait MetricInt\n  case class Metric(key: String, `type`: String, values: MetricInt)\n  case class Counter(count: Long) extends MetricInt\n  case class Timer(count: Long, min: Long, max: Long, mean: Double, `95th`: Double, `1min`: Double, `5min`: Double, `15min`: Double) extends MetricInt\n  case class Meter(count: Long) extends MetricInt\n\n  case class HostInfo(hostname: String, address: String, lastRestart: String)\n  case class Traffic(bytesIn: Long, bytesOut: Long, packetsIn: Long, packetsOut: Long, currentConnections: Long, totalConnections: Long)\n  //case class RequestStats(active: Long, total: Long, minElapsed: Long, avgElapsed: Long, lastRate: Long)\n  case class Requests(active: Long, stats: Timer)\n  case class Responses(responseType: String, stats: Timer)\n\n  case class Status(host: HostInfo, upstreamTraffic: Traffic, downstreamTraffic: Traffic, sessions: Requests, requests: Requests, responses: Seq[Responses])\n\n\n\n  object JsonImplicits extends DefaultJsonProtocol {\n    implicit val timerJson        = jsonFormat8(Timer)\n    implicit val hostinfoJson     = jsonFormat3(HostInfo)\n    implicit val trafficJson      = jsonFormat6(Traffic)\n    implicit val requestsJson     = jsonFormat2(Requests)\n    implicit val responsesJson    = jsonFormat2(Responses)\n    implicit val statusJson       = jsonFormat6(Status)\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/api/SLBApi.scala",
    "content": "package com.ebay.neutrino.api\n\nimport akka.actor.{Actor, ActorSystem, Props}\nimport com.ebay.neutrino.metrics.Instrumented\nimport com.ebay.neutrino.www.WebService\nimport com.ebay.neutrino.www.ui.ResourceServices\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport spray.routing.SimpleRoutingApp\nimport com.ebay.neutrino.cluster.SystemConfiguration\nimport scala.util.{Failure, Success}\nimport scala.concurrent.duration._\nimport akka.util.Timeout\n\n\nclass SLBApi\n  extends Actor\n  with SimpleRoutingApp with ResourceServices with WebService\n  with StrictLogging\n  with Instrumented\n{\n\n  implicit override val system = context.system\n  // Set the timeout for starting the api server\n  implicit val timeout: Timeout = 30 seconds\n\n  import context.dispatcher\n  val config = SystemConfiguration(system)\n\n  // Our web-application routes\n  def routes =  resourceRoutes ~ webRoutes\n\n  if (config.settings.enableApi) {\n    // TODO pull these from configuration\n    val host = \"0.0.0.0\"\n    val port = 8079\n\n    logger.info(\"Starting API on port \")\n\n    //val simpleCache = routeCache(maxCapacity = 1000, timeToIdle = Duration(\"30 min\"))\n    startServer(interface = host, port = port)(routes) onComplete {\n      case Success(b) =>\n        println(s\"Successfully bound to ${b.localAddress}\")\n      case Failure(ex) =>\n        println(ex.getMessage)\n    }\n  }\n\n\n  def receive: Receive = {\n\n    case msg =>\n      logger.warn(\"Unexpected message received: {}\", msg.toString)\n  }\n\n\n}\n\n\n/**\n * Standalone app wrapper around the SLB API.\n */\nobject SLBApi extends App {\n\n  ActorSystem(\"slb-api\").actorOf(Props(classOf[SLBApi]))\n\n}\n"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/balancer/Balancer.scala",
    "content": "package com.ebay.neutrino.balancer\n\nimport com.ebay.neutrino.{NeutrinoRequest, NeutrinoNode}\nimport com.ebay.neutrino.config.BalancerSettings\n\n\ntrait Balancer {\n\n/*\n  // Expose our scheduler's endpoint-statistics\n  def statistics: Traversable[(ChannelId, EndpointStatistics)]\n\n  // Update the status of an endpoint on completion of a response\n  def update(capacity: Capacity)\n\n  // Add a new endpoint to our set\n  def register(endpoint: ChannelId)\n\n  // Remove an endpoint from our set\n  def remove(endpoint: ChannelId)\n*/\n\n\n  // Re(set) the current membership of the load-balancer\n  def rebuild(members: Array[NeutrinoNode])\n\n  // Assign an endpoint for request processing\n  def assign(request: NeutrinoRequest): Option[NeutrinoNode]\n\n  // Release an endpoint from request processing\n  def release(request: NeutrinoRequest, node: NeutrinoNode)\n}\n\n/**\n *\n * TODO - add agent support for synchronous read/asynchronous update\n *\n * Ensure class are available in the system\n *  import scala.reflect.runtime.{universe => ru}\n *  lazy val mirror = ru.runtimeMirror(classOf[Scheduler].getClassLoader)\n *  val schedulerType = mirror.classSymbol(schedulerClass).toType\n *  val scheduler = mirror.create(schedulerType)\n */\nobject Balancer {\n\n  import com.ebay.neutrino.NeutrinoLifecycle._\n\n  /**\n   * Select a load-selection mechanism.\n   * @param settings\n   */\n  def apply(settings: BalancerSettings): Balancer =\n    // Resolve a constructor\n    settings.config match {\n      case Some(config) => (create[Balancer](settings.clazz, config) orElse create[Balancer](settings.clazz)).get\n      case None         => (create[Balancer](settings.clazz)).get\n    }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/balancer/BalancerNodes.scala",
    "content": "package com.ebay.neutrino.balancer\n\nimport com.ebay.neutrino.NeutrinoNode\nimport com.ebay.neutrino.config.HealthState\n\n\nclass BalancerNodes[T] {\n\n  import com.ebay.neutrino.balancer.BalancerNodes._\n\n\n  // Only requires synchronization on structural changes.\n  private var servers = Map[NeutrinoNode, T]()\n\n  // Set the values\n  def set(members: Array[NeutrinoNode], creator: NeutrinoNode => T) =\n    servers = members map (node => node -> creator(node)) toMap\n\n  // Get the node\n  def get(node: NeutrinoNode): Option[T] = servers.get(node)\n\n  // Determine if we have any downstream servers available for balancing\n  def isEmpty() = servers.isEmpty\n\n  // Return a view of available servers\n  def available: Iterable[(NeutrinoNode, T)] = servers.view filter {\n    case (n, e) => isAvailable(n.settings.healthState)\n  }\n\n  def available(f: T => Boolean): Iterable[(NeutrinoNode, T)] =\n    available filter { case (n, e) => f(e) }\n\n  def minBy[B](f: T => B)(implicit cmp: Ordering[B]): Option[(NeutrinoNode, T)] = available match {\n    case iter if iter.isEmpty => None\n    case iter => Option(iter minBy { case (n, e) => f(e) })\n  }\n\n  def find(f: T => Boolean): Option[(NeutrinoNode, T)] =\n    available find { case (_, e) => f(e) }\n\n}\n\n\nobject BalancerNodes {\n\n  // Helper method; determine if this health-state is available\n  @inline def isAvailable(state: HealthState): Boolean = state match {\n    case HealthState.Maintenance => false\n    case _ => true\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/balancer/CNameResolver.scala",
    "content": "package com.ebay.neutrino.balancer\n\nimport javax.annotation.Nullable\n\nimport com.ebay.neutrino._\nimport com.ebay.neutrino.config.{VirtualAddress, VirtualPool, CanonicalAddress}\nimport com.ebay.neutrino.metrics.Instrumented\n\n\n/**\n * A customized PoolResolver supporting an efficient CNAME-based pool selection.\n *\n * This allows a pool to be selected using the request's host value, resolving\n * a matching pool from the pool's configured CNAME and port mapping.\n *\n * Previously, we assumed the NeutrinoPools provided covered the whole set of\n * pools and we were required to filter accordingly. Since V0.5.6 this behaviour\n * has changed so we no longer have to filter out non-matches. Now, we can\n * assume the NeutrinoPools provided is the full set of pools 'available' to\n * the resolver.\n *\n * In particular, the following changes have been made:\n *  - Previously:  matched by hostname, port, protocol\n *  - Currently:   match by hostname (NeutrinoPools has been filtered by port and protocol)\n **\n * TODO make data-structure more efficient for lookups\n * TODO fix parse order/data-structure\n * TODO add config as ctor-parameter\n */\nclass CNameResolver extends CachingResolver[String] with Instrumented {\n\n  import scala.collection.JavaConversions._\n\n\n  // Our UUID for matching\n  object HostKey {\n    def apply(address: CanonicalAddress): String = address.host.toLowerCase\n    def apply(request: NeutrinoRequest): Option[String] = request.host map (h => h.host.toLowerCase)\n  }\n\n  // Our loader version; is incremented on every update\n  private[balancer] var ports = Seq.empty[Int]\n\n\n  /**\n   * Hook our parent's rebuild to cache our associated ports.\n   * @param pools\n   * @return a new pool-set, as generated by our parent\n   */\n  protected override def rebuild(pools: NeutrinoPools): Map[String, NeutrinoPool] = {\n    ports = pools.service.settings.sourcePorts\n    super.rebuild(pools)\n  }\n\n\n  /**\n   * Helper function to determine if the address is a CNAME and the port specified matches\n   * one of ours.\n   *\n   * @param address\n   * @return\n   */\n  @inline def isCanonicalPool(address: VirtualAddress): Boolean =\n    address match {\n      case cname: CanonicalAddress if ports.contains(cname.port) => true\n      case _ => false\n    }\n\n  /**\n   * Rebuild the cached map on change of underlying source pools.\n   *\n   * This will generate one K/V per address/port/protocol tuple\n   *\n   * @param pools source-set of new replacement pools\n   */\n  def rebuild(pools: java.util.Iterator[VirtualPool]): java.util.Map[String, VirtualPool] =\n  {\n    // Helper method; determines if any of the addresses are mapped by our listener's sourcePort\n    val relevant = pools filter { pool => pool.addresses find (isCanonicalPool) isDefined }\n\n    // Split out the relevant (match on protocol/source-port)\n    // Extract host-key\n    val hosts = relevant flatMap { pool => pool.addresses collect {\n        case addr: CanonicalAddress => (HostKey(addr), pool)\n      }\n    }\n\n    // Build a java-map out of this\n    val hostmap = new java.util.HashMap[String, VirtualPool]()\n    hosts foreach { case (addr, pool) => hostmap.put(addr, pool) }\n    hostmap\n  }\n\n\n  /**\n   * This implements a simplified Java-style interface, which allows null-return value.\n   *\n   * @return a valid NeutrinoPool, or null if not resolved\n   */\n  @Nullable\n  def resolve(request: NeutrinoRequest): String = HostKey(request) getOrElse null\n\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/balancer/CNameWildcardResolver.scala",
    "content": "package com.ebay.neutrino.balancer\n\nimport com.ebay.neutrino.config.{CanonicalAddress, Host}\nimport com.ebay.neutrino.metrics.Instrumented\nimport com.ebay.neutrino._\nimport com.google.common.cache.{CacheBuilder, CacheLoader}\nimport io.netty.handler.codec.http.HttpRequest\n\n\n/**\n * A customized PoolResolver supporting an efficient CNAME-based pool selection.\n *\n * This allows a pool to be selected using the request's host value, resolving\n * a matching pool from the pool's configured CNAME and port mapping.\n *\n *\n * This implementation makes a wildcard-based match, matching the first CNAME\n * to make a postfix match. THIS MAY NOT BE WHAT YOU ARE LOOKING FOR...\n * For dynamic cases, you're probably better off using the full-match CNAME resolver.\n *\n *\n * TODO make data-structure more efficient for lookups\n * TODO fix parse order/data-structure\n * TODO add config as ctor-parameter\n */\nclass CNameWildcardResolver extends PoolResolver with Instrumented {\n  import com.ebay.neutrino.util.HttpRequestUtils._\n\n  // Constants   (TODO - move to settings)\n  val MAX_SIZE = 1023\n\n  // Always build the cache from the 'current' poolset.\n  private[balancer] val cache = CacheBuilder.newBuilder().maximumSize(MAX_SIZE).build(new HostLoader())\n\n  // Cached pools; if the pools structure changes we need to pick up the changes here (and flush the cache)\n  private[balancer] var pools: Seq[NeutrinoPool] = Seq.empty\n\n  // Our loader version; is incremented on every update\n  private[balancer] var version = 0\n\n\n  /**\n   * Determine if this pool matches the request provided; find the first matching host/CNAME\n   *\n   * Currently, just checks for postfix match.\n   * For example:\n   *  host = \"www.ebay.com\"\n   *  address.host = \"ebay.com\" (match)\n   *  address.host = \"123.www.ebay.com\" (not match)\n   *\n   * TODO move this find out to caller to cache host\n   * TODO add port support\n   * TODO profile this; might benefit from an outer toIterator()\n   */\n  class HostLoader extends CacheLoader[Host, Option[NeutrinoPool]] {\n    override def load(host: Host) = {\n      val hostname  = host.host\n      val hostpairs = pools flatMap  (pool => pool.settings.addresses collect { case addr:CanonicalAddress => (addr.host, pool)})\n      val foundpair = hostpairs.find (pair => hostname.endsWith(pair._1))\n\n      foundpair map (_._2)\n    }\n  }\n\n\n  // Check for a valid poolset, updating the cache as required\n  // TODO what kind of locking do we want here??\n  // TODO if this was a result of an async setPools, we should do refresh() instead of invalidateAll\n  @inline def cachepools(newpools: NeutrinoPools) = {\n    if (version != newpools.version) {\n      version = newpools.version\n      pools   = newpools().toSeq /*toList*/    // Not sure why we need to cache this here\n      cache.invalidateAll()\n    }\n\n    cache\n  }\n\n\n  /**\n   * Return the pool corresponding to the request provided, if available.\n   * Extract and cache the host\n   *\n   * TODO _ support PORT -> maybe through balancerpoools?\n   */\n  override def resolve(pools: NeutrinoPools, request: NeutrinoRequest): Option[NeutrinoPool] = {\n    val cache = cachepools(pools)\n    val host  = request.host\n\n    host flatMap (cache.get(_))\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/balancer/CachingResolver.scala",
    "content": "package com.ebay.neutrino.balancer\n\nimport javax.annotation.Nullable\n\nimport com.ebay.neutrino.config.VirtualPool\nimport com.ebay.neutrino.{NeutrinoRequest, NeutrinoPools, NeutrinoPool, PoolResolver}\n\n/**\n * Implements a simple Map-caching layer and simplified pool resolver over top of\n * the PoolResolver.\n *\n * This is useful because of the design of the resolver.\n *\n * Since NeutrinoCore and each NeutrinoPools container is immutable, it is replaced on\n * each configuration update. This container does not provide any direct mechansim to\n * optimize lookup, and so if we want to optimize type-specific operations (ie:\n * segregation by port, or lookup by host) we have to implement it externally.\n *\n * The idempotic design provides the pool by parameter, rather than through some type\n * of stateful mechanisim (ie: constructor) so we need some way to determine the\n * source pools have changed. NeutrinoPools provides this through an incremental\n * version ID.\n *\n * This class provides helper wrappers around the refresh process, insulating the\n * resolver implementation from the internals of state management.\n *\n *\n * @tparam K unique key-type for hash-map\n */\nabstract class CachingResolver[K] extends PoolResolver {\n\n  import scala.collection.JavaConversions._\n\n  /*\n  // Instrument the cache\n  metrics.safegauge(\"cache.size\")          { cnames.size }\n  metrics.safegauge(\"cache.hitCount\")      { cnames.stats.hitCount }\n  metrics.safegauge(\"cache.missCount\")     { cnames.stats.missCount }\n  metrics.safegauge(\"cache.totalLoadTime\") { cnames.stats.totalLoadTime }\n  metrics.safegauge(\"cache.evictionCount\") { cnames.stats.evictionCount }\n  */\n\n  // Cached pools; if the pools structure changes we need to pick up the changes here\n  // (and flush the cache)\n  private[balancer] var cached: Map[K, NeutrinoPool] = Map()\n\n  // Our loader version; is incremented on every update\n  private[balancer] var version: Int = 0\n\n\n\n  /**\n   * Return the pool corresponding to the request provided, if available.\n   * Extract and cache the host\n   *\n   * TODO build caching around underlying settings rather than host/port\n   * TODO handle concurrency case to prevent rebuilding of updated version multiple times\n   */\n  override def resolve(pools: NeutrinoPools, request: NeutrinoRequest): Option[NeutrinoPool] = {\n    // Check for version change\n    if (version != pools.version) {\n      // Update the version to minimize regeneration\n      version = pools.version\n      cached  = rebuild(pools)\n    }\n\n    // Delegate to the one-parameter version and handle null-results\n    Option(resolve(request)) flatMap (cached get(_))\n  }\n\n\n  protected def rebuild(pools: NeutrinoPools): Map[K, NeutrinoPool] = {\n    // Generate a reference-set (configs to nodes) and rebuild our set\n    val configs = pools() map (pool => pool.settings -> pool) toMap\n    val rebuilt = mapAsScalaMap(rebuild(configs.keys.toIterator))\n    val newpool = rebuilt mapValues (configs(_)) toMap\n\n    newpool\n  }\n\n\n\n  /**\n   * Rebuild the cached map on change of underlying source pools.\n   *\n   * Subclasses should implement functionality to both extract a key from the\n   * pool-configuration, and filter out any non-matching pools from the\n   * resulting pool-set.\n   *\n   * @param pools source-set of new replacement pools\n   */\n  def rebuild(pools: java.util.Iterator[VirtualPool]): java.util.Map[K, VirtualPool]\n\n  /**\n   * This implements a simplified Java-style interface, which allows null-return value.\n   *\n   * @return a valid NeutrinoPool, or null if not resolved\n   */\n  @Nullable\n  def resolve(request: NeutrinoRequest): K\n}\n"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/balancer/L7AddressResolver.scala",
    "content": "package com.ebay.neutrino.balancer\n\nimport com.ebay.neutrino._\nimport com.ebay.neutrino.config.WildcardAddress\nimport com.ebay.neutrino.metrics.Instrumented\n\n\n/**\n * A customized PoolResolver supporting pool-selection based on the CMS\n * RoutingPolicy/RouteMap configuration.\n *\n * TODO make data-structure more efficient for lookups\n * TODO fix parse order/data-structure\n * TODO add config as ctor-parameter\n */\nclass L7AddressResolver extends PoolResolver with Instrumented {\n\n  // Specify our types for clarity\n  // TODO this could be a Map[String, NeutrinoPool] if we didn't care about ordering\n  type RouteMap  = List[(String, NeutrinoPool)]\n  type HostCache = Map[String, RouteMap]\n\n  // Cached pools; if the pools structure changes we need to pick up the changes here\n  // (and flush the cache)\n  private[balancer] var cached: HostCache = Map()\n\n  // Our loader version; is incremented on every update\n  private[balancer] var version: Int = 0\n\n\n  /**\n   * Build a multi-map of Host => (path, pool) that we can use to resolve.\n   *\n   * @param pools\n   * @return\n   */\n  protected def rebuild(pools: NeutrinoPools): HostCache =\n    synchronized {\n      // Generate a reference-set (configs to nodes) and rebuild our set\n      val configs = pools() map (pool => pool.settings -> pool) toMap\n\n      // Extract relevant address-configurations into tuples\n      val tuples = configs flatMap { case (poolcfg, pool) =>\n        poolcfg.addresses collect {\n          case route: WildcardAddress if (route.host.nonEmpty && route.path.nonEmpty) =>\n            // Group the tuples back out to our cache format\n            (route.host.toLowerCase, route.path, pool)\n        }\n      } toList\n\n      // Group tuples by host-name and aggregate the path/pool pairs\n      tuples groupBy (_._1) mapValues {\n        _ map { case (_, path, pool) => path -> pool }\n      }\n    }\n\n  /**\n   * Return the pool corresponding to the request provided, if available.\n   * Extract and cache the host\n   *\n   * TODO build caching around underlying settings rather than host/port\n   * TODO handle concurrency case to prevent rebuilding of updated version multiple times\n   */\n  override def resolve(pools: NeutrinoPools, request: NeutrinoRequest): Option[NeutrinoPool] = {\n    // Check for version change\n    if (version != pools.version) {\n      // Update the version to minimize regeneration\n      version = pools.version\n      cached = rebuild(pools)\n    }\n\n    // Identify our 'relevant' portions of the reuqest\n    val host = request.host map (_.host.toLowerCase)\n    val path = request.requestUri.getPath\n\n    // Resolve all pools that match hosts\n    val routemap = host flatMap (cached get (_))\n\n    // If found, further try and match against possible pool-prefix => COULD BE REGEXES\n    routemap flatMap (_ collectFirst {\n      case (prefix, pool) if path.startsWith(prefix) => pool\n    })\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/balancer/LeastConnection.scala",
    "content": "package com.ebay.neutrino.balancer\n\nimport com.ebay.neutrino.{NeutrinoNode, NeutrinoRequest}\nimport com.typesafe.scalalogging.slf4j.StrictLogging\n\n/**\n * An algorithmically naive implementation of a least-connection operation.\n *\n * This implementation currently is O(n) to resolve 'lowest'.\n *\n * TODO Efficient Data Structure\n *  - The ideal would be some form of Red/Black tree (ie: TreeMap[Node, Int])\n *  - Unfortunately, we can only order based on Key, not Value\n *  - Recommend custom data structure based on LinkedList:\n *      - Parent structure a linked-array of \"per-count buckets\"  (List[(count, List[Entry[NeutrinoNode]])]\n *      - Assign comes from front of parent list (to determine lowest 'count' bucket)\n *      - Assign from bucket = first in bucket\n *        - Move to next bucket, inserting if not contiguous\n *        - On move, add to front of new/next bucket\n *        - Shift remaining items in old bucket back to root\n *      - Release from bucket is reverse\n *      - Secondary HashMap[NeutrinoNode, Entry[NeutrinoNode]] maps back to node and resolve down\n *      - *** Locking required on every operation; scalability issue\n */\nclass LeastConnectionBalancer extends Balancer with StrictLogging {\n\n  private case class Entry(node: NeutrinoNode, var count: Int = 0)\n\n  // Only requires synchronization on structural changes.\n  private val members = new BalancerNodes[Entry]()\n\n  // Re(set) the current membership of the load-balancer\n  // ?? Should rebuild just do a new map replacement? Need to mark/sweep/copy ints\n  override def rebuild(members: Array[NeutrinoNode]) = this.members.set(members, Entry(_))\n\n\n  /**\n   * Resolve an endpoint for request processing.\n   *\n   * @param request\n   * @return\n   */\n  def assign(request: NeutrinoRequest): Option[NeutrinoNode] =\n    if (members.isEmpty)\n      None\n    else {\n      // INEFFICIENT = THIS NEEDS TO BE FIXED WITH INTERNAL SORTED LIST\n      // SEE NOTE IN CLASS DESCRIPTION\n      // NOTE: This requires either entry synchronization, or tolerance of over-assignment\n      //    - Synchronization of whole data structure prevents inconsistent writes\n      //    - Over-assignment would allow double assignment to the smallest resolved on\n      //        concurrent access\n      members.minBy(_.count) map { case (node, entry) =>\n        entry.synchronized(entry.count += 1)\n        node\n      }\n    }\n\n\n  // Release an endpoint from request processing\n  def release(request: NeutrinoRequest, node: NeutrinoNode) =\n    members.get(node) match {\n      case Some(entry) => entry.synchronized(entry.count -= 1)\n      case None => logger.warn(\"Attempt to release a node which was not found in the servers: {}\", node)\n    }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/balancer/RoundRobin.scala",
    "content": "package com.ebay.neutrino.balancer\n\nimport com.ebay.neutrino.{NeutrinoNode, NeutrinoRequest}\n\n\nclass RoundRobinBalancer extends Balancer {\n\n  type Entry = NeutrinoNode\n\n  private val members = new BalancerNodes[Entry]()\n  private var iter = members.available.iterator\n\n\n  // Re(set) the current membership of the load-balancer\n  override def rebuild(members: Array[NeutrinoNode]) =\n    this.members.set(members, identity)\n\n\n  // Resolve an endpoint for request processing\n  def assign(request: NeutrinoRequest): Option[NeutrinoNode] =\n    if (members.isEmpty)\n      None\n    else\n      // Store the active iterator\n      members.synchronized {\n        if (iter.isEmpty) iter = members.available.iterator\n        if (iter.hasNext) Option(iter.next._1)\n        else None\n      }\n\n  // Release an endpoint from request processing\n  def release(request: NeutrinoRequest, node: NeutrinoNode) = {}\n\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/balancer/Statistics.scala",
    "content": "package com.ebay.neutrino.balancer\n\nimport java.io._\nimport scala.concurrent.duration.FiniteDuration\nimport scala.util.Try\nimport com.typesafe.scalalogging.slf4j.StrictLogging\n\n/**\n * Interesting Statistics to collect\n *\n * - measure how many capacity measurements are piggybacked on data packets\n *    (ie: packets had enough room to send capacity + framing in same TCP)\n */\n// @see http://man7.org/linux/man-pages/man5/proc.5.html\nobject CpuStatistics extends StrictLogging {\n\n  // Data-type for samples\n  case class CpuSample(user: Double, nice: Double, system: Double, idle: Double, iowait: Double, irq: Double, softirq: Double, steal: Double, guest: Double, guest_nice: Double) {\n    lazy val total = (user + nice + system + idle + iowait + irq + softirq + steal + guest + guest_nice)\n\n    // Calculate the usage between this sample and the sample provided\n    def usage(other: CpuSample) = {\n      val idleDiff  = Math.abs(other.idle - idle)\n      val totalDiff = Math.abs(other.total - total)\n\n      1 - (idleDiff / totalDiff)\n    }\n  }\n\n  case class UptimeSample(uptime: Double, idle: Double)\n\n  // Constants\n  val PROC_STAT   = new File(\"/proc/stat\")    //proc_stat.txt\n  val PROC_UPTIME = new File(\"/proc/uptime\")  //proc_uptime.txt\n\n  val ProcessorCount = Runtime.getRuntime.availableProcessors\n\n\n  /**\n   * Opens a stream from a existing file and return it. This style of\n   * implementation does not throw Exceptions to the caller.\n   *\n   * @param path is a file which already exists and can be read.\n   * @throws IOException\n   */\n  def cpuSample(): Option[CpuSample] = {\n    val reader = Try(new BufferedReader(new FileReader(PROC_STAT))).toOption\n    val values = reader map { _.readLine.split(\"\\\\s+\").tail map (_.toDouble) }\n\n    // Clean up our reader\n    reader map { _.close() }\n\n    values map { array =>\n      val Array(user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice) = array\n      CpuSample(user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice)\n    }\n  }\n\n  def uptimeSample(): Option[UptimeSample] = {\n    val reader = Try(new BufferedReader(new FileReader(PROC_UPTIME))).toOption\n    val values = reader map { _.readLine.split(\"\\\\s+\") map (_.toDouble) }\n\n    // Clean up our reader\n    reader map { _.close() }\n\n    values map { array =>\n      val Array(uptime, idletime) = array\n      UptimeSample(uptime, idletime)\n    }\n  }\n\n\n  /**\n   * Parse /proc/stat file and fill the member values list\n   *\n   * Line example:\n   * cpu  144025136 134535 43652864 42274006316 2718910 6408 1534597 0 131907272 0\n   *\n   * This implementation provided by Ashok (@asmurthy) and S.R (@raven)\n   *\n   * Another possible implementation can be found at:\n   * @see http://stackoverflow.com/questions/1420426/calculating-cpu-usage-of-a-process-in-linux\n   */\n  def cpuUsage(delayInMilliseconds: Int=10): Option[Double] = {\n\n    // TODO just use time since last sample (if ongoing)\n    val sampleA = cpuSample()\n    val sampleB = sampleA flatMap {_ => Thread.sleep(delayInMilliseconds); cpuSample()}\n\n    // Calculate CPU %\n    (sampleA, sampleB) match {\n      case (Some(a), Some(b)) if b.total > a.total => Option(a.usage(b))\n      case _ => None\n    }\n  }\n\n  /**\n   * Determine if the CPU measurement is available or not.\n   * Naive solution - just see if a sample works.\n   */\n  def isCpuAvailable() = cpuUsage() isDefined\n\n}\n\n\nobject CpuStatisticsApp extends App with StrictLogging {\n\n  println (s\"Processors: ${CpuStatistics.ProcessorCount}\")\n  println (s\"CPU Usage: ${CpuStatistics.cpuUsage()}\")\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/balancer/WeightedRoundRobinBalancer.scala",
    "content": "package com.ebay.neutrino.balancer\n\nimport com.ebay.neutrino.{NeutrinoNode, NeutrinoRequest}\nimport com.typesafe.scalalogging.slf4j.StrictLogging\n\n\nclass WeightedRoundRobinBalancer extends Balancer with StrictLogging {\n\n\n  private case class Entry(node: NeutrinoNode, var load: Int = 0)\n\n  private val members = new BalancerNodes[Entry]()\n\n\n  //Re(set) the current membership of the load - balancer\n  override def rebuild(members: Array[NeutrinoNode]) =\n    this.members.set(members, Entry(_))\n\n\n  // Resolve an endpoint for request processing\n  def assign(request: NeutrinoRequest): Option[NeutrinoNode] =\n    if (members.isEmpty)\n      None\n    else\n      members.find(e => check(e)) map { case (node, entry) =>\n        entry.synchronized(entry.load += 1)\n        node\n      }\n\n  private def check(entry: Entry): Boolean =\n    entry.node.settings.weight.get > entry.load\n\n  // Release an endpoint from request processing\n  def release(request: NeutrinoRequest, node: NeutrinoNode) =\n    members.get(node) match {\n      case Some(entry) => entry.synchronized(entry.load -= 1)\n      case None => logger.warn(\"Attempt to release a node which was not found in the servers: {}\", node)\n    }\n\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/channel/NeutrinoChannelId.scala",
    "content": "package com.ebay.neutrino.channel\n\nimport java.util\n\nimport io.netty.buffer.{ByteBuf, ByteBufUtil}\nimport io.netty.channel.ChannelId\n\n/**\n * Cut and paste \"re\" implementation of the DefaultChannelId.\n *\n * This class provides globally-unique IDs, but unfortunately is both final and\n * package-private, and hence can't be serialized or used for cross-instance IDs\n * as is required by UUID.\n *\n * @see io.netty.channel.DefaultChannelId\n */\ncase class NeutrinoChannelId(data: Array[Byte]) extends ChannelId {\n  import com.ebay.neutrino.channel.DefaultChannelUtil._\n\n  assert(data.length == DATA_LEN)\n\n  def machineId()  = readLong(data, 0)\n  def processId()  = readInt (data, MACHINE_ID_LEN)\n  def sequenceId() = readInt (data, MACHINE_ID_LEN+PROCESS_ID_LEN)// SEQUENCE_LEN\n  def timestamp()  = readLong(data, MACHINE_ID_LEN+PROCESS_ID_LEN+SEQUENCE_LEN) // TIMESTAMP_LEN\n  def random()     = readInt (data, MACHINE_ID_LEN+PROCESS_ID_LEN+SEQUENCE_LEN+TIMESTAMP_LEN)\n\n\n  override val asShortText: String =\n    ByteBufUtil.hexDump(data, MACHINE_ID_LEN + PROCESS_ID_LEN + SEQUENCE_LEN + TIMESTAMP_LEN, RANDOM_LEN);\n\n  override val asLongText: String = {\n    val buf = new StringBuilder(2 * data.length + 5)\n    var i = 0\n    buf.append(ByteBufUtil.hexDump(data, i, MACHINE_ID_LEN)); i += MACHINE_ID_LEN\n    buf.append('-')\n    buf.append(ByteBufUtil.hexDump(data, i, PROCESS_ID_LEN)); i += PROCESS_ID_LEN\n    buf.append('-')\n    buf.append(ByteBufUtil.hexDump(data, i, SEQUENCE_LEN)); i += SEQUENCE_LEN\n    buf.append('-')\n    buf.append(ByteBufUtil.hexDump(data, i, TIMESTAMP_LEN)); i += TIMESTAMP_LEN\n    buf.append('-')\n    buf.append(ByteBufUtil.hexDump(data, i, RANDOM_LEN)); i += RANDOM_LEN\n    assert(i == data.length)\n    buf.toString\n  }\n\n  override lazy val hashCode: Int = random()\n\n  override def compareTo(o: ChannelId): Int = 0\n\n  override val toString = s\"Id[$asLongText]\"\n\n  override def equals(rhs: Any): Boolean =\n    rhs match {\n      case id: NeutrinoChannelId => (id canEqual this) && util.Arrays.equals(data, id.data)\n      case _ => false\n    }\n}\n\n\nobject NeutrinoChannelId {\n\n  /**\n   * Initialize our ChannelId from its component parts.\n   *\n   * @see io.netty.channel.DefaultChannelId#init()\n   */\n  def apply(): NeutrinoChannelId = {\n    val bytes = DefaultChannelUtil.newInstanceBytes()\n    NeutrinoChannelId(bytes)\n  }\n\n  def apply(buffer: ByteBuf): NeutrinoChannelId = {\n    require(DefaultChannelUtil.DATA_LEN == buffer.readableBytes)\n\n    val bytes = new Array[Byte](DefaultChannelUtil.DATA_LEN)\n    buffer.getBytes(0, bytes)\n\n    NeutrinoChannelId(bytes)\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/channel/NeutrinoChannelLoop.scala",
    "content": "package com.ebay.neutrino.channel\n\nimport java.net.SocketAddress\nimport java.util\nimport java.util.concurrent.TimeUnit\n\nimport io.netty.channel.ChannelHandlerInvokerUtil._\nimport io.netty.channel._\nimport io.netty.util.concurrent.{EventExecutor, Future => NettyFuture}\n\n\n\n/**\n * This was stolen directly from EmbeddedEventLoop, which unfortunately was package scoped and\n * couldn't be used directly.\n *\n * @see io.netty.channel.embedded.EmbeddedEventLoop\n */\nfinal class NeutrinoChannelLoop extends AbstractEventLoop with ChannelHandlerInvoker {\n  private final val tasks = new util.ArrayDeque[Runnable](2)\n\n  def execute(command: Runnable) {\n    if (command == null) {\n      throw new NullPointerException(\"command\")\n    }\n    tasks.add(command)\n  }\n\n\n  // ?? Can we cache the iterator and call takeWhile on each iteration?\n  private[neutrino] def runTasks() =\n    Iterator.continually(tasks.poll) takeWhile(_ != null) foreach (_.run)\n\n\n  def shutdownGracefully(quietPeriod: Long, timeout: Long, unit: TimeUnit): NettyFuture[_] =\n    throw new UnsupportedOperationException\n\n  def terminationFuture: NettyFuture[_] =\n    throw new UnsupportedOperationException\n\n  @Deprecated\n  def shutdown = throw new UnsupportedOperationException\n\n  def isShuttingDown: Boolean = false\n\n  def isShutdown: Boolean = false\n\n  def isTerminated: Boolean = false\n\n  def awaitTermination(timeout: Long, unit: TimeUnit): Boolean = false\n\n  def register(channel: Channel): ChannelFuture =\n    register(channel, new DefaultChannelPromise(channel, this))\n\n\n  def register(channel: Channel, promise: ChannelPromise): ChannelFuture = {\n    channel.unsafe.register(this, promise)\n    promise\n  }\n\n  override def inEventLoop: Boolean = true\n\n  def inEventLoop(thread: Thread): Boolean = true\n\n  def asInvoker: ChannelHandlerInvoker = this\n\n  def executor: EventExecutor = this\n\n  def invokeChannelRegistered(ctx: ChannelHandlerContext) =\n    invokeChannelRegisteredNow(ctx)\n\n  def invokeChannelUnregistered(ctx: ChannelHandlerContext) =\n    invokeChannelUnregisteredNow(ctx)\n\n  def invokeChannelActive(ctx: ChannelHandlerContext) =\n    invokeChannelActiveNow(ctx)\n\n  def invokeChannelInactive(ctx: ChannelHandlerContext) =\n    invokeChannelInactiveNow(ctx)\n\n  def invokeExceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) =\n    invokeExceptionCaughtNow(ctx, cause)\n\n  def invokeUserEventTriggered(ctx: ChannelHandlerContext, event: AnyRef) =\n    invokeUserEventTriggeredNow(ctx, event)\n\n  def invokeChannelRead(ctx: ChannelHandlerContext, msg: AnyRef) =\n    invokeChannelReadNow(ctx, msg)\n\n  def invokeChannelReadComplete(ctx: ChannelHandlerContext) =\n    invokeChannelReadCompleteNow(ctx)\n\n  def invokeChannelWritabilityChanged(ctx: ChannelHandlerContext) =\n    invokeChannelWritabilityChangedNow(ctx)\n\n  def invokeBind(ctx: ChannelHandlerContext, localAddress: SocketAddress, promise: ChannelPromise) =\n    invokeBindNow(ctx, localAddress, promise)\n\n  def invokeConnect(ctx: ChannelHandlerContext, remoteAddress: SocketAddress, localAddress: SocketAddress, promise: ChannelPromise) =\n    invokeConnectNow(ctx, remoteAddress, localAddress, promise)\n\n  def invokeDisconnect(ctx: ChannelHandlerContext, promise: ChannelPromise) =\n    invokeDisconnectNow(ctx, promise)\n\n  def invokeClose(ctx: ChannelHandlerContext, promise: ChannelPromise) =\n    invokeCloseNow(ctx, promise)\n\n  def invokeDeregister(ctx: ChannelHandlerContext, promise: ChannelPromise) =\n    invokeDeregisterNow(ctx, promise)\n\n  def invokeRead(ctx: ChannelHandlerContext) =\n    invokeReadNow(ctx)\n\n  def invokeWrite(ctx: ChannelHandlerContext, msg: AnyRef, promise: ChannelPromise) =\n    invokeWriteNow(ctx, msg, promise)\n\n  def invokeFlush(ctx: ChannelHandlerContext) =\n    invokeFlushNow(ctx)\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/channel/NeutrinoEvent.scala",
    "content": "package com.ebay.neutrino.channel\n\nimport com.ebay.neutrino.NeutrinoRequest\n\n\n// Marker interface for Balancer lifecycle events\nsealed trait NeutrinoEvent {\n  def request: NeutrinoRequest\n}\n\n\nobject NeutrinoEvent {\n\n  // HTTP-specific\n  // These are pretty simple/generic; might need to tighten these up for utility and/or brevity\n  case class RequestCreated(request: NeutrinoRequest) extends NeutrinoEvent\n  case class ResponseReceived(request: NeutrinoRequest) extends NeutrinoEvent\n  case class ResponseCompleted(request: NeutrinoRequest) extends NeutrinoEvent\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/channel/NeutrinoPipelineChannel.scala",
    "content": "package com.ebay.neutrino.channel\n\nimport java.net.SocketAddress\n\nimport com.ebay.neutrino.channel.NeutrinoPipelineChannel.ChannelState\nimport com.ebay.neutrino.metrics.Instrumented\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.channel._\nimport io.netty.util.{Attribute, AttributeKey, ReferenceCountUtil}\n\n/**\n * Base class for {@link Channel} NeutrinoChannel implementations; built on the EmbeddedChannel\n * framework.\n *\n * NeutrinoChannel (EmbeddedChannel) is:\n * - container for pipeline\n * - dedicated to protocol (ie: HTTP)\n * - tightly coupled with the \"request\" (not connection).\n * - Knows about each endpoint's framing protocol\n * - is responsible for framing down into the endpoints' frames\n *\n * For simplicity, writing into the EmbeddedChannel handles memory lifecycle and manages virtual\n * session containing...\n */\n// TODO support user-pipeline thread\nclass NeutrinoPipelineChannel(parentctx: ChannelHandlerContext)\n  extends AbstractChannel(parentctx.channel, NeutrinoChannelId())\n  with StrictLogging\n  with Instrumented\n{\n  import ChannelFutureListener._\n\n\n  class ForwardingHandler extends ChannelHandlerAdapter with ChannelInboundHandler {\n\n    override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) =\n      parentctx.fireExceptionCaught(cause)\n\n    override def channelRegistered(ctx: ChannelHandlerContext): Unit =\n      parentctx.fireChannelRegistered()\n\n    override def channelUnregistered(ctx: ChannelHandlerContext): Unit =\n      parentctx.fireChannelUnregistered()\n\n    override def channelActive(ctx: ChannelHandlerContext): Unit =\n      parentctx.fireChannelActive()\n\n    override def channelInactive(ctx: ChannelHandlerContext): Unit =\n      parentctx.fireChannelInactive()\n\n    override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit =\n      parentctx.fireChannelRead(msg)\n\n    override def channelWritabilityChanged(ctx: ChannelHandlerContext): Unit =\n      parentctx.fireChannelWritabilityChanged()\n\n    override def userEventTriggered(ctx: ChannelHandlerContext, evt: AnyRef): Unit =\n      parentctx.fireUserEventTriggered(evt)\n\n    override def channelReadComplete(ctx: ChannelHandlerContext): Unit =\n      parentctx.fireChannelReadComplete()\n  }\n\n  private class DefaultUnsafe extends AbstractUnsafe {\n    override def connect(remote: SocketAddress, local: SocketAddress, promise: ChannelPromise): Unit =\n      safeSetSuccess(promise)\n\n    //override protected def flush0() = super.flush0()\n  }\n\n  // Immutable data\n  val config = new DefaultChannelConfig(this)\n\n  // Mutable data\n  private var parentPromise: ChannelPromise = null\n  @transient private var state: ChannelState = ChannelState.Open\n\n\n\n  /**\n   * Register the provided event-loop and, after registration, mark ourselves as active.\n   */\n  private[channel] def register() = {\n    // Register ourselves to our event-loop (forcing notification of the channel)\n    require(state == ChannelState.Open)\n    parent.eventLoop.register(this)\n    require(state == ChannelState.Registered)\n\n    // 'Activate' our channel\n    state = ChannelState.Active\n  }\n\n  override protected def newUnsafe: AbstractChannel#AbstractUnsafe =\n    new DefaultUnsafe\n\n  override protected def isCompatible(loop: EventLoop): Boolean =\n    loop.isInstanceOf[SingleThreadEventLoop]\n\n  override protected def doBind(localAddress: SocketAddress) =\n    {}\n\n  override protected def doDisconnect =\n    {}\n\n  override protected def doClose = {\n    state = ChannelState.Closed\n\n    parentPromise match {\n      case null =>\n        parentctx.channel.close()\n      case valid =>\n        parentctx.close(parentPromise)\n        parentPromise = null\n    }\n  }\n\n  override def metadata: ChannelMetadata = NeutrinoPipelineChannel.METADATA\n\n  override protected def doBeginRead = parentctx.read()    //if (isOpen) (this.upstream getOrElse parent).read()\n\n  override protected def doRegister() = (state = ChannelState.Registered)\n\n  // Channel is open if we haven't been closed and our parent channel is open\n  override def isOpen: Boolean = (state != ChannelState.Closed && parentctx.channel.isOpen)\n\n  // Channel is active if we haven't been closed and our parent channel is active\n  override def isActive: Boolean = (state == ChannelState.Active && parentctx.channel.isActive)\n\n\n\n  /**\n   * Get the {@link Attribute} for the given {@link AttributeKey}.\n   * This method will never return null, but may return an {@link Attribute} which does not have a value set yet.\n   */\n  override def attr[T](key: AttributeKey[T]): Attribute[T] = parent.attr(key)\n\n  /**\n   * Returns {@code} true if and only if the given {@link Attribute} exists in this {@link AttributeMap}.\n   */\n  override def hasAttr[T](key: AttributeKey[T]): Boolean = parent.hasAttr(key)\n\n  /**\n   * Override the embedded channel's local address to map to the underlying channel.\n   * @return\n   */\n  override protected def localAddress0(): SocketAddress = parent.localAddress()\n\n  /**\n   * Override the embedded channel's remote address to map to the underlying channel.\n   * @return\n   */\n  override protected def remoteAddress0(): SocketAddress = parent.remoteAddress()\n\n\n  /**\n   * This is called by the AbstractChannel after write/flush through the pipeline\n   * is completed.\n   *\n   * Instead of using outbound message paths, we wire directly back to the upstream\n   * context (which should delegate to the next handler in the upstream-parent.\n   *\n   * @param in\n   *\n   *\n   * NOTE - we currently do this functionality in the UpstreamHandler instead of here.\n   */\n  override protected def doWrite(in: ChannelOutboundBuffer): Unit = {\n    val outbound = Iterator.continually(in.current()) takeWhile (_ != null)\n\n    outbound foreach { msg =>\n      ReferenceCountUtil.retain(msg)\n\n      // TODO handle entry promise; hook promise on future\n\n      // Do the write and attach an appropriate future to it\n      val listener = if (isOpen) CLOSE_ON_FAILURE else CLOSE\n      val future   = parentctx.write(msg).addListener(listener)\n\n      // Signal removal from the current channel.\n      in.remove()\n\n      // Flush as required\n      if (in.isEmpty) parentctx.flush()\n    }\n  }\n\n\n  /**\n   * Hack a custom promise to carry our parent framework's promise through and dispatch the close\n   * properly back, allowing differential behaviour between close() invoked internally vs externally.\n   *\n   * @param parent\n   * @return\n   */\n  override def close(parent: ChannelPromise): ChannelFuture =\n    if (parent.channel() == this) {\n      super.close(parent)\n    }\n    else {\n      parentPromise = parent\n      super.close()\n    }\n\n}\n\n\nobject NeutrinoPipelineChannel {\n  import com.ebay.neutrino.util.AttributeSupport._\n\n  // Constants\n  private val METADATA = new ChannelMetadata(false)\n\n  /**\n   * Channel state constants; these track the allowable states.\n   */\n  sealed private[channel] trait ChannelState\n\n  private[channel] object ChannelState {\n    case object Closed extends ChannelState\n    case object Open   extends ChannelState\n    case object Active extends ChannelState\n    case object Registered extends ChannelState\n  }\n\n  /**\n   * Create a new pipeline-channel and do the initial channel setup.\n   *\n   */\n  def apply(parentctx: ChannelHandlerContext, handlers: Seq[ChannelHandler]): NeutrinoPipelineChannel =\n  {\n    // Create the custom channel\n    val channel = new NeutrinoPipelineChannel(parentctx)\n\n    // Add the handlers.\n    channel.pipeline.addFirst(handlers:_*)\n\n    // Register ourselves to our event-loop (forcing notification of the channel)\n    channel.register()\n\n    // Finally, add a relaying handler to dispatch out from the bottom of the pipeline\n    channel.pipeline.addLast(\"user-loopback\", new channel.ForwardingHandler)\n\n    // Hook the parents' close-future for force closing of our new channel\n    parentctx.channel.closeFuture.addListener(new ChannelFutureListener {\n      override def operationComplete(future: ChannelFuture): Unit =\n        if (channel.isOpen) channel.close()\n    })\n\n    channel\n  }\n\n\n  def apply(parentctx: ChannelHandlerContext): Option[NeutrinoPipelineChannel] = {\n    // Extract handler settings and determine if pipeline is required\n    val settings = parentctx.service map (_.settings)\n    val handlers = settings map (_.handlers) filter (_.nonEmpty)\n\n    // If configured, create pipeline channel around it\n    handlers map { apply(parentctx, _) }\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/channel/NeutrinoService.scala",
    "content": "package com.ebay.neutrino.channel\n\nimport com.ebay.neutrino.{NeutrinoServiceInitializer, _}\nimport com.ebay.neutrino.config._\nimport com.ebay.neutrino.metrics.Instrumented\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.bootstrap.ServerBootstrap\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.channel._\nimport io.netty.channel.socket.nio.NioServerSocketChannel\n\nimport scala.concurrent.Future\n\n/**\n * Channel management service/wrapper around incoming VIPs\n *\n * Incoming channel needs to open like this:\n * 1) New Channel\n * 2) Figure out remote endpoint\n * 3) If Endpoint selected, create endpoint handler last\n * 4) If endpoint is not proxy-only, create pipeline handler\n */\n@Sharable\nclass NeutrinoService(val core: NeutrinoCore, val settings: ListenerSettings)\n  extends StrictLogging\n  with Instrumented\n{\n  implicit val context = core.context\n\n  // Initialized connections\n  private[this] var incomingConnections = Map[VirtualAddress, Future[Channel]]()\n\n  // Configure Listeners\n  private[this] var established = Map[ListenerAddress, Future[ServerChannel]]()\n\n  // Shared handlers\n  val factory  = new NeutrinoServiceInitializer(this)\n  val pools    = new NeutrinoPools(this)\n\n\n  // Listen on all configured addresses.\n  // These will be all-successful or all-failure.\n  def listen(): Seq[(ListenerAddress, Future[Channel])] = {\n    require(established.isEmpty, \"Service already started; can't listen() twice\")\n\n    // Initialize individual ports\n    val addresses = settings.addresses map ((_, settings))\n\n    addresses collect { case (address, settings) =>\n      require(!established.isDefinedAt(address), s\"Address $address already defined; can't initialize twice\")\n\n      // Create a service to handle the addresses\n      val pair = (address -> listen(address))\n\n      // Build a map-tuple out of this\n      established += pair\n      pair\n    }\n  }\n\n\n  def shutdown() = {\n    // TODO close established\n  }\n\n\n  /**\n   * Update the service topology/configuration.\n   *\n   * We prefilter to remove any pools that are configured for an incompatible protocol\n   * as our own.\n   */\n  def update(pools: VirtualPool*) =\n  {\n    // Split out the relevant (by protocol)\n    val relevant = pools filter (pool => pool.protocol == settings.protocol)\n\n    // Update the internal pool-state\n    this.pools.update(relevant:_*)\n  }\n\n\n  /**\n   * Make the connection attempt.\n   *\n   * The challenge here is that we want to inject a fully-formed EndpointHandler\n   * into the initial pipeline, which requires an EndpointConnection object.\n   * Unfortunately, this object is created by the caller only after the\n   * connect has completed.\n   *\n   * TODO handle bind() failure; should probably kill the server ASAP\n   */\n  def listen(address: VirtualAddress): Future[ServerChannel] = {\n    import com.ebay.neutrino.util.Utilities._\n\n    logger.info(\"Creating listener on {}\", address)\n\n    // Do the socket-bind\n    val socket = address.socketAddress\n    val channel = new ServerBootstrap()\n      .channel(classOf[NioServerSocketChannel])\n      .group(core.supervisor, core.workers)\n      .option[java.lang.Integer](ChannelOption.SO_BACKLOG, 512)\n      .childOption[java.lang.Boolean](ChannelOption.TCP_NODELAY, true)\n      .childHandler(factory)\n      .bind(socket)\n      .future()\n      .asInstanceOf[Future[ServerChannel]]\n\n    logger.info(s\"Starting HTTP on port $socket\")\n\n    // Cache the bound socket (not yet complete)\n    incomingConnections += (address -> channel)\n    channel\n  }\n\n\n  /** Delegate the resolve() request to the connection's pools, using any available resolvers */\n  def resolvePool(request: NeutrinoRequest): Option[NeutrinoPool] = {\n    // Iterate any available resolvers, matching the first resolved pool\n    settings.poolResolvers.toStream flatMap (_.resolve(pools, request)) headOption\n  }\n}\n\n\n/**\n * Implementation of a state wrapper around a set of NeutrinoServices\n *\n * Should we allow dynamic configuration??\n * handle quiescence...\n *\n * def configure(listeners: ListenerSettings*) = {\n *   listeners map (new NeutrinoService(this, _))\n * }\n *\n * ?? Should we check for duplicate ports?\n */\nclass NeutrinoServices(val core: NeutrinoCore) extends Iterable[NeutrinoService] with StrictLogging {\n\n  val listeners: Map[ListenerSettings, NeutrinoService] =\n    core.settings.interfaces map (setting => setting -> new NeutrinoService(core, setting)) toMap\n\n\n  // Get service by port\n  def forPort(port: Int): Option[NeutrinoService] =\n    listeners find (_._1.sourcePorts.contains(port)) map (_._2)\n\n  // Return an iterator over our configured services\n  override def iterator: Iterator[NeutrinoService] = listeners.values.iterator\n\n}\n"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/channel/NeutrinoSession.scala",
    "content": "package com.ebay.neutrino.channel\n\nimport io.netty.channel._\n\n/**\n * Create a new instance with the pipeline initialized with the specified handlers.\n *\n * Note initializers need to be in inner-to-outer order\n */\ncase class NeutrinoSession(channel: Channel, service: NeutrinoService)"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/cluster/DataSourceSettings.scala",
    "content": "package com.ebay.neutrino.cluster\n\nimport akka.actor._\nimport com.ebay.neutrino.config.Configuration\nimport com.ebay.neutrino.config.Configuration._\nimport com.typesafe.config.Config\nimport com.ebay.neutrino.datasource.DataSource\n\nimport scala.concurrent.duration.{Duration, FiniteDuration}\n\n\n/**\n * Extension providing all settings available to the application:\n * - Monitor\n *\n */\ncase class DataSourceSettings(\n  refreshPeriod:  FiniteDuration,\n  datasourceReader : Class[DataSource]\n)\n  extends Extension\n{\n  def isEnabled() = (refreshPeriod != Duration.Zero)\n}\n\n\nobject DataSourceSettings {\n\n\n  // Initialization Constructor\n  def apply(c: Config): DataSourceSettings =\n    DataSourceSettings(\n      c getOptionalDuration \"refresh-period\" getOrElse Duration.Zero,\n      c getClass \"datasource-reader\"\n    )\n\n  def getDataSourceObject(): Unit = {\n\n  }\n\n}\n"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/cluster/SLBLoader.scala",
    "content": "package com.ebay.neutrino.cluster\n\nimport akka.actor.Actor\nimport com.ebay.neutrino.config.{LoadBalancer, Configuration}\nimport com.typesafe.scalalogging.slf4j.StrictLogging\n\nimport scala.concurrent.duration._\nimport scala.util.{Failure, Success}\nimport com.ebay.neutrino.datasource.DataSource\n\n\nclass SLBLoader extends Actor with StrictLogging {\n  import context.dispatcher\n\n  // Create a new SLB Configuration based off the file\n  // Note that the system configuration is pulled from common.conf\n  val config  = SystemConfiguration(context.system)\n  val dataSourceReader  = config.settings.dataSource.datasourceReader.getConstructor().newInstance()\n\n\n\n  // Schedule a configuration reload\n  override def preStart() {\n    context.system.scheduler.schedule(5 seconds, config.settings.dataSource.refreshPeriod, self, \"reload\")\n  }\n\n\n  def receive: Receive = {\n    case \"reload\" =>\n      // Create a new SLB configuration\n      val results = dataSourceReader.load();\n      logger.info(\"Reloading the configuration: {}\")\n      config.topology.update(results)\n      sender ! \"complete\"\n\n    case \"complete\" =>\n      logger.info(\"Reloading of configuration complete\")\n\n    case msg =>\n      logger.warn(\"Unexpected message received: {}\", msg.toString)\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/cluster/SLBTopology.scala",
    "content": "package com.ebay.neutrino.cluster\n\nimport com.ebay.neutrino.metrics.Instrumented\nimport com.ebay.neutrino.{NeutrinoPoolId, NeutrinoCore}\nimport com.ebay.neutrino.channel.NeutrinoService\nimport com.ebay.neutrino.config.{LoadBalancer, CanonicalAddress, VirtualPool, Transport}\nimport com.ebay.neutrino.util.DifferentialStateSupport\n\n\nclass SLBTopology(val core: NeutrinoCore)\n  extends DifferentialStateSupport[NeutrinoPoolId, VirtualPool]\n  with Iterable[VirtualPool]\n  with Instrumented {\n\n  type Port = Int\n  type Key  = (Port, Transport)\n\n  protected override def key(pool: VirtualPool): NeutrinoPoolId =\n    NeutrinoPoolId(pool.id.toLowerCase, pool.protocol)\n  protected override def addState(added: VirtualPool): Unit = {}\n  protected override def removeState(remove: VirtualPool): Unit = {}\n  protected override def updateState(pre: VirtualPool, post: VirtualPool): Unit = {}\n\n  // Iterator over available pools\n  override def iterator: Iterator[VirtualPool] = state.iterator\n\n\n  // Split our services into (source-port, protocol) tuples\n  val services: Map[Key, NeutrinoService] =\n    core.services flatMap { service =>\n      val proto = service.settings.protocol\n      val ports = service.settings.sourcePorts\n      val pairs = ports map ((_, proto))\n\n      // Finally, map pairs to services\n      pairs map (_ -> service)\n    } toMap\n\n\n  // Create a gauge for each service...\n  core.services map { service =>\n    // Grab the port as a descriptor of the service\n    val portstr = service.settings.addresses.head.port //map (_.port) mkString \"_\"\n    val pools = service.pools\n\n    // Create pools- and servers-gauge for the service\n    metrics.gauge(\"pools\", portstr.toString)   { pools.size }\n    metrics.gauge(\"servers\", portstr.toString) { pools.foldLeft(0) { (sum, pair) => sum + pair.servers.size }}\n  }\n\n\n  /**\n   * Hook our parent update to delegate the update-call to the core as well.\n   *\n   * We currently use a naive implementation:\n   *  - just find the first matching from-port/protocol\n   *      (eventually, we want to be able to handle multiple matches)\n   */\n  override def update(values: VirtualPool*) = {\n    // Update our internal state first\n    super.update(values:_*)\n\n    // Split per service and update accordingly, using configured CNAMEs\n    val resolved = values flatMap { pool =>\n      //\n      pool.addresses collectFirst {\n        case addr: CanonicalAddress if services isDefinedAt ((addr.port, pool.protocol)) =>\n          services((pool.port, pool.protocol)) -> pool\n      }\n    }\n\n    // Group per-service\n    val grouped = resolved groupBy (_._1) mapValues (_ map (_._2))\n\n    // Update the services directly with their relevant pools\n    grouped map {\n      case (service, pools) => service.update(pools:_*)\n    }\n  }\n\n  def update(lb: LoadBalancer): Unit = {\n    // Update the pool configuration (subclasses will cascade changes)\n    update(lb.pools:_*)\n  }\n\n  def getPool(id: NeutrinoPoolId): Option[VirtualPool] =\n    state collectFirst {\n      case pool @ VirtualPool(id.id, _, id.transport, _, _, _, _, _) => pool }\n}\n"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/cluster/SystemSettings.scala",
    "content": "package com.ebay.neutrino.cluster\n\nimport java.io.File\n\nimport akka.actor._\nimport com.ebay.neutrino.NeutrinoCore\nimport com.ebay.neutrino.config.Configuration._\nimport com.ebay.neutrino.config.{Configuration, NeutrinoSettings}\nimport com.typesafe.config.{ConfigFactory, Config}\n\n\ncase class SystemSettings(\n  enableApi:    Boolean,\n  neutrino:     NeutrinoSettings,\n  dataSource:   DataSourceSettings\n)\n\nobject SystemSettings {\n\n  // This config is already at 'ebay.neutrino'\n  def apply(config: Config): SystemSettings =\n    SystemSettings(\n      config getBoolean \"enable-api\",\n      NeutrinoSettings(config),\n      DataSourceSettings(config getConfig \"datasource\")\n    )\n}\n\n\ncase class SystemConfigurationExtension(system: ExtendedActorSystem) extends Extension\n{\n  // Extract 'ebay.neutrino' config\n  val config = Configuration.load(system.settings.config, \"resolvers\")\n\n  // Load system-settings (including all component settings)\n  val settings = SystemSettings(config)\n\n  // Initialize our Neutrino-core\n  val core = new NeutrinoCore(settings.neutrino)\n\n  // Our use-specific state cluster topology (customized for SLB)\n  val topology = {\n    new SLBTopology(core)\n  }\n\n\n}\n\n/**\n * System-configuration extension for both Monitor and SLB.\n */\nobject SystemConfiguration extends ExtensionId[SystemConfigurationExtension] with ExtensionIdProvider {\n\n  /** Cache our common configuration */\n  private val common = ConfigFactory.load(\"slb.conf\")\n\n  override def lookup() = SystemConfiguration\n\n  override def createExtension(system: ExtendedActorSystem) = SystemConfigurationExtension(system)\n\n  def load(filename: String): Config =\n    filename match {\n      case null => common\n      case file => val slbFile = new File(filename)\n        val slbConfig = ConfigFactory.parseFile(slbFile)\n        if (slbConfig.isEmpty) {\n          common\n        } else {\n          ConfigFactory.load(slbConfig)\n        }\n    }\n\n  def system(filename: String): ActorSystem =\n    ActorSystem(\"slb-cluster\", load(filename))\n\n  // Create an actor-system and return the attached configuration, all in one\n  def apply(filename: String): SystemConfigurationExtension =\n    SystemConfiguration(system(filename))\n\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/config/Configuration.scala",
    "content": "package com.ebay.neutrino.config\n\nimport java.io.File\nimport java.lang.reflect.Constructor\nimport java.net.{URI, URL}\nimport java.util.concurrent.TimeUnit\n\nimport com.typesafe.config.{Config, ConfigFactory, ConfigList, ConfigValue}\nimport com.typesafe.scalalogging.slf4j.StrictLogging\n\nimport scala.util.{Failure, Success, Try}\n\n\n/**\n * Configuration static helper methods.\n */\ntrait HasConfiguration { def config: Config }\n\n\nobject Configuration extends StrictLogging {\n  import scala.collection.JavaConversions._\n  import scala.concurrent.duration._\n\n\n  /**\n   * Load the configuration from the filename and environment provided.\n   *\n   * @param filename\n   * @param environment\n   */\n  def load(filename: String = null, environment: String = null, loader: ClassLoader = null): Config = {\n\n    val basecfg = (filename, loader) match {\n      case (null, null) =>\n        logger.info(\"Loading default configuration.\")\n        ConfigFactory.load()\n      case (null, _) =>\n        logger.info(\"Loading default configuration with provided ClassLoader.\")\n        ConfigFactory.load(loader)\n      case (_, null) =>\n        logger.info(\"Loading configuration from file {}\", filename)\n        val slbFile = new File(filename)\n        val slbConfig = ConfigFactory.parseFile(slbFile)\n        if (slbConfig.isEmpty) {\n          ConfigFactory.load(filename)\n        } else {\n          ConfigFactory.load(slbConfig)\n        }\n      case _ =>\n        logger.info(\"Loading configuration from file {} using provided ClassLoader\", filename)\n        ConfigFactory.load(loader, filename)\n    }\n\n    load(basecfg, environment)\n  }\n\n  /**\n   * Load the configuration from the concrete config-object and environment provided.\n   *\n   * @param basecfg\n   * @param environment\n   */\n  def load(basecfg: Config, environment: String): Config = {\n\n    // If environment is provided, attempt to extract the environment-specific subtree\n    val envcfg = Option(environment) match {\n      case Some(envpath) if basecfg.hasPath(envpath) =>\n        logger.warn(\"Merging with environmental configuration {}\", envpath)\n        basecfg.getConfig(envpath) withFallback basecfg\n      case Some(envpath) =>\n        logger.error(\"Unable to merge with environmental configuration {}; not found\", envpath)\n        basecfg\n      case None =>\n        basecfg\n    }\n\n    // Extract our concrete configuration from the ebay-neutrino tree\n    val neutrino = envcfg.getConfig(\"neutrino\")\n\n    // Finally, re-stitch together the timeout defaults\n    val timeout = neutrino.getConfig(\"timeout\")\n\n    neutrino\n      .withValue(\"pool.timeout\", neutrino.getValue(\"pool.timeout\").withFallback(timeout))\n  }\n\n\n  implicit class ConfigSupport(val self: Config) extends AnyVal {\n\n    // Resolve the constructor provided, skipping unavailable ones\n    def constructor[T](ctor: => Constructor[T]): Option[Constructor[T]] =\n      Try(ctor) match {\n        case Success(ctor) => Option(ctor)\n        case Failure(x: NoSuchMethodException) => None\n        case Failure(x) => throw x\n      }\n\n    def optional[T](path: String, f: String => T): Option[T] = self hasPath path match {\n      case true => Option(f(path))\n      case false => None\n    }\n\n\n    def getIntOrList(path: String): Seq[Int] = self getValue path match {\n      case list: ConfigList => (self getIntList path map (_.toInt)).toList\n      case _ => Seq(self getInt path)\n    }\n\n    def getStringOrList(path: String): Seq[String] = self getOptionalValue path match {\n      case None => Seq()\n      case Some(list: ConfigList) => (self getStringList path).toList\n      case Some(_) => Seq(self getString path)\n    }\n\n    def getMergedConfigList(path: String, defaultPath: String): List[_ <: Config] = {\n      val default = self getConfig defaultPath\n      self getConfigList(path) map (_ withFallback default) toList\n    }\n\n    def getDuration(path: String): FiniteDuration =\n      self getDuration(path, TimeUnit.NANOSECONDS) nanos\n\n    def getProtocol(path: String): Transport =\n      Transport(self getString path)\n\n    def getTimeouts(path: String): TimeoutSettings =\n      TimeoutSettings(self getConfig path)\n\n    def getUrl(path: String): URL =\n      new URL(self getString path)\n\n    def getUri(path: String): URI =\n      URI.create(self getString path)\n\n    def getClass[T](path: String): Class[T] =\n      Class.forName(self getString path).asInstanceOf[Class[T]]\n\n    def getClassList[T](path: String): List[Class[T]] =\n      self getStringList path map (Class.forName(_).asInstanceOf[Class[T]]) toList\n\n    def getClassInstances[T](path: String): List[T] =\n      self getStringOrList path map (Class.forName(_).newInstance().asInstanceOf[T]) toList\n\n    def getInstance[T](path: String): Option[T] =\n      constructor(Class.forName(self getString path).getConstructor()) map (_.newInstance().asInstanceOf[T])\n\n    def getConfigInstance[T](path: String): Option[T] =   // Configuration-aware instance\n      constructor(Class.forName(self getString path).getConstructor(classOf[Config])) map (_.newInstance(self).asInstanceOf[T])\n\n\n\n    def getOptionalValue(path: String): Option[ConfigValue] =\n      optional(path, self getValue)\n\n    def getOptionalConfig(path: String) =\n      optional(path, self getConfig)\n\n    def getOptionalInt(path: String) =\n      optional(path, self getInt)\n\n    def getOptionalString(path: String) =\n      optional(path, self getString)\n\n    def getOptionalConfigList(path: String): List[_ <: Config] =\n      optional(path, self getConfigList) map (_.toList) getOrElse List()\n\n    def getOptionalDuration(path: String): Option[FiniteDuration] =\n      optional(path, getDuration) filterNot (_ == Duration.Zero)\n\n    def getOptionalClass[T](path: String): Option[Class[T]] =\n      optional(path, self getString) map (Class.forName(_).asInstanceOf[Class[T]])\n\n    def getOptionalClassList[T](path: String): List[Class[T]] =\n      optional(path, getClassList[T]) getOrElse List[Class[T]]()\n\n    def getOptionalInstance[T](path: String): Option[T] =\n      optional(path, self getInstance).flatten\n\n    def getOptionalConfigInstance[T](path: String): Option[T] =\n      optional(path, self getConfigInstance).flatten\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/config/ListenerAddress.scala",
    "content": "package com.ebay.neutrino.config\n\nimport java.net.InetSocketAddress\n\nimport com.ebay.neutrino.PoolResolver\nimport com.ebay.neutrino.config.Configuration._\nimport com.typesafe.config.Config\nimport io.netty.channel.ChannelHandler\n\nimport scala.concurrent.duration.Duration\n\n/**\n * An individual listener/port/transport tuple.\n * Note that listener-interfaces are transport specific; we duplicate for ease of resolution.\n */\ncase class ListenerAddress(host: String, port: Int, protocol: Transport) extends VirtualAddress\n{\n  // Expose VirtualAddress as socket-address (?? Does this cache ??)\n  lazy val socketAddress = new InetSocketAddress(host, port)\n}\n\n\n/**\n * Representation of a VIP/Interface Address/Listener.\n *\n * Note that LBaaS models LoadBalancer with the address and Listener (VIP) as port/protocol\n * only LBMS models VIP as containing both address/port.\n *\n * Validation:\n * - Host: Needs to be an IP or DNS address, but is expensive.\n *    @see http://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address\n * - Port: 0..65535\n *\n * @param addresses\n * @param protocol\n * @param handlers\n * @param poolResolver\n * @param timeouts\n */\ncase class ListenerSettings(\n  addresses:      Seq[ListenerAddress],\n  protocol:       Transport,\n  sourcePorts:    Seq[Int],\n  handlers:       Seq[_ <: ChannelHandler],\n  poolResolvers:  Seq[_ <: PoolResolver],\n  channel:        ChannelSettings,\n  timeouts:       TimeoutSettings\n)\n\nobject ListenerSettings {\n  import com.ebay.neutrino.config.Configuration._\n\n\n  // ListenerSettings factory type; this is the preferred construction\n  def apply(cfg: Config) = {\n    // Build a set of addresses around\n    val hosts = cfg getStringOrList \"host\"\n    val ports = cfg getIntOrList \"port\"\n    val alias = cfg getIntOrList \"port-alias\"\n    val proto = cfg getProtocol \"protocol\"\n\n    // Get the cross-product of host:port\n    val addresses = for { host <- hosts; port <- ports } yield ListenerAddress(host, port, proto)\n\n    new ListenerSettings(\n      addresses,\n      proto,\n      ports ++ alias,\n      cfg getClassInstances \"pipeline-class\",\n      cfg getStringOrList \"pool-resolver\" map (PoolResolver(_)),\n      cfg getOptionalConfig \"channel-options\" map (ChannelSettings(_)) getOrElse ChannelSettings.Default,\n      cfg getTimeouts \"timeout\"\n    ) with\n      HasConfiguration { override val config: Config = cfg }\n  }\n\n\n  // Factory type; create with some defaults.\n  def apply(\n    addresses:      Seq[ListenerAddress]=Seq(),\n    protocol:       Transport=Transport.HTTP,\n    sourcePorts:    Seq[Int]=Seq(),\n    handlers:       Seq[_ <: ChannelHandler]=Seq(),\n    poolResolvers:  Seq[_ <: PoolResolver]=Seq()): ListenerSettings =\n  {\n    ListenerSettings(addresses, protocol, sourcePorts, handlers, poolResolvers, ChannelSettings.Default, TimeoutSettings.Default)\n  }\n\n}\n\n\n/**\n * Representation of downstream channel settings.\n *\n */\ncase class ChannelSettings(\n  forceKeepAlive:   Boolean,\n  auditThreshold:   Option[Duration]\n)\n\nobject ChannelSettings {\n\n  val Default = ChannelSettings(true, None)\n\n\n  def apply(cfg: Config) =\n    new ChannelSettings(\n      cfg getBoolean \"force-keepalive\",\n      cfg getOptionalDuration \"audit-threshold\"\n    )\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/config/LoadBalancer.scala",
    "content": "package com.ebay.neutrino.config\n\nimport com.typesafe.config.Config\n\n/**\n * LoadBalancer ~= VIP object\n *  we depict merged with 'Listener' type from DB\n *\n *\n * Missing attributes from LoadBalancer:\n *  - id: Long    not persisted yet; will be rquired for DB backing\n *  - name: String\n *  - description: String\n *  - vip_port_id\n *  - vip_subnet_id\n *  - vip_address\n *  - tenant_id\n *  -\n */\ncase class LoadBalancer(id: String, pools: Seq[VirtualPool])\n\n\nobject LoadBalancer {\n  import scala.collection.JavaConversions._\n\n  // LoadBalancer configuration factory; create a group of VIP configurations\n  def apply(config: Seq[Config]): Seq[LoadBalancer] = config map (LoadBalancer(_))\n\n\n  // LoadBalancer configuration factory; create a single VIP configuration\n  // TODO support for 'default'\n  def apply(cfg: Config): LoadBalancer = {\n    // Build out the subconfig lists, merging with defautls\n    val name  = \"default\" //config getString \"name\"\n    val pool  = cfg getConfig \"pool\"\n    val pools = cfg getConfigList \"pools\" map (c => VirtualPool(c withFallback pool))\n\n    // Initialize the lifecycle-inits and wrap the load-balancer\n    new LoadBalancer(name, pools) with HasConfiguration { def config = cfg }\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/config/README.md",
    "content": "# Neutrino Load Balancer - Configuration\n\n[Project Documentation - Including design and features](wiki.vip.corp.ebay.com/display/neutrino)\n\n## Configuring SLB or ESB\n\nWe use (primarily) the [Typesafe Configuration library](https://github.com/typesafehub/config), which uses a cascading configuration resolved by classpath.\n\nIn essence, the core provides a set of default configuration values in reference.conf, and each application can override with:\n1) Their own reference.conf or application.conf files in the root of their classpath\n2) An application.conf file in the runtime working directory\n3) A custom .conf at a user-defined location within the user's classpath."
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/config/Settings.scala",
    "content": "package com.ebay.neutrino.config\n\nimport java.lang.reflect.Constructor\nimport java.net.URI\n\nimport com.ebay.neutrino.NeutrinoLifecycle\nimport com.ebay.neutrino.balancer.{Balancer, LeastConnectionBalancer, RoundRobinBalancer, WeightedRoundRobinBalancer}\nimport com.ebay.neutrino.metrics.HealthMonitor\nimport com.ebay.neutrino.util.Utilities._\nimport com.typesafe.config.Config\n\nimport scala.concurrent.duration.Duration\nimport scala.util.{Failure, Success, Try}\n\n\n/**\n * Extension providing all settings available to the application:\n * - HttpServer/Frontend Server\n * - LoadBalancer\n * - Daemon\n * - Echo-Server\n *\n * Note that these settings do not include the 'runtime' pool configuration. (LoadBalancer)\n * They need to be populated separately (as they can be set dynamically)\n */\ncase class NeutrinoSettings(\n  interfaces:           Seq[ListenerSettings],\n  lifecycleListeners:   Seq[NeutrinoLifecycle],\n  defaultTimeouts:      TimeoutSettings,\n  supervisorThreadCount:Int,\n  workerThreadCount:    Int\n)\n\n\n/**\n * Core Load-Balancer settings\n */\nobject NeutrinoSettings {\n  import com.ebay.neutrino.config.Configuration._\n\n  import scala.collection.JavaConversions._\n\n  // Try a one-parameter ctor for settings\n  def createLifecycle(lifecycleClass: Class[_ <: NeutrinoLifecycle], c: Config): Option[NeutrinoLifecycle] =\n    constructor(lifecycleClass.getConstructor(classOf[Config])) map (_.newInstance(c))\n\n  // Try a default (no-param) setting\n  def createLifecycle(lifecycleClass: Class[_ <: NeutrinoLifecycle]): Option[NeutrinoLifecycle] =\n    constructor(lifecycleClass.getConstructor()) map (_.newInstance())\n\n  // Resolve the constructor provided, skipping unavailable ones\n  def constructor[T](ctor: => Constructor[T]): Option[Constructor[T]] =\n    Try(ctor) match {\n      case Success(ctor) => Option(ctor)\n      case Failure(x: NoSuchMethodException) => None\n      case Failure(x) => throw x\n    }\n\n\n  // Empty configuration for mocking/empty setup\n  val Empty = NeutrinoSettings(Seq(), Seq(), TimeoutSettings.Default, 1, 4)\n\n\n  // Create a new global LoadBalancer application setting object.\n  // For now, just support 'default'\n  def apply(cfg: Config) = {\n    // Load config-wide defaults\n    val listener = cfg getConfig(\"listener\")\n    val classes: List[Class[NeutrinoLifecycle]] = cfg getOptionalClassList \"initializers\"\n    val instances = classes flatMap (cls => createLifecycle(cls, cfg) orElse createLifecycle(cls))\n\n    new NeutrinoSettings(\n      cfg getConfigList \"listeners\" map (_ withFallback listener) map (ListenerSettings(_)),\n      instances,\n      cfg getTimeouts \"timeout\",\n      cfg getInt  \"supervisorThreadCount\",\n      cfg getInt  \"workerThreadCount\"\n\n    )\n  }\n\n}\n\n\n/**\n * Daemon/End-Node Settings\n *\n *\n */\ncase class DaemonSettings(endpoint: URI)\n{\n  val host = endpoint.validHost\n  val port = endpoint.validPort(80)\n  val isSecure = endpoint.isSecure()\n}\n\nobject DaemonSettings {\n\n  def apply(c: Config): DaemonSettings =\n    DaemonSettings(\n      URI.create(c getString \"endpoint\")\n    )\n}\n\n\n\n// Representation of a Health Monitor/Settings\ncase class HealthSettings(monitorType: String, path: URI, monitor: Option[HealthMonitor]) {\n  require(path.getHost == null && path.getPort == -1 && path.getAuthority == null,\n    \"URL Path/Port/Authority not supported - only path should be set\")\n\n}\n\nobject HealthSettings {\n  import com.ebay.neutrino.config.Configuration._\n\n  // Try a one-parameter ctor for settings\n  def createMonitor(monitorClass: Class[_ <: HealthMonitor], c: Config): Try[HealthMonitor] =\n    Try(monitorClass.getConstructor(classOf[HealthSettings]).newInstance(c))\n\n  // Try a default (no-param) setting\n  def createMonitor(monitorClass: Class[_ <: HealthMonitor]): Try[HealthMonitor] =\n    Try(monitorClass.newInstance())\n\n\n  // HealthMonitor configuration factory\n  def apply(config: Config): HealthSettings = {\n    val clazz: Option[Class[HealthMonitor]] = config getOptionalClass \"monitor-class\"\n\n    HealthSettings(\n      \"type\",\n      config getUri \"url\",\n      clazz flatMap (cls => (createMonitor(cls, config) orElse createMonitor(cls)).toOption)\n    )\n  }\n}\n\n\n\ncase class TimeoutSettings(\n  readIdle:           Duration,\n  writeIdle:          Duration,\n  writeCompletion:    Duration,\n  requestCompletion:  Duration,\n  sessionCompletion:  Duration,\n  connectionTimeout:  Duration\n)\n{\n  require(!readIdle.isFinite() || readIdle != Duration.Zero)\n  require(!writeIdle.isFinite() || writeIdle != Duration.Zero)\n  require(!writeCompletion.isFinite() || writeCompletion != Duration.Zero)\n  require(!requestCompletion.isFinite() || requestCompletion != Duration.Zero)\n  require(!sessionCompletion.isFinite() || sessionCompletion != Duration.Zero)\n  require(!sessionCompletion.isFinite() || sessionCompletion != Duration.Zero)\n}\n\n\nobject TimeoutSettings {\n  import com.ebay.neutrino.config.Configuration._\n\n  import scala.concurrent.duration.Duration.Undefined\n  import scala.concurrent.duration._\n\n  val Default = TimeoutSettings(60 seconds, 60 seconds, 10 seconds, 2 minutes, 2 minutes, 5 seconds)\n  val NoTimeouts = TimeoutSettings(Undefined, Undefined, Undefined, Undefined, Undefined, Undefined)\n\n  def apply(config: Config): TimeoutSettings = TimeoutSettings(\n    config getOptionalDuration \"read-idle-timeout\"  getOrElse Undefined,\n    config getOptionalDuration \"write-idle-timeout\" getOrElse Undefined,\n    config getOptionalDuration \"write-timeout\"      getOrElse Undefined,\n    config getOptionalDuration \"request-timeout\"    getOrElse Undefined,\n    config getOptionalDuration \"session-timeout\"    getOrElse Undefined,\n    config getOptionalDuration \"connection-timeout\" getOrElse Undefined\n  )\n}\n\n\nimport scala.language.existentials\ncase class BalancerSettings(clazz: Class[_ <: Balancer], config: Option[Config])\n{\n  require(NeutrinoLifecycle.hasConstructor(clazz),\n    \"Balancer class must expose either a no-arg constructor or a Config constructor.\")\n}\n\nobject BalancerSettings {\n\n  // Static balancers available\n  val RoundRobin = BalancerSettings(classOf[RoundRobinBalancer], None)\n  val WeightedRoundRobin = BalancerSettings(classOf[WeightedRoundRobinBalancer], None)\n  val LeastConnection = BalancerSettings(classOf[LeastConnectionBalancer], None)\n\n  // Default to Round-Robin scheduling with no additional configuation\n  val Default = RoundRobin\n\n  /**\n   * Select a load-selection mechanism.\n   *\n   * Attempt to resolve class for scheduler.\n   * If it's known, use the class directly. Otherwise, attempt to resolve.\n   *\n   * @param balancer\n   */\n  def apply(balancer: String): BalancerSettings =\n    balancer toLowerCase match {\n      case \"rr\" | \"round-robin\" => RoundRobin\n      case \"wrr\" | \"weighted-round-robin\" => WeightedRoundRobin\n      case \"lc\" | \"least-connection\" => LeastConnection\n      case className =>\n        BalancerSettings(\n          Class.forName(balancer).asInstanceOf[Class[Balancer]],\n          None\n        )\n    }\n\n  // def apply(config: Config): BalancerSettings = ...\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/config/Types.scala",
    "content": "package com.ebay.neutrino.config\n\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.handler.codec.http.HttpResponse\n\n/**\n * This file provides enumeration types.\n *\n */\n// Transport protocol support\nsealed trait Transport\n\nobject Transport extends StrictLogging {\n\n  // Subtype\n  case object HTTP  extends Transport { override val toString = \"http\" }\n  case object HTTPS extends Transport { override val toString = \"https\" }\n  case object ZMQ   extends Transport { override val toString = \"zeromq\" }\n  case class Unknown(value: String) extends Transport { override val toString = value }\n\n  // Resolve a transport-protocol type from the string provided.\n  def apply(protocol: String): Transport =\n    protocol.toLowerCase match {\n      case \"http\" => HTTP\n      case \"https\" | \"ssl\" => HTTPS\n      case \"zmq\"  => ZMQ\n      case lc =>\n        logger.error(\"Protocol '{}' not supported\", protocol)\n        Unknown(protocol)\n    }\n}\n\n\n\n/**\n * Encapsulated request-completion status enumeration/bucketing support.\n */\nsealed trait CompletionStatus\n\nobject CompletionStatus {\n\n  // Supported types\n  case object Status2xx  extends CompletionStatus\n  case object Status4xx  extends CompletionStatus\n  case object Status5xx  extends CompletionStatus\n  case object Other      extends CompletionStatus\n  case object Incomplete extends CompletionStatus\n\n  // Extract/bucket a response\n  def apply(response: Option[HttpResponse]): CompletionStatus =\n\n    (response map (_.status.code / 100) getOrElse 0) match {\n      case 0 => Incomplete\n      case 2 => Status2xx\n      case 4 => Status4xx\n      case 5 => Status5xx\n      case _ => Other\n    }\n}\n\n// Heath-state types\nsealed trait HealthState\nobject HealthState {\n\n  // Supported types\n  case object Healthy extends HealthState\n  case object Unhealthy extends HealthState\n  case object Probation extends HealthState\n  case object Maintenance extends HealthState\n  case object Error extends HealthState\n  case object Unknown extends HealthState\n  case class  Other(value: String) extends HealthState\n}\n\n\n/**\n * Data wrapper classes\n */\ncase class Host(host: String, port: Int)\n{\n  require(port >= 0 && (port >> 16) == 0, \"Illegal port: \" + port)\n  require(host == host.toLowerCase, \"Host should be normalized (to lower case)\")\n\n  def hasPort() = port > 0\n}\n\nobject Host {\n\n  // Create a host-object out of the message provided\n  def apply(host: String): Host =\n    host.split(\":\") match {\n      case Array(host)       => Host(host, 0)\n      case Array(host, port) => Host(host, port.toInt)\n      case array             => throw new IllegalArgumentException(s\"Bad split for host $host: $array\")\n    }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/config/VirtualAddress.scala",
    "content": "package com.ebay.neutrino.config\n\nimport java.net.{InetSocketAddress, SocketAddress}\n\nimport com.typesafe.config.Config\n\n\n/**\n * Representation of a VIP/Interface Address/Listener.\n *\n */\ntrait VirtualAddress {\n\n  def socketAddress: SocketAddress\n}\n\nobject VirtualAddress {\n\n  // Borrowed from http://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address\n  val ValidIpAddressRegex = \"\"\"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$\"\"\".r\n  val ValidHostnameRegex  = \"\"\"^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$\"\"\".r\n\n  // Determine if the IP provided is valid\n  @inline def isIPValid(ip: String) = ValidIpAddressRegex.pattern.matcher(ip).matches\n\n  // Determine if the hostname provided is valid (note - will match IPs loosely too)\n  @inline def isHostnameValid(hostname: String) = ValidHostnameRegex.pattern.matcher(hostname).matches\n}\n\n\n/**\n * Note that LBaaS models LoadBalancer with the address and Listener (VIP) as port/protocol\n * only LBMS models VIP as containing both address/port.\n *\n * Validation:\n * - Host: Needs to be an IP or DNS address, but is expensive.\n *    @see http://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address\n * - Port: 0..65535\n *\n * @param host\n * @param port\n * @param protocol\n * @param handlers\n * @param timeouts\n */\ncase class CanonicalAddress(host: String, port: Int, protocol: Transport, timeouts: TimeoutSettings)\n  extends VirtualAddress\n{\n  require(host.nonEmpty, \"Host is required and was not provided\")\n  require(VirtualAddress.isHostnameValid(host), s\"Host provided '$host' is not valid\")\n\n  lazy val socketAddress: SocketAddress = new InetSocketAddress(host, port)\n}\n\n\nobject CanonicalAddress {\n  import com.ebay.neutrino.config.Configuration._\n\n\n  // VIP parent-configuation factory; this is the preferred way\n  def apply(cfg: Config, port: Int, protocol : Transport): CanonicalAddress =\n    new CanonicalAddress(\n      cfg getString \"host\",\n      port,\n      protocol,\n      TimeoutSettings.Default\n    ) with\n      HasConfiguration { override val config: Config = cfg }\n\n  // VIP configuration factory; factory initializer\n  def apply(host: String=\"localhost\", port: Int=8080, protocol: Transport=Transport.HTTP) =\n    new CanonicalAddress(host, port, protocol, TimeoutSettings.Default)\n\n}\n\n\n/**\n * VIP Address representation that can be applied to URI-based filtering.\n *\n * @param host\n * @param path\n */\ncase class WildcardAddress(host: String, path: String, port : Int)\n  extends VirtualAddress\n{\n  require(host.nonEmpty, \"Host is required and was not provided\")\n  require(VirtualAddress.isHostnameValid(host), s\"Host provided '$host' is not valid\")\n\n  lazy val socketAddress: SocketAddress = new InetSocketAddress(host, port)\n\n\n}\n\nobject WildcardAddress {\n  import com.ebay.neutrino.config.Configuration._\n\n\n  def apply(cfg: Config, port: Int): WildcardAddress =\n    new WildcardAddress(\n      cfg getString \"host\",\n      cfg getString \"path\",\n      port\n    ) with\n      HasConfiguration { override val config: Config = cfg }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/config/VirtualPool.scala",
    "content": "package com.ebay.neutrino.config\n\nimport com.ebay.neutrino.config.Configuration._\nimport com.typesafe.config.Config\n\nimport scala.collection.mutable\n\n\n/**\n * Representation of a Pool\n */\ncase class VirtualPool(\n  id:         String,\n  port:       Int,\n  protocol:   Transport,\n  servers:    Seq[VirtualServer],\n  addresses:  Seq[VirtualAddress],\n  health:     Option[HealthSettings],\n  balancer:   BalancerSettings,\n  timeouts:   TimeoutSettings\n)\n{\n  require(servers.map(_.id).distinct.size == servers.size, \"Server IDs must be unique\")\n}\n\n\nobject VirtualPool {\n  import scala.collection.JavaConversions._\n\n\n  // Pool configuration factory\n  def apply(servers: VirtualServer*): VirtualPool =\n    VirtualPool(\"default\", Transport.HTTP, servers)\n\n  // Pool/Protocol configuration factory\n  def apply(protocol: Transport, servers: VirtualServer*): VirtualPool =\n    VirtualPool(\"default\", protocol, servers)\n\n  // Defaulted values configuration factor\n  def apply(id: String=\"default\", protocol: Transport=Transport.HTTP, servers: Seq[VirtualServer]=Seq(), address: Seq[CanonicalAddress]=Seq(), port: Int=80): VirtualPool =\n    new VirtualPool(id, port, protocol, servers, address, None, BalancerSettings.Default, TimeoutSettings.Default)\n\n  // Create with parent configuration deafults; this is the preferred way\n  def apply(cfg: Config): VirtualPool = {\n\n    var addresses: mutable.Buffer[CanonicalAddress] = mutable.Buffer()\n    // Default the port to 80 if it is not specified\n    val port = if (cfg hasPath \"port\") cfg getInt \"port\" else 80\n    // Default the protocol to http if it is not specified\n    val protocol = if (cfg hasPath \"protocol\") cfg getProtocol \"protocol\" else Transport.HTTP\n    if (cfg hasPath \"addresses\") {\n      addresses = cfg getConfigList \"addresses\" map (CanonicalAddress(_, port, protocol))\n    }\n\n\n    var wildcardAddresses: mutable.Buffer[WildcardAddress] = mutable.Buffer()\n    if (cfg hasPath \"wildcard\") {\n      wildcardAddresses = cfg getConfigList \"wildcard\" map (WildcardAddress(_, port))\n    }\n\n\n    new VirtualPool(\n      cfg getString \"id\",\n      port,\n      protocol,\n      cfg getConfigList \"servers\" map (VirtualServer(_)),\n      addresses ++ wildcardAddresses,\n      cfg getOptionalConfig \"health\" map (HealthSettings(_)),\n      cfg getOptionalString \"balancer\" map (BalancerSettings(_)) getOrElse BalancerSettings.Default,\n      cfg getTimeouts \"timeout\"\n    ) with\n      HasConfiguration {\n      override val config: Config = cfg\n    }\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/config/VirtualServer.scala",
    "content": "package com.ebay.neutrino.config\n\nimport java.net.InetSocketAddress\n\nimport com.typesafe.config.Config\n\n\n// Representation of a Server\ncase class VirtualServer(\n  id: String,\n  host: String,\n  port: Int,\n  weight: Option[Int] = None,\n  health: Option[HealthSettings] = None\n)\n  extends VirtualAddress {\n  // Expose VirtualAddress as socket-address (?? Does this cache ??)\n  lazy val socketAddress = new InetSocketAddress(host, port)\n\n  // Mutable health state\n  @transient var healthState: HealthState = HealthState.Unknown\n}\n\n\nobject VirtualServer {\n\n  import Configuration._\n\n\n  /**\n    * VirtualServer configuration factory.\n    */\n  def apply(cfg: Config): VirtualServer =\n    new VirtualServer(\n      cfg getOptionalString \"id\" getOrElse (cfg getString \"host\"), // fallback to 'host'\n      cfg getString \"host\",\n      cfg getInt \"port\",\n      if (cfg hasPath \"weight\") Option(cfg getInt \"weight\") else None\n    ) with\n      HasConfiguration {\n      override val config: Config = cfg\n    }\n}\n\n\n// Representation of a Backend Service\n//case class Service()\n"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/datasource/DataSource.scala",
    "content": "package com.ebay.neutrino.datasource\n\n\nimport com.ebay.neutrino.config.{LoadBalancer, Configuration}\nimport com.typesafe.config.Config\n\n/**\n * Created by blpaul on 2/24/2016.\n */\ntrait DataSource {\n  // refresh the datasource\n  def load() : LoadBalancer\n\n}\n\n\nclass FileReader extends DataSource {\n\n  override def load(): LoadBalancer = {\n\n    val results = Configuration.load(\"/etc/neutrino/slb.conf\", \"resolvers\")\n    LoadBalancer(results)\n\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/handler/NeutrinoClientHandler.scala",
    "content": "package com.ebay.neutrino.handler\n\nimport java.io.IOException\nimport java.util.ArrayDeque\n\nimport com.ebay.neutrino.channel.NeutrinoSession\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.channel._\nimport io.netty.handler.codec.http._\n\n\n/**\n * A downstream/client handler for the post-pipeline version of the outer channel handler.\n *\n * In its current form, this handler exists only to manage the current state of the downstream\n * channel, and facilitate management from the outside (ie: during allocation/release)\n *\n */\nclass NeutrinoClientHandler extends ChannelDuplexHandler with StrictLogging\n{\n  import ChannelFutureListener.CLOSE_ON_FAILURE\n  import com.ebay.neutrino.util.AttributeSupport._\n\n  /** Keep-Alive pending flag */\n  @volatile var keepAlive = true\n\n  /** A queue that is used for correlating a request and a response. */\n  val queue = new ArrayDeque[HttpMethod]()\n\n\n  /**\n   * Check current state for 'available' and no outstanding request/responses.\n   */\n  @inline def isAvailable(channel: Channel) =\n    channel.isActive() && keepAlive && queue.isEmpty && !inProgress(channel)\n\n  /**\n   * Determine if the current handler has any outstanding/in-progress requests or responses.\n   */\n  @inline def inProgress(channel: Channel) = {\n    val stats = channel.statistics\n    stats.requestCount.get() != stats.responseCount.get()\n  }\n\n  /**\n   * Determine if a close is pending on this channel\n   * (ie: close has been requested and should be performed on downstream completion)\n   */\n  @inline def isClosePending() = queue.isEmpty && !keepAlive\n\n\n  /**\n   * Handle incoming response data coming decoded from our response-decoder.\n   *\n   * Normal case is session-established; just pass thru.\n   * If not established, we should fail unless it's the last packet of the response, in which case\n   * we can just discard.\n   */\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = {\n    // Track our message state/completion\n    if (msg.isInstanceOf[HttpResponse]) {\n      // Update our keep-alive flag to false if the response indicates a downstream intention to close\n      require(queue.poll() != null)\n      keepAlive &= HttpHeaderUtil.isKeepAlive(msg.asInstanceOf[HttpResponse])\n    }\n\n    if (msg.isInstanceOf[LastHttpContent]) {\n      val stats = ctx.statistics\n      require(stats.responseCount.incrementAndGet() == stats.requestCount.get())\n    }\n\n    ctx.session match {\n      case Some(NeutrinoSession(channel, _)) =>\n        channel.write(msg).addListener(CLOSE_ON_FAILURE)\n\n      case None if (msg.isInstanceOf[LastHttpContent]) =>\n        // Just discard\n\n      case None if (isAvailable(ctx.channel)) =>\n        logger.warn(\"Read data on unallocated session: {}\", msg)\n\n      case None =>\n        logger.info(\"Read data on closed session; closing\")\n        ctx.close()\n    }\n  }\n\n  override def channelReadComplete(ctx: ChannelHandlerContext): Unit =\n    ctx.session match {\n      //case Some(session) if state.isAvailable() =>\n        // shouldn't need to flush; downstream handler will on 'last'\n      case Some(NeutrinoSession(channel, _)) => channel.flush()\n      case None => // Fairly common; just ignore\n    }\n\n\n  /**\n   * Handle outgoing HTTP requests.\n   */\n  override def write(ctx: ChannelHandlerContext, msg: AnyRef, promise: ChannelPromise): Unit =\n  {\n    msg match {\n      case data: HttpRequest =>\n        queue.offer(data.method)\n        ctx.statistics.requestCount.incrementAndGet()\n\n      case _ =>\n    }\n\n    ctx.write(msg, promise)\n  }\n\n  override def close(ctx: ChannelHandlerContext, promise: ChannelPromise): Unit = {\n    ctx.session match {\n      case Some(NeutrinoSession(channel, _)) if channel.isOpen() => channel.close(promise)\n      case _ =>\n    }\n    ctx.close(promise)\n  }\n\n\n  override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable): Unit =\n    (cause, ctx.session) match {\n      case (_: IOException, Some(NeutrinoSession(channel, _))) =>\n        channel.close()\n        ctx.close()\n\n      case (_: IOException, None) =>\n        ctx.close()\n\n      case (_, Some(NeutrinoSession(channel, _))) =>\n        channel.pipeline.fireExceptionCaught(cause)\n\n      case (_, None) =>\n        logger.warn(\"Unhandled exception on unallocated session\", cause)\n        ctx.fireExceptionCaught(cause)\n    }\n}\n\n\nobject NeutrinoClientHandler {\n\n  // Helper methods for downstream channel management\n  implicit class NeutrinoClientSupport(val self: Channel) extends AnyVal /*with AttributeKeySupport */{\n    //def attr[T](key: AttributeKey[T]): Attribute[T] = self.attr(key)\n    //def channel = self\n\n    def handler = self.pipeline.get(classOf[NeutrinoClientHandler])\n\n    /**\n     * Stateful introspection into our downstream channel.\n     * Determine whether or not it's open and in a consistent state for receiving new connections.\n     */\n    def isAvailable(): Boolean = self.isActive() && handler.isAvailable(self)\n\n    /**\n     *\n     */\n    def isReusable(): Boolean = self.isActive && !handler.isClosePending()\n  }\n}\n\n\n/**\n * We leverage the majority of Netty's original HttpClientCodec to help facilitate some edge\n * cases, including HttpPipelining and HEAD/CONNECT.\n *\n * A combination of {@link HttpRequestEncoder} and {@link HttpResponseDecoder}\n * which enables easier client side HTTP implementation. {@link HttpClientCodec}\n * provides additional state management for <tt>HEAD</tt> and <tt>CONNECT</tt>\n * requests, which {@link HttpResponseDecoder} lacks.  Please refer to\n * {@link HttpResponseDecoder} to learn what additional state management needs\n * to be done for <tt>HEAD</tt> and <tt>CONNECT</tt> and why\n * {@link HttpResponseDecoder} can not handle it by itself.\n *\n * If the {@link Channel} is closed and there are missing responses,\n * a {@link PrematureChannelClosureException} is thrown.\n *\n *\n * Constants; these should be moved to settings.\n *   val maxInitialLineLength = 4096\n *   val maxHeaderSize = 8192\n *   val maxChunkSize  = 8192\n *   val validateHeaders = true\n */\nclass NeutrinoClientDecoder(queue: ArrayDeque[HttpMethod]) extends HttpResponseDecoder(4096, 8192, 8192, true)\n  with StrictLogging\n{\n  override protected def isContentAlwaysEmpty(msg: HttpMessage): Boolean =\n  {\n    val statusCode = msg.asInstanceOf[HttpResponse].status.code\n\n    // 100-continue response should be excluded from paired comparison.\n    if (statusCode == 100) return true\n\n    // Get the getMethod of the HTTP request that corresponds to the\n    // current response.\n    if (queue.isEmpty) {\n      logger.warn(\"Empty queue... Error\")\n    }\n\n    val method = queue.peek()\n    val firstChar = method.name.charAt(0)\n\n    firstChar match {\n      case 'H' if (HttpMethod.HEAD.equals(method)) =>\n        // According to 4.3, RFC2616:\n        // All responses to the HEAD request getMethod MUST NOT include a\n        // message-body, even though the presence of entity-header fields\n        // might lead one to believe they do.\n        true\n\n      // The following code was inserted to work around the servers\n      // that behave incorrectly.  It has been commented out\n      // because it does not work with well behaving servers.\n      // Please note, even if the 'Transfer-Encoding: chunked'\n      // header exists in the HEAD response, the response should\n      // have absolutely no content.\n      //\n      //// Interesting edge case:\n      //// Some poorly implemented servers will send a zero-byte\n      //// chunk if Transfer-Encoding of the response is 'chunked'.\n      ////\n      //// return !msg.isChunked();\n\n\n      // Successful CONNECT request results in a response with empty body.\n      case 'C' if (statusCode == 200 && HttpMethod.CONNECT.equals(method)) =>\n        // Proxy connection established - Not HTTP anymore.\n        //done = true;\n        true\n\n      case _ =>\n        super.isContentAlwaysEmpty(msg)\n    }\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/handler/NeutrinoDownstreamHandler.scala",
    "content": "package com.ebay.neutrino.handler\n\nimport java.io.IOException\nimport java.net.{ConnectException, NoRouteToHostException, UnknownServiceException}\nimport java.nio.channels.ClosedChannelException\n\nimport com.ebay.neutrino.NeutrinoRequest\nimport com.ebay.neutrino.handler.NeutrinoDownstreamHandler.DownstreamConnection\nimport com.ebay.neutrino.handler.ops.{AuditActivity, NeutrinoAuditHandler}\nimport com.ebay.neutrino.metrics.Instrumented\nimport com.ebay.neutrino.util.{Preconditions, ResponseGenerator, Utilities}\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.buffer.{ByteBuf, ByteBufHolder}\nimport io.netty.channel._\nimport io.netty.handler.codec.http.HttpResponseStatus._\nimport io.netty.handler.codec.http.{HttpContent, LastHttpContent}\n\nimport scala.util.{Failure, Success, Try}\n\n\n/**\n * Base class for {@link Channel} NeutrinoChannel implementations; built on the EmbeddedChannel\n * framework.\n *\n * NeutrinoChannel (EmbeddedChannel) is:\n * - container for pipeline\n * - dedicated to protocol (ie: HTTP)\n * - tightly coupled with the \"request\" (not connection).\n * - Knows about each endpoint's framing protocol\n * - is responsible for framing down into the endpoints' frames\n *\n * For simplicity, writing into the EmbeddedChannel handles memory lifecycle and manages virtual\n * session containing...\n */\nobject NeutrinoDownstreamHandler {\n\n  // Allowed FSM States for the downstream handler\n  //  - Available:    Ready to attempt downstream connection\n  //  - Connecting:   Downstream connection in progress\n  //  - Connected:    Downstream connection established; packets can be sent through as-is\n  //  - Closed:       Connection has been closed and can't handle new requests/events\n  private sealed trait State\n  private case object Available extends State\n  private case class Connecting(attempt: NeutrinoDownstreamAttempt) extends State\n  private case class Connected(request: NeutrinoRequest, downstream: Channel) extends State\n  private case object Closed extends State\n\n\n  // Supported events\n  case class DownstreamConnection(request: NeutrinoRequest, downstream: Try[Channel])\n\n\n  // This could be externalized\n  val generator = new ResponseGenerator()\n}\n\n\n/**\n * Create a new instance with the pipeline initialized with the specified handlers.\n *\n * Note initializers need to be in inner-to-outer order\n */\nclass NeutrinoDownstreamHandler extends ChannelDuplexHandler with Instrumented with StrictLogging\n{\n  import com.ebay.neutrino.handler.NeutrinoDownstreamHandler._\n  import com.ebay.neutrino.util.AttributeSupport._\n\n  // Mutable data\n  private var state: State = Available\n\n\n  /**\n   * Close the underlying session and clean up any outstanding state.\n   */\n  private def close(ctx: ChannelHandlerContext) = {\n    state = Closed\n    if (ctx.channel.isOpen) ctx.close()\n  }\n\n  // These are intended to cascade down through any remaining incoming (upstream) handlers\n  override def channelInactive(ctx: ChannelHandlerContext): Unit = {\n    // Handle state cleanup\n    state match {\n      case Connecting(attempt) if (!attempt.pending.isEmpty) =>\n      case _ =>\n    }\n\n    state = Closed\n    ctx.fireChannelInactive\n  }\n\n  override def channelWritabilityChanged(ctx: ChannelHandlerContext): Unit =\n    ctx.fireChannelWritabilityChanged\n\n  /**\n   * Process reads against the bottom of our pipeline. These will be sent across to the downstream.\n   *\n   * Our possible states during execution of this are:\n   * - Downstream connected, messages pending:  Flush pending, Send packet through\n   * - Downstream connected, no pending: Send packet through\n   * - Not connected,\n   */\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = {\n\n    // Establish/terminate downstream connections here\n    (state, msg) match {\n      case (Available, data: NeutrinoRequest) =>\n        // Initiate our downstream connection attempt\n        state = Connecting(new NeutrinoDownstreamAttempt(ctx, data))\n        metrics.counter(\"created\") += 1\n\n      case (Connecting(attempt), data: HttpContent) =>\n        attempt.pending = attempt.pending :+ data\n\n      case (Connected(request, downstream), data: HttpContent) =>\n        downstream.write(msg).addListener(new NeutrinoDownstreamListener(ctx, data))\n\n      case _ =>\n        logger.warn(\"Received an unexpected message for downstream when channel was {} - ignoring: {}\", state, msg)\n    }\n  }\n\n  override def channelReadComplete(ctx: ChannelHandlerContext): Unit = {\n    state match {\n      case Connected(request, downstream) => downstream.flush()\n      case _ => // ?? error here??\n    }\n    ctx.fireChannelReadComplete()\n  }\n\n\n  /**\n   * Handle our request/downstream-channel lifecycle events.\n   *\n   * This is pretty hacky; see if we can find a better way...\n   */\n  override def userEventTriggered(ctx: ChannelHandlerContext, event: AnyRef): Unit = {\n\n    event match {\n      case DownstreamConnection(request, Success(channel)) if (request.downstream.isEmpty) =>\n        // Channel was connected, but is no longer active; either side timed out\n\n      case DownstreamConnection(request, Success(channel)) =>\n        state match {\n          /**\n           * Flush the pending operations.\n           *\n           * Note that this is externally-called only (not called from within this class).\n           * This allows us to rely on the caller to provide required synchronization, and\n           * implicitly rely on the calling channel's thread-safely.\n           */\n          case Connecting(attempt) =>\n            require(request.downstream.isDefined, \"Downstream should be set\")\n            logger.debug(\"Downstream established. Flushing {} message(s) from {}\", attempt.pending.size.toString, request.requestUri)\n            metrics.counter(\"established\") += 1\n\n            // Flush any pending messages to the channel provided\n            if (attempt.pending.nonEmpty) {\n              attempt.pending foreach { msg =>\n                channel.write(msg).addListener(new NeutrinoDownstreamListener(ctx, msg))\n              }\n              channel.flush()\n              attempt.pending = Seq.empty\n            }\n\n            // Store the successful downstream\n            state = Connected(request, channel)\n\n\n          case Closed =>\n            // Channel was closed between connect-request and now.\n            // Do we have to explicitly release the request and clean up? Or can we just ignore and implicitly do it\n\n          case other =>\n            throw new IllegalStateException(s\"Should have been Connecting or Closed, was $state\")\n        }\n\n\n      case DownstreamConnection(request, Failure(ex)) =>\n        // Generate additional diagnostics on unexpected failures\n        val metricname = ex match {\n          case ex: UnknownServiceException => \"rejected.no-pool\"\n          case ex: ConnectTimeoutException => \"rejected.timeout\"\n          case ex: ConnectException        => \"rejected.no-connect\"\n          case _ => \"rejected\"\n        }\n\n        // Reset our state to take future connections\n        state = Available\n\n        // Propagate the failure to our exception handling\n        metrics.counter(metricname) += 1\n        ctx.pipeline.fireExceptionCaught(ex)\n\n\n      case _ =>\n        ctx.fireUserEventTriggered(event)\n    }\n  }\n\n  /**\n   * Intercept the write; release our downstream on completion of the response.\n   *\n   * @param ctx\n   * @param msg\n   * @param promise\n   */\n  override def write(ctx: ChannelHandlerContext, msg: AnyRef, promise: ChannelPromise): Unit =\n    msg match {\n      case _: LastHttpContent =>\n        require(state.isInstanceOf[Connected], \"State should be connected\")\n        val request = Preconditions.checkDefined(ctx.request, \"Request should be active in the session\")\n\n        // On completion of response, release the current request\n        request.pool.release(false)\n\n        // Write and flush the downstream message\n        // (explicitly flush here, because our pool-release may prevent downstream flush\n        // from finding our way up)\n        ctx.writeAndFlush(msg, promise)\n\n        // Ensure it's in-progress\n        state match {\n          case Available | _:Connected =>\n            // Normal operation; return state to 'ready'\n            state = Available\n            metrics.counter(\"completed.\") += 1\n\n          case Connecting(attempt) =>\n            // Terminated abnormally; we won't be able to recover so just close\n            close(ctx)\n            metrics.counter(\"completed.premature\") += 1\n\n          case Closed =>\n            // Skip - already closed\n\n          case _ =>\n            throw new IllegalStateException(s\"ResponseCompleted should only occur when Available/Connecting/Connected (was $state)\")\n        }\n\n      case _ =>\n        // All other message types\n        ctx.write(msg, promise)\n    }\n  \n\n  /**\n   * These may not be necessary; in lieu of anything better to do, we'll send the eventing\n   * back to our original upstream (inbound).\n   *\n   * Recognized exception:\n   * - UnknownServiceException:   No pool available/resolved\n   * - NoRouteToHostException:    No nodes available in pool\n   * - ConnectException:          Unable to connect to downstream\n   *\n   * @param ctx\n   * @param cause\n   */\n  override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable): Unit =\n    cause match {\n      case ex: NoSuchElementException if (ex.getMessage() == \"None.get\") =>\n        // TODO fix this case; see NEUTRINO-JIRA\n        metrics.counter(\"exception.NoSuchElementException\") += 1\n\n      case ex: UnknownServiceException =>\n        // Unable to resolve a real pool. May be able to recover; ignore and propagate\n        val response = generator.generate(SERVICE_UNAVAILABLE, cause.getMessage)\n        ctx.writeAndFlush(response)\n\n      case ex: NoRouteToHostException =>\n        // Unable to get a healthy member from the resolved pool.\n        val response = generator.generate(SERVICE_UNAVAILABLE, cause.getMessage)\n        ctx.writeAndFlush(response)\n\n      case ex: ConnectException =>\n        // Unable to connect to downstream (including timeout)\n        // Do we need to close on this this??\n        val response = generator.generate(GATEWAY_TIMEOUT, cause.getMessage)\n        ctx.writeAndFlush(response)\n\n      case ex: ClosedChannelException =>\n        // Additional write attempts on a closed connection; just ignore and close\n        close(ctx)\n\n      case ex: IOException =>\n        // We won't be able to recover, so close the session/channel\n        close(ctx)\n\n      case ex =>\n        logger.warn(\"Unhandled exception in channel session\", cause)\n        ctx.fireExceptionCaught(cause)\n    }\n}\n\n\n/**\n * This supports a three-stage connection establishment process.\n *\n * 1) Request downstream from pool\n * 2) On invalid downstream, quit (unable to send)\n * 3) On valid downstream, attempt to send the pending payload message\n * 4) On invalid send return to 1)\n * 5) On valid send, channel is open and we can continue\n *\n * Note that 3) is required to determine whether the channel is actually open, or just hasn't\n * detected the close yet. A send-attempt will fail if the channel is actually closed.\n *\n * @param ctx\n * @param request\n */\nclass NeutrinoDownstreamAttempt(ctx: ChannelHandlerContext, request: NeutrinoRequest) extends ChannelFutureListener with StrictLogging {\n\n  import NeutrinoAuditHandler._\n  import com.ebay.neutrino.util.Utilities._\n\n  // Cache outstanding messages pending completion of our connection\n  var pending = Seq.empty[HttpContent]\n\n  // Apply back-pressure to the incoming channel\n  // Should we mess around with the other settings to minimize in-progress inbound buffer too?\n  ctx.channel.config.setAutoRead(false)\n\n  // Kick off the negotiation\n  establish()\n\n  // If not established, or force is provided, renegotiate the endpoint\n  // Grab the connection's current pool, and see if we have a resolver available\n  // If downstream not available, attempt to connect one here\n  import request.session.service.context\n  def establish() =\n    request.connect() onComplete {\n      case Success(endpoint) =>\n        // Grab our first available result, if possible, and hook configuration of endpoint on success\n        // Attempt to send the pending payload over the channel.\n        endpoint.writeAndFlush(request).addListener(this)\n\n      case failure @ Failure(ex) =>\n        // Flat-out failed to return a viable channel. This is a pool-resolver failure\n\n        // Resend a user-event notification of completion (to process within the thread-env)\n        //?? upstream.setAutoRead(true)\n        ctx.pipeline.fireUserEventTriggered(DownstreamConnection(request, failure))\n    }\n\n\n  /**\n   * Handle any downstream channel-errors here.\n   * @param future\n   */\n  override def operationComplete(future: ChannelFuture): Unit = {\n    import future.channel\n\n    future.isSuccess match {\n      case true =>\n        // Audit-log the completion\n        channel.audit(AuditActivity.ChannelAssigned(channel))\n\n        // Resend a user-event notification of completion (to process within the thread-env)\n        ctx.pipeline.fireUserEventTriggered(DownstreamConnection(request, Success(channel)))\n\n        // Downstream endpoint resolved; turn the tap back on\n        ctx.channel.config.setAutoRead(true)\n\n\n      case false =>\n        // Audit-log the completion\n        channel.audit(AuditActivity.ChannelException(channel, future.cause))\n\n        logger.info(\"Downstream failed on connect - closing channel {} and bound to {} and retrying\", channel.toStringExt, ctx.channel.toStringExt)\n        channel.close\n\n        // Release the request back to the pool (but leave the pool resolved)\n        request.pool.release(false)\n\n        // Reattempt to establish a connection with a new channel\n        establish()\n    }\n  }\n\n}\n\n\nclass NeutrinoDownstreamListener(ctx: ChannelHandlerContext, val payload: HttpContent) extends ChannelFutureListener with StrictLogging\n{\n  import Utilities._\n  import com.ebay.neutrino.handler.ops.NeutrinoAuditHandler.NeutrinoAuditSupport\n\n  import scala.concurrent.duration._\n\n  val start = System.nanoTime()\n\n  val size =\n    payload match {\n      case data: ByteBuf => data.readableBytes()\n      case data: ByteBufHolder => data.content.readableBytes\n      case data => 0\n    }\n\n\n  /**\n   * Handle any downstream channel-errors here.\n   * @param future\n   */\n  override def operationComplete(future: ChannelFuture): Unit = {\n    import future.channel\n\n    channel.audit(\n      AuditActivity.Detail(s\"DownstreamWrite: $size = ${future.isSuccess}, cause ${future.cause}\"))\n\n    future.cause match {\n      case null =>\n        // Successful send; ignore\n        // TODO add audit record\n\n      case ex: ClosedChannelException if (!channel.isActive && payload.isInstanceOf[LastHttpContent]) =>\n        // Downstream channel was already closed; ensure we're closed too\n        logger.debug(\"Downstream write failed - channel was already closed before final packet. Closing our upstream.\")\n        ctx.close()\n\n      case ex: ClosedChannelException if (!channel.isActive) =>\n        // Downstream channel was already closed; ensure we're closed too\n        logger.info(\"Downstream write failed - channel was already closed. Closing our upstream.\")\n        ctx.close() //ctx.pipeline.fireExceptionCaught(future.cause)\n\n      case ex =>\n        // Handle downstream write-failure\n        val elapsed = ((System.nanoTime()-start)/1000) micros;\n        logger.info(\"Downstream write failed - closing channel {} (Failed with {} in {}, size {}, packet # {})\", channel.toStringExt, future.cause, elapsed, size.toString)\n        channel.close\n\n        // Also propagate the IO-failure event to the session for better handling\n        ctx.pipeline.fireExceptionCaught(future.cause)\n    }\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/handler/NeutrinoPipelineHandler.scala",
    "content": "package com.ebay.neutrino.handler\n\nimport com.ebay.neutrino.channel.NeutrinoPipelineChannel\nimport com.ebay.neutrino.util.AttributeSupport\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.channel._\nimport io.netty.util.AttributeKey\n\n/**\n * Stateless user-pipeline handler bridge between our parent (system) channel and\n * the user-pipeline, which is intended to be self-containe.\n *\n * On register, this handler will add a user-pipeline to the parent channel context\n * if required (if there are user-handlers). Subsequent events (both inbound and\n * outbound) will be routed through the user-pipeline channel if it's present,\n * and skipped if not.\n *\n *\n * Incomplete/outstanding items:\n * - TODO handle propagation of close from NeutrinoChannel to parent channel\n */\n@Sharable\nclass NeutrinoPipelineHandler extends ChannelDuplexHandler with StrictLogging\n{\n  import com.ebay.neutrino.handler.NeutrinoPipelineHandler._\n\n\n  override def channelRegistered(ctx: ChannelHandlerContext) = {\n    // Create the custom channel, if we have user handlers\n    ctx.userpipeline = NeutrinoPipelineChannel(ctx)\n\n    // Secondly, dispatch the channel-registered to our children\n    // (creation would have already registered the internal handlers)\n    ctx.fireChannelRegistered()\n  }\n\n  override def channelUnregistered(ctx: ChannelHandlerContext) = {\n    // If user-pipeline is configured, deregister it and remove\n    ctx.userpipeline map { user =>\n      user.pipeline.fireChannelUnregistered()\n      ctx.userpipeline = None\n    }\n\n    ctx.fireChannelUnregistered()\n  }\n\n  override def channelActive(ctx: ChannelHandlerContext): Unit = ctx.userpipeline match {\n    case Some(user) => user.pipeline.fireChannelActive()\n    case None => ctx.fireChannelActive()\n  }\n\n  override def channelInactive(ctx: ChannelHandlerContext): Unit = ctx.userpipeline match {\n    case Some(user) => user.pipeline.fireChannelInactive()\n    case None => ctx.fireChannelInactive()\n  }\n\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = ctx.userpipeline match {\n    case Some(user) => user.pipeline.fireChannelRead(msg)\n    case None => ctx.fireChannelRead(msg)\n  }\n\n  override def channelWritabilityChanged(ctx: ChannelHandlerContext): Unit = ctx.userpipeline match {\n    case Some(user) => user.pipeline.fireChannelWritabilityChanged()\n    case None => ctx.fireChannelWritabilityChanged()\n  }\n\n  override def channelReadComplete(ctx: ChannelHandlerContext): Unit = ctx.userpipeline match {\n    case Some(user) => user.pipeline.fireChannelReadComplete()\n    case None => ctx.fireChannelReadComplete()\n  }\n\n  override def userEventTriggered(ctx: ChannelHandlerContext, msg: AnyRef): Unit = ctx.userpipeline match {\n    case Some(user) => user.pipeline.fireUserEventTriggered(msg)\n    case None => ctx.fireUserEventTriggered(msg)\n  }\n\n  override def flush(ctx: ChannelHandlerContext): Unit = ctx.userpipeline match {\n    case Some(user) => user.pipeline.flush()\n    case None => ctx.flush()\n  }\n\n  override def close(ctx: ChannelHandlerContext, promise: ChannelPromise): Unit = ctx.userpipeline match {\n    case Some(user) => user.close(promise)\n    case None => ctx.close(promise)\n  }\n\n  //override def read(ctx: ChannelHandlerContext): Unit =\n  //  outbound(ctx).read()\n\n  // We'll cheat a little here; we'll assume the downstream handler (shoudl just be 'Downstream')\n  // are not going to put a promise on this\n  override def write(ctx: ChannelHandlerContext, msg: AnyRef, promise: ChannelPromise) =\n    ctx.userpipeline match {\n      case Some(user) =>\n        logger.info(\"Writing outbound message to user-pipeline channel: {}\", msg)\n        //require(promise.isEmpty())\n        //user.pipeline.write(msg, promise)\n        user.pipeline.write(msg)\n\n      case None =>\n        ctx.write(msg, promise)\n    }\n}\n\n\n/**\n * Static helpers for User-pipeline support methods, including Attribute getter/setter.\n *\n * These are localized to this file instead of the stock AttributeSupport utilties as we don't\n * need external visibility.\n *\n */\nobject NeutrinoPipelineHandler {\n\n  import AttributeSupport.AttributeMapSupport\n\n  // Constants\n  private val UserPipelineKey = AttributeKey.valueOf[NeutrinoPipelineChannel](\"user-pipeline\")\n\n  // Neutrino user-pipeline (optional) getter and setter\n  implicit private class NeutrinoPipelineSupport(val self: ChannelHandlerContext) extends AnyVal {\n    def userpipeline = self.get(UserPipelineKey)\n    def userpipeline_=(value: NeutrinoPipelineChannel) = self.set(UserPipelineKey, Option(value))\n    def userpipeline_=(value: Option[NeutrinoPipelineChannel]) = self.set(UserPipelineKey, value)\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/handler/NeutrinoRequestHandler.scala",
    "content": "package com.ebay.neutrino.handler\n\nimport com.codahale.metrics.annotation.Timed\nimport com.ebay.neutrino._\nimport com.ebay.neutrino.channel.NeutrinoEvent\nimport com.ebay.neutrino.handler.ops.AuditActivity\nimport com.ebay.neutrino.handler.ops.NeutrinoAuditHandler._\nimport com.ebay.neutrino.metrics.Instrumented\nimport com.ebay.neutrino.util.Preconditions\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.channel._\nimport io.netty.handler.codec.http._\n\nimport scala.util.{Failure, Success}\n\n/**\n * A handler for managing the HTTP request state of the channel.\n *\n * Since we're using stateful HTTP codecs on both sides of our pipeline channel, we need\n * to sandwich a HTTP-specific handler between them to:\n * 1) Track HTTP state and request/response lifecycle events\n * 2) Perform any channel conditioning.\n *\n * TODO move http-state/modification/logic into a context object\n */\n@Sharable\nclass NeutrinoRequestHandler extends ChannelDuplexHandler with StrictLogging with Instrumented {\n\n  import com.ebay.neutrino.handler.NeutrinoRequestHandler._\n  import com.ebay.neutrino.handler.ops.NeutrinoAuditHandler._\n  import com.ebay.neutrino.util.AttributeSupport._\n  import io.netty.handler.codec.http.HttpHeaderUtil.isKeepAlive\n\n\n  // Composite channel-future listener\n  class ResponseCompleteListener(request: NeutrinoRequest) extends ChannelFutureListener {\n\n    // Handle upstream keep-alive; if not keepAlive, kill it\n    override def operationComplete(future: ChannelFuture): Unit = {\n      val channel = future.channel()\n\n      // Clean up channel, if request is outstanding\n      if (channel.request.isDefined && channel.request.get == request) channel.request = None\n\n      // Handle output write exception\n      future.cause match {\n        case null =>\n          // Close the underlying channel, unless keep-alive is specified\n          if (channel.isActive && !isKeepAlive(request.response.get)) channel.close()\n\n        case ex =>\n          logger.warn(\"Unable to write response\", future.cause)\n          channel.close()\n      }\n\n      // Clear the current request and remove references to this request on our channel\n      request.complete()\n    }\n  }\n\n\n  /**\n   * Set the current keep-alive value.\n   *\n   * Upstream:\n   * Only use if both upstream and downstream permit keepalive\n   *\n   * Downstream:\n   * We use the response, if provided, and default to the request's value.\n   *\n   * @param channel\n   * @param request\n   * @param response\n   */\n  def setResponseHeaders(channel: Channel, request: NeutrinoRequest, response: HttpResponse) =\n  {\n    // Set keep-alive headers; if connection not available just drop packet\n    // Also close the underlying channel if it has timed-out\n    val elapsed    = channel.statistics.elapsed\n    val timeout    = (elapsed > request.session.service.settings.timeouts.sessionCompletion)\n    val downstream = isKeepAlive(response)\n    val upstream   = request.requestKeepalive && downstream && !timeout\n\n    // Update request/response state with the downstream response copy\n    request.response = Option(response)\n\n    // Reset upstream keepalive to match it's expectations, and close downstream if not used anymore\n    if (downstream != upstream) setKeepAlive(response, upstream, request.protocolVersion)\n\n    // We're done with the response on the downstream channel; close it if no keepalive\n    // TODO move this to the downstream channel stateful pipeline\n    if (!downstream) request.downstream map (_.close())\n  }\n\n\n  /**\n   * Connect downstream connection.\n   *\n   * On valid HttpRequest, create a new Connection and add it to the channel provided.\n   * Handle transport-tier proxying issues\n   *\n   * UNUSED - to remove if reference not needed:\n   *\n   * I think we can just pass everything through.\n   *  case _ if ctx.request.isDefined =>\n   *    // Ensure we have a valid connection\n   *\n   *  case _: LastHttpContent =>\n   *    // Don't care about these; may be generated after either side has closed the connection\n   *    //  return\n   *\n   *  case _ =>\n   *    // Connection not valid; unable to handle msg downstream\n   *    ctx.close()\n   *    logger.warn(\"Connection is expected but missing from context; closing channel and swallowing {}\", msg)\n   *\n   * Notes:\n   * TODO Transport-convert the incoming request; reset the correct downstream headers\n   * TODO decrease max-forwards by 1, if 0 then what?\n   *\n   * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html\n   * @param ctx\n   * @param msg\n   */\n  @Timed\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = {\n    import com.ebay.neutrino.util.Utilities._\n\n    logger.info(\"Message read: {}\", msg)\n    msg match {\n      case http: HttpRequest =>\n        // Register the request-level connection entity on the new channel with the Balancer\n        val created = NeutrinoRequest.create(ctx.channel, http)\n\n        // Clean up any existing request\n        // (ie: if the previous request's final write hasn't completed yet\n        ctx.request map (_.complete())\n\n        // Register the new request\n        ctx.request = created.toOption\n\n        created match {\n          case Success(request) =>\n            // Ensure our downstream uses keepalive, if applicable\n            if (request.session.service.settings.channel.forceKeepAlive) HttpHeaderUtil.setKeepAlive(request, true)\n\n            // Register the request-level connection entity on the new channel with the Balancer\n            // Create a Neutrino request-object and event it\n            val event = NeutrinoEvent.RequestCreated(request)\n            ctx.channel.audit(AuditActivity.Event(event))\n            ctx.fireUserEventTriggered(event)\n\n            // Register the request completion event; will notify on request completion\n            request.addListener(new NeutrinoAuditLogger(request))\n\n            // Delegate the request downstream\n            ctx.fireChannelRead(request)\n\n\n          case Failure(ex) =>\n            // Handle invalid requests by failure type\n            val message = (ex, ex.getCause) match {\n              case (_: IllegalArgumentException, se: java.net.URISyntaxException) =>\n                s\"Invalid request URI provided: ${http.uri}\\n\\n${se.getMessage}\"\n\n              case _ =>\n                logger.warn(\"Unable to create NeutrinoRequest\", ex)\n                s\"Invalid request: ${ex.toString}\"\n            }\n\n            // Generate an error message\n            ctx.sendError(HttpResponseStatus.BAD_REQUEST, message).addListener(ChannelFutureListener.CLOSE)\n        }\n\n\n      case last: LastHttpContent if (ctx.request.isEmpty) =>\n        // Probably a pending write after a request-pipeline close. Just swallow\n\n      case _ =>\n        ctx.fireChannelRead(msg)\n    }\n  }\n\n  /**\n   * Auto-close unless keep-alive.\n   *\n   * Update the connection state with the response/headers, setting any appropriate\n   * shared state.\n   */\n  override def write(ctx: ChannelHandlerContext, msg: Object, promise: ChannelPromise): Unit = {\n\n    // Grab the start of the response\n    msg match {\n      case response: HttpResponse =>\n        val session = Preconditions.checkDefined(ctx.session)\n        val request = Preconditions.checkDefined(ctx.request)\n        logger.info(\"Writing response: {}\", response)\n\n        // Reset upstream keepalive to match it's expectations, and close downstream if not used anymore\n        setResponseHeaders(ctx.channel, request, response)\n\n        // Notify a user-event on the response (allows the pipeline to handle...)\n        val event = NeutrinoEvent.ResponseReceived(request)\n        request.audit(ctx.channel, AuditActivity.Event(event))\n        ctx.fireUserEventTriggered(NeutrinoEvent.ResponseReceived(request))\n\n      case _ =>\n    }\n\n    // Handle 'last packet'\n    msg match {\n      case _: LastHttpContent =>\n        logger.info(\"Setting close-listener on last content\")\n\n        // Close the underlying channel, unless keep-alive is specified\n        val listener = new ResponseCompleteListener(ctx.request.get)\n        val unvoid   = promise.unvoid().addListener(listener)\n        ctx.write(msg, unvoid)\n\n      case _ =>\n        ctx.write(msg, promise)\n    }\n  }\n\n\n  /**\n   * Called when backpressure options are set.\n   */\n  override def channelWritabilityChanged(ctx: ChannelHandlerContext): Unit = {\n    logger.info(\"Downstream writeablility changed... is now {}\", ctx.channel.isWritable.toString)\n    ctx.fireChannelWritabilityChanged()\n  }\n}\n\n\nobject NeutrinoRequestHandler extends StrictLogging {\n\n  /**\n   * Set an appropriate request/response keep-alive based on the version and value\n   * provided.\n   *\n   * We override the default implementation in HttpHeaders.setKeepAlive(self, keepalive)\n   * to ensure we send Connection.KeepAlive on HTTP1.1-response to HTTP1.0-request.\n   * (as required by apache-bench)\n   *\n   * @see http://serverfault.com/questions/442960/nginx-ignoring-clients-http-1-0-request-and-respond-by-http-1-1\n   *\n   * @param message\n   * @param keepalive\n   * @param version\n   */\n  def setKeepAlive(message: HttpMessage, keepalive: Boolean, version: HttpVersion) = {\n    import HttpHeaderNames.CONNECTION\n    import message.headers\n\n    logger.info(\"Setting keepalive to {} for request version {}\", keepalive.toString, version)\n\n    (keepalive, version) match {\n      case (true,  HttpVersion.HTTP_1_0)     => headers.set(CONNECTION, HttpHeaderValues.KEEP_ALIVE)\n      case (true,  HttpVersion.HTTP_1_1 | _) => headers.remove(CONNECTION)\n      case (false, HttpVersion.HTTP_1_0)     => headers.remove(CONNECTION)\n      case (false, HttpVersion.HTTP_1_1 | _) => headers.set(CONNECTION, HttpHeaderValues.CLOSE)\n    }\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/handler/NeutrinoServiceInitializer.scala",
    "content": "package com.ebay.neutrino\n\nimport java.util.concurrent.TimeUnit\n\nimport com.ebay.neutrino.channel.{NeutrinoSession, NeutrinoService}\nimport com.ebay.neutrino.handler._\nimport com.ebay.neutrino.handler.ops.{ChannelStatisticsHandler, ChannelTimeoutHandler, NeutrinoAuditHandler}\nimport com.ebay.neutrino.metrics.{Instrumented, Metrics}\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.channel._\nimport io.netty.handler.codec.http.HttpServerCodec\n\n\n/**\n * Neutrino-specific handlers that allow separation of the framework/endpoint's handlers from\n * user-defined handlers (ie: Pipeline Handlers)\n *\n * Ideally, the user would have control over their pipeline customization, and the\n * framework would be able to wrap both operational and transport/framing handlers\n * around in a way that insulates the endpoints.\n *\n * We can solve this one of a couple of ways:\n *\n * 1) Reimplement an interface around ChannelHandlerContext, which in turn reimplements\n *    the ChannelPipeline implementation, to sandbox calls to addFirst() and addLast()\n *    to within a bounded internal subset.\n *\n *    Would call a version of initChannel(customCtx) which would allow the user to\n *    register their own handlers and condition their own application needs, without\n *    gaining access to the underlying pipeline implementation (and consequently the\n *    operational/framework handlers.\n *\n * 2) Invoke the initChannel() method first, and then apply our framework handlers\n *    to the list after the fact.\n *\n *\n * Version 2 is easier in the short term, and is thus implemented. Users may elect from\n * replacing this with Version-1 to provide a truer 'sandbox'\n *\n *\n * Initialize framing handlers.\n * Subclasses should implement, or mix-in, framing implementations\n * For Version-2 (see above) it's critical these implementations install handlers at the\n * front of the pipeline and not rely on addLast()\n */\n@Sharable\nclass NeutrinoServiceInitializer(service: NeutrinoService)\n  extends ChannelInboundHandlerAdapter\n  with ChannelFutureListener\n  with Instrumented\n  with StrictLogging\n{\n  import com.ebay.neutrino.util.AttributeSupport._\n  import service.settings\n\n  // Shared handlers\n  val stats   = new ChannelStatisticsHandler(true)\n  val request = new NeutrinoRequestHandler()\n  val audit   = new NeutrinoAuditHandler()\n  val user    = new NeutrinoPipelineHandler()\n  val timeout = new ChannelTimeoutHandler(settings.timeouts)\n\n  // Stateful handler factories\n  @inline def server     = new HttpServerCodec()\n  @inline def downstream = new NeutrinoDownstreamHandler()\n\n\n  override def channelRegistered(ctx: ChannelHandlerContext): Unit =\n  {\n    // Extract the underlying neutrino pipeline channel\n    val channel  = ctx.channel\n    val pipeline = ctx.pipeline\n    var success  = false\n\n    // Update our downstream metrics\n    Metrics.UpstreamTotal.mark\n    Metrics.UpstreamOpen += 1\n\n    // TODO support NeutrinoPipeline for annotating handler calls.\n    // TODO move into NeutrinoChannel??\n    //val pipeline = new NeutrinoPipeline(channel.pipeline())\n\n    // Create new Neutrino managed pipeline for the channel's user-handlers\n    // TODO add traffic shaping/management support here\n\n    try {\n      // Initialize our framing\n      //    pipeline.addLast(new io.netty.handler.logging.LoggingHandler(io.netty.handler.logging.LogLevel.INFO))\n      //    pipeline.addLast(\"logging\", new HttpDiagnosticsHandler())\n      //\n      // Note; we can only use our underlying pipeline, since some of the handlers are composites\n      // and don't handle the NeutrinoPipeline well\n      pipeline.addLast(\"timeout\",      timeout)\n      pipeline.addLast(\"statistics\",   stats)\n      pipeline.addLast(\"http-decoder\", server)\n      pipeline.addLast(\"http-state\",   request)\n\n      // Add audit pipeline, if configured\n      settings.channel.auditThreshold map (_ => pipeline.addLast(\"audit\", audit))\n\n      // Initialize the user-pipeline handlers... (including ChannelInitializers)\n      pipeline.addLast(\"user-pipeline\", user)\n\n      // Final handler will reroute inbound to our downstream, as available\n      pipeline.addLast(\"downstream\", downstream)\n\n      // Prime the statistics object and hook channel close for proper cleanup\n      channel.service = service\n      channel.session = NeutrinoSession(channel, service)\n      channel.statistics\n\n      // Cleanup on channel close\n      channel.closeFuture.addListener(this)\n\n      // Apply our own\n      ctx.fireChannelRegistered()\n      success = true\n    }\n    catch {\n      case th: Throwable =>\n        // Not successful\n        logger.warn(\"Failed to initialize channel {}. Closing {}\", ctx.channel(), th)\n        ctx.close()\n    }\n    finally {\n      if (pipeline.context(this) != null) pipeline.remove(this)\n    }\n  }\n\n\n  /**\n   * Handle channel-completion events; cleanup any outstanding channel resources.\n   * @param future\n   */\n  override def operationComplete(future: ChannelFuture): Unit = {\n    val stats = future.channel.statistics\n\n    // Cleaning up any outstanding requests\n    future.channel.request map (_.complete())\n    future.channel.request = None\n\n    // Register a close-listener on the session (to clean up the session state)\n    Metrics.UpstreamOpen -= 1\n\n    if (stats.requestCount.get > 0) {\n      Metrics.SessionActive -= 1\n      Metrics.SessionDuration update(System.nanoTime()-stats.startTime, TimeUnit.NANOSECONDS)\n    }\n\n    logger.info(\"Releasing session {}\", this)\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/handler/ops/ChannelStatisticsHandler.scala",
    "content": "package com.ebay.neutrino.handler.ops\n\nimport java.util.concurrent.atomic.{AtomicInteger, AtomicLong}\n\nimport com.ebay.neutrino.metrics.Instrumented\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.buffer.{ByteBuf, ByteBufHolder}\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.channel._\nimport scala.concurrent.duration._\n\n\n/**\n * This handler does rudimentary channel-based accounting.\n *\n *\n */\n@Sharable\nclass ChannelStatisticsHandler(upstream: Boolean) extends ChannelDuplexHandler with StrictLogging with Instrumented\n{\n  import com.ebay.neutrino.util.AttributeSupport._\n  import com.ebay.neutrino.util.Utilities.AtomicLongSupport\n  import com.ebay.neutrino.metrics.Metrics._\n\n  // Global statistics\n  val readBytes    = if (upstream) UpstreamBytesRead else DownstreamBytesRead\n  val writeBytes   = if (upstream) UpstreamBytesWrite else DownstreamBytesWrite\n  val readPackets  = if (upstream) UpstreamPacketsRead else DownstreamPacketsRead\n  val writePackets = if (upstream) UpstreamPacketsWrite else DownstreamPacketsWrite\n\n\n  @inline def calculateSize(msg: AnyRef) = msg match {\n    case data: ByteBuf => data.readableBytes()\n    case data: ByteBufHolder => data.content.readableBytes\n    case data => 0\n  }\n\n\n  // Log to global (and local) statistics\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = {\n    val bytes = calculateSize(msg)\n\n    readPackets.mark\n    readBytes += bytes\n    ctx.statistics.readPackets += 1\n    ctx.statistics.readBytes += bytes\n\n    ctx.fireChannelRead(msg)\n  }\n\n\n  override def write(ctx: ChannelHandlerContext, msg: AnyRef, promise: ChannelPromise): Unit = {\n    val bytes = calculateSize(msg)\n\n    writePackets.mark\n    writeBytes += bytes\n    ctx.statistics.writePackets += 1\n    ctx.statistics.writeBytes += bytes\n\n    ctx.write(msg, promise)\n  }\n}\n\nclass ChannelStatistics {\n\n  // Collected traffic statistics\n  val readBytes     = new AtomicLong()\n  val writeBytes    = new AtomicLong()\n  val readPackets   = new AtomicLong()\n  val writePackets  = new AtomicLong()\n\n  // Channel usage statistics\n  val startTime     = System.nanoTime()\n  val allocations   = new AtomicInteger()\n\n  // Request statistics\n  val requestCount  = new AtomicInteger()\n  val responseCount = new AtomicInteger()\n\n  // Helper methods\n  def elapsed       = (System.nanoTime()-startTime).nanos\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/handler/ops/ChannelTimeoutHandler.scala",
    "content": "package com.ebay.neutrino.handler.ops\n\nimport java.util.concurrent.{ConcurrentLinkedQueue, TimeUnit}\n\nimport com.ebay.neutrino.config.TimeoutSettings\nimport com.ebay.neutrino.metrics.Instrumented\nimport com.ebay.neutrino.util.Utilities\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.buffer.Unpooled\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.channel._\nimport io.netty.handler.timeout.{ReadTimeoutException, WriteTimeoutException}\nimport io.netty.util.concurrent.ScheduledFuture\n\nimport scala.concurrent.duration.Duration\n\n\nobject ChannelTimeout {\n\n  /**\n   * Timeout types.\n   */\n  sealed trait TimeoutType\n  case object ReadIdle extends TimeoutType\n  case object WriteIdle extends TimeoutType\n  case object WriteIncomplete extends TimeoutType\n  case object RequestMax extends TimeoutType\n  case object SessionMax extends TimeoutType\n\n\n  /**\n   * Factor constructor.\n   * Create a ChannelTimeout object if (and only if) related timeouts are defined in the settings.\n   */\n  def apply(ctx: ChannelHandlerContext, settings: TimeoutSettings): Option[ChannelTimeout] = {\n    val min = Seq(settings.readIdle, settings.writeIdle, settings.writeCompletion, settings.requestCompletion).min\n\n    if (min < Duration.Undefined)\n      Option(new ChannelTimeout(ctx, settings))\n    else\n      None\n  }\n\n}\n\n\n/**\n * Generic timeout task for supporting read, write, or combined idle timeouts, and a modified\n * write-completion timeout.\n *\n * Idle timeouts:\n * - simply poll at a minimal interval and check on each interval to see if our time\n *   elapsed has been exceeded.\n *\n * Write completion:\n * - Different than the existing Netty write-completion timeout\n * - Traditionally, would be implemented with a one-task per write; each write would schedule\n *   a timeout callback on itself, and then check to see if it had been cancelled (by a\n *   subsequent write completion)\n * - This incurs a very large cost in terms of GC pressure, as each task and task-schedule\n *   both create objects\n * - Instead, treat in the same way as the idle-timeout\n * - Store 'pending' timeouts in a queue, and pop them as writes complete (we assume in-order\n *   completion)\n * - On scheduled timeout trigger, we check to see if the oldest pending k has exceeded\n *   our maximum write threshold.\n *\n * @param ctx\n */\nclass ChannelTimeout(ctx: ChannelHandlerContext, settings: TimeoutSettings)\n  extends Runnable\n  with ChannelFutureListener\n  with StrictLogging\n{\n  import ChannelTimeout._\n  import com.ebay.neutrino.util.AttributeSupport._\n\n  import scala.concurrent.duration.Duration.Undefined\n  import scala.concurrent.duration._\n\n  val maxDelay = 2500 millis\n\n  // Track our pending writes, ordered by write-time\n  val pendingWrites = new ConcurrentLinkedQueue[Long]()\n\n  // Track our pending timeout (to allow cancellation)\n  @volatile var pendingTask: ScheduledFuture[_] = null\n  @volatile var lastRead  = System.nanoTime\n  @volatile var lastWrite = System.nanoTime\n\n\n  // Start schedule immediate\n  schedule(defaultDelay)\n\n  def schedule(delay: Duration) = {\n    require(pendingTask == null, \"task should not be pending\")\n    if (delay.isFinite) pendingTask = ctx.executor.schedule(this, delay.toNanos, TimeUnit.NANOSECONDS)\n  }\n\n  @inline def elapsed(eventTime: Long) =\n    (System.nanoTime - eventTime) nanoseconds\n\n  def sinceLast =\n    elapsed(Math.max(lastRead, lastWrite))\n\n  def nextDelay =\n    Seq(readDelay, writeDelay, requestDelay).min\n\n  def defaultDelay =\n    Seq(settings.readIdle, settings.writeIdle, settings.requestCompletion, settings.sessionCompletion, maxDelay).min\n\n  def readDelay =\n    settings.readIdle-elapsed(lastRead)\n\n  def writeDelay =\n    settings.writeIdle-elapsed(lastWrite)\n\n  def requestDelay =\n    settings.requestCompletion-(ctx.request map (_.elapsed) getOrElse Undefined)\n\n  def completionDelay = pendingWrites.isEmpty match {\n    case true  => Duration.Undefined\n    case false => settings.writeCompletion-elapsed(pendingWrites.peek())\n  }\n\n  /**\n   * Write-completion notification.\n   *\n   * On write-completion:\n   * - record our last-write time\n   * - remove a pending write-completion (the oldest)\n   */\n  override def operationComplete(future: ChannelFuture) = {\n    lastWrite = System.nanoTime\n    require(pendingWrites.poll().asInstanceOf[Any] != null, \"expected a write pending\")\n  }\n\n\n  // TODO if session is valid, smaller timeout, otherwise bigger timeout\n  override def run(): Unit =\n    if (ctx.channel.isOpen) {\n      // Cache our delay\n      val (read, write, request, completion) = (readDelay, writeDelay, requestDelay, completionDelay)\n      val next = Seq(read, write, request, completion).min\n\n      // Clear our pending task\n      pendingTask = null\n\n      logger.debug(\"Executing a timeout task with delays ({}, {}, {}, {}), {} pending writes.\",\n        read, write, request, completion, pendingWrites.size().toString)\n\n      next match {\n        case delay if (delay.isFinite && delay > Duration.Zero) =>\n          // Operation occurred before the timeout\n          // Set a new timeout with shorter delay, OR handle idle\n          schedule(delay.min(maxDelay))\n\n          if (sinceLast >= maxDelay) {\n            // Force a write-check on the channel\n            ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE)\n\n            import Utilities._\n            logger.debug(\"Channel {} is idle for {}ms\", ctx.channel.toStringExt, sinceLast.toMillis.toString)\n          }\n\n\n        case delay if (delay.isFinite) =>\n          // Timed out - set a new timeout and notify the callback.\n          schedule(defaultDelay)\n\n          val timeoutType = delay match {\n            case delay if (request    <= Duration.Zero) => RequestMax\n            case delay if (read       <= Duration.Zero) => ReadIdle\n            case delay if (write      <= Duration.Zero) => WriteIdle\n            case delay if (completion <= Duration.Zero) => WriteIncomplete\n            case _ =>\n              import Utilities._\n              logger.warn(\"Unrecognized delay {} ({}, {}, {}, {}) on channel {}\",\n                delay, read, write, request, completion, ctx.channel.toStringExt\n              )\n              ReadIdle\n          }\n\n          ctx.pipeline.fireUserEventTriggered(timeoutType)\n\n        case _ =>\n      }\n    }\n\n\n  /**\n   * Cancel any outstanding operations and release the task.\n   */\n  def cancel() =\n    if (pendingTask != null) {\n      // Clear the pending task\n      pendingTask.cancel(false)\n      pendingTask = null\n\n      // Clear any outstanding writes\n      pendingWrites.clear()\n    }\n\n\n  /**\n   * Notify the timeout-handler of a read.\n   */\n  def notifyRead() = lastRead = System.nanoTime\n\n  /**\n   * Notify the timeout-handler of a pending write, and indicate whether or not\n   * we are interested in listening for a callback on write-completion.\n   *\n   * @return true if we should be registered as a write-listener, false otherwise\n   */\n  def notifyWrite(): Boolean = {\n    // Determine our interest in write-operations\n    val listen = (settings.writeCompletion.isFinite || settings.writeIdle.isFinite)\n\n    // Register ourself as a pending write\n    if (listen) pendingWrites.add(System.nanoTime())\n    listen\n  }\n}\n\n/**\n * An amalgamated timeout handler supporting both Netty's stock timeouts and custom timeouts:\n * - read idle:   How long since last read operation\n * - write idle:  How long since last write operation\n * - write completion:  How long until a write has been completed (ack's by other side)\n * - request completion:  How long until an HTTP Request has been completed\n * - session completion:  How long until an HTTP session has been completed\n *\n * @param settings\n */\n@Sharable\nclass ChannelTimeoutHandler(settings: TimeoutSettings, exceptionOnTimeout: Boolean=false)\n  extends ChannelDuplexHandler\n  with Instrumented\n  with StrictLogging\n{\n  import com.ebay.neutrino.handler.ops.ChannelTimeout._\n  import com.ebay.neutrino.handler.ops.NeutrinoAuditHandler._\n  import com.ebay.neutrino.util.AttributeSupport._\n\n  // Context-cached\n  val counter = metrics.counterset(\"timeout\")\n\n\n  /**\n   * Clear any pending timeout tasks on this channel.\n   * @param ctx\n   */\n  private def clearTimeout(ctx: ChannelHandlerContext) = {\n    ctx.timeout map { timeout =>\n      timeout.cancel()\n      ctx.timeout = None\n    }\n  }\n\n\n  /**\n   * Register and activate the timeout on channel activation.\n   *\n   * There is some debate as to where this creation should actually happen.\n   * Other possibilities are channelRegistered() or handlerAdded().\n   * For our specific use-case, we expect this handler will be added prior to the\n   * channel activation, and want it to be as close as possible to the activation.\n   *\n   * @param ctx\n   */\n  override def channelActive(ctx: ChannelHandlerContext): Unit = {\n    require(ctx.timeout.isEmpty)\n    ctx.timeout = ChannelTimeout(ctx, settings)\n    ctx.fireChannelActive()\n  }\n\n  override def channelInactive(ctx: ChannelHandlerContext): Unit = {\n    clearTimeout(ctx)\n    ctx.fireChannelInactive()\n  }\n\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = {\n    ctx.timeout map (_.notifyRead())\n    ctx.fireChannelRead(msg)\n  }\n\n  override def write(ctx: ChannelHandlerContext, msg: AnyRef, promise: ChannelPromise): Unit =\n    ctx.timeout match {\n      // If write-timeouts are specified, put a completion listener to mark our write timers\n      case Some(timeout) if timeout.notifyWrite() =>\n        // Cancel the scheduled timeout if the flush future is complete.\n        val completed = promise.unvoid()\n        completed.addListener(timeout)\n        ctx.write(msg, completed)\n\n      case _ =>\n        ctx.write(msg, promise)\n    }\n\n  /**\n   * Is called when any supported timeout was detected.\n   */\n  override def userEventTriggered(ctx: ChannelHandlerContext, evt: AnyRef): Unit =\n    evt match {\n      case timeout: TimeoutType if ctx.timeout.isDefined =>\n        // Handle timeout notification\n        counter(timeout.getClass) += 1\n\n        val cause = timeout match {\n          case WriteIdle | WriteIncomplete => WriteTimeoutException.INSTANCE\n          case ReadIdle  => ReadTimeoutException.INSTANCE\n          case _         => ReadTimeoutException.INSTANCE\n        }\n\n        // Store the audit record on the request, if available\n        ctx.channel.audit(AuditActivity.ChannelException(ctx.channel, cause))\n\n        // If required, throw the timeout exception\n        if (exceptionOnTimeout) ctx.fireExceptionCaught(cause)\n\n        ctx.close()\n        clearTimeout(ctx)\n\n      case timeout: TimeoutType =>\n        // Already closed; just ignore\n\n      case _ =>\n        ctx.fireUserEventTriggered(evt)\n    }\n\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/handler/ops/HeaderDiagnosticsHandler.scala",
    "content": "package com.ebay.neutrino.handler.ops\n\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.channel._\nimport io.netty.handler.codec.http.HttpMessage\n\n\n/**\n * Simple diagnostics shim to examine HTTP traffic passing through the channel.\n *\n */\n@Sharable\nclass HeaderDiagnosticsHandler(headersOnly: Boolean=true) extends ChannelDuplexHandler with StrictLogging {\n  import scala.collection.JavaConversions._\n\n\n  @inline def format(message: HttpMessage): String =\n    message.headers() map { header => s\"\\t\\t${header.getKey} => ${header.getValue}\" } mkString(\"\\n\")\n\n\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = {\n\n    msg match {\n      case message: HttpMessage =>\n        if (headersOnly)\n          logger.info(\"{} Headers received:\\n{}\", msg.getClass.getName, format(message))\n        else\n          logger.info(\"Message received: {}\", msg)\n\n      case _ =>\n    }\n\n    ctx.fireChannelRead(msg)\n  }\n\n\n  override def write(ctx: ChannelHandlerContext, msg: AnyRef, promise: ChannelPromise): Unit = {\n    msg match {\n      case message: HttpMessage =>\n        if (headersOnly)\n          logger.info(\"{} Headers sent:\\n{}\", msg.getClass.getName, format(message))\n        else\n          logger.info(\"Message sent: {}\", msg)\n\n      case _ =>\n    }\n\n    ctx.write(msg, promise)\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/handler/ops/HttpDiagnosticsHandler.scala",
    "content": "package com.ebay.neutrino.handler.ops\n\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.channel._\n\n\n/**\n * Simple diagnostics shim to examine HTTP traffic passing through the channel.\n *\n */\n@Sharable\nclass HttpDiagnosticsHandler extends ChannelDuplexHandler with StrictLogging {\n  import com.ebay.neutrino.util.AttributeSupport._\n\n  override def close(ctx: ChannelHandlerContext, future: ChannelPromise): Unit = {\n    logger.info(\"Channel {} Closing.\", ctx.session)\n    ctx.close(future)\n  }\n\n  override def channelRegistered(ctx: ChannelHandlerContext): Unit = {\n    logger.info(\"Channel {} Registered.\", ctx.session)\n    ctx.fireChannelRegistered()\n  }\n\n  override def channelUnregistered(ctx: ChannelHandlerContext): Unit = {\n    logger.info(\"Channel {} Un-registered.\", ctx.session)\n    ctx.fireChannelUnregistered()\n  }\n\n  override def channelActive(ctx: ChannelHandlerContext): Unit = {\n    logger.info(\"Channel {} active.\", ctx.session)\n    ctx.fireChannelActive()\n  }\n\n  override def channelInactive(ctx: ChannelHandlerContext): Unit = {\n    logger.info(\"Channel {} Inactive.\", ctx.session)\n    ctx.fireChannelInactive()\n  }\n\n  override def write(ctx: ChannelHandlerContext, msg: Object, promise: ChannelPromise): Unit = {\n    logger.info(\"Channel {} writing msg {}\", ctx.session, msg)\n    ctx.write(msg, promise)\n  }\n\n  override def flush(ctx: ChannelHandlerContext): Unit = {\n    logger.info(\"Channel {} flushing.\", ctx.session)\n    ctx.flush()\n  }\n\n  override def channelRead(ctx: ChannelHandlerContext, msg: Object): Unit = {\n    logger.info(\"Channel {} reading: {}\", ctx.session, msg)\n    ctx.fireChannelRead(msg)\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/handler/ops/NeutrinoAuditHandler.scala",
    "content": "package com.ebay.neutrino.handler.ops\n\nimport java.util\n\nimport com.ebay.neutrino.NeutrinoRequest\nimport com.ebay.neutrino.channel.NeutrinoEvent\nimport com.ebay.neutrino.util.Utilities\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.buffer.{ByteBuf, ByteBufHolder}\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.channel._\nimport io.netty.handler.codec.http._\n\n/**\n * Hook the Channel session to audit incoming/outgoing channel events for post-\n * analysis.\n *\n */\n@Sharable\nclass NeutrinoAuditHandler extends ChannelDuplexHandler with StrictLogging\n{\n  import com.ebay.neutrino.handler.ops.NeutrinoAuditHandler._\n  import com.ebay.neutrino.handler.ops.AuditActivity._\n\n\n  @inline def calculateSize(msg: AnyRef) = msg match {\n    case data: ByteBuf => data.readableBytes()\n    case data: ByteBufHolder => data.content.readableBytes\n    case data => 0\n  }\n\n  override def userEventTriggered(ctx: ChannelHandlerContext, event: AnyRef): Unit = {\n    ctx.channel.audit(UserEvent(event))\n    ctx.fireUserEventTriggered(event)\n  }\n\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = {\n    ctx.channel.audit(\n      msg match {\n        case data: HttpRequest     => Request(data)\n        case data: HttpResponse    => Response(data)\n        case data: LastHttpContent => Content(data.content.readableBytes, true)\n        case data: HttpContent     => Content(data.content.readableBytes)\n        case data: ByteBuf         => ReadData(data.readableBytes)\n      })\n\n    ctx.fireChannelRead(msg)\n  }\n\n  override def write(ctx: ChannelHandlerContext, msg: Object, promise: ChannelPromise): Unit = {\n    ctx.channel.audit(\n      msg match {\n        case data: HttpRequest     => Request(data)\n        case data: HttpResponse    => Response(data)\n        case data: LastHttpContent => Content(data.content.readableBytes, true)\n        case data: HttpContent     => Content(data.content.readableBytes)\n        case data: ByteBuf         => WriteData(data.readableBytes)\n      })\n\n    ctx.write(msg, promise)\n  }\n\n  override def flush(ctx: ChannelHandlerContext): Unit = {\n    ctx.channel.audit(Flush())\n    ctx.flush()\n  }\n\n  override def close(ctx: ChannelHandlerContext, future: ChannelPromise): Unit = {\n    ctx.channel.audit(Close())\n    ctx.close(future)\n  }\n\n  override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) = {\n    ctx.channel.audit(Error(cause))\n    ctx.fireExceptionCaught(cause)\n  }\n}\n\n\nobject NeutrinoAuditHandler extends StrictLogging {\n\n  import Utilities._\n\n\n  implicit class NeutrinoAuditRequestSupport(val self: NeutrinoRequest) extends AnyVal {\n\n    /**\n     * Retrieve the activity-state, creating if necessary.\n     * @return valid state\n     */\n    def state =\n      self.get(classOf[AuditState]) match {\n        case None =>\n          val state = AuditState()\n          self.set(classOf[AuditState], state)\n          state\n\n        case Some(state) =>\n          state\n      }\n\n    // Add the activity provided\n    def audit(channel: Channel, data: => AuditActivity) =\n      state.add(channel, data)\n\n    // Add the activity provided\n    //def audit(channel: Channel, data: Unit => AuditActivity) =\n      //state.add(channel, data())\n\n    // Add the activity provided\n    def audit(channel: Channel, datafx: PartialFunction[Channel, AuditActivity]) =\n      if (datafx.isDefinedAt(channel)) state.add(channel, datafx.apply(channel))\n\n    // Clear the audit state from the request provided\n    def clearstate() = self.clear(classOf[AuditState])\n  }\n\n\n  implicit class NeutrinoAuditSupport(val channel: Channel) extends AnyVal {\n\n    // Retrieve the associated state\n    def auditstate = AuditState.state(channel)\n\n    // Add the activity provided\n    def audit(data: => AuditActivity) = AuditState.audit(channel, data)\n\n    // Clear audit-state, if present\n    def clear() = AuditState.clear(channel)\n  }\n\n\n  class NeutrinoAuditLogger(request: NeutrinoRequest) extends ChannelFutureListener {\n\n    // Log requests that exceed the request's threshold\n    override def operationComplete(future: ChannelFuture): Unit = {\n      // Audit support; if our transaction has taken more than 5 seconds, dump the diagnostics here\n      val settings = request.session.service.settings.channel\n      val channel  = future.channel\n\n      // Generate audit-records and throw debug\n      request.audit(channel, AuditActivity.Event(NeutrinoEvent.ResponseCompleted(request)))\n\n      settings.auditThreshold map { threshold =>\n        // Grab the request's data\n        val state = request.clear(classOf[AuditState])\n\n        state map { state =>\n          if (request.elapsed > threshold)\n            logger.warn(\"Audit state on long running transaction: {}\\n{}\", channel.toStringExt, state)\n          else\n            logger.debug(\"Audit state on transaction: {}\\n{}\", channel.toStringExt, state)\n        }\n      }\n    }\n  }\n}\n\n\nsealed abstract class AuditActivity {\n  val time = System.nanoTime\n}\n\nobject AuditActivity {\n  // Supported types\n  case class Request(request: HttpRequest) extends AuditActivity\n  case class Response(response: HttpResponse) extends AuditActivity\n  case class Content(size: Int, last: Boolean=false) extends AuditActivity\n  case class ReadData(size: Int) extends AuditActivity\n  case class WriteData(size: Int) extends AuditActivity\n  case class Error(cause: Throwable) extends AuditActivity\n  case class Downstream() extends AuditActivity\n  case class DownstreamConnect(success: Boolean) extends AuditActivity\n  case class ChannelAssigned(channel: Channel) extends AuditActivity\n  case class ChannelException(channel: Channel, cause: Throwable) extends AuditActivity\n  case class Event(event: NeutrinoEvent) extends AuditActivity\n  case class UserEvent(event: AnyRef) extends AuditActivity\n  case class Detail(value: String) extends AuditActivity\n  case class Flush() extends AuditActivity\n  case class Close() extends AuditActivity\n}\n\n\ncase class AuditState() {\n\n  import com.ebay.neutrino.handler.ops.AuditActivity._\n  import com.ebay.neutrino.util.AttributeSupport._\n  import scala.collection.JavaConversions.asScalaSet\n\n  private val activity = new util.LinkedList[(Channel, AuditActivity)]\n\n\n  // Add the activity provided\n  def add(data: (Channel, AuditActivity)) =\n    this.synchronized { activity.add(data) }\n\n  def headerStr(msg: HttpMessage) =\n    s\"headers = [${msg.headers.names.mkString(\",\")}]\"\n\n\n  override def toString = {\n    val builder = new StringBuilder(\"AuditState:\\n\")\n    val start   = if (activity.isEmpty) 0L else activity.peekFirst._2.time\n    val iter    = activity.iterator\n\n    // Iterate the activity\n    while (iter.hasNext) {\n      val (channel, item) = iter.next()\n\n      builder\n        .append(\"  \").append(String.format(\"%9s\", \"\"+(item.time-start)/1000)).append(\" micros:\\t\")\n        .append(\n          if (channel.service.isDefined) \"Sx\"+channel.id\n          else \"0x\"+channel.id\n        )\n        .append('\\t')\n\n      builder.append(item match {\n        case Request (data: FullHttpRequest)  => s\"FullRequest (${data.uri}), ${headerStr(data)}\"\n        case Request (data)                   => s\"Request (${data.uri}), ${headerStr(data)}\"\n        case Response(data: FullHttpResponse) => s\"FullResponse, ${headerStr(data)}\"\n        case Response(data)                   => s\"Response, ${headerStr(data)}\"\n        case Content(size, true)     => s\"LastContent($size)\"\n        case Content(size, false)    => s\"Content($size)\"\n        case Error(cause: Throwable) => s\"Error($cause)\"\n        case _                       => item.toString\n      })\n\n      builder.append('\\n')\n    }\n\n    builder.toString\n  }\n}\n\n\n/**\n * Static helper methods.\n *\n * We define them here to ensure the anonymous lambda classes aren't constructed by\n * the value classes.\n */\nobject AuditState {\n\n  import com.ebay.neutrino.handler.ops.NeutrinoAuditHandler._\n  import com.ebay.neutrino.util.AttributeSupport._\n\n\n  def request(channel: Channel): Option[NeutrinoRequest] =\n    channel.request orElse (channel.session flatMap (_.channel.request))\n\n  def state(channel: Channel): Option[AuditState] =\n    request(channel) map (_.state)\n\n  def audit(channel: Channel, data: => AuditActivity) =\n    request(channel) map (_.state.add((channel, data)))\n\n  // Clear audit-state, if present\n  def clear(channel: Channel) =\n    request(channel) map (_.clear(classOf[AuditState]))\n}\n"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/handler/ops/XForwardedHandler.scala",
    "content": "package com.ebay.neutrino.handler.ops\n\nimport java.net.{InetAddress, InetSocketAddress}\n\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.channel.{ChannelHandlerContext, ChannelInboundHandlerAdapter}\nimport io.netty.handler.codec.http.HttpRequest\n\nimport scala.util.Try\n\n\n/**\n * Creates or updates the X-Forwarded-For HTTP header.\n * <p>\n * The general format of the header key: value is:\n * <code>X-Forwarded-For: client, proxy1, proxy2</code>\n * <p>\n * See <a href=\"http://en.wikipedia.org/wiki/X-Forwarded-For\">X-Forwarded-For Wikipedia info</a>.\n *\n * @author <a href=\"mailto://dabecker@ebay.com\">Dan Becker</a>\n */\n@Sharable\nclass XForwardedHandler extends ChannelInboundHandlerAdapter {\n\n  // Child classes should override to customize proxy-IP\n  def localIp(): Option[String] = XForwardedHandler.localHost\n\n  /**\n   * Resolve the client/requestor's IP address.\n   *\n   * Our default implementation returns the socket/channel originator's IP address.\n   * Child classes should override to customize client-IP resolution\n   *\n   * @param ctx\n   * @param request\n   * @return ip/host of client, or None if unable to resolve.\n   */\n  def clientIp(ctx: ChannelHandlerContext, request: HttpRequest): Option[String] =\n    ctx.channel.remoteAddress match {\n      case inet: InetSocketAddress => Option(inet.getAddress.getHostAddress)\n      case _ => None\n    }\n\n\n  /**\n   * Propose the following changes for\n   * Equivalency with previous\n      (forwarded, clientIP, localHost) match {\n        case (Some(fwd), Some(ip), Some(lcl)) if fwd.contains(clientIP) => s\"$fwd,$lcl\"\n        case (Some(fwd), Some(ip), Some(lcl)) => s\"$ip,$fwd,$lcl\"\n        case (Some(fwd), Some(ip), None)      => s\"$ip,$fwd\"\n        case (Some(fwd), None,     Some(lcl)) => s\"$fwd,$lcl\"\n        case (None,      Some(ip), Some(lcl)) => s\"$ip,$lcl\"\n        case (None,      None,     Some(lcl)) => s\"$lcl\"\n        case (Some(fwd), _, _)                => s\"$fwd\"\n        case _                                => null  // \"\"\n      }\n    */\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = {\n\n    msg match {\n      case request: HttpRequest =>\n        val client    = clientIp(ctx, request)\n        val forwarded = Option(request.headers.get(XForwardedHandler.X_FORWARDED_FOR))\n        val headers   = Seq(client, forwarded, localIp).flatten.distinct.mkString(\",\")\n\n        // If valid (ie: non-empty), set the header\n        if (headers.nonEmpty) request.headers.set(XForwardedHandler.X_FORWARDED_FOR, headers)\n\n      case _ =>\n    }\n\n    ctx.fireChannelRead(msg)\n  }\n}\n\n\nobject XForwardedHandler {\n\n  // TODO externalize 'standard' headers\n  val X_FORWARDED_FOR  = \"X-Forwarded-For\"\n\n\n  lazy val localHost = Try(InetAddress.getLocalHost) map (_.getHostAddress) toOption\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/handler/package-info.java",
    "content": "/**\n * Created by cbrawn on 1/16/15.\n */\npackage com.ebay.neutrino.handler;"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/handler/package.scala",
    "content": "package com.ebay.neutrino\n\n// Inbound Handler\n// > take URI, look up an endpoint with it\n// > publish EndpointSelection/EndpointId, remove pipeline (replace?)\n\n// Inbound Handler\n// > Pipeline...\n\n// Inbound Handler\n// > Start connection pair; relay A <=> B\n\npackage handler {}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/handler/pipeline/HttpInboundPipeline.java",
    "content": "package com.ebay.neutrino.handler.pipeline;\n\nimport com.ebay.neutrino.NeutrinoRequest;\nimport io.netty.handler.codec.http.HttpResponse;\n\n/**\n * Very very simplified 'generic' pipeline interface for processing HTTP request pipeline\n * functionality.\n *\n * Note that the conditional-check is removed (ie: shouldExecute(request)). Instead, the\n * pipeline should simply return continue.\n *\n * Created by cbrawn on 9/24/14.\n *\n * TODO - rename something (much) better?\n */\npublic interface HttpInboundPipeline {\n\n    /**\n     * Response status:\n     *  SKIPPED: pipeline was not executed (did not meet conditions); continue pipeline execution with request\n     *  CONTINUE: pipeline was executed successfully; continue pipeline execution with request\n     *  COMPLETE: pipeline was executed successfully; skip remainder of pipeline and return request\n     *  REJECT: pipeline stage failed; a failure-response is optionally provided.\n     */\n    public enum Status { SKIPPED, CONTINUE, COMPLETE, REJECT }\n\n\n    /**\n     * Execution result type from the pipeline execution.\n     *  Status will always be populated, as one of Status.enum\n     *  Response is optional and will only be provided on REJECT status\n     */\n    public interface Result {\n        public Status status();\n        public HttpResponse response();\n    }\n\n\n    /**\n     * Execute the pipeline functionality, and return a conditional response.\n     *\n     * @param connection\n     * @param request\n     */\n    public Result execute(final NeutrinoRequest request);\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/handler/pipeline/HttpPipelineUtils.java",
    "content": "package com.ebay.neutrino.handler.pipeline;\n\nimport com.ebay.neutrino.handler.pipeline.HttpInboundPipeline.Result;\nimport com.ebay.neutrino.handler.pipeline.HttpInboundPipeline.Status;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.handler.codec.http.*;\nimport io.netty.util.CharsetUtil;\n\n/**\n * Helper class for interacting with the HttpInboundPipeline interfaces.\n */\npublic abstract class HttpPipelineUtils {\n    // Prevent instantiation\n    private HttpPipelineUtils() {}\n\n    /**\n     * Convert the string-content provided to a byte-buffer\n     * TODO - should these be hard-coded to prevent heap-allocation issues\n     */\n    public static final ByteBuf toByteBuf(final String content) {\n        // Allocate some memory for this\n        // final ByteBuf buffer = Unpooled.copiedBuffer(content, CharsetUtil.UTF_8);\n        final ByteBuf byteBuf = Unpooled.buffer();\n        byteBuf.writeBytes(content.getBytes(CharsetUtil.UTF_8));\n        return byteBuf;\n    }\n\n    /**\n     * Singleton implementations of the common results.\n     */\n    public static final Result RESULT_SKIPPED = new Result() {\n        public Status status() { return Status.SKIPPED; }\n        public HttpResponse response() { return null; }\n    };\n\n    public static final Result RESULT_CONTINUE = new Result() {\n        public Status status() { return Status.CONTINUE; }\n        public HttpResponse response() { return null; }\n    };\n\n    public static final Result RESULT_COMPLETE = new Result() {\n        public Status status() { return Status.COMPLETE; }\n        public HttpResponse response() { return null; }\n    };\n\n    /**\n     * Create a failed-result with the provided response details.\n     *\n     * @param status HTTP status\n     * @param contentType one of the legal response header CONTENT_TYPE values.\n     * @param content error message to user. It is important not to divulge stack trace or other internal details.\n     * @return\n     */\n    public static Result reject(final HttpResponseStatus status, final String contentType, final String content) {\n        return new Result() {\n            public Status status() { return Status.REJECT; }\n\n            // I think we should rely on the ops pipelines to 'enforce' the correct default outbound headers\n            public HttpResponse response() {\n                final HttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, toByteBuf(content));\n                resp.headers().add(HttpHeaderNames.CONTENT_TYPE, contentType);\n                return resp;\n            }\n        };\n    }\n\n    /**\n     * Create a failed-result with the provided response details. Courtesy shortcut method.\n     *\n     * @param contentType one of the legal response header CONTENT_TYPE values.\n     * @param content error message to user. It is important not to divulge stack trace or other internal details.\n     * @return\n     */\n    public static Result reject(final String contentType, final String content) {\n        return reject( HttpResponseStatus.OK, contentType, content );\n    }\n\n    /**\n     * Create a failed-result with the provided response details. Courtesy shortcut method.\n     *\n     * @param status HTTP status\n     * @return\n     */\n    public static Result reject(final HttpResponseStatus status) {\n        return reject(status, \"text/plain; charset=UTF-8\", \"Reject: \" + status.toString() + \"\\r\\n\");\n    }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/health/HealthProvider.scala",
    "content": "package com.ebay.neutrino.health\n\nimport com.ebay.neutrino.config.VirtualServer\n\n\n/**\n * Marker interface for processing health information.\n */\ntrait HealthProvider {\n\n  /**\n   * Retrieve the health of the specific server.\n   */\n  def getHealth(server: VirtualServer)\n\n  /**\n   * Register a health notification callback for the server provided.\n   */\n  def registerListener(server: VirtualServer, listener: HealthListener)\n\n  /**\n   * Remove a health notification callback for the server provided.\n   */\n  def removeListener(server: VirtualServer)\n}\n\n\n/**\n * Marker interface for a health listener.\n */\ntrait HealthListener {\n\n  // TODO\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/metrics/HealthMonitor.scala",
    "content": "package com.ebay.neutrino.metrics\n\n/**\n * We support the following dynamic instantiations of the trait implementations:\n *  newInstance()\n *  newInstance(c: Config)\n */\ntrait HealthMonitor {}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/metrics/Instrumented.scala",
    "content": "package com.ebay.neutrino.metrics\n\nimport nl.grons.metrics.scala._\nimport scala.collection.mutable\n\n\n/**\n * This mixin trait can used for creating a class which is instrumented with metrics.\n *\n * Reimplented our own base-class instead of extending InstrumentedBuilder because\n * we wanted to support a subclass implementation of the MetricsBuilder class\n *  > extends nl.grons.metrics.scala.InstrumentedBuilder\n *\n */\ntrait Instrumented extends BaseBuilder {\n\n  /**\n   * The MetricBuilder that can be used for creating timers, counters, etc.\n   */\n  lazy val metrics =\n    new MetricBuilder(metricBaseName, Metrics.metricRegistry) with InstrumentedBuilderSupport\n}\n\n\ntrait InstrumentedBuilderSupport { self: MetricBuilder =>\n\n  /** Helper; build a metric-name out of the components provided */\n  def metricName(names: String*) = baseName.append(names:_*)\n\n  /**\n   * Registers a new gauge metric.\n   */\n  def gauge[A](metricName: MetricName, scope: String*)(f: => A): Gauge[A] =\n    new Gauge[A](registry.register(metricName.append(scope:_*).name, new com.codahale.metrics.Gauge[A] {\n      def getValue: A = f })\n    )\n\n  def gauge[A](clazz: Class[_], name: String*)(f: => A): Gauge[A] =\n    gauge(MetricName(clazz, name:_*).name)(f)\n\n\n  /**\n   * Registers a new timer metric.\n   */\n  def timer(metricName: MetricName, scope: String*): Timer =\n    new Timer(registry.timer(metricName.append(scope:_*).name))\n\n  def timer(clazz: Class[_], name: String*): Timer =\n    timer(MetricName(clazz, name:_*).name)\n\n\n  /**\n   * Registers a new meter metric.\n   */\n  def meter(metricName: MetricName, scope: String*): Meter =\n    new Meter(registry.meter(metricName.append(scope:_*).name))\n\n  def meter(clazz: Class[_], name: String*): Meter =\n    meter(MetricName(clazz, name:_*).name)\n\n\n  /**\n   * Registers a new histogram metric.\n   */\n  def histogram(metricName: MetricName, scope: String*): Histogram =\n    new Histogram(registry.histogram(metricName.append(scope:_*).name))\n\n  def histogram(clazz: Class[_], name: String*): Histogram =\n    histogram(MetricName(clazz, name:_*).name)\n\n\n  /**\n   * Registers a new counter metric.\n   */\n  def counter(name: String, scope: String*): Counter =\n    counter(metricName(name +: scope:_*))\n\n  def counter(metricName: MetricName, scope: String*): Counter =\n    new Counter(registry.counter(metricName.append(scope:_*).name))\n\n  def counter(clazz: Class[_], name: String*): Counter =\n    counter(MetricName(clazz, name:_*).name)\n\n  def counterset(name: String, scope: String*): CounterSet =\n    new CounterSet(metricName(name +: scope:_*))\n\n\n  /**\n   * Hack - this works around writing a duplicate gauge (ie: with unit testing)\n   * TODO replace this with a non-static MetricsRegistry\n   */\n  def safegauge[A](metricName: MetricName, scope: String*)(f: => A): Gauge[A] = {\n    val metricname = metricName.append(scope: _*).name\n    registry.remove(metricname)\n    new Gauge[A](registry.register(metricname, new com.codahale.metrics.Gauge[A] { def getValue: A = f }))\n  }\n\n  def safegauge[A](name: String, scope: String = null)(f: => A): Gauge[A] =\n    safegauge(baseName.append(name), scope)(f)\n\n\n\n  class CounterSet(metricName: MetricName) {\n    val set = mutable.Map[String, Counter]()\n\n    def apply(name: String): Counter = set.getOrElseUpdate(name, counter(name))\n    def apply(clazz: Class[_]): Counter = apply(clazz.getSimpleName)\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/metrics/Metrics.scala",
    "content": "package com.ebay.neutrino.metrics\n\nimport com.ebay.neutrino.channel.NeutrinoSession\nimport com.ebay.neutrino.handler.NeutrinoRequestHandler\nimport com.ebay.neutrino.{NeutrinoCore, NeutrinoNode}\nimport io.netty.channel.Channel\nimport nl.grons.metrics.scala._\n\n\n/**\n * Yammer metrics implementation based on metrics-scala (https://github.com/erikvanoosten/metrics-scala)\n * Static metrics (mapped directly to their key equivalent).\n *\n * @author hzariv on 8/29/2014.\n * @author cbrawn on 2/13/2015; static metrics direct link\n */\nobject Metrics extends Instrumented {\n  import com.ebay.neutrino.config.{CompletionStatus => Status}\n  import com.ebay.neutrino.metrics.{MetricsKey => Key}\n\n  /** The application wide metrics registry. */\n  val metricRegistry = new com.codahale.metrics.MetricRegistry()\n\n\n  // Requests\n  val RequestsOpen            = metrics.counter(Key.Requests, \"open\")\n  val RequestsTotal           = metrics.meter  (Key.Requests, \"total\")\n  val RequestsCompleted       = metrics.timer  (Key.Requests, \"duration\")\n\n  // Store bucketed response detail as well;  should we have both total and each? or just each?\n  val RequestsCompletedType   = Map[Status, Timer] (\n    Status.Incomplete        -> metrics.timer(Key.RequestsCompleted, \"none\"),\n    Status.Status2xx         -> metrics.timer(Key.RequestsCompleted, \"2xx\"),\n    Status.Status4xx         -> metrics.timer(Key.RequestsCompleted, \"4xx\"),\n    Status.Status5xx         -> metrics.timer(Key.RequestsCompleted, \"5xx\"),\n    Status.Other             -> metrics.timer(Key.RequestsCompleted, \"other\"))\n\n  // Sessions\n  val SessionActive           = metrics.counter(Key.Session, \"open\")\n  val SessionTotal            = metrics.meter  (Key.Session, \"total\")\n  val SessionDuration         = metrics.timer  (Key.Session, \"duration\")\n\n  // Channels: Upstream/Browser\n  val UpstreamOpen            = metrics.counter(Key.UpstreamChannel, \"open\")\n  val UpstreamTotal           = metrics.meter  (Key.UpstreamChannel, \"total\")\n  val UpstreamCompleted       = metrics.timer  (Key.UpstreamChannel, \"complete\")\n  val UpstreamBytesRead       = metrics.counter(Key.UpstreamChannel, \"bytes.read\")\n  val UpstreamBytesWrite      = metrics.counter(Key.UpstreamChannel, \"bytes.write\")\n  val UpstreamPacketsRead     = metrics.meter  (Key.UpstreamChannel, \"packets.read\")\n  val UpstreamPacketsWrite    = metrics.meter  (Key.UpstreamChannel, \"packets.write\")\n\n  // Channels: Downstream/Server\n  val DownstreamOpen          = metrics.counter(Key.DownstreamChannel, \"open\")\n  val DownstreamTotal         = metrics.meter  (Key.DownstreamChannel, \"total\")\n  val DownstreamCompleted     = metrics.timer  (Key.DownstreamChannel, \"complete\")\n  val DownstreamBytesRead     = metrics.counter(Key.DownstreamChannel, \"bytes.read\")\n  val DownstreamBytesWrite    = metrics.counter(Key.DownstreamChannel, \"bytes.write\")\n  val DownstreamPacketsRead   = metrics.meter  (Key.DownstreamChannel, \"packets.read\")\n  val DownstreamPacketsWrite  = metrics.meter  (Key.DownstreamChannel, \"packets.write\")\n\n  // Channels: Connection-pooling\n  val PooledCreated           = metrics.meter  (Key.PoolAllocated, \"created\")\n  val PooledRecreated         = metrics.meter  (Key.PoolAllocated, \"recreated\")\n  val PooledAssigned          = metrics.meter  (Key.PoolAllocated, \"assigned\")\n  val PooledRetained          = metrics.counter(Key.PoolReleased,  \"retained\")\n  val PooledFailed            = metrics.counter(Key.PoolReleased,  \"failed\")\n  val PooledExpired           = metrics.counter(Key.PoolReleased,  \"expired\")\n  val PooledClosed            = metrics.counter(Key.PoolReleased,  \"closed\")\n}\n\n\n/**\n * Metric-key constants. These are the common metrics for core-system.\n *\n * Consumers shouldn't need to refer to these directly; they should be able to use\n * the static bindings in Metrics.\n *\n * @author cbrawn\n */\nobject MetricsKey {\n\n  // Core\n  val BossThreads             = MetricName(classOf[NeutrinoCore], \"threads.boss\")\n  val IOThreads               = MetricName(classOf[NeutrinoCore], \"threads.io\")\n\n  // Requests\n  val Requests                = MetricName(classOf[NeutrinoRequestHandler], \"requests\")\n  val RequestsCompleted       = Requests.append(\"completed\")\n\n  // Sesssions\n  val Session                 = MetricName(classOf[NeutrinoSession])\n\n  // Channels\n  val Channel                 = MetricName(classOf[Channel])\n  val UpstreamChannel         = Channel.append(\"upstream\")\n  val DownstreamChannel       = Channel.append(\"downstream\")\n\n  // Channels: Connection-pooling\n  val PoolAllocated           = MetricName(classOf[NeutrinoNode], \"allocate\")\n  val PoolReleased            = MetricName(classOf[NeutrinoNode], \"release\")\n}\n\n\nobject HealthCheck {\n  /** The application wide health check registry. */\n  val healthChecksRegistry = new com.codahale.metrics.health.HealthCheckRegistry()\n}\n\n/**\n * This mixin trait can used for creating a class which creates health checks.\n */\ntrait Checked extends nl.grons.metrics.scala.CheckedBuilder {\n  val healthCheckRegistry = HealthCheck.healthChecksRegistry\n}\n"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/metrics/MetricsAnnotationRegistry.scala",
    "content": "package com.ebay.neutrino.handler\n\nimport java.lang.annotation.Annotation\nimport scala.collection.mutable\n\nimport com.codahale.metrics.annotation.{Gauge, Metered, Timed}\nimport io.netty.channel._\n\n\nclass MetricsAnnotationRegistry {\n\n  // Constants\n  val Inbound  = classOf[ChannelInboundHandler]\n  val Outbound = classOf[ChannelOutboundHandler]\n  val Methods  = Inbound.getMethods ++ Outbound.getMethods\n\n\n  // Extract an interface-supported method matching the method-name, from the class provided.\n  case class AnnotatedPair[T](clazz: Class[T], methodName: String) {\n    require(Inbound.isAssignableFrom(clazz) || Outbound.isAssignableFrom(clazz), s\"Class $clazz is not a handler interface\")\n\n    // Check interface for method-signature and find class's matching method\n    // Extract all available annotations from the class-method provided.\n    lazy val annotations = Methods find(_.getName == methodName) map (m =>\n      clazz.getMethod(methodName, m.getParameterTypes:_*)) match {\n      case Some(method) => method.getAnnotations\n      case None => throw new IllegalArgumentException(s\"Method $methodName not found in $clazz\")\n    }\n  }\n\n  val map = mutable.Map[AnnotatedPair[_], Array[Annotation]]()\n\n\n  def getAnnotations(clazz: Class[_], methodName: String) = {\n    val pair = AnnotatedPair(clazz, methodName)\n\n    map getOrElseUpdate (pair, pair.annotations flatMap {\n      case timed: Timed   => Option(timed)\n      case meter: Metered => Option(meter)\n      case gauge: Gauge   => Option(gauge)\n      case _              => None // exception\n    })\n  }\n\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/metrics/MetricsLifecycle.scala",
    "content": "package com.ebay.neutrino.metrics\n\nimport java.net.{InetAddress, InetSocketAddress}\nimport java.util.concurrent.TimeUnit\n\nimport com.codahale.metrics.ConsoleReporter\nimport com.codahale.metrics.graphite.{Graphite, GraphiteReporter}\nimport com.ebay.neutrino.config.Configuration\nimport com.ebay.neutrino.{NeutrinoCore, NeutrinoLifecycle}\nimport com.typesafe.config.Config\nimport com.typesafe.scalalogging.slf4j.StrictLogging\n\n\n/**\n * Simple initializer for configuring console-support for metrics.\n *\n */\nclass MetricsLifecycle(settings: Seq[MetricsSettings]) extends NeutrinoLifecycle with StrictLogging {\n\n  val registry = Metrics.metricRegistry\n\n  // TODO resolve this properly\n  val hostname = InetAddress.getLocalHost.getHostName\n\n  // Our configured reporters\n  val reporters =\n    settings flatMap {\n      case ConsoleMetrics(period) =>\n        val reporter = ConsoleReporter.forRegistry(registry)\n          .convertRatesTo(TimeUnit.SECONDS)\n          .convertDurationsTo(TimeUnit.MILLISECONDS)\n          .build()\n        Option(reporter -> period)\n\n      case GraphiteMetrics(period, host, port) =>\n        val graphite = new Graphite(new InetSocketAddress(host, port))\n        val reporter = GraphiteReporter.forRegistry(registry)\n          .prefixedWith(hostname)\n          .convertRatesTo(TimeUnit.SECONDS)\n          .convertDurationsTo(TimeUnit.MILLISECONDS)\n          //.filter(MetricFilter.ALL)\n          .build(graphite)\n        Option(reporter -> period)\n\n      case _ => None\n    }\n\n\n  logger.info(\"Setting our hostname to {} for metrics reporting.\", hostname)\n\n\n  /**\n   * Overloaded Constructor; create a MetricsLifecycle with a Config object.\n   *\n   * This will be used by the plugin reflection (for dynamic class creation) to inject\n   * a runtime configuration into class creation.\n   */\n  def this(config: Config) = this(MetricsLifecycle.settings(config))\n\n\n  // Start any available reporters\n  def start() =\n    reporters foreach {\n      case (reporter, period) => reporter.start(period.toSeconds, TimeUnit.SECONDS)\n    }\n\n  // Stop all reporters (if appropriate)\n  def shutdown() =\n    reporters foreach {\n      case (reporter, period) => reporter.stop()\n    }\n\n\n  // Neutrino Lifecycle support\n  def start(balancer: NeutrinoCore) = start()\n  def shutdown(balancer: NeutrinoCore) = shutdown()\n}\n\nobject MetricsLifecycle {\n\n  import Configuration._\n\n  def settings(config: Config) =\n    config getOptionalConfigList \"metrics\" map (MetricsSettings(_))\n\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/metrics/MetricsSettings.scala",
    "content": "package com.ebay.neutrino.metrics\n\nimport java.util\n\nimport com.ebay.neutrino.config.HasConfiguration\nimport com.typesafe.config.Config\n\nimport scala.concurrent.duration.FiniteDuration\n\n\nsealed trait MetricsSettings\ncase class ConsoleMetrics(publishPeriod: FiniteDuration) extends MetricsSettings\ncase class GraphiteMetrics(publishPeriod: FiniteDuration, host: String, port: Int) extends MetricsSettings\ncase class UnknownMetrics(config: Config) extends MetricsSettings with HasConfiguration\n\n\nobject MetricsSettings {\n  import com.ebay.neutrino.config.Configuration._\n\n  import scala.collection.JavaConversions._\n\n\n  // Extract a list of appropriate metric-settings\n  def apply(list: util.List[_ <: Config]): List[MetricsSettings] =\n    list.toList map (MetricsSettings(_))\n\n  def apply(list: List[_ <: Config]): List[MetricsSettings] =\n    list.toList map (MetricsSettings(_))\n\n  // Extract the appropriate metric-settings based on the type specified in the config\n  def apply(c: Config): MetricsSettings =\n    c getString \"type\" toLowerCase match {\n      case \"console\"  => console(c)\n      case \"graphite\" => graphite(c)\n      case _          => unknown(c)\n    }\n\n  // Extract settings for publishing console metrics\n  def console(c: Config): MetricsSettings = ConsoleMetrics(\n    c getDuration \"publish-period\"\n  )\n\n  // Extract settings for publishing graphite metrics\n  def graphite(c: Config): MetricsSettings = GraphiteMetrics(\n    c getDuration \"publish-period\",\n    c getString \"host\",\n    c getOptionalInt \"port\" getOrElse 2003\n  )\n\n  // Extract settings for unknown metrics\n  def unknown(c: Config): MetricsSettings = UnknownMetrics(c)\n\n}\n"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/util/Attributes.scala",
    "content": "package com.ebay.neutrino.util\n\nimport com.ebay.neutrino._\nimport com.ebay.neutrino.balancer.Balancer\nimport com.ebay.neutrino.channel.{NeutrinoPipelineChannel, NeutrinoService, NeutrinoSession}\nimport com.ebay.neutrino.handler.ops.{ChannelStatistics, ChannelTimeout}\nimport io.netty.channel._\nimport io.netty.util.{Attribute, AttributeKey, AttributeMap}\n\n\n/**\n * SIP 15 (Static helper) Support for managing known attribute-keys on the\n * channel (or indirectly via the context).\n *\n * This would be easier to do with view-classes, but the compiler is considering them as field annotation:\n *  implicit class AttributeKeySupport[C <% Channel](val self: C)\n *\n * @param self\n */\nobject AttributeSupport {\n\n  val BalancerKey     = AttributeKey.valueOf[NeutrinoCore](\"balancer\")\n  val RequestKey      = AttributeKey.valueOf[NeutrinoRequest](\"request\")\n  val SessionKey      = AttributeKey.valueOf[NeutrinoSession](\"session\")\n  val ServiceKey      = AttributeKey.valueOf[NeutrinoService](\"service\")\n  val TimeoutKey      = AttributeKey.valueOf[ChannelTimeout](\"timeouts\")\n  val StatisticsKey   = AttributeKey.valueOf[ChannelStatistics](\"statistics\")\n\n\n  /**\n   * Generalized attribute-map helper functions, for AttributeMap subtypes (Channel, Context)\n   *\n   * These must be chained on the underlying AttributeMap class (rather than inherited\n   * directly in the caller value-class, as that forces an object instantiation.\n   *\n   * @param self\n   */\n  implicit class AttributeMapSupport(val self: AttributeMap) extends AnyVal {\n\n    // Resolve attribute from the underlying container\n    def attr[T](key: AttributeKey[T]): Attribute[T] = self.attr(key)\n\n    // Generic retrieval method\n    def get[T](key: AttributeKey[T]): Option[T] =\n      Option(attr(key).get())\n\n    // Generic retrieval method, with default\n    def getOrElse[T](key: AttributeKey[T])(default: => T): T =\n      get(key) getOrElse (set(key, Option(default)).get)\n\n    // Generic setter/clear method\n    def set[T](key: AttributeKey[T], value: Option[T]): Option[T] = value match {\n      case Some(set) => attr(key).set(set); value\n      case None => attr(key).remove(); value\n    }\n  }\n\n\n  /**\n   * Attribute-based support methods.\n   *\n   */\n  implicit class AttributeValueSupport(val self: AttributeMap) extends AnyVal {\n\n    // We provide a structural definition for this so our static 'statistics' method\n    // doesn't need to create an anonymous method\n    private def createStatistics = new ChannelStatistics()\n\n\n    // The balancer-connection (request/response context)\n    def request = self.get(RequestKey)\n    def request_=(value: NeutrinoRequest) = self.set(RequestKey, Option(value))\n    def request_=(value: Option[NeutrinoRequest]) = self.set(RequestKey, value)\n\n    // Neutrino-Session (user-pipeline) getter and setter\n    def session = self.get(SessionKey)\n    def session_=(value: NeutrinoSession) = self.set(SessionKey, Option(value))\n    def session_=(value: Option[NeutrinoSession]) = self.set(SessionKey, value)\n\n    // Neutrino-Service (wrapper around core) getter and setter\n    def service = self.get(ServiceKey)\n    def service_=(value: NeutrinoService) = self.set(ServiceKey, Option(value))\n    def service_=(value: Option[NeutrinoService]) = self.set(ServiceKey, value)\n\n    // Channel timeout support\n    def timeout = self.get(TimeoutKey)\n    def timeout_=(value: ChannelTimeout) = self.set(TimeoutKey, Option(value))\n    def timeout_=(value: Option[ChannelTimeout]) = self.set(TimeoutKey, value)\n\n    // IO-Channel Statistics lazy-getter\n    def statistics = self.get(StatisticsKey) match {\n      case Some(stats) => stats\n      case None => self.set(StatisticsKey, Option(new ChannelStatistics())).get\n    }\n\n  }\n\n\n  /**\n   * Attribute support for NeutrinoRequest (class-key mapped) attributes.\n   * @param self\n   */\n  implicit class RequestAttributeSupport(val self: NeutrinoRequest) extends AnyVal {\n\n    def balancer = self.get(classOf[Balancer])\n    def balancer_=(value: Balancer) = self.set(classOf[Balancer], value)\n    def balancer_=(value: Option[Balancer]) = self.set(classOf[Balancer], value getOrElse null)\n\n    def node = self.get(classOf[NeutrinoNode])\n    def node_=(value: NeutrinoNode) = self.set(classOf[NeutrinoNode], value)\n    def node_=(value: Option[NeutrinoNode]) = self.set(classOf[NeutrinoNode], value getOrElse null)\n\n    // Neutrino Pipeline only: downstream context\n    def downstream = self.get(classOf[Channel])\n    def downstream_=(value: Channel) = self.set(classOf[Channel], value)\n    def downstream_=(value: Option[Channel]) = self.set(classOf[Channel], value getOrElse null)\n  }\n\n}\n\n\n\n/**\n * Support for dynamic class-based attributes, which can be mixed into other classes for\n * 'context' support, allowing dynamic class-specific storage and resolution.\n *\n * Note that we don't really support null (as a null value gets mapped to None)\n */\nclass AttributeClassMap {\n\n  val attributes = new java.util.HashMap[Class[_], AnyRef]()\n\n  // Optionally get the value belonging to <clazz>\n  def get[T <: AnyRef](clazz: Class[T]): Option[T] =\n    Option(attributes.get(clazz).asInstanceOf[T])\n\n  // Get the value belonging to <clazz>, defaulting to the provided\n  def get[T <: AnyRef](clazz: Class[T], default: => T): T =\n    attributes.get(clazz) match {\n      case null  => val value = default; attributes.put(clazz, value); value\n      case value => value.asInstanceOf[T]\n    }\n\n  // Set/Clear the value belonging to <clazz>\n  def set[T <: AnyRef](clazz: Class[T], value: T): Unit =\n    value match {\n      case null  => attributes.remove(clazz)\n      case value => attributes.put(clazz, value)\n    }\n\n  // Explicitly clear the value belonging to <clazz>\n  def clear[T <: AnyRef](clazz: Class[T]): Option[T] =\n    Option(attributes.remove(clazz)).asInstanceOf[Option[T]]\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/util/HttpUtils.scala",
    "content": "package com.ebay.neutrino.util\n\nimport java.net.URI\n\nimport com.ebay.neutrino.config.Host\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.buffer.Unpooled\nimport io.netty.channel.ChannelHandlerContext\nimport io.netty.handler.codec.http.HttpHeaders.{Names, Values}\nimport io.netty.handler.codec.http._\nimport io.netty.util.CharsetUtil\n\n\n/**\n * A delegate implementation of the ChannelPipeline to assist with Neutrino pipeline\n * creation.\n *\n */\nobject HttpRequestUtils {\n\n  import com.ebay.neutrino.util.AttributeSupport._\n\n  import scala.collection.JavaConversions._\n\n\n  // Attempt to assign the connection's pool based on the pool-name provided\n  def setPool(ctx: ChannelHandlerContext, poolname: String): Boolean =\n    // Try and grab the connection out of the context\n    ctx.request map (_.pool.set(poolname).isDefined) getOrElse false\n\n\n  // Copy any headers\n  def copy(request: HttpRequest) = {\n    val copy = new DefaultHttpRequest(request.protocolVersion(), request.method(), request.uri())\n    request.headers() foreach { hdr => copy.headers().add(hdr.getKey, hdr.getValue) }\n    copy\n  }\n\n  /**\n   * Attempt to construct an absolute URI from the request, if possible, falling\n   * back on relative URI\n   *\n   * Will take:\n   * 1) HttpRequest URI, if absolute\n   * 2) HttpRequest URI + Host header, if available\n   * 3) HttpRequest relative URI\n   */\n  def URI(request: HttpRequest): URI = {\n\n    val scheme = \"http\" // TODO how to extract?\n    val hosthdr = Option(request.headers.get(HttpHeaderNames.HOST)) map (Host(_))\n\n    (java.net.URI.create(request.uri), request.host(false)) match {\n      case (uri, _) if uri.isAbsolute    => uri\n      case (uri, None)                   => uri\n      case (uri, Some(Host(host, 0)))    => new URI(scheme, uri.getAuthority, host, uri.getPort, uri.getPath, uri.getQuery, uri.getFragment)\n      case (uri, Some(Host(host, port))) => new URI(scheme, uri.getAuthority, host, port, uri.getPath, uri.getQuery, uri.getFragment)\n    }\n  }\n\n\n\n  // Pimp for Netty HttpRequest.\n  implicit class HttpRequestSupport(val self: HttpRequest) extends AnyVal {\n\n    /**\n     * Attempt to construct an absolute URI from the request, if possible, falling\n     * back on relative URI\n     */\n    def URI(): URI = HttpRequestUtils.URI(self)\n\n    def host(useUri: Boolean=true): Option[Host] = useUri match {\n      case true  => host(URI)\n      case false => Option(self.headers.get(HttpHeaderNames.HOST)) map (Host(_))\n    }\n\n    def host(uri: URI) = {\n      Option(uri.getHost) map {\n        case host if uri.getPort <= 0 => Host(host.toLowerCase)\n        case host => Host(host.toLowerCase, uri.getPort)\n      }\n    }\n  }\n}\n\n\n/**\n * A delegate implementation of the ChannelPipeline to assist with Neutrino pipeline\n * creation.\n *\n */\nobject HttpResponseUtils {\n  import io.netty.handler.codec.http.HttpHeaderNames._\n  import io.netty.handler.codec.http.HttpVersion._\n\n\n  def error(status: HttpResponseStatus) = {\n    // Allocate some memory for this\n    val buffer = Unpooled.copiedBuffer(s\"Failure: $status\\r\\n\", CharsetUtil.UTF_8)\n\n    // Package a response\n    val response = new DefaultFullHttpResponse(HTTP_1_1, status, buffer)\n    response.headers().set(CONTENT_TYPE, \"text/plain; charset=UTF-8\")\n    response\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/util/Response.scala",
    "content": "package com.ebay.neutrino.util\n\nimport io.netty.buffer.{ByteBuf, Unpooled}\nimport io.netty.handler.codec.http._\n\nimport scala.xml.Elem\n\n\n/**\n * Convenience methods for generating HTTP response codes.\n * TODO - build a simple cache around this...\n */\nclass ResponseGenerator {\n\n  import com.ebay.neutrino.util.ResponseUtil._\n  import scalatags.Text.all._\n\n\n  // Cached messages\n  val messages: Map[HttpResponseStatus, String] =\n    Map(\n      HttpResponseStatus.BAD_GATEWAY ->\n        \"\"\"Our apologies for the temporary inconvenience. The requested URL generated 503 \"Service Unavailable\" error.\n          |This could be due to no available downstream servers, or overloading or maintenance of the downstream server.\"\"\".stripMargin,\n      HttpResponseStatus.GATEWAY_TIMEOUT ->\n        \"\"\"The server was acting as a gateway or proxy and did not receive a timely response from the upstream server..\"\"\"\n    )\n\n  val DefaultMessage = messages(HttpResponseStatus.BAD_GATEWAY)\n\n\n  /**\n   * Generate a message, defaulting to the HttpStatus's stock message.\n   */\n  def generate(status: HttpResponseStatus, detail: String): FullHttpResponse =\n    generate(status, messages get(status) getOrElse DefaultMessage, detail)\n\n  /**\n   * Generate a message, with the message and detail provided.\n   */\n  def generate(status: HttpResponseStatus, message: String, detail: String): FullHttpResponse =\n    ResponseUtil.generate(status,\n      html(\n        head(title := s\"Error ${status.code} ${status.reasonPhrase}\"),\n        body(\n          h1(s\"${status.code} ${status.reasonPhrase}\"),\n          s\"The server reported: $message\",\n          h2(\"Additional Details:\"),\n          detail\n        )\n      ))\n}\n\n\n/**\n * Convenience methods for generating an HTTP response.\n *\n */\nobject ResponseUtil {\n\n  import scalatags.Text\n\n  // Constants\n  val TextPlain = \"text/plain; charset=UTF-8\"\n  val TextHtml  = \"text/html; charset=UTF-8\"\n\n  // Helper methods\n  private def formatter() = new scala.xml.PrettyPrinter(80, 4)\n\n\n  // Generate a response from the params provided.\n  def generate(status: HttpResponseStatus, content: Text.TypedTag[String]): FullHttpResponse =\n    generate(status, scala.xml.XML.loadString(content.toString()))\n\n  def generate(status: HttpResponseStatus, content: Elem): FullHttpResponse =\n    generate(status, formatter().format(content)+\"\\r\\n\")\n\n  def generate(status: HttpResponseStatus, content: String): FullHttpResponse =\n    generate(status, Unpooled.wrappedBuffer(content.getBytes), TextHtml)\n\n  def generate(status: HttpResponseStatus, buffer: ByteBuf): FullHttpResponse =\n    generate(status, buffer, TextPlain)\n\n  def generate(status: HttpResponseStatus, buffer: ByteBuf, contentType: String): FullHttpResponse =\n    generate(status, buffer, contentType, HttpVersion.HTTP_1_1)\n\n  def generate(status: HttpResponseStatus, buffer: ByteBuf, contentType: String, version: HttpVersion): FullHttpResponse = {\n    val response = new DefaultFullHttpResponse(version, status, buffer)\n    response.headers.set(HttpHeaderNames.CONTENT_TYPE, contentType)\n    response.headers.set(HttpHeaderNames.CONTENT_LENGTH, buffer.readableBytes())\n    response\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/util/ServerContext.scala",
    "content": "package com.ebay.neutrino.util\n\nimport java.net.{UnknownHostException, InetAddress}\n\nimport com.typesafe.scalalogging.slf4j.StrictLogging\n\n/**\n * Created by blpaul on 12/1/2015.\n */\nobject ServerContext extends StrictLogging {\n\n\n  val fullHostName =  {\n    var hostName : String = \"Not Available\"\n    try {\n      hostName = InetAddress.getLocalHost.getHostName\n    }\n    catch {\n      case ex : UnknownHostException =>\n        logger.warn(\"Unable to get the hostname\")\n    }\n    hostName\n  }\n\n  val canonicalHostName = {\n    var hostName : String = \"Not Available\"\n    try {\n      hostName = InetAddress.getLocalHost.getCanonicalHostName\n    }\n    catch {\n      case ex : UnknownHostException =>\n        logger.warn(\"Unable to get the hostname\")\n    }\n    hostName\n  }\n\n  val hostAddress = {\n    var hostAddress : String = \"Not Available\"\n    try {\n      hostAddress = InetAddress.getLocalHost.getHostAddress\n    }\n    catch {\n      case ex : UnknownHostException =>\n        logger.warn(\"Unable to get the hostaddress\")\n    }\n\n    hostAddress\n  }\n\n\n}\n\n"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/util/State.scala",
    "content": "package com.ebay.neutrino.util\n\n\nimport scala.collection.mutable\n\n\n// Not exactly algorithmically tuned; just needed a quick hack for splitting sets\ncase class DifferentialState[T](added: Seq[T], removed: Seq[T], updated: Seq[T])\n\n\nobject DifferentialState {\n\n  /**\n   * Create a new diff-state between the before/after.\n   * NOTE - does not preserve ordering\n   */\n  def apply[T](pre: Iterable[T], post: Iterable[T]): DifferentialState[T] = {\n    // Build a differential count accumulator\n    val occ = new mutable.HashMap[T, Int] { override def default(k: T) = 0 }\n    for (y <- pre) occ(y) -= 1\n    for (y <- post) occ(y) += 1\n\n    val added   = Seq.empty.genericBuilder[T]\n    val removed = Seq.empty.genericBuilder[T]\n    val same    = Seq.empty.genericBuilder[T]\n\n    occ foreach {\n      case (k, -1) => removed += k\n      case (k,  0) => same += k\n      case (k,  1) => added += k\n    }\n\n    DifferentialState(added.result, removed.result, same.result)\n  }\n}\n\n\ntrait DifferentialStateSupport[K,V] {\n\n  // Versioning support; is updated on each update() call\n  private var _version = 0\n\n  // Internal state management\n  protected var state = Seq.empty[V]\n\n  def update(values: V*) =\n    this.synchronized {\n      // Map the key-values and calculate the diffs\n      val before  = state.foldLeft(Map[K,V]()) { (map, v) => map + (key(v) -> v) }\n      val after   = values.foldLeft(Map[K,V]()) { (map, v) => map + (key(v) -> v) }\n      val diff    = DifferentialState(before.keys, after.keys)\n\n      // Extract the associated values\n      val added   = diff.added   map (k => after(k))\n      val removed = diff.removed map (k => before(k))\n      val updated = diff.updated flatMap (k => if (before(k) != after(k)) Option((before(k), after(k))) else None)\n\n      // Update the cached-values\n      state = values\n\n      // If changes have been made, update\n      if (added.nonEmpty || removed.nonEmpty || updated.nonEmpty) {\n        removed foreach (v => removeState(v))\n        updated foreach (v => updateState(v._1, v._2))\n        added   foreach (v => addState(v))\n\n        // Update the version\n        _version += 1\n      }\n    }\n\n\n  // Required subclass methods.\n  // These are protected and should not be called from outside this class.\n  protected def key(v: V): K\n  protected def addState(added: V): Unit\n  protected def removeState(remove: V): Unit\n  protected def updateState(pre: V, post: V): Unit\n\n  // Export version\n  def version = _version\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/util/Utilities.scala",
    "content": "package com.ebay.neutrino.util\n\nimport java.net.URI\nimport java.util.concurrent.atomic.AtomicLong\n\nimport io.netty.buffer.{ByteBuf, Unpooled}\nimport io.netty.channel.{Channel, _}\nimport io.netty.handler.codec.http.{DefaultFullHttpResponse, HttpResponseStatus}\nimport io.netty.util.CharsetUtil\nimport io.netty.util.concurrent.{Future => NettyFuture, FutureListener}\n\nimport scala.concurrent.{Await, CancellationException, Future, Promise}\nimport scala.language.implicitConversions\n\n\nobject Random {\n  import scala.concurrent.duration._\n  import scala.util.{Random => ScalaRandom}\n\n\n  // Return a randomized number of the duration provided, in milliseconds\n  def nextMillis(duration: Duration) =\n    (ScalaRandom.nextLong() % duration.toNanos) nanos\n\n\n  // Return a random number uniformly distributed over the range provided\n  // Note - can only do in millisecond range\n  def nextUniform(min: Duration, max: Duration): Duration =\n    min + nextMillis(max - min)\n\n\n  // Scale our result to the correct sigma\n  def nextGaussian(mean: Duration, sigma: Duration): Duration =\n    mean + (sigma * ScalaRandom.nextGaussian())\n\n}\n\n\n\nobject Utilities {\n\n  import io.netty.util.concurrent.{Future => NettyFuture}\n\n\n  implicit class ChannelContextSupport(val self: ChannelHandlerContext) extends AnyVal {\n    import io.netty.handler.codec.http.HttpHeaderNames._\n    import io.netty.handler.codec.http.HttpVersion._\n\n\n    def sendError(status: HttpResponseStatus): ChannelFuture =\n      sendError(status, s\"Failure: $status\")\n\n\n    def sendError(status: HttpResponseStatus, message: String): ChannelFuture = {\n      val buffer = Unpooled.copiedBuffer(message+\"\\r\\n\", CharsetUtil.UTF_8)\n      val response = new DefaultFullHttpResponse(HTTP_1_1, status, buffer)\n      response.headers().set(CONTENT_TYPE, \"text/plain; charset=UTF-8\")\n\n      // Close the connection as soon as the error message is sent.\n      self.writeAndFlush(response)\n    }\n\n\n    def closeListener = new ChannelFutureListener() {\n      override def operationComplete(future: ChannelFuture): Unit =\n      // was able to flush out data, start to read the next chunk\n        if (future.isSuccess()) {\n          self.channel().read()\n        } else {\n          future.channel().close()\n        }\n    }\n\n    def writeAndFlush(msg: AnyRef, close: Boolean) =\n      if (close) self.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE)\n      else self.writeAndFlush(msg)\n\n    def writeAndClose(msg: AnyRef) =\n      self.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE)\n\n\n    // Closes the specified channel after all queued write requests are flushed.\n    def closeOnFlush = self.channel.closeOnFlush\n  }\n\n\n  implicit class ChannelSupport(val self: Channel) extends AnyVal {\n\n    // Connection-state listener\n    def connectListener = new ChannelFutureListener() {\n      override def operationComplete(future: ChannelFuture): Unit =\n      // connection complete start to read first data\n        if (future.isSuccess()) self.read()\n        // Close the connection if the connection attempt has failed.\n        else self.close()\n    }\n\n    /**\n     * Closes the specified channel after all queued write requests are flushed.\n     */\n    def closeOnFlush = if (self.isActive())\n      self.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE)\n\n    /**\n     * Extract context from the channel (if available)\n     */\n    def context(): Option[ChannelHandlerContext] = Option(self.pipeline().firstContext())\n\n    /**\n     * Readable-string helper.\n     */\n    def toStringExt(): String =\n    {\n      import AttributeSupport._\n\n      import scala.concurrent.duration._\n\n      val stats   = self.statistics\n      val session = s\"requests=${stats.requestCount.get}, elapsed=${stats.elapsed.toMillis}ms\"\n      val downstream = self.request flatMap (_.downstream) match {\n        case Some(channel) => s\"allocations=${channel.statistics.allocations.get()}, open=${(System.nanoTime-channel.statistics.startTime).nanos.toMillis}ms\"\n        case None => \"N/A\"\n      }\n\n      s\"${self.toString.dropRight(1)}, $session, $downstream]\"\n    }\n  }\n\n\n  implicit class ChannelFutureSupport(val self: ChannelFuture) extends AnyVal {\n\n    /**\n     * Add a proxy-promise listener to this ChannelFuture.\n     * @param promise\n     * @return\n     */\n    def addListener(promise: ChannelPromise) =\n      self.addListener(new PromiseListener(promise))\n\n\n    /**\n     * Add a callback listener function to this ChannelFuture.\n     * @param f\n     * @return\n     */\n    def addCloseListener(f: Channel => Unit) =\n      self.addListener(new ChannelFutureListener {\n        override def operationComplete(future: ChannelFuture): Unit = f(future.channel)\n      })\n\n\n    /**\n     * Register a Scala future on this channel.\n     * Note - makes no effort to ensure only one future is returned on multiple calls. TODO improve\n     */\n    def future(): Future[Channel] = {\n      val p = Promise[Channel]()\n\n      self.addListener(new ChannelFutureListener {\n        override def operationComplete(future: ChannelFuture) =\n          if (future.isSuccess) p success (future.channel)\n          else if (future.isCancelled) p failure (new CancellationException)\n          else p failure (future.cause)\n      })\n\n      p.future\n    }\n  }\n\n\n  // Note: this needs to be a concrete AnyRef rather than the existential _\n  // due to Java/Scala support with overriding interfaces w. generic methods.\n  implicit class FutureSupport[T](val self: NettyFuture[T]) extends AnyVal {\n\n    def future(): Future[T] = Netty.toScala(self)\n  }\n\n\n  class PromiseListener(promise: ChannelPromise) extends ChannelFutureListener {\n    override def operationComplete(future: ChannelFuture): Unit =\n      if (future.isSuccess) promise.setSuccess(future.get())\n      else promise.setFailure(future.cause())\n  }\n\n  implicit class StringSupport(val self: Array[Byte]) extends AnyVal {\n\n    def toHexString = self.map(\"%02X\" format _).mkString\n  }\n\n\n\n  implicit class ByteBufferSupport(val self: ByteBuf) extends AnyVal {\n\n    // Trims leading and trailing whitespace from byte-buffer and returns a slice\n    // representing the properly\n    def trim(start: Int=0, length: Int=self.readableBytes) = {\n      var first = start\n      var last = start+length-1\n\n      // Start at leading, and advance to first non-whitespace\n      while (first <= last && self.getByte(first).toChar.isWhitespace) { first += 1 }\n\n      // Walk back from end to first non-whitespace\n      while (last >= first && self.getByte(last).toChar.isWhitespace) { last -= 1 }\n\n      // Build the final slice\n      self.slice(first, last-first+1)\n    }\n\n\n\n    def skipControlCharacters: Unit =\n      while (true) {\n        self.readUnsignedByte.asInstanceOf[Char] match {\n          case ch if Character.isISOControl(ch) =>\n          case ch if Character.isWhitespace(ch) =>\n          case _ =>\n            // Restore positioning\n            self.readerIndex(self.readerIndex - 1)\n            return\n        }\n      }\n\n\n    def preserveIndex[T](f: => T): T = {\n      val startidx = self.readerIndex\n\n      try { f }\n      finally { self.readerIndex(startidx) }\n    }\n  }\n\n\n  /**\n   * URI support methods.\n   *\n   * Previously, we relied on Spray's case-object based implementation, but didn't didn't want to bring\n   * in the Akka transient dependencies.\n   */\n  implicit class URISupport(val self: URI) extends AnyVal {\n\n    def isSecure() =\n      Option(self.getScheme) map (_.startsWith(\"https\")) getOrElse false\n\n\n    def validHost: String = Option(self.getHost) match {\n      case Some(\"\") | None => require(false, \"Endpoint must have a valid host\"); \"\"\n      case Some(host) => host\n    }\n\n    def validPort(default: Int=80) =\n      Option(self.getPort) filter (_ != 0) getOrElse default\n  }\n\n\n  class LazyOption[T](f: => Option[T]) {\n    private var _value: Option[T] = f\n\n    def apply() = _value\n    def reset() = { val result = _value; _value = None; result }\n  }\n\n  object LazyOption {\n    import scala.concurrent.duration._\n\n    def apply[T](f: => Option[Future[T]], timeout: Duration=1.second): LazyOption[T] = {\n      // NOTE!! - TRY NOT TO USE> BLOCKING>>>>>\n      new LazyOption[T]({ f map (Await.result(_, timeout)) })\n    }\n  }\n\n\n  // Not sure if this will be instantiated or not...\n  implicit class WhenSupport[T <: Any](val value: T) extends AnyVal {\n    def when(check: Boolean): Option[T] = if (check) Option(value) else None\n    def whenNot(check: Boolean): Option[T] = if (!check) Option(value) else None\n  }\n\n\n  /**\n   * Atomic counter DSL support\n   */\n  implicit class AtomicLongSupport(val self: AtomicLong) extends AnyVal {\n\n    def +=(delta: Int)  = self.addAndGet(delta)\n    def +=(delta: Long) = self.addAndGet(delta)\n  }\n}\n\n/**\n * Netty-specific utility classes.\n */\nobject Netty {\n\n  def toScala[T](future: NettyFuture[T]): Future[T] = {\n    val p = Promise[T]()\n\n    future.addListener(new FutureListener[T] {\n      override def operationComplete(future: NettyFuture[T]): Unit = {\n        if (future.isSuccess) p success future.get\n        else if (future.isCancelled) p failure new CancellationException\n        else p failure future.cause\n      }\n    })\n\n    p.future\n  }\n}\n\nobject Preconditions {\n\n  // TODO move to Utils.Preconditions\n  def checkDefined[T](f: Option[T]): T = {\n    require(f.isDefined)\n    f.get\n  }\n\n  def checkDefined[T](f: Option[T], msg: => Any): T = {\n    require(f.isDefined, msg)\n    f.get\n  }\n}\n\n\n/**\n * Lazy-object initialization support.\n *\n * Use this when we want to defer lazy creation, but also determine if it has\n * already been created.\n */\nobject Lazy {\n\n  def lazily[A](f: => A): Lazy[A] = new Lazy(f)\n\n  implicit def evalLazy[A](l: Lazy[A]): A = l()\n}\n\nclass Lazy[A] private(f: => A) {\n\n  private var option: Option[A] = None\n\n\n  def apply(): A = option match {\n    case Some(a) => a\n    case None => val a = f; option = Some(a); a\n  }\n\n  def isEvaluated: Boolean = option.isDefined\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/www/Activity.scala",
    "content": "package com.ebay.neutrino.www\n\nimport com.ebay.neutrino.metrics.Instrumented\nimport com.ebay.neutrino.www.ui.PageFormatting\nimport com.ebay.neutrino.www.ui.SideMenu\n\nimport scala.concurrent.duration._\n\n\nobject ActivityPage extends SideMenu(\"Activity\") with PageFormatting with Instrumented {\n\n  import scalatags.Text.all._\n\n\n  def page(): Frag = {\n    page(\n      h1(cls := \"page-header\", \"Activity\"),\n      activity()\n    )\n  }\n\n\n  def activity(): Frag =\n    Seq(\n      h2(cls :=\"sub-header\", \"Recent Activity\"),\n\n      div(cls :=\"table-responsive\",\n        table(cls :=\"table table-striped\",\n          width:=\"100%\", \"cellspacing\".attr:=\"0\", \"cellpadding\".attr:=\"0\", style:=\"width:100%;border:1px solid;padding:4px;\",\n\n          thead(\n            td(b(\"URL\")),\n            td(b(\"Pool\")),\n            td(b(\"Request Size\")),\n            td(b(\"Response Size\")),\n            td(b(\"Duration\")),\n            td(b(\"Status\"))\n          ),\n          tbody(\n            tr(\n              td(\"www.ebay.com\"),\n              td(\"mryjenkins\"),\n              td(count(3500)),\n              td(count(12050)),\n              td(1500.milliseconds.toString),\n              td(\"200 OK\")\n            ),\n            td(\"jenkins.ebay.com\"),\n            td(\"mryjenkins\"),\n            td(count(3500)),\n            td(count(100050)),\n            td(300.milliseconds.toString),\n            td(\"404 NOT FOUND\")\n          )\n        )\n      )\n    )\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/www/Overview.scala",
    "content": "package com.ebay.neutrino.www\n\nimport com.ebay.neutrino.api.ApiData\nimport com.ebay.neutrino.metrics.Instrumented\nimport com.ebay.neutrino.www.ui.PageFormatting\nimport com.ebay.neutrino.www.ui.SideMenu\n\n\nobject Overview extends SideMenu(\"Overview\") with OverviewPage with PageFormatting with ApiData with Instrumented {\n\n  import ApiData._\n\n  import scalatags.Text.all._\n\n\n  //Caller: val status = generateStatus(core)\n  def generate(status: Status): Frag = {\n    page(\n      h1(cls := \"page-header\", \"Overview\"),\n\n      h2(cls :=\"sub-header\", \"Traffic\"),\n      traffic(status),\n\n      h2(cls := \"sub-header\", \"Requests\"),\n      requests(status),\n\n      h2(cls :=\"sub-header\", \"Host Information\"),\n      hostinfo()\n    )\n  }\n}\n"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/www/OverviewPage.scala",
    "content": "package com.ebay.neutrino.www\n\n\nimport com.ebay.neutrino.util.ServerContext\nimport com.ebay.neutrino.www.ui.PageFormatting\nimport com.ebay.neutrino.www.ui.Page\nimport com.ebay.neutrino.api.ApiData\n\n\n\nimport scala.concurrent.duration._\n\n\ntrait OverviewPage extends PageFormatting { self: Page =>\n\n  import ApiData._\n\n  import scalatags.Text.all._\n\n\n  def traffic(status: Status): Frag = {\n    div(cls :=\"table-responsive\",\n      table(cls :=\"table table-striped\",\n        width:=\"100%\", \"cellspacing\".attr:=\"0\", \"cellpadding\".attr:=\"0\", style:=\"width:100%;border:1px solid;padding:4px;\",\n\n        thead(\n          td(i(\"Last restart \", status.host.lastRestart), style:=\"width:25%\"),\n          td(b(\"Bytes\"),       colspan:=2, textAlign:=\"center\", style:=\"width:25%\"),\n          td(b(\"IO Events\"),   colspan:=2, textAlign:=\"center\", style:=\"width:25%\"),\n          td(b(\"Connections\"), colspan:=2, textAlign:=\"center\", style:=\"width:25%\")\n        ),\n        tbody(\n          tr(\n            td(b(\"Type\")),\n            td(b(\"In\")), td(b(\"Out\")),\n            td(b(\"In\")), td(b(\"Out\")),\n            td(b(\"Current\")), td(b(\"Total\"))\n          ),\n          tr(\n            td(\"Upstream\"),\n            td(bytes(status.upstreamTraffic.bytesIn)), td(bytes(status.upstreamTraffic.bytesOut)),\n            td(count(status.upstreamTraffic.packetsIn)), td(count(status.upstreamTraffic.packetsOut)),\n            td(status.upstreamTraffic.currentConnections), td(status.upstreamTraffic.totalConnections)\n          ),\n          tr(\n            td(\"Downstream\"),\n            td(bytes(status.downstreamTraffic.bytesIn)), td(bytes(status.downstreamTraffic.bytesOut)),\n            td(count(status.downstreamTraffic.packetsIn)), td(count(status.downstreamTraffic.packetsOut)),\n            td(status.downstreamTraffic.currentConnections), td(status.downstreamTraffic.totalConnections)\n          )\n        )\n      )\n    )\n  }\n\n  def requests(status: Status): Frag = {\n    // Split the response-statuses into timer-groups\n    val responses = status.responses groupBy (_.responseType) mapValues (_.head.stats)\n\n    // Prepare histogram table portions\n    def timer(title: String, count: Long, data: Option[Timer]): Frag = {\n      // Render-helper\n      def valid(f: Timer => String): String = data map (f(_)) getOrElse \"N/A\"\n\n      tr(\n        td(title),\n        td(if (count >= 0) count.toString else \"\"),\n        td(valid(_.count.toString)),\n        td(valid(_.min.nanos.toMillis + \"ms\")),\n        td(valid(_.mean.nanos.toMillis + \"ms\")),\n        td(valid(_.`95th`.nanos.toMillis + \"ms\")),\n        td(valid(_.max.nanos.toMillis + \"ms\")),\n        td(valid(t => \"%.2f/s\".format(t.`1min`))),\n        td(valid(t => \"%.2f/s\".format(t.`5min`))),\n        td(valid(t => \"%.2f/s\".format(t.`15min`)))\n      )\n    }\n\n\n    div(cls := \"table-responsive\",\n      table(cls := \"table table-striped\",\n        width := \"100%\", \"cellspacing\".attr := \"0\", \"cellpadding\".attr := \"0\", style := \"width:100%;border:1px solid;padding:4px;\",\n        thead(\n          td(\"Type\"),\n          td(\"Active\", style := \"width:8%\"),\n          td(\"Total\", style := \"width:8%\"),\n          td(\"Min Elapsed\", style := \"width:9%\"),\n          td(\"Avs Elapsed\", style := \"width:9%\"),\n          td(\"95% Elapsed\", style := \"width:9%\"),\n          td(\"Max Elapsed\", style := \"width:9%\"),\n          td(\"Last Min Rate\", style := \"width:9%\"),\n          td(\"Last 5m Rate\", style := \"width:9%\"),\n          td(\"Last 15m Rate\", style := \"width:9%\")\n        ),\n        tbody(\n          timer(\n            \"Request Sessions\", status.sessions.active, Option(status.sessions.stats) //Metrics.SessionActive.count, Metrics.SessionDuration\n          ),\n          timer(\n            \"HTTP Requests\", status.requests.active, Option(status.requests.stats) //Metrics.RequestsOpen.count, Metrics.RequestsCompleted\n          ),\n          timer(\n            \"HTTP Requests - 2xx Response\", -1, responses get \"2xx\"\n          ),\n          timer(\n            \"HTTP Requests - 4xx Response\", -1, responses get \"4xx\"\n          ),\n          timer(\n            \"HTTP Requests - 5xx Response\", -1, responses get \"5xx\"\n          ),\n          timer(\n            \"HTTP Requests - Incomplete\", -1, responses get \"incomplete\"\n          ),\n          timer(\n            \"HTTP Requests - Other\", -1, responses get \"other\")\n        )\n      )\n    )\n  }\n\n  def hostinfo(info: HostInfo): Frag =\n    div(cls :=\"table-responsive\",\n      table(cls :=\"table table-striped\",\n        width:=\"100%\", \"cellspacing\".attr:=\"0\", \"cellpadding\".attr:=\"0\", style:=\"width:100%;border:1px solid;padding:4px;\",\n\n        thead(\n          td(\"Hostname\"),\n          td(\"Address\"),\n          td(\"Last Restart Time\")\n        ),\n        tbody(\n          tr(\n            td(info.hostname),\n            td(info.address),\n            td(info.lastRestart)\n          )\n        )\n      )\n    )\n\n  def hostinfo(): Frag =\n    div(cls :=\"table-responsive\",\n      table(cls :=\"table table-striped\",\n        width:=\"100%\", \"cellspacing\".attr:=\"0\", \"cellpadding\".attr:=\"0\", style:=\"width:100%;border:1px solid;padding:4px;\",\n\n        thead(\n          td(\"Property\"),\n          td(\"Detail\", width:=\"85%\")\n        ),\n        tbody(\n          tr(\n            td(\"Last Restart Time\"),\n            td(starttime.toString)\n          ),\n          tr(\n            td(\"Hostname\"),\n            td(str(ServerContext.fullHostName))\n          ),\n          tr(\n            td(\"Canonical Hostname\"),\n            td(str(ServerContext.canonicalHostName))\n          ),\n          tr(\n            td(\"IP/Address\"),\n            td(str(ServerContext.hostAddress))\n          )\n\n          /*tr(\n            td(\"Local Addresses\"),\n            td(str(NetworkUtils.cachedNames mkString \", \"))\n          ),\n          */\n        )\n      )\n    )\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/www/PoolsPages.scala",
    "content": "package com.ebay.neutrino.www\n\nimport com.ebay.neutrino.config.{CanonicalAddress, VirtualPool, WildcardAddress}\nimport com.ebay.neutrino.metrics.Instrumented\nimport com.ebay.neutrino.www.ui.{Page, PageFormatting}\nimport com.ebay.neutrino.{NeutrinoNode, NeutrinoPoolId}\n\n\n/**\n * Result-set support with DataTables.\n * @see http://www.datatables.net/examples/data_sources/dom.html\n */\ntrait PoolsPage extends  PageFormatting with Instrumented { self: Page =>\n\n  import scalatags.Text.all._\n\n  /** Extract canonical-addresses from pool */\n  @inline def canonicals(pool: VirtualPool): Seq[CanonicalAddress] =\n    pool.addresses collect {\n      case canonical: CanonicalAddress => canonical\n    }\n\n\n  def summary(pools: Seq[VirtualPool]): Frag = {\n    pageWithLoad(\n      div(\n         h5(cls := \"pull-right\", a(href := s\"/refresh\", u(\"Reload Config\"))),\n         h1(cls := \"page-header\", \"Pools\")\n\n       ),\n\n\n      //h2(cls :=\"sub-header\", \"Applications Loaded\"),\n      div(cls :=\"table-responsive\",\n        table(id := \"results\", cls :=\"table table-striped\",\n          thead(\n            tr(\n              th(\"Pool Name\"),\n              th(\"Protocol\"),\n              th(\"Servers\"),\n              th(\"CNAME\"),\n              th(\"Balancer\"),\n              th(\"Timeouts\")\n            )\n          ),\n          tbody(\n            pools map { pool =>\n              val id = NeutrinoPoolId(pool.id, pool.protocol)\n              val cname = (canonicals(pool) map (_.host) headOption) getOrElse \"N/A\"\n\n              tr(\n                td(a(href := s\"/pools/$id\", pool.id.split(\":\").head)),\n                td(pool.protocol.toString.toUpperCase),\n                td(a(href := s\"/pools/$id\", pool.servers.size+\" Servers\")),\n                td(a(href := s\"/pools/$id\", cname)),\n                td(pool.balancer.clazz.getSimpleName),\n                td(\"Default\")\n              )\n            }\n          )\n        )\n      )\n    )(\"\"\"\n        $('#results').DataTable();\n      \"\"\")\n  }\n\n\n  // Build the detail page (as applicable)\n  // TODO deal with not-found\n  def detail(pool: Option[VirtualPool]): Frag =\n    page(\n      h1(cls := \"page-header\", s\"Pool Detail\"),\n\n      pool match {\n        case None =>\n          Seq(p(\"Not found\"))\n\n        case Some(pool) =>\n          Seq(\n            h2(cls :=\"sub-header\", \"Details\"),\n            div(cls :=\"table-responsive\",\n              table(cls :=\"table table-striped\",\n                thead(\n                  tr(\n                    th(\"Pool ID\"),\n                    th(\"Type\"),\n                    th(\"Hostname (CNAME/A)\"),\n                    th(\"Source Port\"),\n                    th(\"IP Resolved\"),\n                    th(\"Routing Policy\")\n                  )\n                ),\n                tbody(\n                  pool.addresses collect {\n                    case address: CanonicalAddress =>\n                      tr(\n                        td(pool.id),\n                        td(\"CNAME\"),\n                        td(address.host),\n                        td(address.port),\n                        td(address.socketAddress.toString),\n                        td()\n                      )\n\n                    case address: WildcardAddress =>\n                      tr(\n                        td(pool.id),\n                        td(\"L7 Rule\"),\n                        td(address.host),\n                        td(address.port),\n                        td(),\n                        td(address.socketAddress.toString),\n                        td(address.path)\n                      )\n                  }\n                )\n              )\n            ),\n            div(cls :=\"table-responsive\",\n              table(cls :=\"table table-striped\",\n                thead(\n                  tr(\n                    th(\"Read Idle Timeout\"),\n                    th(\"Write Idle Timeout\"),\n                    th(\"Write Completion Timeout\"),\n                    th(\"Request Timeout\"),\n                    th(\"Session Timeout\")\n                  )\n                ),\n                tbody(\n\n                    tr(\n                      td(pool.timeouts.readIdle.toSeconds+\"s\"),\n                      td(pool.timeouts.writeIdle.toSeconds+\"s\"),\n                      td(pool.timeouts.writeCompletion.toSeconds+\"s\"),\n                      td(pool.timeouts.requestCompletion.toSeconds+\"s\"),\n                      td(pool.timeouts.sessionCompletion.toSeconds+\"s\")\n                    )\n\n                )\n              )\n            ),\n\n            h2(cls :=\"sub-header\", \"Servers\"),\n            div(cls :=\"table-responsive\",\n              table(cls :=\"table table-striped\",\n                thead(\n                  tr(\n                    th(\"ServerId\"),\n                    th(\"Host\"),\n                    th(\"Port\"),\n                    th(\"Health State\")\n                  )\n                ),\n                tbody(\n                  pool.servers map { server =>\n                    tr(\n                      td(server.id),\n                      td(server.host),\n                      td(server.port),\n                      td(server.healthState.toString)\n                    )\n                  }\n                )\n              )\n            )\n\n            //h2(cls :=\"sub-header\", \"Load Balancer\")\n          )\n      }\n    )\n}\n\n\n/**\n * Result-set support with DataTables.\n * @see http://www.datatables.net/examples/data_sources/dom.html\n */\ntrait ServersPage extends PageFormatting with Instrumented { self: Page =>\n\n  import scalatags.Text.all._\n\n\n  def summary(pools: Seq[VirtualPool], nodes: Seq[NeutrinoNode]=Seq()): Frag = {\n    // If available, grab 'active' server info\n    val nodemap = nodes.groupBy(_.settings) mapValues (_.head)\n\n    pageWithLoad(\n      h1(cls := \"page-header\", \"Servers\"),\n\n      //h2(cls :=\"sub-header\", \"Applications Loaded\"),\n      div(cls :=\"table-responsive\",\n        table(id := \"results\", cls :=\"table table-striped\",\n          thead(\n            tr(\n              th(\"Host\"),\n              th(\"Port\"),\n              th(\"Health State\"),\n              th(\"Pool Name\"),\n              th(\"Active Connections\"),\n              th(\"Pooled Connections\"),\n              th(\"Last Activity\")\n            )\n          ),\n          tbody(\n            pools map { pool =>\n              pool.servers map { server =>\n                val node = nodemap get (server)\n                val id = NeutrinoPoolId(pool.id, pool.protocol)\n                val allocated = node map (_.allocated.size.toString) getOrElse \"-\"\n                val available = node map (_.available.size.toString) getOrElse \"-\"\n\n                tr(\n                  td(server.host),\n                  td(server.port),\n                  td(server.healthState.toString),\n                  td(a(href := s\"/pools/$id\", pool.id.split(\":\").head)),\n                  td(allocated),\n                  td(available),\n                  td(\"-\")\n                )\n              }\n            }\n          )\n        )\n      )\n    )(\"\"\"\n        $('#results').DataTable();\n      \"\"\")\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/www/Web.scala",
    "content": "package com.ebay.neutrino.www\n\nimport java.util.concurrent.TimeUnit\n\nimport akka.actor.{ActorRef, Props, ActorSystem, ScalaActorRef}\nimport akka.pattern.ask\nimport akka.util.Timeout\nimport com.ebay.neutrino.{SLB, NeutrinoPoolId}\nimport com.ebay.neutrino.api.ApiData\nimport com.ebay.neutrino.cluster.{SLBTopology, SystemConfiguration}\nimport com.ebay.neutrino.www.ui.SideMenu\nimport com.ebay.neutrino.www.ui.PageFormatting\nimport com.ebay.neutrino.cluster.SLBLoader\n\n\nimport com.typesafe.config.ConfigRenderOptions\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport spray.http.StatusCodes\n\nimport scala.concurrent.Await\nimport scala.concurrent.duration.FiniteDuration\nimport scala.concurrent.ExecutionContext.Implicits.global\nimport scala.util.{Failure, Success}\n\n\ntrait WebService extends spray.routing.HttpService with ApiData with PageFormatting with StrictLogging\n{\n  def system: ActorSystem\n\n  def topology = SystemConfiguration(system).topology\n\n  val poolPage   = new SideMenu(\"Pools\") with PoolsPage\n  val serverPage = new SideMenu(\"Servers\") with ServersPage\n\n  def webRoutes =\n    path (\"activity\") {\n      complete {\n        import PageFormatting.ScalaTagsPrettyMarshaller\n        ActivityPage.page()\n      }\n    } ~\n    path(\"pools\") {\n      complete {\n        import PageFormatting.ScalaTagsPrettyMarshaller\n        poolPage.summary(topology.toSeq)\n      }\n    } ~\n    path(\"pools\" / Segment) { id =>\n      complete {\n        import PageFormatting.ScalaTagsPrettyMarshaller\n        val pool = topology.getPool(NeutrinoPoolId(id))\n        poolPage.detail(pool)\n      }\n    } ~\n    path(\"servers\") {\n      complete {\n        import PageFormatting.ScalaTagsPrettyMarshaller\n        val pools    = topology.toSeq\n        val services = topology.asInstanceOf[SLBTopology].core.services\n        val nodes    = services flatMap (_.pools()) flatMap (_.nodes())\n        serverPage.summary(pools, nodes.toSeq)\n      }\n    } ~\n    path(\"refresh\") {\n      complete {\n        import PageFormatting.ScalaTagsPrettyMarshaller\n        implicit val timeout = Timeout(FiniteDuration(3, TimeUnit.SECONDS))\n        // Wait for the result, since refresh api has to be synchronous\n        val reloader = Await.result(system.actorSelection(\"user/loader\").resolveOne(), timeout.duration)\n        val future = reloader ? \"reload\"\n        val result = Await.result(future, timeout.duration)\n        if (result == \"complete\") {\n            logger.warn(\"Config reloaded, Successfully completed\")\n        } else {\n          logger.warn(\"Unable to load the configuration\")\n        }\n        poolPage.summary(topology.toSeq)\n      }\n    } ~\n    path(\"config\") {\n      complete {\n        val sysconfig = SystemConfiguration(system)\n        sysconfig.config.root.render(ConfigRenderOptions.defaults)\n      }\n    } ~\n    pathEndOrSingleSlash {\n      complete {\n        import PageFormatting.ScalaTagsPrettyMarshaller\n        Overview.generate(generateStatus())\n      }\n    } ~\n    get {\n      redirect(\"/\", StatusCodes.PermanentRedirect)\n    }\n\n}\n\n\n\n\n"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/www/ui/Page.scala",
    "content": "package com.ebay.neutrino.www.ui\n\n\n\nimport scalatags.Text.TypedTag\n\n\ntrait Page extends PageFormatting {\n\n  import scalatags.Text.all._\n  //import scala.language.implicitConversions\n\n  val CSS = \"/css/dashboard.css\"\n\n  // Additional html components\n  val nav = \"nav\".tag[String]\n  val defer = \"defer\".attr\n\n  // Defaults; should be overridden by children/implementors\n  def pagename: String = \"Neutrino\"\n  def prettyPrint: Boolean = true\n\n\n\n  //css/jumbotron.css\n  def header(customStylesheet: Option[String]) = {\n    head(\n      title := \"Neutrino SLB Dashboard\",\n\n      meta(charset := \"utf-8\"),\n      meta(httpEquiv := \"X-UA-Compatible\", content := \"IE=edge\"),\n      meta(name :=\"viewport\", content:=\"width=device-width, initial-scale=1\"),\n      //meta(description := \"Neutrino SLB\"),\n      //meta(author := \"cbrawn\"),\n\n      link(href := \"/favicon.ico\", rel :=\"icon\"),\n\n      // <!-- Bootstrap core CSS -->\n      link(href := \"/css/bootstrap.min.css\", rel :=\"stylesheet\"),\n      // MIT license http://datatables.net/license/\n      link(href := \"http://cdn.datatables.net/1.10.5/css/jquery.dataTables.css\", rel :=\"stylesheet\"),\n\n    // <!-- Custom styles for this template -->\n      if (customStylesheet.isDefined)\n        link(href := customStylesheet.get, rel := \"stylesheet\")\n      else\n        div()\n\n\n    )\n  }\n\n\n  def onLoad(frags: Frag*): Frag = {\n    if (frags.isEmpty)\n      ()\n    else {\n      script(\n        \"\"\"$(document).ready(function(){\"\"\",\n        frags,\n        \"\"\"});\"\"\")\n    }\n  }\n\n\n  // Layout the full page\n  def page(bodyFragment: Frag*): Frag =\n    page(bodyFragment, Seq())\n\n  def pageWithLoad(bodyFragment: Frag*)(onLoads: Frag*): Frag =\n    page(bodyFragment, onLoads)\n\n\n  def page(bodyFragment: Seq[Frag], onLoads: Seq[Frag], pretty: Boolean=true): Frag = {\n    html(lang := \"en\",\n      header(Option(CSS)),\n      body(\n        navigation(),\n\n        div(cls :=\"container-fluid\",\n          div(cls := \"row\",\n            menu(),\n\n            div(cls := \"col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main\",\n              bodyFragment\n            )\n          )\n        ),\n\n\n        // <!-- Bootstrap core JavaScript ================================================== -->\n        // <!-- Placed at the end of the document so the pages load faster -->\n        // ?? should we put jquery inline?\n        script(src := \"https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js\", \" \"),\n        // MIT license http://datatables.net/license/\n        script(src := \"http://cdn.datatables.net/1.10.5/js/jquery.dataTables.min.js\", \" \"),\n        script(src := \"/js/bootstrap.min.js\", \" \"),\n\n        script(src := \"/assets/js/docs.min.js\", \" \"),\n\n          // <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->\n        script(src := \"/assets/js/ie10-viewport-bug-workaround.js\", \" \"),\n\n        // Kick off any page-wide on-load events\n        onLoad(onLoads)\n      )\n    )\n  }\n\n\n  def navigation() =\n    nav(cls := \"navbar navbar-inverse navbar-fixed-top\",\n      div(cls := \"container-fluid\",\n        div(cls := \"navbar-header\",\n          button(cls := \"navbar-toggle collapsed\",\n            `type` := \"button\",\n            data.toggle := \"collapse\",\n            data.target := \"#navbar\",\n            aria.expanded := \"false\",\n            aria.controls := \"navbar\",\n\n            span(cls := \"sr-only\", \"Toggle navigation\"),\n            span(cls := \"icon-bar\"),\n            span(cls := \"icon-bar\"),\n            span(cls := \"icon-bar\")\n          ),\n          a(cls :=\"navbar-brand\", href := \"#\", \"Neutrino Load Balancer\")\n        )\n      )\n    )\n\n\n  def pagelink(link: String, name: String) =\n    if (name == pagename)\n      li(cls :=\"active\", a(href :=link, name, span(cls :=\"sr-only\", \" (current)\")))\n    else\n      li(a(href :=link, name))\n\n\n  def menu(): TypedTag[String]\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/www/ui/PageFormatting.scala",
    "content": "package com.ebay.neutrino.www.ui\n\nimport java.util.Date\n\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport nl.grons.metrics.scala.{Counter, Meter}\nimport spray.http.ContentType\nimport spray.http.MediaTypes._\nimport spray.httpx.marshalling.Marshaller\n\nimport scala.concurrent.duration._\nimport scala.util.{Failure, Success, Try}\nimport scala.xml.Elem\n\n\ntrait PageFormatting extends StrictLogging {\n\n  import com.twitter.conversions.storage._\n  import scala.language.implicitConversions\n\n\n  val pretty    = true\n  val prettyXml = false   // Use XML parsing for prettyprinting\n  val prettier  = new scala.xml.PrettyPrinter(160, 4)\n  val starttime = new Date()\n\n\n  // Run the HTML format content through the pretty-printer\n  def prettify(content: String): String = {\n    if (pretty && prettyXml)\n      Try(prettier.format(scala.xml.XML.loadString(content))) match {\n        case Success(content) => content\n        case Failure(ex) => logger.warn(\"Unable to pretty-print html\", ex); content\n      }\n    else if (pretty)\n      PrettyPrinter.format(content)\n    else\n      content\n  }\n\n  def prettyxml(content: String) = {\n    Try(prettier.format(scala.xml.XML.loadString(content))) match {\n      case Success(content) => content\n      case Failure(ex) => logger.warn(\"Unable to pretty-print html\", ex); content\n    }\n  }\n\n  // Convert current time to uptime\n  def uptime() = pretty((System.currentTimeMillis()-starttime.getTime).millis)\n\n  // Convenience method: pretty print storage size\n  def bytes(data: Long): String = data.bytes.toHuman\n\n  // Convenience method: pretty print count size\n  def count(data: Long): String =\n    data match {\n      case count if count < (2<<10) => s\"$count\"\n      case count if count < (2<<18) => \"%.1f K\".format(count/1000f)\n      case count if count < (2<<28) => \"%.1f M\".format(count/1000000f)\n      case count                    => \"%.1f G\".format(count/1000000000f)\n    }\n\n  // Convenience method; pretty print time\n  def pretty(duration: FiniteDuration): String = {\n    if      (duration.toDays  > 0) duration.toDays+\" days\"\n    else if (duration.toHours > 0) duration.toHours+\" hours\"\n    else if (duration.toMinutes > 0) duration.toMinutes+\" minutes\"\n    else     duration.toSeconds +\" seconds\"\n  }\n\n  // Convenience method; ensure non-null string\n  @inline def str(value: String) = if (value == null) \"\" else value\n}\n\n\nobject PageFormatting extends PageFormatting {\n  import scalatags.Text.all._\n\n  val SupportedOutput: Seq[ContentType] =\n    Seq(`text/xml`, `application/xml`, `text/html`, `application/xhtml+xml`)\n\n\n  implicit val ScalaTagsMarshaller =\n    Marshaller.delegate[Frag, String](SupportedOutput:_*) { frag =>\n      \"<!DOCTYPE html>\\n\" + frag.toString\n    }\n\n  implicit val ScalaTagsPrettyMarshaller =\n    Marshaller.delegate[Frag, String](SupportedOutput:_*) { frag =>\n      \"<!DOCTYPE html>\\n\" + prettyxml(frag.toString)\n    }\n\n  implicit val XmlMarshaller =\n    Marshaller.delegate[Elem, String](SupportedOutput:_*) { elem =>\n      prettify(elem.toString)\n    }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/www/ui/ResourceServices.scala",
    "content": "package com.ebay.neutrino.www.ui\n\nimport akka.actor.ActorSystem\nimport spray.routing.Route\n\n\ntrait ResourceServices extends spray.routing.HttpService\n{\n  def system: ActorSystem\n  def getPathFromResourceDirectory(path: String): Route =\n    pathPrefix (path) {\n      get { getFromResourceDirectory(path) }\n    }\n\n\n  def resourceRoutes =\n    pathPrefix (\"assets\") {\n      get {\n        getFromResourceDirectory(\"assets\")\n      }\n    } ~\n    pathPrefix (\"css\") {\n      get {\n        getFromResourceDirectory(\"css\")\n      }\n    } ~\n    pathPrefix (\"js\") {\n      get {\n        getFromResourceDirectory(\"js\")\n      }\n    }\n\n}\n\n\n\n\n"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/www/ui/SideMenu.scala",
    "content": "package com.ebay.neutrino.www.ui\n\n\nimport com.ebay.neutrino.www.ui\n\n\nclass SideMenu(override val pagename: String=\"Neutrino\", override val prettyPrint: Boolean=true)\n  extends ui.Page\n  with PageFormatting\n{\n  import scalatags.Text.all._\n\n\n  def menu() = {\n    def pagelink(link: String, name: String) =\n      if (name == pagename)\n        li(cls :=\"active\", a(href :=link, name, span(cls :=\"sr-only\", \" (current)\")))\n      else\n        li(a(href :=link, name))\n\n\n    div(cls := \"col-sm-3 col-md-2 sidebar\",\n      ul(cls := \"nav nav-sidebar\",\n        pagelink(\"/\", \"Overview\")\n      ),\n      ul(cls := \"nav nav-sidebar\",\n        pagelink(\"/pools\", \"Pools\"),\n        pagelink(\"/servers\", \"Servers\")\n      ),\n      ul(cls := \"nav nav-sidebar\",\n        pagelink(\"/config\", \"Raw Configuration\")\n      )\n    )\n  }\n}"
  },
  {
    "path": "src/main/scala/com/ebay/neutrino/www/ui/Utilities.scala",
    "content": "package com.ebay.neutrino.www.ui\n\nimport javax.xml.transform.dom.DOMSource\nimport javax.xml.transform.sax.SAXResult\n\nimport scala.xml.parsing.NoBindingFactoryAdapter\n\n\n/**\n * There is a long, sordid tale around pretty printing here. It all starts with\n * some useful JavaScript code that uses a character or two which are verbotten\n * in XML (ie, the && characters).\n *\n * When outputting this, we can write raw() and these will go to the output, but\n * by default when the ScalaTags 'frags' are output the default toString() method\n * simply dumps everything rendered (ie: not the raw() blocks) out as one line.\n *\n * There is PrettyPrinter support, using the default XML library.\n * > new PrettyPrinter(80,4).format(content)\n *\n * This, however, barfs on the parse of the 'invalid' characters. This means we can\n * either have:\n * - unformatted by 'normal' code (as-is)\n * - formatted code without tolerance to JavaScript un-escape\n *\n * Just escape the JavaScript, you might say. Unfortunately, this makes the browser\n * barf, as the JS is not recognized while URLEncoded.\n *\n *\n * The simplest is to do one of two things:\n * 1) Fix the parser (can't use default scala XML).\n *    Lift has one that works but has awful documentation.\n * 2) Escape the javascript with a CDATA block.\n *    Oh yes, this is also not parsed by XML properly.\n * 3) Pretty print at source.\n *    Allows us to print the first block while ignoring the JS completely.\n *    Just isn't really expected...\n *\n * So eff-it; let's just dirty up this 'pretty printer' for generating rolled-over\n * output. It's not glam but it'll keep our output trim.\n *\n * @see http://stackoverflow.com/questions/139076/how-to-pretty-print-xml-from-java\n */\nobject PrettyPrinter {\n\n  val Indent = \"    \"\n\n\n  def format(xml: String): String = {\n    if (xml == null || xml.trim().length() == 0) return \"\";\n\n    var indent = 0\n    var inscript = false\n    val pretty = new StringBuilder()\n    val rows = xml.trim().replaceAll(\">\", \">\\n\").replaceAll(\"<\", \"\\n<\").replaceAll(\"\\n\\n\", \"\\n\").split(\"\\n\")\n\n    // Output while adjusting row-indent\n    rows.iterator foreach {\n      case \"\" =>\n        // skip\n\n      case row if inscript =>\n        pretty.append(row).append('\\n')\n\n      case row if row.startsWith(\"</script\") =>\n        inscript = false\n        indent -= 1\n        pretty.append(Indent*indent).append(row.trim).append('\\n')\n\n      case row if row.startsWith(\"</\") =>\n        indent -= 1\n        pretty.append(Indent*indent).append(row.trim).append('\\n')\n\n    //case \"<script>\n      case row if (row.startsWith(\"<script\") && !row.endsWith(\"/>\") && !row.endsWith(\"]]>\")) =>\n        pretty.append(Indent*indent).append(row.trim).append('\\n')\n        indent += 1\n        inscript = true\n\n      case row if (row.startsWith(\"<\") && !row.endsWith(\"/>\") && !row.endsWith(\"]]>\")) =>\n        pretty.append(Indent*indent).append(row.trim).append('\\n')\n        indent += 1\n\n      case row =>\n        pretty.append(row).append('\\n')\n    }\n\n    pretty.toString\n  }\n\n  /*def toNode(input: TypedTag[String]): xml.Node = {\n    import scalatags.JsDom._\n    val document = input.render\n  }*/\n\n  def toNode(input: org.w3c.dom.Node): xml.Node = {\n    val source = new DOMSource(input)\n    val adapter = new NoBindingFactoryAdapter\n    val saxResult = new SAXResult(adapter)\n    val transformerFactory = javax.xml.transform.TransformerFactory.newInstance()\n    val transformer = transformerFactory.newTransformer()\n    transformer.transform(source, saxResult)\n    adapter.rootElem\n  }\n}"
  },
  {
    "path": "src/main/scala/com/twitter/conversions/storage.scala",
    "content": "/**\n * Copy and paste of Twitter Util's size conversion methods\n *\n * @see https://raw.githubusercontent.com/twitter/util/master/util-core/src/main/scala/com/twitter/conversions/storage.scala\n */\n/*\n * Copyright 2010 Twitter Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n * not use this file except in compliance with the License. You may obtain\n * a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.twitter\npackage conversions\n\nimport com.twitter.util.StorageUnit\n\n\nobject storage {\n  import scala.language.implicitConversions\n\n  class RichWholeNumber(wrapped: Long) {\n    def byte      = bytes\n    def bytes     = new StorageUnit(wrapped)\n    def kilobyte  = kilobytes\n    def kilobytes = new StorageUnit(wrapped * 1024)\n    def megabyte  = megabytes\n    def megabytes = new StorageUnit(wrapped * 1024 * 1024)\n    def gigabyte  = gigabytes\n    def gigabytes = new StorageUnit(wrapped * 1024 * 1024 * 1024)\n    def terabyte  = terabytes\n    def terabytes = new StorageUnit(wrapped * 1024 * 1024 * 1024 * 1024)\n    def petabyte  = petabytes\n    def petabytes = new StorageUnit(wrapped * 1024 * 1024 * 1024 * 1024 * 1024)\n\n    def thousand  = wrapped * 1000\n    def million   = wrapped * 1000 * 1000\n    def billion   = wrapped * 1000 * 1000 * 1000\n  }\n\n  implicit def intToStorageUnitableWholeNumber(i: Int) = new RichWholeNumber(i)\n  implicit def longToStorageUnitableWholeNumber(l: Long) = new RichWholeNumber(l)\n}"
  },
  {
    "path": "src/main/scala/com/twitter/util/StorageUnit.scala",
    "content": "/**\n * Copy and paste of Twitter Util's size conversion methods\n *\n * @see https://raw.githubusercontent.com/twitter/util/master/util-core/src/main/scala/com/twitter/util/StorageUnit.scala\n */\n/*\n * Copyright 2010 Twitter Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n * not use this file except in compliance with the License. You may obtain\n * a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.util\n\n\nobject StorageUnit {\n  val infinite = new StorageUnit(Long.MaxValue)\n  val zero = new StorageUnit(0)\n\n  private def factor(s: String) = {\n    var lower = s.toLowerCase\n    if (lower endsWith \"s\")\n      lower = lower dropRight 1\n\n    lower match {\n      case \"byte\" => 1L\n      case \"kilobyte\" => 1L<<10\n      case \"megabyte\" => 1L<<20\n      case \"gigabyte\" => 1L<<30\n      case \"terabyte\" => 1L<<40\n      case \"petabyte\" => 1L<<50\n      case \"exabyte\" => 1L<<60\n      case badUnit => throw new NumberFormatException(\n        \"Unrecognized unit %s\".format(badUnit))\n    }\n  }\n\n  /**\n   * Note, this can cause overflows of the Long used to represent the\n   * number of bytes.\n   */\n  def parse(s: String): StorageUnit = s.split(\"\\\\.\") match {\n    case Array(v, u) =>\n      val vv = v.toLong\n      val uu = factor(u)\n      new StorageUnit(vv * uu)\n\n    case _ =>\n      throw new NumberFormatException(\"invalid storage unit string: %s\".format(s))\n  }\n}\n\n/**\n * Representation of storage units.\n *\n * If you import the [[com.twitter.conversions.storage]] implicits you can\n * write human-readable values such as `1.gigabyte` or `50.megabytes`.\n *\n * Note: operations can cause overflows of the Long used to represent the\n * number of bytes.\n */\nclass StorageUnit(val bytes: Long) extends Ordered[StorageUnit] {\n  def inBytes     = bytes\n  def inKilobytes = bytes / (1024L)\n  def inMegabytes = bytes / (1024L * 1024)\n  def inGigabytes = bytes / (1024L * 1024 * 1024)\n  def inTerabytes = bytes / (1024L * 1024 * 1024 * 1024)\n  def inPetabytes = bytes / (1024L * 1024 * 1024 * 1024 * 1024)\n  def inExabytes  = bytes / (1024L * 1024 * 1024 * 1024 * 1024 * 1024)\n\n  def +(that: StorageUnit): StorageUnit = new StorageUnit(this.bytes + that.bytes)\n  def -(that: StorageUnit): StorageUnit = new StorageUnit(this.bytes - that.bytes)\n  def *(scalar: Double): StorageUnit = new StorageUnit((this.bytes.toDouble*scalar).toLong)\n  def *(scalar: Long): StorageUnit = new StorageUnit(this.bytes * scalar)\n  def /(scalar: Long): StorageUnit = new StorageUnit(this.bytes / scalar)\n\n  override def equals(other: Any) = {\n    other match {\n      case other: StorageUnit =>\n        inBytes == other.inBytes\n      case _ =>\n        false\n    }\n  }\n\n  override def hashCode: Int = bytes.hashCode\n\n  override def compare(other: StorageUnit) =\n    if (bytes < other.bytes) -1 else if (bytes > other.bytes) 1 else 0\n\n  def min(other: StorageUnit): StorageUnit =\n    if (this < other) this else other\n\n  def max(other: StorageUnit): StorageUnit =\n    if (this > other) this else other\n\n  override def toString() = inBytes + \".bytes\"\n\n  def toHuman(): String = {\n    val prefix = \"KMGTPE\"\n    var prefixIndex = -1\n    var display = bytes.toDouble.abs\n    while (display > 1126.0) {\n      prefixIndex += 1\n      display /= 1024.0\n    }\n    if (prefixIndex < 0) {\n      \"%d B\".format(bytes)\n    } else {\n      \"%.1f %ciB\".format(display*bytes.signum, prefix.charAt(prefixIndex))\n    }\n  }\n}"
  },
  {
    "path": "src/pack/run.sh",
    "content": "#!/bin/sh\n#\nDIR=/neutrino\n\necho \"Starting SLB application\"\n${DIR}/bin/sl-b > /${DIR}/logs/neutrino.log 2>&1\n"
  },
  {
    "path": "src/test/resources/README.md",
    "content": "# Test Configuration\n\n##Configuration Files\n\nA handful of files used either for:\n- driving unit-test cases (partially formed usually)\n- full configurations for test applications/test-setups\n\n## Test Data Files\n\nNot checked into git, but easily generated.\n\nSee [these instructions](http://linuxcommando.blogspot.com/2008/06/create-file-of-given-size-with-random.html) for an example.\n\nRecommend using the following denominations:\n- 1 kb\n- 1 MB\n- 100 MB\n\nI use Nginx installed locally hosting these test files statically and drive via JMeter request."
  },
  {
    "path": "src/test/resources/echo.conf",
    "content": "# Test server configuration\necho-server {\n\n  # Server listener settings\n  host = \"0.0.0.0\"\n  port = 8081\n\n  # Request handling configuration\n  random = true\n  duration = 200ms\n}"
  },
  {
    "path": "src/test/resources/http-service.conf",
    "content": "# Used for unit-testing [HttpServiceTest]\n#\nneutrino {\n\n  # Default listener\n  listener = {\n    # Listen on localhost\n    host = \"0.0.0.0\"\n    pool-resolver = \"default\"\n    #pipeline-class = null\n    pipeline-class = \"com.ebay.neutrino.ProxyPipeline\"\n  }\n\n  listeners = [\n    { port = 8080, port-alias = 80, protocol = \"http\" }\n  ]\n\n  pools = [\n    { id = \"default\", protocol = \"http\", balancer = \"round-robin\", servers = ${neutrino.servers} }\n  ]\n\n  servers = [\n    { host = \"localhost\", port = \"8081\" }\n  ]\n}\n\n\n# Currently unused\ntest.neutrino {\n\n  listener.pipeline-class = \"com.ebay.neutrino.ProxyPipeline\"\n\n}\n\n\ninvalid-host.neutrino {\n\n  pools = [\n    { id = \"default\", protocol = \"http\", balancer = \"round-robin\",\n      servers = [\n        { id=\"1\", host = \"invalidhost\", port = \"8081\" }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/test/resources/multiport.conf",
    "content": "neutrino {\n  # Use this to enable/disable the direct SLB api\n  enable-api = true,\n\n  supervisorThreadCount = 2,\n\n  workerThreadCount = 8\n\n\n}\nneutrino.datasource {\n  #Refresh time of datasource\n  refresh-period = 30s\n\n  datasource-reader = \"com.ebay.neutrino.datasource.FileReader\"\n}\n\nresolvers.neutrino {\n\n\n  listeners = [\n    {\n      # pool resolvers configured\n      pool-resolver = [\"cname\", \"layerseven\"],\n      # listening port of SLB\n      port = 8080,\n      protocol = \"http\"\n    }\n\n    {\n      # pool resolvers configured\n      pool-resolver = [\"cname\", \"layerseven\"],\n      # listening port of SLB\n      port = 8082,\n      protocol = \"http\"\n    }\n\n  ]\n\n  initializers = [\n    \"com.ebay.neutrino.metrics.MetricsLifecycle\"\n  ]\n  metrics = [\n    # Support for console logging\n    { type = \"console\",  publish-period = 1m },\n\n    # Support for graphite publishing\n    { type = \"graphite\", publish-period = 1m, host = \"graphite-9780.phx01.dev.ebayc3.com\", port = 2003 }\n  ]\n\n  # pools configuration\n  pools = [\n    # pool 1, lc = least connection\n    { id = \"cname1_id\", protocol = \"http\", balancer = \"lc\", port = \"8080\",\n      # servers are the VM/Servers to which traffic will be routed\n      servers = [\n        { id=\"server10\", host = \"localhost\", port = \"9999\" }\n        { id=\"server2\", host = \"localhost\", port = \"9998\" }\n      ]\n      # All traffic which comes with header Host=cname1.com will be routed to localhost.\n      # cname1.com should uniquely to idenitfy a pool\n      addresses = [\n        { host=\"cname1.com\" }\n        { host=\"cname5.com\" }\n        { host=\"d-sjc-00541471.corp.ebay.com\" }\n\n      ]\n      # wildcard shares the balancer and protocol with addresses\n      # All traffic which comes with header host=cnamewildcard.com amd path=local will be routed to localhost.\n      # host + path should be unique to identify the pool\n      wildcard = [\n        {host=\"cnamewildcard.com\", path=\"/website\" }\n      ]\n      timeout= {\n        read-idle-timeout = 1s,\n        write-idle-timeout = 1s,\n        write-timeout = 1s,\n        session-timeout = 1s,\n        request-timeout = 1s,\n        connection-timeout = 1s\n\n      }\n    },\n    # pool 2\n    { id = \"cname2_id\", protocol = \"http\", balancer = \"round-robin\", port = \"8080\",\n      servers = [\n        { id=\"server3\", host = \"www.ebay1.com\", port = \"80\" }\n      ]\n      addresses = [\n        {host=\"cname2.com\" }\n      ]\n      wildcard = [\n        {host=\"cnamewildcard.com\", path=\"/ebay\" }\n      ]\n    },\n    # pool 3\n    { id = \"cname3_id\", protocol = \"http\", balancer = \"round-robin\", port = \"8082\",\n      servers = [\n        { id=\"server4\", host = \"www.yahoo.com\", port = \"80\" }\n      ]\n      addresses = [\n        {host=\"cname3.com\" }\n      ]\n      wildcard = [\n        {host=\"cnamewildcard.com\", path=\"/yahoo\" }\n      ]\n    }\n\n  ]\n}"
  },
  {
    "path": "src/test/resources/proxy-duplicate.conf",
    "content": "# Example configuration file for Proxy test application\nneutrino {\n\n  listeners = [\n    { host = \"0.0.0.0\", port = 8080, protocol = \"http\", default-pool = \"localhost\", pipeline-class = \"com.ebay.neutrino.ProxyPipeline\" },\n    { host = \"0.0.0.0\", port = 8088, protocol = \"http\", pool-resolver = \"none\" }\n  ]\n\n  # Default some servers\n  servers = [\n    { id=\"1\", host = \"localhost\", port = \"8081\" },\n    { id=\"2\", host = \"127.0.0.1\", port = \"8082\" }\n  ]\n\n\n  pools += { id = \"default\", health = null }\n  pools += {\n    id = \"localhost\",\n\n    # load-balancer = \"round-robin\"\n    # load-balancer = \"com.ebay.esb.PrimarySecondary\"\n\n    # Note that 127.0.0.1 is slower than localhost...\n    servers = ${neutrino.servers}\n  }\n}"
  },
  {
    "path": "src/test/resources/proxy-echo-test.conf",
    "content": "# Example configuration file for Proxy test application\nneutrino {\n\n  listeners = [\n    { host = \"0.0.0.0\", port = 8080, protocol = \"http\", default-pool = \"default\", pipeline-class = \"com.ebay.neutrino.ProxyPipeline\" }\n  ]\n\n  pools = [\n    { id = \"default\", protocol = \"http\", servers = [{ host = \"localhost\", port = \"8081\" }] }\n  ]\n}"
  },
  {
    "path": "src/test/resources/proxy.conf",
    "content": "\necho.neutrino {\n\n  pools = [\n    { id = \"default\", protocol = \"http\", balancer = \"least-connection\",\n      servers = [\n        { host = \"localhost\", port = \"8081\" }\n      ]\n    }\n  ]\n}\n\n\nleastconnection.neutrino {\n\n  pools = [\n    { id = \"default\", protocol = \"http\", balancer = \"least-connection\",\n      servers = [\n        {host=\"chd1b02c-d027.stratus.phx.ebay.com\", port = 8080 },\n        {host=\"chd1b02c-a16c.stratus.phx.ebay.com\", port = 8080 },\n        {host=\"chd1b02c-ce7f.stratus.phx.ebay.com\", port = 8080 },\n        {host=\"chd1b02c-0069.stratus.phx.ebay.com\", port = 8080 }\n      ]\n    }\n  ]\n}\n\n\n\nlocal.neutrino {\n\n  pools = [\n    { id = \"default\", protocol = \"http\", balancer = \"round-robin\",\n      servers = [\n        { id=\"1\", host = \"localhost\", port = \"8081\" }\n      ]\n    }\n  ]\n}\n\n\nresolvers.ebay.neutrino {\n\n  listeners = [\n    {\n      # Should try cname and then skip to default\n      pool-resolver = [\"cname\", \"layerseven\"],\n      port = 9080,\n      port-alias = 80,\n      protocol = \"http\"\n    }\n  ]\n\n  pools = [\n    { id = \"cname1_id\", protocol = \"http\", balancer = \"round-robin\",\n      servers = [\n        { id=\"1\", host = \"localhost\", port = \"8081\" }\n      ]\n      addresses = [\n        {host=\"cname1.com\", port=\"9080\", remotePort=\"8080\", balancer = \"round-robin\", protocol=\"http\",\n          timeout= {\n            readIdle = \"60\",\n            writeIdle = \"60\",\n            writeCompletion = \"60\",\n            requestCompletion = \"60\",\n            sessionCompletion = \"60\",\n            connectionTimeout = \"60\"\n\n          }\n        }\n      ]\n      wildcard = [\n        {host=\"cnamewildcard.com\", path=\"/first\" }\n      ]\n    },\n    { id = \"cname2_id\", protocol = \"http\", balancer = \"round-robin\",\n      servers = [\n        { id=\"1\", host = \"www.google.com\", port = \"8081\" }\n      ]\n      addresses = [\n        {host=\"cname2.com\", port=\"80\", remotePort=\"8080\", balancer = \"round-robin\", protocol=\"http\",\n          timeout= {\n            readIdle = \"60\",\n            writeIdle = \"60\",\n            writeCompletion = \"60\",\n            requestCompletion = \"60\",\n            sessionCompletion = \"60\",\n            connectionTimeout = \"60\"\n\n          }\n        }\n      ]\n      wildcard = [\n        {host=\"cnamewildcard.com\", path=\"/second\"}\n      ]\n\n    }\n  ]\n}\n\n\ndev.ebay.neutrino {\n\n  # Reporting/Metrics settings\n  metrics = [\n    { type = \"console\",  publish-period = 20m }\n  ]\n\n  # Force fast timeouts\n  listener = {\n    pipeline-class = null\n\n    timeout = {\n      #read-idle-timeout = 10s\n      #request-timeout = 10s\n      #session-timeout = 10s\n    }\n  }\n\n}"
  },
  {
    "path": "src/test/resources/reference.conf.bak",
    "content": "# Defaults\n#\nakka {\n\n  # One of [ERROR, WARNING, INFO, DEBUG]\n  loglevel = WARNING\n\n\n  actor {\n    # Available providers:\n    #    provider = \"akka.actor.LocalActorRefProvider\"\n    #    provider = \"akka.remote.RemoteActorRefProvider\"\n    provider = \"akka.actor.LocalActorRefProvider\"\n\n    serializers {\n      #java  = \"akka.serialization.JavaSerializer\"\n      #proto = \"akka.remote.serialization.ProtobufSerializer\"\n      #bytes   = \"com.ebay.neutrino.core.zmq.ByteStringSerializer\"\n      default = \"akka.serialization.JavaSerializer\"\n    }\n\n    serialization-bindings {\n      \"java.lang.String\" = default\n      \"spray.http.HttpRequest\" = default\n      \"spray.http.HttpResponse\" = default\n      #\"akka.util.ByteString\" = bytes\n      #\"akka.util.ByteString$ByteStrings\" = bytes\n    }\n  }\n\n  remote {\n    enabled-transports = [\"akka.remote.netty.tcp\"]\n  }\n}\n\nspray.can {\n\n    server {\n        # If a request hasn't been responded to after the time period set here\n        # a `spray.http.Timedout` message will be sent to the timeout handler.\n        # Set to `infinite` to completely disable request timeouts.\n        request-timeout = 4s\n\n        # The time after which an idle connection will be automatically closed.\n        # Set to `infinite` to completely disable idle connection timeouts.\n        idle-timeout = 60 s\n        #idle-timeout = 5s\n    }\n\n    server.parsing {\n        # Enables/disables the logging of warning messages in case an incoming\n        # message (request or response) contains an HTTP header which cannot be\n        # parsed into its high-level model class due to incompatible syntax.\n        # Note that, independently of this settings, spray will accept messages\n        # with such headers as long as the message as a whole would still be legal\n        # under the HTTP specification even without this header.\n        # If a header cannot be parsed into a high-level model instance it will be\n        # provided as a `RawHeader`.\n        illegal-header-warnings = off\n    }\n\n    client.parsing {\n        illegal-header-warnings = off\n    }\n\n\thost-connector {\n\t    # The maximum number of parallel connections that an `HttpHostConnector`\n\t    # is allowed to establish to a host. Must be greater than zero.\n\t    #\n\t    # *** Default is 4\n\t    max-connections = 128\n\n    \t# If this setting is enabled, the `HttpHostConnector` pipelines requests\n    \t# across connections, otherwise only one single request can be \"open\"\n    \t# on a particular HTTP connection.\n    \t#\n    \t# *** Default is off\n    \tpipelining = off\n \t }\n}"
  },
  {
    "path": "src/test/resources/server.conf",
    "content": "# Example configuration file for Proxy test application\nneutrino {\n\n  # Initialize the graphite-console logging\n  initializers = [\n    #\"com.ebay.neutrino.metrics.MetricsLifecycle\"\n  ]\n\n  # Reporting/Metrics settings\n  metrics = []\n\n  # Default listener\n  listener = {\n    # Listen on localhost\n    host = \"0.0.0.0\"\n    pool-resolver = \"none\"\n    pipeline-class = \"com.ebay.neutrino.LoremIpsumGenerator\"\n\n    # Zero (0 seconds) disables timeout\n    # timeout.session-timeout = 0\n\n    # Channel Options\n    channel-options {\n      # Force echannel to keep-alive or use client's keep-alive default\n      #force-keepalive = true\n\n      # Enabling this will capture timestamps of events within the pipeline\n      audit-threshold = 3s\n    }\n  }\n\n  listeners = [\n    { port = 8081, port-alias =  80, protocol = \"http\" }\n    #{ port = 8082, port-alias = 443, protocol = \"https\" }\n  ]\n\n}"
  },
  {
    "path": "src/test/resources/test-env.conf",
    "content": "# Example configuration file for Proxy test application\nneutrino {\n\n  listeners = []\n\n  pools = [\n    { id = \"default\", protocol = \"http\", servers = [] }\n  ]\n}\n\n\n# This should be added to (replace) the existing vips\ntest.neutrino {\n\n  listeners = [\n    { host = \"0.0.0.0\", port = 8088, protocol = \"http\" }\n  ]\n}\n\n# This should cancel out the existing pools\nqa.neutrino {\n\n  pools = []\n}"
  },
  {
    "path": "src/test/resources/test-timeout.conf",
    "content": "# Test 1:  Overriding defaults in base-classes\ntest-one.neutrino {\n\n  timeout = {\n    # Override to 1m - this should (hopefully) cascade to the vip/pool\n    request-timeout = 1m\n  }\n\n  # Populate a 'default' VIP\n  listeners += {}\n\n  # Populate a 'default' pool\n  pools += {}\n}\n\n\n# Test 2:  Overriding defaults in pools themselves\ntest-two.ebay.neutrino {\n\n  listeners = [\n    { timeout = { idle-timeout = 0s, write-timeout = 10m } }\n  ]\n\n  pools = [\n    { timeout = { channel-timeout = 30m, request-timeout = 30m } }\n  ]\n}"
  },
  {
    "path": "src/test/resources/test_pool.config",
    "content": "{\n    \"pool\": {\n        \"name\": \"lb-jb-test-03\",\n        \"protocol\": \"HTTP\",\n        \"method\": \"ROUNDROBIN\",\n        \"port\": \"0\",\n        \"services\": [\n            {\n                \"name\": \"192.168.1.113\",\n                \"status\": \"OUT_OF_SERVICE\",\n                \"ip\": \"192.168.1.113\",\n                \"port\": \"8080\",\n                \"weight\": \"2\",\n                \"monitors\": [\n                    {\n                        \"tcp\" - just one value, array representation does not work\n                    }\n                ]\n            },\n            {\n                \"name\": \"192.168.2.113\",\n                \"ip\": \"192.168.2.113\",\n                \"port\": \"80\",\n                \"weight\": \"4\",\n                \"monitors\": [\n                    {\n                        \"http-ecv\" - just one value, array representation does not work\n                    }\n                ]\n            }\n        ]\n    }\n}"
  },
  {
    "path": "src/test/resources/test_vip.conf",
    "content": "{\n  \"vip\": [{\n    \"ip\": \"192.168.33.100\", //Required\n    \"name\": \"ns-vs-test-001\", //Required\n    \"port\": \"8080\", //Required\n    \"protocol\": \"TCP\", //Required\n    \"enabled\": \"true\", //Required\n    \"snatpool\": \"SnatPool_3\", //Optional\n    \"unitId\": 1, //Optional\n    \"sslCert\": \"jbehave_test_profile\",//Optional\n    \"pools\": { //Required\n      \"name\": \"ns_pool_01\", //Required\n      \"policy\": \"XeBayAkamai9-rule\", //Optional\n      \"priority\": \"2\" //Optional\n    },\n    \"policies\": [ //Optional\n      {\n        \"name\": \"jbehave_irule_03\"\n      }\n    ],\n    \"clientTimeout\":1000,  //Optional\n    \"tcpBufferEnabled\":true  //Optional (marks all the services of associated Pool as tcp enabled (Netscaler) and attaches tcp_wan_optimized profile (in case of f5))\n  }, {\n    \"ip\": \"192.168.33.101\",\n    \"name\": \"ns-vs-test-002\",\n    \"port\": \"8081\",\n    \"protocol\": \"TCP\",\n    \"enabled\": \"false\",\n    \"snatpool\": \"SnatPool_3\",\n    \"unitId\": 1,\n    \"pools\": {\n      \"name\": \"ns-ssl_test_01\", //Required\n      \"policy\": \"XeBayAkamai9-rule\", //Optional\n      \"priority\": \"2\"   //Optional\n    },\n    \"clientTimeout\":1200 //Optional\n  }]\n}"
  },
  {
    "path": "src/test/resources/test_virtualserver.conf",
    "content": "neutrino.pools += {\n  id = \"default\", protocol = \"http\", monitor-class = \"SomeClass\"\n  servers = []\n\n  custom-field = 1234\n  custom-sub-configuration = {}\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/Client.scala",
    "content": "package com.ebay.neutrino\n\nimport java.net.InetSocketAddress\nimport java.util.concurrent.atomic.AtomicInteger\n\nimport com.ebay.neutrino.util.Utilities\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.bootstrap.Bootstrap\nimport io.netty.channel._\nimport io.netty.channel.nio.NioEventLoopGroup\nimport io.netty.channel.socket.SocketChannel\nimport io.netty.channel.socket.nio.NioSocketChannel\nimport io.netty.handler.codec.http._\n\nimport scala.annotation.tailrec\nimport scala.concurrent.duration.{FiniteDuration, _}\nimport scala.concurrent.{Await, Future, Promise}\nimport scala.util.{Failure, Success}\n\n\n/**\n * Create a simple Server application\n * (one that doesn't proxy to any downstream applications)\n *\n * val endpoint = EndpointConfig(8081)\n * val vip      = VipSettings()\n * val server   = VirtualServer(\"localhost\", 8081)\n *\n * // Example pluggable handler(s)\n * val pipeline = PipelineInitializer(new PipelineHandler())\n * core.initialize(core.http.service(vip, pipeline))\n * core.register(core.http.client(server))\n */\nobject Client extends App {\n\n  //val threads = 10\n  val concurrency = 200\n  val total = 1000\n\n  val client = new Client\n  client.run()\n}\n\n\n\nclass Client extends ChannelInitializer[SocketChannel] with ChannelFutureListener {\n  import Utilities._\n\n  import scala.concurrent.ExecutionContext.Implicits.global\n\n\n  val workers = new NioEventLoopGroup()\n\n  val address = new InetSocketAddress(\"localhost\", 8080)\n\n  val bootstrap = new Bootstrap()\n    .channel(classOf[NioSocketChannel])\n    .group(workers)\n    .option[java.lang.Boolean](ChannelOption.TCP_NODELAY, true)\n    .option[java.lang.Integer](ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000)\n    .handler(this)\n\n\n  /**\n   * Connect to the downstream.\n   * @return\n   */\n  def connect(): Future[ClientConnection] =\n    bootstrap.connect(address).future.map (_.pipeline.get(classOf[ClientConnection]))\n\n\n  // Framing decoders; extract initial HTTP connect event from HTTP stream\n  protected def initChannel(channel: SocketChannel) = {\n    // Configure our codecs\n    //    channel.pipeline.addLast(new HttpObjectAggregator())\n    //    channel.closeFuture().addListener(this)\n    channel.pipeline.addLast(new HttpClientCodec())\n    channel.pipeline.addLast(new ClientConnection(channel))\n  }\n\n  /**\n   * Customized channel-close listener for Neutrino IO channels.\n   * Capture and output IO data.\n   */\n  override def operationComplete(future: ChannelFuture) = {\n  }\n\n  def release(channel: Channel) = {\n    // TODO reuse if we can\n    // Check for concurrency, total\n  }\n\n\n  def send(request: FullHttpRequest, connection: ClientConnection) = {\n    val result = connection.send(request)\n\n    result map { result =>\n      println(s\"Success:  elapsed = ${result.elapsed.toMillis}ms\")\n      result\n    }\n  }\n\n\n  def run() = {\n    val total       = new AtomicInteger(100)\n    val concurrency = 10\n    val request     = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, \"/\")\n\n    //@tailrec\n    def sender(connection: ClientConnection): Unit = {\n      send(request, connection) onComplete {\n        case Failure(ex)     => println(s\"Exception: $ex\")\n        case Success(result) => if (total.decrementAndGet() > 0) sender(connection)\n      }\n    }\n\n    (0 until concurrency) foreach { _ =>\n      connect() foreach (sender(_))\n    }\n  }\n}\n\n/**\n * Downstream connection initializer.\n *\n// * @param node\n */\nclass ClientConnection(channel: Channel) extends ChannelInboundHandlerAdapter with StrictLogging\n{\n  // Completion promise\n  private var promise: Promise[ClientResult] = null\n  private var result: ClientResult = null\n  private var start = System.nanoTime()\n\n  // Complete the current request/response and notify listeners\n  private def complete(): Unit = {\n    require(promise != null, \"request not in progress\")\n    require(result != null, \"response required\")\n\n    val (p, r) = (promise, result)\n    promise = null\n    result = null\n    p.complete(Success(r))\n  }\n\n\n  /**\n   * Handle incoming messages.\n   * @param ctx\n   * @param msg\n   */\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef) = {\n    msg match {\n      case data: HttpContent =>\n        // add content stats\n      case data: HttpResponse =>\n        require(result == null)\n        result = ClientResult(start, data)\n      case _ =>\n        logger.warn(\"Unexpected message received: {}\", msg)\n    }\n\n    if (msg.isInstanceOf[LastHttpContent]) {\n      complete()\n    }\n  }\n\n\n  /**\n   * Send a request on the current (idle) connection.\n   * @param request\n   * @return\n   */\n  def send(request: FullHttpRequest): Future[ClientResult] = {\n    require(promise == null, \"Request is outstanding; can't start\")\n\n    // Create a new completion promise\n    promise = Promise[ClientResult]()\n    start   = System.nanoTime()\n    result  = null\n\n    // Send the request\n    channel.pipeline.writeAndFlush(request)\n\n    promise.future\n  }\n\n}\n\n\ncase class ClientResult(starttime: Long, response: HttpResponse) {\n\n  val elapsed: FiniteDuration = (System.nanoTime() - starttime) nanos\n}\n"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/EchoIntegrationTest.scala",
    "content": "package com.ebay.neutrino\n\nimport akka.actor.{ActorSystem, Props}\nimport com.typesafe.config.ConfigFactory\nimport org.scalatest._\n\n\ntrait EchoIntegrationTest extends BeforeAndAfterAll { self: Suite =>\n\n  private val echoFile = \"echo.conf\"\n  private var echoSystem: ActorSystem = null\n\n\n  override def beforeAll() {\n    // Start echo server\n    // the handler actor replies to incoming HttpRequests\n    val config = ConfigFactory.load(echoFile)\n    val name   = s\"echo-server\"\n\n    echoSystem = ActorSystem(\"EchoIntegrationTest\", config)\n    echoSystem.actorOf(Props[EchoServer], \"server\")\n\n    super.beforeAll()\n  }\n\n  override def afterAll() {\n    // Shut the actor-system down\n    echoSystem.shutdown()\n    echoSystem = null\n\n    super.afterAll()\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/EchoServer.scala",
    "content": "package com.ebay.neutrino\n\nimport java.util.concurrent.TimeUnit\n\nimport akka.actor._\nimport akka.io.IO\nimport com.ebay.neutrino.util.Random\nimport com.typesafe.config.{Config, ConfigFactory}\nimport spray.can.Http\nimport spray.http._\n\nimport scala.concurrent.duration._\n\n\nobject EchoServer extends App {\n\n  // Extract port from args, if provided\n  val port = if (args.size > 0) args(0).toInt else 8081\n\n  // Load our configuration from file and merge in the port parameter\n  val config = ConfigFactory.parseString(s\"echo-server.port = $port\") withFallback ConfigFactory.load(\"echo.conf\")\n  val system = ActorSystem(\"echo-server\", config)\n  system.actorOf(Props[EchoServer], \"echo-server\")\n}\n\n\nclass EchoServer extends Actor with ActorLogging {\n  import scala.language.implicitConversions\n\n  implicit val system = context.system\n  val startup  = System.currentTimeMillis\n  val settings = EchoServerSettings(system)\n\n  //Use the system's dispatcher as ExecutionContext\n  import system.dispatcher\n\n  // Register connection service\n  IO(Http) ! Http.Bind(self, interface = settings.host, port = settings.port)\n\n  /**\n   *\n   * Wire our message handling:\n   * - Connections: server connection-lifecycle handling (including timeouts)\n   * - Requests:    request-entity dispatch and handling\n   * - Other:       error handling\n   *\n   */\n  def receive = {\n    case Http.Bound(address) =>\n      // Startup our connection-handling\n      println (s\"Server bound on $address\")\n\n    case _:Http.Connected =>\n      sender ! Http.Register(self)\n\n    case HttpRequest(_, uri, _, _, _) =>\n      // Extract configuration\n      val response = HttpResponse(status = StatusCodes.OK, entity = \"Echo!\\r\\n\")\n      val latency  = settings.latency\n\n      // Introduce an appropriate delay and send back a result\n      system.scheduler.scheduleOnce(latency, sender, response)\n      log.info (\"Sending HttpResponse {}\", response)\n\n    case Timedout(request: HttpRequest) =>\n      log.error (\"Request timed out: {}\", request)\n\n    case msg: Http.ConnectionClosed =>\n      log.debug (\"Connection closed: \"+msg)\n\n    case other =>\n      log.error(other.toString)\n  }\n}\n\n\n\n/**\n * EchoSettings\n *\n */\ncase class EchoServerSettings(host: String, port: Int, random: Boolean, duration: FiniteDuration)\n  extends Extension\n{\n  def latency = random match {\n    case false => duration\n    case true  => Random.nextMillis(duration)\n  }\n}\n\nobject EchoServerSettings {\n\n  def apply(c: Config): EchoServerSettings = EchoServerSettings(\n    c getString \"host\",\n    c getInt \"port\",\n    c getBoolean \"random\",\n    c getDuration(\"duration\", TimeUnit.MILLISECONDS) milliseconds\n  )\n\n  def apply(system: ActorSystem): EchoServerSettings =\n    EchoServerSettings(system.settings.config getConfig \"echo-server\")\n\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/ExampleInitializers.scala",
    "content": "package com.ebay.neutrino\n\nimport com.ebay.neutrino.handler.ExamplePipelineHandler\nimport io.netty.channel._\nimport io.netty.handler.codec.http.{HttpClientCodec, HttpServerCodec}\nimport io.netty.handler.logging.LoggingHandler\n\n\nobject ExampleInitializers {\n\n\n  class HttpFramingInitializer extends ChannelInitializer[Channel] {\n\n    // Assume HTTP -> HTTP\n    protected def initChannel(ch: Channel): Unit = {\n      ch.pipeline.addFirst(new HttpServerCodec())\n      ch.pipeline.addLast(new HttpClientCodec())\n    }\n  }\n\n  class OperationsFramingInitializer extends ChannelInitializer[Channel] {\n\n    // Mock with an example framing initializer\n    protected def initChannel(ch: Channel): Unit = {\n      ch.pipeline.addLast(new LoggingHandler())\n    }\n  }\n\n  class UserPipelineInitializer extends ChannelInitializer[Channel] {\n\n    // Mock with an example framing initializer\n    protected def initChannel(ch: Channel): Unit = {\n      ch.pipeline.addLast(new ExamplePipelineHandler())\n    }\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/NettyClientSupport.scala",
    "content": "package com.ebay.neutrino\n\nimport java.net.InetAddress\n\nimport io.netty.bootstrap.{Bootstrap, ServerBootstrap}\nimport io.netty.buffer.Unpooled\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.channel._\nimport io.netty.channel.nio.NioEventLoopGroup\nimport io.netty.channel.socket.nio.{NioServerSocketChannel, NioSocketChannel}\nimport io.netty.handler.codec.http._\nimport io.netty.handler.timeout.{ReadTimeoutException, WriteTimeoutException}\nimport io.netty.util.CharsetUtil\n\nimport scala.concurrent.duration.{Duration, _}\nimport scala.concurrent.{Await, Future, Promise}\n\n/**\n * Created by cbrawn on 3/3/15.\n *\n */\ntrait NettyTestSupport {\n\n  import io.netty.handler.codec.http.HttpVersion._\n\n  // Constants\n  val DefaultHost = InetAddress.getLocalHost\n  val DefaultPort = 9000\n\n  // Shared worker-pools\n  val supervisor = new NioEventLoopGroup(1)\n  val workers = new NioEventLoopGroup()\n\n\n  @Sharable\n  class MessageHandler(message: String) extends ChannelInboundHandlerAdapter {\n\n    override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef) = {\n      // Create a message\n      val buffer   = Unpooled.copiedBuffer(message+\"\\r\\n\", CharsetUtil.UTF_8)\n      val response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK, buffer)\n\n      //response.headers.set(Names.CONTENT_TYPE, \"text/plain; charset=UTF-8\")\n      ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE)\n    }\n\n    override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) =\n      cause match {\n        case _: ReadTimeoutException =>\n        case _: WriteTimeoutException =>\n      }\n  }\n}\n\n\ntrait NettyClientSupport extends NettyTestSupport {\n\n  // Full request/response initializer\n  @Sharable\n  class HttpClientInitializer(aggregate: Boolean) extends ChannelInitializer[Channel] {\n\n    val response = Promise.apply[FullHttpResponse]()\n\n    def initChannel(channel: Channel) = {\n      channel.pipeline.addLast(new HttpClientCodec())\n      if (aggregate) channel.pipeline.addLast(new HttpObjectAggregator(1000000))\n      channel.pipeline.addLast(new ResponseHandler(response))\n    }\n  }\n\n  // Simple response-handler which notifies the promise when response is completed\n  class ResponseHandler(promise: Promise[FullHttpResponse]) extends ChannelDuplexHandler {\n\n    // Default implementation; buffer the incoming data\n    override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit =\n      msg match {\n        case response: FullHttpResponse => promise.success(response)\n        case _ =>\n      }\n\n    override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) = {\n      if (!promise.isCompleted) promise.failure(cause)\n    }\n  }\n\n  //\n  class InboundMsgBuffer extends ChannelInboundHandlerAdapter {\n    var inbound = Seq.empty[AnyRef]\n\n    override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = {\n      inbound = inbound :+ msg\n    }\n  }\n\n\n  /**\n   * In-progress connection wrapper, for facilitating mediation to connection\n   *\n   * @param channel\n   * @param response\n   */\n  case class Connection(channel: Channel, response: Future[FullHttpResponse]) {\n\n    val start = System.currentTimeMillis\n    def elapsed = (System.currentTimeMillis-start).millis\n\n    // Send the request message-portion provided\n    def send(message: HttpObject): ChannelFuture = channel.pipeline.writeAndFlush(message)\n\n    // Wait for the HTTP response, up to the time specified\n    def result(timeout: Duration) = Await.result(response, timeout)\n    def ready(timeout: Duration) = Await.ready(response, timeout)\n  }\n\n\n  case class HttpClient(host: InetAddress=DefaultHost, port: Int=DefaultPort, aggregate: Boolean=true) {\n\n    val workers = new NioEventLoopGroup()\n    def initialzer = new HttpClientInitializer(aggregate)\n\n    def send(request: HttpRequest): Connection = {\n      val init = initialzer\n      val boot = new Bootstrap().channel(classOf[NioSocketChannel]).group(workers).handler(init).connect(host, port)\n      val conn = Connection(boot.sync().channel, init.response.future)\n\n      conn.send(request).sync()\n      conn\n    }\n  }\n}\n\n\ntrait NettyServerSupport extends NettyTestSupport {\n\n  // Full request/response initializer\n  @Sharable\n  class HttpServerInitializer(handlers: ChannelHandler*) extends ChannelInitializer[Channel] {\n    def initChannel(channel: Channel) = {\n      channel.pipeline.addLast(new HttpServerCodec())\n      channel.pipeline.addLast(new HttpObjectAggregator(1000000))\n      channel.pipeline.addLast(handlers:_*)\n    }\n  }\n\n\n  def startup(handlers: Seq[ChannelHandler]): ChannelFuture =\n    startup(new HttpServerInitializer(handlers:_*))\n\n\n  def startup(initializer: ChannelInitializer[Channel], host: InetAddress=InetAddress.getLocalHost, port: Int=9000): ChannelFuture =\n  {\n    val channel = new ServerBootstrap()\n      .channel(classOf[NioServerSocketChannel])\n      .group(supervisor, workers)\n      .childHandler(initializer)\n      .childOption[java.lang.Boolean](ChannelOption.TCP_NODELAY, true)\n      .bind(host, port)\n      .sync()\n\n    assert(channel.isDone && channel.isSuccess)\n    channel\n  }\n\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/NettyClientSupportTest.scala",
    "content": "package com.ebay.neutrino\n\nimport io.netty.handler.codec.http._\nimport org.scalatest.{FlatSpec, Matchers}\n\nimport scala.concurrent.Await\nimport scala.util.Success\n\n\n/**\n * Created by cbrawn on 3/3/15.\n */\nclass NettyClientSupportTest extends FlatSpec with Matchers with NettyClientSupport with NettyServerSupport {\n\n  import io.netty.handler.codec.http.HttpVersion._\n  import scala.concurrent.duration._\n\n\n  it should \"ensure client received events\" in {\n    val server  = startup(Seq(new MessageHandler(\"goodbye\")))\n    val client  = new HttpClient()\n    val request = new DefaultFullHttpRequest(HTTP_1_1, HttpMethod.GET, \"/\")\n    val conn    = client.send(request)\n\n    Await.ready(conn.response, 2 seconds)\n\n    conn.response.isCompleted should be (true)\n    conn.response.value.get shouldBe a [Success[_]]\n\n    server.channel.close().sync()\n  }\n}\n"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/NeutrinoCoreSupport.scala",
    "content": "package com.ebay.neutrino\n\nimport com.ebay.neutrino.config._\nimport com.typesafe.config.Config\nimport org.scalatest.{BeforeAndAfterAll, Suite}\n\nimport scala.concurrent.Await\nimport scala.concurrent.duration._\n\n/**\n * Test base-class for NeutrinoCore configurations.\n *\n */\ntrait NeutrinoCoreSupport extends BeforeAndAfterAll with NeutrinoTestSupport { self: Suite =>\n\n  private var _settings: NeutrinoSettings = null\n  private var _core: NeutrinoCore = null\n\n  implicit def core: NeutrinoCore = _core\n  implicit def settings: NeutrinoSettings = _settings\n  implicit def config: Config = Configuration.load()\n\n  // Default a configuration\n  def createSettings()(implicit config: Config) = NeutrinoSettings(config)\n\n  // By default, start the core on startup\n  def startCore() = true\n\n  // Update the core/service's topology\n  def update(pools: VirtualPool*) = core.services.head.update(pools:_*)\n\n  // Before test-classes, start NeutrinoCore\n  override def beforeAll() = {\n    super.beforeAll()\n\n    _settings = createSettings()\n    _core = new NeutrinoCore(_settings)\n  }\n\n  override def afterAll() = {\n    _core = null\n    _settings = null\n\n    super.afterAll()\n  }\n\n}\n\n\n\ntrait NeutrinoCoreRunning extends NeutrinoCoreSupport { self: Suite =>\n\n  // Before test-classes, start NeutrinoCore\n  override def beforeAll() = {\n    super.beforeAll()\n    Await.result(core.start(), 5 seconds)\n  }\n\n  override def afterAll() = {\n    Await.result(core.shutdown(), 5 seconds)\n    super.afterAll()\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/NeutrinoCoreTest.scala",
    "content": "package com.ebay.neutrino.config\n\nimport com.ebay.neutrino.NeutrinoCore\nimport org.scalatest.{Matchers, FlatSpec}\n\nimport scala.concurrent.Await\nimport scala.concurrent.duration._\n\n\nclass NeutrinoCoreTest extends FlatSpec with Matchers {\n\n  // Hard-code a configuration\n  val settings = NeutrinoSettings(Configuration.load(\"proxy.conf\"))\n\n\n  it should \"startup and shutdown properly\" in {\n    val core = new NeutrinoCore(settings)\n\n    // Start running. This will run until the process is interrupted...\n    val start = core.start()\n\n    // Try over a few seconds\n    Await.ready(start, 500 millis).isCompleted should be (true)\n    core.supervisor.isShuttingDown should be (false) // This includes shutdown and terminated\n\n    // Now, trigger shutdown (ensure it's fast enough to not kill our tests)\n    val stop = core.shutdown()\n    val time = System.currentTimeMillis\n    Await.ready(stop, 5 seconds)\n\n    /*\n    import core.context\n    stop onComplete { _ =>\n      println(s\"Elapsed time = ${System.currentTimeMillis()-time millis}\")\n    }\n    */\n\n    // Check both futures for completion\n    assert(start.isCompleted)\n    assert(stop.isCompleted)\n    assert(core.supervisor.isTerminated)\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/NeutrinoNodesTest.scala",
    "content": "package com.ebay.neutrino\n\nimport com.ebay.neutrino.config.{NeutrinoSettings, VirtualServer}\nimport org.scalatest.{FlatSpec, Matchers}\n\n\nclass NeutrinoNodeTest extends FlatSpec with Matchers {\n\n  it should \"provide testing for NeutrinoNode update\" in {\n    // TODO\n  }\n}\n\n\nclass NeutrinoNodesTest extends FlatSpec with Matchers with NeutrinoTestSupport {\n\n  implicit val core = new NeutrinoCore(NeutrinoSettings.Empty)\n\n\n  def server(id: String=\"id\", host: String=\"www.ebay.com\", post: Int=80): VirtualServer =\n    VirtualServer(id, host, post)\n\n\n  it should \"ensure apply() maps to underlying state\" in {\n    // TODO\n\n  }\n\n\n  it should \"rudmintary test of neutrino-nodes wrapper\" in {\n    val nodes = neutrinoNodes()\n    nodes() shouldBe empty\n\n    // Add a single node\n    nodes.update(server(id=\"1\"))\n    nodes().size should be (1)\n\n    // Add two nodes\n    nodes.update(server(id=\"1\"), server(id=\"2\"))\n    nodes().size should be (2)\n    nodes() map (_.settings.id.toInt) should be (Seq(1,2))\n\n    // Add two nodes\n    nodes.update(server(id=\"3\"))\n    nodes().size should be (1)\n    nodes() map (_.settings.id.toInt) should be (Seq(3))\n\n    // Remove all nodes\n    nodes.update()\n    nodes().size should be (0)\n    nodes() map (_.settings.id.toInt) should be (Seq())\n  }\n\n\n  it should \"test massive concurrency access for safety\" in {\n    // TODO ...\n  }\n\n\n  it should \"resolve pool by name\" in {\n  }\n}\n"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/NeutrinoPoolsTest.scala",
    "content": "package com.ebay.neutrino\n\nimport com.ebay.neutrino.config.{NeutrinoSettings, VirtualPool}\nimport org.scalatest.{FlatSpec, Matchers}\n\n\nclass NeutrinoPoolsTest extends FlatSpec with Matchers with NeutrinoTestSupport {\n\n  implicit val core = new NeutrinoCore(NeutrinoSettings.Empty)\n\n  def startCore() = false\n\n\n  it should \"ensure apply() maps to underlying state\" in {\n    // TODO\n\n  }\n\n\n  it should \"rudmintary test of neutrino-pools wrapper\" in {\n    val pools = neutrinoPools()\n    pools() shouldBe empty\n\n    // Add a single pool\n    pools.update(VirtualPool())\n    pools().size should be (1)\n\n    // Add two pools\n    pools.update(VirtualPool(id=\"1\"), VirtualPool(id=\"2\"))\n    pools().size should be (2)\n    pools() map (_.settings.id.toInt) should be (Seq(1,2))\n\n    // Add two pools\n    pools.update(VirtualPool(id=\"3\"))\n    pools().size should be (1)\n    pools() map (_.settings.id.toInt) should be (Seq(3))\n\n    // Remove all pools\n    pools.update()\n    pools().size should be (0)\n    pools() map (_.settings.id.toInt) should be (Seq())\n  }\n\n\n  it should \"test massive concurrency access for safety\" in {\n    // TODO ...\n  }\n\n/*\n  it should \"resolve pool by name\" in {\n    val pools = new NeutrinoPools(null)\n    val seq = Seq(VirtualPool(\"abc\"), VirtualPool(\"123\"), VirtualPool(\"zyx\", protocol=Transport.HTTPS), VirtualPool(\"987\"))\n\n    // Setup the pools\n    pools.update(seq:_*)\n    pools().size should be (4)\n\n    // Attempt to resolve by name\n    pools.getNamed(\"abc\").map(_.settings) should be (Some(VirtualPool(\"abc\")))\n    pools.getNamed(\"987\").map(_.settings) should be (Some(VirtualPool(\"987\")))\n    pools.getNamed(\"ab7\").map(_.settings) should be (None)\n\n    // Should only match HTTP on default\n    pools.getNamed(\"123\", Transport.HTTP).map(_.settings) should be (Some(VirtualPool(\"123\")))\n    pools.getNamed(\"123\", Transport.HTTPS).map(_.settings) should be (None)\n    pools.getNamed(\"123\").map(_.settings) should be (Some(VirtualPool(\"123\", Transport.HTTP)))\n    pools.getNamed(\"123\").map(_.settings) should not be (Some(VirtualPool(\"123\", Transport.HTTPS)))\n\n    // Should only match HTTPS against specified\n    pools.getNamed(\"zyx\").map(_.settings) should be (None)\n    pools.getNamed(\"zyx\", Transport.HTTP).map(_.settings) should be (None)\n    pools.getNamed(\"zyx\", Transport.HTTPS).map(_.settings) should be (Some(VirtualPool(\"zyx\", protocol=Transport.HTTPS)))\n  }\n  */\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/NeutrinoTestSupport.scala",
    "content": "package com.ebay.neutrino\n\nimport com.ebay.neutrino.channel.{NeutrinoSession, NeutrinoService}\nimport com.ebay.neutrino.config._\nimport io.netty.channel.Channel\nimport io.netty.channel.embedded.EmbeddedChannel\nimport io.netty.handler.codec.http.{DefaultHttpRequest, HttpMethod, HttpRequest, HttpVersion}\n\n\n/**\n * Created by cbrawn on 3/2/15.\n */\ntrait NeutrinoTestSupport extends NeutrinoSettingsSupport {\n\n  implicit def core: NeutrinoCore\n\n\n  /** Default channel creation\n   */\n  def channel(): Channel = new EmbeddedChannel()\n\n\n  /**\n   * Request creation support.\n   * Where possible, we should pass in the containing core (either directly or via session).\n   */\n  def request(uri: String=\"/\"): NeutrinoRequest =\n    request(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri))\n\n  def request(http: HttpRequest): NeutrinoRequest =\n    request(session(), http)\n\n  def request(session: NeutrinoSession, uri: String): NeutrinoRequest =\n    request(session, new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri))\n\n  def request(session: NeutrinoSession, http: HttpRequest): NeutrinoRequest =\n    new NeutrinoRequest(session, http)\n\n\n  /**\n   * Session creation support.\n   * Where possible, we should pass in the containing core.\n   */\n  def session(): NeutrinoSession =\n    session(listenerSettings())\n\n  def session(settings: NeutrinoSettings): NeutrinoSession =\n    session(settings.interfaces.head)\n\n  def session(settings: ListenerSettings): NeutrinoSession =\n    NeutrinoSession(channel(), service(settings))\n\n\n  /**\n   * Service creation support.\n   */\n  def service(): NeutrinoService =\n    service(listenerSettings())\n\n  def service(settings: ListenerSettings): NeutrinoService =\n    new NeutrinoService(core, settings)\n\n\n  /**\n   * Create NeutrinoPool components.\n   */\n  def neutrinoPool() =\n    new NeutrinoPool(service(), VirtualPool())\n\n  /* Shouldn't be required - just call core.update() instead\n  public NeutrinoPool createPool(final String poolname, final String hostname, final int port) {\n      return new NeutrinoPool(virtualPool(poolname, hostname, port), core);\n  }*/\n\n\n  /**\n   * Create a NeutrinoPools component.\n   */\n  def neutrinoPools(pool: VirtualPool, addresses: Seq[CanonicalAddress]): NeutrinoPools =\n    neutrinoPools(pool.copy(addresses=addresses))\n\n  def neutrinoPools(pools: VirtualPool*): NeutrinoPools = {\n    val npools = new NeutrinoPools(service)\n    if (!pools.isEmpty) npools.update(pools:_*)\n    npools\n  }\n\n\n  /**\n   * Create a NeutrinoNode component.\n   */\n  def neutrinoNodes(): NeutrinoNodes =\n    neutrinoNodes(neutrinoPool())\n\n  def neutrinoNodes(pool: NeutrinoPool): NeutrinoNodes =\n    new NeutrinoNodes(pool)\n\n\n\n  // for reasonable ordering\n  def sorted(pools: Seq[NeutrinoPool]) = pools sortBy (pool => pool.settings.id)\n\n}\n\n\n/**\n * Mock support for creating various Neutrino settings types.\n *\n * Created by cbrawn on 3/2/15.\n */\ntrait NeutrinoSettingsSupport {\n\n  /**\n   * Create a BalancerSettings object.\n   */\n  def balancerSettings(pools: VirtualPool*): LoadBalancer =\n    new LoadBalancer(\"id\", pools)\n\n\n  /**\n   * Create a ListenerAddress object.\n   */\n  def listenerAddress(host: String=\"localhost\", port: Int=8080, transport: Transport=Transport.HTTP) =\n    ListenerAddress(host, port, transport)\n\n\n  /**\n   * Create a CanonicalAddress object.\n   * @return\n   */\n  def canonicalAddress(): CanonicalAddress =\n    CanonicalAddress(\"www.ebay.com\", 80, Transport.HTTP, TimeoutSettings.Default)\n\n  def canonicalAddresses(): Seq[CanonicalAddress] =\n    Seq(canonicalAddress())\n\n\n  /**\n   * Create a ListenerSettings object.\n   */\n  def listenerSettings(): ListenerSettings =\n    listenerSettings(listenerAddress())\n\n  def listenerSettings(port: Int): ListenerSettings =\n    listenerSettings(listenerAddress(port=port))\n\n  def listenerSettings(address: ListenerAddress): ListenerSettings =\n    listenerSettings(address, address.protocol)\n\n  def listenerSettings(address: ListenerAddress, transport: Transport): ListenerSettings =\n    ListenerSettings(\n      Seq(address), transport, Seq(address.port), Seq(), Seq(), ChannelSettings.Default, TimeoutSettings.Default\n    )\n\n\n  /**\n   * Create a VirtualPool object.\n   */\n  def virtualPool(id: String=\"id\", host: String=\"www.ebay.com\", port: Int=80, transport: Transport=Transport.HTTP) = {\n    val server  = VirtualServer(id, host, port)\n    val address = CanonicalAddress(host, port, transport)\n    VirtualPool(id, transport, Seq(server), Seq(address))\n  }\n\n  def virtualPool(poolname: String, servers: VirtualServer*): VirtualPool =\n    VirtualPool(poolname, Transport.HTTP, servers, Seq.empty)\n\n\n\n  /**\n   * Create VirtualServer components.\n   */\n  def virtualServer(hostname: String, port: Int): VirtualServer =\n    VirtualServer(hostname, hostname, port, None)\n\n}\n"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/PoolResolverTest.scala",
    "content": "package com.ebay.neutrino\n\nimport com.ebay.neutrino.balancer.CNameResolver\nimport com.ebay.neutrino.config._\nimport org.scalatest.{FlatSpec, Matchers}\n\n\nclass PoolResolverTest extends FlatSpec with Matchers with NeutrinoTestSupport {\n  behavior of \"Creating Pool Resolvers\"\n  implicit val core = new NeutrinoCore(NeutrinoSettings.Empty)\n\n\n  def createAddresses(): Seq[CanonicalAddress] = {\n    Seq(CanonicalAddress(\"www.ebay.com\", 80))\n  }\n\n\n  it should \"create static resolvers using factor\" in {\n    // None\n    PoolResolver(\"\") should be (NoResolver)\n    PoolResolver(\"none\") should be (NoResolver)\n    PoolResolver(\"NoNe\") should be (NoResolver)\n\n    // Default; static\n    PoolResolver(\"default\") should be (DefaultResolver)\n    PoolResolver(\"defaulT\") should be (DefaultResolver)\n\n    // CNAMEs should be unique\n    val cname1 = PoolResolver(\"cname\")\n    val cname2 = PoolResolver(\"cname\")\n    cname1 shouldBe a [CNameResolver]\n    cname2 shouldBe a [CNameResolver]\n    cname1 should not be (cname2)\n\n    // Classname: Test valid\n    val classname = classOf[TestPoolResolver].getName\n    PoolResolver(classname) shouldBe a [TestPoolResolver]\n\n    // Classname: Test invalid classname\n    // This can be either ClassNotFound or NoDefFound on local for some reason\n    try {\n      PoolResolver(classname.toLowerCase)\n    }\n    catch {\n      case ex: ClassNotFoundException =>\n      case ex: NoClassDefFoundError =>\n      case th: Throwable => fail(th)\n    }\n\n    // Classname: Should catch anythign else\n    an [ClassNotFoundException] should be thrownBy PoolResolver(\"asdfjklab\")\n  }\n\n\n  it should \"resolve pools using 'none' resolver\" in {\n    val resolver = PoolResolver(\"none\")\n    val pools    = neutrinoPools()\n    val passpool = VirtualPool(id=\"passpool1\")\n    val testpool = VirtualPool(id=\"testpool1\")\n\n    // Initial configuration; all our pools; no resolution\n    pools.update(passpool, testpool)\n    resolver.resolve(pools, request(\"/\")) should be (None)\n\n    // Same with combinations\n    pools.update(passpool)\n    resolver.resolve(pools, request(\"/\")) should be (None)\n  }\n\n\n  it should \"resolve pools using 'default' resolver\" in {\n    val resolver = PoolResolver(\"default\")\n    val pools    = neutrinoPools()\n    val passpool = VirtualPool(id=\"passpool1\")\n    val testpool = VirtualPool(id=\"default\")\n\n    // Initial configuration; includes testpool\n    pools.update(passpool, testpool)\n    resolver.resolve(pools, request(\"/\")) shouldBe defined\n    resolver.resolve(pools, request(\"/\")).get.settings should be (testpool)\n\n    // Clear the valid pool from the config\n    pools.update(passpool)\n    resolver.resolve(pools, request(\"/\")) should be (None)\n  }\n\n\n  it should \"resolve pools using 'classname' resolver\" in {\n    val resolver = PoolResolver(classOf[TestPoolResolver].getName)\n    val pools    = neutrinoPools()\n    val passpool = VirtualPool(id=\"passpool1\")\n    val testpool = VirtualPool(id=\"testpool1\")\n\n    // Initial configuration; includes testpool\n    pools.update(passpool, testpool)\n    resolver.resolve(pools, request(\"/\")) shouldBe defined\n    resolver.resolve(pools, request(\"/\")).get.settings should be (testpool)\n\n    // Clear the valid pool from the config\n    pools.update(passpool)\n    resolver.resolve(pools, request(\"/\")) should be (None)\n  }\n}\n\n\nclass NamedResolverTest extends FlatSpec with Matchers with NeutrinoTestSupport {\n  behavior of \"Named pool-resolver\"\n  implicit val core = new NeutrinoCore(NeutrinoSettings.Empty)\n\n\n  it should \"resolve pools using a configured named resolver\" in {\n    val resolver = new NamedResolver(\"test-name\")\n    val pools    = neutrinoPools()\n    val passpool = VirtualPool(id=\"passpool1\")\n    val testpool = VirtualPool(id=\"test-name\")\n\n    // Initial configuration; includes testpool\n    pools.update(passpool, testpool)\n    resolver.resolve(pools, request(\"/\")) shouldBe defined\n    resolver.resolve(pools, request(\"/\")).get.settings should be (testpool)\n\n    // Clear the valid pool from the config\n    pools.update(passpool)\n    resolver.resolve(pools, request(\"/\")) should be (None)\n  }\n\n  it should \"resolve pool using static get()\" in {\n    val pools    = neutrinoPools()\n    val poolA    = VirtualPool(id=\"some-pool_A\")\n    val poolB    = VirtualPool(id=\"poolb\")\n\n    // Initial configuration; includes testpool\n    pools.update(poolA, poolB)\n\n    // Check for no/match, should respect casing\n    NamedResolver.get(pools, \"poola\") should be (None)\n    NamedResolver.get(pools, \"poolb\").get.settings should be (poolB)\n    NamedResolver.get(pools, \"poolB\") should be (None)\n  }\n}\n\n\n\n/**\n * Test resolver; will match any pool with an ID that starts with \"test...\"\n */\nclass TestPoolResolver extends PoolResolver {\n\n  /**\n   * Attempt to resolve a pool using this request.\n   */\n  override def resolve(pools: NeutrinoPools, request: NeutrinoRequest): Option[NeutrinoPool] =\n    pools() find (_.settings.id.startsWith(\"test\"))\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/Proxy.scala",
    "content": "package com.ebay.neutrino\n\nimport com.ebay.neutrino.config.{Configuration, LoadBalancer}\nimport com.ebay.neutrino.handler.{ExampleCloseHandler, ExamplePipelineHandler}\nimport io.netty.channel.{Channel, ChannelInitializer}\n\n\n/**\n * Create a Proxy application\n *\n * val endpoint = EndpointConfig(8081)\n * val vip      = VipSettings()\n * val server   = VirtualServer(\"localhost\", 8081)\n *\n * // Example pluggable handler(s)\n * val pipeline = PipelineInitializer(new PipelineHandler())\n * core.initialize(core.http.service(vip, pipeline))\n * core.register(core.http.client(server))\n */\nobject Proxy extends App {\n\n  // Hard-code a configuration\n  // TODO move this to resource...\n  val config = Configuration.load(\"proxy.conf\", \"resolvers\") // dev, echo, local, algo, www, shopping\n\n  // Create a new balancer\n  val core = NeutrinoCore(config)\n\n  // Start running. This will run until the process is interrupted...\n  core.configure(LoadBalancer(config))\n  core.start()\n}\n\n\nclass ProxyPipeline extends ChannelInitializer[Channel] {\n\n  // Initialize the user-configurable pipeline\n  protected def initChannel(ch: Channel): Unit = {\n    ch.pipeline.addLast(new ExampleCloseHandler())\n    ch.pipeline.addLast(new ExamplePipelineHandler())\n    //ch.pipeline.addLast(new ExampleCustomHandler())\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/ProxyIntegrationTest.scala",
    "content": "package com.ebay.neutrino\n\nimport com.ebay.neutrino.config.{Configuration, LoadBalancer, NeutrinoSettings}\nimport org.scalatest._\n\nimport scala.concurrent.Await\n\n\ntrait ProxyIntegrationTest extends BeforeAndAfterAll { self: Suite =>\n  import scala.concurrent.duration._\n\n  // Create default balancer settings from file.\n  // Override to customize/hard-code settings\n  def settings: NeutrinoSettings = NeutrinoSettings(Configuration.load(configurationFile))\n  def config: LoadBalancer = LoadBalancer(Configuration.load(configurationFile))\n\n\n  // Start balancer, using the configuration in the standard echo.conf file\n  def configurationFile: String = null\n\n  var core: NeutrinoCore = null\n\n  override def beforeAll() {\n    // Start running. This will run until the process is interrupted...\n    core = NeutrinoCore(settings, config)\n    core.start()\n\n    super.beforeAll()\n  }\n\n  override def afterAll() {\n    // Now, trigger shutdown (ensure it's fast enough to not kill our tests)\n    val stop = core.shutdown()\n    Await.ready(stop, 5 seconds)\n\n    // Check both futures for completion\n    // assert(start.isCompleted)\n    assert(stop.isCompleted)\n    assert(core.supervisor.isTerminated)\n\n    super.afterAll()\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/SLBTest.scala",
    "content": "package com.ebay.neutrino\n\n/**\n  * Created by blpaul on 12/7/15.\n  */\n\nimport org.scalatest.{FlatSpec, Matchers}\n\nimport scala.concurrent.Await\nimport scala.concurrent.duration._\n\n\nclass SLBTest extends FlatSpec with Matchers {\n\n\n  it should \"slb startup and shutdown properly\" in {\n    val slb = SLB.apply(\"multiport.conf\")\n    val start = slb.start()\n\n    // Try over a few seconds\n    Await.ready(start, 6000 millis).isCompleted should be (true)\n\n    // Now, trigger shutdown (ensure it's fast enough to not kill our tests)\n    val stop = slb.shutdown()\n    val time = System.currentTimeMillis\n    Await.ready(stop, 50 seconds)\n\n\n    // Check both futures for completion\n    assert(start.isCompleted)\n    assert(stop.isCompleted)\n\n  }\n}\n"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/Server.scala",
    "content": "package com.ebay.neutrino\n\nimport java.util.concurrent.TimeUnit\n\nimport com.ebay.neutrino.config.{Configuration, LoadBalancer}\nimport com.ebay.neutrino.util.ResponseUtil\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.buffer.Unpooled\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.channel._\nimport io.netty.handler.codec.http._\nimport io.netty.util.{AsciiString, AttributeKey}\n\n\n/**\n * Create a simple Server application\n * (one that doesn't proxy to any downstream applications)\n *\n * val endpoint = EndpointConfig(8081)\n * val vip      = VipSettings()\n * val server   = VirtualServer(\"localhost\", 8081)\n *\n * // Example pluggable handler(s)\n * val pipeline = PipelineInitializer(new PipelineHandler())\n * core.initialize(core.http.service(vip, pipeline))\n * core.register(core.http.client(server))\n */\nobject Server extends App {\n\n  // Hard-code a configuration\n  val config = Configuration.load(\"server.conf\")\n\n  // Create a new balancer\n  val core = NeutrinoCore(config)\n\n  // Start running. This will run until the process is interrupted...\n  core.configure(LoadBalancer(config))\n  core.start()\n}\n\n\n@Sharable\nclass LoremIpsumGenerator extends ChannelInboundHandlerAdapter with StrictLogging {\n  import scala.concurrent.duration._\n\n  val MinDelay = 10 milliseconds\n\n\n  // Introduce a server delay on the incoming request\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef) = {\n\n    msg match {\n      case data: HttpRequest =>\n        val delay = 1 millisecond //Random.nextInt(1500) milliseconds\n\n        // If delay is too small, just send now\n        if (delay < MinDelay)\n          sendResponse(ctx, delay, 8000)\n        else\n          schedule(ctx, delay, sendResponse(ctx, delay, 8000))\n\n\n      case data: HttpContent =>\n        // Ignore\n\n      case _ =>\n        logger.warn(\"Unexpected HTTP Content: {}\", msg)\n    }\n  }\n\n\n  def schedule(ctx: ChannelHandlerContext, delay: FiniteDuration, fx: => Unit) = {\n    val task = new Runnable { def run(): Unit = fx }\n    val loop = ctx.channel.parent.eventLoop\n    loop.schedule(task, delay.toMillis, TimeUnit.MILLISECONDS)\n  }\n\n\n  // Prep some data sizes\n  //val data = new Array[ByteBuf](10)\n  //data(0) = Unpooled.\n\n  val DelayHeader = new AsciiString(\"X-DELAY\")\n\n  val original =\n    \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\".getBytes()\n\n  val buffers = (0 until 10) map { offset =>\n    val size   = 1024 << offset\n    val buffer = Unpooled.directBuffer(size, size)\n    while (buffer.isWritable) {\n      buffer.writeBytes(original, 0, Math.min(buffer.writableBytes(), original.length))\n      // if (buffer.writableBytes() >= 2) buffer.writeBytes(\"\\r\\n\".getBytes)\n    }\n    buffer\n  } //toArray\n\n  /**\n   * Create and send a custom response.\n   *\n   * @param ctx\n   * @param delay\n   * @return\n   */\n  def sendResponse(ctx: ChannelHandlerContext, delay: FiniteDuration, contentSize: Int) = {\n\n    val options  = ServerOptions.options(ctx.channel)\n\n    val small    = buffers(0)\n    val medium   = buffers(4)\n    val large    = buffers(8)\n    //val content  = Unpooled.wrappedBuffer(small, large, small).retain()\n    val content  = Unpooled.wrappedBuffer(medium).retain()\n    val response = ResponseUtil.generate(HttpResponseStatus.OK, content)\n\n    // Add additional diagnostic headers\n    response.headers.add(DelayHeader, delay.toString)\n\n    // Close if required\n    options.requestCount -= 1\n    if (options.requestCount == 0) HttpHeaderUtil.setKeepAlive(response, false)\n\n    // Do the write\n    ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE_ON_FAILURE)\n  }\n\n}\n\n\n/**\n * Hard-coded options.\n */\n// Set to 0/-ve for no limit on requests\ncase class ServerOptions(var requestCount: Int)\n\nobject ServerOptions {\n\n  // Constants\n  val MaxRequestCount = 100\n\n  val Key = AttributeKey.newInstance[ServerOptions](\"server-options\")\n\n  // Helper methods\n  def options(channel: Channel): ServerOptions = {\n    val attr = channel.attr(Key)\n    attr.setIfAbsent(ServerOptions(MaxRequestCount))\n    attr.get()\n  }\n\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/balancer/CNameResolverTest.scala",
    "content": "package com.ebay.neutrino.balancer\n\nimport com.ebay.neutrino.config._\nimport com.ebay.neutrino.{NeutrinoCore, NeutrinoTestSupport}\nimport org.scalatest.{FlatSpec, Matchers}\n\n\nclass CNameResolverTest extends FlatSpec with Matchers with NeutrinoTestSupport\n{\n  behavior of \"Resolving Pools by CNAME\"\n  implicit val core = new NeutrinoCore(NeutrinoSettings.Empty)\n\n\n  it should \"replace underlying pools on new BalancerPools use\" in {\n    val settings = ListenerSettings(sourcePorts=Seq(80,443))\n    val pools    = service(settings).pools\n    val request  = this.request(\"/\")\n    val resolver = new CNameResolver\n\n    resolver.cached shouldBe empty\n    resolver.version should be (0)\n\n    // Configure pools once and force a resolve\n    pools.update(virtualPool(\"www\", \"www.ebay.com\", 80), virtualPool(\"api\", \"api.ebay.com\", 443))\n\n    pools().size should be (2)\n    resolver.resolve(pools, request)\n    resolver.version should be (1)\n    resolver.cached.size should be (2)\n    sorted(resolver.cached.map(_._2).toList) should be (sorted(pools().toList))\n\n    // Reconfigure and force a resolve with a new set of backing pools\n    pools.update(virtualPool(\"paypal-1\", \"www.paypal.com\", 8082, Transport.HTTPS))\n    pools().size should be (1)\n    resolver.resolve(pools, request)\n    resolver.version should be (2)\n    resolver.cached.size should be (0)\n    resolver.cached.map(_._2) should not be (pools())\n\n    // Now with a matching port/transport\n    pools.update(virtualPool(\"paypal-1\", \"www.paypal.com\", 443, Transport.HTTP))\n    pools().size should be (1)\n    resolver.resolve(pools, request)\n    resolver.version should be (3)\n    resolver.cached.size should be (1)\n    resolver.cached.map(_._2) should not be (pools())\n  }\n\n\n  it should \"test resolution by host, ignoring transport\" in {\n    val settings = listenerSettings(80)\n    val service  = this.service(settings)\n    val session  = this.session(settings)\n    val requestA = this.request(session, \"http://localhost/\")\n    val requestB = this.request(session, \"http://test.stratus.qa.ebay.com/\")\n    val requestC = this.request(session, \"http://test.qa.ebay.com/\")\n    val requestD = this.request(session, \"http://STRATUS.qa.ebaY.com/\")\n\n    // Ensure our hosts are resolved/not resolved properly\n    requestA.host should be (Some(Host(\"localhost\")))\n    requestB.host should be (Some(Host(\"test.stratus.qa.ebay.com\")))\n    requestC.host should be (Some(Host(\"test.qa.ebay.com\")))\n    requestD.host should be (Some(Host(\"stratus.qa.ebay.com\")))\n\n    val resolver = new CNameResolver\n    val cnames = Seq(CanonicalAddress(\"stratus.qa.ebay.com\", 80), CanonicalAddress(\"stratus.qa.ebay.com\", 443))\n    val pool   = VirtualPool(id=\"cache\", address=cnames)\n    service.pools.update(pool)\n\n    // Attempt some resolutions\n    resolver.resolve(service.pools, requestA) should be (None)\n    resolver.resolve(service.pools, requestB) should be (None)\n    resolver.resolve(service.pools, requestC) should be (None)\n    resolver.resolve(service.pools, requestD) should not be (None)\n  }\n\n\n  it should \"test resolution by host with explicit port, ignoring transport\" in {\n    val settings = listenerSettings(443)  // Unmatched default port\n    val service  = this.service(settings)\n    val session  = this.session()\n    val requestA = this.request(session, \"http://STRATUS.qa.ebaY.com/\")\n    val requestB = this.request(session, \"http://STRATUS.qa.ebaY.com:80/\")\n    val requestC = this.request(session, \"http://STRATUS.qa.ebaY.com:1234/\")\n\n    // Verify our hosts\n    requestA.host should be (Some(Host(\"stratus.qa.ebay.com\")))\n    requestB.host should be (Some(Host(\"stratus.qa.ebay.com\", 80)))\n    requestC.host should be (Some(Host(\"stratus.qa.ebay.com\", 1234)))\n\n    val resolver = new CNameResolver\n    val cnames = Seq(CanonicalAddress(\"stratus.qa.ebay.com\", 443))\n    val pool   = VirtualPool(id=\"cache\", address=cnames)\n    service.update(pool)\n\n    // All should resolve; our session matches the configured addresses, regardless of request-host\n    resolver.resolve(service.pools, requestA) should not be (None)\n    resolver.resolve(service.pools, requestB) should not be (None)\n    resolver.resolve(service.pools, requestC) should not be (None)\n  }\n\n\n  it should \"test implicit resolution by host, ignores transport\" in {\n    val settings  = listenerSettings(80)\n    val service   = this.service(settings)\n    val resolver  = new CNameResolver\n    val pool80    = virtualPool(id=\"cache\", \"stratus.qa.ebay.com\", 80, Transport.HTTP)\n    val pool443   = virtualPool(id=\"cache\", \"stratus.qa.ebay.com\", 443, Transport.HTTPS)\n    service.pools.update(pool80, pool443)\n\n    // HTTP Session setup\n    val sessionHA = this.session(listenerSettings(listenerAddress(port=80,  transport=Transport.HTTP)))\n    val sessionHB = this.session(listenerSettings(listenerAddress(port=80,  transport=Transport.HTTPS)))\n    val sessionSA = this.session(listenerSettings(listenerAddress(port=443, transport=Transport.HTTP)))\n    val sessionSB = this.session(listenerSettings(listenerAddress(port=443, transport=Transport.HTTPS)))\n\n    // Transports should line up\n    resolver.resolve(service.pools, request(sessionHA, \"http://STRATUS.qa.ebaY.com/\")) should not be (None)\n    resolver.resolve(service.pools, request(sessionHB, \"http://STRATUS.qa.ebaY.com/\")) should not be (None)\n    resolver.resolve(service.pools, request(sessionSA, \"http://STRATUS.qa.ebaY.com/\")) should not be (None)\n    resolver.resolve(service.pools, request(sessionSB, \"http://STRATUS.qa.ebaY.com/\")) should not be (None)\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/balancer/CNameWildcardResolverTest.scala",
    "content": "package com.ebay.neutrino.balancer\n\nimport com.ebay.neutrino.{NeutrinoTestSupport, NeutrinoCore}\nimport com.ebay.neutrino.config._\nimport org.scalatest.{FlatSpec, Matchers}\n\n\nclass CNameWildcardResolverTest extends FlatSpec with Matchers with NeutrinoTestSupport\n{\n  behavior of \"Resolving Pools by wildcard CNAME\"\n\n  import com.ebay.neutrino.util.HttpRequestUtils._\n\n  implicit val core = new NeutrinoCore(NeutrinoSettings.Empty)\n\n\n  it should \"replace underlying pools on new BalancerPools use\" in {\n    val request  = this.request(\"/\")\n    val resolver = new CNameWildcardResolver\n\n    resolver.version should be (0)\n    resolver.pools shouldBe empty\n\n    // Force a resolve\n    val pools = neutrinoPools()\n    resolver.resolve(pools, request)\n    resolver.version should be (0)\n    sorted(resolver.pools) should be (sorted(pools().toSeq))\n\n    // Force a resolve with a new set of backing pools\n    val pools2 = neutrinoPools(VirtualPool(id=\"pool2\"))\n    resolver.resolve(pools2, request)\n    resolver.version should be (1)\n    sorted(resolver.pools) should be (sorted(pools2().toSeq))\n  }\n\n\n  it should \"test balancer-pool cache refresh\" in {\n    val requestA = this.request(\"/\")\n    val requestB = this.request(\"http://localhost/\")\n\n    // Ensure our hosts are resolved/not resolved properly\n    requestA.host() should be (None)\n    requestB.host() should be (Some(Host(\"localhost\")))\n\n    val resolver = new CNameWildcardResolver\n    val pools = neutrinoPools()\n    resolver.cache.size() should be (0)\n\n    // Attempt resolve without a host\n    resolver.resolve(pools, requestA)\n    resolver.cache.size() should be (0)\n\n    // Attempt resolve with a host; will add the cache-miss to the cache\n    resolver.resolve(pools, requestB)\n    resolver.cache.size() should be (1)\n    resolver.cache.get(Host(\"localhost\")) should be (None)\n\n    // Resolve a second time; should hit the cache\n    resolver.resolve(pools, requestA)\n    resolver.resolve(pools, requestB)\n    resolver.cache.size() should be (1)\n    resolver.cache.get(Host(\"localhost\")) should be (None)\n\n    // \"Refresh\" with a new backing BalancerPools\n    pools.update()\n    resolver.resolve(pools, requestA)\n    resolver.cache.size() should be (1)\n  }\n\n\n\n  it should \"test cache hit\" in {\n    val requestA = this.request(\"http://localhost/\")\n    val requestB = this.request(\"http://test.stratus.qa.ebay.com/\")\n    val requestC = this.request(\"http://test.qa.ebay.com/\")\n\n    // Ensure our hosts are resolved/not resolved properly\n    requestA.host() should be (Some(Host(\"localhost\")))\n    requestB.host() should be (Some(Host(\"test.stratus.qa.ebay.com\")))\n    requestC.host() should be (Some(Host(\"test.qa.ebay.com\")))\n\n    val resolver = new CNameWildcardResolver\n    val cnames = Seq(CanonicalAddress(\"stratus.qa.ebay.com\"))\n    val pool   = VirtualPool(id=\"cache\", address=cnames)\n    val pools  = neutrinoPools(pool)\n\n    resolver.cache.size() should be (0)\n\n    // Attempt some resolutions\n    resolver.resolve(pools, requestA) should be (None)\n    resolver.resolve(pools, requestB) should not be (None)\n    resolver.resolve(pools, requestC) should be (None)\n    resolver.cache.size() should be (3)\n  }\n}\n\n\n"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/balancer/LeastConnectionTest.scala",
    "content": "package com.ebay.neutrino.balancer\n\nimport com.ebay.neutrino.config.{HealthState, VirtualServer}\nimport com.ebay.neutrino.{NeutrinoCoreSupport, NeutrinoNode}\nimport org.scalatest.{FlatSpec, Matchers}\n\n\nclass LeastConnectionTest extends FlatSpec with Matchers with NeutrinoCoreSupport {\n  behavior of \"Resolving Pools by LeastConnection\"\n\n\n  it should \"simple allocation and de-allocation of one node\" in {\n    val request  = this.request(\"/\")\n    val balancer = new LeastConnectionBalancer\n\n    // Check before nodes are set\n    balancer.assign(request) should be (None)\n    balancer.assign(request) should be (None)\n\n    // Create one node to test against\n    val pool   = neutrinoPool()\n    val server = VirtualServer(\"id\", \"localhost\", 8080)\n    val node   = new NeutrinoNode(pool, server)\n\n    balancer.rebuild(Array(node))\n    balancer.assign(request) should be (Option(node))\n    balancer.assign(request) should be (Option(node))\n\n    // Remove the nodes and ensure none are resolved\n    balancer.rebuild(Array())\n    balancer.assign(request) should be (None)\n    balancer.assign(request) should be (None)\n  }\n\n\n  it should \"alternating allocation on all healthy nodes\" in {\n    val request  = this.request(\"/\")\n    val balancer = new LeastConnectionBalancer\n\n    // Check before nodes are set\n    balancer.assign(request) should be (None)\n\n    // Create one node to test against\n    val pool    = neutrinoPool()\n    val node1   = new NeutrinoNode(pool, VirtualServer(\"id1\", \"localhost\", 8080))\n    val node2   = new NeutrinoNode(pool, VirtualServer(\"id2\", \"localhost\", 8081))\n    balancer.rebuild(Array(node1, node2))\n\n    val assign1 = balancer.assign(request); assign1 should be (Option(node1))\n    val assign2 = balancer.assign(request); assign2 should be (Option(node2))\n    val assign3 = balancer.assign(request); assign3 should be (Option(node1))\n    val assign4 = balancer.assign(request); assign4 should be (Option(node2))\n\n    // Remove the nodes and ensure none are resolved\n    balancer.rebuild(Array())\n    balancer.assign(request) should be (None)\n    balancer.assign(request) should be (None)\n  }\n\n\n  it should \"continue allocation on original node with release\" in {\n    val request  = this.request(\"/\")\n    val balancer = new LeastConnectionBalancer\n\n    // Check before nodes are set\n    balancer.assign(request) should be (None)\n\n    // Create one node to test against\n    val pool    = neutrinoPool()\n    val node1   = new NeutrinoNode(pool, VirtualServer(\"id1\", \"localhost\", 8080))\n    val node2   = new NeutrinoNode(pool, VirtualServer(\"id2\", \"localhost\", 8081))\n    balancer.rebuild(Array(node1, node2))\n\n    val assign1 = balancer.assign(request); assign1 should be (Option(node1)); balancer.release(null, assign1.get)\n    val assign2 = balancer.assign(request); assign2 should be (Option(node1))  // should still allocate 1\n    val assign3 = balancer.assign(request); assign3 should be (Option(node2)); balancer.release(null, assign1.get)\n    val assign4 = balancer.assign(request); assign4 should be (Option(node1))  // should be back at 1\n\n    // Remove the nodes and ensure none are resolved\n    balancer.rebuild(Array())\n    balancer.assign(request) should be (None)\n  }\n\n\n\n  it should \"round-robin allocation on all mixed-health nodes\" in {\n    val request  = this.request(\"/\")\n    val balancer = new LeastConnectionBalancer\n\n    // Check before nodes are set\n    balancer.assign(request) should be (None)\n\n    // Create one node to test against\n    val pool    = neutrinoPool()\n    val node1   = new NeutrinoNode(pool, VirtualServer(\"id1\", \"localhost\", 8081))\n    val node2   = new NeutrinoNode(pool, VirtualServer(\"id2\", \"localhost\", 8082))\n    val node3   = new NeutrinoNode(pool, VirtualServer(\"id3\", \"localhost\", 8083))\n    balancer.rebuild(Array(node1, node2, node3))\n\n    // Switch node 2 to maintenance prior to allocation\n    node2.settings.healthState = HealthState.Maintenance\n    val assign1 = balancer.assign(request); assign1 should be (Option(node1))\n    val assign2 = balancer.assign(request); assign2 should be (Option(node3))\n    val assign3 = balancer.assign(request); assign3 should be (Option(node1))\n    val assign4 = balancer.assign(request); assign4 should be (Option(node3))\n    balancer.release(null, assign1.get)\n    balancer.release(null, assign2.get)\n    balancer.release(null, assign3.get)\n    balancer.release(null, assign4.get)\n\n    // Switch node 1 and 3 off\n    node1.settings.healthState = HealthState.Maintenance\n    node3.settings.healthState = HealthState.Maintenance\n    balancer.assign(request) should be (None)\n    balancer.assign(request) should be (None)\n\n    // Switch node 3 back on\n    node3.settings.healthState = HealthState.Healthy\n    balancer.assign(request) should be (Option(node3))\n    balancer.assign(request) should be (Option(node3))\n\n    // Remove the nodes and ensure none are resolved\n    balancer.rebuild(Array())\n    balancer.assign(request) should be (None)\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/balancer/RoundRobinTest.scala",
    "content": "package com.ebay.neutrino.balancer\n\nimport com.ebay.neutrino.config.{HealthState, VirtualPool, VirtualServer}\nimport com.ebay.neutrino.{NeutrinoNode, NeutrinoCoreSupport, NeutrinoNodes, NeutrinoRequest}\nimport io.netty.handler.codec.http.{DefaultHttpRequest, HttpMethod, HttpVersion}\nimport org.scalatest.{FlatSpec, Matchers}\n\n\nclass RoundRobinTest extends FlatSpec with Matchers with NeutrinoCoreSupport {\n  behavior of \"Resolving Pools by RoundRobin\"\n\n\n  it should \"simple allocation and de-allocation\" in {\n    val request  = this.request(\"/\")\n    val balancer = new RoundRobinBalancer\n\n    // Check before nodes are set\n    balancer.assign(request) should be (None)\n    balancer.assign(request) should be (None)\n\n    // Create one node to test against\n    val pool   = neutrinoPool()\n    val server = VirtualServer(\"id\", \"localhost\", 8080)\n    val node   = new NeutrinoNode(pool, server)\n\n    balancer.rebuild(Array(node))\n    balancer.assign(request) should be (Option(node))\n    balancer.assign(request) should be (Option(node))\n\n    // Remove the nodes and ensure none are resolved\n    balancer.rebuild(Array())\n    balancer.assign(request) should be (None)\n    balancer.assign(request) should be (None)\n  }\n\n\n  it should \"round-robin allocation on all healthy nodes\" in {\n    val request  = this.request(\"/\")\n    val balancer = new RoundRobinBalancer\n\n    // Check before nodes are set\n    balancer.assign(request) should be (None)\n\n    // Create one node to test against\n    val pool    = neutrinoPool()\n    val node1   = new NeutrinoNode(pool, VirtualServer(\"id1\", \"localhost\", 8080))\n    val node2   = new NeutrinoNode(pool, VirtualServer(\"id2\", \"localhost\", 8081))\n\n    balancer.rebuild(Array(node1, node2))\n    balancer.assign(request) should be (Option(node1))\n    balancer.assign(request) should be (Option(node2))\n    balancer.assign(request) should be (Option(node1))\n    balancer.assign(request) should be (Option(node2))\n    balancer.assign(request) should be (Option(node1))\n\n    // Remove the nodes and ensure none are resolved\n    balancer.rebuild(Array())\n    balancer.assign(request) should be (None)\n    balancer.assign(request) should be (None)\n  }\n\n\n  it should \"round-robin allocation on all mixed-health nodes\" in {\n    val request  = this.request(\"/\")\n    val balancer = new RoundRobinBalancer\n\n    // Check before nodes are set\n    balancer.assign(request) should be (None)\n\n    // Create one node to test against\n    val pool    = neutrinoPool()\n    val node1   = new NeutrinoNode(pool, VirtualServer(\"id1\", \"localhost\", 8080))\n    val node2   = new NeutrinoNode(pool, VirtualServer(\"id2\", \"localhost\", 8081))\n\n    balancer.rebuild(Array(node1, node2))\n    balancer.assign(request) should be (Option(node1))\n    balancer.assign(request) should be (Option(node2))\n    balancer.assign(request) should be (Option(node1))\n\n    // Switch node 2 to maintenance prior to allocation\n    node2.settings.healthState = HealthState.Maintenance\n    balancer.assign(request) should be (Option(node1))\n    balancer.assign(request) should be (Option(node1))\n\n    // Switch node 1 to maintenance prior to allocation\n    node1.settings.healthState = HealthState.Maintenance\n    balancer.assign(request) should be (None)\n\n    // Switch node 2 back to OK\n    node2.settings.healthState = HealthState.Healthy\n    balancer.assign(request) should be (Option(node2))\n\n    // Remove the nodes and ensure none are resolved\n    balancer.rebuild(Array())\n    balancer.assign(request) should be (None)\n    balancer.assign(request) should be (None)\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/balancer/WeightedRoundRobinTest.scala",
    "content": "package com.ebay.neutrino.balancer\n\nimport com.ebay.neutrino.config.{HealthState, VirtualPool, VirtualServer}\nimport com.ebay.neutrino.{NeutrinoNode, NeutrinoCoreSupport, NeutrinoNodes, NeutrinoRequest}\nimport org.scalatest.{FlatSpec, Matchers}\n\n\nclass WeightedRoundRobinTest extends FlatSpec with Matchers with NeutrinoCoreSupport {\n  behavior of \"Resolving Pools by RoundRobin\"\n\n\n  it should \"simple allocation and de-allocation\" in {\n    val request = this.request(\"/\")\n    val balancer = new WeightedRoundRobinBalancer\n\n    // Check before nodes are set\n    balancer.assign(request) should be(None)\n    balancer.assign(request) should be(None)\n\n    // Create one node to test against\n    val pool = neutrinoPool()\n    val server = VirtualServer(\"id\", \"localhost\", 8080, Some(4))\n    val node = new NeutrinoNode(pool, server)\n\n    balancer.rebuild(Array(node))\n    balancer.assign(request) should be(Option(node))\n    balancer.assign(request) should be(Option(node))\n    balancer.assign(request) should be(Option(node))\n    balancer.assign(request) should be(Option(node))\n    balancer.assign(request) should be(None)\n\n    // Remove the nodes and ensure none are resolved\n    balancer.rebuild(Array())\n    balancer.assign(request) should be(None)\n    balancer.assign(request) should be(None)\n\n  }\n\n\n  it should \"round-robin allocation on all healthy nodes\" in {\n    val request = this.request(\"/\")\n    val balancer = new WeightedRoundRobinBalancer\n\n    // Check before nodes are set\n    balancer.assign(request) should be(None)\n\n    // Create one node to test against\n    val pool = neutrinoPool()\n    val node1 = new NeutrinoNode(pool, VirtualServer(\"id1\", \"localhost\", 8080, Some(2)))\n    val node2 = new NeutrinoNode(pool, VirtualServer(\"id2\", \"localhost\", 8081, Some(3)))\n\n    balancer.rebuild(Array(node1, node2))\n    balancer.assign(request) should be(Option(node1))\n    balancer.assign(request) should be(Option(node1))\n    balancer.assign(request) should be(Option(node2))\n    balancer.assign(request) should be(Option(node2))\n    balancer.assign(request) should be(Option(node2))\n\n    // Remove the nodes and ensure none are resolved\n    balancer.rebuild(Array())\n    balancer.assign(request) should be(None)\n    balancer.assign(request) should be(None)\n  }\n\n\n  it should \"round-robin allocation on all mixed-health nodes\" in {\n    val request = this.request(\"/\")\n    val balancer = new WeightedRoundRobinBalancer\n\n    // Check before nodes are set\n    balancer.assign(request) should be(None)\n\n    // Create one node to test against\n    val pool = neutrinoPool()\n    val node1 = new NeutrinoNode(pool, VirtualServer(\"id1\", \"localhost\", 8080, Some(2)))\n    val node2 = new NeutrinoNode(pool, VirtualServer(\"id2\", \"localhost\", 8081, Some(3)))\n\n    balancer.rebuild(Array(node1, node2))\n    val assign1 = balancer.assign(request); assign1 should be(Option(node1))\n    val assign2 = balancer.assign(request); assign2 should be(Option(node1))\n    val assign3 = balancer.assign(request); assign3 should be(Option(node2))\n    val assign4 = balancer.assign(request); assign4 should be(Option(node2))\n    val assign5 = balancer.assign(request); assign4 should be(Option(node2))\n\n\n    balancer.release(null, assign1.get)\n    balancer.release(null, assign2.get)\n    balancer.release(null, assign3.get)\n    balancer.release(null, assign4.get)\n    balancer.release(null, assign5.get)\n\n    // Switch node 2 to maintenance prior to allocation\n    node2.settings.healthState = HealthState.Maintenance\n    balancer.assign(request) should be(Option(node1))\n    balancer.assign(request) should be(Option(node1))\n\n    // Switch node 1 to maintenance prior to allocation\n    node1.settings.healthState = HealthState.Maintenance\n    balancer.assign(request) should be(None)\n\n    // Switch node 2 back to OK\n    node2.settings.healthState = HealthState.Healthy\n    balancer.assign(request) should be(Option(node2))\n    balancer.assign(request) should be(Option(node2))\n    balancer.assign(request) should be(Option(node2))\n    balancer.assign(request) should be(None)\n\n    // Remove the nodes and ensure none are resolved\n    balancer.rebuild(Array())\n    balancer.assign(request) should be(None)\n    balancer.assign(request) should be(None)\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/channel/KeepAliveTest.scala",
    "content": "package com.ebay.neutrino.channel\n\nimport akka.actor.ActorSystem\nimport akka.io.IO\nimport akka.pattern.ask\nimport akka.util.Timeout\nimport com.ebay.neutrino.{EchoIntegrationTest, ProxyIntegrationTest}\nimport org.scalatest._\nimport spray.can.Http\nimport spray.http._\n\nimport scala.concurrent.Await\nimport scala.concurrent.duration._\n\n/**\n * Parse unit-test for Neutrino-configuration\n *\n * Note - needs echo-server running. TODO fix this...\n *\n * Notes:\n * - we currently use Apache HttpComponents async-client, which is basically our 3rd choice\n * - tried using HttpUnit but too textual, bad API, and no future/async support\n * - tried using Spray.io; very easy to use but no HTTP 1.0\n * - could have used Netty async but setup difficult and would like to use something else\n *    for compliancy reasons\n */\nclass KeepAliveTest extends FlatSpec with ProxyIntegrationTest with EchoIntegrationTest with Matchers {\n  behavior of \"KeepAlive integration support\"\n\n  // Default requests\n  override val configurationFile = \"proxy-echo-test.conf\"\n\n  implicit val system = ActorSystem()\n  implicit val timeout: Timeout = 15 seconds\n  val Get = HttpRequest(HttpMethods.GET, Uri(\"http://localhost:8080/someurl\"))\n\n\n  def send(request: HttpRequest) = (IO(Http) ? request).mapTo[HttpResponse]\n\n\n  it should \"successfully send a single HTTP 1.1 GET\" in {\n    val request  = Get\n    val response = Await.result(send(request), 2 seconds)\n\n    //response.status should be (StatusCodes.OK)\n    //response.protocol should be (HttpProtocols.`HTTP/1.1`)\n    //response.header[HttpHeaders.Connection] shouldBe a [Some[HttpHeader]]\n    //response.headers find (_ == HttpHeaders.Connection) .hasKeepAlive should be (false)\n  }\n\n  it should \"successfully send a single HTTP 1.1 GET with connection-close\" in {\n    val start    = System.currentTimeMillis\n    val request  = Get.copy(headers = List(HttpHeaders.Connection(\"close\")))\n    val response = Await.result(send(request), 2 seconds)\n\n    response.status should be (StatusCodes.OK)\n    response.protocol should be (HttpProtocols.`HTTP/1.1`)\n    response.header[HttpHeaders.Connection] should be (Some(HttpHeaders.Connection(\"close\")))\n  }\n\n  it should \"successfully send a single HTTP 1.0 GET\" in {\n    val request  = Get.copy(protocol=HttpProtocols.`HTTP/1.0`)\n    val response = Await.result(send(request), 2 seconds)\n\n    // Regardless of Client:1.0, server should support 1.1\n    response.status should be (StatusCodes.OK)\n    response.protocol should be (HttpProtocols.`HTTP/1.1`)\n    // response.headers should not contain keep-alive\n  }\n\n  it should \"successfully send a single HTTP 1.0 GET w. keepalive\" in {\n    val request  = Get.copy(protocol=HttpProtocols.`HTTP/1.0`, headers = List(HttpHeaders.Connection(\"Keep-Alive\")))\n    val response = Await.result(send(request), 2 seconds)\n\n    // Regardless of Client:1.0, server should support 1.1\n    response.status should be (StatusCodes.OK)\n    response.protocol should be (HttpProtocols.`HTTP/1.1`)\n    response.header[HttpHeaders.Connection] shouldBe a [Some[_]]\n    response.header[HttpHeaders.Connection].get.hasKeepAlive should be (true)\n  }\n}\n"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/channel/NeutrinoChannelTest.scala",
    "content": "package com.ebay.neutrino.channel\n\nimport com.ebay.neutrino.NeutrinoCore\nimport com.ebay.neutrino.config.{Configuration, LoadBalancer, NeutrinoSettings}\nimport org.scalatest._\n\n/**\n * Parse unit-test for Neutrino-configuration\n *\n * Note - needs echo-server running. TODO fix this...\n *\n * Notes:\n * - we currently use Apache HttpComponents async-client, which is basically our 3rd choice\n * - tried using HttpUnit but too textual, bad API, and no future/async support\n * - tried using Spray.io; very easy to use but no HTTP 1.0\n * - could have used Netty async but setup difficult and would like to use something else\n *    for compliancy reasons\n */\n@Ignore\nclass NeutrinoChannelTest extends FlatSpec with Matchers {\n  behavior of \"Neutrino Channel initialization\"\n\n  // Create default balancer settings from file.\n  // Override to customize/hard-code settings\n  val cfgfile  = Configuration.load(\"proxy.conf\")\n  val settings = NeutrinoSettings(cfgfile)\n  val config   = LoadBalancer(cfgfile)\n  var balancer = NeutrinoCore(settings, config)\n\n\n  it should \"execute a byte-array\" in {\n    // TODO\n    //val channel = new NeutrinoChannel(balancer)\n    //channel.writeInbound()\n  }\n\n\n  // TODO test for correct number of channelRegistered/channelActive\n\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/channel/NeutrinoPipeline.scala",
    "content": "package com.ebay.neutrino.channel\n\nimport com.ebay.neutrino.handler._\nimport com.ebay.neutrino.handler.pipeline.{NeutrinoHandler, NeutrinoProxyHandler}\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.channel._\nimport io.netty.util.concurrent.EventExecutorGroup\n\n\n/**\n * A delegate implementation of the ChannelPipeline to assist with Neutrino pipeline\n * creation.\n *\n *\n */\nclass NeutrinoPipeline(delegate: ChannelPipeline) extends ProxyChannelPipeline(delegate) with StrictLogging\n{\n  /**\n   * Ensure the handler provided is a Neutrino handler, and wrap if not\n   */\n  def toNeutrino(handler: ChannelHandler): NeutrinoHandler = {\n    logger.info(\"Registering Neutrino handler for {}\", handler.getClass.toString)\n\n    handler match {\n      case h: NeutrinoHandler => h\n      case h => NeutrinoProxyHandler(h)\n    }\n  }\n\n\n  override def addFirst(name: String, handler: ChannelHandler): ChannelPipeline =\n    super.addFirst(name, toNeutrino(handler))\n\n  override def addFirst(group: EventExecutorGroup, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addFirst(group, name, toNeutrino(handler))\n\n  override def addFirst(invoker: ChannelHandlerInvoker, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addFirst(invoker, name, toNeutrino(handler))\n\n  override def addFirst(handlers: ChannelHandler*): ChannelPipeline =\n    delegate.addFirst(handlers map (toNeutrino(_)):_*)\n\n  override def addFirst(group: EventExecutorGroup, handlers: ChannelHandler*): ChannelPipeline =\n    delegate.addFirst(group, handlers map (toNeutrino(_)):_*)\n\n  override def addFirst(invoker: ChannelHandlerInvoker, handlers: ChannelHandler*): ChannelPipeline =\n    delegate.addFirst(invoker, handlers map (toNeutrino(_)):_*)\n\n  override def addBefore(baseName: String, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addBefore(baseName, name, toNeutrino(handler))\n\n  override def addBefore(group: EventExecutorGroup, baseName: String, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addBefore(group, baseName, name, toNeutrino(handler))\n\n  override def addBefore(invoker: ChannelHandlerInvoker, baseName: String, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addBefore(invoker, baseName, name, toNeutrino(handler))\n\n  override def addAfter(baseName: String, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addAfter(baseName, name, toNeutrino(handler))\n\n  override def addAfter(group: EventExecutorGroup, baseName: String, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addAfter(group, baseName, name, toNeutrino(handler))\n\n  override def addAfter(invoker: ChannelHandlerInvoker, baseName: String, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addAfter(invoker, baseName, name, toNeutrino(handler))\n\n  override def addLast(name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addLast(name, toNeutrino(handler))\n\n  override def addLast(group: EventExecutorGroup, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addLast(group, name, toNeutrino(handler))\n\n  override def addLast(invoker: ChannelHandlerInvoker, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addLast(invoker, name, toNeutrino(handler))\n\n  override def addLast(handlers: ChannelHandler*): ChannelPipeline =\n    delegate.addLast(handlers map (toNeutrino(_)):_*)\n\n  override def addLast(group: EventExecutorGroup, handlers: ChannelHandler*): ChannelPipeline =\n    delegate.addLast(group, handlers map (toNeutrino(_)):_*)\n\n  override def addLast(invoker: ChannelHandlerInvoker, handlers: ChannelHandler*): ChannelPipeline =\n    delegate.addLast(invoker, handlers map (toNeutrino(_)):_*)\n\n  override def replace(oldHandler: ChannelHandler, newName: String, newHandler: ChannelHandler): ChannelPipeline =\n    delegate.replace(oldHandler, newName, toNeutrino(newHandler))\n\n  override def replace(oldName: String, newName: String, newHandler: ChannelHandler): ChannelHandler =\n    delegate.replace(oldName, newName, toNeutrino(newHandler))\n\n  override def replace[T <: ChannelHandler](oldHandlerType: Class[T], newName: String, newHandler: ChannelHandler): T =\n    delegate.replace(oldHandlerType, newName, toNeutrino(newHandler))\n\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/channel/NeutrinoServiceTest.scala",
    "content": "package com.ebay.neutrino.channel\n\nimport com.ebay.neutrino.NeutrinoCore\nimport com.ebay.neutrino.config.{Configuration, LoadBalancer, NeutrinoSettings}\nimport org.scalatest._\n\n/**\n * Parse unit-test for Neutrino-configuration\n *\n * Note - needs echo-server running. TODO fix this...\n *\n * Notes:\n * - we currently use Apache HttpComponents async-client, which is basically our 3rd choice\n * - tried using HttpUnit but too textual, bad API, and no future/async support\n * - tried using Spray.io; very easy to use but no HTTP 1.0\n * - could have used Netty async but setup difficult and would like to use something else\n *    for compliancy reasons\n */\n@Ignore\nclass NeutrinoServiceTest extends FlatSpec with Matchers {\n  behavior of \"Neutrino Service initialization\"\n\n  // Create default balancer settings from file.\n  // Override to customize/hard-code settings\n  val cfgfile  = Configuration.load(\"proxy.conf\")\n  val settings = NeutrinoSettings(cfgfile)\n  val config   = LoadBalancer(cfgfile)\n  var balancer = NeutrinoCore(settings, config)\n\n\n  it should \"test update()\" in {\n    // TODO\n  }\n\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/channel/ProxyChannelPipeline.scala",
    "content": "package com.ebay.neutrino.channel\n\nimport java.net.SocketAddress\nimport java.util\nimport java.util.Map.Entry\n\nimport io.netty.channel._\nimport io.netty.util.concurrent.EventExecutorGroup\n\n\nclass ProxyChannelPipeline(delegate: ChannelPipeline) extends Proxy with ChannelPipeline {\n  override def self = delegate\n\n  override def toMap: util.Map[String, ChannelHandler] =\n    delegate.toMap\n\n  override def names(): util.List[String] =\n    delegate.names()\n\n  override def channel(): Channel =\n    delegate.channel()\n\n  override def first(): ChannelHandler =\n    delegate.first()\n\n  override def last(): ChannelHandler =\n    delegate.last()\n\n  override def get(name: String): ChannelHandler =\n    delegate.get(name)\n\n  override def get[T <: ChannelHandler](handlerType: Class[T]): T =\n    delegate.get(handlerType)\n\n  override def iterator(): util.Iterator[Entry[String, ChannelHandler]] =\n    delegate.iterator()\n\n  override def context(handler: ChannelHandler): ChannelHandlerContext =\n    delegate.context(handler)\n\n  override def context(name: String): ChannelHandlerContext =\n    delegate.context(name)\n\n  override def context(handlerType: Class[_ <: ChannelHandler]): ChannelHandlerContext =\n    delegate.context(handlerType)\n\n  override def firstContext(): ChannelHandlerContext =\n    delegate.firstContext()\n\n  override def lastContext(): ChannelHandlerContext =\n    delegate.lastContext()\n\n\n  override def bind(localAddress: SocketAddress): ChannelFuture =\n    delegate.bind(localAddress)\n\n  override def bind(localAddress: SocketAddress, promise: ChannelPromise): ChannelFuture =\n    delegate.bind(localAddress, promise)\n\n  override def connect(remoteAddress: SocketAddress): ChannelFuture =\n    delegate.connect(remoteAddress)\n\n  override def connect(remoteAddress: SocketAddress, localAddress: SocketAddress): ChannelFuture =\n    delegate.connect(remoteAddress, localAddress)\n\n  override def connect(remoteAddress: SocketAddress, promise: ChannelPromise): ChannelFuture =\n    delegate.connect(remoteAddress, promise)\n\n  override def connect(remoteAddress: SocketAddress, localAddress: SocketAddress, promise: ChannelPromise): ChannelFuture =\n    delegate.connect(remoteAddress, localAddress, promise)\n\n  override def disconnect(): ChannelFuture =\n    delegate.disconnect()\n\n  override def disconnect(promise: ChannelPromise): ChannelFuture =\n    delegate.disconnect(promise)\n\n\n  override def read(): ChannelPipeline =\n    delegate.read()\n\n  override def write(msg: scala.Any): ChannelFuture =\n    delegate.write(msg)\n\n  override def write(msg: scala.Any, promise: ChannelPromise): ChannelFuture =\n    delegate.write(msg, promise)\n\n  override def writeAndFlush(msg: scala.Any, promise: ChannelPromise): ChannelFuture =\n    delegate.writeAndFlush(msg, promise)\n\n  override def writeAndFlush(msg: scala.Any): ChannelFuture =\n    delegate.writeAndFlush(msg)\n\n  override def flush(): ChannelPipeline =\n    delegate.flush()\n\n  override def close(): ChannelFuture =\n    delegate.close()\n\n  override def close(promise: ChannelPromise): ChannelFuture =\n    delegate.close(promise)\n\n  override def deregister(): ChannelFuture =\n    delegate.deregister()\n\n  override def deregister(promise: ChannelPromise): ChannelFuture =\n    delegate.deregister(promise)\n\n\n  override def fireChannelRegistered(): ChannelPipeline =\n    delegate.fireChannelRegistered()\n\n  override def fireChannelUnregistered(): ChannelPipeline =\n    delegate.fireChannelUnregistered()\n\n  override def fireChannelActive(): ChannelPipeline =\n    delegate.fireChannelActive()\n\n  override def fireChannelInactive(): ChannelPipeline =\n    delegate.fireChannelInactive()\n\n  override def fireChannelRead(msg: scala.Any): ChannelPipeline =\n    delegate.fireChannelRead(msg)\n\n  override def fireChannelReadComplete(): ChannelPipeline =\n    delegate.fireChannelReadComplete()\n\n  override def fireChannelWritabilityChanged(): ChannelPipeline =\n    delegate.fireChannelWritabilityChanged()\n\n  override def fireUserEventTriggered(event: scala.Any): ChannelPipeline =\n    delegate.fireUserEventTriggered(event)\n\n  override def fireExceptionCaught(cause: Throwable): ChannelPipeline =\n    delegate.fireExceptionCaught(cause)\n\n\n  override def addFirst(name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addFirst(name, handler)\n\n  override def addFirst(group: EventExecutorGroup, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addFirst(group, name, handler)\n\n  override def addFirst(invoker: ChannelHandlerInvoker, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addFirst(invoker, name, handler)\n\n  override def addFirst(handlers: ChannelHandler*): ChannelPipeline =\n    delegate.addFirst(handlers:_*)\n\n  override def addFirst(group: EventExecutorGroup, handlers: ChannelHandler*): ChannelPipeline =\n    delegate.addFirst(group, handlers:_*)\n\n  override def addFirst(invoker: ChannelHandlerInvoker, handlers: ChannelHandler*): ChannelPipeline =\n    delegate.addFirst(invoker, handlers:_*)\n\n  override def addBefore(baseName: String, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addBefore(baseName, name, handler)\n\n  override def addBefore(group: EventExecutorGroup, baseName: String, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addBefore(group, baseName, name, handler)\n\n  override def addBefore(invoker: ChannelHandlerInvoker, baseName: String, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addBefore(invoker, baseName, name, handler)\n\n  override def addAfter(baseName: String, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addAfter(baseName, name, handler)\n\n  override def addAfter(group: EventExecutorGroup, baseName: String, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addAfter(group, baseName, name, handler)\n\n  override def addAfter(invoker: ChannelHandlerInvoker, baseName: String, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addAfter(invoker, baseName, name, handler)\n\n  override def addLast(name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addLast(name, handler)\n\n  override def addLast(group: EventExecutorGroup, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addLast(group, name, handler)\n\n  override def addLast(invoker: ChannelHandlerInvoker, name: String, handler: ChannelHandler): ChannelPipeline =\n    delegate.addLast(invoker, name, handler)\n\n  override def addLast(handlers: ChannelHandler*): ChannelPipeline =\n    delegate.addLast(handlers:_*)\n\n  override def addLast(group: EventExecutorGroup, handlers: ChannelHandler*): ChannelPipeline =\n    delegate.addLast(group, handlers:_*)\n\n  override def addLast(invoker: ChannelHandlerInvoker, handlers: ChannelHandler*): ChannelPipeline =\n    delegate.addLast(invoker, handlers:_*)\n\n  override def replace(oldHandler: ChannelHandler, newName: String, newHandler: ChannelHandler): ChannelPipeline =\n    delegate.replace(oldHandler, newName, newHandler)\n\n  override def replace(oldName: String, newName: String, newHandler: ChannelHandler): ChannelHandler =\n    delegate.replace(oldName, newName, newHandler)\n\n  override def replace[T <: ChannelHandler](oldHandlerType: Class[T], newName: String, newHandler: ChannelHandler): T =\n    delegate.replace(oldHandlerType, newName, newHandler)\n\n  override def remove(handler: ChannelHandler): ChannelPipeline =\n    delegate.remove(handler)\n\n  override def remove(name: String): ChannelHandler =\n    delegate.remove(name)\n\n  override def remove[T <: ChannelHandler](handlerType: Class[T]): T =\n    delegate.remove[T](handlerType)\n\n  override def removeFirst(): ChannelHandler =\n    delegate.removeFirst()\n\n  override def removeLast(): ChannelHandler =\n    delegate.removeLast()\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/config/ConfigurationTest.scala",
    "content": "package com.ebay.neutrino.config\n\nimport java.net.URI\n\nimport org.scalatest.{FlatSpec, Matchers}\n\nimport scala.concurrent.duration._\n\n\n/**\n * Parse unit-test for Neutrino-configuration\n */\nclass ConfigurationTest extends FlatSpec with Matchers {\n  behavior of \"Configuration file loading\"\n\n  // Desired prototype\n  val healthSettings = Option(HealthSettings(\"type\", URI.create(\"/\"), None))\n\n  // Cache the default timeouts from reference.conf\n  val poolTimeouts = TimeoutSettings(Configuration.load() getConfig \"pool.timeout\")\n\n\n  it should \"parse test-env.conf without env\" in {\n    // Just the default\n    val config = Configuration.load(\"test-env.conf\")\n    config shouldNot be(null)\n\n    val settings = LoadBalancer(config)\n    settings shouldNot be(null)\n    settings.pools.size shouldBe 1\n    settings.pools(0) should be (VirtualPool(\"default\", 80, Transport.HTTP, Seq(), Seq(), healthSettings, BalancerSettings.Default, poolTimeouts))\n    //settings.pools(0).addresses shouldBe empty\n  }\n\n  it should \"parse test-env.conf with env overriding value\" in {\n    // Just the default\n    val config = Configuration.load(\"test-env.conf\", \"test\")\n    config shouldNot be(null)\n\n    val settings = LoadBalancer(config)\n    settings shouldNot be(null)\n\n    settings.pools.size shouldBe 1\n    settings.pools(0) should be(VirtualPool(\"default\", 80, Transport.HTTP, Seq(), Seq(), healthSettings, BalancerSettings.Default, poolTimeouts))\n    //settings.pools(0).addresses.size shouldBe 1\n  }\n\n  it should \"parse test-env.conf with env negating value\" in {\n    // Just the default\n    val config = Configuration.load(\"test-env.conf\", \"qa\")\n    config shouldNot be(null)\n\n    val settings = LoadBalancer(config)\n    settings shouldNot be(null)\n    settings.pools shouldBe empty\n    //settings.vips shouldBe empty\n  }\n}\n\n\nclass ConfigurationVirtualAddressTest extends FlatSpec with Matchers {\n  behavior of \"Configuration file virtual-address (VIP) settings\"\n}\n\nclass ConfigurationPoolTest extends FlatSpec with Matchers {\n  behavior of \"Configuration file pool settings\"\n}\n\n\nclass ConfigurationTimeoutTest extends FlatSpec with Matchers {\n  behavior of \"Configuration file timeout settings\"\n  import com.ebay.neutrino.config.Configuration._\n\n\n  it should \"parse default settings (reference.conf)\" in {\n    val timeout = Configuration.load().getConfig(\"timeout\")\n    timeout shouldNot be (null)\n\n    // Check path existance\n    timeout.hasPath(\"session-timeout\") should be (true)\n    timeout.hasPath(\"request-timeout\") should be (true)\n    timeout.hasPath(\"read-idle-timeout\") should be (true)\n    timeout.hasPath(\"write-idle-timeout\") should be (true)\n    timeout.hasPath(\"write-timeout\") should be (true)\n\n    // Check for expected values\n    timeout.getDuration(\"session-timeout\") should be (2 minutes)\n    timeout.getDuration(\"request-timeout\") should be (30 seconds)\n    timeout.getDuration(\"read-idle-timeout\") should be (30 seconds)\n    timeout.getDuration(\"write-idle-timeout\") should be (30 seconds)\n    timeout.getDuration(\"write-timeout\") should be (5 seconds)\n  }\n\n  it should \"parse default settings (reference.conf) of inheriting subtypes\" in {\n    val config= Configuration.load()\n\n    // Check for expected values\n    val timeout = config.getConfig(\"pool.timeout\")\n    timeout.getDuration(\"session-timeout\") should be (2 minutes)\n    timeout.getDuration(\"request-timeout\") should be (30 seconds)\n    timeout.getDuration(\"read-idle-timeout\") should be (30 seconds)\n    timeout.getDuration(\"write-idle-timeout\") should be (30 seconds)\n    timeout.getDuration(\"write-timeout\") should be (5 seconds)\n  }\n\n  /** Currently can only promote from within the configuration resolver\n    *\n  it should \"parse merged settings (proxy.conf) of inheriting defaults\" in {\n    val config = Configuration.load(\"proxy.conf\")\n\n    { // Check for overridden VIP values\n    val timeout = config.getConfigList(\"vips\").get(0).getConfig(\"timeout\")\n      timeout.getDuration(\"channel-timeout\") should be (2 minutes)\n      timeout.getDuration(\"request-timeout\") should be (30 seconds)\n      timeout.getDuration(\"idle-timeout\") should be (2 seconds)\n      timeout.getDuration(\"write-timeout\") should be (0 seconds)\n    }\n\n    { // Check for overridden pool values\n    val timeout = config.getConfigList(\"pools\").get(0).getConfig(\"timeout\")\n      timeout.getDuration(\"channel-timeout\") should be (2 minutes)\n      timeout.getDuration(\"request-timeout\") should be (30 seconds)\n      timeout.getDuration(\"idle-timeout\") should be (2 seconds)\n      timeout.getDuration(\"write-timeout\") should be (0 seconds)\n    }\n  }\n    */\n\n\n  it should \"parse merged settings of inheriting overrides\" in {\n    val config = Configuration.load(\"test-timeout.conf\", \"test-one\")\n\n    { // Check for overridden default\n      val timeout = config.getConfig(\"timeout\")\n      timeout.getDuration(\"session-timeout\") should be (2 minutes)\n      timeout.getDuration(\"request-timeout\") should be (1 minute)   // OVERRIDE\n      timeout.getDuration(\"read-idle-timeout\") should be (30 seconds)\n      timeout.getDuration(\"write-idle-timeout\") should be (30 seconds)\n      timeout.getDuration(\"write-timeout\") should be (5 seconds)\n    }\n\n    /** Currently can only promote from within the configuration resolver\n    { // Check for overridden VIP values\n      val timeout = config.getConfig(\"vip.timeout\")\n      timeout.getDuration(\"channel-timeout\") should be (2 minutes)\n      timeout.getDuration(\"request-timeout\") should be (1 minute) // overridden\n      timeout.getDuration(\"idle-timeout\") should be (2 seconds)\n      timeout.getDuration(\"write-timeout\") should be (0 seconds)\n    }\n\n    { // Check for overridden pool values\n      val timeout = config.getConfig(\"pool.timeout\")\n      timeout.getDuration(\"channel-timeout\") should be (2 minutes)\n      timeout.getDuration(\"request-timeout\") should be (1 minute) // overridden\n      timeout.getDuration(\"idle-timeout\") should be (2 seconds)\n      timeout.getDuration(\"write-timeout\") should be (0 seconds)\n    }\n    */\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/config/ConfigurationUtilsTest.scala",
    "content": "package com.ebay.neutrino.config\n\nimport java.net.URI\n\nimport com.ebay.neutrino.NoResolver\nimport org.scalatest.{FlatSpec, Matchers}\n\n\nclass ConfigurationUtilsSpec extends FlatSpec with Matchers {\n  behavior of \"Configuration file parsing utils\"\n\n  val SimpleConfig = Configuration.load(\"proxy-duplicate.conf\")\n\n\n  it should \"handle empty input\" in {\n    //assert(Set.empty.size === 0)\n  }\n\n  it should \"parse simple VIP configuration\" in {\n\n    val settings = NeutrinoSettings(SimpleConfig)\n    assert(settings.interfaces.size == 2)\n    //assert(balancercfg.host == \"balancer\")\n    //assert(balancercfg.defaultPool == \"default\")\n\n    // Check the VIPs against expected\n    {\n      val vip = settings.interfaces(0)\n      vip.addresses should be (Seq(ListenerAddress(\"0.0.0.0\", 8080, Transport.HTTP)))\n      vip.protocol should be (Transport.HTTP)\n      vip.poolResolvers should be (Seq(NoResolver))    // TODO\n      vip.handlers shouldNot be (empty)\n    }\n    {\n      val vip = settings.interfaces(1)\n      vip.addresses should be (Seq(ListenerAddress(\"0.0.0.0\", 8088, Transport.HTTP)))\n      vip.protocol should be (Transport.HTTP)\n      vip.poolResolvers should be (Seq(NoResolver))\n      vip.handlers should be (empty)\n    }\n  }\n\n\n  it should \"parse simple Pool configuration\" in {\n\n    val balancercfg = LoadBalancer(SimpleConfig)\n    assert(balancercfg.pools.size == 2)\n\n    {\n      val pool = balancercfg.pools(0)\n      assert(pool.servers.size == 0)\n      assert(pool.health == None)\n    }\n\n    {\n      // Check the VIP against expected\n      val pool = balancercfg.pools(1)\n      assert(pool.servers.size == 2)\n      assert(pool.protocol == Transport.HTTP)\n\n      assert(pool.health != None)\n      assert(pool.health.get.monitorType == \"type\")\n      assert(pool.health.get.monitor == None)\n      assert(pool.health.get.path == new URI(\"/\"))\n\n      {\n        val server = pool.servers(0)\n        assert(server.host == \"localhost\")\n        assert(server.port == 8081)\n      }\n      {\n        val server = pool.servers(1)\n        assert(server.host == \"127.0.0.1\")\n        assert(server.port == 8082)\n      }\n    }\n  }\n\n\n  // TODO - VIP w. no default pool\n\n  // TODO - bad URI\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/config/VirtualAddressTest.scala",
    "content": "package com.ebay.neutrino.config\n\nimport org.scalatest.{FlatSpec, Matchers}\n\n\nclass VirtualAddressTest extends FlatSpec with Matchers {\n\n\n  it should \"parse IP and Host Regexes properly\" in {\n\n    // Test some IPs\n    VirtualAddress.isIPValid(\"127.0.0.1\") shouldBe true\n    VirtualAddress.isIPValid(\"montereyjenkins.stratus.qa.ebay.com\") shouldBe false\n\n\n    // Test some hosts\n    VirtualAddress.isHostnameValid(\"127.0.0.1\") shouldBe true   // Technically, 127.0.0.1 is also host-valid\n    VirtualAddress.isHostnameValid(\"montereyjenkins.stratus.qa.ebay.com\") shouldBe true\n\n\n    // Test inverses...\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/config/VirtualServerTest.scala",
    "content": "package com.ebay.neutrino.config\n\nimport org.scalatest.{FlatSpec, Matchers}\n\n\nclass VirtualServerTest extends FlatSpec with Matchers {\n  behavior of \"VirtualServer parsing and construction\"\n\n\n  it should \"parse simple VIP configuration from config\" in {\n\n    val config = LoadBalancer(Configuration.load(\"test_virtualserver.conf\"))\n\n    config.pools.size should be (1)\n\n    {\n      val pool = config.pools(0)\n      pool.id should be (\"default\")\n      pool.protocol should be (Transport.HTTP)\n      pool.servers shouldBe empty\n    }\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/handler/ExampleCloseHandler.scala",
    "content": "package com.ebay.neutrino.handler\n\nimport com.ebay.neutrino.NeutrinoRequest\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.buffer.Unpooled\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.channel.{ChannelFutureListener, ChannelHandlerContext, ChannelInboundHandlerAdapter}\nimport io.netty.handler.codec.http._\nimport io.netty.util.CharsetUtil\n\n/**\n * Simulated handler representing a more complex handler execution that (eventually) we'll\n * want to insert at this stage (both inbound and outbound)\n *\n */\n@Sharable\nclass ExampleCloseHandler extends ChannelInboundHandlerAdapter with StrictLogging {\n\n\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit =\n    msg match {\n      case request: NeutrinoRequest if request.uri.startsWith(\"/close\") =>\n        // Handle a 'close' request\n        val status = HttpResponseStatus.OK\n        val buffer = Unpooled.copiedBuffer(s\"Handling /close request by closing connection.\\r\\n\", CharsetUtil.UTF_8)\n        val response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, buffer)\n\n        // Set the content-type\n        response.headers().set(HttpHeaderNames.CONTENT_TYPE, \"text/plain; charset=UTF-8\")\n\n        // Close the connection as soon as the error message is sent.\n        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE)\n\n\n      case _ =>\n        // Fall through\n        ctx.fireChannelRead(msg)\n    }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/handler/ExampleCustomHandler.scala",
    "content": "package com.ebay.neutrino.handler\n\nimport com.codahale.metrics.annotation.{Metered, Timed}\nimport com.ebay.neutrino.channel.NeutrinoEvent\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.channel._\n\n/**\n * Simulated handler representing a raw Channel Handler.\n *\n * We reimplement only the methods we want to customize.\n */\n@Sharable\nclass ExampleCustomHandler extends ChannelDuplexHandler with StrictLogging {\n\n  /**\n   * Example of intercepting write() methods on outbound.\n   *\n   * Metered annotation: will mark entry of each invocation of a write() and send to absolute metric name\n   */\n  @Metered(name = \"pipeline-requests\", absolute = true)\n  override def write(ctx: ChannelHandlerContext, msg: AnyRef, promise: ChannelPromise): Unit = {\n    ctx.write(msg, promise)\n  }\n\n\n  /**\n   * Intercepting user-events (implemented on inbound-handler)\n   */\n  override def userEventTriggered(ctx: ChannelHandlerContext, evt: AnyRef) = {\n\n    // Some recognized events\n    evt match {\n      case NeutrinoEvent.RequestCreated(conn) => logger.info(\"Received start of request event\")\n      case NeutrinoEvent.ResponseReceived(conn)  => logger.info(\"Received start of response event\")\n      case NeutrinoEvent.ResponseCompleted(conn)  => logger.info(\"Received end of request/response event\")\n      case _ =>\n    }\n\n    ctx.fireUserEventTriggered(evt)\n  }\n\n\n  /**\n   * Intercepting read events, on inbound.\n   *\n   * Timed annotation: will measure elapsed execution time of event and send to (class-based) named metric\n   * Metered annotation: apply a secondary meter with the class-relative name provided\n   */\n  @Timed @Metered(name=\"metered-reader\")\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = {\n    ctx.fireChannelRead(msg)\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/handler/ExamplePipelineHandler.scala",
    "content": "package com.ebay.neutrino.handler\n\nimport com.ebay.neutrino.NeutrinoRequest\nimport com.ebay.neutrino.handler.pipeline.{HttpInboundPipeline, HttpPipelineUtils, NeutrinoInboundPipelineHandler}\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.handler.codec.http.HttpResponseStatus\n\n/**\n * Simulated handler representing a more complex handler execution that (eventually) we'll\n * want to insert at this stage (both inbound and outbound)\n *\n */\n@Sharable\nclass ExamplePipelineHandler extends NeutrinoInboundPipelineHandler with StrictLogging {\n\n  // TODO implement a custom pipeline-event handler\n  // (pipeline completed (success || ex))\n\n  // Pipeline:\n  // 0) TCP / HTTP port selector/multiplexorec\n  //    - should replace pipeline with one or the other\n  // 1) HTTP Header extractor\n  //    - extract first line (uri details)\n  //    - frame around headers with indexes into the byte-buffer\n  //    - collect the headers\n  //    - once headers are collected, transition into transfer handler\n  // 2) Transfer handler\n  //    - pass through additional data as-is (w. optional high-order framing)\n  // 3) Pipeline handler\n  //    - turn off auto-read here if delegating externally\n  // 4) Statistics\n  // 5) Timeout handler\n  // 6) Transit handler\n  //\n  // ALSO - needs bidirectional (response pipeline, response statistics) support eventually\n  //\n  /**\n   * Execute the pipeline functionality, and return a conditional response.\n   *\n   * @param request\n   */\n  override def execute(request: NeutrinoRequest): HttpInboundPipeline.Result = {\n\n    // Log the pipeline call, and remove ourself from the pipeline for future execution\n    logger.info(\"Pipeline inbound processing for HTTP request {}\", request.requestUri)\n\n\n\n    // Example of setting the downstream pool\n    if (request.pool.set(\"default\").isDefined)\n      HttpPipelineUtils.RESULT_CONTINUE\n    else\n      HttpPipelineUtils.reject(HttpResponseStatus.BAD_REQUEST)\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/handler/InstrumentationTest.scala",
    "content": "package com.ebay.neutrino.handler\n\nimport com.codahale.metrics.MetricRegistry\nimport nl.grons.metrics.scala.Timer\nimport org.scalatest.FlatSpec\n\n\n// Can be implemented as base-class of all three versions\nclass InstrumentationSpec extends FlatSpec {\n\n  behavior of \"Instrumentation\"\n\n  it should \"handle simple host header\" in {\n  }\n}\n\n/**\n * Performance testing wrapper around header types.\n */\nobject InstrumentationPerf extends App {\n  import scala.concurrent.duration._\n\n  class Counter {\n    var counter = 0.0\n    def increment() = { counter = counter+1.1 }\n\n    // Do some warmup\n    for (i <- 0 until 10*Limit) { counter = counter + 1 }\n    counter = 0.0\n  }\n\n  val Limit = 10*1000*1000   //10m\n  val counter = new Counter\n\n  val flags = Set(0, 4, 7)\n\n  def time(count: Int = Limit)(f: => Unit): FiniteDuration = {\n    val start = System.nanoTime\n    var i = 0\n    while (i < count) { f; i = i+1 }\n    (System.nanoTime-start) nanos\n  }\n\n  val registry = new MetricRegistry()\n  val timer  = new Timer(registry.timer(\"timer\"))\n\n  /**\n   * From execution:\n   *\n   * Connected to the target VM, address: '127.0.0.1:61850', transport: 'socket'\n   * Lambda wrapper elapsed: 15043000 nanoseconds, 1 nanosecond/op\n   * Disconnected from the target VM, address: '127.0.0.1:61850', transport: 'socket'\n   * Direct elapsed: 12222000 nanoseconds, 1 nanosecond/op\n   *\n   * Calls are too simple to measure properly; processor/JVM are just caching in registers\n   * and inlining\n   */\n  {\n    // test 10M calls to method directly\n    val start = System.nanoTime\n    var i = 0\n    while (i < Limit) { counter.increment(); i = i+1 }\n    val elapsed = (System.nanoTime-start) nanos;\n    println(s\"Direct elapsed: $elapsed, ${elapsed/Limit}/op\")\n  }\n\n  {\n    // test 10M calls to counter directly\n    val metric = new MetricRegistry().counter(\"counter\")\n    val start = System.nanoTime\n    var i = 0\n    while (i < Limit) { metric.inc(); i = i+1 }\n    val elapsed = (System.nanoTime-start) nanos;\n    println(s\"Direct counter elapsed: $elapsed, ${elapsed/Limit}/op\")\n  }\n\n  {\n    // Test 10M calls to method via a function wrapper\n    val elapsed = time(Limit) { counter.increment() }\n    println(s\"Lambda wrapper elapsed: $elapsed, ${elapsed/Limit}/op\")\n  }\n\n  {\n    // Test 10M calls to method via a partial-function wrapper\n    val elapsed = time(Limit) { counter.increment() }\n    println(s\"Lambda wrapper elapsed: $elapsed, ${elapsed/Limit}/op\")\n  }\n\n\n  {\n    // test 10M calls to metrics counter within lamda\n    val metric = new MetricRegistry().counter(\"counter\")\n    val elapsed = time(Limit) { metric.inc(); counter.increment() }\n    println(s\"Lambda counter elapsed: $elapsed, ${elapsed/Limit}/op\")\n  }\n\n  {\n    // test 10M calls to metrics timer\n    val elapsed = time(Limit) { timer.time(counter.increment()) }\n    println(s\"Lambda timer elapsed: $elapsed, ${elapsed/Limit}/op\")\n  }\n\n  {\n    // test 10M calls to metrics historgram with manual timing\n    val histo = new MetricRegistry().histogram(\"histo\")\n    val elapsed = time(Limit) {\n      val start = System.nanoTime\n      counter.increment()\n      histo.update(System.nanoTime-start)\n    }\n    println(s\"Lambda histogram w. manual time elapsed: $elapsed, ${elapsed/Limit}/op\")\n  }\n\n\n  /**\n   * These seem to indicate that there is a memory threshold in the metrics collection\n   * past which operations are computationally more expensive (which would make sense\n   * re: the underlying data structure)\n   *\n   * If > 0.1% of the histogram size is\n   */\n  {\n    // test 10M calls to method using a timing lookup for 30% histo\n    val histo = new MetricRegistry().histogram(\"histo\")\n    val elapsed = time(Limit) {\n      val start = System.nanoTime\n      counter.increment()\n      if (flags.contains((start % 100).toInt)) histo.update(System.nanoTime-start)\n    }\n    println(s\"Lambda flag selection for 3% histogram w. manual time elapsed: $elapsed, ${elapsed/Limit}/op\")\n  }\n  {\n    // test 10M calls to method using a random for 30% histo\n    val histo = new MetricRegistry().histogram(\"histo\")\n    val elapsed = time(Limit) {\n      val start = System.nanoTime\n      counter.increment()\n      if ((start % 100).toInt < 3) histo.update(System.nanoTime-start)\n    }\n    println(s\"Lambda 3% histogram w. manual time elapsed: $elapsed, ${elapsed/Limit}/op\")\n  }\n\n  {\n    // test 10-M calls to method using a timing lookup for 30% histo\n    val histo = new MetricRegistry().histogram(\"histo\")\n    val elapsed = time(Limit) {\n      val start = System.nanoTime\n      counter.increment()\n      if (flags.contains((start % 10000).toInt)) histo.update(System.nanoTime-start)\n    }\n    println(s\"Lambda flag selection for << 1% histogram w. manual time elapsed: $elapsed, ${elapsed/Limit}/op\")\n  }\n  {\n    // test 10M calls to method using a random for 30% histo\n    val histo = new MetricRegistry().histogram(\"histo\")\n    val elapsed = time(Limit) {\n      val start = System.nanoTime\n      counter.increment()\n      if ((start % 10000).toInt < 3) histo.update(System.nanoTime-start)\n    }\n    println(s\"Lambda << 1% histogram w. manual time elapsed: $elapsed, ${elapsed/Limit}/op\")\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/handler/contrib/SandboxClientHandler.scala",
    "content": "package com.ebay.neutrino.handler.contrib\n\nimport com.ebay.neutrino.NeutrinoRequest\nimport com.ebay.neutrino.util.HttpRequestUtils\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.buffer.ByteBufHolder\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.channel._\nimport io.netty.handler.codec.http.HttpRequest\nimport io.netty.util.{Attribute, AttributeKey}\n\n/**\n * Client request/response handler for outbound connections; for use with the Sandbox Pipeline.\n *\n *\n */\n@Sharable\nclass SandboxTop extends ChannelDuplexHandler with StrictLogging {\n  import SandboxSupport._\n\n  // Support for deep-object copy; we want to make a copy of any objects coming through which\n  // are writeable or not idempotic.\n  def clone(msg: AnyRef) = msg match {\n    case bytes: ByteBufHolder => bytes.duplicate()\n    case request: HttpRequest => HttpRequestUtils.copy(request)\n    case msg => msg\n  }\n\n\n  // Register our previous context in the channel-attributes. The outbound contexts are referenced directly.\n  override def channelActive(ctx: ChannelHandlerContext) = {\n    ctx.outbound = ctx\n    ctx.fireChannelActive()\n  }\n\n  override def userEventTriggered(ctx: ChannelHandlerContext, msg: AnyRef): Unit = {\n    ctx.inbound.fireUserEventTriggered(msg)\n    ctx.fireUserEventTriggered(msg)\n  }\n\n  override def channelRead(ctx: ChannelHandlerContext, msg: Object): Unit = {\n    // Split events into two; delegate one down through the bottom and split a secondary through our sandbox\n    ctx.inbound.fireChannelRead(msg)\n    ctx.fireChannelRead(clone(msg))\n  }\n\n  // Swallow outgoing events\n  override def write(ctx: ChannelHandlerContext, msg: scala.AnyRef, promise: ChannelPromise): Unit = {}\n}\n\n\n@Sharable\nclass SandboxBottom extends ChannelDuplexHandler with StrictLogging {\n  import SandboxSupport._\n\n  // Register ourselves in the channel-attributes\n  override def channelActive(ctx: ChannelHandlerContext) = {\n    ctx.inbound = ctx\n    ctx.fireChannelActive()\n    logger.info(\"channelActive: Swallowing\")\n  }\n\n  // Swallow user-messages from within the sandbox\n  override def userEventTriggered(ctx: ChannelHandlerContext, msg: AnyRef): Unit = {\n    logger.info(\"userEventTriggered: Swallowing\")\n  }\n\n  // Dipatch sandbox reads out to the secondary sandbox channel\n  override def channelRead(ctx: ChannelHandlerContext, msg: Object): Unit = {\n    // TODO\n    msg match {\n      case request: HttpRequest => /*println(s\"Connecting T-proxy to ${request.uri()}\")*/\n/*\n        // Clone a balancer-connection\n        val sandbox = ctx.connection.get.copy()\n        establish(ctx, sandbox)\n*/\n\n      case _ =>\n    }\n  }\n\n  // Circumvent the sandbox and write directly to the outbound\n  override def write(ctx: ChannelHandlerContext, msg: scala.AnyRef, promise: ChannelPromise): Unit = {\n    ctx.outbound.write(msg, promise)\n  }\n\n  def establish(ctx: ChannelHandlerContext, connection: NeutrinoRequest) = {\n/*\n    core.resolve(connection) map {\n      case Some(future) =>\n      case None =>\n    }*/\n  }\n}\n\n\nobject SandboxSupport {\n\n  import com.ebay.neutrino.util.AttributeSupport._\n\n  val SandboxOutboundKey = AttributeKey.valueOf[ChannelHandlerContext](\"sandbox-top-ctx\")\n  val SandboxInboundKey  = AttributeKey.valueOf[ChannelHandlerContext](\"sandbox-bottom-ctx\")\n\n\n  implicit class SandboxContextSupport(val self: ChannelHandlerContext) extends AnyVal {\n    def attr[T](key: AttributeKey[T]): Attribute[T] = self.attr(key)\n    def channel = self.channel\n\n    def outbound = self.get(SandboxOutboundKey).get\n    def outbound_=(value: ChannelHandlerContext) = self.set(SandboxOutboundKey, Option(value))\n\n    def inbound = self.get(SandboxInboundKey).get\n    def inbound_=(value: ChannelHandlerContext) = self.set(SandboxInboundKey, Option(value))\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/handler/contrib/SandboxPipeline.scala",
    "content": "package com.ebay.neutrino.handler.contrib\n\nimport com.ebay.neutrino.channel.NeutrinoPipeline\nimport com.ebay.neutrino.handler._\nimport com.ebay.neutrino.handler.pipeline.{NeutrinoHandler, DelegateOutboundAdapter, DelegateInboundAdapter, DelegateHandler}\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.channel._\n\n\n/**\n * Helper object for the sandbox-pipeline; this message is intended to mark messages that\n * should not be dispatched by sandbox handlers\n * @param msg\n */\ncase class SandboxMessage(msg: AnyRef)\n\n\n/**\n * Implementation of Splitter-functionality.\n *\n * SplitterHandler provides a top/bottom sandwich for routing around 'user' pipline handlers that\n * we want to sandbox.\n *\n * Handler-methods we want to hook:\n * - Inbound:  channelRead()\n * - Inbound:  userEventTriggered()\n * - Outbound: write()\n *\n * These methods can't be supported (they don't have a message we can wrap, or the\n * message isn't generic enough); they just pass-through and we rely on the user-pipeline\n * forwarding them properly:\n * - Inbound:  exceptionCaught\n * - read()\n * - flush()\n * - close()\n *\n *\n */\n@Sharable\nclass SandboxBarrier(top: Boolean) extends ChannelDuplexHandler with StrictLogging\n{\n  def bottom = !top\n\n\n  def filter(inbound: Boolean, message: AnyRef)(f: AnyRef => Unit) = {\n    val front = if (inbound) top else bottom\n    val back  = if (inbound) bottom else top\n\n    message match {\n      // Split message into pass-thru and sandbox\n      case msg if front =>\n        f(SandboxMessage(msg))\n        f(msg)\n\n      // Unroll pass-thru events\n      case SandboxMessage(msg) =>\n        require(back, s\"SandboxMessage should not come from side of Sandbox: Inbound=$inbound, Top=$top\")\n        f(msg)\n\n      // Swallow sandboxed (empty) message\n      case msg if back =>\n    }\n  }\n\n\n  // Inbound events\n  //\n  override def userEventTriggered(ctx: ChannelHandlerContext, msg: AnyRef): Unit =\n    filter(true, msg) { ctx.fireUserEventTriggered(_) }\n\n\n  override def channelRead(ctx: ChannelHandlerContext, msg: Object): Unit =\n    filter(true, msg) { ctx.fireChannelRead(_) }\n\n\n  // Outbound events\n  override def write(ctx: ChannelHandlerContext, msg: scala.AnyRef, promise: ChannelPromise): Unit =\n    filter(false, msg) { ctx.write(_) }\n}\n\n\n/**\n * A delegate implementation of the ChannelPipeline to assist with Neutrino pipeline\n * creation.\n *\n * Subclasses are expected to extend/reimplement initPipline(), and to delegate up to\n * us on completion of initialization.\n */\nclass SandboxPipelineInitializer {\n\n  /**\n   * Create a new Neutrino pipeline based on the channel pipeline provided\n   */\n  def create(pipeline: ChannelPipeline): NeutrinoPipeline = new SandboxPipeline(pipeline)\n}\n\n\nclass SandboxPipeline(delegate: ChannelPipeline) extends NeutrinoPipeline(delegate) {\n\n  class Inbound[T <: ChannelInboundHandler](inbound: T)\n    extends DelegateHandler[T](inbound) with NeutrinoHandler with SandboxInboundHandler[T]\n\n  class Outbound[T <: ChannelOutboundHandler](outbound: T)\n    extends DelegateHandler[T](outbound) with NeutrinoHandler with SandboxOutboundHandler[T]\n\n  class Duplex[T <: ChannelInboundHandler with ChannelOutboundHandler](both: T)\n    extends DelegateHandler[T](both) with NeutrinoHandler with SandboxInboundHandler[T] with SandboxOutboundHandler[T]\n\n\n  val top = new SandboxTop()\n  val bottom = new SandboxBottom()\n\n\n  /**\n   * Create a new operational ChannelHandler adapter to provide operational delegation.\n   *\n   * @param handler\n   * @return\n   *\n  override def toNeutrino(handler: ChannelHandler): NeutrinoHandler = {\n    // Re-wrap the neutrino handler in our own sandbox delegate\n    super.toNeutrino(handler) match {\n      case both: ChannelInboundHandler with ChannelOutboundHandler => new Duplex(both)\n      case inbound:  ChannelInboundHandler  => new Inbound(inbound)\n      case outbound: ChannelOutboundHandler => new Outbound(outbound)\n      case other => other\n    }\n  }\n   */\n\n  /**\n   * Initialize the sandbox-pipeline by wrapping the user-pipeline with our custom\n   * sandbox handlers.\n   */\n  def initialize(pipeline: ChannelPipeline): Unit = {\n    // Box our existing pipeline with the sandbox handler sandwich\n    delegate.addFirst(\"sandbox-top\", top)\n    delegate.addLast(\"sandbox-bottom\", bottom)\n  }\n}\n\n\n/**\n * Inbound and outbound sandbox handlers; filter out any sandbox messages and only delegate through\n * 'valid' messages.\n */\ntrait SandboxInboundHandler[T <: ChannelInboundHandler] extends DelegateInboundAdapter[T] {\n\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = msg match {\n    case SandboxMessage(_) => ctx.fireChannelRead(msg)\n    case _ => delegate.channelRead(ctx, msg)\n  }\n\n  override def userEventTriggered(ctx: ChannelHandlerContext, msg: AnyRef): Unit = msg match {\n    case SandboxMessage(_) => ctx.fireUserEventTriggered(msg)\n    case _ => delegate.userEventTriggered(ctx, msg)\n  }\n}\n\ntrait SandboxOutboundHandler[T <: ChannelOutboundHandler] extends DelegateOutboundAdapter[T] {\n\n  override def write(ctx: ChannelHandlerContext, msg: scala.Any, promise: ChannelPromise): Unit = msg match {\n    case SandboxMessage(_) => ctx.write(msg, promise)\n    case _ => delegate.write(ctx, msg, promise)\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/handler/ops/ChannelTimeoutHandlerTest.scala",
    "content": "package com.ebay.neutrino.handler.ops\n\nimport java.nio.channels.ClosedChannelException\n\nimport com.ebay.neutrino.config.TimeoutSettings\nimport com.ebay.neutrino.{NettyClientSupport, NettyServerSupport}\nimport io.netty.channel.{Channel, ChannelHandler, ChannelInboundHandlerAdapter, ChannelInitializer}\nimport io.netty.handler.codec.http._\nimport org.scalatest.{FlatSpec, Matchers}\n\n\n\nclass ChannelTimeoutHandlerTest extends FlatSpec with Matchers with NettyClientSupport with NettyServerSupport\n{\n  behavior of \"Channel timeout handler\"\n\n  import io.netty.handler.codec.http.HttpMethod._\n  import io.netty.handler.codec.http.HttpVersion._\n\n  import scala.concurrent.duration.Duration.Undefined\n  import scala.concurrent.duration._\n\n  // Full request/response initializer\n  class TimeoutInitializer(settings: TimeoutSettings, serverHandler: ChannelHandler) extends ChannelInitializer[Channel] {\n    def initChannel(channel: Channel) = {\n      //Channel.pipeline.addLast(new LoggingHandler(LogLevel.INFO))\n      channel.pipeline.addLast(new ChannelTimeoutHandler(settings))\n      channel.pipeline.addLast(new HttpServerCodec())\n      channel.pipeline.addLast(new HttpObjectAggregator(1000000))\n      channel.pipeline.addLast(serverHandler)\n    }\n  }\n\n  class TimeoutClient extends HttpClient {\n    import com.ebay.neutrino.util.Utilities._\n\n    override def initialzer = new HttpClientInitializer(aggregate) {\n      override def initChannel(channel: Channel) = {\n        super.initChannel(channel)\n\n        // Throw a close-listener on the channel\n        channel.closeFuture.addCloseListener(_ =>\n          if (!response.isCompleted) response.failure(new ClosedChannelException())\n        )\n      }\n    }\n  }\n\n\n  it should \"handle no-timeout settings\" in {\n    /*\n    val settings = TimeoutSettings.Undefined\n    val handler  = new ChannelTimeoutHandler(settings)\n    val server   = startup(handler)\n\n    // These timers won't work I don't think...\n    //val channel = new EmbeddedChannel()\n\n    println(\"Shutting down\")\n    server.channel.close().sync()\n    println(\"Shut down\")\n    */\n  }\n\n\n  it should \"test read-idle timeout on partial request\" in {\n    // Server: Responds \"No Timeout Received\" to FullHttpRequest\n    // Client: Send a partial request (indicate data pending w. content-length)\n    val timeout    = 2 seconds\n    val settings   = TimeoutSettings(timeout, Undefined, Undefined, Undefined, Undefined, Undefined)\n    val handler    = new MessageHandler(\"No timeout received\")\n    val server     = startup(new TimeoutInitializer(settings, handler))\n    val client     = new TimeoutClient()\n\n    try {\n      val request  = new DefaultHttpRequest(HTTP_1_1, POST, \"/\")\n      HttpHeaderUtil.setContentLength(request, 100)\n\n      val connection = client.send(request)\n      connection.ready(3 seconds)\n\n      connection.response.value shouldBe a [Some[_]]\n      connection.response.value.get.isFailure should be (true)\n      connection.response.value.get.failed.get shouldBe a [ClosedChannelException]\n      (connection.elapsed > (timeout)) should be (true)\n      (connection.elapsed < (timeout + 500.millis)) should be (true)\n    }\n    finally {\n      // Clean up the server\n      server.channel.close().sync()\n    }\n  }\n\n\n  it should \"test write-idle timeout on full request\" in {\n    // Server: No response to any requests\n    // Client: Send a partial request (indicate data pending w. content-length)\n    val timeout    = 1500 millis\n    val settings   = TimeoutSettings(Undefined, timeout, Undefined, Undefined, Undefined, Undefined)\n    val handler    = new ChannelInboundHandlerAdapter()\n    val server     = startup(new TimeoutInitializer(settings, handler))\n    val client     = new TimeoutClient()\n\n    try {\n      val request    = new DefaultHttpRequest(HTTP_1_1, GET, \"/\")\n      val connection = client.send(request)\n      connection.ready(3 seconds)\n\n      connection.response.value shouldBe a [Some[_]]\n      connection.response.value.get.isFailure should be (true)\n      connection.response.value.get.failed.get shouldBe a [ClosedChannelException]\n      (connection.elapsed > (timeout)) should be (true)\n      (connection.elapsed < (timeout + 500.millis)) should be (true)\n    }\n    finally {\n      // Clean up the server\n      server.channel.close().sync()\n    }\n  }\n\n\n  it should \"test read-idle timeout in connect only (no request)\" in {\n  }\n\n  it should \"test write-idle timeout in connect only (no request)\" in {\n  }\n\n  it should \"test idle (both read and write specified) on min timeout on partial request\" in {\n  }\n\n  it should \"test write timeout with full request and no response reader (slow reader)\" in {\n  }\n\n  it should \"test request timeout\" in {\n  }\n\n  it should \"test session timeout\" in {\n  }\n}\n\n\n"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/handler/ops/HeaderDiagnosticsHandlerTest.scala",
    "content": "package com.ebay.neutrino.handler.ops\n\nimport io.netty.channel.embedded.EmbeddedChannel\nimport io.netty.handler.codec.http._\nimport org.scalatest.{FlatSpec, Matchers}\n\n\n/**\n * Let's be perfectly clear - this unit-test exists only to bolster the code-coverage\n * of the underlying class.\n */\nclass HeaderDiagnosticsHandlerTest extends FlatSpec with Matchers\n{\n  behavior of \"HeaderDiagnostics handler\"\n\n  // Create a request\n  def request() = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, \"/\")\n  def response() = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK)\n\n\n  it should \"work like a regular passthru handler\" in {\n    val marker  = new InboundMarker /// TODO duplex marker\n    val channel = new EmbeddedChannel(new HeaderDiagnosticsHandler(), marker)\n\n    // Ensure marker is empty (except for the initial registration\n    marker.markerCounts should be (Map(\"channelActive\" -> 1, \"channelRegistered\" -> 1))\n\n    // Run some events through the channel and ensure they all go through\n    channel.pipeline.fireChannelRegistered()\n    channel.pipeline.fireChannelActive()\n    channel.pipeline.fireChannelRead(request())\n    channel.pipeline.fireChannelReadComplete()\n    channel.pipeline.fireChannelWritabilityChanged()\n    channel.pipeline.fireUserEventTriggered(\"userevent\")\n    channel.pipeline.fireChannelInactive()\n    channel.pipeline.fireChannelUnregistered()\n    channel.pipeline.fireExceptionCaught(new RuntimeException())\n    // channel.pipeline.close() TODO\n    channel.pipeline.write(response())\n    // channel.pipeline.flush() TODO\n\n    // Ensure they're all received downstream\n    marker.markerCounts(\"channelRegistered\") should be (2)\n    marker.markerCounts(\"channelActive\") should be (2)\n    marker.markerCounts(\"channelInactive\") should be (1)\n    marker.markerCounts(\"channelUnregistered\") should be (1)\n    marker.markerCounts(\"channelRead\") should be (1)\n    marker.markerCounts(\"channelReadComplete\") should be (1)\n    marker.markerCounts(\"userEventTriggered\") should be (1)\n    marker.markerCounts(\"channelWritabilityChanged\") should be (1)\n    marker.markerCounts(\"exceptionCaught\") should be (1)\n  }\n\n  // Headers only off and on\n\n\n\n\n  it should \"verify output format\" in {\n    val handler = new HeaderDiagnosticsHandler()\n\n    // Zero handlers = Zero string\n    val request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, \"/\")\n    handler.format(request) should be (\"\")\n\n    // Add header\n    request.headers().add(\"HEADER\", \"VALUE\")\n    handler.format(request) should not be (\"\")\n  }\n}\n\n\n"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/handler/ops/MarkerSupport.scala",
    "content": "package com.ebay.neutrino.handler.ops\n\nimport io.netty.channel.{ChannelHandlerContext, ChannelInboundHandlerAdapter}\n\n\n// Not currently needed - pipeline functionality only used in /test\n// Please restore if moved back to /main\nclass InboundMarker extends ChannelInboundHandlerAdapter with Marker {\n\n  override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable): Unit =\n    mark(\"exceptionCaught\") { ctx.fireExceptionCaught(cause) }\n\n  override def channelActive(ctx: ChannelHandlerContext): Unit =\n    mark(\"channelActive\") { ctx.fireChannelActive() }\n\n  override def channelInactive(ctx: ChannelHandlerContext): Unit =\n    mark(\"channelInactive\") { ctx.fireChannelInactive() }\n\n  override def channelUnregistered(ctx: ChannelHandlerContext): Unit =\n    mark(\"channelUnregistered\") { ctx.fireChannelUnregistered() }\n\n  override def channelRead(ctx: ChannelHandlerContext, msg: scala.Any): Unit =\n    mark(\"channelRead\") { ctx.fireChannelRead(msg) }\n\n  override def channelRegistered(ctx: ChannelHandlerContext): Unit =\n    mark(\"channelRegistered\") { ctx.fireChannelRegistered() }\n\n  override def channelReadComplete(ctx: ChannelHandlerContext): Unit =\n    mark(\"channelReadComplete\") { ctx.fireChannelReadComplete() }\n\n  override def userEventTriggered(ctx: ChannelHandlerContext, evt: scala.Any): Unit =\n    mark(\"userEventTriggered\") { ctx.fireUserEventTriggered(evt) }\n\n  override def channelWritabilityChanged(ctx: ChannelHandlerContext): Unit =\n    mark(\"channelWritabilityChanged\") { ctx.fireChannelWritabilityChanged() }\n}\n\n\ntrait Marker {\n  import scala.collection.mutable\n\n  // Data-structure for counting our method invocations\n  var markerCounts: mutable.Map[String, Int] = mutable.Map()\n\n  // Clear mark\n  def clearMark() = markerCounts = mutable.Map.empty\n\n  def isEmpty() = markerCounts.isEmpty\n\n  // Increment mark\n  def mark[T](marker: String)(f: => T ={}) = {\n    markerCounts += marker -> (markerCounts.getOrElse(marker, 0)+1)\n    f\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/handler/ops/NeutrinoAuditHandlerTest.scala",
    "content": "package com.ebay.neutrino.handler.ops\n\nimport com.ebay.neutrino.NeutrinoRequest\nimport io.netty.buffer.{Unpooled, ByteBufUtil, ByteBuf, ByteBufHolder}\nimport io.netty.channel.embedded.EmbeddedChannel\nimport io.netty.handler.codec.http.DefaultHttpContent\nimport org.scalatest.{FlatSpec, Matchers}\n\n\n/**\n * Let's be perfectly clear - this unit-test exists only to bolster the code-coverage\n * of the underlying class.\n */\nclass NeutrinoAuditHandlerTest extends FlatSpec with Matchers\n{\n  behavior of \"Neutrino-audit handler\"\n\n\n/*\n  def request() = new NeutrinoRequest()\n\n\n  it should \"log read events\" in {\n\n    val channel = new EmbeddedChannel(new NeutrinoAuditHandler())\n    val request = new NeutrinoRequest()\n    channel.request =\n\n    //\n\n\n    override def userEventTriggered(ctx: ChannelHandlerContext, event: AnyRef): Unit = {\n      state(ctx) map { state =>\n        event match {\n          case event: NeutrinoEvent => state.add(Event(event))\n          case _ => state.add(UserEvent(event))\n        }\n      }\n      ctx.fireUserEventTriggered(event)\n    }\n  }\n*/\n\n\n  it should \"calculate size correctly for various types\" in {\n    val handler = new NeutrinoAuditHandler()\n\n    handler.calculateSize(null) should be (0)\n    handler.calculateSize(\"\") should be (0)\n\n    // Byte-buffer\n    val buffer = Unpooled.wrappedBuffer(\"some string\".getBytes)\n    handler.calculateSize(buffer) should be (11)\n\n    // Byte-holder\n    val holder = new DefaultHttpContent(Unpooled.wrappedBuffer(\"other\".getBytes))\n    handler.calculateSize(holder) should be (5)\n  }\n}\n\n\n"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/handler/pipeline/DelegateHandler.scala",
    "content": "package com.ebay.neutrino.handler.pipeline\n\nimport java.net.SocketAddress\n\nimport io.netty.channel._\n\n\n/**\n * Default proxy implementations of our common ChannelHandler types.\n * Intended for use with overriding default Netty channel internals implementation.\n *\n * @param delegate\n */\nclass DelegateHandler[T <: ChannelHandler](val delegate: T) extends ChannelHandler with Proxy\n{\n  override def self: T = delegate\n\n  override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable): Unit =\n    delegate.exceptionCaught(ctx, cause)\n\n  override def handlerRemoved(ctx: ChannelHandlerContext): Unit =\n    delegate.handlerRemoved(ctx)\n\n  override def handlerAdded(ctx: ChannelHandlerContext): Unit =\n    delegate.handlerAdded(ctx)\n}\n\n\ntrait DelegateInboundAdapter[T <: ChannelInboundHandler] extends DelegateHandler[T] with ChannelInboundHandler\n{\n  def delegate: T\n\n\n  override def channelRegistered(ctx: ChannelHandlerContext): Unit =\n    delegate.channelRegistered(ctx)\n\n  override def channelUnregistered(ctx: ChannelHandlerContext): Unit =\n    delegate.channelUnregistered(ctx)\n\n  override def channelActive(ctx: ChannelHandlerContext): Unit =\n    delegate.channelActive(ctx)\n\n  override def channelInactive(ctx: ChannelHandlerContext): Unit =\n    delegate.channelInactive(ctx)\n\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit =\n    delegate.channelRead(ctx, msg)\n\n  override def channelWritabilityChanged(ctx: ChannelHandlerContext): Unit =\n    delegate.channelWritabilityChanged(ctx)\n\n  override def userEventTriggered(ctx: ChannelHandlerContext, evt: AnyRef): Unit =\n    delegate.userEventTriggered(ctx, evt)\n\n  override def channelReadComplete(ctx: ChannelHandlerContext): Unit =\n    delegate.channelReadComplete(ctx)\n}\n\n\ntrait DelegateOutboundAdapter[T <: ChannelOutboundHandler] extends DelegateHandler[T] with ChannelOutboundHandler\n{\n  def delegate: T\n\n\n  override def bind(ctx: ChannelHandlerContext, localAddress: SocketAddress, promise: ChannelPromise): Unit =\n    delegate.bind(ctx, localAddress, promise)\n\n  override def disconnect(ctx: ChannelHandlerContext, promise: ChannelPromise): Unit =\n    delegate.disconnect(ctx, promise)\n\n  override def flush(ctx: ChannelHandlerContext): Unit =\n    delegate.flush(ctx)\n\n  override def write(ctx: ChannelHandlerContext, msg: scala.Any, promise: ChannelPromise): Unit =\n    delegate.write(ctx, msg, promise)\n\n  override def close(ctx: ChannelHandlerContext, promise: ChannelPromise): Unit =\n    delegate.close(ctx, promise)\n\n  override def read(ctx: ChannelHandlerContext): Unit =\n    delegate.read(ctx)\n\n  override def deregister(ctx: ChannelHandlerContext, promise: ChannelPromise): Unit =\n    delegate.deregister(ctx, promise)\n\n  override def connect(ctx: ChannelHandlerContext, remoteAddress: SocketAddress, localAddress: SocketAddress, promise: ChannelPromise): Unit =\n    delegate.connect(ctx, remoteAddress, localAddress, promise)\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/handler/pipeline/NeutrinoHandler.scala",
    "content": "package com.ebay.neutrino.handler.pipeline\n\nimport io.netty.channel._\n\n\ntrait NeutrinoHandler extends ChannelHandler\n\n\n/**\n * Create a new operational ChannelHandler adapter to provide operational delegation.\n *\n * Due to the runtime-typecheck requirements of the inbound/outbound handlers,\n * we have to create with the explicit delegate typing.\n *\n * @param delegate\n * @return\n */\n/*\nobject NeutrinoHandler {\n  import io.netty.channel.{ChannelInboundHandler => Inbound, ChannelOutboundHandler => Outbound}\n\n  def apply(delegate: ChannelHandler): NeutrinoHandler =\n    delegate match {\n      case both: Inbound with Outbound => new NeutrinoDuplexHandler(both)\n      case inbound: Inbound            => new NeutrinoInboundHandler(inbound)\n      case outbound: Outbound          => new NeutrinoOutboundHandler(outbound)\n      case other                       => new ChannelHandlerAdapter with NeutrinoHandler\n    }\n}*/\n"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/handler/pipeline/NeutrinoPipelineHandler.scala",
    "content": "package com.ebay.neutrino.handler.pipeline\n\nimport com.codahale.metrics.annotation.Timed\nimport com.ebay.neutrino.NeutrinoRequest\nimport com.ebay.neutrino.handler.pipeline.HttpInboundPipeline.Status\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.channel._\nimport io.netty.handler.codec.http.{HttpRequest, HttpResponse, HttpResponseStatus}\n\n\nabstract class NeutrinoInboundPipelineHandler\n  extends ChannelInboundHandlerAdapter\n  with NeutrinoInboundPipelineTrait\n\nabstract class NeutrinoOutboundPipelineHandler\n  extends ChannelOutboundHandlerAdapter\n  with NeutrinoOutboundPipelineTrait\n\nabstract class NeutrinoPipelineHandler\n  extends ChannelDuplexHandler\n  with NeutrinoInboundPipelineTrait\n  with NeutrinoOutboundPipelineTrait\n\n\n/**\n * Support types for request processing.\n */\nobject NeutrinoInbound {\n\n  sealed trait RequestStatus\n  case class Continue(request: Option[HttpRequest]=None) extends RequestStatus\n  case class Reject(response: Option[HttpResponse]=None, status: HttpResponseStatus=HttpResponseStatus.FORBIDDEN) extends RequestStatus\n}\n\n\n/**\n * Neutrino Inbound/Outbound Handler Base-Class.\n *\n * Provides message support for the following generic use-cases:\n * a) Neutrino-lifecycle events\n * b) Connection-level pipeline events\n * c) Connection-level\n *\n * TODO handle @RequestSharable handlers...?\n */\n\nsealed trait NeutrinoPipelineTrait extends StrictLogging {\n\n  // Overrideable settings\n  def connectionRequired(): Boolean = true\n\n}\n\nsealed trait NeutrinoInboundPipelineTrait extends ChannelInboundHandler with NeutrinoPipelineTrait with HttpInboundPipeline\n{\n  import com.ebay.neutrino.util.AttributeSupport._\n\n\n  // TODO handle addListener(ChannelFutureListener.CLOSE) on error?? Always? Some errors?\n  @Timed\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = {\n    require (connectionRequired || ctx.session.isDefined, \"Session is required for this channel and has not been initialized\")\n\n    msg match {\n      // Valid connection\n      case request: NeutrinoRequest =>\n        // Run the user pipeline\n        val result = execute(request)\n\n        result.status match {\n          case Status.SKIPPED | Status.CONTINUE =>\n            ctx.fireChannelRead(request)\n\n          case Status.COMPLETE | Status.REJECT =>\n            ctx.writeAndFlush(result.response)\n        }\n\n      // Fall through\n      case _ =>\n        ctx.fireChannelRead(msg)\n    }\n  }\n}\n\nsealed trait NeutrinoOutboundPipelineTrait extends ChannelOutboundHandler with NeutrinoPipelineTrait\n{\n  import com.ebay.neutrino.util.AttributeSupport._\n\n\n  /**\n   * Enable this to intercept outbound pipeline events as well.\n   */\n  override def write(ctx: ChannelHandlerContext, msg: AnyRef, promise: ChannelPromise) = {\n    require (connectionRequired || ctx.session.isDefined, \"Session is required for this channel and has not been initialized\")\n\n    logger.info(\"Pipeline outbound processing {}\", msg)\n\n    (ctx.request, msg) match {\n      case (Some(request), response: HttpResponse) =>\n        process(request, response)\n        ctx.write(msg, promise)\n\n      case _ =>\n        ctx.write(msg, promise)\n    }\n  }\n\n  // Required handler methods\n  def process(connection: NeutrinoRequest, response: HttpResponse): Unit = {}\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/handler/pipeline/NeutrinoProxyHandler.scala",
    "content": "package com.ebay.neutrino.handler.pipeline\n\nimport java.net.SocketAddress\n\nimport com.codahale.metrics.MetricRegistry\nimport com.codahale.metrics.annotation.{Metered, Timed}\nimport com.ebay.neutrino.handler.MetricsAnnotationRegistry\nimport com.ebay.neutrino.metrics.Metrics\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.channel._\nimport nl.grons.metrics.scala.{InstrumentedBuilder, Meter, Timer}\n\n\n/**\n * Default proxy implementations of our common ChannelHandler types.\n * Intended for use with overriding default Netty channel internals implementation.\n *\n * @param delegate\n */\nclass NeutrinoProxyHandler[T <: ChannelHandler](delegate: T) extends DelegateHandler(delegate) with NeutrinoHandler\n\n\nfinal class NeutrinoInboundProxyHandler[T <: ChannelInboundHandler](self: T)(implicit val metricRegistry: MetricRegistry)\n  extends NeutrinoProxyHandler[T](self)\n  with NeutrinoInboundProxyAdapter[T]\n  with NeutrinoHandler\n\n\nfinal class NeutrinoOutboundProxyHandler[T <: ChannelOutboundHandler](self: T)(implicit val metricRegistry: MetricRegistry)\n  extends NeutrinoProxyHandler[T](self)\n  with NeutrinoOutboundProxyAdapter[T]\n  with NeutrinoHandler\n\n\nfinal class NeutrinoDuplexProxyHandler[T <: ChannelInboundHandler with ChannelOutboundHandler](self: T)(implicit val metricRegistry: MetricRegistry)\n  extends NeutrinoProxyHandler[T](self)\n  with NeutrinoInboundProxyAdapter[T]\n  with NeutrinoOutboundProxyAdapter[T]\n  with NeutrinoHandler\n\n\nobject NeutrinoProxyHandler {\n  import io.netty.channel.{ChannelInboundHandler => Inbound, ChannelOutboundHandler => Outbound}\n\n  implicit val registry = Metrics.metricRegistry\n  val annotationRegistry = new MetricsAnnotationRegistry\n\n  /**\n   * Create a new operational ChannelHandler adapter to provide operational delegation.\n   *\n   * Due to the runtime-typecheck requirements of the inbound/outbound handlers,\n   * we have to create with the explicit delegate typing.\n   *\n   * @param delegate\n   * @return\n   */\n  def apply(delegate: ChannelHandler): NeutrinoHandler =\n    delegate match {\n      case both: Inbound with Outbound => new NeutrinoDuplexProxyHandler(both)\n      case inbound: Inbound            => new NeutrinoInboundProxyHandler(inbound)\n      case outbound: Outbound          => new NeutrinoOutboundProxyHandler(outbound)\n      case other                       => new ChannelHandlerAdapter with NeutrinoHandler\n    }\n}\n\n\nsealed trait NeutrinoInboundProxyAdapter[T <: ChannelInboundHandler]\n  extends DelegateInboundAdapter[T]\n  with NeutrinoInstrumentation[T]\n  with NeutrinoHandler\n{\n  // Determine the instrumentation and cache the function pointers\n  val fnChannelRegistered   = instrument(\"channelRegistered\")\n  val fnChannelUnregistered = instrument(\"channelUnregistered\")\n  val fnChannelActive       = instrument(\"channelActive\")\n  val fnChannelInactive     = instrument(\"channelInactive\")\n  val fnChannelRead         = instrument(\"channelRead\")\n  val fnUserEventTriggered  = instrument(\"userEventTriggered\")\n\n\n  override def channelRegistered(ctx: ChannelHandlerContext): Unit = {\n    fnChannelRegistered { delegate.channelRegistered(ctx) }\n  }\n\n  override def channelUnregistered(ctx: ChannelHandlerContext): Unit =\n    fnChannelUnregistered { delegate.channelUnregistered(ctx) }\n\n  override def channelActive(ctx: ChannelHandlerContext): Unit =\n    fnChannelActive { delegate.channelActive(ctx) }\n\n  override def channelInactive(ctx: ChannelHandlerContext): Unit =\n    fnChannelInactive { delegate.channelInactive(ctx) }\n\n  // TODO - JIRA annotation\n  // TODO - p4 PoC on compiler macro\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit =\n    fnChannelRead { delegate.channelRead(ctx, msg) }\n\n  override def userEventTriggered(ctx: ChannelHandlerContext, evt: AnyRef): Unit =\n    fnUserEventTriggered { delegate.userEventTriggered(ctx, evt) }\n}\n\n\nsealed trait NeutrinoOutboundProxyAdapter[T <: ChannelOutboundHandler]\n  extends DelegateOutboundAdapter[T]\n  with NeutrinoInstrumentation[T]\n  with NeutrinoHandler\n{\n  // Determine the instrumentation and cache the function pointers\n  val fnWrite     = instrument(\"write\")\n  val fnConnect   = instrument(\"connect\")\n\n\n  override def write(ctx: ChannelHandlerContext, msg: scala.Any, promise: ChannelPromise): Unit =\n    fnWrite { delegate.write(ctx, msg, promise) }\n\n  override def connect(ctx: ChannelHandlerContext, remoteAddress: SocketAddress, localAddress: SocketAddress, promise: ChannelPromise): Unit =\n    fnConnect { delegate.connect(ctx, remoteAddress, localAddress, promise) }\n}\n\n\nsealed trait NeutrinoInstrumentation[T] extends InstrumentedBuilder with StrictLogging {\n  import InstrumentationSupport._\n\n  type Wrapper = (Unit => Unit)\n\n  def delegate: T\n\n\n  /** These are simpler wrapper functions but recreate the metric object on each invocation.\n   *  Ideally, we just want to create once and reuse the handle.\n   *\n  // Wrap a metering/counting metric around the function provided\n  def meter[T](metricname: String)(f: => T): T =\n    new Meter(metrics.registry.meter(metricname)).metered { f }\n\n  // Wrap a timing/stopwatch metric around the function provided\n  def time[T](metricname: String)(f: => T): T =\n    new Timer(metrics.registry.timer(metricname)).time { f }\n  */\n\n\n  // Wrap a metering/counting metric around the function provided\n  def wrapMeter(metricname: String): Wrapper = {\n    val metric = new Meter(metrics.registry.meter(metricname))\n    (f: Unit) => metric.metered { f }\n  }\n\n  // Wrap a timing/stopwatch metric around the function provided\n  def wrapTimer(metricname: String): Wrapper = {\n    val metric = new Timer(metrics.registry.timer(metricname))\n    (f: Unit) => metric.time { f }\n  }\n\n\n  def instrument(methodName: String): Wrapper = {\n    val clazz = delegate.getClass\n    val annotations = NeutrinoProxyHandler.annotationRegistry.getAnnotations(clazz, methodName)\n\n    def annotationName(defaultName: String, absolute: Boolean) =\n      defaultName match {\n        case null | \"\" if absolute => methodName\n        case null | \"\"             => MetricRegistry.name(clazz, methodName)\n        case name if absolute      => name\n        case name                  => MetricRegistry.name(clazz, name)\n      }\n\n    annotations.foldLeft((f: Unit) => f) { (fn, annotation) =>\n      logger.info(\"Annotating metrics for {}::{} of type {}\", clazz, methodName, annotation)\n\n      annotation match {\n        case ann: Timed   => wrapTimer(annotationName(ann.name, ann.absolute))\n        case ann: Metered => wrapMeter(annotationName(ann.name, ann.absolute))\n        case ann          => logger.warn(\"Unsupported annotation {}\", ann); fn\n      }\n    }\n  }\n}\n\n\nobject InstrumentationSupport {\n\n  implicit class MeterSupport(val self: Meter) extends AnyVal {\n    def metered[A](f: => A): A = {\n      self.mark()\n      f\n    }\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/health/HealthMonitorTest.scala",
    "content": "package com.ebay.neutrino.health\n\nimport com.ebay.neutrino.NeutrinoCore\nimport com.ebay.neutrino.config._\nimport com.ebay.neutrino.metrics.HealthMonitor\nimport com.typesafe.config.Config\n\n\n/**\n * To reuse an endpoint/service? Or just create/wrap our own...\n * @param settings\n */\nclass Status200Monitor(config: Config) extends HealthMonitor\n{\n  // Wrap in a simple Netty Connection\n  def check(server: VirtualServer)(implicit balancer: NeutrinoCore) = {\n    // Make dedicated connection\n    /** TODO\n    val address = server.socketAddress\n    val client = new HttpEndpointClient(None)\n     */\n  }\n}\n\n\nobject HealthMonitorSpec {\n\n  case class TestHealthMonitorA() extends HealthMonitor {}\n  case class TestHealthMonitorB(settings: HealthSettings) extends HealthMonitor {}\n  case class TestHealthMonitorC(settings: String) extends HealthMonitor {}\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/integ/HttpServiceTest.scala",
    "content": "package com.ebay.neutrino.integ\n\nimport com.ebay.neutrino.NeutrinoCoreRunning\nimport com.ebay.neutrino.config.{TimeoutSettings, Configuration}\nimport com.typesafe.config.Config\nimport org.scalatest._\nimport spray.http.{HttpMethods, HttpRequest, StatusCodes, Uri}\n\nimport scala.concurrent.Await\nimport scala.concurrent.duration._\n\n/**\n * Regression test for HttpRequests.\n */\nclass HttpServiceTest extends FlatSpec with NeutrinoCoreRunning with SprayRequestSupport with Matchers {\n  behavior of \"Http Service Test initialization\"\n\n  // Override the default configuration\n  override implicit val config: Config = Configuration.load(\"http-service.conf\")\n\n  // Reduced connection-timeout for testing pool connections\n  val poolTimeout = TimeoutSettings.Default.copy(connectionTimeout = 1 second)\n\n\n  it should \"get a 504 repsonse to an pool with bad downstream member-info\" in {\n    // Configure pool for a non-mapped server\n    val badport = 1\n    val server  = virtualServer(\"localhost\", badport)\n    val pool    = virtualPool(\"default\", server).copy(timeouts = poolTimeout)\n\n    // Update the topology\n    core.services.head.update(pool)\n    core.services.head.pools.toSeq should be (Seq(pool))\n\n    // Send a simple request and wait briefly for a repsonse\n    val get = HttpRequest(HttpMethods.GET, Uri(\"http://localhost:8080/\"))\n    val response = Await.result(send(get), 3 seconds)\n\n    response.status should be (StatusCodes.GatewayTimeout)\n    response.entity.asString should include (\"Error 504 Gateway Timeout\")\n  }\n\n\n  it should \"get a 503 repsonse to an pool with no available members\" in {\n    // Configure pool with a non-resolved name (ie: not 'default')\n    val pool = virtualPool(\"default\").copy(timeouts = poolTimeout)\n\n    // Update the topology\n    core.services.head.update(pool)\n\n    // Send a simple request and wait briefly for a repsonse\n    val get = HttpRequest(HttpMethods.GET, Uri(\"http://localhost:8080/\"))\n    val response = Await.result(send(get), 5 seconds)\n\n    response.status should be (StatusCodes.ServiceUnavailable)\n    response.entity.asString should include (\"Unable to connect to\")\n  }\n\n  // it should \"get a 503 repsonse to an invalid pool\" in {\n\n\n  // Also test with existing connection but with bad health\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/integ/NettyEchoServer.scala",
    "content": "package com.ebay.neutrino.integ\n\nimport java.net.InetAddress\n\nimport com.ebay.neutrino.integ.NettyEchoServer._\nimport io.netty.bootstrap.ServerBootstrap\nimport io.netty.buffer.{ByteBuf, Unpooled}\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.channel._\nimport io.netty.channel.nio.NioEventLoopGroup\nimport io.netty.channel.socket.nio.NioServerSocketChannel\nimport io.netty.handler.codec.http._\nimport io.netty.util.{CharsetUtil, ReferenceCountUtil}\n\n/**\n * Implementation of a simple HTTP echo server using Netty\n */\nclass NettyEchoServer(host: InetAddress=InetAddress.getLocalHost(), port: Int=DefaultPort) {\n\n  val supervisor = new NioEventLoopGroup(1)\n  val workers    = new NioEventLoopGroup()\n  val bootstrap  = new ServerBootstrap()\n    .channel(classOf[NioServerSocketChannel])\n    .group(supervisor, workers)\n    .childHandler(new NettyEchoInitializer())\n    .childOption[java.lang.Boolean](ChannelOption.TCP_NODELAY, true)\n\n  private var channel: Channel = _\n\n  def start() = {\n    if (channel == null) channel = bootstrap.bind(host, port).sync().channel()\n    channel.closeFuture\n  }\n\n  def shutdown() =\n    if (channel != null) {\n      channel.close\n      channel.closeFuture.sync\n      channel = null\n    }\n}\n\nobject NettyEchoServer {\n\n  val DefaultPort = 8081\n}\n\n\n@Sharable\nclass NettyEchoInitializer extends ChannelInboundHandlerAdapter {\n  import io.netty.handler.codec.http.HttpHeaderNames._\n  import io.netty.handler.codec.http.HttpVersion._\n\n  // TODO cache the underying HTTP-raw-bytebuf\n  val echoBuffer = Unpooled.copiedBuffer(s\"Echo!\", CharsetUtil.UTF_8)\n\n\n  def createResponse(content: ByteBuf, keepalive: Boolean): FullHttpResponse = {\n    val response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK, content)\n    val headers = response.headers\n\n    // Set appropriate headers\n    headers.set(CONTENT_TYPE, \"text/plain; charset=UTF-8\")\n    HttpHeaderUtil.setContentLength(response, content.readableBytes())\n    HttpHeaderUtil.setKeepAlive(response, keepalive)\n\n    response\n  }\n\n\n  override def channelRegistered(ctx: ChannelHandlerContext) = {\n    // Inject an HTTP handler upstream\n    ctx.pipeline.addFirst(new HttpServerCodec(), new HttpObjectAggregator(2*1000*1000))\n  }\n\n\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef) = {\n    msg match {\n      case request: HttpRequest =>\n        // Build an echo response\n        val keepalive = HttpHeaderUtil.isKeepAlive(request)\n        val content   = echoBuffer.duplicate().retain()\n        val response  = createResponse(content, keepalive)\n        val future    = ctx.writeAndFlush(response)\n\n        // If not keepalive, close the connection as soon as the error message is sent.\n        if (!keepalive) future.addListener(ChannelFutureListener.CLOSE)\n\n      case _ =>\n        ReferenceCountUtil.release(msg)\n        ctx.close()\n    }\n\n    // Release the request\n    ReferenceCountUtil.release(msg)\n  }\n\n\n  override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) = {}\n}\n\n\n/**\n * TODO start programmatically\n */\nobject NettyEchoServerApp extends App {\n\n  //ResourceLeakDetector.setLevel(Level.PARANOID)\n\n  val server = new NettyEchoServer()\n  server.start().sync()\n}\n"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/integ/ProxyIntegrationTest.scala",
    "content": "package com.ebay.neutrino.integ\n\nimport com.ebay.neutrino.config.{Configuration, LoadBalancer, VirtualPool, VirtualServer}\nimport com.ebay.neutrino.handler.{ExampleCloseHandler, ExamplePipelineHandler}\nimport com.ebay.neutrino.{NettyClientSupport, NeutrinoCore}\nimport io.netty.channel.{Channel, ChannelInitializer}\nimport io.netty.handler.codec.http.{DefaultFullHttpRequest, HttpMethod, HttpVersion}\nimport org.scalatest.{BeforeAndAfterAll, FlatSpec, Ignore, Matchers}\n\nimport scala.concurrent.Await\nimport scala.concurrent.duration._\n\n\n/**\n * Simple proxy test to a local integration server.\n * (also in netty)\n */\n@Ignore\nclass ProxyIntegrationTest extends FlatSpec with NettyClientSupport with Matchers with BeforeAndAfterAll\n{\n  // Create a new balancer\n  val config = Configuration.load(\"proxy.conf\")\n  val core   = NeutrinoCore(config)\n  val server = new NettyEchoServer()\n\n\n  override def beforeAll() = {\n    val servers = Seq(VirtualServer(\"id\", \"localhost\", 8081))\n    val pools   = Seq(VirtualPool(servers=servers))\n\n    // Start running the downstream server\n    server.start()\n\n    // Start running the proxy. This will run until the process is interrupted...\n    core.configure(LoadBalancer(\"id\", pools))\n    Await.ready(core.start(), 5 seconds)\n  }\n\n  override def afterAll() = {\n    Await.ready(core.shutdown(), 5 seconds)\n    server.shutdown()\n  }\n\n\n  it should \"run 10000 requests\" in {\n\n    // We'll have to connect as well\n    val client = HttpClient(port=8080)\n    val request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, \"/\")\n\n    for (i <- 0 until 10000) {\n      val conn = client.send(request)\n      conn.channel.close()\n    }\n  }\n\n}\n\n\n\nclass ProxyIntegrationInitializer extends ChannelInitializer[Channel] {\n\n  // Initialize the user-configurable pipeline\n  protected def initChannel(ch: Channel): Unit = {\n    ch.pipeline.addLast(new ExampleCloseHandler())\n    ch.pipeline.addLast(new ExamplePipelineHandler())\n    //pipeline.addLast(new ExampleCustomHandler())\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/integ/SprayRequestSupport.scala",
    "content": "package com.ebay.neutrino.integ\n\nimport akka.actor.ActorSystem\nimport akka.io.IO\nimport akka.util.Timeout\nimport spray.can.Http\nimport spray.http.{HttpRequest, HttpResponse}\n\n/**\n * Support mixin for ActorSystem/Spray functionality.\n */\ntrait SprayRequestSupport {\n\n  import akka.pattern.ask\n  import scala.concurrent.duration._\n\n  implicit val system = ActorSystem()\n  implicit val timeout: Timeout = 15 seconds\n\n  // Send the request provided and map back to a response\n  def send(request: HttpRequest) = (IO(Http) ? request).mapTo[HttpResponse]\n\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/metrics/MetricsAnnotationRegistryTest.scala",
    "content": "package com.ebay.neutrino.health\n\nimport com.codahale.metrics.annotation.{Gauge, Metered, Timed}\nimport com.ebay.neutrino.handler.MetricsAnnotationRegistry\nimport io.netty.channel.{ChannelHandlerContext, ChannelInboundHandler}\nimport org.scalatest.{FlatSpec, Matchers}\n\n\nclass MetricsAnnotationRegistryTest extends FlatSpec with Matchers {\n  behavior of \"Parsing Metrics Annotations\"\n\n\n  it should \"skip class with no annotations\" in {\n    abstract class Test extends ChannelInboundHandler {\n      override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef) {}\n    }\n\n    // No annotations for the inbound-methods\n    val registry = new MetricsAnnotationRegistry\n    registry.getAnnotations(classOf[Test], \"channelRegistered\") shouldBe empty\n    registry.getAnnotations(classOf[Test], \"channelRead\") shouldBe empty\n\n    // Does not implement outbound interface; should fail trying to resolve\n    a [NoSuchMethodException] should be thrownBy registry.getAnnotations(classOf[Test], \"write\")\n  }\n\n  it should \"find only marked annotation\" in {\n    abstract class Test extends ChannelInboundHandler {\n      @Timed @Gauge @Metered\n      override def channelRegistered(ctx: ChannelHandlerContext) {}\n      @Timed\n      override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef) {}\n    }\n\n    // No annotations for the inbound-methods\n    val registry = new MetricsAnnotationRegistry\n    registry.getAnnotations(classOf[Test], \"channelRegistered\").size shouldBe 3\n    registry.getAnnotations(classOf[Test], \"channelRegistered\")(0) shouldBe a [Timed]\n    registry.getAnnotations(classOf[Test], \"channelRegistered\")(1) shouldBe a [Gauge]\n    registry.getAnnotations(classOf[Test], \"channelRegistered\")(2) shouldBe a [Metered]\n    registry.getAnnotations(classOf[Test], \"channelRead\").size shouldBe 1\n    registry.getAnnotations(classOf[Test], \"channelRead\")(0) shouldBe a [Timed]\n  }\n\n  it should \"skip unrelated annotations\" in {\n    abstract class Test extends ChannelInboundHandler {\n      override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef) {}\n    }\n\n    val registry = new MetricsAnnotationRegistry\n    registry.getAnnotations(classOf[Test], \"channelRead\") shouldBe empty\n  }\n\n  // registry cachings\n  it should \"cache subsequent invocations\" in {\n    //fail(\"Not implemented yet...\")\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/pipeline/HttpRequestUtilsTest.scala",
    "content": "package com.ebay.neutrino.config\n\nimport com.ebay.neutrino.util.HttpRequestUtils\nimport io.netty.handler.codec.http._\nimport org.scalatest.FlatSpec\n\n\nclass HttpRequestSupportTest extends FlatSpec {\n\n  val ver = HttpVersion.HTTP_1_1\n  val get = HttpMethod.GET\n\n\n  it should \"URI() should parse a valid request\" in {\n    import HttpRequestUtils.HttpRequestSupport\n\n    // Check relative URI only\n    { val uri = new DefaultHttpRequest(ver, get, \"/\").URI()\n      assert(!uri.isAbsolute)\n      assert(uri.getHost == null)\n      assert(uri.getPort == -1)\n    }\n\n    // Check absolute URI only\n    { val uri = new DefaultHttpRequest(ver, get, \"http://localhost\").URI()\n      assert(uri.isAbsolute)\n      assert(uri.getHost == \"localhost\")\n      assert(uri.getPort == -1)\n    }\n\n    // Check absolute URI and port only\n    { val uri = new DefaultHttpRequest(ver, get, \"http://localhost:2344\").URI()\n      assert(uri.isAbsolute)\n      assert(uri.getHost == \"localhost\")\n      assert(uri.getPort == 2344)\n    }\n\n    // Check absolute URI with port\n    { val uri = new DefaultHttpRequest(ver, get, \"http://localhost:2344/some/uri\").URI()\n      assert(uri.isAbsolute)\n      assert(uri.getHost == \"localhost\")\n      assert(uri.getPort == 2344)\n      assert(uri.getPath == \"/some/uri\")\n    }\n\n    // Check relative URI\n    { val uri = new DefaultHttpRequest(ver, get, \"/some/uri\").URI()\n      assert(!uri.isAbsolute)\n      assert(uri.getHost == null)\n      assert(uri.getPort == -1)\n      assert(uri.getPath == \"/some/uri\")\n    }\n\n    // Check fmt errors\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/ssl/PacketProxy.scala",
    "content": "package com.ebay.neutrino\n\nimport java.net.{InetAddress, InetSocketAddress, SocketAddress}\nimport com.ebay.neutrino.util.Utilities\n\nimport scala.concurrent.Future\nimport scala.util.{Failure, Success}\n\nimport com.typesafe.scalalogging.slf4j.StrictLogging\nimport io.netty.bootstrap.{Bootstrap, ServerBootstrap}\nimport io.netty.channel.ChannelHandler.Sharable\nimport io.netty.channel._\nimport io.netty.channel.nio.NioEventLoopGroup\nimport io.netty.channel.socket.nio.{NioServerSocketChannel, NioSocketChannel}\nimport io.netty.util.AttributeKey\n\n\n/**\n * Create a simulated Layer-4 Proxy application; we'll use SSL to test an\n * end-to-end.\n *\n *\n * I used the following to test:\n * > curl -k -H 'Host: www.paypal.com' \"https://localhost:7000\"\n *\n */\nobject PacketProxy extends App {\n\n  // Hard-code a configuration\n  //val settings = LoadBalancerSettings(Configuration.load(\"proxy.conf\"))\n\n  val supervisor = new NioEventLoopGroup(1)\n  val workers = new NioEventLoopGroup()\n\n  new ServerBootstrap()\n    .channel(classOf[NioServerSocketChannel])\n    .group(supervisor, workers)\n    .childHandler(new InboundHandler(workers))\n    .childOption[java.lang.Boolean](ChannelOption.TCP_NODELAY, true)\n    .childOption[java.lang.Boolean](ChannelOption.AUTO_READ, false)\n    .bind(InetAddress.getLocalHost(), 7000)\n    //.bind(InetAddress.getByName(\"0.0.0.0\"), 8081)\n    .sync()\n\n}\n\n@Sharable\nclass InboundHandler(workers: EventLoopGroup) extends ChannelInboundHandlerAdapter with StrictLogging {\n\n  val Downstream = new InetSocketAddress(\"www.paypal.com\", 443)\n  val DownstreamKey = AttributeKey.valueOf[Channel](\"balancer\")\n\n  /**\n   * Setup the incoming channel once established.\n   */\n  override def channelRegistered(ctx: ChannelHandlerContext): Unit = {\n    import scala.concurrent.ExecutionContext.Implicits.global\n    println(s\"Initializing Proxy Channel $ctx\")\n\n    // Establish a downstream connection\n    val channel = ctx.channel\n    val downstream = connect(channel, Downstream)\n\n    downstream onComplete {\n      case Success(downstream) =>\n        println(s\"Connected downstream $downstream\")\n        ctx.attr(DownstreamKey).set(downstream)\n        channel.config.setAutoRead(true)\n        channel.read()\n\n      case Failure(ex) =>\n        logger.warn(\"Downstream connection failed.\", ex)\n    }\n\n    ctx.fireChannelRegistered()\n  }\n\n  /**\n    */\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef) {\n    println(\"Writing packet from upstream to downstream...\")\n    ctx.attr(DownstreamKey).get().writeAndFlush(msg)\n    //ctx.fireChannelRead(msg)\n  }\n\n\n  def connect(channel: Channel, address: SocketAddress): Future[Channel] = {\n    import Utilities._\n\n    new Bootstrap()\n      .channel(classOf[NioSocketChannel])\n      .group(workers)\n      .handler(new OutboundHandler(channel))\n      .option[java.lang.Boolean](ChannelOption.TCP_NODELAY, true)\n      .connect(address)\n      .future\n  }\n}\n\nclass OutboundHandler(upstream: Channel) extends ChannelInboundHandlerAdapter {\n\n  override def channelRegistered(ctx: ChannelHandlerContext): Unit = {\n    println(\"Registered downstream\")\n    ctx.fireChannelRegistered()\n  }\n\n  /**\n   */\n  override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef) {\n    println(\"Writing packet from downstream to upstream...\")\n    upstream.writeAndFlush(msg)\n    //ctx.fireChannelRead(msg)\n  }\n\n  override def channelInactive(ctx: ChannelHandlerContext): Unit = {\n    println(\"Downstream closing..\")\n    upstream.close()\n    ctx.fireChannelInactive()\n  }\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/util/NeutrinoCoreTest.scala",
    "content": "package com.ebay.neutrino.util\n\nimport org.scalatest.{FlatSpec, Matchers}\n\n\nclass DifferentialStateTest extends FlatSpec with Matchers {\n\n  // Resort the state (for comparison)\n  def sorted(state: DifferentialState[Int]): DifferentialState[Int] =\n    DifferentialState(state.added.sorted, state.removed.sorted, state.updated.sorted)\n\n\n  it should \"test diff generation with pre/post variables\" in {\n    // Note - does not preserve ordering...\n    {\n      // Simple input lists\n      val pre  = Seq(1,2,3)\n      val post = Seq(2,4,6)\n      sorted(DifferentialState(pre, post)) should be (DifferentialState(Seq(4,6), Seq(1,3), Seq(2)))\n    }\n    {\n      // All added\n      val pre  = Seq()\n      val post = Seq(2,4,6)\n      sorted(DifferentialState(pre, post)) should be (DifferentialState(Seq(2,4,6), Seq(), Seq()))\n    }\n    {\n      // All removed\n      val pre  = Seq(1,2,3)\n      val post = Seq()\n      sorted(DifferentialState(pre, post)) should be (DifferentialState(Seq(), Seq(1,2,3), Seq()))\n    }\n    {\n      // All same\n      val pre  = Seq(1,2,3)\n      val post = Seq(1,2,3)\n      sorted(DifferentialState(pre, post)) should be (DifferentialState(Seq(), Seq(), Seq(1,2,3)))\n    }\n  }\n\n  // Test empty-pre/empty-post\n}\n\n\nclass DifferentialStateSupportTest extends FlatSpec with Matchers {\n\n  class Tester[T] extends DifferentialStateSupport[String,T] {\n\n    var added   = Seq.empty[T]\n    var removed = Seq.empty[T]\n    var updated = Seq.empty[(T,T)]\n\n    // Required methods\n    def key(v: T): String = v.toString.toLowerCase\n    def addState(t: T) = added = added :+ t\n    def removeState(t: T) = removed = removed :+ t\n    def updateState(pre: T, post: T) = updated = updated :+ (pre,post)\n\n    def reset() = {\n      added   = Seq.empty[T]\n      removed = Seq.empty[T]\n      updated = Seq.empty[(T,T)]\n    }\n  }\n\n\n  // Test diffs\n  it should \"test diff generation\" in {\n\n    val tester = new Tester[Int]\n\n    // Initial update\n    tester.update(1,2,3,4)\n    tester.added.sorted should be (Seq(1,2,3,4))\n    tester.removed.sorted should be (Seq())\n    tester.updated.sorted should be (Seq())\n    tester.reset()\n\n    // Second update\n    tester.update(7,6,3,1)\n    tester.added.sorted should be (Seq(6,7))\n    tester.removed.sorted should be (Seq(2,4))\n    tester.updated.sorted should be (Seq())   // Values are the same; no update\n    tester.reset()\n\n    // Third update\n    tester.update(1,1,1,1)   // Note extras will be uniquely condensed by k\n    tester.added.sorted should be (Seq())\n    tester.removed.sorted should be (Seq(3,6,7))\n    tester.updated.sorted should be (Seq())   // Values are the same; no update\n    tester.reset()\n\n    // Final update\n    tester.update()\n    tester.added.sorted should be (Seq())\n    tester.removed.sorted should be (Seq(1))\n    tester.updated.sorted should be (Seq())\n    tester.reset()\n  }\n\n  // Test version TODO\n\n\n  it should \"test update generation\" in {\n\n    val tester = new Tester[String]\n\n    // Initial setup\n    tester.update(\"A\",\"B\",\"C\")\n    tester.added.sorted should be (Seq(\"A\",\"B\",\"C\"))\n    tester.removed.sorted should be (Seq())\n    tester.updated.sorted should be (Seq())\n    tester.reset()\n\n    // Update, dropping and updating A to a (same key, different value)\n    tester.update(\"a\",\"C\")\n    tester.added.sorted should be (Seq())\n    tester.removed.sorted should be (Seq(\"B\"))\n    tester.updated.sorted should be (Seq((\"A\",\"a\")))\n    tester.reset()\n\n  }\n\n}"
  },
  {
    "path": "src/test/scala/com/ebay/neutrino/zmtp/Utilities.scala",
    "content": "package com.ebay.neutrino.zmtp\n\nimport java.nio.ByteOrder\n\nimport io.netty.buffer.ByteBuf\n\n\nobject Utilities {\n\n  import io.netty.buffer.ByteBufUtil._\n\n\n  implicit class ByteBufSupport(val self: ByteBuf) extends AnyVal {\n\n    def isBigEndian() = (self.order eq ByteOrder.BIG_ENDIAN)\n\n\n    def readOptionalByte(): Option[Byte] =\n      if (self.isReadable) Option(self.readByte) else None\n\n\n    def readLongByEndian() = isBigEndian match {\n      case true  => self.readLong\n      case false => swapLong(self.readLong)\n    }\n\n    def writeLongByEndian(value: Long) = isBigEndian match {\n      case true  => self.writeLong(value)\n      case false => self.writeLong(swapLong(value))\n    }\n\n    def copyToByteArray(): Array[Byte] = {\n      val bytes = new Array[Byte](self.readableBytes())\n      self.getBytes(self.readerIndex, bytes)\n      bytes\n    }\n  }\n}\n\n// Hack around Option/Iterator support\nobject OptionIterator {\n\n  def continually[T](elem: => Option[T]): Iterator[T] =\n    Iterator.continually(elem) takeWhile (_.isDefined) flatten\n}"
  },
  {
    "path": "supervisord.conf",
    "content": "[supervisord]\nnodaemon=true\npidfile=/var/run/supervisord.pid\nlogfile=/var/log/supervisor/supervisord.log\nlogfile_maxbytes=50MB\nlogfile_backups=3\nloglevel=info\n\n[program:sshd]\ncommand=/usr/sbin/sshd -D\nlogfile=/var/log/sshd.log\nlog_stdout=true\nlog_stderr=true\nlogfile_maxbytes=50MB\nlogfile_backups=3\n"
  }
]