├── public ├── stylesheets │ └── main.css ├── images │ └── favicon.png └── javascripts │ └── jquery-1.9.0.min.js ├── project ├── build.properties └── plugins.sbt ├── app ├── views │ ├── index.scala.html │ └── main.scala.html ├── controllers │ ├── Application.scala │ └── HystrixSupport.scala ├── commands │ ├── HelloWorld.scala │ └── HelloWorldAsync.scala └── util │ └── Futures.scala ├── .gitignore ├── conf ├── routes └── application.conf ├── test ├── IntegrationSpec.scala └── ApplicationSpec.scala ├── README.md └── LICENSE /public/stylesheets/main.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.11 2 | -------------------------------------------------------------------------------- /public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/knutwalker/hystrix-play/HEAD/public/images/favicon.png -------------------------------------------------------------------------------- /app/views/index.scala.html: -------------------------------------------------------------------------------- 1 | @(message: String) 2 | 3 | @main("Welcome to Play") { 4 | 5 | @play20.welcome(message) 6 | 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | project/project 3 | project/target 4 | target 5 | tmp 6 | .history 7 | dist 8 | /.idea 9 | /*.iml 10 | /out 11 | /.idea_modules 12 | /.classpath 13 | /.project 14 | /RUNNING_PID 15 | /.settings 16 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // Comment to get more information during initialization 2 | logLevel := Level.Warn 3 | 4 | // The Typesafe repository 5 | resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" 6 | 7 | // Use the Play sbt plugin for Play projects 8 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.3") 9 | -------------------------------------------------------------------------------- /app/views/main.scala.html: -------------------------------------------------------------------------------- 1 | @(title: String)(content: Html) 2 | 3 | 4 | 5 | 6 | 7 | @title 8 | 9 | 10 | 11 | 12 | 13 | @content 14 | 15 | 16 | -------------------------------------------------------------------------------- /conf/routes: -------------------------------------------------------------------------------- 1 | # Routes 2 | # This file defines all application routes (Higher priority routes first) 3 | # ~~~~ 4 | 5 | # Home page 6 | GET / controllers.Application.index 7 | 8 | GET /bernd controllers.Application.test 9 | GET /async controllers.Application.async 10 | GET /hystrix.stream controllers.HystrixSupport.stream(delay: Option[Int]) 11 | 12 | # Map static resources from the /public folder to the /assets URL path 13 | GET /assets/*file controllers.Assets.at(path="/public", file) 14 | -------------------------------------------------------------------------------- /test/IntegrationSpec.scala: -------------------------------------------------------------------------------- 1 | import org.specs2.mutable._ 2 | import org.specs2.runner._ 3 | import org.junit.runner._ 4 | 5 | import play.api.test._ 6 | import play.api.test.Helpers._ 7 | 8 | /** 9 | * add your integration spec here. 10 | * An integration test will fire up a whole play application in a real (or headless) browser 11 | */ 12 | @RunWith(classOf[JUnitRunner]) 13 | class IntegrationSpec extends Specification { 14 | 15 | "Application" should { 16 | 17 | "work from within a browser" in new WithBrowser { 18 | 19 | browser.goTo("http://localhost:" + port) 20 | 21 | browser.pageSource must contain("Your new application is ready.") 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/controllers/Application.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import play.api.mvc._ 4 | import akka.actor.ActorSystem 5 | import commands.{HelloWorld, HelloWorldAsync} 6 | import util.Futures 7 | import javax.inject.Inject 8 | 9 | class Application @Inject()(implicit system: ActorSystem) extends Controller { 10 | import play.api.libs.concurrent.Execution.Implicits.defaultContext 11 | 12 | import util.Futures._ 13 | 14 | def index = Action { 15 | Ok(views.html.index("Your new application is ready.")) 16 | } 17 | 18 | def test = Action.async { 19 | new HelloWorld("Bernd").future.map(Ok(_)) 20 | } 21 | 22 | def async = Action.async { 23 | new HelloWorldAsync("Bernd").future.map(Ok(_)) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/ApplicationSpec.scala: -------------------------------------------------------------------------------- 1 | import org.specs2.mutable._ 2 | import org.specs2.runner._ 3 | import org.junit.runner._ 4 | 5 | import play.api.test._ 6 | import play.api.test.Helpers._ 7 | 8 | /** 9 | * Add your spec here. 10 | * You can mock out a whole application including requests, plugins etc. 11 | * For more information, consult the wiki. 12 | */ 13 | @RunWith(classOf[JUnitRunner]) 14 | class ApplicationSpec extends Specification { 15 | 16 | "Application" should { 17 | 18 | "send 404 on a bad request" in new WithApplication{ 19 | route(FakeRequest(GET, "/boum")) must beNone 20 | } 21 | 22 | "render the index page" in new WithApplication{ 23 | val home = route(FakeRequest(GET, "/")).get 24 | 25 | status(home) must equalTo(OK) 26 | contentType(home) must beSome.which(_ == "text/html") 27 | contentAsString(home) must contain ("Your new application is ready.") 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/commands/HelloWorld.scala: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import com.netflix.hystrix.{HystrixCommandGroupKey, HystrixCommand} 4 | 5 | class HelloWorld(name: String) extends HystrixCommand[String](HelloWorld.key) { 6 | def run(): String = { 7 | if (math.random > 0.6) throw new RuntimeException("haha") 8 | Thread.sleep((math.random * 1500).toLong) 9 | s"Hello, $name!" 10 | } 11 | 12 | override def getFallback: String = 13 | new HelloWorld2().execute() 14 | } 15 | 16 | class HelloWorld2 extends HystrixCommand[String](HelloWorld.key) { 17 | def run(): String = { 18 | if (math.random > 0.8) throw new RuntimeException("wow, we're pretty fucked up") 19 | "Booya! Fallback here!" 20 | } 21 | 22 | override def getFallback: String = new HelloWorld3().execute() 23 | } 24 | 25 | class HelloWorld3 extends HystrixCommand[String](HelloWorld.key) { 26 | def run(): String = "Always working" 27 | } 28 | 29 | object HelloWorld { 30 | private[commands] final val key = HystrixCommandGroupKey.Factory.asKey("ExampleGroup") 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | hystrix-play 2 | ============ 3 | 4 | Playground for using [Hystrix](https://github.com/Netflix/Hystrix#readme) together with the playframework 5 | 6 | 7 | ## Using Hystrix Commands with Plays asynchronous actions 8 | 9 | There is an implicit method `future` available on any `HystrixCommand`, that will return a [Scala Future](http://www.scala-lang.org/api/current/#scala.concurrent.Future) rather then what Java calls a Future. 10 | This is implemented by [`HystrixCommandWithScalaFuture`](app/util/Futures.scala#L13) 11 | 12 | You can see it in Action in the [Application controller](app/controllers/Application.scala#L16). 13 | 14 | 15 | ## Providing a metrics stream, consumable by the hystrix-dashboard 16 | 17 | This is a replacement for the [`hystrix-metrics-event-stream`](https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-metrics-event-stream) module. 18 | This module is only available as a Servlet/WAR, which is not suitable for Play. 19 | 20 | There is a [`HystrixSupport` Controller](app/controllers/HystrixSupport.scala), that does the same as the original Servlet 21 | and allows for this play application to be monitored by the [Hystrix Dashboard](https://github.com/Netflix/Hystrix/wiki/Dashboard) 22 | -------------------------------------------------------------------------------- /app/commands/HelloWorldAsync.scala: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import scala.concurrent.{Future, ExecutionContext} 4 | 5 | import akka.actor.{Actor, Props, ActorSystem} 6 | import akka.pattern.ask 7 | import akka.util.Timeout 8 | import java.util.concurrent.TimeUnit 9 | 10 | import com.netflix.hystrix.HystrixCommandGroupKey 11 | import rx.Observable 12 | 13 | import util.Futures.HystrixFutureCommand 14 | 15 | 16 | class HelloWorldAsync(name: String)(implicit system: ActorSystem, ec: ExecutionContext) 17 | extends HystrixFutureCommand[String](HelloWorldAsync.key) { 18 | 19 | implicit val timeout = Timeout(5000, TimeUnit.MILLISECONDS) 20 | 21 | override def run(): Future[String] = { 22 | val actor = system.actorOf(HelloWorldAsync.actor(name)) 23 | val f = actor ? HelloWorldAsync.ReturnMessage 24 | f.asInstanceOf[Future[String]] 25 | } 26 | 27 | override protected def resumeWithFallback(): Observable[String] = { 28 | Observable.just("Fallback (Async)") 29 | } 30 | 31 | } 32 | 33 | object HelloWorldAsync { 34 | private final val key = HystrixCommandGroupKey.Factory.asKey("HelloWorldAsync") 35 | 36 | def actor(name: String) = Props(new HelloWorldActor(name)) 37 | 38 | private class HelloWorldActor(name: String) extends Actor { 39 | 40 | def receive = { 41 | case ReturnMessage => { 42 | Thread.sleep((math.random * 1500).toLong) 43 | sender ! s"Hello, $name! (Async)" 44 | } 45 | } 46 | 47 | } 48 | 49 | case object ReturnMessage 50 | } 51 | -------------------------------------------------------------------------------- /app/util/Futures.scala: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import scala.concurrent.{Future, Promise, ExecutionContext} 4 | import scala.util.{Failure, Success} 5 | 6 | import com.netflix.hystrix.{HystrixCommandGroupKey, HystrixObservable, HystrixObservableCommand} 7 | import rx.Observable 8 | import rx.lang.scala.subjects.ReplaySubject 9 | 10 | object Futures { 11 | 12 | private class ForPromiseObserver[T](p: Promise[T]) extends rx.Observer[T] { 13 | def onNext(t: T): Unit = p.trySuccess(t) 14 | def onError(e: Throwable): Unit = p.tryFailure(e) 15 | def onCompleted(): Unit = () 16 | } 17 | 18 | implicit final class HystrixCommandWithScalaFuture[T](val cmd: HystrixObservable[T]) extends AnyVal { 19 | def future: Future[T] = { 20 | val promise = Promise[T]() 21 | val observer = new ForPromiseObserver(promise) 22 | 23 | cmd.observe().subscribe(observer) 24 | 25 | promise.future 26 | } 27 | } 28 | 29 | abstract class HystrixFutureCommand[T](groupKey: HystrixCommandGroupKey)(implicit ec: ExecutionContext) 30 | extends HystrixObservableCommand[T](groupKey) { 31 | 32 | override def construct(): Observable[T] = { 33 | val channel = ReplaySubject[T]() 34 | 35 | run().onComplete { 36 | case Success(v) => { 37 | channel.onNext(v) 38 | channel.onCompleted() 39 | } 40 | case Failure(t) => { 41 | channel.onError(t) 42 | channel.onCompleted() 43 | } 44 | } 45 | 46 | channel.asJavaSubject 47 | } 48 | 49 | def run(): Future[T] 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /conf/application.conf: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for the application. 2 | # ~~~~~ 3 | 4 | # Secret key 5 | # ~~~~~ 6 | # The secret key is used to secure cryptographics functions. 7 | # If you deploy your application to several instances be sure to use the same key! 8 | application.secret="G`ftJZ^8nofQlKl5H4LLPgy25V delayOpt.getOrElse(500)). 30 | fold(unavailable(maxConnections)) { delay => 31 | val y = HystrixDashboardStream.getInstance().observe().concatMap(new Func1[DashboardData, Observable[String]]() { 32 | def call(dashboardData:DashboardData) = { 33 | Logger.info("Getting data") 34 | Observable.from(SerialHystrixDashboardData.toMultipleJsonStrings(dashboardData).asScala.map(x => x + "\n\n").asJava); 35 | } 36 | }) 37 | 38 | val publisher = RxReactiveStreams.toPublisher(y); 39 | val source = Source.fromPublisher(publisher).via(EventSource.flow).watchTermination() { (m, f) => 40 | f.onComplete{_ => Logger.info("Releasing connection"); concurrentConnections.decrementAndGet()} 41 | m 42 | }.delay(delay.milli) 43 | 44 | Ok.chunked(source ).withHeaders( 45 | "Content-Type" -> "text/event-stream;charset=UTF-8", 46 | "Cache-Control" -> "no-cache, no-store, max-age=0, must-revalidate", 47 | "Pragma" -> "no-cache" 48 | ) 49 | } 50 | } 51 | 52 | private[this] def unavailable(max: Int) = { 53 | concurrentConnections.decrementAndGet() 54 | ServiceUnavailable(s"MaxConcurrentConnections reached: $maxConcurrentConnections") 55 | } 56 | 57 | private[this] final val concurrentConnections: AtomicInteger = new AtomicInteger(0) 58 | private[this] final val maxConcurrentConnections: DynamicIntProperty = 59 | new DynamicIntProperty("hystrix.stream.maxConcurrentConnections", 5) 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /public/javascripts/jquery-1.9.0.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v1.9.0 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license */(function(e,t){"use strict";function n(e){var t=e.length,n=st.type(e);return st.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}function r(e){var t=Tt[e]={};return st.each(e.match(lt)||[],function(e,n){t[n]=!0}),t}function i(e,n,r,i){if(st.acceptData(e)){var o,a,s=st.expando,u="string"==typeof n,l=e.nodeType,c=l?st.cache:e,f=l?e[s]:e[s]&&s;if(f&&c[f]&&(i||c[f].data)||!u||r!==t)return f||(l?e[s]=f=K.pop()||st.guid++:f=s),c[f]||(c[f]={},l||(c[f].toJSON=st.noop)),("object"==typeof n||"function"==typeof n)&&(i?c[f]=st.extend(c[f],n):c[f].data=st.extend(c[f].data,n)),o=c[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[st.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[st.camelCase(n)])):a=o,a}}function o(e,t,n){if(st.acceptData(e)){var r,i,o,a=e.nodeType,u=a?st.cache:e,l=a?e[st.expando]:st.expando;if(u[l]){if(t&&(r=n?u[l]:u[l].data)){st.isArray(t)?t=t.concat(st.map(t,st.camelCase)):t in r?t=[t]:(t=st.camelCase(t),t=t in r?[t]:t.split(" "));for(i=0,o=t.length;o>i;i++)delete r[t[i]];if(!(n?s:st.isEmptyObject)(r))return}(n||(delete u[l].data,s(u[l])))&&(a?st.cleanData([e],!0):st.support.deleteExpando||u!=u.window?delete u[l]:u[l]=null)}}}function a(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(Nt,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:wt.test(r)?st.parseJSON(r):r}catch(o){}st.data(e,n,r)}else r=t}return r}function s(e){var t;for(t in e)if(("data"!==t||!st.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function u(){return!0}function l(){return!1}function c(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function f(e,t,n){if(t=t||0,st.isFunction(t))return st.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return st.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=st.grep(e,function(e){return 1===e.nodeType});if(Wt.test(t))return st.filter(t,r,!n);t=st.filter(t,r)}return st.grep(e,function(e){return st.inArray(e,t)>=0===n})}function p(e){var t=zt.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function d(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function h(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function g(e){var t=nn.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function m(e,t){for(var n,r=0;null!=(n=e[r]);r++)st._data(n,"globalEval",!t||st._data(t[r],"globalEval"))}function y(e,t){if(1===t.nodeType&&st.hasData(e)){var n,r,i,o=st._data(e),a=st._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)st.event.add(t,n,s[n][r])}a.data&&(a.data=st.extend({},a.data))}}function v(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!st.support.noCloneEvent&&t[st.expando]){r=st._data(t);for(i in r.events)st.removeEvent(t,i,r.handle);t.removeAttribute(st.expando)}"script"===n&&t.text!==e.text?(h(t).text=e.text,g(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),st.support.html5Clone&&e.innerHTML&&!st.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Zt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}function b(e,n){var r,i,o=0,a=e.getElementsByTagName!==t?e.getElementsByTagName(n||"*"):e.querySelectorAll!==t?e.querySelectorAll(n||"*"):t;if(!a)for(a=[],r=e.childNodes||e;null!=(i=r[o]);o++)!n||st.nodeName(i,n)?a.push(i):st.merge(a,b(i,n));return n===t||n&&st.nodeName(e,n)?st.merge([e],a):a}function x(e){Zt.test(e.type)&&(e.defaultChecked=e.checked)}function T(e,t){if(t in e)return t;for(var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Nn.length;i--;)if(t=Nn[i]+n,t in e)return t;return r}function w(e,t){return e=t||e,"none"===st.css(e,"display")||!st.contains(e.ownerDocument,e)}function N(e,t){for(var n,r=[],i=0,o=e.length;o>i;i++)n=e[i],n.style&&(r[i]=st._data(n,"olddisplay"),t?(r[i]||"none"!==n.style.display||(n.style.display=""),""===n.style.display&&w(n)&&(r[i]=st._data(n,"olddisplay",S(n.nodeName)))):r[i]||w(n)||st._data(n,"olddisplay",st.css(n,"display")));for(i=0;o>i;i++)n=e[i],n.style&&(t&&"none"!==n.style.display&&""!==n.style.display||(n.style.display=t?r[i]||"":"none"));return e}function C(e,t,n){var r=mn.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function k(e,t,n,r,i){for(var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;4>o;o+=2)"margin"===n&&(a+=st.css(e,n+wn[o],!0,i)),r?("content"===n&&(a-=st.css(e,"padding"+wn[o],!0,i)),"margin"!==n&&(a-=st.css(e,"border"+wn[o]+"Width",!0,i))):(a+=st.css(e,"padding"+wn[o],!0,i),"padding"!==n&&(a+=st.css(e,"border"+wn[o]+"Width",!0,i)));return a}function E(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=ln(e),a=st.support.boxSizing&&"border-box"===st.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=un(e,t,o),(0>i||null==i)&&(i=e.style[t]),yn.test(i))return i;r=a&&(st.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+k(e,t,n||(a?"border":"content"),r,o)+"px"}function S(e){var t=V,n=bn[e];return n||(n=A(e,t),"none"!==n&&n||(cn=(cn||st("