Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add start of Java Web Build Examples page #3274

Merged
merged 15 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,7 @@ object example extends MillScalaModule {
object cross extends Cross[ExampleCrossModule](listIn(millSourcePath / "cross"))
object misc extends Cross[ExampleCrossModule](listIn(millSourcePath / "misc"))
object web extends Cross[ExampleCrossModule](listIn(millSourcePath / "web"))
object javaweb extends Cross[ExampleCrossModule](listIn(millSourcePath / "javaweb"))

trait ExampleCrossModuleJava extends ExampleCrossModule {

Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* xref:Java_Builtin_Commands.adoc[]
* xref:Java_Build_Examples.adoc[]
* xref:Java_Module_Config.adoc[]
* xref:Java_Web_Build_Examples.adoc[]

// This section is all about developing a deeper understanding of specific
// topics in Mill. This is the opposite of `Quick Start` above: while we touch
Expand Down
18 changes: 18 additions & 0 deletions docs/modules/ROOT/pages/Java_Web_Build_Examples.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
= Web Build Examples

This page contains examples of using Mill as a build tool for web-applications.
It covers setting up a basic backend server with a variety of server frameworks

== Jetty Hello World App

include::example/javaweb/1-hello-jetty.adoc[]


== Spring Boot Hello World App

include::example/javaweb/2-hello-spring-boot.adoc[]

== Spring Boot TodoMvc App

include::example/javaweb/3-todo-spring-boot.adoc[]

28 changes: 28 additions & 0 deletions example/javaweb/1-hello-jetty/build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import mill._, javalib._, publish._

object hello extends RootModule with JavaModule {
def ivyDeps = Agg(
ivy"org.eclipse.jetty:jetty-server:9.4.43.v20210629",
ivy"javax.servlet:javax.servlet-api:4.0.1"
)

object test extends JavaModuleTests with TestModule.Junit4
}

// This example demonstrates how to set up a simple Jetty webserver,
// able to handle a single HTTP request at `/` and reply with a single response.


/** Usage

> mill test
...HelloJettyTest.testHelloJetty finished...

> mill runBackground

> curl http://localhost:8085
...<h1>Hello, World!</h1>...

> mill clean runBackground

*/
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
server.port=8084
24 changes: 24 additions & 0 deletions example/javaweb/1-hello-jetty/src/HelloJetty.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class HelloJetty extends AbstractHandler {

public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html; charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
baseRequest.setHandled(true);
response.getWriter().println("<h1>Hello, World!</h1>");
}

public static void main(String[] args) throws Exception {
Server server = new Server(8085);
server.setHandler(new HelloJetty());
server.start();
server.join();
}
}
52 changes: 52 additions & 0 deletions example/javaweb/1-hello-jetty/test/src/HelloJettyTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

import static org.junit.Assert.assertEquals;

public class HelloJettyTest {
private Server server;
private int port;

@Before
public void setUp() throws Exception {
server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0); // dynamically assign port
server.addConnector(connector);

HandlerList handlers = new HandlerList();
handlers.addHandler(new HelloJetty());
server.setHandler(handlers);

server.start();
port = connector.getLocalPort();
}

@After
public void tearDown() throws Exception {
if (server != null) {
server.stop();
}
}

@Test
public void testHelloJetty() throws IOException {
URL url = new URL("http://localhost:" + port + "/");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

connection.setRequestMethod("GET");
connection.connect();

int responseCode = connection.getResponseCode();
assertEquals(HttpURLConnection.HTTP_OK, responseCode);
}
}
32 changes: 32 additions & 0 deletions example/javaweb/2-hello-spring-boot/build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import mill._, javalib._, publish._

object hello extends RootModule with JavaModule {
def ivyDeps = Agg(
ivy"org.springframework.boot:spring-boot-starter-web:2.5.6",
ivy"org.springframework.boot:spring-boot-starter-actuator:2.5.6"
)

object test extends JavaModuleTests with TestModule.Junit5 {
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"org.springframework.boot:spring-boot-starter-test:2.5.6"
)
}
}

// This example demonstrates how to set up a simple Spring Boot webserver,
// able to handle a single HTTP request at `/` and reply with a single response.


/** Usage

> mill test
...com.example.HelloSpringBootTest#shouldReturnDefaultMessage() finished...

> mill runBackground

> curl http://localhost:8086
...<h1>Hello, World!</h1>...

> mill clean runBackground

*/
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
server.port=8086
22 changes: 22 additions & 0 deletions example/javaweb/2-hello-spring-boot/src/HelloSpringBoot.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class HelloSpringBoot {

public static void main(String[] args) {
SpringApplication.run(HelloSpringBoot.class, args);
}

@RestController
class HelloController {
@GetMapping("/")
public String hello() {
return "<h1>Hello, World!</h1>";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.example;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloSpringBootTest {

@LocalServerPort
private int port;

@Autowired
private TestRestTemplate restTemplate;

@Test
public void shouldReturnDefaultMessage() {
String response = restTemplate.getForObject("http://localhost:" + port + "/", String.class);
assertEquals("<h1>Hello, World!</h1>", response);
}
}
48 changes: 48 additions & 0 deletions example/javaweb/3-todo-spring-boot/build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import mill._, javalib._, publish._

object hello extends RootModule with JavaModule {
def ivyDeps = Agg(
ivy"org.springframework.boot:spring-boot-starter-data-jpa:2.5.4",
ivy"org.springframework.boot:spring-boot-starter-thymeleaf:2.5.4",
ivy"org.springframework.boot:spring-boot-starter-validation:2.5.4",
ivy"org.springframework.boot:spring-boot-starter-web:2.5.4",

ivy"javax.xml.bind:jaxb-api:2.3.1",

ivy"org.webjars:webjars-locator:0.41",
ivy"org.webjars.npm:todomvc-common:1.0.5",
ivy"org.webjars.npm:todomvc-app-css:2.4.1",

ivy"com.h2database:h2:2.3.230"
)

object test extends JavaModuleTests with TestModule.Junit5 {
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"org.springframework.boot:spring-boot-starter-test:2.5.6"
)
}
}

// This is a larger example using Spring Boot, implementing the well known
// https://todomvc.com/[TodoMVC] example app. Apart from running a webserver,
// this example also demonstrates:
//
// * Serving HTML templates using Thymeleaf
// * Serving static Javascript and CSS using Webjars
// * Querying a SQL database using JPA and H2


/** Usage

> mill test
...com.example.TodomvcApplicationTests#homePageLoads() finished...
...com.example.TodomvcApplicationTests#addNewTodoItem() finished...

> mill runBackground

> curl http://localhost:8087
...<h1>todos</h1>...

> mill clean runBackground

*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
spring.mvc.hiddenmethod.filter.enabled=true
server.port=8087
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" lang="en">
<li th:fragment="todoItem(item)" th:classappend="${item.completed?'completed':''}">
<div class="view">
<form th:action="@{/{id}/toggle(id=${item.id})}" th:method="put">
<input class="toggle" type="checkbox"
onchange="this.form.submit()"
th:attrappend="checked=${item.completed?'true':null}">
<label th:text="${item.title}">Taste JavaScript</label>
</form>
<form th:action="@{/{id}(id=${item.id})}" th:method="delete">
<button class="destroy"></button>
</form>
</div>
<input class="edit" value="Create a TodoMVC template">
</li>
</html>
84 changes: 84 additions & 0 deletions example/javaweb/3-todo-spring-boot/resources/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Template • TodoMVC</title>
<link rel="stylesheet" th:href="@{/webjars/todomvc-common/base.css}">
<link rel="stylesheet" th:href="@{/webjars/todomvc-app-css/index.css}">
</head>
<body>
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<form th:action="@{/}" method="post" th:object="${item}">
<input class="new-todo" placeholder="What needs to be done?" autofocus
th:field="*{title}">
</form>
</header>
<!-- This section should be hidden by default and shown when there are todos -->
<section class="main" th:if="${totalItemCount > 0}">
<form th:action="@{/toggle-all}" th:method="put">
<input id="toggle-all" class="toggle-all" type="checkbox"
onclick="this.form.submit()">
<label for="toggle-all">Mark all as complete</label>
</form>
<ul class="todo-list" th:remove="all-but-first">
<li th:insert="fragments :: todoItem(${item})" th:each="item : ${todoItems}" th:remove="tag">
</li>
<!-- These are here just to show the structure of the list items -->
<!-- List items should get the class `editing` when editing and `completed` when marked as completed -->
<li class="completed">
<div class="view">
<input class="toggle" type="checkbox" checked>
<label>Taste JavaScript</label>
<button class="destroy"></button>
</div>
<input class="edit" value="Create a TodoMVC template">
</li>
<li>
<div class="view">
<input class="toggle" type="checkbox">
<label>Buy a unicorn</label>
<button class="destroy"></button>
</div>
<input class="edit" value="Rule the web">
</li>
</ul>
</section>
<!-- This footer should be hidden by default and shown when there are todos -->
<footer class="footer" th:if="${totalItemCount > 0}">
<th:block th:unless="${activeItemCount == 1}">
<span class="todo-count"><strong th:text="${activeItemCount}">0</strong> items left</span>
</th:block>
<th:block th:if="${activeItemCount == 1}">
<span class="todo-count"><strong>1</strong> item left</span>
</th:block>
<ul class="filters">
<li>
<a th:href="@{/}"
th:classappend="${todoFilter.name() == 'ALL'?'selected':''}">All</a>
</li>
<li>
<a th:href="@{/active}"
th:classappend="${todoFilter.name() == 'ACTIVE'?'selected':''}">Active</a>
</li>
<li>
<a th:href="@{/completed}"
th:classappend="${todoFilter.name() == 'COMPLETED'?'selected':''}">Completed</a>
</li>
</ul>
<form th:action="@{/completed}" th:method="delete"
th:if="${completedItemCount > 0}">
<button class="clear-completed">Clear completed</button>
</form>
</footer>
</section>
<footer class="info">
<p>Double-click to edit a todo</p>
</footer>
<script th:src="@{/webjars/todomvc-common/base.js}"></script>
</body>
</html>
11 changes: 11 additions & 0 deletions example/javaweb/3-todo-spring-boot/src/TodomvcApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TodomvcApplication {
public static void main(String[] args) {
SpringApplication.run(TodomvcApplication.class, args);
}
}
Loading