├── src ├── main │ ├── resources │ │ ├── application.properties │ │ ├── templates │ │ │ ├── footer.mustache │ │ │ ├── article.mustache │ │ │ ├── blog.mustache │ │ │ └── header.mustache │ │ └── static │ │ │ └── assets │ │ │ ├── spring.png │ │ │ ├── blog.css │ │ │ ├── grids-responsive-min.css │ │ │ └── pure-min.css │ └── java │ │ └── io │ │ └── spring │ │ └── deepdive │ │ ├── Application.java │ │ ├── repository │ │ ├── UserRepository.java │ │ └── ArticleRepository.java │ │ ├── MarkdownConverter.java │ │ ├── web │ │ ├── UserController.java │ │ ├── PostDto.java │ │ ├── ArticleController.java │ │ └── HtmlController.java │ │ ├── Utils.java │ │ ├── model │ │ ├── User.java │ │ └── Article.java │ │ └── DatabaseInitializer.java └── test │ └── java │ └── io │ └── spring │ └── deepdive │ ├── HtmlTests.java │ ├── UserJsonApiTests.java │ └── ArticleJsonApiTests.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── README.md ├── gradlew.bat └── gradlew /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.mustache.suffix=.mustache 2 | -------------------------------------------------------------------------------- /src/main/resources/templates/footer.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdeleuze/spring-kotlin-deepdive/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/static/assets/spring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdeleuze/spring-kotlin-deepdive/HEAD/src/main/resources/static/assets/spring.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build/ 3 | /out/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### NetBeans ### 21 | nbproject/private/ 22 | build/ 23 | nbbuild/ 24 | dist/ 25 | nbdist/ 26 | .nb-gradle/ -------------------------------------------------------------------------------- /src/main/resources/templates/article.mustache: -------------------------------------------------------------------------------- 1 | {{> header}} 2 | 3 |
4 |
5 |

{{article.title}}

6 | 7 |
8 | 9 |
10 | {{article.headline}} 11 | 12 | {{article.content}} 13 |
14 |
15 | 16 | {{> footer}} 17 | -------------------------------------------------------------------------------- /src/main/resources/templates/blog.mustache: -------------------------------------------------------------------------------- 1 | {{> header}} 2 | 3 |

Recent Posts

4 | 5 |
6 | 7 | {{#articles}} 8 |
9 |
10 |

{{title}}

11 | 12 |
13 |
14 | {{headline}} 15 |
16 |
17 | {{/articles}} 18 |
19 | 20 | 21 | {{> footer}} 22 | -------------------------------------------------------------------------------- /src/main/java/io/spring/deepdive/Application.java: -------------------------------------------------------------------------------- 1 | package io.spring.deepdive; 2 | 3 | import com.samskivert.mustache.Mustache; 4 | 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Bean; 8 | 9 | import static com.samskivert.mustache.Mustache.*; 10 | 11 | @SpringBootApplication 12 | public class Application { 13 | 14 | @Bean 15 | public Mustache.Compiler mustacheCompiler(TemplateLoader loader) { 16 | return compiler().escapeHTML(false).withLoader(loader); 17 | } 18 | 19 | public static void main(String[] args) { 20 | SpringApplication.run(Application.class, args); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/spring/deepdive/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.spring.deepdive.repository; 17 | 18 | import io.spring.deepdive.model.User; 19 | 20 | import org.springframework.data.repository.CrudRepository; 21 | import org.springframework.stereotype.Repository; 22 | 23 | @Repository 24 | public interface UserRepository extends CrudRepository { 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/io/spring/deepdive/repository/ArticleRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.spring.deepdive.repository; 17 | 18 | import io.spring.deepdive.model.Article; 19 | 20 | import org.springframework.data.repository.CrudRepository; 21 | import org.springframework.stereotype.Repository; 22 | 23 | @Repository 24 | public interface ArticleRepository extends CrudRepository { 25 | 26 | Iterable
findAllByOrderByAddedAtDesc(); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/templates/header.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{#title}}{{title}}{{/title}} 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 21 | 22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Kotlin deepdive 2 | 3 | This project is designed to show step by step how to migrate from Java to Kotlin with 4 | Spring Boot step by step: 5 | * [Step 0](https://github.com/sdeleuze/spring-kotlin-deepdive/): Initial Java project 6 | * [Step 1](https://github.com/sdeleuze/spring-kotlin-deepdive/tree/step1): Java to Kotlin 7 | * [Step 2](https://github.com/sdeleuze/spring-kotlin-deepdive/tree/step2): Spring Boot 2 8 | * [Step 3](https://github.com/sdeleuze/spring-kotlin-deepdive/tree/step3): Spring WebFlux 9 | * [Step 4](https://github.com/sdeleuze/spring-kotlin-deepdive/tree/step4): Kotlin routing DSL 10 | 11 | See [Spring Kotlin support documentation](https://docs.spring.io/spring/docs/current/spring-framework-reference/languages.html#kotlin) for more details. 12 | 13 | ## [Step 0]((https://github.com/sdeleuze/spring-kotlin-deepdive)): The initial Java project 14 | 15 | * Simple blog with JSON HTTP API 16 | * Integration tests can be run via `./gradlew test` (or in the IDE) 17 | * Run the project via `./gradlew bootRun` (or in the IDE) and go to `http://localhost:8080/` with your browser 18 | * Present the Java application software design 19 | * Reminders: 20 | * No need for annotating constructor when single constructor for autowiring it (as of Spring 4.3), show 2 syntaxes 21 | * `@RequestMapping` aliases: `@GetMapping`, `@PostMapping`, etc. 22 | * Reload via CTRL + F9 in IDEA (CMD + SHIFT + F9 on Mac) 23 | 24 | **[Go to step 1: Java to Kotlin](https://github.com/sdeleuze/spring-kotlin-deepdive/tree/step1)** 25 | -------------------------------------------------------------------------------- /src/main/java/io/spring/deepdive/MarkdownConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.spring.deepdive; 17 | 18 | import java.util.Arrays; 19 | import java.util.function.Function; 20 | 21 | import org.commonmark.ext.autolink.AutolinkExtension; 22 | import org.commonmark.parser.Parser; 23 | import org.commonmark.renderer.html.HtmlRenderer; 24 | 25 | import org.springframework.stereotype.Service; 26 | 27 | @Service 28 | public class MarkdownConverter implements Function { 29 | 30 | private Parser parser = Parser.builder().extensions(Arrays.asList(AutolinkExtension.create())).build(); 31 | private HtmlRenderer renderer = HtmlRenderer.builder().build(); 32 | 33 | 34 | @Override 35 | public String apply(String input) { 36 | if (input == null || input.isEmpty()) { 37 | return ""; 38 | } 39 | return renderer.render(parser.parse(input)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/io/spring/deepdive/HtmlTests.java: -------------------------------------------------------------------------------- 1 | package io.spring.deepdive; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.boot.test.web.client.TestRestTemplate; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | import static org.springframework.boot.test.context.SpringBootTest.*; 13 | 14 | @RunWith(SpringRunner.class) 15 | @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) 16 | public class HtmlTests { 17 | 18 | @Autowired 19 | private TestRestTemplate restTemplate; 20 | 21 | @Test 22 | public void assertContentOnBlogPage() { 23 | String body = restTemplate.getForObject("/", String.class); 24 | assertThat(body) 25 | .contains("Reactor Bismuth is out") 26 | .contains("September 28th") 27 | .contains("Sebastien") 28 | .doesNotContain("brand-new generation"); 29 | } 30 | 31 | @Test 32 | public void assertContentOnBlogPostPage() { 33 | String body = restTemplate.getForObject("/article/spring-framework-5-0-goes-ga", String.class); 34 | assertThat(body) 35 | .contains("Spring Framework 5.0 goes GA") 36 | .contains("Dear Spring community") 37 | .contains("brand-new generation") 38 | .contains("Juergen") 39 | .doesNotContain("Sebastien"); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/io/spring/deepdive/web/UserController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.spring.deepdive.web; 17 | 18 | import io.spring.deepdive.model.User; 19 | import io.spring.deepdive.repository.UserRepository; 20 | 21 | import org.springframework.web.bind.annotation.GetMapping; 22 | import org.springframework.web.bind.annotation.PathVariable; 23 | import org.springframework.web.bind.annotation.RequestMapping; 24 | import org.springframework.web.bind.annotation.RestController; 25 | 26 | @RestController 27 | @RequestMapping("/api/user") 28 | public class UserController { 29 | 30 | private final UserRepository userRepository; 31 | 32 | public UserController(UserRepository userRepository) { 33 | this.userRepository = userRepository; 34 | } 35 | 36 | @GetMapping("/") 37 | public Iterable findAll() { 38 | return userRepository.findAll(); 39 | } 40 | 41 | @GetMapping("/{login}") 42 | public User findOne(@PathVariable String login) { 43 | return userRepository.findOne(login); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/io/spring/deepdive/web/PostDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.spring.deepdive.web; 17 | 18 | import io.spring.deepdive.MarkdownConverter; 19 | import io.spring.deepdive.Utils; 20 | import io.spring.deepdive.model.Article; 21 | import io.spring.deepdive.model.User; 22 | 23 | class PostDto { 24 | 25 | private final String slug; 26 | 27 | private final String title; 28 | 29 | private final String headline; 30 | 31 | private final String content; 32 | 33 | private final User author; 34 | 35 | private final String addedAt; 36 | 37 | 38 | public PostDto(Article article, MarkdownConverter markdownConverter) { 39 | this.slug = article.getSlug(); 40 | this.title = article.getTitle(); 41 | this.headline = markdownConverter.apply(article.getHeadline()); 42 | this.content = markdownConverter.apply(article.getContent()); 43 | this.author = article.getAuthor(); 44 | this.addedAt = Utils.formatToEnglish(article.getAddedAt()); 45 | } 46 | 47 | public String getSlug() { 48 | return slug; 49 | } 50 | 51 | public String getTitle() { 52 | return title; 53 | } 54 | 55 | public String getContent() { 56 | return content; 57 | } 58 | 59 | public User getAuthor() { 60 | return author; 61 | } 62 | 63 | public String getAddedAt() { 64 | return addedAt; 65 | } 66 | 67 | public String getHeadline() { 68 | return headline; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/io/spring/deepdive/UserJsonApiTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.spring.deepdive; 17 | 18 | import java.util.List; 19 | 20 | import io.spring.deepdive.model.User; 21 | import org.junit.Test; 22 | import org.junit.runner.RunWith; 23 | 24 | import org.springframework.beans.factory.annotation.Autowired; 25 | import org.springframework.boot.test.context.SpringBootTest; 26 | import org.springframework.boot.test.web.client.TestRestTemplate; 27 | import org.springframework.core.ParameterizedTypeReference; 28 | import org.springframework.http.HttpMethod; 29 | import org.springframework.test.context.junit4.SpringRunner; 30 | 31 | import static org.assertj.core.api.Assertions.assertThat; 32 | import static org.springframework.boot.test.context.SpringBootTest.*; 33 | 34 | @RunWith(SpringRunner.class) 35 | @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) 36 | public class UserJsonApiTests { 37 | 38 | @Autowired 39 | private TestRestTemplate restTemplate; 40 | 41 | @Test 42 | public void assertFindAllJsonApiIsParsedCorrectlyAndContains11Elements() { 43 | List users = restTemplate.exchange("/api/user/", HttpMethod.GET, null, new ParameterizedTypeReference>() {}).getBody(); 44 | assertThat(users).hasSize(11); 45 | } 46 | 47 | @Test 48 | public void verifyFindOneJsonApi() { 49 | User user = restTemplate.getForObject("/api/user/MkHeck", User.class); 50 | assertThat(user.getLogin()).isEqualTo("MkHeck"); 51 | assertThat(user.getFirstname()).isEqualTo("Mark"); 52 | assertThat(user.getLastname()).isEqualTo("Heckler"); 53 | assertThat(user.getDescription()).startsWith("Spring Developer Advocate"); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/io/spring/deepdive/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.spring.deepdive; 17 | 18 | import java.time.format.DateTimeFormatter; 19 | import java.time.format.DateTimeFormatterBuilder; 20 | import java.time.temporal.ChronoField; 21 | import java.time.temporal.TemporalAccessor; 22 | import java.util.Locale; 23 | import java.util.Map; 24 | import java.util.stream.Collectors; 25 | import java.util.stream.IntStream; 26 | 27 | public abstract class Utils { 28 | 29 | private static Map daysLookup = 30 | IntStream.rangeClosed(1, 31).boxed().collect(Collectors.toMap(Integer::longValue, Utils::getOrdinal)); 31 | 32 | private static DateTimeFormatter englishDateFormatter = new DateTimeFormatterBuilder() 33 | .appendPattern("MMMM") 34 | .appendLiteral(" ") 35 | .appendText(ChronoField.DAY_OF_MONTH, daysLookup) 36 | .appendLiteral(" ") 37 | .appendPattern("yyyy") 38 | .toFormatter(Locale.ENGLISH); 39 | 40 | public static String slugify(String text) { 41 | return String.join("-", 42 | text.toLowerCase().replaceAll("[^a-z\\d\\s]", " ").split(" ") 43 | ); 44 | } 45 | 46 | public static String formatToEnglish(TemporalAccessor temporal) { 47 | return englishDateFormatter.format(temporal); 48 | } 49 | 50 | private static String getOrdinal(int n) { 51 | if (n >= 11 && n <= 13 ) { 52 | return n + "th"; 53 | } 54 | if ( n % 10 == 1) { 55 | return n + "st"; 56 | } 57 | if ( n % 10 == 2) { 58 | return n + "nd"; 59 | } 60 | if ( n % 10 == 3) { 61 | return n + "rd"; 62 | } 63 | return n + "th"; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/java/io/spring/deepdive/web/ArticleController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.spring.deepdive.web; 17 | 18 | import io.spring.deepdive.MarkdownConverter; 19 | import io.spring.deepdive.model.Article; 20 | import io.spring.deepdive.repository.ArticleRepository; 21 | 22 | import org.springframework.web.bind.annotation.GetMapping; 23 | import org.springframework.web.bind.annotation.PathVariable; 24 | import org.springframework.web.bind.annotation.RequestMapping; 25 | import org.springframework.web.bind.annotation.RequestParam; 26 | import org.springframework.web.bind.annotation.RestController; 27 | 28 | @RestController 29 | @RequestMapping("/api/article") 30 | public class ArticleController { 31 | 32 | private final ArticleRepository articleRepository; 33 | 34 | private final MarkdownConverter markdownConverter; 35 | 36 | public ArticleController(ArticleRepository articleRepository, MarkdownConverter markdownConverter) { 37 | this.articleRepository = articleRepository; 38 | this.markdownConverter = markdownConverter; 39 | } 40 | 41 | @GetMapping("/") 42 | public Iterable
findAll() { 43 | return articleRepository.findAllByOrderByAddedAtDesc(); 44 | } 45 | 46 | @GetMapping("/{slug}") 47 | public Article findOne(@PathVariable String slug, @RequestParam(required = false) String converter) { 48 | Article article = articleRepository.findOne(slug); 49 | if (converter != null) { 50 | if (converter.equals("markdown")) { 51 | article.setHeadline(markdownConverter.apply(article.getHeadline())); 52 | article.setContent(markdownConverter.apply(article.getContent())); 53 | 54 | } 55 | else { 56 | throw new IllegalArgumentException("Only markdown converter is supported"); 57 | } 58 | } 59 | return article; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/spring/deepdive/web/HtmlController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.spring.deepdive.web; 17 | 18 | import java.util.stream.Collectors; 19 | import java.util.stream.StreamSupport; 20 | 21 | import io.spring.deepdive.MarkdownConverter; 22 | import io.spring.deepdive.model.Article; 23 | import io.spring.deepdive.repository.ArticleRepository; 24 | 25 | import org.springframework.stereotype.Controller; 26 | import org.springframework.ui.Model; 27 | import org.springframework.util.Assert; 28 | import org.springframework.web.bind.annotation.GetMapping; 29 | import org.springframework.web.bind.annotation.PathVariable; 30 | 31 | @Controller 32 | public class HtmlController { 33 | 34 | private final ArticleRepository articleRepository; 35 | 36 | private final MarkdownConverter markdownConverter; 37 | 38 | 39 | public HtmlController(ArticleRepository articleRepository, MarkdownConverter markdownConverter) { 40 | this.articleRepository = articleRepository; 41 | this.markdownConverter = markdownConverter; 42 | } 43 | 44 | @GetMapping("/") 45 | public String blog(Model model) { 46 | Iterable
posts = articleRepository.findAllByOrderByAddedAtDesc(); 47 | Iterable postDtos = StreamSupport.stream(posts.spliterator(), false).map(post -> new PostDto(post, markdownConverter)).collect(Collectors.toList()); 48 | model.addAttribute("title", "Blog"); 49 | model.addAttribute("articles", postDtos); 50 | return "blog"; 51 | } 52 | 53 | @GetMapping("/article/{slug}") 54 | public String post(@PathVariable String slug, Model model) { 55 | Article article = articleRepository.findOne(slug); 56 | Assert.notNull(article, "Wrong article slug provided"); 57 | PostDto postDto = new PostDto(article, markdownConverter); 58 | model.addAttribute("article", postDto); 59 | return "article"; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/resources/static/assets/blog.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-box-sizing: border-box; 3 | -moz-box-sizing: border-box; 4 | box-sizing: border-box; 5 | } 6 | 7 | a { 8 | text-decoration: none; 9 | color: rgb(61, 146, 201); 10 | } 11 | a:hover, 12 | a:focus { 13 | text-decoration: underline; 14 | } 15 | 16 | h3 { 17 | font-weight: 100; 18 | } 19 | 20 | /* LAYOUT CSS */ 21 | .pure-img-responsive { 22 | max-width: 100%; 23 | height: auto; 24 | } 25 | 26 | #layout { 27 | padding: 0; 28 | } 29 | 30 | .header { 31 | text-align: center; 32 | top: auto; 33 | margin: 3em auto; 34 | } 35 | 36 | .sidebar { 37 | background: rgb(61, 79, 93); 38 | color: #fff; 39 | } 40 | 41 | .brand-title, 42 | .brand-tagline { 43 | margin: 0; 44 | } 45 | .brand-title { 46 | text-transform: uppercase; 47 | } 48 | .brand-tagline { 49 | font-weight: 300; 50 | color: rgb(176, 202, 219); 51 | } 52 | 53 | .nav-list { 54 | margin: 0; 55 | padding: 0; 56 | list-style: none; 57 | } 58 | .nav-item { 59 | display: inline-block; 60 | *display: inline; 61 | zoom: 1; 62 | } 63 | .nav-item a { 64 | background: transparent; 65 | border: 2px solid rgb(176, 202, 219); 66 | color: #fff; 67 | margin-top: 1em; 68 | letter-spacing: 0.05em; 69 | text-transform: uppercase; 70 | font-size: 85%; 71 | } 72 | .nav-item a:hover, 73 | .nav-item a:focus { 74 | border: 2px solid rgb(61, 146, 201); 75 | text-decoration: none; 76 | } 77 | 78 | .content-subhead { 79 | text-transform: uppercase; 80 | color: #aaa; 81 | border-bottom: 1px solid #eee; 82 | padding: 0.4em 0; 83 | font-size: 80%; 84 | font-weight: 500; 85 | letter-spacing: 0.1em; 86 | } 87 | 88 | .content { 89 | padding: 2em 1em 0; 90 | } 91 | 92 | .article { 93 | padding-bottom: 2em; 94 | } 95 | .article-title { 96 | font-size: 2em; 97 | color: #222; 98 | margin-bottom: 0.2em; 99 | } 100 | .article-avatar { 101 | border-radius: 50px; 102 | float: right; 103 | margin-left: 1em; 104 | } 105 | .article-description { 106 | font-family: Georgia, "Cambria", serif; 107 | color: #444; 108 | line-height: 1.8em; 109 | } 110 | .article-meta { 111 | color: #999; 112 | font-size: 90%; 113 | margin: 0; 114 | } 115 | 116 | .articlet-category { 117 | margin: 0 0.1em; 118 | padding: 0.3em 1em; 119 | color: #fff; 120 | background: #999; 121 | font-size: 80%; 122 | } 123 | .article-category-design { 124 | background: #5aba59; 125 | } 126 | .article-category-pure { 127 | background: #4d85d1; 128 | } 129 | .article-category-yui { 130 | background: #8156a7; 131 | } 132 | .article-category-js { 133 | background: #df2d4f; 134 | } 135 | 136 | .article-images { 137 | margin: 1em 0; 138 | } 139 | .article-image-meta { 140 | margin-top: -3.5em; 141 | margin-left: 1em; 142 | color: #fff; 143 | text-shadow: 0 1px 1px #333; 144 | } 145 | 146 | .footer { 147 | text-align: center; 148 | padding: 1em 0; 149 | } 150 | .footer a { 151 | color: #ccc; 152 | font-size: 80%; 153 | } 154 | .footer .pure-menu a:hover, 155 | .footer .pure-menu a:focus { 156 | background: none; 157 | } 158 | 159 | @media (min-width: 48em) { 160 | .content { 161 | padding: 2em 3em 0; 162 | margin-left: 25%; 163 | } 164 | 165 | .header { 166 | margin: 80% 2em 0; 167 | text-align: right; 168 | } 169 | 170 | .sidebar { 171 | position: fixed; 172 | top: 0; 173 | bottom: 0; 174 | } 175 | } -------------------------------------------------------------------------------- /src/main/java/io/spring/deepdive/model/User.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.spring.deepdive.model; 17 | 18 | import org.springframework.data.annotation.Id; 19 | 20 | public class User { 21 | 22 | @Id 23 | private String login; 24 | 25 | private String firstname; 26 | 27 | private String lastname; 28 | 29 | private String description; 30 | 31 | 32 | public User() { 33 | } 34 | 35 | public User(String login, String firstname, String lastname) { 36 | this(login, firstname, lastname, null); 37 | } 38 | 39 | public User(String login, String firstname, String lastname, String description) { 40 | this.login = login; 41 | this.firstname = firstname; 42 | this.lastname = lastname; 43 | this.description = description; 44 | } 45 | 46 | 47 | public String getLogin() { 48 | return login; 49 | } 50 | 51 | public void setLogin(String login) { 52 | this.login = login; 53 | } 54 | 55 | public String getFirstname() { 56 | return firstname; 57 | } 58 | 59 | public void setFirstname(String firstname) { 60 | this.firstname = firstname; 61 | } 62 | 63 | public String getLastname() { 64 | return lastname; 65 | } 66 | 67 | public void setLastname(String lastname) { 68 | this.lastname = lastname; 69 | } 70 | 71 | public String getDescription() { 72 | return description; 73 | } 74 | 75 | public void setDescription(String description) { 76 | this.description = description; 77 | } 78 | 79 | 80 | @Override 81 | public boolean equals(Object o) { 82 | if (this == o) return true; 83 | if (o == null || getClass() != o.getClass()) return false; 84 | 85 | User user = (User) o; 86 | 87 | if (login != null ? !login.equals(user.login) : user.login != null) return false; 88 | if (firstname != null ? !firstname.equals(user.firstname) : user.firstname != null) 89 | return false; 90 | if (lastname != null ? !lastname.equals(user.lastname) : user.lastname != null) 91 | return false; 92 | return description != null ? description.equals(user.description) : user.description == null; 93 | } 94 | 95 | @Override 96 | public int hashCode() { 97 | int result = login != null ? login.hashCode() : 0; 98 | result = 31 * result + (firstname != null ? firstname.hashCode() : 0); 99 | result = 31 * result + (lastname != null ? lastname.hashCode() : 0); 100 | result = 31 * result + (description != null ? description.hashCode() : 0); 101 | return result; 102 | } 103 | 104 | @Override 105 | public String toString() { 106 | return "User{" + 107 | "login='" + login + '\'' + 108 | ", firstname='" + firstname + '\'' + 109 | ", lastname='" + lastname + '\'' + 110 | ", description='" + description + '\'' + 111 | '}'; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/io/spring/deepdive/ArticleJsonApiTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.spring.deepdive; 17 | 18 | import java.time.LocalDateTime; 19 | import java.util.List; 20 | 21 | import io.spring.deepdive.model.Article; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | 25 | import org.springframework.beans.factory.annotation.Autowired; 26 | import org.springframework.boot.test.context.SpringBootTest; 27 | import org.springframework.boot.test.web.client.TestRestTemplate; 28 | import org.springframework.core.ParameterizedTypeReference; 29 | import org.springframework.http.HttpMethod; 30 | import org.springframework.http.HttpStatus; 31 | import org.springframework.http.ResponseEntity; 32 | import org.springframework.test.context.junit4.SpringRunner; 33 | import org.springframework.web.client.HttpServerErrorException; 34 | 35 | import static org.assertj.core.api.Assertions.assertThat; 36 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 37 | import static org.springframework.boot.test.context.SpringBootTest.*; 38 | 39 | @RunWith(SpringRunner.class) 40 | @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) 41 | public class ArticleJsonApiTests { 42 | 43 | @Autowired 44 | private TestRestTemplate restTemplate; 45 | 46 | @Test 47 | public void assertFindAllJsonApiIsParsedCorrectlyAndContains3Elements() { 48 | List
articles = restTemplate.exchange("/api/article/", HttpMethod.GET, null, new ParameterizedTypeReference>() {}).getBody(); 49 | assertThat(articles).hasSize(3); 50 | } 51 | 52 | @Test 53 | public void verifyFindOneJsonApi() { 54 | Article article = restTemplate.getForObject("/api/article/reactor-bismuth-is-out", Article.class); 55 | assertThat(article.getTitle()).isEqualTo("Reactor Bismuth is out"); 56 | assertThat(article.getHeadline()).startsWith("It is my great pleasure to"); 57 | assertThat(article.getContent()).startsWith("With the release of"); 58 | assertThat(article.getAddedAt()).isEqualTo(LocalDateTime.of(2017, 9, 28, 12, 00)); 59 | assertThat(article.getAuthor().getFirstname()).isEqualTo("Simon"); 60 | } 61 | 62 | @Test 63 | public void verifyFindOneJsonApiWithMarkdownConverter() { 64 | Article article = restTemplate.getForObject("/api/article/reactor-bismuth-is-out?converter=markdown", Article.class); 65 | assertThat(article.getTitle()).startsWith("Reactor Bismuth is out"); 66 | assertThat(article.getHeadline()).doesNotContain("**3.1.0.RELEASE**").contains("3.1.0.RELEASE"); 67 | assertThat(article.getContent()).doesNotContain("[Spring Framework 5.0](https://spring.io/blog/2017/09/28/spring-framework-5-0-goes-ga)").contains(""); 68 | assertThat(article.getAddedAt()).isEqualTo(LocalDateTime.of(2017, 9, 28, 12, 00)); 69 | assertThat(article.getAuthor().getFirstname()).isEqualTo("Simon"); 70 | } 71 | 72 | @Test 73 | public void verifyFindOneJsonApiWithInvalidConverter() { 74 | ResponseEntity entity = restTemplate.getForEntity("/api/article/reactor-bismuth-is-out?converter=foo", String.class); 75 | assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); 76 | assertThat(entity.getBody()).contains("Only markdown converter is supported"); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/io/spring/deepdive/model/Article.java: -------------------------------------------------------------------------------- 1 | package io.spring.deepdive.model; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | import org.springframework.data.annotation.Id; 6 | import org.springframework.data.mongodb.core.mapping.DBRef; 7 | import org.springframework.data.mongodb.core.mapping.Document; 8 | 9 | @Document 10 | public class Article { 11 | 12 | @Id 13 | private String slug; 14 | 15 | private String title; 16 | 17 | private String headline; 18 | 19 | private String content; 20 | 21 | @DBRef 22 | private User author; 23 | 24 | private LocalDateTime addedAt; 25 | 26 | 27 | public Article() { 28 | } 29 | 30 | public Article(String slug, String title, String headline, String content, User author) { 31 | this(slug, title, headline, content, author, LocalDateTime.now()); 32 | } 33 | 34 | public Article(String slug, String title, String headline, String content, User author, LocalDateTime addedAt) { 35 | this.slug = slug; 36 | this.title = title; 37 | this.headline = headline; 38 | this.content = content; 39 | this.author = author; 40 | this.addedAt = addedAt; 41 | } 42 | 43 | public String getSlug() { 44 | return slug; 45 | } 46 | 47 | public void setSlug(String slug) { 48 | this.slug = slug; 49 | } 50 | 51 | public String getTitle() { 52 | return title; 53 | } 54 | 55 | public void setTitle(String title) { 56 | this.title = title; 57 | } 58 | 59 | public LocalDateTime getAddedAt() { 60 | return addedAt; 61 | } 62 | 63 | public void setHeadline(String headline) { 64 | this.headline = headline; 65 | } 66 | 67 | public String getContent() { 68 | return content; 69 | } 70 | 71 | public void setContent(String content) { 72 | this.content = content; 73 | } 74 | 75 | public User getAuthor() { 76 | return author; 77 | } 78 | 79 | public void setAuthor(User author) { 80 | this.author = author; 81 | } 82 | 83 | public void setAddedAt(LocalDateTime addedAt) { 84 | this.addedAt = addedAt; 85 | } 86 | 87 | public String getHeadline() { 88 | return headline; 89 | } 90 | 91 | @Override 92 | public boolean equals(Object o) { 93 | if (this == o) return true; 94 | if (o == null || getClass() != o.getClass()) return false; 95 | 96 | Article article = (Article) o; 97 | 98 | if (slug != null ? !slug.equals(article.slug) : article.slug != null) return false; 99 | if (title != null ? !title.equals(article.title) : article.title != null) return false; 100 | if (headline != null ? !headline.equals(article.headline) : article.headline != null) 101 | return false; 102 | if (content != null ? !content.equals(article.content) : article.content != null) 103 | return false; 104 | if (author != null ? !author.equals(article.author) : article.author != null) 105 | return false; 106 | return addedAt != null ? addedAt.equals(article.addedAt) : article.addedAt == null; 107 | } 108 | 109 | @Override 110 | public int hashCode() { 111 | int result = slug != null ? slug.hashCode() : 0; 112 | result = 31 * result + (title != null ? title.hashCode() : 0); 113 | result = 31 * result + (headline != null ? headline.hashCode() : 0); 114 | result = 31 * result + (content != null ? content.hashCode() : 0); 115 | result = 31 * result + (author != null ? author.hashCode() : 0); 116 | result = 31 * result + (addedAt != null ? addedAt.hashCode() : 0); 117 | return result; 118 | } 119 | 120 | @Override 121 | public String toString() { 122 | return "Post{" + 123 | "slug='" + slug + '\'' + 124 | ", title='" + title + '\'' + 125 | ", headline='" + headline + '\'' + 126 | ", content='" + content + '\'' + 127 | ", author=" + author + 128 | ", addedAt=" + addedAt + 129 | '}'; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/main/java/io/spring/deepdive/DatabaseInitializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.spring.deepdive; 17 | 18 | 19 | import java.time.LocalDateTime; 20 | import java.util.Arrays; 21 | 22 | import io.spring.deepdive.model.Article; 23 | import io.spring.deepdive.model.User; 24 | import io.spring.deepdive.repository.ArticleRepository; 25 | import io.spring.deepdive.repository.UserRepository; 26 | 27 | import org.springframework.boot.CommandLineRunner; 28 | import org.springframework.stereotype.Component; 29 | 30 | @Component 31 | public class DatabaseInitializer implements CommandLineRunner { 32 | 33 | private final UserRepository userRepository; 34 | 35 | private final ArticleRepository articleRepository; 36 | 37 | public DatabaseInitializer(UserRepository userRepository, ArticleRepository articleRepository) { 38 | this.userRepository = userRepository; 39 | this.articleRepository = articleRepository; 40 | } 41 | 42 | @Override 43 | public void run(String... strings) throws Exception { 44 | 45 | User brian = new User("bclozel", "Brian", "Clozel", "Spring Framework & Spring Boot @pivotal — @LaCordeeLyon coworker"); 46 | 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"); 47 | User arjen = new User("poutsma", "Arjen", "Poutsma"); 48 | User rossen = new User("rstoyanchev", "Rossen", "Stoyanchev", "Spring Framework committer @Pivotal"); 49 | 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."); 50 | User seb = new User("sdeleuze", "Sebastien", "Deleuze", "Spring Framework committer @Pivotal, @Kotlin addict, #WebAssembly believer, @mixitconf organizer, #techactivism"); 51 | User simon = new User("simonbasle", "Simon", "Basle", "software development aficionado, Reactor Software Engineer @pivotal"); 52 | User stephanem = new User("smaldini", "Stephane", "Maldini", "Project Reactor Lead @Pivotal -All things Reactive and Distributed - ex Londoner - opinions != Pivotal"); 53 | User stephanen = new User("snicoll", "Stephane", "Nicoll", "Proud husband. Passionate and enthusiastic Software engineer. Working on @springboot, @springframework & Spring Initializr at @Pivotal"); 54 | User juergen = new User("springjuergen", "Juergen", "Hoeller"); 55 | User violeta = new User("violetagg", "Violeta", "Georgieva", "All views are my own!"); 56 | 57 | this.userRepository.save(Arrays.asList(brian, mark, arjen, rossen, sam, seb, simon, stephanem, stephanen, juergen, violeta)); 58 | 59 | String reactorTitle = "Reactor Bismuth is out"; 60 | Article reactorArticle = new Article( 61 | Utils.slugify(reactorTitle), 62 | reactorTitle, 63 | "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", 64 | "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", 65 | simon, 66 | LocalDateTime.of(2017, 9, 28, 12, 0) 67 | ); 68 | 69 | String springTitle = "Spring Framework 5.0 goes GA"; 70 | Article spring5Article = new Article( 71 | Utils.slugify(springTitle), 72 | springTitle, 73 | "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!", 74 | "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.", 75 | juergen, 76 | LocalDateTime.of(2017, 9, 28, 11, 30) 77 | ); 78 | 79 | String springKotlinTitle = "Introducing Kotlin support in Spring Framework 5.0"; 80 | Article springKotlinArticle = new Article( 81 | Utils.slugify(springKotlinTitle), 82 | springKotlinTitle, 83 | "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.", 84 | "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.", 85 | seb, 86 | LocalDateTime.of(2017, 1, 4, 9, 0) 87 | ); 88 | 89 | this.articleRepository.save(Arrays.asList(reactorArticle, spring5Article, springKotlinArticle)); 90 | 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/main/resources/static/assets/grids-responsive-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v1.0.0 3 | Copyright 2013 Yahoo! 4 | Licensed under the BSD License. 5 | https://github.com/yahoo/pure/blob/master/LICENSE.md 6 | */ 7 | @media screen and (min-width:35.5em){.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-1-12,.pure-u-sm-1-2,.pure-u-sm-1-24,.pure-u-sm-1-3,.pure-u-sm-1-4,.pure-u-sm-1-5,.pure-u-sm-1-6,.pure-u-sm-1-8,.pure-u-sm-10-24,.pure-u-sm-11-12,.pure-u-sm-11-24,.pure-u-sm-12-24,.pure-u-sm-13-24,.pure-u-sm-14-24,.pure-u-sm-15-24,.pure-u-sm-16-24,.pure-u-sm-17-24,.pure-u-sm-18-24,.pure-u-sm-19-24,.pure-u-sm-2-24,.pure-u-sm-2-3,.pure-u-sm-2-5,.pure-u-sm-20-24,.pure-u-sm-21-24,.pure-u-sm-22-24,.pure-u-sm-23-24,.pure-u-sm-24-24,.pure-u-sm-3-24,.pure-u-sm-3-4,.pure-u-sm-3-5,.pure-u-sm-3-8,.pure-u-sm-4-24,.pure-u-sm-4-5,.pure-u-sm-5-12,.pure-u-sm-5-24,.pure-u-sm-5-5,.pure-u-sm-5-6,.pure-u-sm-5-8,.pure-u-sm-6-24,.pure-u-sm-7-12,.pure-u-sm-7-24,.pure-u-sm-7-8,.pure-u-sm-8-24,.pure-u-sm-9-24{display:inline-block;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-sm-1-24{width:4.1667%}.pure-u-sm-1-12,.pure-u-sm-2-24{width:8.3333%}.pure-u-sm-1-8,.pure-u-sm-3-24{width:12.5%}.pure-u-sm-1-6,.pure-u-sm-4-24{width:16.6667%}.pure-u-sm-1-5{width:20%}.pure-u-sm-5-24{width:20.8333%}.pure-u-sm-1-4,.pure-u-sm-6-24{width:25%}.pure-u-sm-7-24{width:29.1667%}.pure-u-sm-1-3,.pure-u-sm-8-24{width:33.3333%}.pure-u-sm-3-8,.pure-u-sm-9-24{width:37.5%}.pure-u-sm-2-5{width:40%}.pure-u-sm-10-24,.pure-u-sm-5-12{width:41.6667%}.pure-u-sm-11-24{width:45.8333%}.pure-u-sm-1-2,.pure-u-sm-12-24{width:50%}.pure-u-sm-13-24{width:54.1667%}.pure-u-sm-14-24,.pure-u-sm-7-12{width:58.3333%}.pure-u-sm-3-5{width:60%}.pure-u-sm-15-24,.pure-u-sm-5-8{width:62.5%}.pure-u-sm-16-24,.pure-u-sm-2-3{width:66.6667%}.pure-u-sm-17-24{width:70.8333%}.pure-u-sm-18-24,.pure-u-sm-3-4{width:75%}.pure-u-sm-19-24{width:79.1667%}.pure-u-sm-4-5{width:80%}.pure-u-sm-20-24,.pure-u-sm-5-6{width:83.3333%}.pure-u-sm-21-24,.pure-u-sm-7-8{width:87.5%}.pure-u-sm-11-12,.pure-u-sm-22-24{width:91.6667%}.pure-u-sm-23-24{width:95.8333%}.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-24-24,.pure-u-sm-5-5{width:100%}}@media screen and (min-width:48em){.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-1-12,.pure-u-md-1-2,.pure-u-md-1-24,.pure-u-md-1-3,.pure-u-md-1-4,.pure-u-md-1-5,.pure-u-md-1-6,.pure-u-md-1-8,.pure-u-md-10-24,.pure-u-md-11-12,.pure-u-md-11-24,.pure-u-md-12-24,.pure-u-md-13-24,.pure-u-md-14-24,.pure-u-md-15-24,.pure-u-md-16-24,.pure-u-md-17-24,.pure-u-md-18-24,.pure-u-md-19-24,.pure-u-md-2-24,.pure-u-md-2-3,.pure-u-md-2-5,.pure-u-md-20-24,.pure-u-md-21-24,.pure-u-md-22-24,.pure-u-md-23-24,.pure-u-md-24-24,.pure-u-md-3-24,.pure-u-md-3-4,.pure-u-md-3-5,.pure-u-md-3-8,.pure-u-md-4-24,.pure-u-md-4-5,.pure-u-md-5-12,.pure-u-md-5-24,.pure-u-md-5-5,.pure-u-md-5-6,.pure-u-md-5-8,.pure-u-md-6-24,.pure-u-md-7-12,.pure-u-md-7-24,.pure-u-md-7-8,.pure-u-md-8-24,.pure-u-md-9-24{display:inline-block;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-md-1-24{width:4.1667%}.pure-u-md-1-12,.pure-u-md-2-24{width:8.3333%}.pure-u-md-1-8,.pure-u-md-3-24{width:12.5%}.pure-u-md-1-6,.pure-u-md-4-24{width:16.6667%}.pure-u-md-1-5{width:20%}.pure-u-md-5-24{width:20.8333%}.pure-u-md-1-4,.pure-u-md-6-24{width:25%}.pure-u-md-7-24{width:29.1667%}.pure-u-md-1-3,.pure-u-md-8-24{width:33.3333%}.pure-u-md-3-8,.pure-u-md-9-24{width:37.5%}.pure-u-md-2-5{width:40%}.pure-u-md-10-24,.pure-u-md-5-12{width:41.6667%}.pure-u-md-11-24{width:45.8333%}.pure-u-md-1-2,.pure-u-md-12-24{width:50%}.pure-u-md-13-24{width:54.1667%}.pure-u-md-14-24,.pure-u-md-7-12{width:58.3333%}.pure-u-md-3-5{width:60%}.pure-u-md-15-24,.pure-u-md-5-8{width:62.5%}.pure-u-md-16-24,.pure-u-md-2-3{width:66.6667%}.pure-u-md-17-24{width:70.8333%}.pure-u-md-18-24,.pure-u-md-3-4{width:75%}.pure-u-md-19-24{width:79.1667%}.pure-u-md-4-5{width:80%}.pure-u-md-20-24,.pure-u-md-5-6{width:83.3333%}.pure-u-md-21-24,.pure-u-md-7-8{width:87.5%}.pure-u-md-11-12,.pure-u-md-22-24{width:91.6667%}.pure-u-md-23-24{width:95.8333%}.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-24-24,.pure-u-md-5-5{width:100%}}@media screen and (min-width:64em){.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-1-12,.pure-u-lg-1-2,.pure-u-lg-1-24,.pure-u-lg-1-3,.pure-u-lg-1-4,.pure-u-lg-1-5,.pure-u-lg-1-6,.pure-u-lg-1-8,.pure-u-lg-10-24,.pure-u-lg-11-12,.pure-u-lg-11-24,.pure-u-lg-12-24,.pure-u-lg-13-24,.pure-u-lg-14-24,.pure-u-lg-15-24,.pure-u-lg-16-24,.pure-u-lg-17-24,.pure-u-lg-18-24,.pure-u-lg-19-24,.pure-u-lg-2-24,.pure-u-lg-2-3,.pure-u-lg-2-5,.pure-u-lg-20-24,.pure-u-lg-21-24,.pure-u-lg-22-24,.pure-u-lg-23-24,.pure-u-lg-24-24,.pure-u-lg-3-24,.pure-u-lg-3-4,.pure-u-lg-3-5,.pure-u-lg-3-8,.pure-u-lg-4-24,.pure-u-lg-4-5,.pure-u-lg-5-12,.pure-u-lg-5-24,.pure-u-lg-5-5,.pure-u-lg-5-6,.pure-u-lg-5-8,.pure-u-lg-6-24,.pure-u-lg-7-12,.pure-u-lg-7-24,.pure-u-lg-7-8,.pure-u-lg-8-24,.pure-u-lg-9-24{display:inline-block;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-lg-1-24{width:4.1667%}.pure-u-lg-1-12,.pure-u-lg-2-24{width:8.3333%}.pure-u-lg-1-8,.pure-u-lg-3-24{width:12.5%}.pure-u-lg-1-6,.pure-u-lg-4-24{width:16.6667%}.pure-u-lg-1-5{width:20%}.pure-u-lg-5-24{width:20.8333%}.pure-u-lg-1-4,.pure-u-lg-6-24{width:25%}.pure-u-lg-7-24{width:29.1667%}.pure-u-lg-1-3,.pure-u-lg-8-24{width:33.3333%}.pure-u-lg-3-8,.pure-u-lg-9-24{width:37.5%}.pure-u-lg-2-5{width:40%}.pure-u-lg-10-24,.pure-u-lg-5-12{width:41.6667%}.pure-u-lg-11-24{width:45.8333%}.pure-u-lg-1-2,.pure-u-lg-12-24{width:50%}.pure-u-lg-13-24{width:54.1667%}.pure-u-lg-14-24,.pure-u-lg-7-12{width:58.3333%}.pure-u-lg-3-5{width:60%}.pure-u-lg-15-24,.pure-u-lg-5-8{width:62.5%}.pure-u-lg-16-24,.pure-u-lg-2-3{width:66.6667%}.pure-u-lg-17-24{width:70.8333%}.pure-u-lg-18-24,.pure-u-lg-3-4{width:75%}.pure-u-lg-19-24{width:79.1667%}.pure-u-lg-4-5{width:80%}.pure-u-lg-20-24,.pure-u-lg-5-6{width:83.3333%}.pure-u-lg-21-24,.pure-u-lg-7-8{width:87.5%}.pure-u-lg-11-12,.pure-u-lg-22-24{width:91.6667%}.pure-u-lg-23-24{width:95.8333%}.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-24-24,.pure-u-lg-5-5{width:100%}}@media screen and (min-width:80em){.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-1-12,.pure-u-xl-1-2,.pure-u-xl-1-24,.pure-u-xl-1-3,.pure-u-xl-1-4,.pure-u-xl-1-5,.pure-u-xl-1-6,.pure-u-xl-1-8,.pure-u-xl-10-24,.pure-u-xl-11-12,.pure-u-xl-11-24,.pure-u-xl-12-24,.pure-u-xl-13-24,.pure-u-xl-14-24,.pure-u-xl-15-24,.pure-u-xl-16-24,.pure-u-xl-17-24,.pure-u-xl-18-24,.pure-u-xl-19-24,.pure-u-xl-2-24,.pure-u-xl-2-3,.pure-u-xl-2-5,.pure-u-xl-20-24,.pure-u-xl-21-24,.pure-u-xl-22-24,.pure-u-xl-23-24,.pure-u-xl-24-24,.pure-u-xl-3-24,.pure-u-xl-3-4,.pure-u-xl-3-5,.pure-u-xl-3-8,.pure-u-xl-4-24,.pure-u-xl-4-5,.pure-u-xl-5-12,.pure-u-xl-5-24,.pure-u-xl-5-5,.pure-u-xl-5-6,.pure-u-xl-5-8,.pure-u-xl-6-24,.pure-u-xl-7-12,.pure-u-xl-7-24,.pure-u-xl-7-8,.pure-u-xl-8-24,.pure-u-xl-9-24{display:inline-block;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-xl-1-24{width:4.1667%}.pure-u-xl-1-12,.pure-u-xl-2-24{width:8.3333%}.pure-u-xl-1-8,.pure-u-xl-3-24{width:12.5%}.pure-u-xl-1-6,.pure-u-xl-4-24{width:16.6667%}.pure-u-xl-1-5{width:20%}.pure-u-xl-5-24{width:20.8333%}.pure-u-xl-1-4,.pure-u-xl-6-24{width:25%}.pure-u-xl-7-24{width:29.1667%}.pure-u-xl-1-3,.pure-u-xl-8-24{width:33.3333%}.pure-u-xl-3-8,.pure-u-xl-9-24{width:37.5%}.pure-u-xl-2-5{width:40%}.pure-u-xl-10-24,.pure-u-xl-5-12{width:41.6667%}.pure-u-xl-11-24{width:45.8333%}.pure-u-xl-1-2,.pure-u-xl-12-24{width:50%}.pure-u-xl-13-24{width:54.1667%}.pure-u-xl-14-24,.pure-u-xl-7-12{width:58.3333%}.pure-u-xl-3-5{width:60%}.pure-u-xl-15-24,.pure-u-xl-5-8{width:62.5%}.pure-u-xl-16-24,.pure-u-xl-2-3{width:66.6667%}.pure-u-xl-17-24{width:70.8333%}.pure-u-xl-18-24,.pure-u-xl-3-4{width:75%}.pure-u-xl-19-24{width:79.1667%}.pure-u-xl-4-5{width:80%}.pure-u-xl-20-24,.pure-u-xl-5-6{width:83.3333%}.pure-u-xl-21-24,.pure-u-xl-7-8{width:87.5%}.pure-u-xl-11-12,.pure-u-xl-22-24{width:91.6667%}.pure-u-xl-23-24{width:95.8333%}.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-24-24,.pure-u-xl-5-5{width:100%}} -------------------------------------------------------------------------------- /src/main/resources/static/assets/pure-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v1.0.0 3 | Copyright 2013 Yahoo! 4 | Licensed under the BSD License. 5 | https://github.com/yahoo/pure/blob/master/LICENSE.md 6 | */ 7 | /*! 8 | normalize.css v^3.0 | MIT License | git.io/normalize 9 | Copyright (c) Nicolas Gallagher and Jonathan Neal 10 | */ 11 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */.pure-button:focus,a:active,a:hover{outline:0}.pure-table,table{border-collapse:collapse;border-spacing:0}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}abbr[title]{border-bottom:1px dotted}b,optgroup,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre,textarea{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}.pure-button,input{line-height:normal}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}.pure-button,.pure-form input:not([type]),.pure-menu{box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend,td,th{padding:0}legend{border:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){table .pure-g{display:block}}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u,.pure-u-1,.pure-u-1-1,.pure-u-1-12,.pure-u-1-2,.pure-u-1-24,.pure-u-1-3,.pure-u-1-4,.pure-u-1-5,.pure-u-1-6,.pure-u-1-8,.pure-u-10-24,.pure-u-11-12,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-2-24,.pure-u-2-3,.pure-u-2-5,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24,.pure-u-3-24,.pure-u-3-4,.pure-u-3-5,.pure-u-3-8,.pure-u-4-24,.pure-u-4-5,.pure-u-5-12,.pure-u-5-24,.pure-u-5-5,.pure-u-5-6,.pure-u-5-8,.pure-u-6-24,.pure-u-7-12,.pure-u-7-24,.pure-u-7-8,.pure-u-8-24,.pure-u-9-24{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;display:inline-block;zoom:1}.pure-g [class*=pure-u]{font-family:sans-serif}.pure-u-1-24{width:4.1667%}.pure-u-1-12,.pure-u-2-24{width:8.3333%}.pure-u-1-8,.pure-u-3-24{width:12.5%}.pure-u-1-6,.pure-u-4-24{width:16.6667%}.pure-u-1-5{width:20%}.pure-u-5-24{width:20.8333%}.pure-u-1-4,.pure-u-6-24{width:25%}.pure-u-7-24{width:29.1667%}.pure-u-1-3,.pure-u-8-24{width:33.3333%}.pure-u-3-8,.pure-u-9-24{width:37.5%}.pure-u-2-5{width:40%}.pure-u-10-24,.pure-u-5-12{width:41.6667%}.pure-u-11-24{width:45.8333%}.pure-u-1-2,.pure-u-12-24{width:50%}.pure-u-13-24{width:54.1667%}.pure-u-14-24,.pure-u-7-12{width:58.3333%}.pure-u-3-5{width:60%}.pure-u-15-24,.pure-u-5-8{width:62.5%}.pure-u-16-24,.pure-u-2-3{width:66.6667%}.pure-u-17-24{width:70.8333%}.pure-u-18-24,.pure-u-3-4{width:75%}.pure-u-19-24{width:79.1667%}.pure-u-4-5{width:80%}.pure-u-20-24,.pure-u-5-6{width:83.3333%}.pure-u-21-24,.pure-u-7-8{width:87.5%}.pure-u-11-12,.pure-u-22-24{width:91.6667%}.pure-u-23-24{width:95.8333%}.pure-u-1,.pure-u-1-1,.pure-u-24-24,.pure-u-5-5{width:100%}.pure-button{display:inline-block;zoom:1;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-group{letter-spacing:-.31em;text-rendering:optimizespeed}.opera-only :-o-prefocus,.pure-button-group{word-spacing:-.43em}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);border:1px solid #999;border:transparent;background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:focus,.pure-button:hover{filter:alpha(opacity=90);background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000\9}.pure-button-disabled,.pure-button-disabled:active,.pure-button-disabled:focus,.pure-button-disabled:hover,.pure-button[disabled]{border:none;background-image:none;filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none;pointer-events:none}.pure-button-hidden{display:none}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-button-group .pure-button{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;margin:0;border-radius:0;border-right:1px solid #111;border-right:1px solid rgba(0,0,0,.2)}.pure-button-group .pure-button:first-child{border-top-left-radius:2px;border-bottom-left-radius:2px}.pure-button-group .pure-button:last-child{border-top-right-radius:2px;border-bottom-right-radius:2px;border-right:none}.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=tel],.pure-form input[type=color],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=text],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px}.pure-form input[type=color]{padding:.2em .5em}.pure-form input:not([type]):focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=text]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=checkbox]:focus,.pure-form input[type=radio]:focus{outline:#129FEA auto 1px}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input:not([type])[disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=text][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form select:focus:invalid,.pure-form textarea:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input:not([type]),.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked input[type=file],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=text],.pure-form-stacked label,.pure-form-stacked select,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-aligned .pure-help-inline,.pure-form-aligned input,.pure-form-aligned select,.pure-form-aligned textarea,.pure-form-message-inline{display:inline-block;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form .pure-input-rounded,.pure-form input.pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-3-4{width:75%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=tel],.pure-form input[type=color],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=text],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=tel],.pure-group input[type=color],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=text]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message,.pure-form-message-inline{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-item,.pure-menu-list{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-heading,.pure-menu-link{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-separator{display:inline-block;zoom:1;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-active>.pure-menu-children,.pure-menu-allow-hover:hover>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;padding:.5em 0}.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar{display:none}.pure-menu-horizontal .pure-menu-children .pure-menu-separator,.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-horizontal .pure-menu-children .pure-menu-separator{display:block;width:auto}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-disabled,.pure-menu-heading,.pure-menu-link{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent}.pure-menu-active>.pure-menu-link,.pure-menu-link:focus,.pure-menu-link:hover{background-color:#eee}.pure-menu-selected .pure-menu-link,.pure-menu-selected .pure-menu-link:visited{color:#000}.pure-table{empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td,.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0} --------------------------------------------------------------------------------