= ChuckNorris = * Test app that invokes a ChuckNorris random joke API == Structure == {{{#!highlight bash . ├── pom.xml ├── src │ └── main │ └── java │ ├── chucknorris │ │ └── bitarus │ │ └── allowed │ │ └── org │ │ ├── Application.java │ │ ├── ChuckNorrisController.java │ │ ├── Joke.java │ │ └── JokeResponse.java }}} === pom.xml === {{{#!highlight xml 4.0.0 bitarus.allowed.org chucknorris 0.1.0 org.springframework.boot spring-boot-starter-parent 2.1.6.RELEASE org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web com.google.code.gson gson 2.8.6 compile chucknorris.bitarus.allowed.org.Application org.springframework.boot spring-boot-maven-plugin spring-milestone http://repo.spring.io/libs-release spring-milestone http://repo.spring.io/libs-release }}} === Application.java === {{{#!highlight java package chucknorris.bitarus.allowed.org; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.ComponentScan; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @ComponentScan //scans for @Component beans @EnableAutoConfiguration public class Application { private static Logger logger; public static void main(String[] args) { logger = LoggerFactory.getLogger(Application.class); logger.info("Starting application"); SpringApplication.run(Application.class, args); } } }}} === ChuckNorrisController.java === {{{#!highlight java package chucknorris.bitarus.allowed.org; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.ui.Model; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.ResponseBody; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import org.springframework.http.HttpMethod; import com.google.gson.Gson; import java.util.stream.Stream; import java.util.stream.Collectors; import java.util.List; import java.util.ArrayList; @Controller public class ChuckNorrisController{ private final Logger logger = LoggerFactory.getLogger(ChuckNorrisController.class); public ChuckNorrisController(){ logger.info("ChuckNorrisController created"); } @RequestMapping("/chucknorris") @ResponseBody // http://localhost:8080/chucknorris public JokeResponse chucknorris() { String ret = ""; Gson gson = new Gson(); try { URL url = new URL("https://api.chucknorris.io/jokes/random"); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.connect(); logger.info( Integer.toString(connection.getResponseCode() )); try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"))) { StringBuilder response = new StringBuilder(); String responseLine = null; while ((responseLine = br.readLine()) != null) { response.append(responseLine.trim()); } Joke joke = gson.fromJson(response.toString(), Joke.class); List jokesList = new ArrayList(); jokesList.add(joke); List jokesList2 = jokesList.stream().map( item ->{item.setValue(item.getValue().toUpperCase()); return item;} ).collect(Collectors.toList()); joke = jokesList2.get(0); ret = joke.getValue(); } } catch(Exception ex){ logger.error("error",ex); } JokeResponse jr = new JokeResponse(); jr.setResponse(ret); return jr; } } }}} === Joke.java === {{{#!highlight java package chucknorris.bitarus.allowed.org; import com.google.gson.annotations.SerializedName; public class Joke{ @SerializedName("categories") private String[] categories; @SerializedName("created_at") private String createdAt; @SerializedName("icon_url") private String iconUrl; @SerializedName("id") private String id; @SerializedName("updated_at") private String updatedAt; @SerializedName("url") private String url; @SerializedName("value") private String value; public String[] getCategories(){ return this.categories; } public String getCreatedAt(){ return this.createdAt; } public String getIconUrl(){ return this.iconUrl; } public String getId(){ return this.id; } public String getUpdatedAt(){ return this.updatedAt; } public String getUrl(){ return this.url; } public String getValue(){ return this.value; } public void setCategories(String[] categories){ this.categories = categories; } public void setCreatedAt(String createdAt ){ this.createdAt = createdAt; } public void setIconUrl(String iconUrl){ this.iconUrl=iconUrl; } public void setId(String id){ this.id=id; } public void setUpdatedAt(String updatedAt){ this.updatedAt=updatedAt; } public void setUrl(String url){ this.url=url; } public void setValue(String value){ this.value=value; } } }}} === JokeResponse.java === {{{#!highlight java package chucknorris.bitarus.allowed.org; import com.google.gson.annotations.SerializedName; public class JokeResponse{ @SerializedName("response") private String response; public String getResponse(){ return this.response; } public void setResponse(String response){ this.response = response; } } }}} == curl call == {{{#!highlight bash curl https://api.chucknorris.io/jokes/random 2>/dev/null | json_reformat { "categories": [ ], "created_at": "2020-01-05 13:42:26.991637", "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png", "id": "H-fvHao6QmiQxunoX2llBg", "updated_at": "2020-01-05 13:42:26.991637", "url": "https://api.chucknorris.io/jokes/H-fvHao6QmiQxunoX2llBg", "value": "A doctor once tried to test Chuck Norris' reflexes by lightly tapping on his knee with a small wooden mallet. Needless to say, that doctor is now in orbit." } }}} == Example 0.2 == === Structure === {{{#!highlight sh . ├── pom.xml ├── src │   └── main │   ├── java │   │   └── com │   │   └── mooo │   │   └── bitarus │   │   └── chucknorris │   │   ├── Application.java │   │   ├── ChuckNorrisController.java │   │   ├── JokeEntity.java │   │   ├── Joke.java │   │   ├── JokeMapper.java │   │   ├── JokeProjection.java │   │   ├── JokeRepository.java │   │   ├── JokeResponse.java │   │   └── QueryDSLConfiguration.java │   └── resources │   └── application.yaml }}} === pom.xml === {{{#!highlight xml 4.0.0 com.mooo.bitarus chucknorris 0.2.0 org.springframework.boot spring-boot-starter-parent 3.2.5 org.mapstruct mapstruct ${mapstruct.version} org.springdoc springdoc-openapi-starter-webmvc-ui 2.5.0 org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-jpa org.hsqldb hsqldb 2.7.2 com.querydsl querydsl-jpa ${querydsl.version} jakarta com.querydsl querydsl-apt ${querydsl.version} jakarta 21 com.mooo.bitarus.chucknorris.Application 5.1.0 1.5.5.Final org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 3.11.0 org.mapstruct mapstruct-processor ${mapstruct.version} com.querydsl querydsl-apt ${querydsl.version} jakarta jakarta.persistence jakarta.persistence-api 3.1.0 }}} === src/main/resources/application.yaml === {{{#!highlight yaml server: port: 8181 chucknorris: url: https://api.chucknorris.io/jokes/random springdoc: swagger-ui: enabled: true # http://localhost:8181/swagger-ui/index.html # http://localhost:8181/v3/api-docs.yaml # https://docs.swagger.io/swagger-core/v2.0.0-RC3/apidocs/io/swagger/v3/oas/annotations/Operation.html spring: datasource: url: jdbc:hsqldb:mem:mydb username: sa password: sa }}} === src/main/java/com/mooo/bitarus/chucknorris/Application.java === {{{#!highlight java package com.mooo.bitarus.chucknorris; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.ComponentScan; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @ComponentScan //scans for @Component beans @EnableAutoConfiguration public class Application { private static Logger logger; public static void main(String[] args) { logger = LoggerFactory.getLogger(Application.class); logger.info("Starting application"); SpringApplication.run(Application.class, args); } } }}} === src/main/java/com/mooo/bitarus/chucknorris/ChuckNorrisController.java === {{{#!highlight java package com.mooo.bitarus.chucknorris; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.ui.Model; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.ResponseBody; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import org.springframework.http.HttpMethod; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.stream.Stream; import java.util.stream.Collectors; import java.util.List; import java.util.ArrayList; import org.springframework.beans.factory.annotation.Value; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import com.querydsl.jpa.impl.JPAQueryFactory; @Tag(name="CNJokeController") @Controller public class ChuckNorrisController{ private final Logger logger = LoggerFactory.getLogger(ChuckNorrisController.class); @Value("${chucknorris.url}") private String chuckNorrisURL; private ObjectMapper objectMapper; private JokeRepository repo; private JPAQueryFactory queryFactory; private JokeMapper jokeMapper; public ChuckNorrisController(JokeRepository repo, JPAQueryFactory queryFactory, JokeMapper jokeMapper){ logger.info("ChuckNorrisController created"); this.objectMapper = new ObjectMapper(); this.repo = repo; this.queryFactory = queryFactory; this.jokeMapper = jokeMapper; } @GetMapping("/chucknorris") @ResponseBody @Operation(summary="Gets joke", description="Gets a Chuck Norris random joke.") // http://localhost:8080/chucknorris public JokeResponse chucknorris() { String ret = ""; Joke joke=null; try { URL url = new URL(chuckNorrisURL); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.connect(); logger.info( Integer.toString(connection.getResponseCode() )); try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"))) { StringBuilder response = new StringBuilder(); String responseLine = null; while ((responseLine = br.readLine()) != null) { response.append(responseLine.trim()); } joke = this.objectMapper.readValue(response.toString(), Joke.class); List jokesList = new ArrayList(); jokesList.add(joke); List jokesList2 = jokesList.stream().map( item -> { item.setValue(item.getValue().toUpperCase()); return item; } ).toList(); joke = jokesList2.get(0); ret = joke.getValue(); JokeEntity je = new JokeEntity(); je.setJoke(joke.getValue()); JokeEntity saved = repo.saveAndFlush(je); logger.info( repo.count() + " " + saved ); } } catch(Exception ex){ logger.error("error",ex); } return jokeMapper.jokeToJokeResponse(joke); } @GetMapping("/getsavedjokes") @ResponseBody @Operation(summary="Gets saved jokes", description="Gets saved Chuck Norris jokes.") public List getSavedJokes() { QJokeEntity jokeEntity = QJokeEntity.jokeEntity; return queryFactory .select( new QJokeProjection( jokeEntity.id , jokeEntity.joke ) ) .from(jokeEntity) .fetch(); } @GetMapping("/getsavedjokebyid") @ResponseBody @Operation(summary="Get saved joke by id", description="Get saved Chuck Norris joke by id.") public JokeProjection getSavedJokeById(Long jokeId) { QJokeEntity jokeEntity = QJokeEntity.jokeEntity; List ret = queryFactory .select( new QJokeProjection( jokeEntity.id , jokeEntity.joke ) ) .from(jokeEntity) .where(jokeEntity.id.eq(jokeId)) .fetch(); if(ret.size() > 0) return ret.get(0); return null; } } }}} === src/main/java/com/mooo/bitarus/chucknorris/JokeEntity.java === {{{#!highlight java package com.mooo.bitarus.chucknorris; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @Entity public class JokeEntity{ @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; private String joke; public JokeEntity(){ } public String getJoke(){ return this.joke; } public void setJoke(String joke){ this.joke=joke; } public Long getId(){ return this.id; } public void setId(Long id){ this.id=id; } @Override public String toString(){ return String.format("", this.id, this.joke); } } }}} === src/main/java/com/mooo/bitarus/chucknorris/Joke.java === {{{#!highlight java package com.mooo.bitarus.chucknorris; // import com.fasterxml.jackson.annotation.JsonProperty // @JsonProperty("propInJSON") public class Joke{ private String[] categories; private String created_at; private String icon_url; private String id; private String updated_at; private String url; private String value; public String[] getCategories(){ return this.categories; } public String getCreated_at(){ return this.created_at; } public String getIcon_url(){ return this.icon_url; } public String getId(){ return this.id; } public String getUpdated_at(){ return this.updated_at; } public String getUrl(){ return this.url; } public String getValue(){ return this.value; } public void setCategories(String[] categories){ this.categories = categories; } public void setCreated_at(String createdAt){ this.created_at = created_at; } public void setIcon_url(String iconUrl){ this.icon_url=icon_url; } public void setId(String id){ this.id=id; } public void setUpdated_at(String updatedAt){ this.updated_at=updated_at; } public void setUrl(String url){ this.url=url; } public void setValue(String value){ this.value=value; } } }}} === src/main/java/com/mooo/bitarus/chucknorris/JokeMapper.java === {{{#!highlight java package com.mooo.bitarus.chucknorris; import org.mapstruct.Mapper; import org.mapstruct.Mapping; @Mapper(componentModel = "spring") public interface JokeMapper { @Mapping(source = "value" , target = "response") JokeResponse jokeToJokeResponse(Joke joke); @Mapping(source = "response", target = "value") Joke jokeResponseToJoke(JokeResponse jokeResponse); } }}} === src/main/java/com/mooo/bitarus/chucknorris/JokeProjection.java === {{{#!highlight java package com.mooo.bitarus.chucknorris; import com.querydsl.core.annotations.QueryProjection; public record JokeProjection(Long id, String joke) { @QueryProjection public JokeProjection { } } }}} === src/main/java/com/mooo/bitarus/chucknorris/JokeRepository.java === {{{#!highlight java package com.mooo.bitarus.chucknorris; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface JokeRepository extends JpaRepository{ } }}} === src/main/java/com/mooo/bitarus/chucknorris/JokeResponse.java === {{{#!highlight java package com.mooo.bitarus.chucknorris; public class JokeResponse{ private String response; public JokeResponse(){} public JokeResponse(String response){ this.response = response; } public String getResponse(){ return this.response; } public void setResponse(String response){ this.response = response; } } }}} === src/main/java/com/mooo/bitarus/chucknorris/QueryDSLConfiguration.java === {{{#!highlight java package com.mooo.bitarus.chucknorris; import org.springframework.context.annotation.Configuration; import jakarta.persistence.PersistenceContext; import org.springframework.context.annotation.Bean; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; @Configuration class QueryDslConfiguration { @PersistenceContext private EntityManager em; @Bean public JPAQueryFactory jpaQueryFactory() { return new JPAQueryFactory(em); } } }}}