diff --git a/.gitignore b/.gitignore index 57f1cb2..171f386 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -/.idea/ \ No newline at end of file +/.idea/ +/target/ +/build/ +/.gradle/ diff --git a/README.md b/README.md index 2b09400..a78ec74 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # springboot-java8 -The project is made on spring boot. The project summarize the new features present in Java 8. +The project is made on Spring Boot 3.3 and Java 21. It summarizes the functional-programming features introduced in Java 8. It contain list of harcoded topics list. You can call the apis's with POSTMAN to add,delete,update Topic list In addition, it uses 1) Java 8 NIO methods diff --git a/build.gradle b/build.gradle index 09f1083..68d6697 100644 --- a/build.gradle +++ b/build.gradle @@ -1,32 +1,35 @@ -buildscript { - repositories { - mavenCentral() - } - dependencies { - classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.2.RELEASE") - } +plugins { + id 'java' + id 'eclipse' + id 'idea' + id 'org.springframework.boot' version '3.3.4' + id 'io.spring.dependency-management' version '1.1.6' } -apply plugin: 'java' -apply plugin: 'eclipse' -apply plugin: 'idea' -apply plugin: 'org.springframework.boot' -apply plugin: 'io.spring.dependency-management' +group = 'org.springframework' +version = '0.1.0' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} bootJar { - baseName = 'gs-spring-boot' - version = '0.1.0' + archiveBaseName = 'gs-spring-boot' } repositories { mavenCentral() } -sourceCompatibility = 1.8 -targetCompatibility = 1.8 - dependencies { - compile("org.springframework.boot:spring-boot-starter-web") - testCompile("junit:junit") + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-jdbc' + runtimeOnly 'com.h2database:h2' + testImplementation 'org.springframework.boot:spring-boot-starter-test' } +tasks.named('test') { + useJUnitPlatform() +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a2ff3cc..0a55694 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip diff --git a/pom.xml b/pom.xml index 63f5cbd..77504fc 100644 --- a/pom.xml +++ b/pom.xml @@ -5,21 +5,17 @@ org.springframework gs-spring-boot - pom + jar 0.1.0 org.springframework.boot spring-boot-starter-parent - 2.0.2.RELEASE + 3.3.4 + - - org.springframework.boot - spring-boot-properties-migrator - runtime - org.springframework.boot spring-boot-starter-web @@ -32,10 +28,15 @@ com.h2database h2 + + org.springframework.boot + spring-boot-starter-test + test + - 1.8 + 21 diff --git a/src/main/java/hello/Application.java b/src/main/java/hello/Application.java index 7cf8faf..f9ca78d 100644 --- a/src/main/java/hello/Application.java +++ b/src/main/java/hello/Application.java @@ -16,28 +16,26 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; @SpringBootApplication public class Application implements CommandLineRunner { private static final Logger log = LoggerFactory.getLogger(Application.class); + private static final String QUOTE_URL = "http://gturnquist-quoters.cfapps.io/api/random"; public static void main(String[] args) { ApplicationContext ctx = SpringApplication.run(Application.class, args); - + System.out.println("Let's inspect the beans provided by Spring Boot:"); - + String[] beanNames = ctx.getBeanDefinitionNames(); Arrays.sort(beanNames); for (String beanName : beanNames) { System.out.println(beanName); } - - RestTemplate restTemplate = new RestTemplate(); - Quote quote = restTemplate.getForObject("http://gturnquist-quoters.cfapps.io/api/random", Quote.class); - log.info(quote.toString()); } @@ -49,9 +47,12 @@ public RestTemplate restTemplate(RestTemplateBuilder builder) { @Bean public CommandLineRunner run(RestTemplate restTemplate) throws Exception { return args -> { - Quote quote = restTemplate.getForObject( - "http://gturnquist-quoters.cfapps.io/api/random", Quote.class); - log.info(quote.toString()); + try { + Quote quote = restTemplate.getForObject(QUOTE_URL, Quote.class); + log.info(String.valueOf(quote)); + } catch (RestClientException e) { + log.warn("Could not fetch quote from {}: {}", QUOTE_URL, e.getMessage()); + } }; } @@ -63,8 +64,8 @@ public CommandLineRunner run(RestTemplate restTemplate) throws Exception { public void run(String... args) throws Exception { log.info("Creating tables"); - jdbcTemplate.execute("DROP TABLE customers IF EXISTS"); - jdbcTemplate.execute("CREATE TABLE customers(id SERIAL, first_name VARCHAR(255), last_name VARCHAR(255))"); + jdbcTemplate.execute("DROP TABLE IF EXISTS customers"); + jdbcTemplate.execute("CREATE TABLE customers(id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, first_name VARCHAR(255), last_name VARCHAR(255))"); // Split up the array of whole names into an array of first/last names List splitUpNames = Arrays.asList("John Woo", "Jeff Dean", "Josh Bloch", "Josh Long") @@ -80,8 +81,9 @@ public void run(String... args) throws Exception { log.info("Querying for customer records where first_name = 'Josh':"); jdbcTemplate.query( - "SELECT id, first_name, last_name FROM customers WHERE first_name = ?", new Object[]{"Josh"}, - (rs, rowNum) -> new Customer(rs.getLong("id"), rs.getString("first_name"), rs.getString("last_name")) + "SELECT id, first_name, last_name FROM customers WHERE first_name = ?", + (rs, rowNum) -> new Customer(rs.getLong("id"), rs.getString("first_name"), rs.getString("last_name")), + "Josh" ).forEach(customer -> log.info(customer.toString())); } diff --git a/src/test/java/hello/ApplicationTests.java b/src/test/java/hello/ApplicationTests.java new file mode 100644 index 0000000..094b69e --- /dev/null +++ b/src/test/java/hello/ApplicationTests.java @@ -0,0 +1,12 @@ +package hello; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ApplicationTests { + + @Test + void contextLoads() { + } +} diff --git a/src/test/java/hello/controller/GreetingAndHelloControllerTest.java b/src/test/java/hello/controller/GreetingAndHelloControllerTest.java new file mode 100644 index 0000000..bcf453d --- /dev/null +++ b/src/test/java/hello/controller/GreetingAndHelloControllerTest.java @@ -0,0 +1,60 @@ +package hello.controller; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; + +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +class GreetingAndHelloControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Test + void greetingDefaultsToWorld() throws Exception { + mockMvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.content").value("Hello, World!")); + } + + @Test + void greetingUsesNameParam() throws Exception { + mockMvc.perform(get("/").param("name", "Devin")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.content").value("Hello, Devin!")); + } + + @Test + void datetimeEndpoint() throws Exception { + mockMvc.perform(get("/datetime")) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("Greetings from Spring Boot!"))) + .andExpect(content().string(containsString("Time in California:"))); + } + + @Test + void stringOperationEndpoint() throws Exception { + mockMvc.perform(get("/topic/string/operation")) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("spring:java:javascript"))) + .andExpect(content().string(containsString("java:javascript"))) + .andExpect(content().string(containsString("[spring]"))); + } + + @Test + void fileOperationEndpoint() throws Exception { + mockMvc.perform(get("/topic/file/operation")) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("Read \"temp.txt\""))) + .andExpect(content().string(containsString(" Hello, this, is, Rehman"))); + } +} diff --git a/src/test/java/hello/controller/TopicControllerTest.java b/src/test/java/hello/controller/TopicControllerTest.java new file mode 100644 index 0000000..490e14e --- /dev/null +++ b/src/test/java/hello/controller/TopicControllerTest.java @@ -0,0 +1,81 @@ +package hello.controller; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import static org.hamcrest.Matchers.hasSize; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +class TopicControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Test + void getAllTopicsReturnsSeededTopics() throws Exception { + mockMvc.perform(get("/topic")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(3))) + .andExpect(jsonPath("$[0].id").value("spring")); + } + + @Test + void getTopicById() throws Exception { + mockMvc.perform(get("/topic/java")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.subjectName").value("Core Java")); + } + + @Test + void addUpdateAndDeleteTopic() throws Exception { + mockMvc.perform(post("/topic") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"id\":\"kotlin\",\"subjectName\":\"Kotlin\",\"subjectDescription\":\"Kotlin Desc\"}")) + .andExpect(status().isOk()); + + mockMvc.perform(get("/topic/kotlin")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.subjectName").value("Kotlin")); + + mockMvc.perform(put("/topic/kotlin") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"id\":\"kotlin\",\"subjectName\":\"Kotlin 2\",\"subjectDescription\":\"Updated\"}")) + .andExpect(status().isOk()); + + mockMvc.perform(get("/topic/kotlin")) + .andExpect(jsonPath("$.subjectName").value("Kotlin 2")); + + mockMvc.perform(delete("/topic/kotlin")) + .andExpect(status().isOk()); + + mockMvc.perform(get("/topic")) + .andExpect(jsonPath("$", hasSize(3))); + } + + @Test + void filterByMinimumIdLength() throws Exception { + mockMvc.perform(get("/topic/minimum/length/5")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))); + } + + @Test + void sortTopicsById() throws Exception { + mockMvc.perform(get("/topic/sort")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value("java")) + .andExpect(jsonPath("$[1].id").value("javascript")) + .andExpect(jsonPath("$[2].id").value("spring")); + } +} diff --git a/src/test/java/hello/service/TopicServiceTest.java b/src/test/java/hello/service/TopicServiceTest.java new file mode 100644 index 0000000..2864319 --- /dev/null +++ b/src/test/java/hello/service/TopicServiceTest.java @@ -0,0 +1,64 @@ +package hello.service; + +import hello.model.Topic; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class TopicServiceTest { + + private TopicService service; + + @BeforeEach + void setUp() { + service = new TopicService(); + } + + @Test + void getTopicWithId() { + assertEquals("Spring Framework", service.getTopicWithId("spring").getSubjectName()); + } + + @Test + void updateTopicReplacesMatchingId() { + service.updateTopic("java", new Topic("java", "Java 21", "Modern Java")); + assertEquals("Java 21", service.getTopicWithId("java").getSubjectName()); + } + + @Test + void updateTopicIgnoresUnknownId() { + service.updateTopic("nope", new Topic("nope", "x", "y")); + assertEquals(3, service.getAllTopics().size()); + } + + @Test + void deleteTopic() { + service.deleteTopic("spring"); + assertEquals(2, service.getAllTopics().size()); + } + + @Test + void filterMinimumLengthForId() { + List result = service.filterMinimumLengthForId(4); + assertEquals(2, result.size()); + assertTrue(result.stream().allMatch(t -> t.getId().length() > 4)); + } + + @Test + void stringOperations() { + String join = service.returnAllTopicIDWithStringSlicing(); + assertEquals("spring:java:javascript", join); + assertEquals(":acgijnprstv", service.makeDistinctAndSortCharacters(join)); + assertEquals("java:javascript", service.splitAllIdWithColonSelectIDWithJavaKeywordThenSortThenJoin(join)); + assertEquals("[spring]", service.findIdHavingCharacter()); + } + + @Test + void readFileWithStreamFunction() { + assertEquals(" Hello, this, is, Rehman", service.readFileWithStreamFunction()); + } +} diff --git a/target/classes/hello/Application.class b/target/classes/hello/Application.class deleted file mode 100644 index 245a020..0000000 Binary files a/target/classes/hello/Application.class and /dev/null differ diff --git a/target/classes/hello/controller/GreetingController.class b/target/classes/hello/controller/GreetingController.class deleted file mode 100644 index 1149660..0000000 Binary files a/target/classes/hello/controller/GreetingController.class and /dev/null differ diff --git a/target/classes/hello/controller/HelloController.class b/target/classes/hello/controller/HelloController.class deleted file mode 100644 index 79b82ce..0000000 Binary files a/target/classes/hello/controller/HelloController.class and /dev/null differ diff --git a/target/classes/hello/controller/TopicController.class b/target/classes/hello/controller/TopicController.class deleted file mode 100644 index c32cad5..0000000 Binary files a/target/classes/hello/controller/TopicController.class and /dev/null differ diff --git a/target/classes/hello/declaration/CustomPredicate.class b/target/classes/hello/declaration/CustomPredicate.class deleted file mode 100644 index e35803f..0000000 Binary files a/target/classes/hello/declaration/CustomPredicate.class and /dev/null differ diff --git a/target/classes/hello/declaration/TimeClient.class b/target/classes/hello/declaration/TimeClient.class deleted file mode 100644 index a077c3b..0000000 Binary files a/target/classes/hello/declaration/TimeClient.class and /dev/null differ diff --git a/target/classes/hello/model/Customer.class b/target/classes/hello/model/Customer.class deleted file mode 100644 index 06e7307..0000000 Binary files a/target/classes/hello/model/Customer.class and /dev/null differ diff --git a/target/classes/hello/model/Greeting.class b/target/classes/hello/model/Greeting.class deleted file mode 100644 index f875277..0000000 Binary files a/target/classes/hello/model/Greeting.class and /dev/null differ diff --git a/target/classes/hello/model/Quote.class b/target/classes/hello/model/Quote.class deleted file mode 100644 index 3ec6c6c..0000000 Binary files a/target/classes/hello/model/Quote.class and /dev/null differ diff --git a/target/classes/hello/model/SimpleTimeClient.class b/target/classes/hello/model/SimpleTimeClient.class deleted file mode 100644 index f9bdddd..0000000 Binary files a/target/classes/hello/model/SimpleTimeClient.class and /dev/null differ diff --git a/target/classes/hello/model/Topic.class b/target/classes/hello/model/Topic.class deleted file mode 100644 index 3a53aa5..0000000 Binary files a/target/classes/hello/model/Topic.class and /dev/null differ diff --git a/target/classes/hello/model/Value.class b/target/classes/hello/model/Value.class deleted file mode 100644 index 862a68f..0000000 Binary files a/target/classes/hello/model/Value.class and /dev/null differ diff --git a/target/classes/hello/service/TopicService.class b/target/classes/hello/service/TopicService.class deleted file mode 100644 index b9f4992..0000000 Binary files a/target/classes/hello/service/TopicService.class and /dev/null differ diff --git a/target/classes/public/index.html b/target/classes/public/index.html deleted file mode 100644 index 566549b..0000000 --- a/target/classes/public/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Title - - - - - \ No newline at end of file