From 3448d279e00ece118ee0bc4f0267d5cdf1cab73a Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Sun, 19 Mar 2017 19:19:02 -0700 Subject: [PATCH 01/61] Initial commit --- .gitignore | 10 ++++ README.md | 3 + app/Filters.java | 26 +++++++++ app/Module.java | 65 +++++++++++++++++++++ app/controllers/HomeController.java | 15 +++++ app/v1/post/JPAPostRepository.java | 60 +++++++++++++++++++ app/v1/post/PostAction.java | 76 +++++++++++++++++++++++++ app/v1/post/PostController.java | 49 ++++++++++++++++ app/v1/post/PostData.java | 25 ++++++++ app/v1/post/PostExecutionContext.java | 36 ++++++++++++ app/v1/post/PostRepository.java | 15 +++++ app/v1/post/PostResource.java | 45 +++++++++++++++ app/v1/post/PostResourceHandler.java | 62 ++++++++++++++++++++ app/v1/post/TimeoutPostRepository.java | 61 ++++++++++++++++++++ app/views/index.scala.html | 39 +++++++++++++ app/views/timeout.scala.html | 13 +++++ build.sbt | 25 ++++++++ conf/META-INF/persistence.xml | 15 +++++ conf/application.conf | 43 ++++++++++++++ conf/logback.xml | 39 +++++++++++++ conf/posts.routes | 5 ++ conf/routes | 3 + gatling/simulation/GatlingSpec.scala | 39 +++++++++++++ project/build.properties | 1 + project/plugins.sbt | 6 ++ project/scaffold.sbt | 3 + public/images/favicon.png | Bin 0 -> 687 bytes public/javascripts/main.js | 0 public/stylesheets/main.css | 0 test/controllers/IntegrationTest.java | 37 ++++++++++++ 30 files changed, 816 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 app/Filters.java create mode 100644 app/Module.java create mode 100644 app/controllers/HomeController.java create mode 100644 app/v1/post/JPAPostRepository.java create mode 100644 app/v1/post/PostAction.java create mode 100644 app/v1/post/PostController.java create mode 100644 app/v1/post/PostData.java create mode 100644 app/v1/post/PostExecutionContext.java create mode 100644 app/v1/post/PostRepository.java create mode 100644 app/v1/post/PostResource.java create mode 100644 app/v1/post/PostResourceHandler.java create mode 100644 app/v1/post/TimeoutPostRepository.java create mode 100644 app/views/index.scala.html create mode 100644 app/views/timeout.scala.html create mode 100644 build.sbt create mode 100644 conf/META-INF/persistence.xml create mode 100644 conf/application.conf create mode 100644 conf/logback.xml create mode 100644 conf/posts.routes create mode 100644 conf/routes create mode 100644 gatling/simulation/GatlingSpec.scala create mode 100644 project/build.properties create mode 100644 project/plugins.sbt create mode 100644 project/scaffold.sbt create mode 100644 public/images/favicon.png create mode 100644 public/javascripts/main.js create mode 100644 public/stylesheets/main.css create mode 100644 test/controllers/IntegrationTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..8fddb041c --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +logs +target +/.idea +/.idea_modules +/.classpath +/.project +/.settings +/RUNNING_PID + +/.vscode diff --git a/README.md b/README.md new file mode 100644 index 000000000..bf555999d --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# play-java-rest-api + +A REST API showing Play with a JPA backend. \ No newline at end of file diff --git a/app/Filters.java b/app/Filters.java new file mode 100644 index 000000000..185428ed1 --- /dev/null +++ b/app/Filters.java @@ -0,0 +1,26 @@ +import play.filters.csrf.CSRFFilter; +import play.filters.headers.SecurityHeadersFilter; +import play.filters.hosts.AllowedHostsFilter; +import play.http.DefaultHttpFilters; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * This class configures filters that run on every request. This + * class is queried by Play to get a list of filters. + * + * https://www.playframework.com/documentation/latest/ScalaCsrf + * https://www.playframework.com/documentation/latest/AllowedHostsFilter + * https://www.playframework.com/documentation/latest/SecurityHeaders + */ +@Singleton +public class Filters extends DefaultHttpFilters { + + @Inject + public Filters(CSRFFilter csrfFilter, + AllowedHostsFilter allowedHostsFilter, + SecurityHeadersFilter securityHeadersFilter) { + super(csrfFilter, allowedHostsFilter, securityHeadersFilter); + } +} diff --git a/app/Module.java b/app/Module.java new file mode 100644 index 000000000..c807f67ff --- /dev/null +++ b/app/Module.java @@ -0,0 +1,65 @@ +import com.codahale.metrics.ConsoleReporter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Slf4jReporter; +import com.google.inject.AbstractModule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import v1.post.PostRepository; +import v1.post.JPAPostRepository; +import v1.post.TimeoutPostRepository; + +import javax.inject.Provider; +import java.util.concurrent.TimeUnit; + +/** + * This class is a Guice module that tells Guice how to bind several + * different types. This Guice module is created when the Play + * application starts. + * + * Play will automatically use any class called `Module` that is in + * the root package. You can create modules in other locations by + * adding `play.modules.enabled` settings to the `application.conf` + * configuration file. + */ +public class Module extends AbstractModule { + + @Override + public void configure() { + bind(MetricRegistry.class).toProvider(MetricRegistryProvider.class).asEagerSingleton(); + bind(PostRepository.class).to(JPAPostRepository.class).asEagerSingleton(); + //bind(PostRepository.class).to(TimeoutPostRepository.class).asEagerSingleton(); + } +} + +class MetricRegistryProvider implements Provider { + private static final Logger logger = LoggerFactory.getLogger("application.Metrics"); + + private static final MetricRegistry registry = new MetricRegistry(); + + private void consoleReporter() { + ConsoleReporter reporter = ConsoleReporter.forRegistry(registry) + .convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS) + .build(); + reporter.start(1, TimeUnit.SECONDS); + } + + private void slf4jReporter() { + final Slf4jReporter reporter = Slf4jReporter.forRegistry(registry) + .outputTo(logger) + .convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS) + .build(); + reporter.start(1, TimeUnit.MINUTES); + } + + public MetricRegistryProvider() { + //consoleReporter(); + slf4jReporter(); + } + + @Override + public MetricRegistry get() { + return registry; + } +} diff --git a/app/controllers/HomeController.java b/app/controllers/HomeController.java new file mode 100644 index 000000000..ba65a87ff --- /dev/null +++ b/app/controllers/HomeController.java @@ -0,0 +1,15 @@ +package controllers; + +import play.mvc.*; + +/** + * This controller contains an action to handle HTTP requests + * to the application's home page. + */ +public class HomeController extends Controller { + + public Result index() { + return ok(views.html.index.render()); + } + +} diff --git a/app/v1/post/JPAPostRepository.java b/app/v1/post/JPAPostRepository.java new file mode 100644 index 000000000..45236277d --- /dev/null +++ b/app/v1/post/JPAPostRepository.java @@ -0,0 +1,60 @@ +package v1.post; + +import play.Logger; +import play.db.jpa.JPAApi; + +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; + +import static java.util.concurrent.CompletableFuture.*; + +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import java.util.stream.Stream; + +/** + * A repository that provides a non-blocking API with a custom execution context. + */ +public class JPAPostRepository implements PostRepository { + + private static final Logger.ALogger logger = Logger.of(JPAPostRepository.class); + + private final JPAApi jpaApi; + private final PostExecutionContext ec; + + @Inject + public JPAPostRepository(JPAApi api, PostExecutionContext ec) { + this.jpaApi = api; + this.ec = ec; + } + + @Override + public CompletionStage> list() { + // Run the list inside this execution context. + return supplyAsync(() -> jpaApi.withTransaction(em -> select(em)), ec); + } + + @Override + public CompletionStage create(PostData postData) { + return supplyAsync(() -> jpaApi.withTransaction(em -> insert(em, postData)), ec); + } + + @Override + public CompletionStage> get(Integer id) { + return supplyAsync(() -> jpaApi.withTransaction(em -> lookup(em, id)), ec); + } + + private Optional lookup(EntityManager em, Integer id) { + return Optional.ofNullable(em.find(PostData.class, id)); + } + + private Stream select(EntityManager em) { + TypedQuery query = em.createQuery("SELECT p FROM PostData p", PostData.class); + return query.getResultList().stream(); + } + + private PostData insert(EntityManager em, PostData postData) { + return em.merge(postData); + } +} diff --git a/app/v1/post/PostAction.java b/app/v1/post/PostAction.java new file mode 100644 index 000000000..2bcda5161 --- /dev/null +++ b/app/v1/post/PostAction.java @@ -0,0 +1,76 @@ +package v1.post; + +import akka.actor.ActorSystem; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import net.jodah.failsafe.AsyncFailsafe; +import net.jodah.failsafe.Failsafe; +import net.jodah.failsafe.RetryPolicy; +import net.jodah.failsafe.util.concurrent.Scheduler; +import play.Logger; +import play.libs.concurrent.Futures; +import play.libs.concurrent.HttpExecutionContext; +import play.mvc.Http; +import play.mvc.Result; +import play.mvc.Results; +import scala.concurrent.duration.FiniteDuration; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.concurrent.*; +import java.util.function.Function; + +import static com.codahale.metrics.MetricRegistry.name; +import static play.mvc.Http.Status.GATEWAY_TIMEOUT; +import static play.mvc.Http.Status.NOT_ACCEPTABLE; + +public class PostAction extends play.mvc.Action.Simple { + private final Logger.ALogger logger = play.Logger.of("application.PostAction"); + + private final Meter requestsMeter; + private final Timer responsesTimer; + private final HttpExecutionContext ec; + + @Singleton + @Inject + public PostAction(MetricRegistry metrics, HttpExecutionContext ec, ActorSystem actorSystem) { + this.ec = ec; + this.requestsMeter = metrics.meter("requestsMeter"); + this.responsesTimer = metrics.timer(name(PostAction.class, "responsesTimer")); + } + + public CompletionStage call(Http.Context ctx) { + if (logger.isDebugEnabled()) { + logger.debug("call: ctx = " + ctx); + } + requestsMeter.mark(); + if (ctx.request().accepts("application/json")) { + final Timer.Context time = responsesTimer.time(); + return timeout(doCall(ctx), 1L, TimeUnit.SECONDS).whenComplete((r, e) -> time.close()); + } else { + return CompletableFuture.completedFuture( + status(NOT_ACCEPTABLE, "We only accept application/json") + ); + } + } + + private CompletionStage doCall(Http.Context ctx) { + return delegate.call(ctx).handleAsync((result, e) -> { + if (e != null) { + logger.error(e.getMessage(), e); + return internalServerError(); + } else { + return result; + } + }, ec.current()); + } + + private CompletionStage timeout(final CompletionStage stage, final long delay, final TimeUnit unit) { + final CompletionStage timeoutFuture = Futures.timeout(delay, unit).handle((v, e) -> { + return Results.status(GATEWAY_TIMEOUT, views.html.timeout.render()); + }); + return stage.applyToEither(timeoutFuture, Function.identity()); + } + +} diff --git a/app/v1/post/PostController.java b/app/v1/post/PostController.java new file mode 100644 index 000000000..4eab018c7 --- /dev/null +++ b/app/v1/post/PostController.java @@ -0,0 +1,49 @@ +package v1.post; + +import com.fasterxml.jackson.databind.JsonNode; +import play.libs.Json; +import play.libs.concurrent.HttpExecutionContext; +import play.mvc.*; + +import javax.inject.Inject; +import java.util.List; +import java.util.concurrent.CompletionStage; +import java.util.stream.Collectors; + +@With(PostAction.class) +public class PostController extends Controller { + + private HttpExecutionContext ec; + private PostResourceHandler handler; + + @Inject + public PostController(HttpExecutionContext ec, PostResourceHandler handler) { + this.ec = ec; + this.handler = handler; + } + + public CompletionStage list() { + return handler.find().thenApplyAsync(posts -> { + final List postList = posts.collect(Collectors.toList()); + return ok(Json.toJson(postList)); + }, ec.current()); + } + + public CompletionStage show(String id) { + return handler.lookup(id).thenApplyAsync(optionalResource -> { + return optionalResource.map(resource -> + ok(Json.toJson(resource)) + ).orElseGet(() -> + notFound() + ); + }, ec.current()); + } + + public CompletionStage create() { + JsonNode json = request().body().asJson(); + final PostResource resource = Json.fromJson(json, PostResource.class); + return handler.create(resource).thenApplyAsync(savedResource -> { + return ok(Json.toJson(savedResource)); + }, ec.current()); + } +} diff --git a/app/v1/post/PostData.java b/app/v1/post/PostData.java new file mode 100644 index 000000000..dfb10ae29 --- /dev/null +++ b/app/v1/post/PostData.java @@ -0,0 +1,25 @@ +package v1.post; + +import javax.persistence.*; + +/** + * Data returned from the database + */ +@Entity +@Table(name = "posts") +public class PostData { + + public PostData() { + } + + public PostData(String title, String body) { + this.title = title; + this.body = body; + } + + @Id + @GeneratedValue(strategy= GenerationType.AUTO) + public Long id; + public String title; + public String body; +} diff --git a/app/v1/post/PostExecutionContext.java b/app/v1/post/PostExecutionContext.java new file mode 100644 index 000000000..fe9352665 --- /dev/null +++ b/app/v1/post/PostExecutionContext.java @@ -0,0 +1,36 @@ +package v1.post; + +import akka.actor.ActorSystem; +import scala.concurrent.ExecutionContext; +import scala.concurrent.ExecutionContextExecutor; + +import javax.inject.Inject; + +/** + * Custom execution context wired to "post.repository" thread pool + */ +public class PostExecutionContext implements ExecutionContextExecutor { + private final ExecutionContext executionContext; + + private static final String name = "post.repository"; + + @Inject + public PostExecutionContext(ActorSystem actorSystem) { + this.executionContext = actorSystem.dispatchers().lookup(name); + } + + @Override + public ExecutionContext prepare() { + return executionContext.prepare(); + } + + @Override + public void execute(Runnable command) { + executionContext.execute(command); + } + + @Override + public void reportFailure(Throwable cause) { + executionContext.reportFailure(cause); + } +} diff --git a/app/v1/post/PostRepository.java b/app/v1/post/PostRepository.java new file mode 100644 index 000000000..35e3f1fa9 --- /dev/null +++ b/app/v1/post/PostRepository.java @@ -0,0 +1,15 @@ +package v1.post; + +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import java.util.stream.Stream; + +public interface PostRepository { + + CompletionStage> list(); + + CompletionStage create(PostData postData); + + CompletionStage> get(Integer id); +} + diff --git a/app/v1/post/PostResource.java b/app/v1/post/PostResource.java new file mode 100644 index 000000000..299acf1b9 --- /dev/null +++ b/app/v1/post/PostResource.java @@ -0,0 +1,45 @@ +package v1.post; + +/** + * Resource for the API. This is a presentation class for frontend work. + */ +public class PostResource { + private String id; + private String link; + private String title; + private String body; + + public PostResource() { + } + + public PostResource(String id, String link, String title, String body) { + this.id = id; + this.link = link; + this.title = title; + this.body = body; + } + + public PostResource(PostData data, String link) { + this.id = data.id.toString(); + this.link = link; + this.title = data.title; + this.body = data.body; + } + + public String getId() { + return id; + } + + public String getLink() { + return link; + } + + public String getTitle() { + return title; + } + + public String getBody() { + return body; + } + +} diff --git a/app/v1/post/PostResourceHandler.java b/app/v1/post/PostResourceHandler.java new file mode 100644 index 000000000..499c31791 --- /dev/null +++ b/app/v1/post/PostResourceHandler.java @@ -0,0 +1,62 @@ +package v1.post; + +import com.palominolabs.http.url.UrlBuilder; +import play.libs.concurrent.HttpExecutionContext; +import play.mvc.Http; + +import javax.inject.Inject; +import java.nio.charset.CharacterCodingException; +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import java.util.stream.Stream; + +/** + * Handles presentation of Post resources, which map to JSON. + */ +public class PostResourceHandler { + + private final PostRepository repository; + private final HttpExecutionContext ec; + + @Inject + public PostResourceHandler(PostRepository repository, HttpExecutionContext ec) { + this.repository = repository; + this.ec = ec; + } + + public CompletionStage> find() { + return repository.list().thenApplyAsync(postDataStream -> { + return postDataStream.map(data -> new PostResource(data, link(data))); + }, ec.current()); + } + + public CompletionStage create(PostResource resource) { + final PostData data = new PostData(resource.getTitle(), resource.getBody()); + return repository.create(data).thenApplyAsync(savedData -> { + return new PostResource(savedData, link(savedData)); + }, ec.current()); + } + + public CompletionStage> lookup(String id) { + return repository.get(Integer.parseInt(id)).thenApplyAsync(optionalData -> { + return optionalData.map(data -> new PostResource(data, link(data))); + }, ec.current()); + } + + private String link(PostData data) { + // Make a point of using request context here, even if it's a bit strange + final Http.Request request = Http.Context.current().request(); + final String[] hostPort = request.host().split(":"); + String host = hostPort[0]; + int port = (hostPort.length == 2) ? Integer.parseInt(hostPort[1]) : -1; + final String scheme = request.secure() ? "https" : "http"; + try { + return UrlBuilder.forHost(scheme, host, port) + .pathSegments("v1", "posts", data.id.toString()) + .toUrlString(); + } catch (CharacterCodingException e) { + throw new IllegalStateException(e); + } + } + +} diff --git a/app/v1/post/TimeoutPostRepository.java b/app/v1/post/TimeoutPostRepository.java new file mode 100644 index 000000000..b8b190deb --- /dev/null +++ b/app/v1/post/TimeoutPostRepository.java @@ -0,0 +1,61 @@ +package v1.post; + +import play.db.jpa.JPAApi; + +import javax.inject.Inject; +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import java.util.stream.Stream; + +import static java.util.concurrent.CompletableFuture.supplyAsync; + +/** + * + */ +public class TimeoutPostRepository implements PostRepository { + private final PostExecutionContext ec; + + @Inject + public TimeoutPostRepository(PostExecutionContext ec) { + this.ec = ec; + } + + @Override + public CompletionStage> list() { + return supplyAsync(() -> { + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return Stream.empty(); + }, ec); + } + + @Override + public CompletionStage create(PostData postData) { + return supplyAsync(() -> { + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + postData.id = System.currentTimeMillis(); + return postData; + }, ec); + } + + @Override + public CompletionStage> get(Integer id) { + return supplyAsync(() -> { + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return Optional.empty(); + }, ec); + } +} diff --git a/app/views/index.scala.html b/app/views/index.scala.html new file mode 100644 index 000000000..b6c289527 --- /dev/null +++ b/app/views/index.scala.html @@ -0,0 +1,39 @@ +@() + + + + + + Play REST API + + + +

Play REST API

+ +

+ This is a placeholder page to show you the REST API. Use httpie to post JSON to the application. +

+ +

+ To see all posts, you can do a GET: +

+ + +
+    http GET localhost:9000/v1/posts
+
+ +

+ To create new posts, do a post +

+ +

+    http POST localhost:9000/v1/posts title="Some title" body="Some Body"
+
+ +

+ You can always look at the API directly: /v1/posts +

+ + + diff --git a/app/views/timeout.scala.html b/app/views/timeout.scala.html new file mode 100644 index 000000000..50a3898c8 --- /dev/null +++ b/app/views/timeout.scala.html @@ -0,0 +1,13 @@ +@() + + + + + Timeout Page + + +

Timeout Page

+ + Database timed out, so showing this page instead. + + diff --git a/build.sbt b/build.sbt new file mode 100644 index 000000000..03ea8c59a --- /dev/null +++ b/build.sbt @@ -0,0 +1,25 @@ +name := """play-java-rest-api""" + +version := "1.0-SNAPSHOT" + +lazy val GatlingTest = config("gatling") extend Test + +lazy val root = (project in file(".")).enablePlugins(PlayJava, GatlingPlugin).configs(GatlingTest) + .settings(inConfig(GatlingTest)(Defaults.testSettings): _*) + .settings( + scalaSource in GatlingTest := baseDirectory.value / "/gatling/simulation" + ) + +scalaVersion := "2.11.8" + +libraryDependencies += filters +libraryDependencies += javaJpa + +libraryDependencies += "org.hibernate" % "hibernate-entitymanager" % "5.1.0.Final" +libraryDependencies += "io.dropwizard.metrics" % "metrics-core" % "3.2.1" +libraryDependencies += "com.palominolabs.http" % "url-builder" % "1.1.0" + +libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "2.2.2" % Test +libraryDependencies += "io.gatling" % "gatling-test-framework" % "2.2.2" % Test + +PlayKeys.externalizeResources := false diff --git a/conf/META-INF/persistence.xml b/conf/META-INF/persistence.xml new file mode 100644 index 000000000..7b14af7ec --- /dev/null +++ b/conf/META-INF/persistence.xml @@ -0,0 +1,15 @@ + + + + org.hibernate.jpa.HibernatePersistenceProvider + DefaultDS + + + + + + + diff --git a/conf/application.conf b/conf/application.conf new file mode 100644 index 000000000..cf5aee39b --- /dev/null +++ b/conf/application.conf @@ -0,0 +1,43 @@ +# This is the main configuration file for the application. +# https://www.playframework.com/documentation/latest/ConfigFile + +db { + default.driver = org.h2.Driver + default.url = "jdbc:h2:mem:play" + #default.username = sa + #default.password = "" + + default.jndiName=DefaultDS + + # You can turn on SQL logging for any datasource + default.logSql=true +} + +# Point JPA at our database configuration +jpa.default=defaultPersistenceUnit + +# Set up automatic database evolutions +//play.evolutions { +// db.default.enabled = true +// autoApply = true +//} + +# Number of database connections +fixedConnectionPool = 9 + +# Set Hikari to fixed size +play.db { + prototype { + hikaricp.minimumIdle = ${fixedConnectionPool} + hikaricp.maximumPoolSize = ${fixedConnectionPool} + } +} + +# Job queue sized to HikariCP connection pool +post.repository { + executor = "thread-pool-executor" + throughput = 1 + thread-pool-executor { + fixed-pool-size = ${fixedConnectionPool} + } +} diff --git a/conf/logback.xml b/conf/logback.xml new file mode 100644 index 000000000..19e0e19ef --- /dev/null +++ b/conf/logback.xml @@ -0,0 +1,39 @@ + + + + + + + ${application.home:-.}/logs/application.log + + %date [%level] from %logger in %thread - %message%n%xException + + + + + + %coloredLevel %logger{15} - %message%n%xException{10} + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/posts.routes b/conf/posts.routes new file mode 100644 index 000000000..a8a549c6e --- /dev/null +++ b/conf/posts.routes @@ -0,0 +1,5 @@ + +GET / v1.post.PostController.list +POST / v1.post.PostController.create + +GET /:id v1.post.PostController.show(id) diff --git a/conf/routes b/conf/routes new file mode 100644 index 000000000..3b829b608 --- /dev/null +++ b/conf/routes @@ -0,0 +1,3 @@ +GET / controllers.HomeController.index + +-> /v1/posts posts.Routes diff --git a/gatling/simulation/GatlingSpec.scala b/gatling/simulation/GatlingSpec.scala new file mode 100644 index 000000000..5252fdeff --- /dev/null +++ b/gatling/simulation/GatlingSpec.scala @@ -0,0 +1,39 @@ +package simulation + +import io.gatling.core.Predef._ +import io.gatling.http.Predef._ +import scala.concurrent.duration._ +import scala.language.postfixOps + +// run with "sbt gatling:test" on another machine so you don't have resources contending. +// http://gatling.io/docs/2.2.2/general/simulation_structure.html#simulation-structure +class GatlingSpec extends Simulation { + + // change this to another machine, make sure you have Play running in producion mode + // i.e. sbt stage / sbt dist and running the script + val httpConf = http.baseURL("http://localhost:9000") + + val readClients = scenario("Clients").exec(Index.refreshManyTimes) + + setUp( + // For reference, this hits 25% CPU on a 5820K with 32 GB, running both server and load test. + // In general, you want to ramp up load slowly, and measure with a JVM that has been "warmed up": + // https://groups.google.com/forum/#!topic/gatling/mD15aj-fyo4 + readClients.inject(rampUsers(2000) over (100 seconds)).protocols(httpConf) + ) +} + +object Index { + + def post = { + val body = StringBody("""{ "title": "hello", "body": "world" }""") + exec(http("Index").post("/v1/posts").body(body).asJSON.check(status.is(200))).pause(1) + } + + def refreshAfterOneSecond = + exec(http("Index").get("/").check(status.is(200))).pause(1) + + val refreshManyTimes = repeat(500) { + refreshAfterOneSecond + } +} diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 000000000..27e88aa11 --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.13 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 000000000..1f6a6c7fb --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,6 @@ +// The Play plugin +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.13") + +// Load testing tool: +// http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html +addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.0") diff --git a/project/scaffold.sbt b/project/scaffold.sbt new file mode 100644 index 000000000..91355f76e --- /dev/null +++ b/project/scaffold.sbt @@ -0,0 +1,3 @@ +// Defines scaffolding (found under .g8 folder) +// sbt "g8Scaffold form" +addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.7.1") diff --git a/public/images/favicon.png b/public/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..c7d92d2ae47434d9a61c90bc205e099b673b9dd5 GIT binary patch literal 687 zcmV;g0#N;lP)ezT{T_ZJ?}AL z5NC{NW(ESID=>(O3&Eg8 zmA9J&6c`h4_f6L;=bU>_H8aNG`kfvCj9zomNt)?O;rzWqZs0LEt%1WB218%1fo9uB zsW^yhBR7C(mqN%GEK9&msg0~ zWY?#bf4q8G-~2KttQZ($odJvy&_-~f?9*ThK@fwR$U^1)p*8=_+^3BXx0$i1BC8XC zr21u6D5nVK&^!dOAw&|1E;qC3uFNj3*Jj#&%Oje@0D-nhfmM*o%^5f}-pxQ07(95H z3|LoV>V19w#rLgmRmtVy9!T3M3FUE3><0T8&b3yEsWcLW`0(=1+qsqc(k(ymBLK0h zK!6(6$7MX~M`-QA2$wk7n(7hhkJ}4Rwi-Vd(_ZFX1Yk7TXuB0IJYpo@kLb2G8m)E{ z`9v=!hi}fOytKckfN^C@6+Z*+MVI9-W_p@_3yyR#UYc0FTpD}i#k>c!wYCS)4v@E$ zchZCo=zV@)`v^$;V18ixdjFMY#q^2$wEX%{f(XD8POnsn$bpbClpC@hPxjzyO>pY|*pF3UU2tYcCN?rUk{Sskej70Mmu9vPwMYhO1m{AxAt(zqDT|0jP7FaX=6 V`?~}E4H^Id002ovPDHLkV1hC)G==~G literal 0 HcmV?d00001 diff --git a/public/javascripts/main.js b/public/javascripts/main.js new file mode 100644 index 000000000..e69de29bb diff --git a/public/stylesheets/main.css b/public/stylesheets/main.css new file mode 100644 index 000000000..e69de29bb diff --git a/test/controllers/IntegrationTest.java b/test/controllers/IntegrationTest.java new file mode 100644 index 000000000..4e37ba933 --- /dev/null +++ b/test/controllers/IntegrationTest.java @@ -0,0 +1,37 @@ +package controllers; + +import org.junit.Test; +import play.Application; +import play.inject.guice.GuiceApplicationBuilder; +import play.mvc.Http; +import play.mvc.Result; +import play.test.WithApplication; +import v1.post.PostData; +import v1.post.PostRepository; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThat; +import static play.test.Helpers.*; + +public class IntegrationTest extends WithApplication { + + @Override + protected Application provideApplication() { + return new GuiceApplicationBuilder().build(); + } + + @Test + public void testIndex() { + PostRepository repository = app.injector().instanceOf(PostRepository.class); + repository.create(new PostData("title", "body")); + + Http.RequestBuilder request = new Http.RequestBuilder() + .method(GET) + .uri("/v1/post"); + + Result result = route(app, request); + final String body = contentAsString(result); + assertThat(body, containsString("body")); + } + +} From 45c1d961d56655cae99f19d87e0f3c571779c699 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Sun, 19 Mar 2017 20:29:13 -0700 Subject: [PATCH 02/61] Fix failsafe imports --- app/v1/post/PostAction.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/v1/post/PostAction.java b/app/v1/post/PostAction.java index 2bcda5161..11baa44c4 100644 --- a/app/v1/post/PostAction.java +++ b/app/v1/post/PostAction.java @@ -4,10 +4,6 @@ import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; -import net.jodah.failsafe.AsyncFailsafe; -import net.jodah.failsafe.Failsafe; -import net.jodah.failsafe.RetryPolicy; -import net.jodah.failsafe.util.concurrent.Scheduler; import play.Logger; import play.libs.concurrent.Futures; import play.libs.concurrent.HttpExecutionContext; From 4d3f94aec41bdbd4856af793eb38a80290ac7e53 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Mon, 20 Mar 2017 10:48:20 -0700 Subject: [PATCH 03/61] Add circuit breaker --- app/v1/post/JPAPostRepository.java | 25 +++++++++++++++++-------- build.sbt | 1 + 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app/v1/post/JPAPostRepository.java b/app/v1/post/JPAPostRepository.java index 45236277d..ba6ce76a2 100644 --- a/app/v1/post/JPAPostRepository.java +++ b/app/v1/post/JPAPostRepository.java @@ -1,27 +1,33 @@ package v1.post; +import net.jodah.failsafe.CircuitBreaker; +import net.jodah.failsafe.Failsafe; import play.Logger; import play.db.jpa.JPAApi; import javax.inject.Inject; +import javax.inject.Singleton; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; - -import static java.util.concurrent.CompletableFuture.*; - import java.util.Optional; import java.util.concurrent.CompletionStage; +import java.util.function.Function; import java.util.stream.Stream; +import static java.util.concurrent.CompletableFuture.supplyAsync; + /** - * A repository that provides a non-blocking API with a custom execution context. + * A repository that provides a non-blocking API with a custom execution context + * and circuit breaker. */ +@Singleton public class JPAPostRepository implements PostRepository { private static final Logger.ALogger logger = Logger.of(JPAPostRepository.class); private final JPAApi jpaApi; private final PostExecutionContext ec; + private final CircuitBreaker circuitBreaker = new CircuitBreaker(); @Inject public JPAPostRepository(JPAApi api, PostExecutionContext ec) { @@ -31,18 +37,21 @@ public JPAPostRepository(JPAApi api, PostExecutionContext ec) { @Override public CompletionStage> list() { - // Run the list inside this execution context. - return supplyAsync(() -> jpaApi.withTransaction(em -> select(em)), ec); + return supplyAsync(() -> wrap(em -> select(em)), ec); } @Override public CompletionStage create(PostData postData) { - return supplyAsync(() -> jpaApi.withTransaction(em -> insert(em, postData)), ec); + return supplyAsync(() -> wrap(em -> insert(em, postData)), ec); } @Override public CompletionStage> get(Integer id) { - return supplyAsync(() -> jpaApi.withTransaction(em -> lookup(em, id)), ec); + return supplyAsync(() -> wrap(em -> lookup(em, id)), ec); + } + + private T wrap(Function function) { + return Failsafe.with(circuitBreaker).get(() -> jpaApi.withTransaction(function)); } private Optional lookup(EntityManager em, Integer id) { diff --git a/build.sbt b/build.sbt index 03ea8c59a..fc7f4438f 100644 --- a/build.sbt +++ b/build.sbt @@ -18,6 +18,7 @@ libraryDependencies += javaJpa libraryDependencies += "org.hibernate" % "hibernate-entitymanager" % "5.1.0.Final" libraryDependencies += "io.dropwizard.metrics" % "metrics-core" % "3.2.1" libraryDependencies += "com.palominolabs.http" % "url-builder" % "1.1.0" +libraryDependencies += "net.jodah" % "failsafe" % "1.0.3" libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "2.2.2" % Test libraryDependencies += "io.gatling" % "gatling-test-framework" % "2.2.2" % Test From bcaffce9406772ddea229fe7dba98371b7374e95 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Mon, 20 Mar 2017 11:03:54 -0700 Subject: [PATCH 04/61] Add travis --- .travis.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..a8602aead --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +language: scala +scala: +- 2.11.8 +jdk: +- oraclejdk8 +cache: + directories: + - "$HOME/.ivy2/cache" +before_cache: +- rm -rf $HOME/.ivy2/cache/com.typesafe.play/* +- rm -rf $HOME/.ivy2/cache/scala_*/sbt_*/com.typesafe.play/* +- find $HOME/.ivy2/cache -name "ivydata-*.properties" -print0 | xargs -n10 -0 rm +notifications: + slack: + secure: XYhzPxvvw2QxwR238NJ7pb6KSUArMwn+LzLLt12xS3QQj+NN4sMcK5yDU7ZMvZ6qgZmiEB/AgmF2Ws4jibr1rB4k469+PEFBwFk2zuPMqM46UeK3KBPmlRQlRWxTuJcO+WUpkt29cbZBJ8+e1y3as+15UfOC/2CQ6579tOUjvJ8KzbFvI4RrreQZuKYmpCQRcfOw6ChCrpHW5rQlvoJpM4LHSl8Ib+DMZyfsc3WDhEGmzvm0TfLJ3fQiD2xYByiNcVUqUu06QLeciniwi7PBl4Cx9xwvJoF7Klms7j1zeatWknJ2tagX5mRuleoGQRCud73/aaGV85NoQQRUUtblXkNBHXjBXEpPL6lf9S+4GxjvC+LXK/Z+WWAdIYvwDpiqspJ6SWIfW5PQK9okZFhLaaDvqsGnEntXffccTTlzyuTTmyn7Y9U6U6ZB9Blq3U5GJd+ncPX99XpACxumV+Xz8NgtlkIG86qLgNKl1qm6GItkaSRYGvdWgtslAgh1I7cbQD8tZ73m60Z7Yk3ZWxNY0I+I27SKocwyyRENHi/n2zA2WycTswUzxXEJ72+23mpWrMhnOY4VczviqPRgdpKEYncLqcv9yLDNLM4V1uDi16xywxqqE9coew6PWw0gUQWBi2/x3ZPNnnEg/cC++LR2oHKmGpahGgQGqGCJYyH5Ylw= From 4b852b6b9e04589fe1ba5c6f89e7335f687ab53d Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Mon, 20 Mar 2017 11:22:20 -0700 Subject: [PATCH 05/61] Remove post repository logger --- app/v1/post/JPAPostRepository.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/v1/post/JPAPostRepository.java b/app/v1/post/JPAPostRepository.java index ba6ce76a2..6cb80cbe3 100644 --- a/app/v1/post/JPAPostRepository.java +++ b/app/v1/post/JPAPostRepository.java @@ -2,7 +2,6 @@ import net.jodah.failsafe.CircuitBreaker; import net.jodah.failsafe.Failsafe; -import play.Logger; import play.db.jpa.JPAApi; import javax.inject.Inject; @@ -23,8 +22,6 @@ @Singleton public class JPAPostRepository implements PostRepository { - private static final Logger.ALogger logger = Logger.of(JPAPostRepository.class); - private final JPAApi jpaApi; private final PostExecutionContext ec; private final CircuitBreaker circuitBreaker = new CircuitBreaker(); From 67dc89087996d0120cff2eeeaa7abdf5f6cf20ff Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Mon, 20 Mar 2017 12:05:33 -0700 Subject: [PATCH 06/61] More documentation and cleanup --- app/v1/post/PostAction.java | 5 +++-- app/v1/post/TimeoutPostRepository.java | 6 +----- conf/application.conf | 13 ++----------- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/app/v1/post/PostAction.java b/app/v1/post/PostAction.java index 11baa44c4..bf27d7d50 100644 --- a/app/v1/post/PostAction.java +++ b/app/v1/post/PostAction.java @@ -10,11 +10,12 @@ import play.mvc.Http; import play.mvc.Result; import play.mvc.Results; -import scala.concurrent.duration.FiniteDuration; import javax.inject.Inject; import javax.inject.Singleton; -import java.util.concurrent.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; import java.util.function.Function; import static com.codahale.metrics.MetricRegistry.name; diff --git a/app/v1/post/TimeoutPostRepository.java b/app/v1/post/TimeoutPostRepository.java index b8b190deb..2f10a7e42 100644 --- a/app/v1/post/TimeoutPostRepository.java +++ b/app/v1/post/TimeoutPostRepository.java @@ -1,10 +1,6 @@ package v1.post; -import play.db.jpa.JPAApi; - import javax.inject.Inject; -import java.util.Collection; -import java.util.Collections; import java.util.Optional; import java.util.concurrent.CompletionStage; import java.util.stream.Stream; @@ -12,7 +8,7 @@ import static java.util.concurrent.CompletableFuture.supplyAsync; /** - * + * A repository which only times out. */ public class TimeoutPostRepository implements PostRepository { private final PostExecutionContext ec; diff --git a/conf/application.conf b/conf/application.conf index cf5aee39b..b190c3fa7 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -4,25 +4,16 @@ db { default.driver = org.h2.Driver default.url = "jdbc:h2:mem:play" - #default.username = sa - #default.password = "" + # Provided for JPA access default.jndiName=DefaultDS - - # You can turn on SQL logging for any datasource - default.logSql=true } # Point JPA at our database configuration jpa.default=defaultPersistenceUnit -# Set up automatic database evolutions -//play.evolutions { -// db.default.enabled = true -// autoApply = true -//} - # Number of database connections +# See https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing fixedConnectionPool = 9 # Set Hikari to fixed size From 1d446cd39b0b39be2cb3fd2a0eb56135c383e5df Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Tue, 21 Mar 2017 22:38:15 -0700 Subject: [PATCH 07/61] More failsafe work --- app/v1/post/JPAPostRepository.java | 14 +++++++++++++- app/v1/post/PostAction.java | 11 ++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/v1/post/JPAPostRepository.java b/app/v1/post/JPAPostRepository.java index 6cb80cbe3..1b064f84a 100644 --- a/app/v1/post/JPAPostRepository.java +++ b/app/v1/post/JPAPostRepository.java @@ -1,7 +1,9 @@ package v1.post; import net.jodah.failsafe.CircuitBreaker; +import net.jodah.failsafe.CircuitBreakerOpenException; import net.jodah.failsafe.Failsafe; +import net.jodah.failsafe.function.Predicate; import play.db.jpa.JPAApi; import javax.inject.Inject; @@ -48,7 +50,11 @@ public CompletionStage> get(Integer id) { } private T wrap(Function function) { - return Failsafe.with(circuitBreaker).get(() -> jpaApi.withTransaction(function)); + try { + return Failsafe.with(circuitBreaker).get(() -> jpaApi.withTransaction(function)); + } catch (CircuitBreakerOpenException e) { + throw new UnavailableRepositoryException(e.getMessage(), e); + } } private Optional lookup(EntityManager em, Integer id) { @@ -64,3 +70,9 @@ private PostData insert(EntityManager em, PostData postData) { return em.merge(postData); } } + +class UnavailableRepositoryException extends RuntimeException { + public UnavailableRepositoryException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/app/v1/post/PostAction.java b/app/v1/post/PostAction.java index bf27d7d50..749acb5e7 100644 --- a/app/v1/post/PostAction.java +++ b/app/v1/post/PostAction.java @@ -21,6 +21,7 @@ import static com.codahale.metrics.MetricRegistry.name; import static play.mvc.Http.Status.GATEWAY_TIMEOUT; import static play.mvc.Http.Status.NOT_ACCEPTABLE; +import static play.mvc.Http.Status.SERVICE_UNAVAILABLE; public class PostAction extends play.mvc.Action.Simple { private final Logger.ALogger logger = play.Logger.of("application.PostAction"); @@ -38,8 +39,8 @@ public PostAction(MetricRegistry metrics, HttpExecutionContext ec, ActorSystem a } public CompletionStage call(Http.Context ctx) { - if (logger.isDebugEnabled()) { - logger.debug("call: ctx = " + ctx); + if (logger.isTraceEnabled()) { + logger.trace("call: ctx = " + ctx); } requestsMeter.mark(); if (ctx.request().accepts("application/json")) { @@ -56,7 +57,11 @@ private CompletionStage doCall(Http.Context ctx) { return delegate.call(ctx).handleAsync((result, e) -> { if (e != null) { logger.error(e.getMessage(), e); - return internalServerError(); + if (e instanceof UnavailableRepositoryException) { + return Results.status(SERVICE_UNAVAILABLE, views.html.timeout.render()); + } else { + return internalServerError(); + } } else { return result; } From 055bf9a8e31749147aa51917e89d43eee74a512f Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Wed, 22 Mar 2017 08:21:18 -0700 Subject: [PATCH 08/61] Make circuit breaker trip --- app/Module.java | 2 +- app/v1/post/JPAPostRepository.java | 30 ++++++++++++------------------ app/v1/post/PostAction.java | 16 +++++++++++++--- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/app/Module.java b/app/Module.java index c807f67ff..fd5016fd4 100644 --- a/app/Module.java +++ b/app/Module.java @@ -55,7 +55,7 @@ private void slf4jReporter() { public MetricRegistryProvider() { //consoleReporter(); - slf4jReporter(); + // slf4jReporter(); } @Override diff --git a/app/v1/post/JPAPostRepository.java b/app/v1/post/JPAPostRepository.java index 1b064f84a..4f506081e 100644 --- a/app/v1/post/JPAPostRepository.java +++ b/app/v1/post/JPAPostRepository.java @@ -1,8 +1,9 @@ package v1.post; -import net.jodah.failsafe.CircuitBreaker; -import net.jodah.failsafe.CircuitBreakerOpenException; -import net.jodah.failsafe.Failsafe; +import net.jodah.failsafe.*; +import net.jodah.failsafe.function.CheckedFunction; +import net.jodah.failsafe.function.CheckedRunnable; +import net.jodah.failsafe.function.ContextualCallable; import net.jodah.failsafe.function.Predicate; import play.db.jpa.JPAApi; @@ -10,7 +11,9 @@ import javax.inject.Singleton; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; +import java.sql.SQLException; import java.util.Optional; +import java.util.concurrent.Callable; import java.util.concurrent.CompletionStage; import java.util.function.Function; import java.util.stream.Stream; @@ -26,7 +29,7 @@ public class JPAPostRepository implements PostRepository { private final JPAApi jpaApi; private final PostExecutionContext ec; - private final CircuitBreaker circuitBreaker = new CircuitBreaker(); + private final CircuitBreaker circuitBreaker = new CircuitBreaker().withFailureThreshold(1).withSuccessThreshold(3); @Inject public JPAPostRepository(JPAApi api, PostExecutionContext ec) { @@ -46,19 +49,16 @@ public CompletionStage create(PostData postData) { @Override public CompletionStage> get(Integer id) { - return supplyAsync(() -> wrap(em -> lookup(em, id)), ec); + return supplyAsync(() -> wrap(em -> Failsafe.with(circuitBreaker).get(() -> lookup(em, id))), ec); } private T wrap(Function function) { - try { - return Failsafe.with(circuitBreaker).get(() -> jpaApi.withTransaction(function)); - } catch (CircuitBreakerOpenException e) { - throw new UnavailableRepositoryException(e.getMessage(), e); - } + return jpaApi.withTransaction(function); } - private Optional lookup(EntityManager em, Integer id) { - return Optional.ofNullable(em.find(PostData.class, id)); + private Optional lookup(EntityManager em, Integer id) throws SQLException { + throw new SQLException("Call this to cause the circuit breaker to trip"); + //return Optional.ofNullable(em.find(PostData.class, id)); } private Stream select(EntityManager em) { @@ -70,9 +70,3 @@ private PostData insert(EntityManager em, PostData postData) { return em.merge(postData); } } - -class UnavailableRepositoryException extends RuntimeException { - public UnavailableRepositoryException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/app/v1/post/PostAction.java b/app/v1/post/PostAction.java index 749acb5e7..b242f22ee 100644 --- a/app/v1/post/PostAction.java +++ b/app/v1/post/PostAction.java @@ -4,6 +4,8 @@ import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; +import net.jodah.failsafe.CircuitBreakerOpenException; +import net.jodah.failsafe.FailsafeException; import play.Logger; import play.libs.concurrent.Futures; import play.libs.concurrent.HttpExecutionContext; @@ -14,6 +16,7 @@ import javax.inject.Inject; import javax.inject.Singleton; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import java.util.function.Function; @@ -56,10 +59,17 @@ public CompletionStage call(Http.Context ctx) { private CompletionStage doCall(Http.Context ctx) { return delegate.call(ctx).handleAsync((result, e) -> { if (e != null) { - logger.error(e.getMessage(), e); - if (e instanceof UnavailableRepositoryException) { - return Results.status(SERVICE_UNAVAILABLE, views.html.timeout.render()); + if (e instanceof CompletionException) { + Throwable completionException = e.getCause(); + if (completionException instanceof FailsafeException) { + logger.error("Circuit breaker is open!", completionException); + return Results.status(SERVICE_UNAVAILABLE, "Service has timed out"); + } else { + logger.error("Direct exception " + e.getMessage(), e); + return internalServerError(); + } } else { + logger.error("Unknown exception " + e.getMessage(), e); return internalServerError(); } } else { From 2dafabb54bd5a65a3ac37aa7351254c3887a5831 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Thu, 23 Mar 2017 11:56:26 -0700 Subject: [PATCH 09/61] Move timeout repository to test circuit breaker --- {app/v1/post => test/controllers}/TimeoutPostRepository.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {app/v1/post => test/controllers}/TimeoutPostRepository.java (100%) diff --git a/app/v1/post/TimeoutPostRepository.java b/test/controllers/TimeoutPostRepository.java similarity index 100% rename from app/v1/post/TimeoutPostRepository.java rename to test/controllers/TimeoutPostRepository.java From a4fd9850d99d055cc9234a1e001585641debc586 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Thu, 23 Mar 2017 16:37:59 -0700 Subject: [PATCH 10/61] Update for testing of circuit breaker --- app/Module.java | 2 - app/v1/post/JPAPostRepository.java | 10 ++-- app/v1/post/PostAction.java | 18 +++++- build.sbt | 3 + test/controllers/TimeoutPostRepository.java | 57 ------------------- test/{controllers => it}/IntegrationTest.java | 21 ++++++- 6 files changed, 43 insertions(+), 68 deletions(-) delete mode 100644 test/controllers/TimeoutPostRepository.java rename test/{controllers => it}/IntegrationTest.java (61%) diff --git a/app/Module.java b/app/Module.java index c807f67ff..a97ec80da 100644 --- a/app/Module.java +++ b/app/Module.java @@ -6,7 +6,6 @@ import org.slf4j.LoggerFactory; import v1.post.PostRepository; import v1.post.JPAPostRepository; -import v1.post.TimeoutPostRepository; import javax.inject.Provider; import java.util.concurrent.TimeUnit; @@ -27,7 +26,6 @@ public class Module extends AbstractModule { public void configure() { bind(MetricRegistry.class).toProvider(MetricRegistryProvider.class).asEagerSingleton(); bind(PostRepository.class).to(JPAPostRepository.class).asEagerSingleton(); - //bind(PostRepository.class).to(TimeoutPostRepository.class).asEagerSingleton(); } } diff --git a/app/v1/post/JPAPostRepository.java b/app/v1/post/JPAPostRepository.java index 6cb80cbe3..187debaea 100644 --- a/app/v1/post/JPAPostRepository.java +++ b/app/v1/post/JPAPostRepository.java @@ -8,6 +8,7 @@ import javax.inject.Singleton; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; +import java.sql.SQLException; import java.util.Optional; import java.util.concurrent.CompletionStage; import java.util.function.Function; @@ -44,15 +45,16 @@ public CompletionStage create(PostData postData) { @Override public CompletionStage> get(Integer id) { - return supplyAsync(() -> wrap(em -> lookup(em, id)), ec); + return supplyAsync(() -> wrap(em -> Failsafe.with(circuitBreaker).get(() -> lookup(em, id))), ec); } private T wrap(Function function) { - return Failsafe.with(circuitBreaker).get(() -> jpaApi.withTransaction(function)); + return jpaApi.withTransaction(function); } - private Optional lookup(EntityManager em, Integer id) { - return Optional.ofNullable(em.find(PostData.class, id)); + private Optional lookup(EntityManager em, Integer id) throws SQLException { + throw new SQLException("Call this to cause the circuit breaker to trip"); + //return Optional.ofNullable(em.find(PostData.class, id)); } private Stream select(EntityManager em) { diff --git a/app/v1/post/PostAction.java b/app/v1/post/PostAction.java index bf27d7d50..8b28e12f0 100644 --- a/app/v1/post/PostAction.java +++ b/app/v1/post/PostAction.java @@ -4,6 +4,7 @@ import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; +import net.jodah.failsafe.FailsafeException; import play.Logger; import play.libs.concurrent.Futures; import play.libs.concurrent.HttpExecutionContext; @@ -14,6 +15,7 @@ import javax.inject.Inject; import javax.inject.Singleton; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import java.util.function.Function; @@ -21,6 +23,7 @@ import static com.codahale.metrics.MetricRegistry.name; import static play.mvc.Http.Status.GATEWAY_TIMEOUT; import static play.mvc.Http.Status.NOT_ACCEPTABLE; +import static play.mvc.Http.Status.SERVICE_UNAVAILABLE; public class PostAction extends play.mvc.Action.Simple { private final Logger.ALogger logger = play.Logger.of("application.PostAction"); @@ -55,8 +58,19 @@ public CompletionStage call(Http.Context ctx) { private CompletionStage doCall(Http.Context ctx) { return delegate.call(ctx).handleAsync((result, e) -> { if (e != null) { - logger.error(e.getMessage(), e); - return internalServerError(); + if (e instanceof CompletionException) { + Throwable completionException = e.getCause(); + if (completionException instanceof FailsafeException) { + logger.error("Circuit breaker is open!", completionException); + return Results.status(SERVICE_UNAVAILABLE, "Service has timed out"); + } else { + logger.error("Direct exception " + e.getMessage(), e); + return internalServerError(); + } + } else { + logger.error("Unknown exception " + e.getMessage(), e); + return internalServerError(); + } } else { return result; } diff --git a/build.sbt b/build.sbt index fc7f4438f..504a590a8 100644 --- a/build.sbt +++ b/build.sbt @@ -19,8 +19,11 @@ libraryDependencies += "org.hibernate" % "hibernate-entitymanager" % "5.1.0.Fina libraryDependencies += "io.dropwizard.metrics" % "metrics-core" % "3.2.1" libraryDependencies += "com.palominolabs.http" % "url-builder" % "1.1.0" libraryDependencies += "net.jodah" % "failsafe" % "1.0.3" +//libraryDependencies += "com.novocode" % "junit-interface" % "0.11" libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "2.2.2" % Test libraryDependencies += "io.gatling" % "gatling-test-framework" % "2.2.2" % Test PlayKeys.externalizeResources := false + +testOptions in Test := Seq(Tests.Argument(TestFrameworks.JUnit, "-a", "-v")) \ No newline at end of file diff --git a/test/controllers/TimeoutPostRepository.java b/test/controllers/TimeoutPostRepository.java deleted file mode 100644 index 2f10a7e42..000000000 --- a/test/controllers/TimeoutPostRepository.java +++ /dev/null @@ -1,57 +0,0 @@ -package v1.post; - -import javax.inject.Inject; -import java.util.Optional; -import java.util.concurrent.CompletionStage; -import java.util.stream.Stream; - -import static java.util.concurrent.CompletableFuture.supplyAsync; - -/** - * A repository which only times out. - */ -public class TimeoutPostRepository implements PostRepository { - private final PostExecutionContext ec; - - @Inject - public TimeoutPostRepository(PostExecutionContext ec) { - this.ec = ec; - } - - @Override - public CompletionStage> list() { - return supplyAsync(() -> { - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - return Stream.empty(); - }, ec); - } - - @Override - public CompletionStage create(PostData postData) { - return supplyAsync(() -> { - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - postData.id = System.currentTimeMillis(); - return postData; - }, ec); - } - - @Override - public CompletionStage> get(Integer id) { - return supplyAsync(() -> { - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - return Optional.empty(); - }, ec); - } -} diff --git a/test/controllers/IntegrationTest.java b/test/it/IntegrationTest.java similarity index 61% rename from test/controllers/IntegrationTest.java rename to test/it/IntegrationTest.java index 4e37ba933..461e15b06 100644 --- a/test/controllers/IntegrationTest.java +++ b/test/it/IntegrationTest.java @@ -1,5 +1,6 @@ -package controllers; +package it; +import com.google.inject.AbstractModule; import org.junit.Test; import play.Application; import play.inject.guice.GuiceApplicationBuilder; @@ -10,6 +11,7 @@ import v1.post.PostRepository; import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static play.test.Helpers.*; @@ -21,17 +23,30 @@ protected Application provideApplication() { } @Test - public void testIndex() { + public void testList() { PostRepository repository = app.injector().instanceOf(PostRepository.class); repository.create(new PostData("title", "body")); Http.RequestBuilder request = new Http.RequestBuilder() .method(GET) - .uri("/v1/post"); + .uri("/v1/posts"); Result result = route(app, request); final String body = contentAsString(result); assertThat(body, containsString("body")); } + @Test + public void testShow() { + PostRepository repository = app.injector().instanceOf(PostRepository.class); + repository.create(new PostData("title", "body")); + + Http.RequestBuilder request = new Http.RequestBuilder() + .method(GET) + .uri("/v1/posts/1"); + + Result result = route(app, request); + assertEquals(result.status(), SERVICE_UNAVAILABLE); + } + } From 5ae675653af24fb5ead4321398d3b3bb9ae55d09 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Thu, 23 Mar 2017 19:19:13 -0700 Subject: [PATCH 11/61] Add test for timeout functionality --- app/v1/post/JPAPostRepository.java | 19 +++++++++++++++++-- app/v1/post/PostController.java | 14 +++++++++++++- app/v1/post/PostRepository.java | 4 +++- app/v1/post/PostResourceHandler.java | 11 +++++++++-- build.sbt | 1 - conf/posts.routes | 1 + test/it/IntegrationTest.java | 26 +++++++++++++++++++++++--- 7 files changed, 66 insertions(+), 10 deletions(-) diff --git a/app/v1/post/JPAPostRepository.java b/app/v1/post/JPAPostRepository.java index 4f506081e..415162705 100644 --- a/app/v1/post/JPAPostRepository.java +++ b/app/v1/post/JPAPostRepository.java @@ -48,15 +48,20 @@ public CompletionStage create(PostData postData) { } @Override - public CompletionStage> get(Integer id) { + public CompletionStage> get(Long id) { return supplyAsync(() -> wrap(em -> Failsafe.with(circuitBreaker).get(() -> lookup(em, id))), ec); } + @Override + public CompletionStage> update(Long id, PostData postData) { + return supplyAsync(() -> wrap(em -> Failsafe.with(circuitBreaker).get(() -> modify(em, id, postData))), ec); + } + private T wrap(Function function) { return jpaApi.withTransaction(function); } - private Optional lookup(EntityManager em, Integer id) throws SQLException { + private Optional lookup(EntityManager em, Long id) throws SQLException { throw new SQLException("Call this to cause the circuit breaker to trip"); //return Optional.ofNullable(em.find(PostData.class, id)); } @@ -66,6 +71,16 @@ private Stream select(EntityManager em) { return query.getResultList().stream(); } + private Optional modify(EntityManager em, Long id, PostData postData) throws InterruptedException { + final PostData data = em.find(PostData.class, id); + if (data != null) { + data.title = postData.title; + data.body = postData.body; + } + Thread.sleep(10000L); + return Optional.ofNullable(data); + } + private PostData insert(EntityManager em, PostData postData) { return em.merge(postData); } diff --git a/app/v1/post/PostController.java b/app/v1/post/PostController.java index 4eab018c7..faf0aaf74 100644 --- a/app/v1/post/PostController.java +++ b/app/v1/post/PostController.java @@ -39,11 +39,23 @@ public CompletionStage show(String id) { }, ec.current()); } + public CompletionStage update(String id) { + JsonNode json = request().body().asJson(); + PostResource resource = Json.fromJson(json, PostResource.class); + return handler.update(id, resource).thenApplyAsync(optionalResource -> { + return optionalResource.map(r -> + ok(Json.toJson(r)) + ).orElseGet(() -> + notFound() + ); + }, ec.current()); + } + public CompletionStage create() { JsonNode json = request().body().asJson(); final PostResource resource = Json.fromJson(json, PostResource.class); return handler.create(resource).thenApplyAsync(savedResource -> { - return ok(Json.toJson(savedResource)); + return created(Json.toJson(savedResource)); }, ec.current()); } } diff --git a/app/v1/post/PostRepository.java b/app/v1/post/PostRepository.java index 35e3f1fa9..a22c324cd 100644 --- a/app/v1/post/PostRepository.java +++ b/app/v1/post/PostRepository.java @@ -10,6 +10,8 @@ public interface PostRepository { CompletionStage create(PostData postData); - CompletionStage> get(Integer id); + CompletionStage> get(Long id); + + CompletionStage> update(Long id, PostData postData); } diff --git a/app/v1/post/PostResourceHandler.java b/app/v1/post/PostResourceHandler.java index 499c31791..500f37efa 100644 --- a/app/v1/post/PostResourceHandler.java +++ b/app/v1/post/PostResourceHandler.java @@ -1,5 +1,6 @@ package v1.post; +import com.fasterxml.jackson.databind.JsonNode; import com.palominolabs.http.url.UrlBuilder; import play.libs.concurrent.HttpExecutionContext; import play.mvc.Http; @@ -38,11 +39,18 @@ public CompletionStage create(PostResource resource) { } public CompletionStage> lookup(String id) { - return repository.get(Integer.parseInt(id)).thenApplyAsync(optionalData -> { + return repository.get(Long.parseLong(id)).thenApplyAsync(optionalData -> { return optionalData.map(data -> new PostResource(data, link(data))); }, ec.current()); } + public CompletionStage> update(String id, PostResource resource) { + final PostData data = new PostData(resource.getTitle(), resource.getBody()); + return repository.update(Long.parseLong(id), data).thenApplyAsync(optionalData -> { + return optionalData.map(op -> new PostResource(op, link(op))); + }, ec.current()); + } + private String link(PostData data) { // Make a point of using request context here, even if it's a bit strange final Http.Request request = Http.Context.current().request(); @@ -58,5 +66,4 @@ private String link(PostData data) { throw new IllegalStateException(e); } } - } diff --git a/build.sbt b/build.sbt index 504a590a8..bc2a7f8b1 100644 --- a/build.sbt +++ b/build.sbt @@ -19,7 +19,6 @@ libraryDependencies += "org.hibernate" % "hibernate-entitymanager" % "5.1.0.Fina libraryDependencies += "io.dropwizard.metrics" % "metrics-core" % "3.2.1" libraryDependencies += "com.palominolabs.http" % "url-builder" % "1.1.0" libraryDependencies += "net.jodah" % "failsafe" % "1.0.3" -//libraryDependencies += "com.novocode" % "junit-interface" % "0.11" libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "2.2.2" % Test libraryDependencies += "io.gatling" % "gatling-test-framework" % "2.2.2" % Test diff --git a/conf/posts.routes b/conf/posts.routes index a8a549c6e..82dfda6cc 100644 --- a/conf/posts.routes +++ b/conf/posts.routes @@ -3,3 +3,4 @@ GET / v1.post.PostController.list POST / v1.post.PostController.create GET /:id v1.post.PostController.show(id) +POST /:id v1.post.PostController.update(id) diff --git a/test/it/IntegrationTest.java b/test/it/IntegrationTest.java index 461e15b06..302e070ed 100644 --- a/test/it/IntegrationTest.java +++ b/test/it/IntegrationTest.java @@ -1,17 +1,20 @@ package it; +import com.fasterxml.jackson.databind.JsonNode; import com.google.inject.AbstractModule; import org.junit.Test; import play.Application; import play.inject.guice.GuiceApplicationBuilder; +import play.libs.Json; import play.mvc.Http; import play.mvc.Result; import play.test.WithApplication; import v1.post.PostData; import v1.post.PostRepository; +import v1.post.PostResource; import static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.assertEquals; +import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; import static play.test.Helpers.*; @@ -37,7 +40,23 @@ public void testList() { } @Test - public void testShow() { + public void testTimeoutOnUpdate() { + PostRepository repository = app.injector().instanceOf(PostRepository.class); + repository.create(new PostData("title", "body")); + + JsonNode json = Json.toJson(new PostResource("1", "http://localhost:9000/v1/posts/1", "some title", "somebody")); + + Http.RequestBuilder request = new Http.RequestBuilder() + .method(POST) + .bodyJson(json) + .uri("/v1/posts/1"); + + Result result = route(app, request); + assertThat(result.status(), equalTo(GATEWAY_TIMEOUT)); + } + + @Test + public void testCircuitBreakerOnShow() { PostRepository repository = app.injector().instanceOf(PostRepository.class); repository.create(new PostData("title", "body")); @@ -46,7 +65,8 @@ public void testShow() { .uri("/v1/posts/1"); Result result = route(app, request); - assertEquals(result.status(), SERVICE_UNAVAILABLE); + assertThat(result.status(), equalTo(SERVICE_UNAVAILABLE)); } + } From b1ed6a2c886f1d48f05c15201433b8e6357fc792 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Sun, 2 Apr 2017 11:14:23 -0700 Subject: [PATCH 12/61] Update README.md --- README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bf555999d..62e6c1661 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,19 @@ # play-java-rest-api -A REST API showing Play with a JPA backend. \ No newline at end of file +A REST API showing Play with a JPA backend. For the Scala version, please see https://github.com/playframework/play-scala-rest-api-example + +## Best Practices for Blocking API + +If you look at the controller: https://github.com/playframework/play-java-rest-api-example/blob/master/app/v1/post/PostController.java +then you can see that when calling out to a blocking API like JDBC, you should put it behind an asynchronous boundary -- in practice, this means using the CompletionStage API to make sure that you're not blocking the rendering thread while the database call is going on in the background. + +``` +public CompletionStage list() { + return handler.find().thenApplyAsync(posts -> { + final List postList = posts.collect(Collectors.toList()); + return ok(Json.toJson(postList)); + }, ec.current()); +} +``` + +There is more detail in https://www.playframework.com/documentation/2.5.x/ThreadPools -- notably, you can always bump up the number of threads in the rendering thread pool rather than do this -- but it gives you an idea of best practices. From ad8897665708ea98011095682df965c52e494b32 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Mon, 10 Apr 2017 19:31:55 -0700 Subject: [PATCH 13/61] Update readme --- README.md | 11 +++++++---- build.sbt | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 62e6c1661..026848175 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -# play-java-rest-api +[](https://travis-ci.org/playframework/play-java-rest-api-example) + + +# play-java-rest-api-example A REST API showing Play with a JPA backend. For the Scala version, please see https://github.com/playframework/play-scala-rest-api-example @@ -7,13 +10,13 @@ A REST API showing Play with a JPA backend. For the Scala version, please see h If you look at the controller: https://github.com/playframework/play-java-rest-api-example/blob/master/app/v1/post/PostController.java then you can see that when calling out to a blocking API like JDBC, you should put it behind an asynchronous boundary -- in practice, this means using the CompletionStage API to make sure that you're not blocking the rendering thread while the database call is going on in the background. -``` +```java public CompletionStage list() { return handler.find().thenApplyAsync(posts -> { final List postList = posts.collect(Collectors.toList()); return ok(Json.toJson(postList)); - }, ec.current()); + }, ec.current()); } ``` -There is more detail in https://www.playframework.com/documentation/2.5.x/ThreadPools -- notably, you can always bump up the number of threads in the rendering thread pool rather than do this -- but it gives you an idea of best practices. +There is more detail in https://www.playframework.com/documentation/latest/ThreadPools -- notably, you can always bump up the number of threads in the rendering thread pool rather than do this -- but it gives you an idea of best practices. diff --git a/build.sbt b/build.sbt index bc2a7f8b1..74937ce88 100644 --- a/build.sbt +++ b/build.sbt @@ -1,4 +1,4 @@ -name := """play-java-rest-api""" +name := """play-java-rest-api-example""" version := "1.0-SNAPSHOT" From a642053e11bc57aa229849d88a429d2322323f57 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Mon, 10 Apr 2017 21:17:04 -0700 Subject: [PATCH 14/61] Add SNAPSHOT --- build.sbt | 7 ++++--- conf/application.conf | 3 +++ project/build.properties | 2 +- project/plugins.sbt | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index 74937ce88..bfa9e853c 100644 --- a/build.sbt +++ b/build.sbt @@ -12,10 +12,11 @@ lazy val root = (project in file(".")).enablePlugins(PlayJava, GatlingPlugin).co scalaVersion := "2.11.8" -libraryDependencies += filters +libraryDependencies += guice libraryDependencies += javaJpa +libraryDependencies += "com.h2database" % "h2" % "1.4.192" -libraryDependencies += "org.hibernate" % "hibernate-entitymanager" % "5.1.0.Final" +libraryDependencies += "org.hibernate" % "hibernate-core" % "5.2.9.Final" libraryDependencies += "io.dropwizard.metrics" % "metrics-core" % "3.2.1" libraryDependencies += "com.palominolabs.http" % "url-builder" % "1.1.0" libraryDependencies += "net.jodah" % "failsafe" % "1.0.3" @@ -25,4 +26,4 @@ libraryDependencies += "io.gatling" % "gatling-test-framework" % "2.2.2" % Test PlayKeys.externalizeResources := false -testOptions in Test := Seq(Tests.Argument(TestFrameworks.JUnit, "-a", "-v")) \ No newline at end of file +testOptions in Test := Seq(Tests.Argument(TestFrameworks.JUnit, "-a", "-v")) diff --git a/conf/application.conf b/conf/application.conf index b190c3fa7..a8a62d08f 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -24,6 +24,9 @@ play.db { } } +# disable the built in filters +play.http.filters = play.api.http.NoHttpFilters + # Job queue sized to HikariCP connection pool post.repository { executor = "thread-pool-executor" diff --git a/project/build.properties b/project/build.properties index 27e88aa11..64317fdae 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.13 +sbt.version=0.13.15 diff --git a/project/plugins.sbt b/project/plugins.sbt index 1f6a6c7fb..b1dfc1ea8 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.13") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M4") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From 6215b3aa09fad1bea5c757ced016298b27a3bf68 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Tue, 18 Apr 2017 13:11:58 -0700 Subject: [PATCH 15/61] Updated with template-control on 2017-04-18T20:11:58.735Z /LICENSE: wrote /LICENSE **/build.sbt: scalaVersion := "2.12.2" --- LICENSE | 7 +++++++ build.sbt | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..b018ae2bc --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +License +------- +Written in 2016 by Lightbend + +To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. + +You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see . diff --git a/build.sbt b/build.sbt index bfa9e853c..0cf422a44 100644 --- a/build.sbt +++ b/build.sbt @@ -10,7 +10,7 @@ lazy val root = (project in file(".")).enablePlugins(PlayJava, GatlingPlugin).co scalaSource in GatlingTest := baseDirectory.value / "/gatling/simulation" ) -scalaVersion := "2.11.8" +scalaVersion := "2.12.2" libraryDependencies += guice libraryDependencies += javaJpa From 899b0b459def7333e245ddb626fe33b1304a214b Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Tue, 18 Apr 2017 15:00:25 -0700 Subject: [PATCH 16/61] Updated with template-control on 2017-04-18T22:00:25.328Z /LICENSE: wrote /LICENSE **/build.sbt: libraryDependencies += "com.h2database" % "h2" % "1.4.194" --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 0cf422a44..713fea0e4 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,7 @@ scalaVersion := "2.12.2" libraryDependencies += guice libraryDependencies += javaJpa -libraryDependencies += "com.h2database" % "h2" % "1.4.192" +libraryDependencies += "com.h2database" % "h2" % "1.4.194" libraryDependencies += "org.hibernate" % "hibernate-core" % "5.2.9.Final" libraryDependencies += "io.dropwizard.metrics" % "metrics-core" % "3.2.1" From 78ab7291df1467b9486bd11f51ae5a56ccc46bed Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Thu, 27 Apr 2017 18:46:58 -0700 Subject: [PATCH 17/61] Updated with template-control on 2017-04-28T01:46:57.988Z /LICENSE: wrote /LICENSE **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M5") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index b1dfc1ea8..27179bc46 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M4") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M5") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From b5e7827f7c6010006ed71c2142f5bb061c054f11 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Fri, 28 Apr 2017 18:49:20 -0700 Subject: [PATCH 18/61] Update tests --- app/Filters.java | 26 ------------------------ app/v1/post/JPAPostRepository.java | 8 ++------ app/v1/post/PostAction.java | 29 +++++++++++++-------------- app/v1/post/PostExecutionContext.java | 25 +++-------------------- app/v1/post/PostResourceHandler.java | 1 - build.sbt | 2 +- conf/application.conf | 23 +++++++++------------ test/it/IntegrationTest.java | 1 - 8 files changed, 30 insertions(+), 85 deletions(-) delete mode 100644 app/Filters.java diff --git a/app/Filters.java b/app/Filters.java deleted file mode 100644 index 185428ed1..000000000 --- a/app/Filters.java +++ /dev/null @@ -1,26 +0,0 @@ -import play.filters.csrf.CSRFFilter; -import play.filters.headers.SecurityHeadersFilter; -import play.filters.hosts.AllowedHostsFilter; -import play.http.DefaultHttpFilters; - -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * This class configures filters that run on every request. This - * class is queried by Play to get a list of filters. - * - * https://www.playframework.com/documentation/latest/ScalaCsrf - * https://www.playframework.com/documentation/latest/AllowedHostsFilter - * https://www.playframework.com/documentation/latest/SecurityHeaders - */ -@Singleton -public class Filters extends DefaultHttpFilters { - - @Inject - public Filters(CSRFFilter csrfFilter, - AllowedHostsFilter allowedHostsFilter, - SecurityHeadersFilter securityHeadersFilter) { - super(csrfFilter, allowedHostsFilter, securityHeadersFilter); - } -} diff --git a/app/v1/post/JPAPostRepository.java b/app/v1/post/JPAPostRepository.java index 415162705..39d4529d3 100644 --- a/app/v1/post/JPAPostRepository.java +++ b/app/v1/post/JPAPostRepository.java @@ -1,10 +1,7 @@ package v1.post; -import net.jodah.failsafe.*; -import net.jodah.failsafe.function.CheckedFunction; -import net.jodah.failsafe.function.CheckedRunnable; -import net.jodah.failsafe.function.ContextualCallable; -import net.jodah.failsafe.function.Predicate; +import net.jodah.failsafe.CircuitBreaker; +import net.jodah.failsafe.Failsafe; import play.db.jpa.JPAApi; import javax.inject.Inject; @@ -13,7 +10,6 @@ import javax.persistence.TypedQuery; import java.sql.SQLException; import java.util.Optional; -import java.util.concurrent.Callable; import java.util.concurrent.CompletionStage; import java.util.function.Function; import java.util.stream.Stream; diff --git a/app/v1/post/PostAction.java b/app/v1/post/PostAction.java index dc09188cc..086c05838 100644 --- a/app/v1/post/PostAction.java +++ b/app/v1/post/PostAction.java @@ -4,7 +4,6 @@ import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; - import net.jodah.failsafe.FailsafeException; import play.Logger; import play.libs.concurrent.Futures; @@ -15,11 +14,7 @@ import javax.inject.Inject; import javax.inject.Singleton; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; +import java.util.concurrent.*; import static com.codahale.metrics.MetricRegistry.name; import static play.mvc.Http.Status.GATEWAY_TIMEOUT; @@ -32,11 +27,13 @@ public class PostAction extends play.mvc.Action.Simple { private final Meter requestsMeter; private final Timer responsesTimer; private final HttpExecutionContext ec; + private final Futures futures; @Singleton @Inject - public PostAction(MetricRegistry metrics, HttpExecutionContext ec, ActorSystem actorSystem) { + public PostAction(MetricRegistry metrics, HttpExecutionContext ec, ActorSystem actorSystem, Futures futures) { this.ec = ec; + this.futures = futures; this.requestsMeter = metrics.meter("requestsMeter"); this.responsesTimer = metrics.timer(name(PostAction.class, "responsesTimer")); } @@ -45,10 +42,19 @@ public CompletionStage call(Http.Context ctx) { if (logger.isTraceEnabled()) { logger.trace("call: ctx = " + ctx); } + requestsMeter.mark(); if (ctx.request().accepts("application/json")) { final Timer.Context time = responsesTimer.time(); - return timeout(doCall(ctx), 1L, TimeUnit.SECONDS).whenComplete((r, e) -> time.close()); + return futures.timeout(doCall(ctx), 1L, TimeUnit.SECONDS).handle((r, e) -> { + time.close(); + if (e instanceof TimeoutException) { + logger.info("Timeout after 1 second", e); + return Results.status(GATEWAY_TIMEOUT, views.html.timeout.render()); + } else { + return r; + } + }); } else { return CompletableFuture.completedFuture( status(NOT_ACCEPTABLE, "We only accept application/json") @@ -78,11 +84,4 @@ private CompletionStage doCall(Http.Context ctx) { }, ec.current()); } - private CompletionStage timeout(final CompletionStage stage, final long delay, final TimeUnit unit) { - final CompletionStage timeoutFuture = Futures.timeout(delay, unit).handle((v, e) -> { - return Results.status(GATEWAY_TIMEOUT, views.html.timeout.render()); - }); - return stage.applyToEither(timeoutFuture, Function.identity()); - } - } diff --git a/app/v1/post/PostExecutionContext.java b/app/v1/post/PostExecutionContext.java index fe9352665..17a5365c5 100644 --- a/app/v1/post/PostExecutionContext.java +++ b/app/v1/post/PostExecutionContext.java @@ -1,36 +1,17 @@ package v1.post; import akka.actor.ActorSystem; -import scala.concurrent.ExecutionContext; -import scala.concurrent.ExecutionContextExecutor; +import play.libs.concurrent.CustomExecutionContext; import javax.inject.Inject; /** * Custom execution context wired to "post.repository" thread pool */ -public class PostExecutionContext implements ExecutionContextExecutor { - private final ExecutionContext executionContext; - - private static final String name = "post.repository"; +public class PostExecutionContext extends CustomExecutionContext { @Inject public PostExecutionContext(ActorSystem actorSystem) { - this.executionContext = actorSystem.dispatchers().lookup(name); - } - - @Override - public ExecutionContext prepare() { - return executionContext.prepare(); - } - - @Override - public void execute(Runnable command) { - executionContext.execute(command); - } - - @Override - public void reportFailure(Throwable cause) { - executionContext.reportFailure(cause); + super(actorSystem, "post.repository"); } } diff --git a/app/v1/post/PostResourceHandler.java b/app/v1/post/PostResourceHandler.java index 500f37efa..722367625 100644 --- a/app/v1/post/PostResourceHandler.java +++ b/app/v1/post/PostResourceHandler.java @@ -1,6 +1,5 @@ package v1.post; -import com.fasterxml.jackson.databind.JsonNode; import com.palominolabs.http.url.UrlBuilder; import play.libs.concurrent.HttpExecutionContext; import play.mvc.Http; diff --git a/build.sbt b/build.sbt index 713fea0e4..8f50614c8 100644 --- a/build.sbt +++ b/build.sbt @@ -10,7 +10,7 @@ lazy val root = (project in file(".")).enablePlugins(PlayJava, GatlingPlugin).co scalaSource in GatlingTest := baseDirectory.value / "/gatling/simulation" ) -scalaVersion := "2.12.2" +scalaVersion in ThisBuild := "2.11.11" libraryDependencies += guice libraryDependencies += javaJpa diff --git a/conf/application.conf b/conf/application.conf index a8a62d08f..2c1026c69 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -1,13 +1,6 @@ # This is the main configuration file for the application. # https://www.playframework.com/documentation/latest/ConfigFile -db { - default.driver = org.h2.Driver - default.url = "jdbc:h2:mem:play" - - # Provided for JPA access - default.jndiName=DefaultDS -} # Point JPA at our database configuration jpa.default=defaultPersistenceUnit @@ -16,12 +9,16 @@ jpa.default=defaultPersistenceUnit # See https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing fixedConnectionPool = 9 -# Set Hikari to fixed size -play.db { - prototype { - hikaricp.minimumIdle = ${fixedConnectionPool} - hikaricp.maximumPoolSize = ${fixedConnectionPool} - } +db.default { + driver = org.h2.Driver + url = "jdbc:h2:mem:play" + + # Provided for JPA access + jndiName=DefaultDS + + # Set Hikari to fixed size + hikaricp.minimumIdle = ${fixedConnectionPool} + hikaricp.maximumPoolSize = ${fixedConnectionPool} } # disable the built in filters diff --git a/test/it/IntegrationTest.java b/test/it/IntegrationTest.java index 302e070ed..0d9a966f7 100644 --- a/test/it/IntegrationTest.java +++ b/test/it/IntegrationTest.java @@ -1,7 +1,6 @@ package it; import com.fasterxml.jackson.databind.JsonNode; -import com.google.inject.AbstractModule; import org.junit.Test; import play.Application; import play.inject.guice.GuiceApplicationBuilder; From 36e0d454eb800d9389de50c19fbb8568989d880a Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Tue, 2 May 2017 16:12:18 -0700 Subject: [PATCH 19/61] upgrade timeout --- app/v1/post/PostAction.java | 25 +++++++++---------------- build.sbt | 4 ++-- project/plugins.sbt | 2 +- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/app/v1/post/PostAction.java b/app/v1/post/PostAction.java index dc09188cc..15e357ddd 100644 --- a/app/v1/post/PostAction.java +++ b/app/v1/post/PostAction.java @@ -1,10 +1,8 @@ package v1.post; -import akka.actor.ActorSystem; import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; - import net.jodah.failsafe.FailsafeException; import play.Logger; import play.libs.concurrent.Futures; @@ -15,16 +13,13 @@ import javax.inject.Inject; import javax.inject.Singleton; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; -import java.util.function.Function; import static com.codahale.metrics.MetricRegistry.name; -import static play.mvc.Http.Status.GATEWAY_TIMEOUT; -import static play.mvc.Http.Status.NOT_ACCEPTABLE; -import static play.mvc.Http.Status.SERVICE_UNAVAILABLE; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static play.mvc.Http.Status.*; public class PostAction extends play.mvc.Action.Simple { private final Logger.ALogger logger = play.Logger.of("application.PostAction"); @@ -32,11 +27,13 @@ public class PostAction extends play.mvc.Action.Simple { private final Meter requestsMeter; private final Timer responsesTimer; private final HttpExecutionContext ec; + private final Futures futures; @Singleton @Inject - public PostAction(MetricRegistry metrics, HttpExecutionContext ec, ActorSystem actorSystem) { + public PostAction(MetricRegistry metrics, HttpExecutionContext ec, Futures futures) { this.ec = ec; + this.futures = futures; this.requestsMeter = metrics.meter("requestsMeter"); this.responsesTimer = metrics.timer(name(PostAction.class, "responsesTimer")); } @@ -48,9 +45,11 @@ public CompletionStage call(Http.Context ctx) { requestsMeter.mark(); if (ctx.request().accepts("application/json")) { final Timer.Context time = responsesTimer.time(); - return timeout(doCall(ctx), 1L, TimeUnit.SECONDS).whenComplete((r, e) -> time.close()); + return futures.timeout(doCall(ctx), 1L, TimeUnit.SECONDS).exceptionally(e -> { + return (Results.status(GATEWAY_TIMEOUT, views.html.timeout.render())); + }).whenComplete((r, e) -> time.close()); } else { - return CompletableFuture.completedFuture( + return completedFuture( status(NOT_ACCEPTABLE, "We only accept application/json") ); } @@ -78,11 +77,5 @@ private CompletionStage doCall(Http.Context ctx) { }, ec.current()); } - private CompletionStage timeout(final CompletionStage stage, final long delay, final TimeUnit unit) { - final CompletionStage timeoutFuture = Futures.timeout(delay, unit).handle((v, e) -> { - return Results.status(GATEWAY_TIMEOUT, views.html.timeout.render()); - }); - return stage.applyToEither(timeoutFuture, Function.identity()); - } } diff --git a/build.sbt b/build.sbt index 713fea0e4..c571b8b12 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ name := """play-java-rest-api-example""" -version := "1.0-SNAPSHOT" +version := "2.6.x" lazy val GatlingTest = config("gatling") extend Test @@ -10,7 +10,7 @@ lazy val root = (project in file(".")).enablePlugins(PlayJava, GatlingPlugin).co scalaSource in GatlingTest := baseDirectory.value / "/gatling/simulation" ) -scalaVersion := "2.12.2" +scalaVersion in ThisBuild := "2.11.11" libraryDependencies += guice libraryDependencies += javaJpa diff --git a/project/plugins.sbt b/project/plugins.sbt index b1dfc1ea8..27179bc46 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M4") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M5") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From 2c0020b1f2ff521b6cc6d862764770065530994a Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Sun, 4 Jun 2017 00:19:55 +0200 Subject: [PATCH 20/61] Updated with template-control on 2017-06-03T16:01:09.184Z (#17) **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-RC2") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 27179bc46..c86b404c7 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M5") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-RC2") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From 6e8b1a5d017e71adf1cd2781c9ba393a616419e5 Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Fri, 23 Jun 2017 02:18:35 +0000 Subject: [PATCH 21/61] Updated with template-control on 2017-06-23T02:18:35.880Z **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index c86b404c7..0fbf60c2e 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-RC2") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From e1826eb181e367256a20af270b5940cb060c9e5b Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Fri, 7 Jul 2017 00:45:45 +0000 Subject: [PATCH 22/61] Updated with template-control on 2017-07-07T00:45:45.094Z **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.1") **/plugins.sbt: addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.1") --- project/plugins.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 0fbf60c2e..0a65974f5 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.1") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html -addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.0") +addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.1") From 0b8aca1f72fa6b30f139a851d38985fca631fc41 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Thu, 20 Jul 2017 00:06:54 -0300 Subject: [PATCH 23/61] Updated with template-control on 2017-07-20T01:10:11.415Z (#22) **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.2") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 0a65974f5..f9ddea105 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.1") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.2") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From c8deb8b5ed48db74e59c714b9441c7b248b0d482 Mon Sep 17 00:00:00 2001 From: Rich Dougherty Date: Sat, 12 Aug 2017 16:32:40 +1200 Subject: [PATCH 24/61] Updated with template-control on 2017-08-12T02:43:21.025Z (#24) **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.3") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index f9ddea105..17c7824dd 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.2") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.3") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From 7882f0b0e076a850d79e3efc0471a27131e3adf5 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Mon, 4 Sep 2017 06:54:29 -0700 Subject: [PATCH 25/61] Upgrade to Scala 2.12 (#25) --- .travis.yml | 2 +- build.sbt | 20 ++++++++++++++++---- project/build.properties | 2 +- project/plugins.sbt | 2 +- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index a8602aead..28dba9c5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: scala scala: -- 2.11.8 +- 2.12.3 jdk: - oraclejdk8 cache: diff --git a/build.sbt b/build.sbt index c571b8b12..62dede18a 100644 --- a/build.sbt +++ b/build.sbt @@ -2,6 +2,20 @@ name := """play-java-rest-api-example""" version := "2.6.x" +inThisBuild( + List( + scalaVersion := "2.12.3", + dependencyOverrides := Set( + "org.codehaus.plexus" % "plexus-utils" % "3.0.18", + "com.google.code.findbugs" % "jsr305" % "3.0.1", + "com.google.guava" % "guava" % "22.0", + "com.typesafe.akka" %% "akka-stream" % "2.5.4", + "com.typesafe.akka" %% "akka-actor" % "2.5.4" + ) + ) +) + + lazy val GatlingTest = config("gatling") extend Test lazy val root = (project in file(".")).enablePlugins(PlayJava, GatlingPlugin).configs(GatlingTest) @@ -10,8 +24,6 @@ lazy val root = (project in file(".")).enablePlugins(PlayJava, GatlingPlugin).co scalaSource in GatlingTest := baseDirectory.value / "/gatling/simulation" ) -scalaVersion in ThisBuild := "2.11.11" - libraryDependencies += guice libraryDependencies += javaJpa libraryDependencies += "com.h2database" % "h2" % "1.4.194" @@ -21,8 +33,8 @@ libraryDependencies += "io.dropwizard.metrics" % "metrics-core" % "3.2.1" libraryDependencies += "com.palominolabs.http" % "url-builder" % "1.1.0" libraryDependencies += "net.jodah" % "failsafe" % "1.0.3" -libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "2.2.2" % Test -libraryDependencies += "io.gatling" % "gatling-test-framework" % "2.2.2" % Test +libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "2.3.0" % Test +libraryDependencies += "io.gatling" % "gatling-test-framework" % "2.3.0" % Test PlayKeys.externalizeResources := false diff --git a/project/build.properties b/project/build.properties index 64317fdae..c091b86ca 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.15 +sbt.version=0.13.16 diff --git a/project/plugins.sbt b/project/plugins.sbt index 17c7824dd..c2f77316c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,4 +3,4 @@ addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.3") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html -addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.1") +addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.2") From 1e7183fd11ce7789f8ab692837a46fb1e1b3e7b1 Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Thu, 14 Sep 2017 23:29:36 +0000 Subject: [PATCH 26/61] Updated with template-control on 2017-09-14T23:29:36.501Z **/build.properties: sbt.version=0.13.15 **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.5") **/plugins.sbt: addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.1") --- project/build.properties | 2 +- project/plugins.sbt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/project/build.properties b/project/build.properties index c091b86ca..64317fdae 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.16 +sbt.version=0.13.15 diff --git a/project/plugins.sbt b/project/plugins.sbt index c2f77316c..74ed006a3 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.3") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.5") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html -addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.2") +addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.1") From 126b9fbcdf4486b8fc12e909609b02c30dcf16da Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Sun, 15 Oct 2017 18:35:32 -0500 Subject: [PATCH 27/61] Upgrade branch 2.6.x using TemplateControl (#29) * Updated with template-control on 2017-10-05T23:19:00.152Z **/build.properties: sbt.version=1.0.2 **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.6") **/plugins.sbt: addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.2") * Remove giter8 scaffold --- .travis.yml | 1 + build.sbt | 6 +++--- project/build.properties | 2 +- project/plugins.sbt | 4 ++-- project/scaffold.sbt | 3 --- 5 files changed, 7 insertions(+), 9 deletions(-) delete mode 100644 project/scaffold.sbt diff --git a/.travis.yml b/.travis.yml index 28dba9c5d..70619a9e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: scala scala: +- 2.11.11 - 2.12.3 jdk: - oraclejdk8 diff --git a/build.sbt b/build.sbt index 62dede18a..254b8a6dd 100644 --- a/build.sbt +++ b/build.sbt @@ -5,12 +5,12 @@ version := "2.6.x" inThisBuild( List( scalaVersion := "2.12.3", - dependencyOverrides := Set( + dependencyOverrides := Seq( "org.codehaus.plexus" % "plexus-utils" % "3.0.18", "com.google.code.findbugs" % "jsr305" % "3.0.1", "com.google.guava" % "guava" % "22.0", - "com.typesafe.akka" %% "akka-stream" % "2.5.4", - "com.typesafe.akka" %% "akka-actor" % "2.5.4" + "com.typesafe.akka" %% "akka-stream" % "2.5.6", + "com.typesafe.akka" %% "akka-actor" % "2.5.6" ) ) ) diff --git a/project/build.properties b/project/build.properties index 64317fdae..b7dd3cb2a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.15 +sbt.version=1.0.2 diff --git a/project/plugins.sbt b/project/plugins.sbt index 74ed006a3..c5b902ebc 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.5") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.6") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html -addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.1") +addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.2") diff --git a/project/scaffold.sbt b/project/scaffold.sbt deleted file mode 100644 index 91355f76e..000000000 --- a/project/scaffold.sbt +++ /dev/null @@ -1,3 +0,0 @@ -// Defines scaffolding (found under .g8 folder) -// sbt "g8Scaffold form" -addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.7.1") From a87f9c316df3846f70869ec8ff51f845254adc8f Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Thu, 2 Nov 2017 02:51:11 +0000 Subject: [PATCH 28/61] Updated with template-control on 2017-11-02T02:51:11.509Z **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.7") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index c5b902ebc..de01d9805 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.6") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.7") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From 6c8b5aa7ad8782c4157e153e8421ce570c08d607 Mon Sep 17 00:00:00 2001 From: dusanstanojeviccs Date: Mon, 27 Nov 2017 10:05:46 -0800 Subject: [PATCH 29/61] update route changed from POST method to PUT method (#33) Updated posts.routes to use PUT method for updating. Updated IntegrationTest.java to use PUT method for running the test. --- conf/posts.routes | 2 +- test/it/IntegrationTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conf/posts.routes b/conf/posts.routes index 82dfda6cc..48e55b4b0 100644 --- a/conf/posts.routes +++ b/conf/posts.routes @@ -3,4 +3,4 @@ GET / v1.post.PostController.list POST / v1.post.PostController.create GET /:id v1.post.PostController.show(id) -POST /:id v1.post.PostController.update(id) +PUT /:id v1.post.PostController.update(id) diff --git a/test/it/IntegrationTest.java b/test/it/IntegrationTest.java index 0d9a966f7..1325f43f6 100644 --- a/test/it/IntegrationTest.java +++ b/test/it/IntegrationTest.java @@ -46,7 +46,7 @@ public void testTimeoutOnUpdate() { JsonNode json = Json.toJson(new PostResource("1", "http://localhost:9000/v1/posts/1", "some title", "somebody")); Http.RequestBuilder request = new Http.RequestBuilder() - .method(POST) + .method(PUT) .bodyJson(json) .uri("/v1/posts/1"); From 84ec37d374173bb731360940a6940482ab4f0c87 Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Sat, 9 Dec 2017 03:16:27 +0000 Subject: [PATCH 30/61] Updated with template-control on 2017-12-09T03:16:27.798Z **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.9") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index de01d9805..8e91abf62 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.7") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.9") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From b16f092a604589ed485fa0175c16cfb4d8581911 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Thu, 14 Dec 2017 15:13:00 -0200 Subject: [PATCH 31/61] Add Java 9 to travis build (#36) --- .gitignore | 2 + .travis.yml | 24 +++- README.md | 11 +- build.gradle | 64 +++++++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 172 +++++++++++++++++++++++ gradlew.bat | 84 +++++++++++ scripts/script-helper | 13 ++ scripts/test-gradle | 13 ++ scripts/test-sbt | 8 ++ 11 files changed, 388 insertions(+), 8 deletions(-) create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 scripts/script-helper create mode 100755 scripts/test-gradle create mode 100755 scripts/test-sbt diff --git a/.gitignore b/.gitignore index 8fddb041c..d4e13c0bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ logs target +build /.idea /.idea_modules /.classpath +/.gradle /.project /.settings /RUNNING_PID diff --git a/.travis.yml b/.travis.yml index 70619a9e4..36cb27b9f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,36 @@ language: scala scala: -- 2.11.11 -- 2.12.3 +# When updating Scala versions, also check the excludes +# in build matrix below. +- 2.11.12 +- 2.12.4 jdk: - oraclejdk8 +- oraclejdk9 +env: + matrix: + - SCRIPT=scripts/test-sbt + - SCRIPT=scripts/test-gradle +script: +- $SCRIPT cache: directories: - "$HOME/.ivy2/cache" + - "$HOME/.gradle/caches" before_cache: - rm -rf $HOME/.ivy2/cache/com.typesafe.play/* - rm -rf $HOME/.ivy2/cache/scala_*/sbt_*/com.typesafe.play/* - find $HOME/.ivy2/cache -name "ivydata-*.properties" -print0 | xargs -n10 -0 rm + +# Exclude some combinations from build matrix. See: +# https://docs.travis-ci.com/user/customizing-the-build/#Build-Matrix +matrix: + exclude: + - scala: 2.11.12 + jdk: oraclejdk9 + +# See https://blog.travis-ci.com/2014-03-13-slack-notifications/ +# created with travis encrypt command line tool notifications: slack: secure: XYhzPxvvw2QxwR238NJ7pb6KSUArMwn+LzLLt12xS3QQj+NN4sMcK5yDU7ZMvZ6qgZmiEB/AgmF2Ws4jibr1rB4k469+PEFBwFk2zuPMqM46UeK3KBPmlRQlRWxTuJcO+WUpkt29cbZBJ8+e1y3as+15UfOC/2CQ6579tOUjvJ8KzbFvI4RrreQZuKYmpCQRcfOw6ChCrpHW5rQlvoJpM4LHSl8Ib+DMZyfsc3WDhEGmzvm0TfLJ3fQiD2xYByiNcVUqUu06QLeciniwi7PBl4Cx9xwvJoF7Klms7j1zeatWknJ2tagX5mRuleoGQRCud73/aaGV85NoQQRUUtblXkNBHXjBXEpPL6lf9S+4GxjvC+LXK/Z+WWAdIYvwDpiqspJ6SWIfW5PQK9okZFhLaaDvqsGnEntXffccTTlzyuTTmyn7Y9U6U6ZB9Blq3U5GJd+ncPX99XpACxumV+Xz8NgtlkIG86qLgNKl1qm6GItkaSRYGvdWgtslAgh1I7cbQD8tZ73m60Z7Yk3ZWxNY0I+I27SKocwyyRENHi/n2zA2WycTswUzxXEJ72+23mpWrMhnOY4VczviqPRgdpKEYncLqcv9yLDNLM4V1uDi16xywxqqE9coew6PWw0gUQWBi2/x3ZPNnnEg/cC++LR2oHKmGpahGgQGqGCJYyH5Ylw= diff --git a/README.md b/README.md index 026848175..83f18c04d 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,12 @@ -[](https://travis-ci.org/playframework/play-java-rest-api-example) - - # play-java-rest-api-example -A REST API showing Play with a JPA backend. For the Scala version, please see https://github.com/playframework/play-scala-rest-api-example +[](https://travis-ci.org/playframework/play-java-rest-api-example) + +A REST API showing Play with a JPA backend. For the Scala version, please see . ## Best Practices for Blocking API -If you look at the controller: https://github.com/playframework/play-java-rest-api-example/blob/master/app/v1/post/PostController.java +If you look at the controller: then you can see that when calling out to a blocking API like JDBC, you should put it behind an asynchronous boundary -- in practice, this means using the CompletionStage API to make sure that you're not blocking the rendering thread while the database call is going on in the background. ```java @@ -19,4 +18,4 @@ public CompletionStage list() { } ``` -There is more detail in https://www.playframework.com/documentation/latest/ThreadPools -- notably, you can always bump up the number of threads in the rendering thread pool rather than do this -- but it gives you an idea of best practices. +There is more detail in -- notably, you can always bump up the number of threads in the rendering thread pool rather than do this -- but it gives you an idea of best practices. diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..58a3a739f --- /dev/null +++ b/build.gradle @@ -0,0 +1,64 @@ +plugins { + id 'play' + id 'idea' + id "com.github.lkishalmi.gatling" version "0.7.1" +} + +def playVersion = '2.6.9' +def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") +def gatlingVersion = "2.3.0" + +model { + components { + play { + platform play: playVersion, scala: scalaVersion, java: '1.8' + injectedRoutesGenerator = true + + sources { + twirlTemplates { + defaultImports = TwirlImports.JAVA + } + } + } + } +} + +project.sourceSets { + gatling { + scala.srcDirs = ["gatling"] + } +} + +gatling { + sourceRoot = "gatling" + simulationsDir = "gatling" + toolVersion = gatlingVersion +} + +dependencies { + play "com.typesafe.play:play-guice_$scalaVersion:$playVersion" + play "com.typesafe.play:play-logback_$scalaVersion:$playVersion" + play "com.typesafe.play:play-java-jpa_$scalaVersion:$playVersion" + play "com.h2database:h2:1.4.194" + + play "org.hibernate:hibernate-core:5.2.9.Final" + play "io.dropwizard.metrics:metrics-core:3.2.1" + play "com.palominolabs.http:url-builder:1.1.0" + play "net.jodah:failsafe:1.0.3" + + playTest "io.gatling.highcharts:gatling-charts-highcharts:$gatlingVersion" + playTest "io.gatling:gatling-test-framework:$gatlingVersion" +} + +repositories { + jcenter() + maven { + name "lightbend-maven-releases" + url "https://repo.lightbend.com/lightbend/maven-release" + } + ivy { + name "lightbend-ivy-release" + url "https://repo.lightbend.com/lightbend/ivy-releases" + layout "ivy" + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..01b8bf6b1f99cad9213fc495b33ad5bbab8efd20 GIT binary patch literal 54329 zcmagFV|ZrKvM!pAZQHhO+qP}9lTNj?q^^Y^VFp)SH8qbSJ)2BQ2giqeFT zAwqu@)c?v~^Z#E_K}1nTQbJ9gQ9<%vVRAxVj)8FwL5_iTdUB>&m3fhE=kRWl;g`&m z!W5kh{WsV%fO*%je&j+Lv4xxK~zsEYQls$Q-p&dwID|A)!7uWtJF-=Tm1{V@#x*+kUI$=%KUuf2ka zjiZ{oiL1MXE2EjciJM!jrjFNwCh`~hL>iemrqwqnX?T*MX;U>>8yRcZb{Oy+VKZos zLiFKYPw=LcaaQt8tj=eoo3-@bG_342HQ%?jpgAE?KCLEHC+DmjxAfJ%Og^$dpC8Xw zAcp-)tfJm}BPNq_+6m4gBgBm3+CvmL>4|$2N$^Bz7W(}fz1?U-u;nE`+9`KCLuqg} zwNstNM!J4Uw|78&Y9~9>MLf56to!@qGkJw5Thx%zkzj%Ek9Nn1QA@8NBXbwyWC>9H z#EPwjMNYPigE>*Ofz)HfTF&%PFj$U6mCe-AFw$U%-L?~-+nSXHHKkdgC5KJRTF}`G zE_HNdrE}S0zf4j{r_f-V2imSqW?}3w-4=f@o@-q+cZgaAbZ((hn))@|eWWhcT2pLpTpL!;_5*vM=sRL8 zqU##{U#lJKuyqW^X$ETU5ETeEVzhU|1m1750#f}38_5N9)B_2|v@1hUu=Kt7-@dhA zq_`OMgW01n`%1dB*}C)qxC8q;?zPeF_r;>}%JYmlER_1CUbKa07+=TV45~symC*g8 zW-8(gag#cAOuM0B1xG8eTp5HGVLE}+gYTmK=`XVVV*U!>H`~j4+ROIQ+NkN$LY>h4 zqpwdeE_@AX@PL};e5vTn`Ro(EjHVf$;^oiA%@IBQq>R7_D>m2D4OwwEepkg}R_k*M zM-o;+P27087eb+%*+6vWFCo9UEGw>t&WI17Pe7QVuoAoGHdJ(TEQNlJOqnjZ8adCb zI`}op16D@v7UOEo%8E-~m?c8FL1utPYlg@m$q@q7%mQ4?OK1h%ODjTjFvqd!C z-PI?8qX8{a@6d&Lb_X+hKxCImb*3GFemm?W_du5_&EqRq!+H?5#xiX#w$eLti-?E$;Dhu`{R(o>LzM4CjO>ICf z&DMfES#FW7npnbcuqREgjPQM#gs6h>`av_oEWwOJZ2i2|D|0~pYd#WazE2Bbsa}X@ zu;(9fi~%!VcjK6)?_wMAW-YXJAR{QHxrD5g(ou9mR6LPSA4BRG1QSZT6A?kelP_g- zH(JQjLc!`H4N=oLw=f3{+WmPA*s8QEeEUf6Vg}@!xwnsnR0bl~^2GSa5vb!Yl&4!> zWb|KQUsC$lT=3A|7vM9+d;mq=@L%uWKwXiO9}a~gP4s_4Yohc!fKEgV7WbVo>2ITbE*i`a|V!^p@~^<={#?Gz57 zyPWeM2@p>D*FW#W5Q`1`#5NW62XduP1XNO(bhg&cX`-LYZa|m-**bu|>}S;3)eP8_ zpNTnTfm8 ze+7wDH3KJ95p)5tlwk`S7mbD`SqHnYD*6`;gpp8VdHDz%RR_~I_Ar>5)vE-Pgu7^Y z|9Px+>pi3!DV%E%4N;ii0U3VBd2ZJNUY1YC^-e+{DYq+l@cGtmu(H#Oh%ibUBOd?C z{y5jW3v=0eV0r@qMLgv1JjZC|cZ9l9Q)k1lLgm))UR@#FrJd>w^`+iy$c9F@ic-|q zVHe@S2UAnc5VY_U4253QJxm&Ip!XKP8WNcnx9^cQ;KH6PlW8%pSihSH2(@{2m_o+m zr((MvBja2ctg0d0&U5XTD;5?d?h%JcRJp{_1BQW1xu&BrA3(a4Fh9hon-ly$pyeHq zG&;6q?m%NJ36K1Sq_=fdP(4f{Hop;_G_(i?sPzvB zDM}>*(uOsY0I1j^{$yn3#U(;B*g4cy$-1DTOkh3P!LQ;lJlP%jY8}Nya=h8$XD~%Y zbV&HJ%eCD9nui-0cw!+n`V~p6VCRqh5fRX z8`GbdZ@73r7~myQLBW%db;+BI?c-a>Y)m-FW~M=1^|<21_Sh9RT3iGbO{o-hpN%d6 z7%++#WekoBOP^d0$$|5npPe>u3PLvX_gjH2x(?{&z{jJ2tAOWTznPxv-pAv<*V7r$ z6&glt>7CAClWz6FEi3bToz-soY^{ScrjwVPV51=>n->c(NJngMj6TyHty`bfkF1hc zkJS%A@cL~QV0-aK4>Id!9dh7>0IV;1J9(myDO+gv76L3NLMUm9XyPauvNu$S<)-|F zZS}(kK_WnB)Cl`U?jsdYfAV4nrgzIF@+%1U8$poW&h^c6>kCx3;||fS1_7JvQT~CV zQ8Js+!p)3oW>Df(-}uqC`Tcd%E7GdJ0p}kYj5j8NKMp(KUs9u7?jQ94C)}0rba($~ zqyBx$(1ae^HEDG`Zc@-rXk1cqc7v0wibOR4qpgRDt#>-*8N3P;uKV0CgJE2SP>#8h z=+;i_CGlv+B^+$5a}SicVaSeaNn29K`C&=}`=#Nj&WJP9Xhz4mVa<+yP6hkrq1vo= z1rX4qg8dc4pmEvq%NAkpMK>mf2g?tg_1k2%v}<3`$6~Wlq@ItJ*PhHPoEh1Yi>v57 z4k0JMO)*=S`tKvR5gb-(VTEo>5Y>DZJZzgR+j6{Y`kd|jCVrg!>2hVjz({kZR z`dLlKhoqT!aI8=S+fVp(5*Dn6RrbpyO~0+?fy;bm$0jmTN|t5i6rxqr4=O}dY+ROd zo9Et|x}!u*xi~>-y>!M^+f&jc;IAsGiM_^}+4|pHRn{LThFFpD{bZ|TA*wcGm}XV^ zr*C6~@^5X-*R%FrHIgo-hJTBcyQ|3QEj+cSqp#>&t`ZzB?cXM6S(lRQw$I2?m5=wd z78ki`R?%;o%VUhXH?Z#(uwAn9$m`npJ=cA+lHGk@T7qq_M6Zoy1Lm9E0UUysN)I_x zW__OAqvku^>`J&CB=ie@yNWsaFmem}#L3T(x?a`oZ+$;3O-icj2(5z72Hnj=9Z0w% z<2#q-R=>hig*(t0^v)eGq2DHC%GymE-_j1WwBVGoU=GORGjtaqr0BNigOCqyt;O(S zKG+DoBsZU~okF<7ahjS}bzwXxbAxFfQAk&O@>LsZMsZ`?N?|CDWM(vOm%B3CBPC3o z%2t@%H$fwur}SSnckUm0-k)mOtht`?nwsDz=2#v=RBPGg39i#%odKq{K^;bTD!6A9 zskz$}t)sU^=a#jLZP@I=bPo?f-L}wpMs{Tc!m7-bi!Ldqj3EA~V;4(dltJmTXqH0r z%HAWKGutEc9vOo3P6Q;JdC^YTnby->VZ6&X8f{obffZ??1(cm&L2h7q)*w**+sE6dG*;(H|_Q!WxU{g)CeoT z(KY&bv!Usc|m+Fqfmk;h&RNF|LWuNZ!+DdX*L=s-=_iH=@i` z?Z+Okq^cFO4}_n|G*!)Wl_i%qiMBaH8(WuXtgI7EO=M>=i_+;MDjf3aY~6S9w0K zUuDO7O5Ta6+k40~xh~)D{=L&?Y0?c$s9cw*Ufe18)zzk%#ZY>Tr^|e%8KPb0ht`b( zuP@8#Ox@nQIqz9}AbW0RzE`Cf>39bOWz5N3qzS}ocxI=o$W|(nD~@EhW13Rj5nAp; zu2obEJa=kGC*#3=MkdkWy_%RKcN=?g$7!AZ8vBYKr$ePY(8aIQ&yRPlQ=mudv#q$q z4%WzAx=B{i)UdLFx4os?rZp6poShD7Vc&mSD@RdBJ=_m^&OlkEE1DFU@csgKcBifJ zz4N7+XEJhYzzO=86 z#%eBQZ$Nsf2+X0XPHUNmg#(sNt^NW1Y0|M(${e<0kW6f2q5M!2YE|hSEQ*X-%qo(V zHaFwyGZ0on=I{=fhe<=zo{=Og-_(to3?cvL4m6PymtNsdDINsBh8m>a%!5o3s(en) z=1I z6O+YNertC|OFNqd6P=$gMyvmfa`w~p9*gKDESFqNBy(~Zw3TFDYh}$iudn)9HxPBi zdokK@o~nu?%imcURr5Y~?6oo_JBe}t|pU5qjai|#JDyG=i^V~7+a{dEnO<(y>ahND#_X_fcEBNiZ)uc&%1HVtx8Ts z*H_Btvx^IhkfOB#{szN*n6;y05A>3eARDXslaE>tnLa>+`V&cgho?ED+&vv5KJszf zG4@G;7i;4_bVvZ>!mli3j7~tPgybF5|J6=Lt`u$D%X0l}#iY9nOXH@(%FFJLtzb%p zzHfABnSs;v-9(&nzbZytLiqqDIWzn>JQDk#JULcE5CyPq_m#4QV!}3421haQ+LcfO*>r;rg6K|r#5Sh|y@h1ao%Cl)t*u`4 zMTP!deC?aL7uTxm5^nUv#q2vS-5QbBKP|drbDXS%erB>fYM84Kpk^au99-BQBZR z7CDynflrIAi&ahza+kUryju5LR_}-Z27g)jqOc(!Lx9y)e z{cYc&_r947s9pteaa4}dc|!$$N9+M38sUr7h(%@Ehq`4HJtTpA>B8CLNO__@%(F5d z`SmX5jbux6i#qc}xOhumzbAELh*Mfr2SW99=WNOZRZgoCU4A2|4i|ZVFQt6qEhH#B zK_9G;&h*LO6tB`5dXRSBF0hq0tk{2q__aCKXYkP#9n^)@cq}`&Lo)1KM{W+>5mSed zKp~=}$p7>~nK@va`vN{mYzWN1(tE=u2BZhga5(VtPKk(*TvE&zmn5vSbjo zZLVobTl%;t@6;4SsZ>5+U-XEGUZGG;+~|V(pE&qqrp_f~{_1h@5ZrNETqe{bt9ioZ z#Qn~gWCH!t#Ha^n&fT2?{`}D@s4?9kXj;E;lWV9Zw8_4yM0Qg-6YSsKgvQ*fF{#Pq z{=(nyV>#*`RloBVCs;Lp*R1PBIQOY=EK4CQa*BD0MsYcg=opP?8;xYQDSAJBeJpw5 zPBc_Ft9?;<0?pBhCmOtWU*pN*;CkjJ_}qVic`}V@$TwFi15!mF1*m2wVX+>5p%(+R zQ~JUW*zWkalde{90@2v+oVlkxOZFihE&ZJ){c?hX3L2@R7jk*xjYtHi=}qb+4B(XJ z$gYcNudR~4Kz_WRq8eS((>ALWCO)&R-MXE+YxDn9V#X{_H@j616<|P(8h(7z?q*r+ zmpqR#7+g$cT@e&(%_|ipI&A%9+47%30TLY(yuf&*knx1wNx|%*H^;YB%ftt%5>QM= z^i;*6_KTSRzQm%qz*>cK&EISvF^ovbS4|R%)zKhTH_2K>jP3mBGn5{95&G9^a#4|K zv+!>fIsR8z{^x4)FIr*cYT@Q4Z{y}};rLHL+atCgHbfX*;+k&37DIgENn&=k(*lKD zG;uL-KAdLn*JQ?@r6Q!0V$xXP=J2i~;_+i3|F;_En;oAMG|I-RX#FwnmU&G}w`7R{ z788CrR-g1DW4h_`&$Z`ctN~{A)Hv_-Bl!%+pfif8wN32rMD zJDs$eVWBYQx1&2sCdB0!vU5~uf)=vy*{}t{2VBpcz<+~h0wb7F3?V^44*&83Z2#F` z32!rd4>uc63rQP$3lTH3zb-47IGR}f)8kZ4JvX#toIpXH`L%NnPDE~$QI1)0)|HS4 zVcITo$$oWWwCN@E-5h>N?Hua!N9CYb6f8vTFd>h3q5Jg-lCI6y%vu{Z_Uf z$MU{{^o~;nD_@m2|E{J)q;|BK7rx%`m``+OqZAqAVj-Dy+pD4-S3xK?($>wn5bi90CFAQ+ACd;&m6DQB8_o zjAq^=eUYc1o{#+p+ zn;K<)Pn*4u742P!;H^E3^Qu%2dM{2slouc$AN_3V^M7H_KY3H)#n7qd5_p~Za7zAj|s9{l)RdbV9e||_67`#Tu*c<8!I=zb@ z(MSvQ9;Wrkq6d)!9afh+G`!f$Ip!F<4ADdc*OY-y7BZMsau%y?EN6*hW4mOF%Q~bw z2==Z3^~?q<1GTeS>xGN-?CHZ7a#M4kDL zQxQr~1ZMzCSKFK5+32C%+C1kE#(2L=15AR!er7GKbp?Xd1qkkGipx5Q~FI-6zt< z*PTpeVI)Ngnnyaz5noIIgNZtb4bQdKG{Bs~&tf)?nM$a;7>r36djllw%hQxeCXeW^ z(i6@TEIuxD<2ulwLTt|&gZP%Ei+l!(%p5Yij6U(H#HMkqM8U$@OKB|5@vUiuY^d6X zW}fP3;Kps6051OEO(|JzmVU6SX(8q>*yf*x5QoxDK={PH^F?!VCzES_Qs>()_y|jg6LJlJWp;L zKM*g5DK7>W_*uv}{0WUB0>MHZ#oJZmO!b3MjEc}VhsLD~;E-qNNd?x7Q6~v zR=0$u>Zc2Xr}>x_5$-s#l!oz6I>W?lw;m9Ae{Tf9eMX;TI-Wf_mZ6sVrMnY#F}cDd z%CV*}fDsXUF7Vbw>PuDaGhu631+3|{xp<@Kl|%WxU+vuLlcrklMC!Aq+7n~I3cmQ! z`e3cA!XUEGdEPSu``&lZEKD1IKO(-VGvcnSc153m(i!8ohi`)N2n>U_BemYJ`uY>8B*Epj!oXRLV}XK}>D*^DHQ7?NY*&LJ9VSo`Ogi9J zGa;clWI8vIQqkngv2>xKd91K>?0`Sw;E&TMg&6dcd20|FcTsnUT7Yn{oI5V4@Ow~m zz#k~8TM!A9L7T!|colrC0P2WKZW7PNj_X4MfESbt<-soq*0LzShZ}fyUx!(xIIDwx zRHt^_GAWe0-Vm~bDZ(}XG%E+`XhKpPlMBo*5q_z$BGxYef8O!ToS8aT8pmjbPq)nV z%x*PF5ZuSHRJqJ!`5<4xC*xb2vC?7u1iljB_*iUGl6+yPyjn?F?GOF2_KW&gOkJ?w z3e^qc-te;zez`H$rsUCE0<@7PKGW?7sT1SPYWId|FJ8H`uEdNu4YJjre`8F*D}6Wh z|FQ`xf7yiphHIAkU&OYCn}w^ilY@o4larl?^M7&8YI;hzBIsX|i3UrLsx{QDKwCX< zy;a>yjfJ6!sz`NcVi+a!Fqk^VE^{6G53L?@Tif|j!3QZ0fk9QeUq8CWI;OmO-Hs+F zuZ4sHLA3{}LR2Qlyo+{d@?;`tpp6YB^BMoJt?&MHFY!JQwoa0nTSD+#Ku^4b{5SZVFwU9<~APYbaLO zu~Z)nS#dxI-5lmS-Bnw!(u15by(80LlC@|ynj{TzW)XcspC*}z0~8VRZq>#Z49G`I zgl|C#H&=}n-ajxfo{=pxPV(L*7g}gHET9b*s=cGV7VFa<;Htgjk>KyW@S!|z`lR1( zGSYkEl&@-bZ*d2WQ~hw3NpP=YNHF^XC{TMG$Gn+{b6pZn+5=<()>C!N^jncl0w6BJ zdHdnmSEGK5BlMeZD!v4t5m7ct7{k~$1Ie3GLFoHjAH*b?++s<|=yTF+^I&jT#zuMx z)MLhU+;LFk8bse|_{j+d*a=&cm2}M?*arjBPnfPgLwv)86D$6L zLJ0wPul7IenMvVAK$z^q5<^!)7aI|<&GGEbOr=E;UmGOIa}yO~EIr5xWU_(ol$&fa zR5E(2vB?S3EvJglTXdU#@qfDbCYs#82Yo^aZN6`{Ex#M)easBTe_J8utXu(fY1j|R z9o(sQbj$bKU{IjyhosYahY{63>}$9_+hWxB3j}VQkJ@2$D@vpeRSldU?&7I;qd2MF zSYmJ>zA(@N_iK}m*AMPIJG#Y&1KR)6`LJ83qg~`Do3v^B0>fU&wUx(qefuTgzFED{sJ65!iw{F2}1fQ3= ziFIP{kezQxmlx-!yo+sC4PEtG#K=5VM9YIN0z9~c4XTX?*4e@m;hFM!zVo>A`#566 z>f&3g94lJ{r)QJ5m7Xe3SLau_lOpL;A($wsjHR`;xTXgIiZ#o&vt~ zGR6KdU$FFbLfZCC3AEu$b`tj!9XgOGLSV=QPIYW zjI!hSP#?8pn0@ezuenOzoka8!8~jXTbiJ6+ZuItsWW03uzASFyn*zV2kIgPFR$Yzm zE<$cZlF>R8?Nr2_i?KiripBc+TGgJvG@vRTY2o?(_Di}D30!k&CT`>+7ry2!!iC*X z<@=U0_C#16=PN7bB39w+zPwDOHX}h20Ap);dx}kjXX0-QkRk=cr};GYsjSvyLZa-t zzHONWddi*)RDUH@RTAsGB_#&O+QJaaL+H<<9LLSE+nB@eGF1fALwjVOl8X_sdOYme z0lk!X=S(@25=TZHR7LlPp}fY~yNeThMIjD}pd9+q=j<_inh0$>mIzWVY+Z9p<{D^#0Xk+b_@eNSiR8;KzSZ#7lUsk~NGMcB8C2c=m2l5paHPq`q{S(kdA7Z1a zyfk2Y;w?^t`?@yC5Pz9&pzo}Hc#}mLgDmhKV|PJ3lKOY(Km@Fi2AV~CuET*YfUi}u zfInZnqDX(<#vaS<^fszuR=l)AbqG{}9{rnyx?PbZz3Pyu!eSJK`uwkJU!ORQXy4x83r!PNgOyD33}}L=>xX_93l6njNTuqL8J{l%*3FVn3MG4&Fv*`lBXZ z?=;kn6HTT^#SrPX-N)4EZiIZI!0ByXTWy;;J-Tht{jq1mjh`DSy7yGjHxIaY%*sTx zuy9#9CqE#qi>1misx=KRWm=qx4rk|}vd+LMY3M`ow8)}m$3Ggv&)Ri*ON+}<^P%T5 z_7JPVPfdM=Pv-oH<tecoE}(0O7|YZc*d8`Uv_M*3Rzv7$yZnJE6N_W=AQ3_BgU_TjA_T?a)U1csCmJ&YqMp-lJe`y6>N zt++Bi;ZMOD%%1c&-Q;bKsYg!SmS^#J@8UFY|G3!rtyaTFb!5@e(@l?1t(87ln8rG? z--$1)YC~vWnXiW3GXm`FNSyzu!m$qT=Eldf$sMl#PEfGmzQs^oUd=GIQfj(X=}dw+ zT*oa0*oS%@cLgvB&PKIQ=Ok?>x#c#dC#sQifgMwtAG^l3D9nIg(Zqi;D%807TtUUCL3_;kjyte#cAg?S%e4S2W>9^A(uy8Ss0Tc++ZTjJw1 z&Em2g!3lo@LlDyri(P^I8BPpn$RE7n*q9Q-c^>rfOMM6Pd5671I=ZBjAvpj8oIi$! zl0exNl(>NIiQpX~FRS9UgK|0l#s@#)p4?^?XAz}Gjb1?4Qe4?j&cL$C8u}n)?A@YC zfmbSM`Hl5pQFwv$CQBF=_$Sq zxsV?BHI5bGZTk?B6B&KLdIN-40S426X3j_|ceLla*M3}3gx3(_7MVY1++4mzhH#7# zD>2gTHy*%i$~}mqc#gK83288SKp@y3wz1L_e8fF$Rb}ex+`(h)j}%~Ld^3DUZkgez zOUNy^%>>HHE|-y$V@B}-M|_{h!vXpk01xaD%{l{oQ|~+^>rR*rv9iQen5t?{BHg|% zR`;S|KtUb!X<22RTBA4AAUM6#M?=w5VY-hEV)b`!y1^mPNEoy2K)a>OyA?Q~Q*&(O zRzQI~y_W=IPi?-OJX*&&8dvY0zWM2%yXdFI!D-n@6FsG)pEYdJbuA`g4yy;qrgR?G z8Mj7gv1oiWq)+_$GqqQ$(ZM@#|0j7})=#$S&hZwdoijFI4aCFLVI3tMH5fLreZ;KD zqA`)0l~D2tuIBYOy+LGw&hJ5OyE+@cnZ0L5+;yo2pIMdt@4$r^5Y!x7nHs{@>|W(MzJjATyWGNwZ^4j+EPU0RpAl-oTM@u{lx*i0^yyWPfHt6QwPvYpk9xFMWfBFt!+Gu6TlAmr zeQ#PX71vzN*_-xh&__N`IXv6`>CgV#eA_%e@7wjgkj8jlKzO~Ic6g$cT`^W{R{606 zCDP~+NVZ6DMO$jhL~#+!g*$T!XW63#(ngDn#Qwy71yj^gazS{e;3jGRM0HedGD@pt z?(ln3pCUA(ekqAvvnKy0G@?-|-dh=eS%4Civ&c}s%wF@0K5Bltaq^2Os1n6Z3%?-Q zAlC4goQ&vK6TpgtzkHVt*1!tBYt-`|5HLV1V7*#45Vb+GACuU+QB&hZ=N_flPy0TY zR^HIrdskB#<$aU;HY(K{a3(OQa$0<9qH(oa)lg@Uf>M5g2W0U5 zk!JSlhrw8quBx9A>RJ6}=;W&wt@2E$7J=9SVHsdC?K(L(KACb#z)@C$xXD8^!7|uv zZh$6fkq)aoD}^79VqdJ!Nz-8$IrU(_-&^cHBI;4 z^$B+1aPe|LG)C55LjP;jab{dTf$0~xbXS9!!QdcmDYLbL^jvxu2y*qnx2%jbL%rB z{aP85qBJe#(&O~Prk%IJARcdEypZ)vah%ZZ%;Zk{eW(U)Bx7VlzgOi8)x z`rh4l`@l_Ada7z&yUK>ZF;i6YLGwI*Sg#Fk#Qr0Jg&VLax(nNN$u-XJ5=MsP3|(lEdIOJ7|(x3iY;ea)5#BW*mDV%^=8qOeYO&gIdJVuLLN3cFaN=xZtFB=b zH{l)PZl_j^u+qx@89}gAQW7ofb+k)QwX=aegihossZq*+@PlCpb$rpp>Cbk9UJO<~ zDjlXQ_Ig#W0zdD3&*ei(FwlN#3b%FSR%&M^ywF@Fr>d~do@-kIS$e%wkIVfJ|Ohh=zc zF&Rnic^|>@R%v?@jO}a9;nY3Qrg_!xC=ZWUcYiA5R+|2nsM*$+c$TOs6pm!}Z}dfM zGeBhMGWw3$6KZXav^>YNA=r6Es>p<6HRYcZY)z{>yasbC81A*G-le8~QoV;rtKnkx z;+os8BvEe?0A6W*a#dOudsv3aWs?d% z0oNngyVMjavLjtjiG`!007#?62ClTqqU$@kIY`=x^$2e>iqIy1>o|@Tw@)P)B8_1$r#6>DB_5 zmaOaoE~^9TolgDgooKFuEFB#klSF%9-~d2~_|kQ0Y{Ek=HH5yq9s zDq#1S551c`kSiWPZbweN^A4kWiP#Qg6er1}HcKv{fxb1*BULboD0fwfaNM_<55>qM zETZ8TJDO4V)=aPp_eQjX%||Ud<>wkIzvDlpNjqW>I}W!-j7M^TNe5JIFh#-}zAV!$ICOju8Kx)N z0vLtzDdy*rQN!7r>Xz7rLw8J-(GzQlYYVH$WK#F`i_i^qVlzTNAh>gBWKV@XC$T-` z3|kj#iCquDhiO7NKum07i|<-NuVsX}Q}mIP$jBJDMfUiaWR3c|F_kWBMw0_Sr|6h4 zk`_r5=0&rCR^*tOy$A8K;@|NqwncjZ>Y-75vlpxq%Cl3EgH`}^^~=u zoll6xxY@a>0f%Ddpi;=cY}fyG!K2N-dEyXXmUP5u){4VnyS^T4?pjN@Ot4zjL(Puw z_U#wMH2Z#8Pts{olG5Dy0tZj;N@;fHheu>YKYQU=4Bk|wcD9MbA`3O4bj$hNRHwzb zSLcG0SLV%zywdbuwl(^E_!@&)TdXge4O{MRWk2RKOt@!8E{$BU-AH(@4{gxs=YAz9LIob|Hzto0}9cWoz6Tp2x0&xi#$ zHh$dwO&UCR1Ob2w00-2eG7d4=cN(Y>0R#$q8?||q@iTi+7-w-xR%uMr&StFIthC<# zvK(aPduwuNB}oJUV8+Zl)%cnfsHI%4`;x6XW^UF^e4s3Z@S<&EV8?56Wya;HNs0E> z`$0dgRdiUz9RO9Au3RmYq>K#G=X%*_dUbSJHP`lSfBaN8t-~@F>)BL1RT*9I851A3 z<-+Gb#_QRX>~av#Ni<#zLswtu-c6{jGHR>wflhKLzC4P@b%8&~u)fosoNjk4r#GvC zlU#UU9&0Hv;d%g72Wq?Ym<&&vtA3AB##L}=ZjiTR4hh7J)e>ei} zt*u+>h%MwN`%3}b4wYpV=QwbY!jwfIj#{me)TDOG`?tI!%l=AwL2G@9I~}?_dA5g6 zCKgK(;6Q0&P&K21Tx~k=o6jwV{dI_G+Ba*Zts|Tl6q1zeC?iYJTb{hel*x>^wb|2RkHkU$!+S4OU4ZOKPZjV>9OVsqNnv5jK8TRAE$A&^yRwK zj-MJ3Pl?)KA~fq#*K~W0l4$0=8GRx^9+?w z!QT8*-)w|S^B0)ZeY5gZPI2G(QtQf?DjuK(s^$rMA!C%P22vynZY4SuOE=wX2f8$R z)A}mzJi4WJnZ`!bHG1=$lwaxm!GOnRbR15F$nRC-M*H<*VfF|pQw(;tbSfp({>9^5 zw_M1-SJ9eGF~m(0dvp*P8uaA0Yw+EkP-SWqu zqal$hK8SmM7#Mrs0@OD+%_J%H*bMyZiWAZdsIBj#lkZ!l2c&IpLu(5^T0Ge5PHzR} zn;TXs$+IQ_&;O~u=Jz+XE0wbOy`=6>m9JVG} zJ~Kp1e5m?K3x@@>!D)piw^eMIHjD4RebtR`|IlckplP1;r21wTi8v((KqNqn%2CB< zifaQc&T}*M&0i|LW^LgdjIaX|o~I$`owHolRqeH_CFrqCUCleN130&vH}dK|^kC>) z-r2P~mApHotL4dRX$25lIcRh_*kJaxi^%ZN5-GAAMOxfB!6flLPY-p&QzL9TE%ho( zRwftE3sy5<*^)qYzKkL|rE>n@hyr;xPqncY6QJ8125!MWr`UCWuC~A#G1AqF1@V$kv>@NBvN&2ygy*{QvxolkRRb%Ui zsmKROR%{*g*WjUUod@@cS^4eF^}yQ1>;WlGwOli z+Y$(8I`0(^d|w>{eaf!_BBM;NpCoeem2>J}82*!em=}}ymoXk>QEfJ>G(3LNA2-46 z5PGvjr)Xh9>aSe>vEzM*>xp{tJyZox1ZRl}QjcvX2TEgNc^(_-hir@Es>NySoa1g^ zFow_twnHdx(j?Q_3q51t3XI7YlJ4_q&(0#)&a+RUy{IcBq?)eaWo*=H2UUVIqtp&lW9JTJiP&u zw8+4vo~_IJXZIJb_U^&=GI1nSD%e;P!c{kZALNCm5c%%oF+I3DrA63_@4)(v4(t~JiddILp7jmoy+>cD~ivwoctFfEL zP*#2Rx?_&bCpX26MBgp^4G>@h`Hxc(lnqyj!*t>9sOBcXN(hTwEDpn^X{x!!gPX?1 z*uM$}cYRwHXuf+gYTB}gDTcw{TXSOUU$S?8BeP&sc!Lc{{pEv}x#ELX>6*ipI1#>8 zKes$bHjiJ1OygZge_ak^Hz#k;=od1wZ=o71ba7oClBMq>Uk6hVq|ePPt)@FM5bW$I z;d2Or@wBjbTyZj|;+iHp%Bo!Vy(X3YM-}lasMItEV_QrP-Kk_J4C>)L&I3Xxj=E?| zsAF(IfVQ4w+dRRnJ>)}o^3_012YYgFWE)5TT=l2657*L8_u1KC>Y-R{7w^S&A^X^U}h20jpS zQsdeaA#WIE*<8KG*oXc~$izYilTc#z{5xhpXmdT-YUnGh9v4c#lrHG6X82F2-t35} zB`jo$HjKe~E*W$=g|j&P>70_cI`GnOQ;Jp*JK#CT zuEGCn{8A@bC)~0%wsEv?O^hSZF*iqjO~_h|>xv>PO+?525Nw2472(yqS>(#R)D7O( zg)Zrj9n9$}=~b00=Wjf?E418qP-@8%MQ%PBiCTX=$B)e5cHFDu$LnOeJ~NC;xmOk# z>z&TbsK>Qzk)!88lNI8fOE2$Uxso^j*1fz>6Ot49y@=po)j4hbTIcVR`ePHpuJSfp zxaD^Dn3X}Na3@<_Pc>a;-|^Pon(>|ytG_+U^8j_JxP=_d>L$Hj?|0lz>_qQ#a|$+( z(x=Lipuc8p4^}1EQhI|TubffZvB~lu$zz9ao%T?%ZLyV5S9}cLeT?c} z>yCN9<04NRi~1oR)CiBakoNhY9BPnv)kw%*iv8vdr&&VgLGIs(-FbJ?d_gfbL2={- zBk4lkdPk~7+jIxd4{M(-W1AC_WcN&Oza@jZoj zaE*9Y;g83#m(OhA!w~LNfUJNUuRz*H-=$s*z+q+;snKPRm9EptejugC-@7-a-}Tz0 z@KHra#Y@OXK+KsaSN9WiGf?&jlZ!V7L||%KHP;SLksMFfjkeIMf<1e~t?!G3{n)H8 zQAlFY#QwfKuj;l@<$YDATAk;%PtD%B(0<|8>rXU< zJ66rkAVW_~Dj!7JGdGGi4NFuE?7ZafdMxIh65Sz7yQoA7fBZCE@WwysB=+`kT^LFX zz8#FlSA5)6FG9(qL3~A24mpzL@@2D#>0J7mMS1T*9UJ zvOq!!a(%IYY69+h45CE?(&v9H4FCr>gK0>mK~F}5RdOuH2{4|}k@5XpsX7+LZo^Qa4sH5`eUj>iffoBVm+ zz4Mtf`h?NW$*q1yr|}E&eNl)J``SZvTf6Qr*&S%tVv_OBpbjnA0&Vz#(;QmGiq-k! zgS0br4I&+^2mgA15*~Cd00cXLYOLA#Ep}_)eED>m+K@JTPr_|lSN}(OzFXQSBc6fM z@f-%2;1@BzhZa*LFV z-LrLmkmB%<<&jEURBEW>soaZ*rSIJNwaV%-RSaCZi4X)qYy^PxZ=oL?6N-5OGOMD2 z;q_JK?zkwQ@b3~ln&sDtT5SpW9a0q+5Gm|fpVY2|zqlNYBR}E5+ahgdj!CvK$Tlk0 z9g$5N;aar=CqMsudQV>yb4l@hN(9Jcc=1(|OHsqH6|g=K-WBd8GxZ`AkT?OO z-z_Ued-??Z*R4~L7jwJ%-`s~FK|qNAJ;EmIVDVpk{Lr7T4l{}vL)|GuUuswe9c5F| zv*5%u01hlv08?00Vpwyk*Q&&fY8k6MjOfpZfKa@F-^6d=Zv|0@&4_544RP5(s|4VPVP-f>%u(J@23BHqo2=zJ#v9g=F!cP((h zpt0|(s++ej?|$;2PE%+kc6JMmJjDW)3BXvBK!h!E`8Y&*7hS{c_Z?4SFP&Y<3evqf z9-ke+bSj$%Pk{CJlJbWwlBg^mEC^@%Ou?o>*|O)rl&`KIbHrjcpqsc$Zqt0^^F-gU2O=BusO+(Op}!jNzLMc zT;0YT%$@ClS%V+6lMTfhuzzxomoat=1H?1$5Ei7&M|gxo`~{UiV5w64Np6xV zVK^nL$)#^tjhCpTQMspXI({TW^U5h&Wi1Jl8g?P1YCV4=%ZYyjSo#5$SX&`r&1PyC zzc;uzCd)VTIih|8eNqFNeBMe#j_FS6rq81b>5?aXg+E#&$m++Gz9<+2)h=K(xtn}F ziV{rmu+Y>A)qvF}ms}4X^Isy!M&1%$E!rTO~5(p+8{U6#hWu>(Ll1}eD64Xa>~73A*538wry?v$vW z>^O#FRdbj(k0Nr&)U`Tl(4PI*%IV~;ZcI2z&rmq=(k^}zGOYZF3b2~Klpzd2eZJl> zB=MOLwI1{$RxQ7Y4e30&yOx?BvAvDkTBvWPpl4V8B7o>4SJn*+h1Ms&fHso%XLN5j z-zEwT%dTefp~)J_C8;Q6i$t!dnlh-!%haR1X_NuYUuP-)`IGWjwzAvp!9@h`kPZhf zwLwFk{m3arCdx8rD~K2`42mIN4}m%OQ|f)4kf%pL?Af5Ul<3M2fv>;nlhEPR8b)u} zIV*2-wyyD%%) zl$G@KrC#cUwoL?YdQyf9WH)@gWB{jd5w4evI& zOFF)p_D8>;3-N1z6mES!OPe>B^<;9xsh)){Cw$Vs-ez5nXS95NOr3s$IU;>VZSzKn zBvub8_J~I%(DozZW@{)Vp37-zevxMRZ8$8iRfwHmYvyjOxIOAF2FUngKj289!(uxY zaClWm!%x&teKmr^ABrvZ(ikx{{I-lEzw5&4t3P0eX%M~>$wG0ZjA4Mb&op+0$#SO_ z--R`>X!aqFu^F|a!{Up-iF(K+alKB{MNMs>e(i@Tpy+7Z-dK%IEjQFO(G+2mOb@BO zP>WHlS#fSQm0et)bG8^ZDScGnh-qRKIFz zfUdnk=m){ej0i(VBd@RLtRq3Ep=>&2zZ2%&vvf?Iex01hx1X!8U+?>ER;yJlR-2q4 z;Y@hzhEC=d+Le%=esE>OQ!Q|E%6yG3V_2*uh&_nguPcZ{q?DNq8h_2ahaP6=pP-+x zK!(ve(yfoYC+n(_+chiJ6N(ZaN+XSZ{|H{TR1J_s8x4jpis-Z-rlRvRK#U%SMJ(`C z?T2 zF(NNfO_&W%2roEC2j#v*(nRgl1X)V-USp-H|CwFNs?n@&vpRcj@W@xCJwR6@T!jt377?XjZ06=`d*MFyTdyvW!`mQm~t3luzYzvh^F zM|V}rO>IlBjZc}9Z zd$&!tthvr>5)m;5;96LWiAV0?t)7suqdh0cZis`^Pyg@?t>Ms~7{nCU;z`Xl+raSr zXpp=W1oHB*98s!Tpw=R5C)O{{Inl>9l7M*kq%#w9a$6N~v?BY2GKOVRkXYCgg*d

<5G2M1WZP5 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..e95643d6a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/scripts/script-helper b/scripts/script-helper new file mode 100644 index 000000000..ac39d9a51 --- /dev/null +++ b/scripts/script-helper @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +java_version=$(java -version 2>&1 | java -version 2>&1 | awk -F '"' '/version/ {print $2}') + +if [[ $java_version = 9* ]] ; then + echo "The build is using Java 9 ($java_version). We need additional JVM parameters" + export _JAVA_OPTIONS="$_JAVA_OPTIONS --add-modules=java.xml.bind" +else + echo "The build is NOT using Java 9 ($java_version). No addional JVM params needed." +fi diff --git a/scripts/test-gradle b/scripts/test-gradle new file mode 100755 index 000000000..298c7ebe1 --- /dev/null +++ b/scripts/test-gradle @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +. "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/script-helper" + +# Using cut because TRAVIS_SCALA_VERSION is the full Scala +# version (for example 2.12.4), but Gradle expects just the +# binary version (for example 2.12) +scala_binary_version=$(echo $TRAVIS_SCALA_VERSION | cut -c1-4) + +echo "+------------------------------+" +echo "| Executing tests using Gradle |" +echo "+------------------------------+" +./gradlew -Dscala.binary.version=$scala_binary_version test -i --stacktrace diff --git a/scripts/test-sbt b/scripts/test-sbt new file mode 100755 index 000000000..0425367b1 --- /dev/null +++ b/scripts/test-sbt @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +. "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/script-helper" + +echo "+----------------------------+" +echo "| Executing tests using sbt |" +echo "+----------------------------+" +sbt ++$TRAVIS_SCALA_VERSION test From 135257111d0ead9a08387e4596a90df9a975b312 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Thu, 21 Dec 2017 18:16:53 -0200 Subject: [PATCH 32/61] Updated with template-control on 2017-12-21T19:35:22.079Z (#37) **/build.properties: sbt.version=1.0.4 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index b7dd3cb2a..394cb75cf 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.0.2 +sbt.version=1.0.4 From 0ca9f8ddd17e98dc63a376a6cd08b8bb1fe8c21a Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Fri, 22 Dec 2017 12:09:06 -0200 Subject: [PATCH 33/61] Update play to version 2.6.10 (#39) --- build.sbt | 4 +--- project/plugins.sbt | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 254b8a6dd..a6aa1509e 100644 --- a/build.sbt +++ b/build.sbt @@ -8,9 +8,7 @@ inThisBuild( dependencyOverrides := Seq( "org.codehaus.plexus" % "plexus-utils" % "3.0.18", "com.google.code.findbugs" % "jsr305" % "3.0.1", - "com.google.guava" % "guava" % "22.0", - "com.typesafe.akka" %% "akka-stream" % "2.5.6", - "com.typesafe.akka" %% "akka-actor" % "2.5.6" + "com.google.guava" % "guava" % "22.0" ) ) ) diff --git a/project/plugins.sbt b/project/plugins.sbt index 8e91abf62..e7a744a03 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.9") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.10") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From f7301189207ce4a2cd82ad14b5bcfb48e29dcf34 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Wed, 3 Jan 2018 13:22:58 -0200 Subject: [PATCH 34/61] Upgrade branch 2.6.x using TemplateControl (#40) * Updated with template-control on 2017-12-22T16:49:12.583Z **/test-gradle: ./gradlew -Dscala.binary.version=$scala_binary_version check -i --stacktrace * Add run permission to test-gradle script * Use gradle version compatible with Scala version --- build.gradle | 8 +++++++- scripts/test-gradle | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 58a3a739f..f01c8de1e 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,13 @@ plugins { def playVersion = '2.6.9' def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") -def gatlingVersion = "2.3.0" +def gatlingVersion = getGlatlingVersion(scalaVersion) + +// Gatling 2.3.0 works for Scala 2.12 +// Gatling 2.2.5 works for Scala 2.11 +String getGlatlingVersion(String scalaVer) { + if (scalaVer == "2.12") "2.3.0" else "2.2.5" +} model { components { diff --git a/scripts/test-gradle b/scripts/test-gradle index 298c7ebe1..84a051a20 100755 --- a/scripts/test-gradle +++ b/scripts/test-gradle @@ -10,4 +10,4 @@ scala_binary_version=$(echo $TRAVIS_SCALA_VERSION | cut -c1-4) echo "+------------------------------+" echo "| Executing tests using Gradle |" echo "+------------------------------+" -./gradlew -Dscala.binary.version=$scala_binary_version test -i --stacktrace +./gradlew -Dscala.binary.version=$scala_binary_version check -i --stacktrace From 21b986f291b11cd24002f1f74ec6c28097b086a6 Mon Sep 17 00:00:00 2001 From: Aidara Moussa Date: Wed, 3 Jan 2018 17:00:48 +0100 Subject: [PATCH 35/61] Link not reachable on master. (#41) Since there is no master branche, `PostController.java` can be reach on branche 2.5.x or 2.6.x. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 83f18c04d..3e6335b6b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A REST API showing Play with a JPA backend. For the Scala version, please see < ## Best Practices for Blocking API -If you look at the controller: +If you look at the controller: then you can see that when calling out to a blocking API like JDBC, you should put it behind an asynchronous boundary -- in practice, this means using the CompletionStage API to make sure that you're not blocking the rendering thread while the database call is going on in the background. ```java From 692268b5989d7116e3d0c9718b6ed6d03edd5052 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Wed, 10 Jan 2018 07:28:08 -0200 Subject: [PATCH 36/61] Upgrade branch 2.6.x using TemplateControl (#42) * Updated with template-control on 2018-01-09T20:17:23.926Z **build.sbt: scalaVersion := "2.12.4" **build.sbt: libraryDependencies += "com.h2database" % "h2" % "1.4.196" **/build.properties: sbt.version=1.1.0 **build.gradle: def playVersion = "2.6.10" **build.gradle: play "com.h2database:h2:1.4.196" * test-gradle-permissions * Fix scalaVersion update --- build.gradle | 4 ++-- build.sbt | 4 ++-- project/build.properties | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index f01c8de1e..1b05b6bc7 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id "com.github.lkishalmi.gatling" version "0.7.1" } -def playVersion = '2.6.9' +def playVersion = "2.6.10" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") def gatlingVersion = getGlatlingVersion(scalaVersion) @@ -45,7 +45,7 @@ dependencies { play "com.typesafe.play:play-guice_$scalaVersion:$playVersion" play "com.typesafe.play:play-logback_$scalaVersion:$playVersion" play "com.typesafe.play:play-java-jpa_$scalaVersion:$playVersion" - play "com.h2database:h2:1.4.194" + play "com.h2database:h2:1.4.196" play "org.hibernate:hibernate-core:5.2.9.Final" play "io.dropwizard.metrics:metrics-core:3.2.1" diff --git a/build.sbt b/build.sbt index a6aa1509e..ab830c6b7 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ version := "2.6.x" inThisBuild( List( - scalaVersion := "2.12.3", + scalaVersion := "2.12.4", dependencyOverrides := Seq( "org.codehaus.plexus" % "plexus-utils" % "3.0.18", "com.google.code.findbugs" % "jsr305" % "3.0.1", @@ -24,7 +24,7 @@ lazy val root = (project in file(".")).enablePlugins(PlayJava, GatlingPlugin).co libraryDependencies += guice libraryDependencies += javaJpa -libraryDependencies += "com.h2database" % "h2" % "1.4.194" +libraryDependencies += "com.h2database" % "h2" % "1.4.196" libraryDependencies += "org.hibernate" % "hibernate-core" % "5.2.9.Final" libraryDependencies += "io.dropwizard.metrics" % "metrics-core" % "3.2.1" diff --git a/project/build.properties b/project/build.properties index 394cb75cf..8b697bbb9 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.0.4 +sbt.version=1.1.0 From bc351e7dda672ef70055a10c25e698e2d49f9d0b Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Thu, 11 Jan 2018 19:13:55 -0200 Subject: [PATCH 37/61] Fix cross build to sbt 1.1.0 (#43) * Fix cross build to sbt 1.1.0 * Fix gatling dependency --- build.sbt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index ab830c6b7..f63f98ba0 100644 --- a/build.sbt +++ b/build.sbt @@ -2,9 +2,15 @@ name := """play-java-rest-api-example""" version := "2.6.x" +def gatlingVersion(scalaBinVer: String): String = scalaBinVer match { + case "2.11" => "2.2.5" + case "2.12" => "2.3.0" +} + inThisBuild( List( scalaVersion := "2.12.4", + crossScalaVersions := Seq("2.11.12", "2.12.4"), dependencyOverrides := Seq( "org.codehaus.plexus" % "plexus-utils" % "3.0.18", "com.google.code.findbugs" % "jsr305" % "3.0.1", @@ -31,8 +37,8 @@ libraryDependencies += "io.dropwizard.metrics" % "metrics-core" % "3.2.1" libraryDependencies += "com.palominolabs.http" % "url-builder" % "1.1.0" libraryDependencies += "net.jodah" % "failsafe" % "1.0.3" -libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "2.3.0" % Test -libraryDependencies += "io.gatling" % "gatling-test-framework" % "2.3.0" % Test +libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % gatlingVersion(scalaBinaryVersion.value) % Test +libraryDependencies += "io.gatling" % "gatling-test-framework" % gatlingVersion(scalaBinaryVersion.value) % Test PlayKeys.externalizeResources := false From 91b3279ae159af9baa33f8ba1c43defc8495581a Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Fri, 12 Jan 2018 15:13:52 -0200 Subject: [PATCH 38/61] Upgrade branch 2.6.x using TemplateControl (#44) * Updated with template-control on 2018-01-11T21:32:27.253Z **build.sbt: scalaVersion := "2.12.4" **build.sbt: libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "2.3.0" % Test **build.sbt: libraryDependencies += "io.gatling" % "gatling-test-framework" % "2.3.0" % Test **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.11") **build.gradle: def playVersion = "2.6.11" * test-gradle-permissions * Reverting gatling version changes * Revert scala version changes --- build.gradle | 2 +- project/plugins.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 1b05b6bc7..cba4d657a 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id "com.github.lkishalmi.gatling" version "0.7.1" } -def playVersion = "2.6.10" +def playVersion = "2.6.11" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") def gatlingVersion = getGlatlingVersion(scalaVersion) diff --git a/project/plugins.sbt b/project/plugins.sbt index e7a744a03..ee16f41fa 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.10") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.11") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From c59fae52f1e9c3fc2788860c47bdd3727942fdfa Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Mon, 5 Mar 2018 12:05:10 -0300 Subject: [PATCH 39/61] Upgrade branch 2.6.x using TemplateControl (#45) * Updated with template-control on 2018-03-02T18:56:28.237Z **build.sbt: scalaVersion := "2.12.4" **build.sbt: libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "2.3.0" % Test **build.sbt: libraryDependencies += "io.gatling" % "gatling-test-framework" % "2.3.0" % Test **/build.properties: sbt.version=1.1.1 **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.12") **build.gradle: def playVersion = "2.6.12" * Correct the template change * Revert gatling update --- build.gradle | 2 +- project/build.properties | 2 +- project/plugins.sbt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index cba4d657a..f2bd36104 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id "com.github.lkishalmi.gatling" version "0.7.1" } -def playVersion = "2.6.11" +def playVersion = "2.6.12" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") def gatlingVersion = getGlatlingVersion(scalaVersion) diff --git a/project/build.properties b/project/build.properties index 8b697bbb9..31334bbd3 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.1.0 +sbt.version=1.1.1 diff --git a/project/plugins.sbt b/project/plugins.sbt index ee16f41fa..ce2809d36 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.11") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.12") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From 1bf2a999c8b41a00fbb4887fec13f20c2d021115 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Mon, 9 Apr 2018 14:57:02 -0300 Subject: [PATCH 40/61] Upgrade branch 2.6.x using TemplateControl (#46) * Updated with template-control on 2018-04-06T19:34:58.085Z **build.sbt: scalaVersion := "2.12.4" **build.sbt: libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "2.3.0" % Test **build.sbt: libraryDependencies += "io.gatling" % "gatling-test-framework" % "2.3.0" % Test **/build.properties: sbt.version=1.1.2 **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.13") **build.gradle: def playVersion = "2.6.13" * Revert gatling change --- build.gradle | 2 +- project/build.properties | 2 +- project/plugins.sbt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index f2bd36104..ab835c42f 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id "com.github.lkishalmi.gatling" version "0.7.1" } -def playVersion = "2.6.12" +def playVersion = "2.6.13" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") def gatlingVersion = getGlatlingVersion(scalaVersion) diff --git a/project/build.properties b/project/build.properties index 31334bbd3..05313438a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.1.1 +sbt.version=1.1.2 diff --git a/project/plugins.sbt b/project/plugins.sbt index ce2809d36..03c512916 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.12") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.13") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From f865d8bbf804cba19f4cf7f00ff6959b2f7b8a87 Mon Sep 17 00:00:00 2001 From: Rich Dougherty Date: Mon, 28 May 2018 11:54:09 +1200 Subject: [PATCH 41/61] Updated with template-control on 2018-05-27T23:54:09.848Z **build.sbt: scalaVersion := "2.12.6" **build.sbt: libraryDependencies += "com.h2database" % "h2" % "1.4.197" **build.sbt: libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "2.3.1" % Test **build.sbt: libraryDependencies += "io.gatling" % "gatling-test-framework" % "2.3.1" % Test **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.15") **build.gradle: def playVersion = "2.6.15" **build.gradle: play "com.h2database:h2:1.4.197" --- build.gradle | 4 ++-- build.sbt | 8 ++++---- project/plugins.sbt | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index ab835c42f..3c26424e9 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id "com.github.lkishalmi.gatling" version "0.7.1" } -def playVersion = "2.6.13" +def playVersion = "2.6.15" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") def gatlingVersion = getGlatlingVersion(scalaVersion) @@ -45,7 +45,7 @@ dependencies { play "com.typesafe.play:play-guice_$scalaVersion:$playVersion" play "com.typesafe.play:play-logback_$scalaVersion:$playVersion" play "com.typesafe.play:play-java-jpa_$scalaVersion:$playVersion" - play "com.h2database:h2:1.4.196" + play "com.h2database:h2:1.4.197" play "org.hibernate:hibernate-core:5.2.9.Final" play "io.dropwizard.metrics:metrics-core:3.2.1" diff --git a/build.sbt b/build.sbt index f63f98ba0..c2e672265 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ def gatlingVersion(scalaBinVer: String): String = scalaBinVer match { inThisBuild( List( - scalaVersion := "2.12.4", +scalaVersion := "2.12.6" crossScalaVersions := Seq("2.11.12", "2.12.4"), dependencyOverrides := Seq( "org.codehaus.plexus" % "plexus-utils" % "3.0.18", @@ -30,15 +30,15 @@ lazy val root = (project in file(".")).enablePlugins(PlayJava, GatlingPlugin).co libraryDependencies += guice libraryDependencies += javaJpa -libraryDependencies += "com.h2database" % "h2" % "1.4.196" +libraryDependencies += "com.h2database" % "h2" % "1.4.197" libraryDependencies += "org.hibernate" % "hibernate-core" % "5.2.9.Final" libraryDependencies += "io.dropwizard.metrics" % "metrics-core" % "3.2.1" libraryDependencies += "com.palominolabs.http" % "url-builder" % "1.1.0" libraryDependencies += "net.jodah" % "failsafe" % "1.0.3" -libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % gatlingVersion(scalaBinaryVersion.value) % Test -libraryDependencies += "io.gatling" % "gatling-test-framework" % gatlingVersion(scalaBinaryVersion.value) % Test +libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "2.3.1" % Test +libraryDependencies += "io.gatling" % "gatling-test-framework" % "2.3.1" % Test PlayKeys.externalizeResources := false diff --git a/project/plugins.sbt b/project/plugins.sbt index 03c512916..f1d5ccaf3 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.13") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.15") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From e8993c8809117e4053436a8bf84bd42fe0e02f17 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Mon, 28 May 2018 11:16:36 -0400 Subject: [PATCH 42/61] Revert gatling version changes --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index c2e672265..c2d0c8c8f 100644 --- a/build.sbt +++ b/build.sbt @@ -37,8 +37,8 @@ libraryDependencies += "io.dropwizard.metrics" % "metrics-core" % "3.2.1" libraryDependencies += "com.palominolabs.http" % "url-builder" % "1.1.0" libraryDependencies += "net.jodah" % "failsafe" % "1.0.3" -libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "2.3.1" % Test -libraryDependencies += "io.gatling" % "gatling-test-framework" % "2.3.1" % Test +libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % gatlingVersion(scalaBinaryVersion.value) % Test +libraryDependencies += "io.gatling" % "gatling-test-framework" % gatlingVersion(scalaBinaryVersion.value) % Test PlayKeys.externalizeResources := false From 15a24b9402d0b0314df5a525b51c0bd0423d0a21 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Mon, 28 May 2018 13:33:20 -0400 Subject: [PATCH 43/61] Correct scalaVersion setting --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index c2d0c8c8f..1a846c876 100644 --- a/build.sbt +++ b/build.sbt @@ -9,8 +9,8 @@ def gatlingVersion(scalaBinVer: String): String = scalaBinVer match { inThisBuild( List( -scalaVersion := "2.12.6" - crossScalaVersions := Seq("2.11.12", "2.12.4"), + scalaVersion := "2.12.6", + crossScalaVersions := Seq("2.11.12", "2.12.6"), dependencyOverrides := Seq( "org.codehaus.plexus" % "plexus-utils" % "3.0.18", "com.google.code.findbugs" % "jsr305" % "3.0.1", From 43b33a6c3e3a2df12c0c482d548090535b04386c Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Mon, 18 Jun 2018 13:00:43 -0400 Subject: [PATCH 44/61] Upgrade branch 2.6.x using TemplateControl (#49) * Updated with template-control on 2018-06-08T21:15:48.903Z **build.sbt: scalaVersion := "2.12.6" **/build.properties: sbt.version=1.1.6 * Add Java 10 and 11 to Travis build * Correct scala update * Update deps --- .travis.yml | 16 +++++++++++++++- build.sbt | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- project/build.properties | 2 +- scripts/script-helper | 8 ++++---- 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 36cb27b9f..aa5e49edc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,10 +3,12 @@ scala: # When updating Scala versions, also check the excludes # in build matrix below. - 2.11.12 -- 2.12.4 +- 2.12.6 jdk: - oraclejdk8 - oraclejdk9 +- oraclejdk10 +- oraclejdk11 env: matrix: - SCRIPT=scripts/test-sbt @@ -28,6 +30,18 @@ matrix: exclude: - scala: 2.11.12 jdk: oraclejdk9 + - scala: 2.11.12 + jdk: oraclejdk10 + - scala: 2.11.12 + jdk: oraclejdk11 + allow_failures: + # We should allow failures here since Java 11 removed some modules including + # java.xml.bind which we are adding when running with Java 9+. For more details + # see http://openjdk.java.net/jeps/320 + # + # Play already has a fix for that, but it needs to be backported and released + # for 2.6.x: https://github.com/playframework/playframework/pull/8382 + - jdk: oraclejdk11 # See https://blog.travis-ci.com/2014-03-13-slack-notifications/ # created with travis encrypt command line tool diff --git a/build.sbt b/build.sbt index 1a846c876..dd1fdc477 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ version := "2.6.x" def gatlingVersion(scalaBinVer: String): String = scalaBinVer match { case "2.11" => "2.2.5" - case "2.12" => "2.3.0" + case "2.12" => "2.3.1" } inThisBuild( @@ -32,7 +32,7 @@ libraryDependencies += guice libraryDependencies += javaJpa libraryDependencies += "com.h2database" % "h2" % "1.4.197" -libraryDependencies += "org.hibernate" % "hibernate-core" % "5.2.9.Final" +libraryDependencies += "org.hibernate" % "hibernate-core" % "5.2.17.Final" libraryDependencies += "io.dropwizard.metrics" % "metrics-core" % "3.2.1" libraryDependencies += "com.palominolabs.http" % "url-builder" % "1.1.0" libraryDependencies += "net.jodah" % "failsafe" % "1.0.3" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5ce78ed19..5a17e7455 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-bin.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/project/build.properties b/project/build.properties index 05313438a..d6e35076c 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.1.2 +sbt.version=1.1.6 diff --git a/scripts/script-helper b/scripts/script-helper index ac39d9a51..9a2faa643 100644 --- a/scripts/script-helper +++ b/scripts/script-helper @@ -5,9 +5,9 @@ set -o pipefail java_version=$(java -version 2>&1 | java -version 2>&1 | awk -F '"' '/version/ {print $2}') -if [[ $java_version = 9* ]] ; then - echo "The build is using Java 9 ($java_version). We need additional JVM parameters" - export _JAVA_OPTIONS="$_JAVA_OPTIONS --add-modules=java.xml.bind" +if [[ $java_version = 1.8* ]] ; then + echo "The build is using Java 8 ($java_version). No addional JVM params needed." else - echo "The build is NOT using Java 9 ($java_version). No addional JVM params needed." + echo "The build is using Java 9+ ($java_version). We need additional JVM parameters" + export _JAVA_OPTIONS="$_JAVA_OPTIONS --add-modules=java.xml.bind" fi From 4f2263b48ce38d046035c5e091d6396a5f5e7dbe Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 17 Jul 2018 10:28:25 -0400 Subject: [PATCH 45/61] Upgrade branch 2.6.x using TemplateControl (#50) * Updated with template-control on 2018-07-16T18:38:45.495Z **build.sbt: scalaVersion := "2.12.6" **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.16") **build.gradle: def playVersion = "2.6.16" * Revert Scala version change --- build.gradle | 2 +- project/plugins.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 3c26424e9..35d04cf7e 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id "com.github.lkishalmi.gatling" version "0.7.1" } -def playVersion = "2.6.15" +def playVersion = "2.6.16" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") def gatlingVersion = getGlatlingVersion(scalaVersion) diff --git a/project/plugins.sbt b/project/plugins.sbt index f1d5ccaf3..6a4458008 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.15") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.16") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From ad6a6f8c1d823fd7a3ed952032aab5f76be07bc8 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Thu, 19 Jul 2018 14:54:38 -0400 Subject: [PATCH 46/61] Upgrade branch 2.6.x using TemplateControl (#51) * Updated with template-control on 2018-07-19T01:58:56.003Z **build.sbt: scalaVersion := "2.12.6" **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.17") **build.gradle: def playVersion = "2.6.17" * Revert scala change --- build.gradle | 2 +- build.sbt | 2 +- project/plugins.sbt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 35d04cf7e..0384dfde7 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id "com.github.lkishalmi.gatling" version "0.7.1" } -def playVersion = "2.6.16" +def playVersion = "2.6.17" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") def gatlingVersion = getGlatlingVersion(scalaVersion) diff --git a/build.sbt b/build.sbt index dd1fdc477..2450b8601 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ def gatlingVersion(scalaBinVer: String): String = scalaBinVer match { inThisBuild( List( - scalaVersion := "2.12.6", + scalaVersion := "2.12.6", crossScalaVersions := Seq("2.11.12", "2.12.6"), dependencyOverrides := Seq( "org.codehaus.plexus" % "plexus-utils" % "3.0.18", diff --git a/project/plugins.sbt b/project/plugins.sbt index 6a4458008..c926cd9c9 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.16") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.17") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From 9ca5a71461857ea3c41fb42de908045c1890697e Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 21 Aug 2018 10:13:19 -0400 Subject: [PATCH 47/61] Upgrade branch 2.6.x using TemplateControl (#54) * Updated with template-control on 2018-08-20T20:37:13.676Z **build.sbt: scalaVersion := "2.12.6" **/build.properties: sbt.version=1.2.1 **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.18") **build.gradle: def playVersion = "2.6.18" **gradle/wrapper/gradle-wrapper.properties: distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip * Revert scala version change --- build.gradle | 2 +- build.sbt | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- project/build.properties | 2 +- project/plugins.sbt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 0384dfde7..1d683c744 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id "com.github.lkishalmi.gatling" version "0.7.1" } -def playVersion = "2.6.17" +def playVersion = "2.6.18" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") def gatlingVersion = getGlatlingVersion(scalaVersion) diff --git a/build.sbt b/build.sbt index 2450b8601..dd1fdc477 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ def gatlingVersion(scalaBinVer: String): String = scalaBinVer match { inThisBuild( List( - scalaVersion := "2.12.6", + scalaVersion := "2.12.6", crossScalaVersions := Seq("2.11.12", "2.12.6"), dependencyOverrides := Seq( "org.codehaus.plexus" % "plexus-utils" % "3.0.18", diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5a17e7455..89dba2d9d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/project/build.properties b/project/build.properties index d6e35076c..5620cc502 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.1.6 +sbt.version=1.2.1 diff --git a/project/plugins.sbt b/project/plugins.sbt index c926cd9c9..f4d570006 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.17") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.18") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From 58ee5f10e7e9649bb51bca293fba7bcf73297777 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 18 Sep 2018 00:08:50 -0400 Subject: [PATCH 48/61] Upgrade branch 2.6.x using TemplateControl (#55) * Updated with template-control on 2018-09-11T20:14:50.250Z **build.sbt: scalaVersion := "2.12.6" **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.19") **build.gradle: def playVersion = "2.6.19" * Reverse wrong scala replacement --- build.gradle | 2 +- project/plugins.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 1d683c744..1b3ce62de 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id "com.github.lkishalmi.gatling" version "0.7.1" } -def playVersion = "2.6.18" +def playVersion = "2.6.19" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") def gatlingVersion = getGlatlingVersion(scalaVersion) diff --git a/project/plugins.sbt b/project/plugins.sbt index f4d570006..e90ed6090 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.18") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.19") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From 038901a40d73df1232cb02ff185fd42bcb155f3c Mon Sep 17 00:00:00 2001 From: Renato Cavalcanti Date: Fri, 16 Nov 2018 15:25:15 +0100 Subject: [PATCH 49/61] Updated with template-control on 2018-11-16T14:25:15.563Z **build.sbt: scalaVersion := "2.12.6" **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.20") **build.gradle: def playVersion = "2.6.20" --- build.gradle | 2 +- build.sbt | 2 +- project/plugins.sbt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 1b3ce62de..ac630598f 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id "com.github.lkishalmi.gatling" version "0.7.1" } -def playVersion = "2.6.19" +def playVersion = "2.6.20" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") def gatlingVersion = getGlatlingVersion(scalaVersion) diff --git a/build.sbt b/build.sbt index dd1fdc477..fb6d2e4f8 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ def gatlingVersion(scalaBinVer: String): String = scalaBinVer match { inThisBuild( List( - scalaVersion := "2.12.6", +scalaVersion := "2.12.6" crossScalaVersions := Seq("2.11.12", "2.12.6"), dependencyOverrides := Seq( "org.codehaus.plexus" % "plexus-utils" % "3.0.18", diff --git a/project/plugins.sbt b/project/plugins.sbt index e90ed6090..184947994 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.19") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.20") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From 7197d65db275272f65037f88e38c9aaf25bfb45a Mon Sep 17 00:00:00 2001 From: Renato Cavalcanti Date: Tue, 20 Nov 2018 16:48:51 +0100 Subject: [PATCH 50/61] fixed build.sbt --- bin/gatling/simulation/GatlingSpec.scala | 39 ++++++++++++++++++++++++ build.sbt | 4 +-- 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 bin/gatling/simulation/GatlingSpec.scala diff --git a/bin/gatling/simulation/GatlingSpec.scala b/bin/gatling/simulation/GatlingSpec.scala new file mode 100644 index 000000000..5252fdeff --- /dev/null +++ b/bin/gatling/simulation/GatlingSpec.scala @@ -0,0 +1,39 @@ +package simulation + +import io.gatling.core.Predef._ +import io.gatling.http.Predef._ +import scala.concurrent.duration._ +import scala.language.postfixOps + +// run with "sbt gatling:test" on another machine so you don't have resources contending. +// http://gatling.io/docs/2.2.2/general/simulation_structure.html#simulation-structure +class GatlingSpec extends Simulation { + + // change this to another machine, make sure you have Play running in producion mode + // i.e. sbt stage / sbt dist and running the script + val httpConf = http.baseURL("http://localhost:9000") + + val readClients = scenario("Clients").exec(Index.refreshManyTimes) + + setUp( + // For reference, this hits 25% CPU on a 5820K with 32 GB, running both server and load test. + // In general, you want to ramp up load slowly, and measure with a JVM that has been "warmed up": + // https://groups.google.com/forum/#!topic/gatling/mD15aj-fyo4 + readClients.inject(rampUsers(2000) over (100 seconds)).protocols(httpConf) + ) +} + +object Index { + + def post = { + val body = StringBody("""{ "title": "hello", "body": "world" }""") + exec(http("Index").post("/v1/posts").body(body).asJSON.check(status.is(200))).pause(1) + } + + def refreshAfterOneSecond = + exec(http("Index").get("/").check(status.is(200))).pause(1) + + val refreshManyTimes = repeat(500) { + refreshAfterOneSecond + } +} diff --git a/build.sbt b/build.sbt index fb6d2e4f8..f8f99a0b5 100644 --- a/build.sbt +++ b/build.sbt @@ -9,8 +9,8 @@ def gatlingVersion(scalaBinVer: String): String = scalaBinVer match { inThisBuild( List( -scalaVersion := "2.12.6" - crossScalaVersions := Seq("2.11.12", "2.12.6"), + scalaVersion := "2.12.7", + crossScalaVersions := Seq("2.11.12", "2.12.7"), dependencyOverrides := Seq( "org.codehaus.plexus" % "plexus-utils" % "3.0.18", "com.google.code.findbugs" % "jsr305" % "3.0.1", From a9b1e11f67c7de0115677b8996ffba3845552f01 Mon Sep 17 00:00:00 2001 From: Renato Cavalcanti Date: Tue, 20 Nov 2018 16:51:50 +0100 Subject: [PATCH 51/61] removed misterious GatlingSpec I don't know how this got added. :-) --- bin/gatling/simulation/GatlingSpec.scala | 39 ------------------------ 1 file changed, 39 deletions(-) delete mode 100644 bin/gatling/simulation/GatlingSpec.scala diff --git a/bin/gatling/simulation/GatlingSpec.scala b/bin/gatling/simulation/GatlingSpec.scala deleted file mode 100644 index 5252fdeff..000000000 --- a/bin/gatling/simulation/GatlingSpec.scala +++ /dev/null @@ -1,39 +0,0 @@ -package simulation - -import io.gatling.core.Predef._ -import io.gatling.http.Predef._ -import scala.concurrent.duration._ -import scala.language.postfixOps - -// run with "sbt gatling:test" on another machine so you don't have resources contending. -// http://gatling.io/docs/2.2.2/general/simulation_structure.html#simulation-structure -class GatlingSpec extends Simulation { - - // change this to another machine, make sure you have Play running in producion mode - // i.e. sbt stage / sbt dist and running the script - val httpConf = http.baseURL("http://localhost:9000") - - val readClients = scenario("Clients").exec(Index.refreshManyTimes) - - setUp( - // For reference, this hits 25% CPU on a 5820K with 32 GB, running both server and load test. - // In general, you want to ramp up load slowly, and measure with a JVM that has been "warmed up": - // https://groups.google.com/forum/#!topic/gatling/mD15aj-fyo4 - readClients.inject(rampUsers(2000) over (100 seconds)).protocols(httpConf) - ) -} - -object Index { - - def post = { - val body = StringBody("""{ "title": "hello", "body": "world" }""") - exec(http("Index").post("/v1/posts").body(body).asJSON.check(status.is(200))).pause(1) - } - - def refreshAfterOneSecond = - exec(http("Index").get("/").check(status.is(200))).pause(1) - - val refreshManyTimes = repeat(500) { - refreshAfterOneSecond - } -} From 0a371a66f41e8af7edcf581bf409ba47e08c6127 Mon Sep 17 00:00:00 2001 From: Renato Cavalcanti Date: Tue, 20 Nov 2018 17:10:01 +0100 Subject: [PATCH 52/61] removed jdk 9 and 10 --- .travis.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index aa5e49edc..83d95a53b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,7 @@ scala: - 2.12.6 jdk: - oraclejdk8 -- oraclejdk9 -- oraclejdk10 -- oraclejdk11 +- openjdk11 env: matrix: - SCRIPT=scripts/test-sbt @@ -29,11 +27,7 @@ before_cache: matrix: exclude: - scala: 2.11.12 - jdk: oraclejdk9 - - scala: 2.11.12 - jdk: oraclejdk10 - - scala: 2.11.12 - jdk: oraclejdk11 + jdk: openjdk11 allow_failures: # We should allow failures here since Java 11 removed some modules including # java.xml.bind which we are adding when running with Java 9+. For more details @@ -41,7 +35,7 @@ matrix: # # Play already has a fix for that, but it needs to be backported and released # for 2.6.x: https://github.com/playframework/playframework/pull/8382 - - jdk: oraclejdk11 + - jdk: openjdk11 # See https://blog.travis-ci.com/2014-03-13-slack-notifications/ # created with travis encrypt command line tool From 3182bd026c31bf04627c4323eb89084f5f88f0d7 Mon Sep 17 00:00:00 2001 From: Renato Cavalcanti Date: Thu, 29 Nov 2018 17:03:57 +0100 Subject: [PATCH 53/61] Updated with template-control on 2018-11-29T16:03:57.787Z /.mergify.yml: wrote /.mergify.yml **build.sbt: scalaVersion := "2.12.7" **/build.properties: sbt.version=1.2.6 --- .mergify.yml | 57 ++++++++++++++++++++++++++++++++++++++++ build.sbt | 2 +- project/build.properties | 2 +- 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 .mergify.yml diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 000000000..4a37a16dd --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,57 @@ +pull_request_rules: + - name: automatic merge on CI success require review + conditions: + - status-success=continuous-integration/travis-ci/pr + - "#approved-reviews-by>=1" + - "#changes-requested-reviews-by=0" + - label=merge-when-green + - label!=block-merge + actions: + merge: + method: squash + strict: smart + + - name: automatic merge on CI success for TemplateControl + conditions: + - status-success=continuous-integration/travis-ci/pr + - label=merge-when-green + - label=template-control + - label!=block-merge + actions: + merge: + method: squash + strict: smart + + # delete any branch when already merged + # doesn't matter if marked with labels or not + - name: delete branch after merge + conditions: + - merged + actions: + delete_head_branch: {} + + # delete 'merge-when-green' label if present and merged + - name: remove label after merge + conditions: + - merged + - label=merge-when-green + actions: + label: + remove: [merge-when-green] + + # delete 'template-control' label if present and merged + - name: remove label after merge + conditions: + - merged + - label=template-control + actions: + label: + remove: [template-control] + + - name: auto add wip + conditions: + # match a few flavours of wip + - title~=^(\[wip\]( |:) |\[WIP\]( |:) |wip( |:) |WIP( |:)).* + actions: + label: + add: ["block-merge"] \ No newline at end of file diff --git a/build.sbt b/build.sbt index f8f99a0b5..7d171a044 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ def gatlingVersion(scalaBinVer: String): String = scalaBinVer match { inThisBuild( List( - scalaVersion := "2.12.7", +scalaVersion := "2.12.7" crossScalaVersions := Seq("2.11.12", "2.12.7"), dependencyOverrides := Seq( "org.codehaus.plexus" % "plexus-utils" % "3.0.18", diff --git a/project/build.properties b/project/build.properties index 5620cc502..7c58a83ab 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.2.1 +sbt.version=1.2.6 From 0c15c123c08f9482ab657e2221aecda73d513a25 Mon Sep 17 00:00:00 2001 From: Renato Cavalcanti Date: Thu, 29 Nov 2018 20:43:28 +0100 Subject: [PATCH 54/61] fixing wrong template-ctrl replacement --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 7d171a044..f8f99a0b5 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ def gatlingVersion(scalaBinVer: String): String = scalaBinVer match { inThisBuild( List( -scalaVersion := "2.12.7" + scalaVersion := "2.12.7", crossScalaVersions := Seq("2.11.12", "2.12.7"), dependencyOverrides := Seq( "org.codehaus.plexus" % "plexus-utils" % "3.0.18", From bd73d44d7205983d7927e92691507fcae2e2eac8 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Fri, 4 Jan 2019 15:49:03 -0500 Subject: [PATCH 55/61] Upgrade branch 2.6.x using TemplateControl (#65) * Updated with template-control on 2019-01-04T17:13:23.850Z /.travis.yml: wrote /.travis.yml **build.sbt: scalaVersion := "2.12.8" **/build.properties: sbt.version=1.2.8 * Fix scalaVersion update --- .travis.yml | 74 ++++++++++++++++++++++------------------ build.sbt | 2 +- project/build.properties | 2 +- 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/.travis.yml b/.travis.yml index 83d95a53b..1e8c0e7c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,44 +1,50 @@ language: scala scala: -# When updating Scala versions, also check the excludes -# in build matrix below. -- 2.11.12 -- 2.12.6 -jdk: -- oraclejdk8 -- openjdk11 + - 2.12.8 + +before_install: + - curl -sL https://github.com/shyiko/jabba/raw/master/install.sh | bash && . ~/.jabba/jabba.sh + env: + global: + - JABBA_HOME=$HOME/.jabba matrix: - - SCRIPT=scripts/test-sbt - - SCRIPT=scripts/test-gradle -script: -- $SCRIPT -cache: - directories: - - "$HOME/.ivy2/cache" - - "$HOME/.gradle/caches" -before_cache: -- rm -rf $HOME/.ivy2/cache/com.typesafe.play/* -- rm -rf $HOME/.ivy2/cache/scala_*/sbt_*/com.typesafe.play/* -- find $HOME/.ivy2/cache -name "ivydata-*.properties" -print0 | xargs -n10 -0 rm + # There is no concise way to specify multi-dimensional build matrix: + # https://github.com/travis-ci/travis-ci/issues/1519 + - SCRIPT=scripts/test-sbt TRAVIS_JDK=adopt@1.8.192-12 + - SCRIPT=scripts/test-sbt TRAVIS_JDK=adopt@1.11.0-1 + - SCRIPT=scripts/test-gradle TRAVIS_JDK=adopt@1.8.192-12 + - SCRIPT=scripts/test-gradle TRAVIS_JDK=adopt@1.11.0-1 # Exclude some combinations from build matrix. See: # https://docs.travis-ci.com/user/customizing-the-build/#Build-Matrix matrix: - exclude: - - scala: 2.11.12 - jdk: openjdk11 + fast_finish: true allow_failures: - # We should allow failures here since Java 11 removed some modules including - # java.xml.bind which we are adding when running with Java 9+. For more details - # see http://openjdk.java.net/jeps/320 - # - # Play already has a fix for that, but it needs to be backported and released - # for 2.6.x: https://github.com/playframework/playframework/pull/8382 - - jdk: openjdk11 + # Current release of Gradle still does not supports Play 2.7.x releases + # As soon as there is a release of Gradle that fixes that, we can then + # remove this allowed failure. + - env: SCRIPT=scripts/test-gradle TRAVIS_JDK=adopt@1.8.192-12 + - env: SCRIPT=scripts/test-gradle TRAVIS_JDK=adopt@1.11.0-1 + # Java 11 is still not fully supported. It is good that we are already + # testing our sample applications to better discover possible problems + # but we can allow failures here too. + - env: SCRIPT=scripts/test-sbt TRAVIS_JDK=adopt@1.11.0-1 + +install: + - $JABBA_HOME/bin/jabba install $TRAVIS_JDK + - unset _JAVA_OPTIONS + - export JAVA_HOME="$JABBA_HOME/jdk/$TRAVIS_JDK" && export PATH="$JAVA_HOME/bin:$PATH" && java -Xmx32m -version -# See https://blog.travis-ci.com/2014-03-13-slack-notifications/ -# created with travis encrypt command line tool -notifications: - slack: - secure: XYhzPxvvw2QxwR238NJ7pb6KSUArMwn+LzLLt12xS3QQj+NN4sMcK5yDU7ZMvZ6qgZmiEB/AgmF2Ws4jibr1rB4k469+PEFBwFk2zuPMqM46UeK3KBPmlRQlRWxTuJcO+WUpkt29cbZBJ8+e1y3as+15UfOC/2CQ6579tOUjvJ8KzbFvI4RrreQZuKYmpCQRcfOw6ChCrpHW5rQlvoJpM4LHSl8Ib+DMZyfsc3WDhEGmzvm0TfLJ3fQiD2xYByiNcVUqUu06QLeciniwi7PBl4Cx9xwvJoF7Klms7j1zeatWknJ2tagX5mRuleoGQRCud73/aaGV85NoQQRUUtblXkNBHXjBXEpPL6lf9S+4GxjvC+LXK/Z+WWAdIYvwDpiqspJ6SWIfW5PQK9okZFhLaaDvqsGnEntXffccTTlzyuTTmyn7Y9U6U6ZB9Blq3U5GJd+ncPX99XpACxumV+Xz8NgtlkIG86qLgNKl1qm6GItkaSRYGvdWgtslAgh1I7cbQD8tZ73m60Z7Yk3ZWxNY0I+I27SKocwyyRENHi/n2zA2WycTswUzxXEJ72+23mpWrMhnOY4VczviqPRgdpKEYncLqcv9yLDNLM4V1uDi16xywxqqE9coew6PWw0gUQWBi2/x3ZPNnnEg/cC++LR2oHKmGpahGgQGqGCJYyH5Ylw= +script: + - $SCRIPT + +before_cache: + - find $HOME/.ivy2 -name "ivydata-*.properties" -delete + - find $HOME/.sbt -name "*.lock" -delete + +cache: + directories: + - "$HOME/.ivy2/cache" + - "$HOME/.gradle/caches" + - "$HOME/.jabba/jdk" diff --git a/build.sbt b/build.sbt index f8f99a0b5..1f7405622 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ def gatlingVersion(scalaBinVer: String): String = scalaBinVer match { inThisBuild( List( - scalaVersion := "2.12.7", + scalaVersion := "2.12.8", crossScalaVersions := Seq("2.11.12", "2.12.7"), dependencyOverrides := Seq( "org.codehaus.plexus" % "plexus-utils" % "3.0.18", diff --git a/project/build.properties b/project/build.properties index 7c58a83ab..c0bab0494 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.2.6 +sbt.version=1.2.8 From 5ceb4fba9e7c96230cb4c0239aca93e86d82f7d7 Mon Sep 17 00:00:00 2001 From: Renato Cavalcanti Date: Tue, 8 Jan 2019 18:44:23 +0100 Subject: [PATCH 56/61] Upgrade branch 2.6.x using TemplateControl (#74) * Updated with template-control on 2019-01-08T14:44:41.710Z **build.sbt: scalaVersion := "2.12.8" **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.21") **build.gradle: def playVersion = "2.6.21" **build.gradle: playTest "io.gatling.highcharts:gatling-charts-highcharts:\$gatlingVersion" **build.gradle: playTest "io.gatling:gatling-test-framework:\$gatlingVersion" * fix build --- build.gradle | 6 +++--- build.sbt | 6 +++--- project/plugins.sbt | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index ac630598f..c82fc41a8 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id "com.github.lkishalmi.gatling" version "0.7.1" } -def playVersion = "2.6.20" +def playVersion = "2.6.21" def scalaVersion = System.getProperty("scala.binary.version", /* default = */ "2.12") def gatlingVersion = getGlatlingVersion(scalaVersion) @@ -52,8 +52,8 @@ dependencies { play "com.palominolabs.http:url-builder:1.1.0" play "net.jodah:failsafe:1.0.3" - playTest "io.gatling.highcharts:gatling-charts-highcharts:$gatlingVersion" - playTest "io.gatling:gatling-test-framework:$gatlingVersion" + playTest "io.gatling.highcharts:gatling-charts-highcharts:\$gatlingVersion" + playTest "io.gatling:gatling-test-framework:\$gatlingVersion" } repositories { diff --git a/build.sbt b/build.sbt index 1f7405622..20bb68633 100644 --- a/build.sbt +++ b/build.sbt @@ -9,13 +9,13 @@ def gatlingVersion(scalaBinVer: String): String = scalaBinVer match { inThisBuild( List( - scalaVersion := "2.12.8", - crossScalaVersions := Seq("2.11.12", "2.12.7"), + crossScalaVersions := Seq("2.11.12", "2.12.8"), dependencyOverrides := Seq( "org.codehaus.plexus" % "plexus-utils" % "3.0.18", "com.google.code.findbugs" % "jsr305" % "3.0.1", "com.google.guava" % "guava" % "22.0" - ) + ), + scalaVersion := "2.12.8" ) ) diff --git a/project/plugins.sbt b/project/plugins.sbt index 184947994..26ca64bc6 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.20") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.21") // Load testing tool: // http://gatling.io/docs/2.2.2/extensions/sbt_plugin.html From 41eac6d326ecd61d11a939b25aae0bab66a9f50b Mon Sep 17 00:00:00 2001 From: Renato Cavalcanti Date: Wed, 16 Jan 2019 13:08:46 +0100 Subject: [PATCH 57/61] Updated with template-control on 2019-01-16T12:08:46.124Z /LICENSE: wrote /LICENSE /NOTICE: wrote /NOTICE /.mergify.yml: wrote /.mergify.yml **build.sbt: scalaVersion := "2.12.8" **build.gradle: playTest "io.gatling.highcharts:gatling-charts-highcharts:$gatlingVersion" **build.gradle: playTest "io.gatling:gatling-test-framework:$gatlingVersion" --- .mergify.yml | 10 ++--- LICENSE | 119 ++++++++++++++++++++++++++++++++++++++++++++++++--- NOTICE | 8 ++++ build.gradle | 4 +- build.sbt | 2 +- 5 files changed, 128 insertions(+), 15 deletions(-) create mode 100644 NOTICE diff --git a/.mergify.yml b/.mergify.yml index 4a37a16dd..3549efd41 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -22,16 +22,13 @@ pull_request_rules: method: squash strict: smart - # delete any branch when already merged - # doesn't matter if marked with labels or not - name: delete branch after merge conditions: - merged actions: delete_head_branch: {} - # delete 'merge-when-green' label if present and merged - - name: remove label after merge + - name: remove merge-when-green label after merge conditions: - merged - label=merge-when-green @@ -39,8 +36,7 @@ pull_request_rules: label: remove: [merge-when-green] - # delete 'template-control' label if present and merged - - name: remove label after merge + - name: remove template-control label after merge conditions: - merged - label=template-control @@ -54,4 +50,4 @@ pull_request_rules: - title~=^(\[wip\]( |:) |\[WIP\]( |:) |wip( |:) |WIP( |:)).* actions: label: - add: ["block-merge"] \ No newline at end of file + add: ["block-merge"] diff --git a/LICENSE b/LICENSE index b018ae2bc..670154e35 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,116 @@ -License -------- -Written in 2016 by Lightbend +CC0 1.0 Universal -To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. +Statement of Purpose -You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see . +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later +claims of infringement build upon, modify, incorporate in other works, reuse +and redistribute as freely as possible in any form whatsoever and for any +purposes, including without limitation commercial purposes. These owners may +contribute to the Commons to promote the ideal of a free culture and the +further production of creative, cultural and scientific works, or to gain +reputation or greater distribution for their Work in part through the use and +efforts of others. + +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with a +Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or her +Copyright and Related Rights in the Work and the meaning and intended legal +effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not limited +to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + + iii. publicity and privacy rights pertaining to a person's image or likeness + depicted in a Work; + + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + + v. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + + vii. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time +extensions), (iii) in any current or future medium and for any number of +copies, and (iv) for any purpose whatsoever, including without limitation +commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes +the Waiver for the benefit of each member of the public at large and to the +detriment of Affirmer's heirs and successors, fully intending that such Waiver +shall not be subject to revocation, rescission, cancellation, termination, or +any other legal or equitable action to disrupt the quiet enjoyment of the Work +by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account +Affirmer's express Statement of Purpose. In addition, to the extent the Waiver +is so judged Affirmer hereby grants to each affected person a royalty-free, +non transferable, non sublicensable, non exclusive, irrevocable and +unconditional license to exercise Affirmer's Copyright and Related Rights in +the Work (i) in all territories worldwide, (ii) for the maximum duration +provided by applicable law or treaty (including future time extensions), (iii) +in any current or future medium and for any number of copies, and (iv) for any +purpose whatsoever, including without limitation commercial, advertising or +promotional purposes (the "License"). The License shall be deemed effective as +of the date CC0 was applied by Affirmer to the Work. Should any part of the +License for any reason be judged legally invalid or ineffective under +applicable law, such partial invalidity or ineffectiveness shall not +invalidate the remainder of the License, and in such case Affirmer hereby +affirms that he or she will not (i) exercise any of his or her remaining +Copyright and Related Rights in the Work or (ii) assert any associated claims +and causes of action with respect to the Work, in either case contrary to +Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + + b. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or otherwise, + including without limitation warranties of title, merchantability, fitness + for a particular purpose, non infringement, or the absence of latent or + other defects, accuracy, or the present or absence of errors, whether or not + discoverable, all to the greatest extent permissible under applicable law. + + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without limitation + any person's Copyright and Related Rights in the Work. Further, Affirmer + disclaims responsibility for obtaining any necessary consents, permissions + or other rights required for any use of the Work. + + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +For more information, please see + diff --git a/NOTICE b/NOTICE new file mode 100644 index 000000000..6d6c034d3 --- /dev/null +++ b/NOTICE @@ -0,0 +1,8 @@ +Written by Lightbend + +To the extent possible under law, the author(s) have dedicated all copyright and +related and neighboring rights to this software to the public domain worldwide. +This software is distributed without any warranty. + +You should have received a copy of the CC0 Public Domain Dedication along with +this software. If not, see . diff --git a/build.gradle b/build.gradle index c82fc41a8..56079f0b1 100644 --- a/build.gradle +++ b/build.gradle @@ -52,8 +52,8 @@ dependencies { play "com.palominolabs.http:url-builder:1.1.0" play "net.jodah:failsafe:1.0.3" - playTest "io.gatling.highcharts:gatling-charts-highcharts:\$gatlingVersion" - playTest "io.gatling:gatling-test-framework:\$gatlingVersion" + playTest "io.gatling.highcharts:gatling-charts-highcharts:$gatlingVersion" + playTest "io.gatling:gatling-test-framework:$gatlingVersion" } repositories { diff --git a/build.sbt b/build.sbt index 20bb68633..a309f141c 100644 --- a/build.sbt +++ b/build.sbt @@ -15,7 +15,7 @@ inThisBuild( "com.google.code.findbugs" % "jsr305" % "3.0.1", "com.google.guava" % "guava" % "22.0" ), - scalaVersion := "2.12.8" +scalaVersion := "2.12.8" ) ) From 37dbbe85454623dee1e8e172b2b288deddc46d65 Mon Sep 17 00:00:00 2001 From: Renato Cavalcanti Date: Thu, 17 Jan 2019 16:10:58 +0100 Subject: [PATCH 58/61] Updated with template-control on 2019-01-17T15:10:58.259Z /.mergify.yml: wrote /.mergify.yml --- .mergify.yml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index 3549efd41..b215a7709 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -4,7 +4,6 @@ pull_request_rules: - status-success=continuous-integration/travis-ci/pr - "#approved-reviews-by>=1" - "#changes-requested-reviews-by=0" - - label=merge-when-green - label!=block-merge actions: merge: @@ -15,7 +14,6 @@ pull_request_rules: conditions: - status-success=continuous-integration/travis-ci/pr - label=merge-when-green - - label=template-control - label!=block-merge actions: merge: @@ -35,19 +33,3 @@ pull_request_rules: actions: label: remove: [merge-when-green] - - - name: remove template-control label after merge - conditions: - - merged - - label=template-control - actions: - label: - remove: [template-control] - - - name: auto add wip - conditions: - # match a few flavours of wip - - title~=^(\[wip\]( |:) |\[WIP\]( |:) |wip( |:) |WIP( |:)).* - actions: - label: - add: ["block-merge"] From c9b128bf35e96171dde7e54f5fa29b22a6d618f4 Mon Sep 17 00:00:00 2001 From: Renato Cavalcanti Date: Fri, 1 Feb 2019 14:37:26 +0100 Subject: [PATCH 59/61] Upgrade branch 2.6.x using TemplateControl (#79) ``` Updated with template-control on 2019-02-01T10:40:34.512Z /.mergify.yml: wrote /.mergify.yml ``` --- .mergify.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index b215a7709..fbbe4380f 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -25,11 +25,3 @@ pull_request_rules: - merged actions: delete_head_branch: {} - - - name: remove merge-when-green label after merge - conditions: - - merged - - label=merge-when-green - actions: - label: - remove: [merge-when-green] From c92efd24c630e116a1b1185c3beca27e72abf497 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Thu, 14 Feb 2019 00:39:39 -0500 Subject: [PATCH 60/61] Updated with template-control on 2019-02-13T20:25:39.721Z (#81) /.mergify.yml: wrote /.mergify.yml --- .mergify.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index fbbe4380f..32f8689ae 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -1,7 +1,7 @@ pull_request_rules: - name: automatic merge on CI success require review conditions: - - status-success=continuous-integration/travis-ci/pr + - status-success=Travis CI - Pull Request - "#approved-reviews-by>=1" - "#changes-requested-reviews-by=0" - label!=block-merge @@ -12,7 +12,7 @@ pull_request_rules: - name: automatic merge on CI success for TemplateControl conditions: - - status-success=continuous-integration/travis-ci/pr + - status-success=Travis CI - Pull Request - label=merge-when-green - label!=block-merge actions: From c52e8748f6f966cbef9b551e4c92c4d82e6f7ded Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 23 Apr 2019 15:49:55 +0100 Subject: [PATCH 61/61] Nest play-java-rest-api-example --- .gitignore => play-java-rest-api-example/.gitignore | 0 .../.mergify.yml | 0 .../.travis.yml | 0 LICENSE => play-java-rest-api-example/LICENSE | 0 NOTICE => play-java-rest-api-example/NOTICE | 0 README.md => play-java-rest-api-example/README.md | 0 {app => play-java-rest-api-example/app}/Module.java | 0 .../app}/controllers/HomeController.java | 0 .../app}/v1/post/JPAPostRepository.java | 0 .../app}/v1/post/PostAction.java | 0 .../app}/v1/post/PostController.java | 0 .../app}/v1/post/PostData.java | 0 .../app}/v1/post/PostExecutionContext.java | 0 .../app}/v1/post/PostRepository.java | 0 .../app}/v1/post/PostResource.java | 0 .../app}/v1/post/PostResourceHandler.java | 0 .../app}/views/index.scala.html | 0 .../app}/views/timeout.scala.html | 0 .../build.gradle | 0 build.sbt => play-java-rest-api-example/build.sbt | 0 .../conf}/META-INF/persistence.xml | 0 .../conf}/application.conf | 0 .../conf}/logback.xml | 0 .../conf}/posts.routes | 0 {conf => play-java-rest-api-example/conf}/routes | 0 .../gatling}/simulation/GatlingSpec.scala | 0 .../gradle}/wrapper/gradle-wrapper.jar | Bin .../gradle}/wrapper/gradle-wrapper.properties | 0 gradlew => play-java-rest-api-example/gradlew | 0 .../gradlew.bat | 0 .../project}/build.properties | 0 .../project}/plugins.sbt | 0 .../public}/images/favicon.png | Bin .../public}/javascripts/main.js | 0 .../public}/stylesheets/main.css | 0 .../scripts}/script-helper | 0 .../scripts}/test-gradle | 0 .../scripts}/test-sbt | 0 .../test}/it/IntegrationTest.java | 0 39 files changed, 0 insertions(+), 0 deletions(-) rename .gitignore => play-java-rest-api-example/.gitignore (100%) rename .mergify.yml => play-java-rest-api-example/.mergify.yml (100%) rename .travis.yml => play-java-rest-api-example/.travis.yml (100%) rename LICENSE => play-java-rest-api-example/LICENSE (100%) rename NOTICE => play-java-rest-api-example/NOTICE (100%) rename README.md => play-java-rest-api-example/README.md (100%) rename {app => play-java-rest-api-example/app}/Module.java (100%) rename {app => play-java-rest-api-example/app}/controllers/HomeController.java (100%) rename {app => play-java-rest-api-example/app}/v1/post/JPAPostRepository.java (100%) rename {app => play-java-rest-api-example/app}/v1/post/PostAction.java (100%) rename {app => play-java-rest-api-example/app}/v1/post/PostController.java (100%) rename {app => play-java-rest-api-example/app}/v1/post/PostData.java (100%) rename {app => play-java-rest-api-example/app}/v1/post/PostExecutionContext.java (100%) rename {app => play-java-rest-api-example/app}/v1/post/PostRepository.java (100%) rename {app => play-java-rest-api-example/app}/v1/post/PostResource.java (100%) rename {app => play-java-rest-api-example/app}/v1/post/PostResourceHandler.java (100%) rename {app => play-java-rest-api-example/app}/views/index.scala.html (100%) rename {app => play-java-rest-api-example/app}/views/timeout.scala.html (100%) rename build.gradle => play-java-rest-api-example/build.gradle (100%) rename build.sbt => play-java-rest-api-example/build.sbt (100%) rename {conf => play-java-rest-api-example/conf}/META-INF/persistence.xml (100%) rename {conf => play-java-rest-api-example/conf}/application.conf (100%) rename {conf => play-java-rest-api-example/conf}/logback.xml (100%) rename {conf => play-java-rest-api-example/conf}/posts.routes (100%) rename {conf => play-java-rest-api-example/conf}/routes (100%) rename {gatling => play-java-rest-api-example/gatling}/simulation/GatlingSpec.scala (100%) rename {gradle => play-java-rest-api-example/gradle}/wrapper/gradle-wrapper.jar (100%) rename {gradle => play-java-rest-api-example/gradle}/wrapper/gradle-wrapper.properties (100%) rename gradlew => play-java-rest-api-example/gradlew (100%) rename gradlew.bat => play-java-rest-api-example/gradlew.bat (100%) rename {project => play-java-rest-api-example/project}/build.properties (100%) rename {project => play-java-rest-api-example/project}/plugins.sbt (100%) rename {public => play-java-rest-api-example/public}/images/favicon.png (100%) rename {public => play-java-rest-api-example/public}/javascripts/main.js (100%) rename {public => play-java-rest-api-example/public}/stylesheets/main.css (100%) rename {scripts => play-java-rest-api-example/scripts}/script-helper (100%) rename {scripts => play-java-rest-api-example/scripts}/test-gradle (100%) rename {scripts => play-java-rest-api-example/scripts}/test-sbt (100%) rename {test => play-java-rest-api-example/test}/it/IntegrationTest.java (100%) diff --git a/.gitignore b/play-java-rest-api-example/.gitignore similarity index 100% rename from .gitignore rename to play-java-rest-api-example/.gitignore diff --git a/.mergify.yml b/play-java-rest-api-example/.mergify.yml similarity index 100% rename from .mergify.yml rename to play-java-rest-api-example/.mergify.yml diff --git a/.travis.yml b/play-java-rest-api-example/.travis.yml similarity index 100% rename from .travis.yml rename to play-java-rest-api-example/.travis.yml diff --git a/LICENSE b/play-java-rest-api-example/LICENSE similarity index 100% rename from LICENSE rename to play-java-rest-api-example/LICENSE diff --git a/NOTICE b/play-java-rest-api-example/NOTICE similarity index 100% rename from NOTICE rename to play-java-rest-api-example/NOTICE diff --git a/README.md b/play-java-rest-api-example/README.md similarity index 100% rename from README.md rename to play-java-rest-api-example/README.md diff --git a/app/Module.java b/play-java-rest-api-example/app/Module.java similarity index 100% rename from app/Module.java rename to play-java-rest-api-example/app/Module.java diff --git a/app/controllers/HomeController.java b/play-java-rest-api-example/app/controllers/HomeController.java similarity index 100% rename from app/controllers/HomeController.java rename to play-java-rest-api-example/app/controllers/HomeController.java diff --git a/app/v1/post/JPAPostRepository.java b/play-java-rest-api-example/app/v1/post/JPAPostRepository.java similarity index 100% rename from app/v1/post/JPAPostRepository.java rename to play-java-rest-api-example/app/v1/post/JPAPostRepository.java diff --git a/app/v1/post/PostAction.java b/play-java-rest-api-example/app/v1/post/PostAction.java similarity index 100% rename from app/v1/post/PostAction.java rename to play-java-rest-api-example/app/v1/post/PostAction.java diff --git a/app/v1/post/PostController.java b/play-java-rest-api-example/app/v1/post/PostController.java similarity index 100% rename from app/v1/post/PostController.java rename to play-java-rest-api-example/app/v1/post/PostController.java diff --git a/app/v1/post/PostData.java b/play-java-rest-api-example/app/v1/post/PostData.java similarity index 100% rename from app/v1/post/PostData.java rename to play-java-rest-api-example/app/v1/post/PostData.java diff --git a/app/v1/post/PostExecutionContext.java b/play-java-rest-api-example/app/v1/post/PostExecutionContext.java similarity index 100% rename from app/v1/post/PostExecutionContext.java rename to play-java-rest-api-example/app/v1/post/PostExecutionContext.java diff --git a/app/v1/post/PostRepository.java b/play-java-rest-api-example/app/v1/post/PostRepository.java similarity index 100% rename from app/v1/post/PostRepository.java rename to play-java-rest-api-example/app/v1/post/PostRepository.java diff --git a/app/v1/post/PostResource.java b/play-java-rest-api-example/app/v1/post/PostResource.java similarity index 100% rename from app/v1/post/PostResource.java rename to play-java-rest-api-example/app/v1/post/PostResource.java diff --git a/app/v1/post/PostResourceHandler.java b/play-java-rest-api-example/app/v1/post/PostResourceHandler.java similarity index 100% rename from app/v1/post/PostResourceHandler.java rename to play-java-rest-api-example/app/v1/post/PostResourceHandler.java diff --git a/app/views/index.scala.html b/play-java-rest-api-example/app/views/index.scala.html similarity index 100% rename from app/views/index.scala.html rename to play-java-rest-api-example/app/views/index.scala.html diff --git a/app/views/timeout.scala.html b/play-java-rest-api-example/app/views/timeout.scala.html similarity index 100% rename from app/views/timeout.scala.html rename to play-java-rest-api-example/app/views/timeout.scala.html diff --git a/build.gradle b/play-java-rest-api-example/build.gradle similarity index 100% rename from build.gradle rename to play-java-rest-api-example/build.gradle diff --git a/build.sbt b/play-java-rest-api-example/build.sbt similarity index 100% rename from build.sbt rename to play-java-rest-api-example/build.sbt diff --git a/conf/META-INF/persistence.xml b/play-java-rest-api-example/conf/META-INF/persistence.xml similarity index 100% rename from conf/META-INF/persistence.xml rename to play-java-rest-api-example/conf/META-INF/persistence.xml diff --git a/conf/application.conf b/play-java-rest-api-example/conf/application.conf similarity index 100% rename from conf/application.conf rename to play-java-rest-api-example/conf/application.conf diff --git a/conf/logback.xml b/play-java-rest-api-example/conf/logback.xml similarity index 100% rename from conf/logback.xml rename to play-java-rest-api-example/conf/logback.xml diff --git a/conf/posts.routes b/play-java-rest-api-example/conf/posts.routes similarity index 100% rename from conf/posts.routes rename to play-java-rest-api-example/conf/posts.routes diff --git a/conf/routes b/play-java-rest-api-example/conf/routes similarity index 100% rename from conf/routes rename to play-java-rest-api-example/conf/routes diff --git a/gatling/simulation/GatlingSpec.scala b/play-java-rest-api-example/gatling/simulation/GatlingSpec.scala similarity index 100% rename from gatling/simulation/GatlingSpec.scala rename to play-java-rest-api-example/gatling/simulation/GatlingSpec.scala diff --git a/gradle/wrapper/gradle-wrapper.jar b/play-java-rest-api-example/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from gradle/wrapper/gradle-wrapper.jar rename to play-java-rest-api-example/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/play-java-rest-api-example/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from gradle/wrapper/gradle-wrapper.properties rename to play-java-rest-api-example/gradle/wrapper/gradle-wrapper.properties diff --git a/gradlew b/play-java-rest-api-example/gradlew similarity index 100% rename from gradlew rename to play-java-rest-api-example/gradlew diff --git a/gradlew.bat b/play-java-rest-api-example/gradlew.bat similarity index 100% rename from gradlew.bat rename to play-java-rest-api-example/gradlew.bat diff --git a/project/build.properties b/play-java-rest-api-example/project/build.properties similarity index 100% rename from project/build.properties rename to play-java-rest-api-example/project/build.properties diff --git a/project/plugins.sbt b/play-java-rest-api-example/project/plugins.sbt similarity index 100% rename from project/plugins.sbt rename to play-java-rest-api-example/project/plugins.sbt diff --git a/public/images/favicon.png b/play-java-rest-api-example/public/images/favicon.png similarity index 100% rename from public/images/favicon.png rename to play-java-rest-api-example/public/images/favicon.png diff --git a/public/javascripts/main.js b/play-java-rest-api-example/public/javascripts/main.js similarity index 100% rename from public/javascripts/main.js rename to play-java-rest-api-example/public/javascripts/main.js diff --git a/public/stylesheets/main.css b/play-java-rest-api-example/public/stylesheets/main.css similarity index 100% rename from public/stylesheets/main.css rename to play-java-rest-api-example/public/stylesheets/main.css diff --git a/scripts/script-helper b/play-java-rest-api-example/scripts/script-helper similarity index 100% rename from scripts/script-helper rename to play-java-rest-api-example/scripts/script-helper diff --git a/scripts/test-gradle b/play-java-rest-api-example/scripts/test-gradle similarity index 100% rename from scripts/test-gradle rename to play-java-rest-api-example/scripts/test-gradle diff --git a/scripts/test-sbt b/play-java-rest-api-example/scripts/test-sbt similarity index 100% rename from scripts/test-sbt rename to play-java-rest-api-example/scripts/test-sbt diff --git a/test/it/IntegrationTest.java b/play-java-rest-api-example/test/it/IntegrationTest.java similarity index 100% rename from test/it/IntegrationTest.java rename to play-java-rest-api-example/test/it/IntegrationTest.java