diff --git a/README.md b/README.md index be9790f..24cef09 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,35 @@ This project is designed to show step by step how to migrate from Java to Kotlin with Spring Boot. -## [Step 0]((https://github.com/sdeleuze/spring-kotlin-deepdive)): The original Java application +## [Step 1](https://github.com/sdeleuze/spring-kotlin-deepdive/tree/step1): Java to Kotlin -* Simple blog with JSON HTTP API -* Demo -* Present the Java application software design -* Reminders: - * No need for annotating constructor when single constructor for autowiring it (as of Spring 4.3), show 2 syntaxes - * `@RequestMapping` aliases: `@GetMapping`, `@PostMapping`, etc. -* Reload via CTRL + F9 in IDEA +Code: +* No more semicolon at end of lines +* Type suffixed with colon, as statically typed as Java, optional type inference +* Show how to configure return type inference hints +* Short syntax for declaring properties and initializing them from the primary constructor instead of super verbose mostly auto-generated POJO +* [Data classes](https://kotlinlang.org/docs/reference/data-classes.html) +* Syntax help using naturally immutable classes +* `:` instead of `extends` +* No need for `{ }` for empty classes / interfaces +* `public` by the default +* `fun` to declare functions +* Better lambdas: `{ }` last parameter notation, lambda without collect, `it` default parameter +* Constructor without `new` +* `main()` top level method +* `Utils` class -> [Kotlin extensions](https://kotlinlang.org/docs/reference/extensions.html) +* `.getBody()` -> `.body` +* Meaningful function names between backticks -**[Go to step 1: Java to Kotlin](https://github.com/sdeleuze/spring-kotlin-deepdive/tree/step1)** +Build: +* Dependencies: + * `kotlin-stdlib-jre8` + * `kotlin-reflect` + * `jackson-module-kotlin` +* Plugins: + * `kotlin` + * `kotlin-spring` + * `kotlin-noarg` +* Configure to build Java 8 bytecode + +**[Go to step 2: Spring Boot 2](https://github.com/sdeleuze/spring-kotlin-deepdive/tree/step2)** diff --git a/build.gradle b/build.gradle index 36fff45..4084986 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ buildscript { ext { + kotlinVersion = "1.1.51" springBootVersion = "1.5.7.RELEASE" } repositories { @@ -7,21 +8,39 @@ buildscript { } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}") + classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}") + classpath("org.jetbrains.kotlin:kotlin-noarg:${kotlinVersion}") } } -apply plugin: "java" +apply plugin: "kotlin" +apply plugin: "kotlin-spring" +apply plugin: "kotlin-noarg" apply plugin: "org.springframework.boot" group = "io.spring" version = "1.0.0-SNAPSHOT" -sourceCompatibility = 1.8 repositories { mavenCentral() } +noArg { + annotation("org.springframework.data.mongodb.core.mapping.Document") +} + +compileKotlin { + kotlinOptions.jvmTarget = "1.8" +} +compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" +} + dependencies { + compile("org.jetbrains.kotlin:kotlin-stdlib-jre8") + compile("org.jetbrains.kotlin:kotlin-reflect") + compile("com.fasterxml.jackson.module:jackson-module-kotlin") compile("org.springframework.boot:spring-boot-starter-actuator") compile("org.springframework.boot:spring-boot-starter-data-mongodb") compile("org.springframework.boot:spring-boot-starter-web") diff --git a/src/main/java/io/spring/deepdive/Application.java b/src/main/java/io/spring/deepdive/Application.java deleted file mode 100644 index ef9171d..0000000 --- a/src/main/java/io/spring/deepdive/Application.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.spring.deepdive; - -import com.samskivert.mustache.Mustache; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; - -@SpringBootApplication -public class Application { - - @Bean - public Mustache.Compiler mustacheCompiler(Mustache.TemplateLoader templateLoader) { - return Mustache.compiler().escapeHTML(false).withLoader(templateLoader); - } - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } -} diff --git a/src/main/java/io/spring/deepdive/DatabaseInitializer.java b/src/main/java/io/spring/deepdive/DatabaseInitializer.java deleted file mode 100644 index 2853dbd..0000000 --- a/src/main/java/io/spring/deepdive/DatabaseInitializer.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.spring.deepdive; - - -import java.time.LocalDateTime; -import java.util.Arrays; - -import io.spring.deepdive.model.Post; -import io.spring.deepdive.model.User; -import io.spring.deepdive.repository.PostRepository; -import io.spring.deepdive.repository.UserRepository; - -import org.springframework.boot.CommandLineRunner; -import org.springframework.stereotype.Component; - -@Component -public class DatabaseInitializer implements CommandLineRunner { - - private final UserRepository userRepository; - - private final PostRepository postRepository; - - public DatabaseInitializer(UserRepository userRepository, PostRepository postRepository) { - this.userRepository = userRepository; - this.postRepository = postRepository; - } - - @Override - public void run(String... strings) throws Exception { - - User brian = new User("bclozel", "Brian", "Clozel", "Spring Framework & Spring Boot @pivotal — @LaCordeeLyon coworker"); - User mark = new User("MkHeck","Mark", "Heckler", "Spring Developer Advocate @Pivotal. Computer scientist+MBA, inglés y español, @Java_Champions. Pragmatic optimist. #Spring #Reactive #Microservices #IoT #Cloud"); - User arjen = new User("poutsma", "Arjen", "Poutsma"); - User rossen = new User("rstoyanchev", "Rossen", "Stoyanchev", "Spring Framework committer @Pivotal"); - User sam = new User("sam_brannen", "Sam", "Brannen", "Core @SpringFramework & @JUnitTeam Committer. Enterprise @Java Consultant at @Swiftmind. #Spring Trainer. Spring User Group Lead at @JUGCH."); - User seb = new User("sdeleuze", "Sebastien", "Deleuze", "Spring Framework committer @Pivotal, @Kotlin addict, #WebAssembly believer, @mixitconf organizer, #techactivism"); - User simon = new User("simonbasle", "Simon", "Basle", "software development aficionado, Reactor Software Engineer @pivotal"); - User stephanem = new User("smaldini", "Stephane", "Maldini", "Project Reactor Lead @Pivotal -All things Reactive and Distributed - ex Londoner - opinions != Pivotal"); - User stephanen = new User("snicoll", "Stephane", "Nicoll", "Proud husband. Passionate and enthusiastic Software engineer. Working on @springboot, @springframework & Spring Initializr at @Pivotal"); - User juergen = new User("springjuergen", "Juergen", "Hoeller"); - User violeta = new User("violetagg", "Violeta", "Georgieva", "All views are my own!"); - - this.userRepository.save(Arrays.asList(brian, mark, arjen, rossen, sam, seb, simon, stephanem, stephanen, juergen, violeta)); - - String reactorTitle = "Reactor Bismuth is out"; - Post reactorPost = new Post( - Utils.slugify(reactorTitle), - reactorTitle, - "It is my great pleasure to announce the GA release of **Reactor Bismuth**, which notably encompasses `reactor-core` **3.1.0.RELEASE** and `reactor-netty` **0.7.0.RELEASE** \uD83C\uDF89", - "With the release of [Spring Framework 5.0](https://spring.io/blog/2017/09/28/spring-framework-5-0-goes-ga) now just happening, you can imagine this is a giant step for Project Reactor :)\n", - simon, - LocalDateTime.of(2017, 9, 28, 12, 00) - ); - - String springTitle = "Spring Framework 5.0 goes GA"; - Post spring5Post = new Post( - Utils.slugify(springTitle), - springTitle, - "Dear Spring community,\n\nIt is my pleasure to announce that, after more than a year of milestones and RCs and almost two years of development overall, Spring Framework 5.0 is finally generally available as 5.0.0.RELEASE from [repo.spring.io](https://repo.spring.io) and Maven Central!", - "This brand-new generation of the framework is ready for 2018 and beyond: with support for JDK 9 and the Java EE 8 API level (e.g. Servlet 4.0), as well as comprehensive integration with Reactor 3.1, JUnit 5, and the Kotlin language. On top of that all, Spring Framework 5 comes with many functional API variants and introduces a dedicated reactive web framework called Spring WebFlux, next to a revised version of our Servlet-based web framework Spring MVC.", - juergen, - LocalDateTime.of(2017, 9, 28, 11, 30) - ); - - String postTitle = "Introducing Kotlin support in Spring Framework 5.0"; - Post springKotlinPost = new Post( - Utils.slugify(postTitle), - postTitle, - "Following the [Kotlin support on start.spring.io](https://spring.io/blog/2016/02/15/developing-spring-boot-applications-with-kotlin) we introduced a few months ago, we have continued to work to ensure that Spring and [Kotlin](https://kotlin.link/) play well together.", - "One of the key strengths of Kotlin is that it provides a very good [interoperability](https://kotlinlang.org/docs/reference/java-interop.html) with libraries written in Java. But there are ways to go even further and allow writing fully idiomatic Kotlin code when developing your next Spring application. In addition to Spring Framework support for Java 8 that Kotlin applications can leverage like functional web or bean registration APIs, there are additional Kotlin dedicated features that should allow you to reach a new level of productivity.", - seb, - LocalDateTime.of(2017, 1, 4, 9, 0) - ); - - this.postRepository.save(Arrays.asList(reactorPost, spring5Post, springKotlinPost)); - - } - -} diff --git a/src/main/java/io/spring/deepdive/Utils.java b/src/main/java/io/spring/deepdive/Utils.java deleted file mode 100644 index c9cfd41..0000000 --- a/src/main/java/io/spring/deepdive/Utils.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.spring.deepdive; - -import java.text.Normalizer; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; -import java.time.temporal.ChronoField; -import java.time.temporal.TemporalAccessor; -import java.util.Locale; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -public abstract class Utils { - - private static Map daysLookup = - IntStream.rangeClosed(1, 31).boxed().collect(Collectors.toMap(Integer::longValue, Utils::getOrdinal)); - - private static DateTimeFormatter englishDateFormatter = new DateTimeFormatterBuilder() - .appendPattern("MMMM") - .appendLiteral(" ") - .appendText(ChronoField.DAY_OF_MONTH, daysLookup) - .appendLiteral(" ") - .appendPattern("yyyy") - .toFormatter(Locale.ENGLISH); - - public static String slugify(String text) { - return String.join("-", stripAccents(text.toLowerCase()) - .replaceAll("\n", " ") - .replaceAll("[^a-z\\d\\s]", " ") - .split(" ")) - .replaceAll("-+", "-"); - } - - public static String stripAccents(String text) { - return Normalizer - .normalize(text, Normalizer.Form.NFD) - .replace("\\p{InCombiningDiacriticalMarks}+", ""); - } - - public static String formatToEnglish(TemporalAccessor temporal) { - return englishDateFormatter.format(temporal); - } - - private static String getOrdinal(int n) { - if (n >= 11 && n <= 13 ) { - return n + "th"; - } - if ( n % 10 == 1) { - return n + "st"; - } - if ( n % 10 == 2) { - return n + "nd"; - } - if ( n % 10 == 3) { - return n + "rd"; - } - return n + "th"; - } - -} diff --git a/src/main/java/io/spring/deepdive/model/Post.java b/src/main/java/io/spring/deepdive/model/Post.java deleted file mode 100644 index 55e56c4..0000000 --- a/src/main/java/io/spring/deepdive/model/Post.java +++ /dev/null @@ -1,131 +0,0 @@ -package io.spring.deepdive.model; - -import java.time.LocalDateTime; - -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.DBRef; -import org.springframework.data.mongodb.core.mapping.Document; - -@Document -public class Post { - - @Id - private String slug; - - private String title; - - private LocalDateTime addedAt; - - private String headline; - - private String content; - - @DBRef - private User author; - - - public Post() { - } - - public Post(String slug, String title, String headline, String content, User author) { - this(slug, title, headline, content, author, LocalDateTime.now()); - } - - public Post(String slug, String title, String headline, String content, User author, LocalDateTime addedAt) { - this.slug = slug; - this.title = title; - this.addedAt = addedAt; - this.headline = headline; - this.content = content; - this.author = author; - } - - public String getSlug() { - return slug; - } - - public void setSlug(String slug) { - this.slug = slug; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public LocalDateTime getAddedAt() { - return addedAt; - } - - public void setAddedAt(LocalDateTime addedAt) { - this.addedAt = addedAt; - } - - public String getHeadline() { - return headline; - } - - public void setHeadline(String headline) { - this.headline = headline; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public User getAuthor() { - return author; - } - - public void setAuthor(User author) { - this.author = author; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Post post = (Post) o; - - if (slug != null ? !slug.equals(post.slug) : post.slug != null) return false; - if (title != null ? !title.equals(post.title) : post.title != null) return false; - if (addedAt != null ? !addedAt.equals(post.addedAt) : post.addedAt != null) - return false; - if (headline != null ? !headline.equals(post.headline) : post.headline != null) - return false; - if (content != null ? !content.equals(post.content) : post.content != null) - return false; - return author != null ? author.equals(post.author) : post.author == null; - } - - @Override - public int hashCode() { - int result = slug != null ? slug.hashCode() : 0; - result = 31 * result + (title != null ? title.hashCode() : 0); - result = 31 * result + (addedAt != null ? addedAt.hashCode() : 0); - result = 31 * result + (headline != null ? headline.hashCode() : 0); - result = 31 * result + (content != null ? content.hashCode() : 0); - result = 31 * result + (author != null ? author.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "Post{" + - "slug='" + slug + '\'' + - ", title='" + title + '\'' + - ", addedAt=" + addedAt + - ", headline='" + headline + '\'' + - ", content='" + content + '\'' + - ", author=" + author + - '}'; - } -} diff --git a/src/main/java/io/spring/deepdive/model/User.java b/src/main/java/io/spring/deepdive/model/User.java deleted file mode 100644 index dd99b14..0000000 --- a/src/main/java/io/spring/deepdive/model/User.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.spring.deepdive.model; - -import org.springframework.data.annotation.Id; - -public class User { - - @Id - private String login; - - private String firstname; - - private String lastname; - - private String description; - - - public User() { - } - - public User(String login, String firstname, String lastname) { - this(login, firstname, lastname, null); - } - - public User(String login, String firstname, String lastname, String description) { - this.login = login; - this.firstname = firstname; - this.lastname = lastname; - this.description = description; - } - - - public String getLogin() { - return login; - } - - public void setLogin(String login) { - this.login = login; - } - - 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 String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - User user = (User) o; - - if (login != null ? !login.equals(user.login) : user.login != null) return false; - if (firstname != null ? !firstname.equals(user.firstname) : user.firstname != null) - return false; - if (lastname != null ? !lastname.equals(user.lastname) : user.lastname != null) - return false; - return description != null ? description.equals(user.description) : user.description == null; - } - - @Override - public int hashCode() { - int result = login != null ? login.hashCode() : 0; - result = 31 * result + (firstname != null ? firstname.hashCode() : 0); - result = 31 * result + (lastname != null ? lastname.hashCode() : 0); - result = 31 * result + (description != null ? description.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "User{" + - "login='" + login + '\'' + - ", firstname='" + firstname + '\'' + - ", lastname='" + lastname + '\'' + - ", description='" + description + '\'' + - '}'; - } -} diff --git a/src/main/java/io/spring/deepdive/web/HtmlPages.java b/src/main/java/io/spring/deepdive/web/HtmlPages.java deleted file mode 100644 index 85ee023..0000000 --- a/src/main/java/io/spring/deepdive/web/HtmlPages.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.spring.deepdive.web; - -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -import io.spring.deepdive.MarkdownConverter; -import io.spring.deepdive.model.Post; -import io.spring.deepdive.repository.PostRepository; - -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; - -@Controller -public class HtmlPages { - - private final PostRepository postRepository; - - private final MarkdownConverter markdownConverter; - - - public HtmlPages(PostRepository postRepository, MarkdownConverter markdownConverter) { - this.postRepository = postRepository; - this.markdownConverter = markdownConverter; - } - - @GetMapping(value = "/") - public String blog(Model model) { - Iterable posts = postRepository.findAll(); - Iterable postDtos = StreamSupport.stream(posts.spliterator(), false).map(post -> new PostDto(post, markdownConverter)).collect(Collectors.toList()); - model.addAttribute("title", "Blog"); - model.addAttribute("posts", postDtos); - return "blog"; - } - - @GetMapping("/{slug}") - public String post(@PathVariable String slug, Model model) { - Post post = postRepository.findOne(slug); - Assert.notNull(post, "Wrong post slug provided"); - PostDto postDto = new PostDto(post, markdownConverter); - model.addAttribute("post", postDto); - return "post"; - } - -} diff --git a/src/main/java/io/spring/deepdive/web/PostApi.java b/src/main/java/io/spring/deepdive/web/PostApi.java deleted file mode 100644 index dabc2d8..0000000 --- a/src/main/java/io/spring/deepdive/web/PostApi.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.spring.deepdive.web; - -import io.spring.deepdive.model.Post; -import io.spring.deepdive.repository.PostRepository; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/api/post") -public class PostApi { - - private final PostRepository postRepository; - - public PostApi(PostRepository postRepository) { - this.postRepository = postRepository; - } - - @GetMapping("/") - public Iterable findAll() { - return postRepository.findAll(); - } - - @GetMapping("/{slug}") - public Post findOne(@PathVariable String slug) { - return postRepository.findOne(slug); - } - -} diff --git a/src/main/java/io/spring/deepdive/web/PostDto.java b/src/main/java/io/spring/deepdive/web/PostDto.java deleted file mode 100644 index 2f4e1a3..0000000 --- a/src/main/java/io/spring/deepdive/web/PostDto.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.spring.deepdive.web; - -import io.spring.deepdive.MarkdownConverter; -import io.spring.deepdive.Utils; -import io.spring.deepdive.model.Post; -import io.spring.deepdive.model.User; - - -class PostDto { - - private final String slug; - - private final String title; - - private final String addedAt; - - private final String headline; - - private final String content; - - private final User author; - - - public PostDto(Post post, MarkdownConverter markdownConverter) { - this.slug = post.getSlug(); - this.title = markdownConverter.apply(post.getTitle()); - this.addedAt = Utils.formatToEnglish(post.getAddedAt()); - this.headline = markdownConverter.apply(post.getHeadline()); - this.content = markdownConverter.apply(post.getContent()); - this.author = post.getAuthor(); - } - - public String getSlug() { - return slug; - } - - public String getTitle() { - return title; - } - - public String getAddedAt() { - return addedAt; - } - - public String getHeadline() { - return headline; - } - - public String getContent() { - return content; - } - - public User getAuthor() { - return author; - } - -} diff --git a/src/main/java/io/spring/deepdive/web/UserApi.java b/src/main/java/io/spring/deepdive/web/UserApi.java deleted file mode 100644 index 86cc662..0000000 --- a/src/main/java/io/spring/deepdive/web/UserApi.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.spring.deepdive.web; - -import io.spring.deepdive.model.User; -import io.spring.deepdive.repository.UserRepository; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/api/user") -public class UserApi { - - private final UserRepository userRepository; - - public UserApi(UserRepository userRepository) { - this.userRepository = userRepository; - } - - @GetMapping("/") - public Iterable findAll() { - return userRepository.findAll(); - } - - @GetMapping("/{login}") - public User findOne(@PathVariable String login) { - return userRepository.findOne(login); - } - -} diff --git a/src/main/kotlin/io/spring/deepdive/Application.kt b/src/main/kotlin/io/spring/deepdive/Application.kt new file mode 100644 index 0000000..6365b88 --- /dev/null +++ b/src/main/kotlin/io/spring/deepdive/Application.kt @@ -0,0 +1,20 @@ +package io.spring.deepdive; + +import com.samskivert.mustache.Mustache; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +class Application { + + @Bean + fun mustacheCompiler(templateLoader: Mustache.TemplateLoader) = + Mustache.compiler().escapeHTML(false).withLoader(templateLoader); + +} + +fun main(args: Array) { + SpringApplication.run(Application::class.java, *args) +} \ No newline at end of file diff --git a/src/main/kotlin/io/spring/deepdive/DatabaseInitializer.kt b/src/main/kotlin/io/spring/deepdive/DatabaseInitializer.kt new file mode 100644 index 0000000..7209d5e --- /dev/null +++ b/src/main/kotlin/io/spring/deepdive/DatabaseInitializer.kt @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.spring.deepdive + + +import java.time.LocalDateTime +import java.util.Arrays + +import io.spring.deepdive.model.Post +import io.spring.deepdive.model.User +import io.spring.deepdive.repository.PostRepository +import io.spring.deepdive.repository.UserRepository + +import org.springframework.boot.CommandLineRunner +import org.springframework.stereotype.Component + +@Component +class DatabaseInitializer(private val userRepository: UserRepository, private val postRepository: PostRepository) : CommandLineRunner { + + override fun run(vararg args: String) { + val brian = User("bclozel", "Brian", "Clozel", "Spring Framework & Spring Boot @pivotal — @LaCordeeLyon coworker") + val mark = User("MkHeck","Mark", "Heckler", "Spring Developer Advocate @Pivotal. Computer scientist+MBA, inglés y español, @Java_Champions. Pragmatic optimist. #Spring #Reactive #Microservices #IoT #Cloud") + val arjen = User("poutsma", "Arjen", "Poutsma") + val rossen = User("rstoyanchev", "Rossen", "Stoyanchev", "Spring Framework committer @Pivotal") + val sam = User("sam_brannen", "Sam", "Brannen", "Core @SpringFramework & @JUnitTeam Committer. Enterprise @Java Consultant at @Swiftmind. #Spring Trainer. Spring User Group Lead at @JUGCH.") + val seb = User("sdeleuze", "Sebastien", "Deleuze", "Spring Framework committer @Pivotal, @Kotlin addict, #WebAssembly believer, @mixitconf organizer, #techactivism") + val simon = User("simonbasle", "Simon", "Basle", "software development aficionado, Reactor Software Engineer @pivotal") + val stephanem = User("smaldini", "Stephane", "Maldini", "Project Reactor Lead @Pivotal -All things Reactive and Distributed - ex Londoner - opinions != Pivotal") + val stephanen = User("snicoll", "Stephane", "Nicoll", "Proud husband. Passionate and enthusiastic Software engineer. Working on @springboot, @springframework & Spring Initializr at @Pivotal") + val juergen = User("springjuergen", "Juergen", "Hoeller") + val violeta = User("violetagg", "Violeta", "Georgieva", "All views are my own!") + + userRepository.save(Arrays.asList(brian, mark, arjen, rossen, sam, seb, simon, stephanem, stephanen, juergen, violeta)) + + val reactorTitle = "Reactor Bismuth is out" + val reactorPost = Post( + reactorTitle.slugify(), + reactorTitle, + """It is my great pleasure to announce the GA release of **Reactor Bismuth**, which notably encompasses + |`reactor-core` **3.1.0.RELEASE** and `reactor-netty` **0.7.0.RELEASE** \uD83C\uDF89""".trimMargin(), + """With the release of [Spring Framework 5.0](https://spring.io/blog/2017/09/28/spring-framework-5-0-goes-ga) + |now just happening, you can imagine this is a giant step for Project Reactor :)""".trimMargin(), + simon, + LocalDateTime.of(2017, 9, 28, 12, 0) + ) + + val springTitle = "Spring Framework 5.0 goes GA" + val spring5Post = Post( + springTitle.slugify(), + springTitle, + """Dear Spring community, + |It is my pleasure to announce that, after more than a year of milestones and RCs and almost two years of development overall, + |Spring Framework 5.0 is finally generally available as 5.0.0.RELEASE from [repo.spring.io](https://repo.spring.io) + |and Maven Central!""".trimMargin(), + """This brand-new generation of the framework is ready for 2018 and beyond: with support for JDK 9 and the Java EE 8 API level (e.g. Servlet 4.0), + |as well as comprehensive integration with Reactor 3.1, JUnit 5, and the Kotlin language. On top of that all, Spring Framework 5 comes with + |many functional API variants and introduces a dedicated reactive web framework called Spring WebFlux, next to a revised version of our + |Servlet-based web framework Spring MVC.""".trimMargin(), + juergen, + LocalDateTime.of(2017, 9, 28, 11, 30) + ) + + val postTitle = "Introducing Kotlin support in Spring Framework 5.0" + val springKotlinPost = Post( + postTitle.slugify(), + postTitle, + """Following the [Kotlin support on start.spring.io](https://spring.io/blog/2016/02/15/developing-spring-boot-applications-with-kotlin) + |we introduced a few months ago, we have continued to work to ensure that Spring and [Kotlin](https://kotlin.link/) play well together.""".trimMargin(), + """One of the key strengths of Kotlin is that it provides a very good [interoperability](https://kotlinlang.org/docs/reference/java-interop.html) + |with libraries written in Java. But there are ways to go even further and allow writing fully idiomatic Kotlin code when developing your next + |Spring application. In addition to Spring Framework support for Java 8 that Kotlin applications can leverage like functional web or bean registration APIs, + |there are additional Kotlin dedicated features that should allow you to reach a new level of productivity.""".trimMargin(), + seb, + LocalDateTime.of(2017, 1, 4, 9, 0) + ) + + postRepository.save(Arrays.asList(reactorPost, spring5Post, springKotlinPost)) + } +} diff --git a/src/main/kotlin/io/spring/deepdive/Extensions.kt b/src/main/kotlin/io/spring/deepdive/Extensions.kt new file mode 100644 index 0000000..ea61cec --- /dev/null +++ b/src/main/kotlin/io/spring/deepdive/Extensions.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.spring.deepdive + +import java.text.Normalizer +import java.time.LocalDateTime +import java.time.format.DateTimeFormatterBuilder +import java.time.temporal.ChronoField +import java.util.Locale +import java.util.stream.Collectors +import java.util.stream.IntStream + + +fun LocalDateTime.formatDate() = this.format(englishDateFormatter) + +fun String.slugify() = toLowerCase() + .stripAccents() + .replace("\n", " ") + .replace("[^a-z\\d\\s]".toRegex(), " ") + .split(" ") + .joinToString("-") + .replace("-+".toRegex(), "-") // Avoid multiple consecutive "--" + + +private val daysLookup: kotlin.collections.Map = + IntStream.rangeClosed(1, 31).boxed().collect(Collectors.toMap(Int::toLong, ::getOrdinal)) + +private val englishDateFormatter = DateTimeFormatterBuilder() + .appendPattern("MMMM") + .appendLiteral(" ") + .appendText(ChronoField.DAY_OF_MONTH, daysLookup) + .appendLiteral(" ") + .appendPattern("yyyy") + .toFormatter(Locale.ENGLISH) + +private fun getOrdinal(n: Int) = when { + n in 11..13 -> "${n}th" + n % 10 == 1 -> "${n}st" + n % 10 == 2 -> "${n}nd" + n % 10 == 3 -> "${n}rd" + else -> "${n}th" +} + +private fun String.stripAccents() = Normalizer + .normalize(this, Normalizer.Form.NFD) + .replace("\\p{InCombiningDiacriticalMarks}+".toRegex(), "") + diff --git a/src/main/java/io/spring/deepdive/MarkdownConverter.java b/src/main/kotlin/io/spring/deepdive/MarkdownConverter.kt similarity index 52% rename from src/main/java/io/spring/deepdive/MarkdownConverter.java rename to src/main/kotlin/io/spring/deepdive/MarkdownConverter.kt index ca58ede..782b39a 100644 --- a/src/main/java/io/spring/deepdive/MarkdownConverter.java +++ b/src/main/kotlin/io/spring/deepdive/MarkdownConverter.kt @@ -13,28 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.spring.deepdive; +package io.spring.deepdive -import java.util.Arrays; -import java.util.function.Function; +import java.util.Arrays +import java.util.function.Function -import org.commonmark.ext.autolink.AutolinkExtension; -import org.commonmark.parser.Parser; -import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.ext.autolink.AutolinkExtension +import org.commonmark.parser.Parser +import org.commonmark.renderer.html.HtmlRenderer -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Service @Service -public class MarkdownConverter implements Function { +class MarkdownConverter : Function { - private Parser parser = Parser.builder().extensions(Arrays.asList(AutolinkExtension.create())).build(); - private HtmlRenderer renderer = HtmlRenderer.builder().build(); + private val parser = Parser.builder().extensions(Arrays.asList(AutolinkExtension.create())).build() + private val renderer = HtmlRenderer.builder().build() - - @Override - public String apply(String input) { - if (input == null || input.isEmpty()) { - return ""; + override fun apply(input: String?): String { + if (input == null || input == "") { + return "" } return renderer.render(parser.parse(input)); } diff --git a/src/main/kotlin/io/spring/deepdive/model/Post.kt b/src/main/kotlin/io/spring/deepdive/model/Post.kt new file mode 100644 index 0000000..46d7655 --- /dev/null +++ b/src/main/kotlin/io/spring/deepdive/model/Post.kt @@ -0,0 +1,16 @@ +package io.spring.deepdive.model + +import java.time.LocalDateTime + +import org.springframework.data.annotation.Id +import org.springframework.data.mongodb.core.mapping.DBRef +import org.springframework.data.mongodb.core.mapping.Document + +@Document +data class Post( + @Id val slug: String, + val title: String, + val headline: String, + val content: String, + @DBRef val author: User, + val addedAt: LocalDateTime) diff --git a/src/main/kotlin/io/spring/deepdive/model/User.kt b/src/main/kotlin/io/spring/deepdive/model/User.kt new file mode 100644 index 0000000..0ddc168 --- /dev/null +++ b/src/main/kotlin/io/spring/deepdive/model/User.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.spring.deepdive.model + +import org.springframework.data.annotation.Id +import org.springframework.data.mongodb.core.mapping.Document + +@Document +data class User( + @Id val login: String, + val firstname: String, + val lastname: String, + val description: String? = null) diff --git a/src/main/java/io/spring/deepdive/repository/PostRepository.java b/src/main/kotlin/io/spring/deepdive/repository/PostRepository.kt similarity index 71% rename from src/main/java/io/spring/deepdive/repository/PostRepository.java rename to src/main/kotlin/io/spring/deepdive/repository/PostRepository.kt index cd054ef..34e143b 100644 --- a/src/main/java/io/spring/deepdive/repository/PostRepository.java +++ b/src/main/kotlin/io/spring/deepdive/repository/PostRepository.kt @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.spring.deepdive.repository; +package io.spring.deepdive.repository -import io.spring.deepdive.model.Post; +import io.spring.deepdive.model.Post -import org.springframework.data.repository.CrudRepository; -import org.springframework.stereotype.Repository; +import org.springframework.data.repository.CrudRepository +import org.springframework.stereotype.Repository @Repository -public interface PostRepository extends CrudRepository { -} +interface PostRepository : CrudRepository diff --git a/src/main/java/io/spring/deepdive/repository/UserRepository.java b/src/main/kotlin/io/spring/deepdive/repository/UserRepository.kt similarity index 71% rename from src/main/java/io/spring/deepdive/repository/UserRepository.java rename to src/main/kotlin/io/spring/deepdive/repository/UserRepository.kt index 2dcffa3..35df90b 100644 --- a/src/main/java/io/spring/deepdive/repository/UserRepository.java +++ b/src/main/kotlin/io/spring/deepdive/repository/UserRepository.kt @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.spring.deepdive.repository; +package io.spring.deepdive.repository -import io.spring.deepdive.model.User; +import io.spring.deepdive.model.User -import org.springframework.data.repository.CrudRepository; -import org.springframework.stereotype.Repository; +import org.springframework.data.repository.CrudRepository +import org.springframework.stereotype.Repository @Repository -public interface UserRepository extends CrudRepository { -} +interface UserRepository : CrudRepository diff --git a/src/main/kotlin/io/spring/deepdive/web/HtmlPages.kt b/src/main/kotlin/io/spring/deepdive/web/HtmlPages.kt new file mode 100644 index 0000000..2e930e3 --- /dev/null +++ b/src/main/kotlin/io/spring/deepdive/web/HtmlPages.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.spring.deepdive.web + +import java.util.stream.StreamSupport + +import io.spring.deepdive.MarkdownConverter +import io.spring.deepdive.repository.PostRepository + +import org.springframework.stereotype.Controller +import org.springframework.ui.Model +import org.springframework.util.Assert +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import kotlin.streams.toList + +@Controller +class HtmlPages(private val postRepository: PostRepository, private val markdownConverter: MarkdownConverter) { + + @GetMapping("/") + fun blog(model: Model): String { + val posts = postRepository.findAll() + val postDtos = StreamSupport.stream(posts.spliterator(), false).map { it.toDto(markdownConverter) }.toList() + model.addAttribute("title", "Blog") + model.addAttribute("posts", postDtos) + return "blog" + } + + @GetMapping("/{slug}") + fun post(@PathVariable slug: String, model: Model): String { + val post = postRepository.findOne(slug) + Assert.notNull(post, "Wrong post slug provided") + model.addAttribute("post", post.toDto(markdownConverter)) + return "post" + } + +} diff --git a/src/main/kotlin/io/spring/deepdive/web/PostApi.kt b/src/main/kotlin/io/spring/deepdive/web/PostApi.kt new file mode 100644 index 0000000..1deb6eb --- /dev/null +++ b/src/main/kotlin/io/spring/deepdive/web/PostApi.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.spring.deepdive.web + +import io.spring.deepdive.repository.PostRepository +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/api/post") +class PostApi(private val postRepository: PostRepository) { + + @GetMapping("/") + fun findAll() = postRepository.findAll() + + @GetMapping("/{slug}") + fun findOne(@PathVariable slug: String) = postRepository.findOne(slug) + +} diff --git a/src/main/kotlin/io/spring/deepdive/web/PostDto.kt b/src/main/kotlin/io/spring/deepdive/web/PostDto.kt new file mode 100644 index 0000000..457d62a --- /dev/null +++ b/src/main/kotlin/io/spring/deepdive/web/PostDto.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.spring.deepdive.web; + +import io.spring.deepdive.MarkdownConverter +import io.spring.deepdive.formatDate +import io.spring.deepdive.model.Post +import io.spring.deepdive.model.User + +data class PostDto( + val slug: String, + val title: String, + val headline: String, + val content: String, + val author: User, + val addedAt: String) + +fun Post.toDto(markdownConverter: MarkdownConverter) = PostDto( + slug, + markdownConverter.apply(title), + markdownConverter.apply(headline), + markdownConverter.apply(content), + author, + addedAt.formatDate() + ) \ No newline at end of file diff --git a/src/main/kotlin/io/spring/deepdive/web/UserApi.kt b/src/main/kotlin/io/spring/deepdive/web/UserApi.kt new file mode 100644 index 0000000..7d4d832 --- /dev/null +++ b/src/main/kotlin/io/spring/deepdive/web/UserApi.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.spring.deepdive.web + +import io.spring.deepdive.repository.UserRepository + +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/api/user") +class UserApi(private val userRepository: UserRepository) { + + @GetMapping("/") + fun findAll() = userRepository.findAll() + + @GetMapping("/{login}") + fun findOne(@PathVariable login: String) = userRepository.findOne(login) + +} diff --git a/src/test/java/io/spring/deepdive/FunnyTests.kt b/src/test/java/io/spring/deepdive/FunnyTests.kt new file mode 100644 index 0000000..ed26f44 --- /dev/null +++ b/src/test/java/io/spring/deepdive/FunnyTests.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.spring.deepdive + +import org.junit.Test + +class FunnyTests { + + @Test + fun `Why Spring ❤ Kotlin`() { + println("Because I can use emoj in function names \uD83D\uDE09") + } +} \ No newline at end of file diff --git a/src/test/java/io/spring/deepdive/HtmlPagesTests.java b/src/test/java/io/spring/deepdive/HtmlPagesTests.java deleted file mode 100644 index 86b63ab..0000000 --- a/src/test/java/io/spring/deepdive/HtmlPagesTests.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.spring.deepdive; - -import org.junit.Test; -import org.junit.runner.RunWith; - -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.test.context.junit4.SpringRunner; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.*; - -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -public class HtmlPagesTests { - - @Autowired - private TestRestTemplate restTemplate; - - @Test - public void assertContentOnBlogPage() { - String body = this.restTemplate.getForObject("/", String.class); - assertThat(body) - .contains("Reactor Bismuth is out") - .contains("September 28th") - .contains("Sebastien") - .doesNotContain("brand-new generation"); - } - - @Test - public void assertContentOnBlogPostPage() { - String body = this.restTemplate.getForObject("/spring-framework-5-0-goes-ga", String.class); - assertThat(body) - .contains("Spring Framework 5.0 goes GA") - .contains("Dear Spring community") - .contains("brand-new generation") - .contains("Juergen") - .doesNotContain("Sebastien"); - } - -} diff --git a/src/test/java/io/spring/deepdive/HtmlPagesTests.kt b/src/test/java/io/spring/deepdive/HtmlPagesTests.kt new file mode 100644 index 0000000..405f2f0 --- /dev/null +++ b/src/test/java/io/spring/deepdive/HtmlPagesTests.kt @@ -0,0 +1,41 @@ +package io.spring.deepdive; + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.context.SpringBootTest.* +import org.springframework.boot.test.web.client.TestRestTemplate +import org.springframework.test.context.junit4.SpringRunner + +@RunWith(SpringRunner::class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +class HtmlPagesTests { + + @Autowired + private lateinit var restTemplate: TestRestTemplate + + @Test + fun `Assert content on blog page`() { + val body = restTemplate.getForObject("/", String::class.java) + assertThat(body) + .contains("Reactor Bismuth is out") + .contains("September 28th") + .contains("Sebastien") + .doesNotContain("brand-new generation") + } + + @Test + fun `Assert content on blog post page`() { + val body = restTemplate.getForObject("/spring-framework-5-0-goes-ga", String::class.java) + assertThat(body) + .contains("Spring Framework 5.0 goes GA") + .contains("Dear Spring community") + .contains("brand-new generation") + .contains("Juergen") + .doesNotContain("Sebastien") + } + +} diff --git a/src/test/java/io/spring/deepdive/PostApiTests.java b/src/test/java/io/spring/deepdive/PostApiTests.java deleted file mode 100644 index b524911..0000000 --- a/src/test/java/io/spring/deepdive/PostApiTests.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.spring.deepdive; - -import java.time.LocalDateTime; -import java.util.List; - -import io.spring.deepdive.model.Post; -import org.junit.Test; -import org.junit.runner.RunWith; - -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.core.ParameterizedTypeReference; -import org.springframework.http.HttpMethod; -import org.springframework.test.context.junit4.SpringRunner; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.*; - -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -public class PostApiTests { - - @Autowired - private TestRestTemplate restTemplate; - - - @Test - public void assertFindAllJsonApiIsParsedCorrectlyAndContains3Elements() { - List posts = this.restTemplate.exchange("/api/post/", HttpMethod.GET, null, new ParameterizedTypeReference>() {}).getBody(); - assertThat(posts).hasSize(3); - } - - @Test - public void verifyFindOneJsonApi() { - Post post = this.restTemplate.getForObject("/api/post/reactor-bismuth-is-out", Post.class); - assertThat(post.getTitle()).isEqualTo("Reactor Bismuth is out"); - assertThat(post.getHeadline()).startsWith("It is my great pleasure to"); - assertThat(post.getContent()).startsWith("With the release of"); - assertThat(post.getAddedAt()).isEqualTo(LocalDateTime.of(2017, 9, 28, 12, 00)); - assertThat(post.getAuthor().getFirstname()).isEqualTo("Simon"); - } - -} diff --git a/src/test/java/io/spring/deepdive/PostApiTests.kt b/src/test/java/io/spring/deepdive/PostApiTests.kt new file mode 100644 index 0000000..249e8a7 --- /dev/null +++ b/src/test/java/io/spring/deepdive/PostApiTests.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.spring.deepdive + +import java.time.LocalDateTime + +import io.spring.deepdive.model.Post +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.context.SpringBootTest.* +import org.springframework.boot.test.web.client.TestRestTemplate +import org.springframework.core.ParameterizedTypeReference +import org.springframework.http.HttpMethod +import org.springframework.test.context.junit4.SpringRunner + +@RunWith(SpringRunner::class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +class PostApiTests { + + @Autowired + private lateinit var restTemplate: TestRestTemplate + + + @Test + fun `assert findAll JSON API is parsed correctly and contains 3 elements`() { + val posts = restTemplate.exchange("/api/post/", HttpMethod.GET, null, object: ParameterizedTypeReference>() {}).body + assertThat(posts).hasSize(3); + } + + @Test + fun `verify findOne JSON API`() { + val post = restTemplate.getForObject("/api/post/reactor-bismuth-is-out", Post::class.java) + assertThat(post.title).isEqualTo("Reactor Bismuth is out") + assertThat(post.headline).startsWith("It is my great pleasure to") + assertThat(post.content).startsWith("With the release of") + assertThat(post.addedAt).isEqualTo(LocalDateTime.of(2017, 9, 28, 12, 0)) + assertThat(post.author.firstname).isEqualTo("Simon") + } + +} diff --git a/src/test/java/io/spring/deepdive/UserApiTests.java b/src/test/java/io/spring/deepdive/UserApiTests.java deleted file mode 100644 index c7ce4be..0000000 --- a/src/test/java/io/spring/deepdive/UserApiTests.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.spring.deepdive; - -import java.util.List; - -import io.spring.deepdive.model.User; -import org.junit.Test; -import org.junit.runner.RunWith; - -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.core.ParameterizedTypeReference; -import org.springframework.http.HttpMethod; -import org.springframework.test.context.junit4.SpringRunner; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.*; - -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -public class UserApiTests { - - @Autowired - private TestRestTemplate restTemplate; - - - @Test - public void assertFindAllJsonApiIsParsedCorrectlyAndContains11Elements() { - List users = this.restTemplate.exchange("/api/user/", HttpMethod.GET, null, new ParameterizedTypeReference>() {}).getBody(); - assertThat(users).hasSize(11); - } - - @Test - public void verifyFindOneJsonApi() { - User user = this.restTemplate.getForObject("/api/user/MkHeck", User.class); - assertThat(user.getLogin()).isEqualTo("MkHeck"); - assertThat(user.getFirstname()).isEqualTo("Mark"); - assertThat(user.getLastname()).isEqualTo("Heckler"); - assertThat(user.getDescription()).startsWith("Spring Developer Advocate"); - } - -} diff --git a/src/test/java/io/spring/deepdive/UserApiTests.kt b/src/test/java/io/spring/deepdive/UserApiTests.kt new file mode 100644 index 0000000..1892ce0 --- /dev/null +++ b/src/test/java/io/spring/deepdive/UserApiTests.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.spring.deepdive + +import io.spring.deepdive.model.User +import org.junit.Test +import org.junit.runner.RunWith + +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.core.ParameterizedTypeReference +import org.springframework.http.HttpMethod +import org.springframework.test.context.junit4.SpringRunner + +import org.assertj.core.api.Assertions.assertThat + +@RunWith(SpringRunner::class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class UserApiTests { + + @Autowired + private lateinit var restTemplate: TestRestTemplate + + + @Test + fun `Assert FindAll JSON API is parsed correctly and contains 11 elements`() { + val users = restTemplate.exchange("/api/user/", HttpMethod.GET, null, object: ParameterizedTypeReference>() {}).body + assertThat(users).hasSize(11) + } + + @Test + fun `verify findOne JSON API`() { + val user = this.restTemplate.getForObject("/api/user/MkHeck", User::class.java) + assertThat(user.login).isEqualTo("MkHeck") + assertThat(user.firstname).isEqualTo("Mark") + assertThat(user.lastname).isEqualTo("Heckler") + assertThat(user.description).startsWith("Spring Developer Advocate") + } + +}