[JAVA-13854] Half list moved (#12598)
* [JAVA-13854] added parent module * [JAVA-13854] moved apache-tapestry(submodule) to web-modules(parent) * [JAVA-13854] moved bootique(submodule) to web-modules(parent) * [JAVA-13854] moved dropwizard(submodule) to web-modules(parent) * [JAVA-13854] moved blade(submodule) to web-modules(parent) * [JAVA-13854] moved java-lite(submodule) to web-modules(parent) * [JAVA-13854] moved jooby(submodule) to web-modules(parent) * [JAVA-13854] moved linkrest(submodule) to web-modules(parent) * [JAVA-13854] moved ninja(submodule) to web-modules(parent) * [JAVA-13854] moved ratpack(submodule) to web-modules(parent) * [JAVA-13854] moved resteasy(submodule) to web-modules(parent) * [JAVA-13854] moved restx(submodule) to web-modules(parent) * [JAVA-13854] moved spark-java(submodule) to web-modules(parent) * [JAVA-13854] moved vraptor(submodule) to web-modules(parent) * [JAVA-13854] delete modules that were moved * [JAVA-13854] * [JAVA-13854] * [JAVA-13854] delete ninja submodule + moved raml(submodule) to web-modules(parent) * [JAVA-13854] moved gwt(submodule) to web-modules(parent) * [JAVA-13854] moved jakarta-ee(submodule) to web-modules(parent) * [JAVA-13854] moved javax-servlets(submodule) to web-modules(parent) * [JAVA-13854] moved javax-servlets-2(submodule) to web-modules(parent) * [JAVA-13854] moved jee-7(submodule) to web-modules(parent) * [JAVA-13854] moved play-framework(not a module) to web-modules * [JAVA-13854] fix failing test * [JAVA-13854] moved struts-2(submodule) to web-modules(parent) * [JAVA-13854] moved wicket(submodule) to web-modules(parent) * [JAVA-13854] deleted modules that were moved to web-modules * JAVA-13854 Removed moved modules from the main pom.xml Co-authored-by: panagiotiskakos <panagiotis.kakos@libra-is.com> Co-authored-by: Dhawal Kapil <dhawalkapil@gmail.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
## Play Framework
|
||||
|
||||
This module contains articles about the Play Framework.
|
||||
|
||||
### Relevant Articles:
|
||||
- [REST API with Play Framework in Java](https://www.baeldung.com/rest-api-with-play)
|
||||
- [Routing In Play Applications in Java](https://www.baeldung.com/routing-in-play)
|
||||
- [Introduction To Play In Java](https://www.baeldung.com/java-intro-to-the-play-framework)
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package controllers;
|
||||
|
||||
import play.data.Form;
|
||||
import play.data.FormFactory;
|
||||
import play.mvc.Controller;
|
||||
import play.mvc.Result;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
// Add the following to conf/routes
|
||||
/*
|
||||
GET /$model;format="camel"$ controllers.$model;format="Camel"$Controller.$model;format="camel"$Get
|
||||
POST /$model;format="camel"$ controllers.$model;format="Camel"$Controller.$model;format="camel"$Post
|
||||
*/
|
||||
|
||||
/**
|
||||
* $model;format="Camel"$ form controller for Play Java
|
||||
*/
|
||||
public class $model;format="Camel"$Controller extends Controller {
|
||||
|
||||
private final Form<$model;format="Camel"$Data> $model;format="camel"$Form;
|
||||
|
||||
@Inject
|
||||
public $model;format="Camel"$Controller(FormFactory formFactory) {
|
||||
this.$model;format="camel"$Form = formFactory.form($model;format="Camel"$Data.class);
|
||||
}
|
||||
|
||||
public Result $model;format="camel"$Get() {
|
||||
return ok(views.html.$model;format="camel"$.form.render($model;format="camel"$Form));
|
||||
}
|
||||
|
||||
public Result $model;format="camel"$Post() {
|
||||
Form<$model;format="Camel"$Data> boundForm = $model;format="camel"$Form.bindFromRequest();
|
||||
if (boundForm.hasErrors()) {
|
||||
return badRequest(views.html.$model;format="camel"$.form.render(boundForm));
|
||||
} else {
|
||||
$model;format="Camel"$Data $model;format="camel"$ = boundForm.get();
|
||||
flash("success", "$model;format="Camel"$ " + $model;format="camel"$);
|
||||
return redirect(routes.$model;format="Camel"$Controller.$model;format="camel"$Get());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package controllers;
|
||||
|
||||
import play.data.validation.Constraints;
|
||||
|
||||
public class $model;format="Camel"$Data {
|
||||
|
||||
@Constraints.Required
|
||||
private String name;
|
||||
|
||||
@Constraints.Required
|
||||
private Integer age;
|
||||
|
||||
public $model;format="Camel"$Data() {
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(Integer age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("$model;format="Camel"$Data(%s, %s)", name, age);
|
||||
}
|
||||
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
@($model;format="camel"$Form: Form[$model;format="Camel"$Data])
|
||||
|
||||
<h1>$model;format="camel"$ form</h1>
|
||||
|
||||
@flash.getOrDefault("success", "")
|
||||
|
||||
@helper.form(action = routes.$model;format="Camel"$Controller.$model;format="camel"$Post()) {
|
||||
@helper.CSRF.formField
|
||||
@helper.inputText($model;format="camel"$Form("name"))
|
||||
@helper.inputText($model;format="camel"$Form("age"))
|
||||
<input type="submit" value="submit"/>
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
description = Generates a Controller with form handling
|
||||
model = user
|
||||
@@ -0,0 +1 @@
|
||||
Temporary file until g8-scaffold will generate "test" directory
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package controllers;
|
||||
|
||||
import org.junit.Test;
|
||||
import play.Application;
|
||||
import play.filters.csrf.*;
|
||||
import play.inject.guice.GuiceApplicationBuilder;
|
||||
import play.mvc.*;
|
||||
import play.test.WithApplication;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static play.mvc.Http.RequestBuilder;
|
||||
import static play.mvc.Http.Status.OK;
|
||||
import static play.test.Helpers.*;
|
||||
import static play.api.test.CSRFTokenHelper.*;
|
||||
|
||||
public class $model;format="Camel"$ControllerTest extends WithApplication {
|
||||
|
||||
@Override
|
||||
protected Application provideApplication() {
|
||||
return new GuiceApplicationBuilder().build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test$model;format="Camel"$Get() {
|
||||
RequestBuilder request = new RequestBuilder()
|
||||
.method(GET)
|
||||
.uri("/$model;format="camel"$");
|
||||
|
||||
Result result = route(app, request);
|
||||
assertEquals(OK, result.status());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test$model;format="Camel"$Post() {
|
||||
HashMap<String, String> formData = new HashMap<>();
|
||||
formData.put("name", "play");
|
||||
formData.put("age", "4");
|
||||
RequestBuilder request = addCSRFToken(new RequestBuilder()
|
||||
.header(Http.HeaderNames.HOST, "localhost")
|
||||
.method(POST)
|
||||
.bodyForm(formData)
|
||||
.uri("/$model;format="camel"$"));
|
||||
|
||||
Result result = route(app, request);
|
||||
assertEquals(SEE_OTHER, result.status());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
logs
|
||||
target
|
||||
/.idea
|
||||
/.idea_modules
|
||||
/.classpath
|
||||
/.project
|
||||
/.settings
|
||||
/RUNNING_PID
|
||||
@@ -0,0 +1,3 @@
|
||||
### Relevant Articles:
|
||||
|
||||
- [Asynchronous HTTP Programming with Play Framework](https://www.baeldung.com/java-play-asynchronous-http-programming)
|
||||
@@ -0,0 +1,38 @@
|
||||
package controllers;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import play.mvc.Controller;
|
||||
import play.mvc.Http;
|
||||
import play.mvc.Result;
|
||||
|
||||
/**
|
||||
* This controller contains an action to handle HTTP requests to the application's home page.
|
||||
*/
|
||||
public class HomeController extends Controller {
|
||||
|
||||
/**
|
||||
* An action that renders an HTML page with a welcome message. The configuration in the
|
||||
* <code>routes</code> file means that this method will be called when the application receives
|
||||
* a
|
||||
* <code>GET</code> request with a path of <code>/</code>.
|
||||
*/
|
||||
public Result index(Http.Request request) throws JsonProcessingException {
|
||||
return ok(printStats(request));
|
||||
}
|
||||
|
||||
private String printStats(Http.Request request) throws JsonProcessingException {
|
||||
Map<String, String[]> stringMap = request.body()
|
||||
.asFormUrlEncoded();
|
||||
Map<String, Object> map = ImmutableMap.of(
|
||||
"Result", "ok",
|
||||
"GetParams", request.queryString(),
|
||||
"PostParams", stringMap == null ? Collections.emptyMap() : stringMap,
|
||||
"Headers", request.getHeaders().toMap()
|
||||
);
|
||||
return new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(map);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@()
|
||||
|
||||
@main("Welcome to Play") {
|
||||
<h1>Welcome to Play!</h1>
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
@*
|
||||
* This template is called from the `index` template. This template
|
||||
* handles the rendering of the page header and body tags. It takes
|
||||
* two arguments, a `String` for the title of the page and an `Html`
|
||||
* object to insert into the body of the page.
|
||||
*@
|
||||
@(title: String)(content: Html)
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@* Here's where we render the page title `String`. *@
|
||||
<title>@title</title>
|
||||
<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
|
||||
<link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
|
||||
</head>
|
||||
<body>
|
||||
@* And here's where we render the `Html` object containing
|
||||
* the page content. *@
|
||||
@content
|
||||
|
||||
<script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,12 @@
|
||||
name := """async"""
|
||||
organization := "com.example"
|
||||
|
||||
version := "1.0-SNAPSHOT"
|
||||
|
||||
lazy val root = (project in file(".")).enablePlugins(PlayJava)
|
||||
|
||||
scalaVersion := "2.13.1"
|
||||
|
||||
// comment out the original line
|
||||
libraryDependencies += guice
|
||||
libraryDependencies += javaWs
|
||||
@@ -0,0 +1,11 @@
|
||||
# This is the main configuration file for the application.
|
||||
# https://www.playframework.com/documentation/latest/ConfigFile
|
||||
play.ws.followRedirects=false
|
||||
play.ws.useragent=MyPlayApplication
|
||||
play.ws.compressionEnabled=true
|
||||
# time to wait for the connection to be established
|
||||
play.ws.timeout.connection=30.seconds
|
||||
# time to wait for data after the connection is open
|
||||
play.ws.timeout.idle=30.seconds
|
||||
# max time available to complete the request
|
||||
play.ws.timeout.request=300.seconds
|
||||
@@ -0,0 +1,36 @@
|
||||
<!-- https://www.playframework.com/documentation/latest/SettingsLogger -->
|
||||
<configuration>
|
||||
|
||||
<conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel" />
|
||||
|
||||
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||
<file>${application.home:-.}/logs/application.log</file>
|
||||
<encoder>
|
||||
<pattern>%date [%level] from %logger in %thread - %message%n%xException</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%coloredLevel %logger{15} - %message%n%xException{10}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<appender-ref ref="FILE" />
|
||||
</appender>
|
||||
|
||||
<appender name="ASYNCSTDOUT" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</appender>
|
||||
|
||||
<logger name="play" level="INFO" />
|
||||
<logger name="application" level="DEBUG" />
|
||||
<logger name="controllers" level="DEBUG" />
|
||||
|
||||
<root level="WARN">
|
||||
<appender-ref ref="ASYNCFILE" />
|
||||
<appender-ref ref="ASYNCSTDOUT" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
@@ -0,0 +1,10 @@
|
||||
# Routes
|
||||
# This file defines all application routes (Higher priority routes first)
|
||||
# ~~~~
|
||||
|
||||
# An example controller showing a sample home page
|
||||
GET / controllers.HomeController.index(request: Request)
|
||||
POST / controllers.HomeController.index(request: Request)
|
||||
|
||||
# Map static resources from the /public folder to the /assets URL path
|
||||
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
|
||||
@@ -0,0 +1 @@
|
||||
sbt.version=1.3.3
|
||||
@@ -0,0 +1,7 @@
|
||||
// The Play plugin
|
||||
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.7.3")
|
||||
|
||||
// Defines scaffolding (found under .g8 folder)
|
||||
// http://www.foundweekends.org/giter8/scaffolding.html
|
||||
// sbt "g8Scaffold form"
|
||||
addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.11.0")
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 687 B |
@@ -0,0 +1,232 @@
|
||||
package controllers;
|
||||
|
||||
import static java.time.temporal.ChronoUnit.SECONDS;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static play.mvc.Http.Status.SERVICE_UNAVAILABLE;
|
||||
|
||||
import akka.Done;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.util.ByteString;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.IntStream;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import play.Application;
|
||||
import play.inject.guice.GuiceApplicationBuilder;
|
||||
import play.libs.concurrent.Futures;
|
||||
import play.libs.ws.WSClient;
|
||||
import play.libs.ws.WSResponse;
|
||||
import play.libs.ws.ahc.AhcCurlRequestLogger;
|
||||
import play.mvc.Result;
|
||||
import play.mvc.Results;
|
||||
import play.test.WithServer;
|
||||
|
||||
public class HomeControllerTest extends WithServer {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(HomeControllerTest.class);
|
||||
private String url;
|
||||
private int port;
|
||||
|
||||
@Override
|
||||
protected Application provideApplication() {
|
||||
return new GuiceApplicationBuilder().build();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
OptionalInt optHttpsPort = testServer.getRunningHttpsPort();
|
||||
if (optHttpsPort.isPresent()) {
|
||||
port = optHttpsPort.getAsInt();
|
||||
url = "https://localhost:" + port;
|
||||
} else {
|
||||
port = testServer.getRunningHttpPort()
|
||||
.getAsInt();
|
||||
url = "http://localhost:" + port;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenASingleGetRequestWhenResponseThenBlockWithCompletableAndLog()
|
||||
throws Exception {
|
||||
WSClient ws = play.test.WSTestClient.newClient(port);
|
||||
WSResponse wsResponse = ws.url(url)
|
||||
.setRequestFilter(new AhcCurlRequestLogger())
|
||||
.addHeader("key", "value")
|
||||
.addQueryParameter("num", "" + 1)
|
||||
.get()
|
||||
.toCompletableFuture()
|
||||
.get();
|
||||
|
||||
log.debug("Thread#" + Thread.currentThread()
|
||||
.getId() + " Request complete: Response code = "
|
||||
+ wsResponse.getStatus()
|
||||
+ " | Response: " + wsResponse.getBody() + " | Current Time:"
|
||||
+ System.currentTimeMillis());
|
||||
assert (HttpStatus.SC_OK == wsResponse.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenASingleGetRequestWhenResponseThenLog() throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
WSClient ws = play.test.WSTestClient.newClient(port);
|
||||
ws.url(url)
|
||||
.setRequestFilter(new AhcCurlRequestLogger())
|
||||
.addHeader("key", "value")
|
||||
.addQueryParameter("num", "" + 1)
|
||||
.get()
|
||||
.thenAccept(r -> {
|
||||
log.debug("Thread#" + Thread.currentThread()
|
||||
.getId() + " Request complete: Response code = "
|
||||
+ r.getStatus()
|
||||
+ " | Response: " + r.getBody() + " | Current Time:" + System.currentTimeMillis());
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
log.debug(
|
||||
"Waiting for requests to be completed. Current Time: " + System.currentTimeMillis());
|
||||
latch.await(5, TimeUnit.SECONDS );
|
||||
assertEquals(0, latch.getCount());
|
||||
log.debug("All requests have been completed. Exiting test.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenASinglePostRequestWhenResponseThenLog() throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
WSClient ws = play.test.WSTestClient.newClient(port);
|
||||
ws.url(url)
|
||||
.setContentType("application/x-www-form-urlencoded")
|
||||
.post("key1=value1&key2=value2")
|
||||
.thenAccept(r -> {
|
||||
log.debug("Thread#" + Thread.currentThread()
|
||||
.getId() + " Request complete: Response code = "
|
||||
+ r.getStatus()
|
||||
+ " | Response: " + r.getBody() + " | Current Time:" + System.currentTimeMillis());
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
log.debug(
|
||||
"Waiting for requests to be completed. Current Time: " + System.currentTimeMillis());
|
||||
latch.await(5, TimeUnit.SECONDS );
|
||||
assertEquals(0, latch.getCount());
|
||||
log.debug("All requests have been completed. Exiting test.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenMultipleRequestsWhenResponseThenLog() throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(100);
|
||||
WSClient ws = play.test.WSTestClient.newClient(port);
|
||||
IntStream.range(0, 100)
|
||||
.parallel()
|
||||
.forEach(num ->
|
||||
ws.url(url)
|
||||
.setRequestFilter(new AhcCurlRequestLogger())
|
||||
.addHeader("key", "value")
|
||||
.addQueryParameter("num", "" + num)
|
||||
.get()
|
||||
.thenAccept(r -> {
|
||||
log.debug(
|
||||
"Thread#" + num + " Request complete: Response code = " + r.getStatus()
|
||||
+ " | Response: " + r.getBody() + " | Current Time:"
|
||||
+ System.currentTimeMillis());
|
||||
latch.countDown();
|
||||
})
|
||||
);
|
||||
|
||||
log.debug(
|
||||
"Waiting for requests to be completed. Current Time: " + System.currentTimeMillis());
|
||||
latch.await(5, TimeUnit.SECONDS );
|
||||
assertEquals(0, latch.getCount());
|
||||
log.debug("All requests have been completed. Exiting test.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLongResponseWhenTimeoutThenHandle() throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
WSClient ws = play.test.WSTestClient.newClient(port);
|
||||
Futures futures = app.injector()
|
||||
.instanceOf(Futures.class);
|
||||
CompletionStage<Result> f = futures.timeout(
|
||||
ws.url(url)
|
||||
.setRequestTimeout(Duration.of(1, SECONDS))
|
||||
.get()
|
||||
.thenApply(result -> {
|
||||
try {
|
||||
Thread.sleep(2000L);
|
||||
return Results.ok();
|
||||
} catch (InterruptedException e) {
|
||||
return Results.status(
|
||||
SERVICE_UNAVAILABLE);
|
||||
}
|
||||
}), 1L, TimeUnit.SECONDS
|
||||
);
|
||||
CompletionStage<Object> res = f.handleAsync((result, e) -> {
|
||||
if (e != null) {
|
||||
log.error("Exception thrown", e);
|
||||
latch.countDown();
|
||||
return e.getCause();
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
});
|
||||
res.thenAccept(result -> assertEquals(TimeoutException.class, result));
|
||||
|
||||
log.debug(
|
||||
"Waiting for requests to be completed. Current Time: " + System.currentTimeMillis());
|
||||
latch.await(5, TimeUnit.SECONDS );
|
||||
assertEquals(0, latch.getCount());
|
||||
log.debug("All requests have been completed. Exiting test.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenMultigigabyteResponseConsumeWithStreams() throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final ActorMaterializer materializer = ActorMaterializer.create(system);
|
||||
final Path path = Files.createTempFile("tmp_", ".out");
|
||||
|
||||
WSClient ws = play.test.WSTestClient.newClient(port);
|
||||
log.info("Starting test server on url: " + url);
|
||||
ws.url(url)
|
||||
.stream()
|
||||
.thenAccept(
|
||||
response -> {
|
||||
try {
|
||||
OutputStream outputStream = java.nio.file.Files.newOutputStream(path);
|
||||
Sink<ByteString, CompletionStage<Done>> outputWriter =
|
||||
Sink.foreach(bytes -> {
|
||||
log.info("Reponse: " + bytes.utf8String());
|
||||
outputStream.write(bytes.toArray());
|
||||
});
|
||||
|
||||
response.getBodyAsSource()
|
||||
.runWith(outputWriter, materializer);
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("An error happened while opening the output stream", e);
|
||||
}
|
||||
})
|
||||
.whenComplete((value, error) -> latch.countDown());
|
||||
|
||||
log.debug(
|
||||
"Waiting for requests to be completed. Current Time: " + System.currentTimeMillis());
|
||||
latch.await(5, TimeUnit.SECONDS );
|
||||
assertEquals(0, latch.getCount());
|
||||
log.debug("All requests have been completed. Exiting test.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
logs
|
||||
target
|
||||
/.idea
|
||||
/.g8
|
||||
/.idea_modules
|
||||
/.classpath
|
||||
/.project
|
||||
/.settings
|
||||
/RUNNING_PID
|
||||
@@ -0,0 +1,68 @@
|
||||
package controllers;
|
||||
|
||||
import play.libs.concurrent.HttpExecutionContext;
|
||||
import play.mvc.*;
|
||||
import play.twirl.api.Html;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
import static java.util.concurrent.CompletableFuture.supplyAsync;
|
||||
|
||||
/**
|
||||
* This controller contains an action to handle HTTP requests
|
||||
* to the application's home page.
|
||||
*/
|
||||
public class HomeController extends Controller {
|
||||
private HttpExecutionContext ec;
|
||||
|
||||
@Inject
|
||||
public HomeController(HttpExecutionContext ec) {
|
||||
this.ec = ec;
|
||||
}
|
||||
|
||||
/**
|
||||
* An action that renders an HTML page with a welcome message.
|
||||
* The configuration in the <code>routes</code> file means that
|
||||
* this method will be called when the application receives a
|
||||
* <code>GET</code> request with a path of <code>/</code>.
|
||||
*/
|
||||
public Result index() {
|
||||
return ok(views.html.index.render());
|
||||
}
|
||||
|
||||
public Result applyHtml() {
|
||||
return ok(Html.apply("<h1>This text will appear as a heading 1</h1>"));
|
||||
}
|
||||
|
||||
public Result badRequestPage() {
|
||||
return badRequest("Your request data has issues.");
|
||||
}
|
||||
|
||||
public Result notFoundPage() {
|
||||
return notFound("Could not find the page you requested.");
|
||||
}
|
||||
|
||||
public Result customContentType() {
|
||||
return ok("This is some text content").as("text/html");
|
||||
}
|
||||
|
||||
public CompletionStage<Result> asyncOperation() {
|
||||
return supplyAsync(() -> {
|
||||
return longRunningTask();
|
||||
}, ec.current())
|
||||
.thenApplyAsync(s -> {
|
||||
return ok("Got result -> " + s);
|
||||
}, ec.current());
|
||||
}
|
||||
|
||||
private String longRunningTask() {
|
||||
return "Long running task has completed";
|
||||
}
|
||||
|
||||
public Result setHeaders() {
|
||||
return ok("This is some text content")
|
||||
.as("text/html")
|
||||
.withHeader("Header-Key", "Some value");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@()
|
||||
|
||||
@main("Welcome to Play") {
|
||||
<h1>Welcome to Play!</h1>
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
@*
|
||||
* This template is called from the `index` template. This template
|
||||
* handles the rendering of the page header and body tags. It takes
|
||||
* two arguments, a `String` for the title of the page and an `Html`
|
||||
* object to insert into the body of the page.
|
||||
*@
|
||||
@(title: String)(content: Html)
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@* Here's where we render the page title `String`. *@
|
||||
<title>@title</title>
|
||||
<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
|
||||
<link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
|
||||
</head>
|
||||
<body>
|
||||
@* And here's where we render the `Html` object containing
|
||||
* the page content. *@
|
||||
@content
|
||||
|
||||
<script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
name := """introduction"""
|
||||
organization := "com.baeldung"
|
||||
|
||||
version := "1.0-SNAPSHOT"
|
||||
|
||||
lazy val root = (project in file(".")).enablePlugins(PlayJava)
|
||||
|
||||
scalaVersion := "2.13.0"
|
||||
|
||||
libraryDependencies += guice
|
||||
@@ -0,0 +1,2 @@
|
||||
# This is the main configuration file for the application.
|
||||
# https://www.playframework.com/documentation/latest/ConfigFile
|
||||
@@ -0,0 +1,35 @@
|
||||
<!-- https://www.playframework.com/documentation/latest/SettingsLogger -->
|
||||
<configuration>
|
||||
|
||||
<conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel" />
|
||||
|
||||
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||
<file>${application.home:-.}/logs/application.log</file>
|
||||
<encoder>
|
||||
<pattern>%date [%level] from %logger in %thread - %message%n%xException</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%coloredLevel %logger{15} - %message%n%xException{10}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<appender-ref ref="FILE" />
|
||||
</appender>
|
||||
|
||||
<appender name="ASYNCSTDOUT" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</appender>
|
||||
|
||||
<logger name="play" level="INFO" />
|
||||
<logger name="application" level="DEBUG" />
|
||||
|
||||
<root level="WARN">
|
||||
<appender-ref ref="ASYNCFILE" />
|
||||
<appender-ref ref="ASYNCSTDOUT" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
@@ -0,0 +1,15 @@
|
||||
# Routes
|
||||
# This file defines all application routes (Higher priority routes first)
|
||||
# ~~~~
|
||||
|
||||
# An example controller showing a sample home page
|
||||
GET / controllers.HomeController.index
|
||||
GET /baeldung/html controllers.HomeController.applyHtml
|
||||
GET /baeldung/bad-req controllers.HomeController.badRequestPage
|
||||
GET /baeldung/not-found controllers.HomeController.notFoundPage
|
||||
GET /baeldung/custom-content-type controllers.HomeController.customContentType
|
||||
GET /baeldung/async controllers.HomeController.asyncOperation
|
||||
GET /baeldung/headers controllers.HomeController.setHeaders
|
||||
|
||||
# Map static resources from the /public folder to the /assets URL path
|
||||
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
|
||||
@@ -0,0 +1 @@
|
||||
sbt.version=1.2.8
|
||||
@@ -0,0 +1,7 @@
|
||||
// The Play plugin
|
||||
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.7.3")
|
||||
|
||||
// Defines scaffolding (found under .g8 folder)
|
||||
// http://www.foundweekends.org/giter8/scaffolding.html
|
||||
// sbt "g8Scaffold form"
|
||||
addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.11.0")
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 687 B |
@@ -0,0 +1,97 @@
|
||||
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 java.util.Optional;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static play.mvc.Http.Status.*;
|
||||
import static play.test.Helpers.GET;
|
||||
import static play.test.Helpers.route;
|
||||
|
||||
public class HomeControllerUnitTest extends WithApplication {
|
||||
|
||||
@Override
|
||||
protected Application provideApplication() {
|
||||
return new GuiceApplicationBuilder().build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenRequest_whenRootPath_ThenStatusOkay() {
|
||||
Http.RequestBuilder request = new Http.RequestBuilder()
|
||||
.method(GET)
|
||||
.uri("/");
|
||||
|
||||
Result result = route(app, request);
|
||||
assertEquals(OK, result.status());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenRequest_whenHtmlPath_ThenStatusOkay() {
|
||||
Http.RequestBuilder request = new Http.RequestBuilder()
|
||||
.method(GET)
|
||||
.uri("/baeldung/html");
|
||||
|
||||
Result result = route(app, request);
|
||||
assertEquals(OK, result.status());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenRequest_whenBadRequest_ThenStatusBadRequest() {
|
||||
Http.RequestBuilder request = new Http.RequestBuilder()
|
||||
.method(GET)
|
||||
.uri("/baeldung/bad-req");
|
||||
|
||||
Result result = route(app, request);
|
||||
assertEquals(BAD_REQUEST, result.status());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenRequest_whenNotFound_ThenStatusNotFound() {
|
||||
Http.RequestBuilder request = new Http.RequestBuilder()
|
||||
.method(GET)
|
||||
.uri("/baeldung/not-found");
|
||||
|
||||
Result result = route(app, request);
|
||||
assertEquals(NOT_FOUND, result.status());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenRequest_whenCustomContentTypePath_ThenContextTypeTextHtml() {
|
||||
Http.RequestBuilder request = new Http.RequestBuilder()
|
||||
.method(GET)
|
||||
.uri("/baeldung/custom-content-type");
|
||||
|
||||
Result result = route(app, request);
|
||||
assertTrue(result.contentType().isPresent());
|
||||
assertEquals("text/html", result.contentType().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenRequest_whenAsyncPath_ThenStatusOkay() {
|
||||
Http.RequestBuilder request = new Http.RequestBuilder()
|
||||
.method(GET)
|
||||
.uri("/baeldung/async");
|
||||
|
||||
Result result = route(app, request);
|
||||
assertEquals(OK, result.status());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenRequest_whenHeadersPath_ThenCustomHeaderFieldSet() {
|
||||
Http.RequestBuilder request = new Http.RequestBuilder()
|
||||
.method(GET)
|
||||
.uri("/baeldung/headers");
|
||||
|
||||
Result result = route(app, request);
|
||||
final Optional<String> headerOptional = result.header("Header-Key");
|
||||
assertTrue(headerOptional.isPresent());
|
||||
assertEquals("Some value", headerOptional.get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package controllers;
|
||||
|
||||
import play.libs.concurrent.HttpExecutionContext;
|
||||
import play.mvc.*;
|
||||
import play.twirl.api.Html;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
import static java.util.concurrent.CompletableFuture.supplyAsync;
|
||||
|
||||
/**
|
||||
* This controller contains an action to handle HTTP requests
|
||||
* to the application's home page.
|
||||
*/
|
||||
public class HomeController extends Controller {
|
||||
|
||||
/**
|
||||
* An action that renders an HTML page with a welcome message.
|
||||
* The configuration in the <code>routes</code> file means that
|
||||
* this method will be called when the application receives a
|
||||
* <code>GET</code> request with a path of <code>/</code>.
|
||||
*/
|
||||
public Result index() {
|
||||
return ok(views.html.index.render());
|
||||
}
|
||||
|
||||
public Result writer(String author) {
|
||||
return ok("Routing in Play by " + author);
|
||||
}
|
||||
|
||||
public Result viewUser(String userId) {
|
||||
final String response = String.format("Got user id {} in request.", userId);
|
||||
return ok(response);
|
||||
}
|
||||
|
||||
public Result greet(String name, int age) {
|
||||
return ok("Hello " + name + ", you are " + age + " years old");
|
||||
}
|
||||
|
||||
public Result squareMe(Long num) {
|
||||
return ok(num + " Squared is " + (num * num));
|
||||
}
|
||||
|
||||
public Result writer(String author, int id) {
|
||||
return ok("Routing in Play by: " + author + " ID: " + id);
|
||||
}
|
||||
|
||||
public Result introduceMe(String data) {
|
||||
String[] clientData = data.split(",");
|
||||
return ok("Your name is " + clientData[0] + ", you are " + clientData[1] + " years old");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@()
|
||||
|
||||
@main("Welcome to Play") {
|
||||
<h1>Welcome to Play!</h1>
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
@*
|
||||
* This template is called from the `index` template. This template
|
||||
* handles the rendering of the page header and body tags. It takes
|
||||
* two arguments, a `String` for the title of the page and an `Html`
|
||||
* object to insert into the body of the page.
|
||||
*@
|
||||
@(title: String)(content: Html)
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@* Here's where we render the page title `String`. *@
|
||||
<title>@title</title>
|
||||
<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
|
||||
<link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
|
||||
</head>
|
||||
<body>
|
||||
@* And here's where we render the `Html` object containing
|
||||
* the page content. *@
|
||||
@content
|
||||
|
||||
<script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
name := """play-routing"""
|
||||
organization := "com.baeldung"
|
||||
|
||||
version := "1.0-SNAPSHOT"
|
||||
|
||||
lazy val root = (project in file(".")).enablePlugins(PlayJava)
|
||||
|
||||
scalaVersion := "2.13.0"
|
||||
|
||||
libraryDependencies += guice
|
||||
@@ -0,0 +1,2 @@
|
||||
# This is the main configuration file for the application.
|
||||
# https://www.playframework.com/documentation/latest/ConfigFile
|
||||
@@ -0,0 +1,35 @@
|
||||
<!-- https://www.playframework.com/documentation/latest/SettingsLogger -->
|
||||
<configuration>
|
||||
|
||||
<conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel" />
|
||||
|
||||
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||
<file>${application.home:-.}/logs/application.log</file>
|
||||
<encoder>
|
||||
<pattern>%date [%level] from %logger in %thread - %message%n%xException</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%coloredLevel %logger{15} - %message%n%xException{10}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<appender-ref ref="FILE" />
|
||||
</appender>
|
||||
|
||||
<appender name="ASYNCSTDOUT" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</appender>
|
||||
|
||||
<logger name="play" level="INFO" />
|
||||
<logger name="application" level="DEBUG" />
|
||||
|
||||
<root level="WARN">
|
||||
<appender-ref ref="ASYNCFILE" />
|
||||
<appender-ref ref="ASYNCSTDOUT" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
@@ -0,0 +1,15 @@
|
||||
# Routes
|
||||
# This file defines all application routes (Higher priority routes first)
|
||||
# ~~~~
|
||||
|
||||
# An example controller showing a sample home page
|
||||
GET / controllers.HomeController.index
|
||||
GET /writer controllers.HomeController.writer(author = "Baeldung", id: Int ?= 1)
|
||||
GET /writer/:author controllers.HomeController.writer(author: String, id: Int ?= 1)
|
||||
GET /baeldung/:id controllers.HomeController.viewUser(id: String)
|
||||
GET /greet/:name/:age controllers.HomeController.greet(name: String, age: Integer)
|
||||
GET /square/$num<[0-9]+> controllers.HomeController.squareMe(num: Long)
|
||||
GET /*data controllers.HomeController.introduceMe(data)
|
||||
|
||||
# Map static resources from the /public folder to the /assets URL path
|
||||
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
|
||||
@@ -0,0 +1 @@
|
||||
sbt.version=1.2.8
|
||||
@@ -0,0 +1,7 @@
|
||||
// The Play plugin
|
||||
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.7.3")
|
||||
|
||||
// Defines scaffolding (found under .g8 folder)
|
||||
// http://www.foundweekends.org/giter8/scaffolding.html
|
||||
// sbt "g8Scaffold form"
|
||||
addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.11.0")
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 687 B |
+36
@@ -0,0 +1,36 @@
|
||||
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 play.twirl.api.Html;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static play.mvc.Http.Status.*;
|
||||
import static play.test.Helpers.GET;
|
||||
import static play.test.Helpers.route;
|
||||
|
||||
public class HomeControllerUnitTest extends WithApplication {
|
||||
|
||||
@Override
|
||||
protected Application provideApplication() {
|
||||
return new GuiceApplicationBuilder().build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenRequest_whenRootPath_ThenStatusOkay() {
|
||||
Http.RequestBuilder request = new Http.RequestBuilder()
|
||||
.method(GET)
|
||||
.uri("/");
|
||||
|
||||
Result result = route(app, request);
|
||||
assertEquals(OK, result.status());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
logs
|
||||
target
|
||||
/.idea
|
||||
/.g8
|
||||
/.idea_modules
|
||||
/.classpath
|
||||
/.project
|
||||
/.settings
|
||||
/RUNNING_PID
|
||||
@@ -0,0 +1,91 @@
|
||||
package controllers;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import model.Student;
|
||||
import play.libs.Json;
|
||||
import play.libs.concurrent.HttpExecutionContext;
|
||||
import play.mvc.Controller;
|
||||
import play.mvc.Http;
|
||||
import play.mvc.Result;
|
||||
import store.StudentStore;
|
||||
import utils.Util;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
import static java.util.concurrent.CompletableFuture.supplyAsync;
|
||||
|
||||
public class StudentController extends Controller {
|
||||
private HttpExecutionContext ec;
|
||||
private StudentStore studentStore;
|
||||
|
||||
@Inject
|
||||
public StudentController(HttpExecutionContext ec, StudentStore studentStore) {
|
||||
this.studentStore = studentStore;
|
||||
this.ec = ec;
|
||||
}
|
||||
|
||||
public CompletionStage<Result> create(Http.Request request) {
|
||||
JsonNode json = request.body().asJson();
|
||||
return supplyAsync(() -> {
|
||||
if (json == null) {
|
||||
return badRequest(Util.createResponse("Expecting Json data", false));
|
||||
}
|
||||
|
||||
Optional<Student> studentOptional = studentStore.addStudent(Json.fromJson(json, Student.class));
|
||||
return studentOptional.map(student -> {
|
||||
JsonNode jsonObject = Json.toJson(student);
|
||||
return created(Util.createResponse(jsonObject, true));
|
||||
}).orElse(internalServerError(Util.createResponse("Could not create data.", false)));
|
||||
}, ec.current());
|
||||
}
|
||||
|
||||
public CompletionStage<Result> listStudents() {
|
||||
return supplyAsync(() -> {
|
||||
Set<Student> result = studentStore.getAllStudents();
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonNode jsonData = mapper.convertValue(result, JsonNode.class);
|
||||
return ok(Util.createResponse(jsonData, true));
|
||||
}, ec.current());
|
||||
}
|
||||
|
||||
public CompletionStage<Result> retrieve(int id) {
|
||||
return supplyAsync(() -> {
|
||||
final Optional<Student> studentOptional = studentStore.getStudent(id);
|
||||
return studentOptional.map(student -> {
|
||||
JsonNode jsonObjects = Json.toJson(student);
|
||||
return ok(Util.createResponse(jsonObjects, true));
|
||||
}).orElse(notFound(Util.createResponse("Student with id:" + id + " not found", false)));
|
||||
}, ec.current());
|
||||
}
|
||||
|
||||
public CompletionStage<Result> update(Http.Request request) {
|
||||
JsonNode json = request.body().asJson();
|
||||
return supplyAsync(() -> {
|
||||
if (json == null) {
|
||||
return badRequest(Util.createResponse("Expecting Json data", false));
|
||||
}
|
||||
Optional<Student> studentOptional = studentStore.updateStudent(Json.fromJson(json, Student.class));
|
||||
return studentOptional.map(student -> {
|
||||
if (student == null) {
|
||||
return notFound(Util.createResponse("Student not found", false));
|
||||
}
|
||||
JsonNode jsonObject = Json.toJson(student);
|
||||
return ok(Util.createResponse(jsonObject, true));
|
||||
}).orElse(internalServerError(Util.createResponse("Could not create data.", false)));
|
||||
}, ec.current());
|
||||
}
|
||||
|
||||
public CompletionStage<Result> delete(int id) {
|
||||
return supplyAsync(() -> {
|
||||
boolean status = studentStore.deleteStudent(id);
|
||||
if (!status) {
|
||||
return notFound(Util.createResponse("Student with id:" + id + " not found", false));
|
||||
}
|
||||
return ok(Util.createResponse("Student with id:" + id + " deleted", true));
|
||||
}, ec.current());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package model;
|
||||
|
||||
public class Student {
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private int age;
|
||||
private int id;
|
||||
|
||||
public Student() {
|
||||
}
|
||||
|
||||
public Student(String firstName, String lastName, int age, int id) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.age = age;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package store;
|
||||
|
||||
import model.Student;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class StudentStore {
|
||||
private Map<Integer, Student> students = new HashMap<>();
|
||||
|
||||
public Optional<Student> addStudent(Student student) {
|
||||
int id = students.size();
|
||||
student.setId(id);
|
||||
students.put(id, student);
|
||||
return Optional.ofNullable(student);
|
||||
}
|
||||
|
||||
public Optional<Student> getStudent(int id) {
|
||||
return Optional.ofNullable(students.get(id));
|
||||
}
|
||||
|
||||
public Set<Student> getAllStudents() {
|
||||
return new HashSet<>(students.values());
|
||||
}
|
||||
|
||||
public Optional<Student> updateStudent(Student student) {
|
||||
int id = student.getId();
|
||||
if (students.containsKey(id)) {
|
||||
students.put(id, student);
|
||||
return Optional.ofNullable(student);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public boolean deleteStudent(int id) {
|
||||
return students.remove(id) != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package utils;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import play.libs.Json;
|
||||
|
||||
public class Util {
|
||||
public static ObjectNode createResponse(Object response, boolean ok) {
|
||||
ObjectNode result = Json.newObject();
|
||||
result.put("isSuccessful", ok);
|
||||
if (response instanceof String) {
|
||||
result.put("body", (String) response);
|
||||
} else {
|
||||
result.putPOJO("body", response);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
name := """student-api"""
|
||||
organization := "com.baeldung"
|
||||
|
||||
version := "1.0-SNAPSHOT"
|
||||
|
||||
lazy val root = (project in file(".")).enablePlugins(PlayJava)
|
||||
|
||||
scalaVersion := "2.13.0"
|
||||
|
||||
libraryDependencies += guice
|
||||
@@ -0,0 +1,2 @@
|
||||
# This is the main configuration file for the application.
|
||||
# https://www.playframework.com/documentation/latest/ConfigFile
|
||||
@@ -0,0 +1,35 @@
|
||||
<!-- https://www.playframework.com/documentation/latest/SettingsLogger -->
|
||||
<configuration>
|
||||
|
||||
<conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel" />
|
||||
|
||||
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||
<file>${application.home:-.}/logs/application.log</file>
|
||||
<encoder>
|
||||
<pattern>%date [%level] from %logger in %thread - %message%n%xException</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%coloredLevel %logger{15} - %message%n%xException{10}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<appender-ref ref="FILE" />
|
||||
</appender>
|
||||
|
||||
<appender name="ASYNCSTDOUT" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</appender>
|
||||
|
||||
<logger name="play" level="INFO" />
|
||||
<logger name="application" level="DEBUG" />
|
||||
|
||||
<root level="WARN">
|
||||
<appender-ref ref="ASYNCFILE" />
|
||||
<appender-ref ref="ASYNCSTDOUT" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
@@ -0,0 +1,12 @@
|
||||
# Routes
|
||||
# This file defines all application routes (Higher priority routes first)
|
||||
# ~~~~
|
||||
|
||||
GET / controllers.StudentController.listStudents()
|
||||
GET /:id controllers.StudentController.retrieve(id:Int)
|
||||
POST / controllers.StudentController.create(request: Request)
|
||||
PUT / controllers.StudentController.update(request: Request)
|
||||
DELETE /:id controllers.StudentController.delete(id:Int)
|
||||
|
||||
# Map static resources from the /public folder to the /assets URL path
|
||||
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
|
||||
@@ -0,0 +1 @@
|
||||
sbt.version=1.2.8
|
||||
@@ -0,0 +1,7 @@
|
||||
// The Play plugin
|
||||
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.7.3")
|
||||
|
||||
// Defines scaffolding (found under .g8 folder)
|
||||
// http://www.foundweekends.org/giter8/scaffolding.html
|
||||
// sbt "g8Scaffold form"
|
||||
addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.11.0")
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package controllers;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
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 static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static play.mvc.Http.Status.OK;
|
||||
import static play.test.Helpers.*;
|
||||
|
||||
public class StudentControllerUnitTest extends WithApplication {
|
||||
|
||||
@Override
|
||||
protected Application provideApplication() {
|
||||
return new GuiceApplicationBuilder().build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenStudentPostData_whenCreatingStudent_ThenShouldReturnCreatedStudent() {
|
||||
final ObjectNode jsonNode = Json.newObject();
|
||||
jsonNode.put("firstName", "John");
|
||||
jsonNode.put("lastName", "Baeldung");
|
||||
jsonNode.put("age", 25);
|
||||
|
||||
Http.RequestBuilder request = new Http.RequestBuilder()
|
||||
.method(POST)
|
||||
.bodyJson(jsonNode)
|
||||
.uri("/");
|
||||
|
||||
Result result = route(app, request);
|
||||
assertEquals(CREATED, result.status());
|
||||
assertTrue(result.contentType().isPresent());
|
||||
assertEquals("application/json", result.contentType().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenUrlToListStudents_whenListingStudents_ThenShouldReturnStudentList() {
|
||||
Http.RequestBuilder request = new Http.RequestBuilder()
|
||||
.method(GET)
|
||||
.uri("/");
|
||||
|
||||
Result result = route(app, request);
|
||||
assertEquals(OK, result.status());
|
||||
assertTrue(result.contentType().isPresent());
|
||||
assertEquals("application/json", result.contentType().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenUrlToRetrieveSingleStudent_whenRetrievingStudent_ThenShouldReturn404NotFound() {
|
||||
Http.RequestBuilder request = new Http.RequestBuilder()
|
||||
.method(GET)
|
||||
.uri("/1");
|
||||
|
||||
Result result = route(app, request);
|
||||
assertEquals(NOT_FOUND, result.status());
|
||||
assertTrue(result.contentType().isPresent());
|
||||
assertEquals("application/json", result.contentType().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenUrlToUpdateStudent_whenaUpdatingStudent_ThenShouldFail() {
|
||||
final ObjectNode jsonNode = Json.newObject();
|
||||
jsonNode.put("firstName", "John");
|
||||
jsonNode.put("lastName", "Baeldung");
|
||||
jsonNode.put("age", 25);
|
||||
|
||||
Http.RequestBuilder request = new Http.RequestBuilder()
|
||||
.method(PUT)
|
||||
.bodyJson(jsonNode)
|
||||
.uri("/");
|
||||
|
||||
Result result = route(app, request);
|
||||
assertEquals(INTERNAL_SERVER_ERROR, result.status());
|
||||
assertTrue(result.contentType().isPresent());
|
||||
assertEquals("application/json", result.contentType().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenIdToDeleteStudent_whenDeletingStudent_ThenShouldFail() {
|
||||
Http.RequestBuilder request = new Http.RequestBuilder()
|
||||
.method(DELETE)
|
||||
.uri("/1");
|
||||
|
||||
Result result = route(app, request);
|
||||
assertEquals(NOT_FOUND, result.status());
|
||||
assertTrue(result.contentType().isPresent());
|
||||
assertEquals("application/json", result.contentType().get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
### Relevant Articles:
|
||||
|
||||
- [WebSockets with the Play Framework and Akka](https://www.baeldung.com/akka-play-websockets)
|
||||
@@ -0,0 +1,111 @@
|
||||
package actors;
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.PoisonPill;
|
||||
import akka.actor.Props;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
import akka.http.javadsl.Http;
|
||||
import akka.http.javadsl.marshallers.jackson.Jackson;
|
||||
import akka.http.javadsl.model.HttpMessage;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.HttpResponse;
|
||||
import akka.stream.Materializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import dto.MessageDTO;
|
||||
import dto.RequestDTO;
|
||||
import utils.MessageConverter;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class Messenger extends AbstractActor {
|
||||
private LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
|
||||
|
||||
private ActorRef out;
|
||||
|
||||
public Messenger(ActorRef out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
public static Props props(ActorRef out) {
|
||||
return Props.create(Messenger.class, () -> new Messenger(out));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStart() throws Exception {
|
||||
log.info("Messenger actor started at {}",
|
||||
OffsetDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postStop() throws Exception {
|
||||
log.info("Messenger actor stopped at {}",
|
||||
OffsetDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
|
||||
}
|
||||
|
||||
private void onSendMessage(JsonNode jsonNode) {
|
||||
RequestDTO requestDTO = MessageConverter.jsonNodeToRequest(jsonNode);
|
||||
String message = requestDTO.getMessage().toLowerCase();
|
||||
if("stop".equals(message)) {
|
||||
MessageDTO messageDTO = createMessageDTO("1", "1", "Stop", "Stopping actor");
|
||||
out.tell(MessageConverter.messageToJsonNode(messageDTO), getSelf());
|
||||
self().tell(PoisonPill.getInstance(), getSelf());
|
||||
} else {
|
||||
log.info("Actor received. {}", requestDTO);
|
||||
processMessage(requestDTO);
|
||||
}
|
||||
}
|
||||
|
||||
private MessageDTO createMessageDTO(String userId, String id, String title, String message) {
|
||||
MessageDTO messageDTO = new MessageDTO();
|
||||
messageDTO.setUserId(UUID.randomUUID().toString());
|
||||
messageDTO.setId(UUID.randomUUID().toString());
|
||||
messageDTO.setTitle("Self Kill");
|
||||
messageDTO.setBody("Stopping actor");
|
||||
return messageDTO;
|
||||
}
|
||||
|
||||
private void processMessage(RequestDTO requestDTO) {
|
||||
CompletionStage<HttpResponse> responseFuture = getRandomMessage();
|
||||
responseFuture.thenCompose(this::consumeHttpResponse)
|
||||
.thenAccept(messageDTO ->
|
||||
out.tell(MessageConverter.messageToJsonNode(messageDTO), getSelf()));
|
||||
}
|
||||
|
||||
private CompletionStage<HttpResponse> getRandomMessage() {
|
||||
int postId = ThreadLocalRandom.current().nextInt(0, 100);
|
||||
return Http.get(getContext().getSystem()).singleRequest(
|
||||
HttpRequest.create("https://jsonplaceholder.typicode.com/posts/" + postId)
|
||||
);
|
||||
}
|
||||
|
||||
private void discardEntity(HttpResponse httpResponse, Materializer materializer) {
|
||||
httpResponse.discardEntityBytes(materializer)
|
||||
.completionStage()
|
||||
.whenComplete((done, ex) -> log.info("Entity discarded completely!"));
|
||||
}
|
||||
|
||||
private CompletionStage<MessageDTO> consumeHttpResponse(HttpResponse httpResponse) {
|
||||
Materializer materializer = Materializer.matFromSystem(getContext().getSystem());
|
||||
return Jackson.unmarshaller(MessageDTO.class)
|
||||
.unmarshal(httpResponse.entity(), materializer)
|
||||
.thenApply(messageDTO -> {
|
||||
log.info("Received message: {}", messageDTO);
|
||||
discardEntity(httpResponse, materializer);
|
||||
return messageDTO;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(JsonNode.class, this::onSendMessage)
|
||||
.matchAny(o -> log.error("Received unknown message: {}", o.getClass()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package controllers;
|
||||
|
||||
import actors.Messenger;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import dto.MessageDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import play.libs.F;
|
||||
import play.libs.streams.ActorFlow;
|
||||
import play.mvc.*;
|
||||
import utils.MessageConverter;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
@Slf4j
|
||||
public class HomeController extends Controller {
|
||||
private ActorSystem actorSystem;
|
||||
private Materializer materializer;
|
||||
|
||||
@Inject
|
||||
public HomeController(ActorSystem actorSystem, Materializer materializer) {
|
||||
this.actorSystem = actorSystem;
|
||||
this.materializer = materializer;
|
||||
}
|
||||
|
||||
public Result index(Http.Request request) {
|
||||
String url = routes.HomeController.socket().webSocketURL(request);
|
||||
//To test WebSockets with akka streams, uncomment the next line and comment out the previous
|
||||
//String url = routes.HomeController.akkaStreamsSocket().webSocketURL(request);
|
||||
return ok(views.html.index.render(url));
|
||||
}
|
||||
|
||||
|
||||
public WebSocket socket() {
|
||||
return WebSocket.Json.acceptOrResult(this::createActorFlow);
|
||||
}
|
||||
|
||||
private CompletionStage<F.Either<Result, Flow<JsonNode, JsonNode, ?>>> createActorFlow(
|
||||
Http.RequestHeader request) {
|
||||
return CompletableFuture.completedFuture(F.Either.Right(createFlowForActor()));
|
||||
}
|
||||
|
||||
private CompletionStage<F.Either<Result, Flow<JsonNode, JsonNode, ?>>>
|
||||
createActorFlow2(Http.RequestHeader request) {
|
||||
return CompletableFuture.completedFuture(
|
||||
request.session()
|
||||
.getOptional("username")
|
||||
.map(username ->
|
||||
F.Either.<Result, Flow<JsonNode, JsonNode, ?>>Right(
|
||||
createFlowForActor()))
|
||||
.orElseGet(() -> F.Either.Left(forbidden())));
|
||||
}
|
||||
|
||||
private Flow<JsonNode, JsonNode, ?> createFlowForActor() {
|
||||
return ActorFlow.actorRef(out -> Messenger.props(out), actorSystem, materializer);
|
||||
}
|
||||
|
||||
public WebSocket akkaStreamsSocket() {
|
||||
return WebSocket.Json.accept(
|
||||
request -> {
|
||||
Sink<JsonNode, ?> in = Sink.foreach(System.out::println);
|
||||
MessageDTO messageDTO = new MessageDTO("1", "1", "Title", "Test Body");
|
||||
Source<JsonNode, ?> out = Source.tick(
|
||||
Duration.ofSeconds(2),
|
||||
Duration.ofSeconds(2),
|
||||
MessageConverter.messageToJsonNode(messageDTO)
|
||||
);
|
||||
return Flow.fromSinkAndSource(in, out);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package dto;
|
||||
|
||||
public class MessageDTO {
|
||||
private String userId;
|
||||
private String id;
|
||||
private String title;
|
||||
private String body;
|
||||
|
||||
public MessageDTO() {
|
||||
}
|
||||
|
||||
public MessageDTO(String userId, String id, String title, String body) {
|
||||
this.userId = userId;
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public void setBody(String body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MessageDTO{" +
|
||||
"userId='" + userId + '\'' +
|
||||
", id='" + id + '\'' +
|
||||
", title='" + title + '\'' +
|
||||
", body='" + body + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package dto;
|
||||
|
||||
public class RequestDTO {
|
||||
private String message;
|
||||
|
||||
public RequestDTO() {
|
||||
}
|
||||
|
||||
public RequestDTO(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RequestDTO{" +
|
||||
"message='" + message + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package utils;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import dto.MessageDTO;
|
||||
import dto.RequestDTO;
|
||||
|
||||
public class MessageConverter {
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
public static MessageDTO jsonNodeToMessage(JsonNode jsonNode) {
|
||||
return OBJECT_MAPPER.convertValue(jsonNode, MessageDTO.class);
|
||||
}
|
||||
|
||||
public static JsonNode messageToJsonNode(MessageDTO messageDTO) {
|
||||
return OBJECT_MAPPER.convertValue(messageDTO, JsonNode.class);
|
||||
}
|
||||
public static RequestDTO jsonNodeToRequest(JsonNode jsonNode) {
|
||||
return OBJECT_MAPPER.convertValue(jsonNode, RequestDTO.class);
|
||||
}
|
||||
|
||||
public static JsonNode requestToJsonNode(RequestDTO requestDTO) {
|
||||
return OBJECT_MAPPER.convertValue(requestDTO, JsonNode.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
@(url: String)
|
||||
@main("Welcome to Play") {
|
||||
<h1>Welcome to Play WebSockets!</h1>
|
||||
<div id="messageContent"></div>
|
||||
<form>
|
||||
<textarea id="messageInput"></textarea>
|
||||
<button id="sendButton">Send</button>
|
||||
</form>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
|
||||
|
||||
<script>
|
||||
var webSocket;
|
||||
var messageInput;
|
||||
|
||||
function init() {
|
||||
initWebSocket();
|
||||
}
|
||||
|
||||
function initWebSocket() {
|
||||
webSocket = new WebSocket("@url");
|
||||
webSocket.onopen = onOpen;
|
||||
webSocket.onclose = onClose;
|
||||
webSocket.onmessage = onMessage;
|
||||
webSocket.onerror = onError;
|
||||
}
|
||||
|
||||
function onOpen(evt) {
|
||||
writeToScreen("CONNECTED");
|
||||
}
|
||||
|
||||
function onClose(evt) {
|
||||
writeToScreen("DISCONNECTED");
|
||||
appendMessageToView(":", "DISCONNECTED");
|
||||
}
|
||||
|
||||
function onError(evt) {
|
||||
writeToScreen("ERROR: " + evt.data);
|
||||
writeToScreen("ERROR: " + JSON.stringify(evt));
|
||||
}
|
||||
|
||||
function onMessage(evt) {
|
||||
var receivedData = JSON.parse(evt.data);
|
||||
console.log("New Data: ", receivedData);
|
||||
appendMessageToView("Server", receivedData.body);
|
||||
}
|
||||
|
||||
function appendMessageToView(title, message) {
|
||||
$("#messageContent").append("<p>" + title + ": " + message + "</p>");
|
||||
}
|
||||
|
||||
function writeToScreen(message) {
|
||||
console.log("New message: ", message);
|
||||
}
|
||||
|
||||
function doSend(protocolMessage) {
|
||||
if(webSocket.readyState == WebSocket.OPEN) {
|
||||
writeToScreen("SENT: " + protocolMessage.message);
|
||||
webSocket.send(JSON.stringify(protocolMessage));
|
||||
} else {
|
||||
writeToScreen("Could not send data. Websocket is not open.");
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("load", init, false);
|
||||
|
||||
|
||||
$(".sendButton").click(function () {
|
||||
console.log("Submitting.");
|
||||
newMessage();
|
||||
});
|
||||
|
||||
$(window).on("keydown", function (e) {
|
||||
if (e.which == 13) {
|
||||
console.log("Enter pressed.");
|
||||
newMessage();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
function newMessage() {
|
||||
messageInput = $("#messageInput").val();
|
||||
$("#messageInput").val("");
|
||||
if ($.trim(messageInput) == "") {
|
||||
return false;
|
||||
}
|
||||
|
||||
appendMessageToView("Me", messageInput);
|
||||
|
||||
var message = {
|
||||
message: messageInput
|
||||
};
|
||||
|
||||
doSend(message);
|
||||
}
|
||||
</script>
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
@(title: String)(content: Html)
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>@title</title>
|
||||
<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
|
||||
<link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
|
||||
</head>
|
||||
<body>
|
||||
@content
|
||||
<script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
name := """websockets"""
|
||||
organization := "com.baeldung"
|
||||
|
||||
version := "1.0-SNAPSHOT"
|
||||
|
||||
lazy val root = (project in file(".")).enablePlugins(PlayJava)
|
||||
|
||||
scalaVersion := "2.13.0"
|
||||
|
||||
lazy val akkaVersion = "2.6.0-M8"
|
||||
lazy val akkaHttpVersion = "10.1.10"
|
||||
|
||||
libraryDependencies += guice
|
||||
libraryDependencies += "com.typesafe.akka" %% "akka-actor" % akkaVersion
|
||||
libraryDependencies += "com.typesafe.akka" %% "akka-testkit" % akkaVersion
|
||||
libraryDependencies += "com.typesafe.akka" %% "akka-stream" % akkaVersion
|
||||
libraryDependencies += "com.typesafe.akka" %% "akka-http-jackson" % akkaHttpVersion
|
||||
libraryDependencies += "com.typesafe.akka" %% "akka-http" % akkaHttpVersion
|
||||
libraryDependencies += "org.projectlombok" % "lombok" % "1.18.8" % "provided"
|
||||
libraryDependencies += "junit" % "junit" % "4.12"
|
||||
|
||||
PlayKeys.devSettings += "play.server.http.idleTimeout" -> "infinite"
|
||||
@@ -0,0 +1,7 @@
|
||||
# This is the main configuration file for the application.
|
||||
# https://www.playframework.com/documentation/latest/ConfigFile
|
||||
########################################
|
||||
# akka-http-core Reference Config File #
|
||||
########################################
|
||||
|
||||
play.server.http.idleTimeout = "infinite"
|
||||
@@ -0,0 +1,37 @@
|
||||
<!-- https://www.playframework.com/documentation/latest/SettingsLogger -->
|
||||
<configuration>
|
||||
|
||||
<conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel" />
|
||||
|
||||
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||
<file>${application.home:-.}/logs/application.log</file>
|
||||
<encoder>
|
||||
<pattern>%date [%level] from %logger in %thread - %message%n%xException</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%coloredLevel %logger{15} - %message%n%xException{10}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<appender-ref ref="FILE" />
|
||||
</appender>
|
||||
|
||||
<appender name="ASYNCSTDOUT" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</appender>
|
||||
|
||||
<logger name="akka" level="INFO" />
|
||||
<logger name="actors" level="INFO"/>
|
||||
<logger name="play" level="INFO" />
|
||||
<logger name="application" level="DEBUG" />
|
||||
|
||||
<root level="WARN">
|
||||
<appender-ref ref="ASYNCFILE" />
|
||||
<appender-ref ref="ASYNCSTDOUT" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
@@ -0,0 +1,11 @@
|
||||
# Routes
|
||||
# This file defines all application routes (Higher priority routes first)
|
||||
# ~~~~
|
||||
|
||||
# An example controller showing a sample home page
|
||||
GET / controllers.HomeController.index(request: Request)
|
||||
GET /chat controllers.HomeController.socket
|
||||
GET /chat/with/streams controllers.HomeController.akkaStreamsSocket
|
||||
|
||||
# Map static resources from the /public folder to the /assets URL path
|
||||
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
|
||||
@@ -0,0 +1 @@
|
||||
sbt.version=1.2.8
|
||||
@@ -0,0 +1,7 @@
|
||||
// The Play plugin
|
||||
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.7.3")
|
||||
|
||||
// Defines scaffolding (found under .g8 folder)
|
||||
// http://www.foundweekends.org/giter8/scaffolding.html
|
||||
// sbt "g8Scaffold form"
|
||||
addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.11.0")
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 687 B |
@@ -0,0 +1,32 @@
|
||||
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 static org.junit.Assert.assertEquals;
|
||||
import static play.mvc.Http.Status.OK;
|
||||
import static play.test.Helpers.GET;
|
||||
import static play.test.Helpers.route;
|
||||
|
||||
public class HomeControllerTest extends WithApplication {
|
||||
|
||||
@Override
|
||||
protected Application provideApplication() {
|
||||
return new GuiceApplicationBuilder().build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void giveRequest_whenRootPath_ThenStatusOkay() {
|
||||
Http.RequestBuilder request = new Http.RequestBuilder()
|
||||
.method(GET)
|
||||
.uri("/");
|
||||
|
||||
Result result = route(app, request);
|
||||
assertEquals(OK, result.status());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user