├── project ├── build.properties └── plugins.sbt ├── .github └── workflows │ └── main.yml ├── .gitignore ├── src ├── main │ └── scala │ │ └── com │ │ └── github │ │ └── takezoe │ │ └── retry │ │ ├── BackOff.scala │ │ ├── RetryPolicy.scala │ │ ├── FutureRetryManager.scala │ │ ├── package.scala │ │ └── CircuitBreakerPolicy.scala └── test │ └── scala │ └── com │ └── github │ └── takezoe │ └── retry │ ├── CircuitBreakerTest.scala │ └── RetryTest.scala ├── README.md └── LICENSE /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.4.6 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0") 2 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up JDK 1.8 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 1.8 16 | - name: Test with SBT 17 | run: sbt +test 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | .ensime 4 | .ensime_cache 5 | 6 | # sbt specific 7 | dist/* 8 | target/ 9 | lib_managed/ 10 | src_managed/ 11 | project/boot/ 12 | project/plugins/project/ 13 | 14 | # Scala-IDE specific 15 | .scala_dependencies 16 | .classpath 17 | .project 18 | .cache 19 | .settings 20 | 21 | # IntelliJ specific 22 | .idea/ 23 | .idea_modules/ 24 | 25 | # Metals specific 26 | .vscode/ 27 | .metals/ 28 | .bloop/ 29 | project/metals.sbt 30 | -------------------------------------------------------------------------------- /src/main/scala/com/github/takezoe/retry/BackOff.scala: -------------------------------------------------------------------------------- 1 | package com.github.takezoe.retry 2 | 3 | sealed trait BackOff extends java.io.Serializable { 4 | def nextDuration(count: Int, duration: Long): Long 5 | } 6 | 7 | object LinerBackOff extends BackOff { 8 | override def nextDuration(count: Int, duration: Long): Long = duration * count 9 | } 10 | 11 | object ExponentialBackOff extends BackOff { 12 | override def nextDuration(count: Int, duration: Long): Long = duration * scala.math.pow(2, count).toLong 13 | } 14 | 15 | object FixedBackOff extends BackOff { 16 | override def nextDuration(count: Int, duration: Long): Long = duration 17 | } 18 | -------------------------------------------------------------------------------- /src/main/scala/com/github/takezoe/retry/RetryPolicy.scala: -------------------------------------------------------------------------------- 1 | package com.github.takezoe.retry 2 | 3 | import scala.concurrent.duration._ 4 | 5 | case class RetryPolicy( 6 | maxAttempts: Int, 7 | retryDuration: FiniteDuration, 8 | backOff: BackOff = FixedBackOff, 9 | jitter: FiniteDuration = 0.second, 10 | onRetry: RetryContext => Unit = _ => (), 11 | onFailure: RetryContext => Unit = _ => () 12 | ) 13 | 14 | case class RetryContext( 15 | retryCount: Int, 16 | exception: Throwable 17 | ) 18 | 19 | object RetryPolicy { 20 | 21 | val NoRetry = RetryPolicy(0, Duration.Zero, FixedBackOff) 22 | 23 | def Immediately(attempts: Int): RetryPolicy = RetryPolicy(attempts, Duration.Zero, FixedBackOff) 24 | 25 | } -------------------------------------------------------------------------------- /src/test/scala/com/github/takezoe/retry/CircuitBreakerTest.scala: -------------------------------------------------------------------------------- 1 | package com.github.takezoe.retry 2 | 3 | import org.scalatest.FunSuite 4 | import scala.concurrent.{Await, Future} 5 | import scala.concurrent.duration._ 6 | import scala.concurrent.ExecutionContext.Implicits.global 7 | 8 | class CircuitBreakerTest extends FunSuite { 9 | 10 | test("circuitBreaker") { 11 | implicit val policy = CircuitBreakerPolicy( 12 | failureThreshold = 3, 13 | successThreshold = 2, 14 | retryDuration = 1.second, 15 | onOpen = c => println(c), 16 | onClose = c => println(c), 17 | onHalfOpen = c => println(c) 18 | ) 19 | 20 | assert(circuitBreaker { "OK" } == "OK") 21 | 22 | assertThrows[RuntimeException] { 23 | circuitBreaker { throw new RuntimeException() } 24 | } 25 | assertThrows[RuntimeException] { 26 | circuitBreaker { throw new RuntimeException() } 27 | } 28 | 29 | // -> Open 30 | assertThrows[RuntimeException] { 31 | circuitBreaker { throw new RuntimeException() } 32 | } 33 | assertThrows[RuntimeException] { 34 | circuitBreaker { "OK" } 35 | } 36 | 37 | Thread.sleep(1000) 38 | // -> HalfOpen 39 | assert(circuitBreaker { "OK" } == "OK") 40 | 41 | // -> Close 42 | assert(circuitBreaker { "OK" } == "OK") 43 | assertThrows[RuntimeException] { 44 | circuitBreaker { throw new RuntimeException() } 45 | } 46 | assert(circuitBreaker { "OK" } == "OK") 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scala-retry [![CI](https://github.com/takezoe/scala-retry/workflows/CI/badge.svg)](https://github.com/takezoe/scala-retry/actions?query=branch%3Amaster) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.takezoe/scala-retry_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.takezoe/scala-retry_2.12) 2 | 3 | Offers simple retry functionality for Scala. 4 | 5 | ```scala 6 | libraryDependencies += "com.github.takezoe" %% "scala-retry" % "0.0.6" 7 | ``` 8 | 9 | ## Retry synchronously 10 | 11 | `retry` runs and retries a given block on the current thread. If the block is successful, it returns a value. Otherwise, it throws an exception. Note that the current thread is blocked during retrying. 12 | 13 | ```scala 14 | import com.github.takezoe.retry._ 15 | import scala.concurrent.duration._ 16 | 17 | implicit val policy = RetryPolicy( 18 | maxAttempts = 3, 19 | retryDuration = 1.second, 20 | backOff = ExponentialBackOff, // default is FixedBackOff 21 | jitter = 1.second // default is no jitter 22 | ) 23 | 24 | val result: String = retry { 25 | // something to retry 26 | "Hello World!" 27 | } 28 | ``` 29 | 30 | ## Retry Future 31 | 32 | `retryFuture` takes `Future` (a block which generates `Future`, more precisely) instead of a function. Note that it requires `ExecutionContext` additionally. 33 | 34 | ```scala 35 | import com.github.takezoe.retry._ 36 | import scala.concurrent.duration._ 37 | import scala.concurrent.Future 38 | import scala.concurrent.ExecutionContext.Implicits.global 39 | 40 | implicit val rm = new RetryManager() 41 | implicit val policy = RetryPolicy( 42 | maxAttempts = 3, 43 | retryDuration = 1.second, 44 | backOff = ExponentialBackOff 45 | ) 46 | 47 | val future: Future[String] = retryFuture { 48 | Future { 49 | // something to retry 50 | "Hello World!" 51 | } 52 | } 53 | ``` 54 | 55 | ## CircuitBreaker 56 | 57 | ```scala 58 | import com.github.takezoe.retry._ 59 | import scala.concurrent.duration._ 60 | 61 | implicit val policy = CircuitBreakerPolicy( 62 | failureThreshold = 3, 63 | successThreshold = 3, 64 | retryDuration = 1.minute 65 | ) 66 | 67 | val result: String = circuitBreaker { 68 | // Something can be failed 69 | "Hello World!" 70 | } 71 | ``` 72 | -------------------------------------------------------------------------------- /src/main/scala/com/github/takezoe/retry/FutureRetryManager.scala: -------------------------------------------------------------------------------- 1 | package com.github.takezoe.retry 2 | 3 | import java.util.concurrent.ConcurrentLinkedQueue 4 | import java.util.concurrent.atomic.AtomicBoolean 5 | 6 | import scala.concurrent.{ExecutionContext, Future, Promise} 7 | import scala.util.control.NonFatal 8 | import scala.util.{Failure, Success} 9 | import java.util.function.Consumer 10 | 11 | class RetryManager { 12 | 13 | private val tasks = new ConcurrentLinkedQueue[FutureRetryTask]() 14 | 15 | private val running = new AtomicBoolean(true) 16 | 17 | private val thread = new Thread { 18 | override def run(): Unit = { 19 | while(running.get()){ 20 | val currentTime = System.currentTimeMillis 21 | tasks.iterator().forEachRemaining(new Consumer[FutureRetryTask]{ 22 | override def accept(task: FutureRetryTask): Unit = { 23 | if(task.nextRun <= currentTime){ 24 | tasks.remove(task) 25 | val future = task.f() 26 | future.onComplete { 27 | case Success(v) => task.promise.success(v) 28 | case Failure(e) => { 29 | if(task.count == task.policy.maxAttempts){ 30 | task.policy.onFailure(RetryContext(task.count + 1, e)) 31 | task.promise.failure(e) 32 | } else { 33 | task.policy.onRetry(RetryContext(task.count + 1, e)) 34 | val count = task.count + 1 35 | val nextDuration = task.policy.backOff.nextDuration(count, task.policy.retryDuration.toMillis) 36 | val nextRun = currentTime + nextDuration + jitter(task.policy.jitter.toMillis) 37 | tasks.add(new FutureRetryTask(task.f, task.policy, task.ec, task.promise, nextRun, count)) 38 | } 39 | } 40 | }(task.ec) 41 | } 42 | } 43 | }) 44 | Thread.sleep(100) 45 | } 46 | } 47 | } 48 | 49 | thread.start() 50 | 51 | def scheduleFuture[T](f: => Future[T])(implicit policy: RetryPolicy, ec: ExecutionContext): Future[T] = { 52 | val promise = Promise[T]() 53 | val task = new FutureRetryTask(() => f, policy, ec, promise.asInstanceOf[Promise[Any]], System.currentTimeMillis + policy.retryDuration.toMillis) 54 | tasks.add(task) 55 | promise.future 56 | } 57 | 58 | def shutdown(): Unit = { 59 | running.set(false) 60 | } 61 | 62 | } 63 | 64 | private[retry] class FutureRetryTask( 65 | val f: () => Future[Any], 66 | val policy: RetryPolicy, 67 | val ec: ExecutionContext, 68 | val promise: Promise[Any], 69 | val nextRun: Long, 70 | val count: Int = 1 71 | ) -------------------------------------------------------------------------------- /src/test/scala/com/github/takezoe/retry/RetryTest.scala: -------------------------------------------------------------------------------- 1 | package com.github.takezoe.retry 2 | 3 | import org.scalatest.FunSuite 4 | import scala.concurrent.{Await, Future} 5 | import scala.concurrent.duration._ 6 | import scala.concurrent.ExecutionContext.Implicits.global 7 | 8 | class RetryTest extends FunSuite { 9 | 10 | test("retry (success)"){ 11 | implicit val retryPolicy = RetryPolicy( 12 | maxAttempts = 2, 13 | retryDuration = 1.second, 14 | backOff = FixedBackOff, 15 | onRetry = c => println(s"retry: ${c.toString}"), 16 | onFailure = c => println(s"failure: ${c.toString}") 17 | ) 18 | var count = 0 19 | 20 | val result = retry { 21 | count = count + 1 22 | if(count < 3){ 23 | throw new RuntimeException() 24 | } 25 | count 26 | } 27 | 28 | assert(result == 3) 29 | } 30 | 31 | test("retry (failure)"){ 32 | implicit val retryPolicy = RetryPolicy( 33 | maxAttempts = 2, 34 | retryDuration = 1.second, 35 | backOff = FixedBackOff, 36 | onRetry = c => println(s"retry: ${c.toString}"), 37 | onFailure = c => println(s"failure: ${c.toString}") 38 | ) 39 | var count = 0 40 | 41 | assertThrows[RuntimeException] { 42 | retry { 43 | count = count + 1 44 | if(count < 4){ 45 | throw new RuntimeException() 46 | } 47 | count 48 | } 49 | } 50 | } 51 | 52 | test("retryFuture (success)"){ 53 | implicit val retryPolicy = RetryPolicy( 54 | maxAttempts = 2, 55 | retryDuration = 1.second, 56 | backOff = FixedBackOff, 57 | onRetry = c => println(s"retry: ${c.toString}"), 58 | onFailure = c => println(s"failure: ${c.toString}") 59 | ) 60 | implicit val retryManager = new RetryManager() 61 | var count = 0 62 | 63 | val f = retryFuture { 64 | Future { 65 | count = count + 1 66 | if(count < 3){ 67 | throw new RuntimeException() 68 | } 69 | count 70 | } 71 | } 72 | val result = Await.result(f, 5.seconds) 73 | 74 | assert(result == 3) 75 | retryManager.shutdown() 76 | } 77 | 78 | test("retryFuture (failure)"){ 79 | implicit val retryPolicy = RetryPolicy( 80 | maxAttempts = 2, 81 | retryDuration = 1.second, 82 | backOff = FixedBackOff, 83 | onRetry = c => println(s"retry: ${c.toString}"), 84 | onFailure = c => println(s"failure: ${c.toString}") 85 | ) 86 | implicit val retryManager = new RetryManager() 87 | var count = 0 88 | 89 | val f = retryFuture { 90 | Future { 91 | count = count + 1 92 | if(count < 4){ 93 | throw new RuntimeException() 94 | } 95 | count 96 | } 97 | } 98 | 99 | assertThrows[RuntimeException]{ 100 | Await.result(f, 5.seconds) 101 | } 102 | 103 | retryManager.shutdown() 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/scala/com/github/takezoe/retry/package.scala: -------------------------------------------------------------------------------- 1 | package com.github.takezoe 2 | 3 | import scala.concurrent.{ExecutionContext, Future} 4 | import scala.util.control.NonFatal 5 | import scala.util.{Failure, Random, Success, Try} 6 | 7 | package object retry { 8 | 9 | import java.util.concurrent.ThreadLocalRandom 10 | 11 | def retry[T](f: => T)(implicit policy: RetryPolicy): T = { 12 | var count = 0 13 | 14 | while(true){ 15 | try { 16 | return f 17 | } catch { 18 | case NonFatal(e) => 19 | if(count == policy.maxAttempts){ 20 | policy.onFailure(RetryContext(count + 1, e)) 21 | throw e 22 | } 23 | policy.onRetry(RetryContext(count + 1, e)) 24 | count = count + 1 25 | Thread.sleep( 26 | policy.backOff.nextDuration(count, policy.retryDuration.toMillis) + jitter(policy.jitter.toMillis) 27 | ) 28 | } 29 | } 30 | ??? // never come to here 31 | } 32 | 33 | def retryAsEither[T](f: => T)(implicit policy: RetryPolicy): Either[Throwable, T] = { 34 | try { 35 | val result = retry(f) 36 | Right(result) 37 | } catch { 38 | case NonFatal(e) => Left(e) 39 | } 40 | } 41 | 42 | def retryBlockingAsTry[T](f: => T)(implicit policy: RetryPolicy): Try[T] = { 43 | try { 44 | val result = retry(f) 45 | Success(result) 46 | } catch { 47 | case NonFatal(e) => Failure(e) 48 | } 49 | } 50 | 51 | def retryFuture[T](f: => Future[T])(implicit policy: RetryPolicy, retryManager: RetryManager, ec: ExecutionContext): Future[T] = { 52 | val future = f 53 | if(policy.maxAttempts > 0){ 54 | future.recoverWith { case _ => 55 | retryManager.scheduleFuture(f) 56 | } 57 | } else { 58 | future 59 | } 60 | } 61 | 62 | private[retry] def jitter(maxMills: Long): Long = { 63 | if(maxMills == 0){ 64 | 0 65 | } else { 66 | (ThreadLocalRandom.current().nextDouble() * maxMills).toLong 67 | } 68 | } 69 | 70 | def circuitBreaker[T](f: => T)(implicit policy: CircuitBreakerPolicy): T = { 71 | import CircuitBreakerPolicy._ 72 | def run(): T = { 73 | try { 74 | val result = f 75 | policy.succeeded() 76 | result 77 | } catch { 78 | case NonFatal(e) => 79 | policy.failed(e) 80 | throw e 81 | } 82 | } 83 | 84 | policy.getContext() match { 85 | case CircuitBreakerContext(Open, _, _, Some(FailureInfo(lastFailureTimeMillis, lastException))) => { 86 | if (System.currentTimeMillis - lastFailureTimeMillis >= policy.retryDuration.toMillis) { 87 | run() 88 | } else { 89 | throw lastException 90 | } 91 | } 92 | case _ => run() 93 | } 94 | } 95 | 96 | def circuitBreakerAsEither[T](f: => T)(implicit policy: CircuitBreakerPolicy): Either[Throwable, T] = { 97 | try { 98 | val result = circuitBreaker(f) 99 | Right(result) 100 | } catch { 101 | case NonFatal(e) => Left(e) 102 | } 103 | } 104 | 105 | def circuitBreakerAsTry[T](f: => T)(implicit policy: CircuitBreakerPolicy): Try[T] = { 106 | try { 107 | val result = circuitBreaker(f) 108 | Success(result) 109 | } catch { 110 | case NonFatal(e) => Failure(e) 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/scala/com/github/takezoe/retry/CircuitBreakerPolicy.scala: -------------------------------------------------------------------------------- 1 | package com.github.takezoe.retry 2 | 3 | import scala.concurrent.duration._ 4 | import java.util.concurrent.atomic.LongAdder 5 | import java.util.concurrent.atomic.AtomicReference 6 | import CircuitBreakerPolicy._ 7 | 8 | case class CircuitBreakerContext( 9 | state: State = Close, 10 | failureCount: Int = 0, 11 | successCount: Int = 0, 12 | lastFailure: Option[FailureInfo] = None 13 | ) 14 | 15 | case class FailureInfo( 16 | timestampMillis: Long, 17 | exception: Throwable 18 | ) 19 | 20 | class CircuitBreakerPolicy( 21 | val failureThreshold: Int, 22 | val successThreshold: Int, 23 | val retryDuration: FiniteDuration, 24 | val onClose: (CircuitBreakerContext) => Unit = _ => (), 25 | val onOpen: (CircuitBreakerContext) => Unit = _ => (), 26 | val onHalfOpen: (CircuitBreakerContext) => Unit = _ => () 27 | ){ 28 | private val context = new AtomicReference[CircuitBreakerContext](CircuitBreakerContext()) 29 | 30 | def failed(e: Throwable): Unit = { 31 | // if failureThreashod <= 0 then the circuit breaker never closes. 32 | if (failureThreshold > 0) { 33 | val nextContext = context.get() match { 34 | case c @ CircuitBreakerContext(Close, failureCount, _, _) => 35 | if (failureCount + 1 >= failureThreshold) { 36 | val nextContext = c.copy(state = Open, failureCount = failureCount + 1, successCount = 0, lastFailure = Some(FailureInfo(System.currentTimeMillis(), e))) 37 | onOpen(nextContext) 38 | nextContext 39 | } else { 40 | val nextContext = c.copy(failureCount = failureCount + 1) 41 | nextContext 42 | } 43 | case c @ CircuitBreakerContext(Open, failureCount, _, _) => 44 | val nextContext = c.copy(failureCount = failureCount + 1, successCount = 0) 45 | nextContext 46 | case c @ CircuitBreakerContext(HalfOpen, failureCount, _, _) => 47 | val nextContext = c.copy(state = Open, failureCount = failureCount + 1, successCount = 0, lastFailure = Some(FailureInfo(System.currentTimeMillis(), e))) 48 | onOpen(nextContext) 49 | nextContext 50 | } 51 | context.set(nextContext) 52 | } 53 | } 54 | 55 | def succeeded(): Unit = { 56 | // if failureThreashod <= 0 then the circuit breaker never closes. 57 | if (failureThreshold > 0) { 58 | val nextContext = context.get() match { 59 | case c @ CircuitBreakerContext(Close, _, successCount, _) => 60 | val nextContext = c.copy(failureCount = 0, successCount = successCount + 1) 61 | nextContext 62 | case c @ CircuitBreakerContext(Open, _, successCount, _) => 63 | if (successCount + 1 >= successThreshold) { 64 | val nextContext = c.copy(state = Close, failureCount = 0, successCount = successCount + 1, lastFailure = None) 65 | onClose(nextContext) 66 | nextContext 67 | } else { 68 | val nextContext = c.copy(state = HalfOpen, failureCount = 0, successCount = successCount + 1, lastFailure = None) 69 | onHalfOpen(nextContext) 70 | nextContext 71 | } 72 | case c @ CircuitBreakerContext(HalfOpen, _, successCount, _) => 73 | if (successCount + 1 >= successThreshold) { 74 | val nextContext = c.copy(state = Close, failureCount = 0, successCount = successCount + 1, lastFailure = None) 75 | onClose(nextContext) 76 | nextContext 77 | } else { 78 | val nextContext = c.copy(failureCount = 0, successCount = successCount + 1) 79 | nextContext 80 | } 81 | } 82 | context.set(nextContext) 83 | } 84 | } 85 | 86 | def getContext(): CircuitBreakerContext = context.get() 87 | } 88 | 89 | object CircuitBreakerPolicy { 90 | sealed trait State 91 | case object Open extends State 92 | case object Close extends State 93 | case object HalfOpen extends State 94 | 95 | val NeverOpen = new CircuitBreakerPolicy(0, 0, 0.second) 96 | 97 | def apply( 98 | failureThreshold: Int, 99 | successThreshold: Int, 100 | retryDuration: FiniteDuration, 101 | onClose: (CircuitBreakerContext) => Unit = _ => (), 102 | onOpen: (CircuitBreakerContext) => Unit = _ => (), 103 | onHalfOpen: (CircuitBreakerContext) => Unit = _ => () 104 | ): CircuitBreakerPolicy = { 105 | new CircuitBreakerPolicy( 106 | failureThreshold = failureThreshold, 107 | successThreshold = successThreshold, 108 | retryDuration = retryDuration, 109 | onClose = onClose, 110 | onOpen = onOpen, 111 | onHalfOpen = onHalfOpen 112 | ) 113 | } 114 | 115 | } -------------------------------------------------------------------------------- /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. 202 | --------------------------------------------------------------------------------