├── .gitignore ├── 3scale.md ├── README.md ├── Stream Processing and Change Data Capture - A Story with Kafka and Debezium.pdf ├── assets ├── 3scale-config.png ├── apicast-template.png ├── architecture-overview.png ├── cdc-in-brownfield.png ├── chuck-demo.mp4 ├── chuck-demo.png ├── chuck-norris-facts-funko.jpg ├── process-chuck-rentals-sf-mapping.png └── rental-event-to-telegram-mapping.png ├── camel-k-routes ├── route-local.xml ├── route.xml ├── telegram-route.xml └── test.xml ├── camel-k.md ├── cdc-config.md ├── chuck-norris-facts-api-swagger.yaml ├── chuck-norris-facts-api.yaml ├── chuck-norris-facts-api ├── .gitignore ├── ReadMe.md ├── pom.xml └── src │ └── main │ ├── java │ └── nexgen │ │ ├── Application.java │ │ ├── MyTransformer.java │ │ └── RestConfigurator.java │ └── resources │ ├── api-definitions │ └── basic-api.yaml │ ├── application-dev.properties │ ├── application.properties │ ├── data │ └── facts.json │ ├── logback.xml │ ├── spring │ └── camel-context.xml │ └── static │ └── index.html ├── chuck-norris-filter-camel ├── .gitignore ├── ReadMe.md ├── pom.xml └── src │ ├── main │ ├── java │ │ └── nexgen │ │ │ ├── Application.java │ │ │ ├── MyTransformer.java │ │ │ └── RestConfigurator.java │ └── resources │ │ ├── api-definitions │ │ └── basic-api.yaml │ │ ├── application-dev.properties │ │ ├── application.properties │ │ ├── logback.xml │ │ ├── spring │ │ └── camel-context.xml │ │ └── static │ │ └── index.html │ └── test │ └── resources │ └── test.json ├── chuck-norris-filter-kstreams-quarkus ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── README.md ├── deployment.yaml ├── mvnw ├── mvnw.cmd ├── pom.xml ├── src │ ├── main │ │ ├── docker │ │ │ ├── Dockerfile.jvm │ │ │ ├── Dockerfile.multistage │ │ │ └── Dockerfile.native │ │ ├── java │ │ │ └── com │ │ │ │ └── github │ │ │ │ └── lbroudoux │ │ │ │ └── chucknorris │ │ │ │ └── filter │ │ │ │ ├── FilterConfig.java │ │ │ │ ├── JNIRegistrationFeature.java │ │ │ │ ├── TopologyProducer.java │ │ │ │ ├── model │ │ │ │ ├── Customer.java │ │ │ │ ├── CustomerRentalMovieAggregate.java │ │ │ │ ├── DefaultId.java │ │ │ │ ├── EventType.java │ │ │ │ ├── Movie.java │ │ │ │ └── Rental.java │ │ │ │ └── serdes │ │ │ │ ├── JsonHybridDeserializer.java │ │ │ │ ├── JsonPojoSerializer.java │ │ │ │ └── SerdeFactory.java │ │ └── resources │ │ │ ├── META-INF │ │ │ └── resources │ │ │ │ └── index.html │ │ │ └── application.properties │ └── test │ │ └── java │ │ └── com │ │ └── github │ │ └── lbroudoux │ │ └── chucknorris │ │ └── filter │ │ └── serdes │ │ └── JsonPojoSerializerTest.java └── topic.yaml ├── chuck-norris-filter-kstreams ├── .classpath ├── .project ├── .settings │ ├── org.eclipse.core.resources.prefs │ ├── org.eclipse.jdt.apt.core.prefs │ ├── org.eclipse.jdt.core.prefs │ └── org.eclipse.m2e.core.prefs ├── Dockerfile ├── data.json ├── data.md ├── deployment.yaml ├── pom.xml ├── scripts │ ├── dynamic_resources.sh │ └── launch_java.sh ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── lbroudoux │ │ │ └── chucknorris │ │ │ └── filter │ │ │ ├── Example.java │ │ │ ├── FilterConfig.java │ │ │ ├── Main.java │ │ │ ├── model │ │ │ ├── Customer.java │ │ │ ├── CustomerRentalMovieAggregate.java │ │ │ ├── DefaultId.java │ │ │ ├── EventType.java │ │ │ ├── Movie.java │ │ │ └── Rental.java │ │ │ └── serdes │ │ │ ├── JsonHybridDeserializer.java │ │ │ ├── JsonPojoSerializer.java │ │ │ └── SerdeFactory.java │ └── test │ │ └── java │ │ └── com │ │ └── github │ │ └── lbroudoux │ │ └── chucknorris │ │ └── filter │ │ └── FilterTest.java └── topic.yaml ├── debezium-connect.yml ├── debezium-connector.yml ├── fuse-online-routes ├── process-chuck-rentals-sf-lbr-export │ ├── model-info.json │ └── model.json └── rental-event-to-telegram-lbr-export │ ├── model-info.json │ └── model.json ├── fuse-online.md ├── kamelets ├── rentals-topic-to-s3.yml └── telegram-to-s3.yml ├── kstream-config.md └── rental-service ├── .gitignore ├── ReadMe.md ├── pom.xml ├── src └── main │ ├── java │ ├── META-INF │ │ └── MANIFEST.MF │ └── nextgen │ │ ├── Application.java │ │ ├── MyTransformer.java │ │ ├── RestConfigurator.java │ │ ├── apimodel │ │ └── RentalEdit.java │ │ ├── controller │ │ └── AppController.java │ │ ├── model │ │ ├── AbstractBaseEntity.java │ │ ├── Customer.java │ │ ├── Movie.java │ │ └── Rental.java │ │ ├── processor │ │ ├── AbstractCRUD.java │ │ ├── RentalCRUD.java │ │ └── RentalPost.java │ │ └── repo │ │ ├── CustomerRepo.java │ │ ├── MovieRepo.java │ │ └── RentalRepo.java │ └── resources │ ├── api-definitions │ └── basic-api.yaml │ ├── application-dev.properties │ ├── application.properties │ ├── logback.xml │ ├── spring │ └── camel-context.xml │ ├── sql │ └── examples.sql │ ├── static │ ├── An Eye for an Eye.jpg │ ├── Charly Chaplin in Wien.jpg │ ├── Delta Force.jpg │ ├── Invasion U.S.A..jpg │ ├── King Kong.jpg │ ├── Robin Hood.jpg │ ├── The Elephant Man.jpg │ ├── The Octagon.jpg │ ├── Vertigo.jpg │ ├── chuck.png │ ├── test.html │ └── video.png │ └── templates │ ├── fragments │ └── header.html │ ├── index.html │ ├── login.html │ ├── logout.html │ └── rented.html └── test ├── test.json └── test2.json /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ IDEA specific 2 | .idea/ 3 | *.iml 4 | */target/ 5 | -------------------------------------------------------------------------------- /3scale.md: -------------------------------------------------------------------------------- 1 | 2 | # Apicast setup 3 | 4 | ![](./assets/apicast-template.png) 5 | 6 | # 3scale backend setup 7 | 8 | ![](./assets/3scale-config.png) 9 | -------------------------------------------------------------------------------- /Stream Processing and Change Data Capture - A Story with Kafka and Debezium.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/Stream Processing and Change Data Capture - A Story with Kafka and Debezium.pdf -------------------------------------------------------------------------------- /assets/3scale-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/assets/3scale-config.png -------------------------------------------------------------------------------- /assets/apicast-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/assets/apicast-template.png -------------------------------------------------------------------------------- /assets/architecture-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/assets/architecture-overview.png -------------------------------------------------------------------------------- /assets/cdc-in-brownfield.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/assets/cdc-in-brownfield.png -------------------------------------------------------------------------------- /assets/chuck-demo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/assets/chuck-demo.mp4 -------------------------------------------------------------------------------- /assets/chuck-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/assets/chuck-demo.png -------------------------------------------------------------------------------- /assets/chuck-norris-facts-funko.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/assets/chuck-norris-facts-funko.jpg -------------------------------------------------------------------------------- /assets/process-chuck-rentals-sf-mapping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/assets/process-chuck-rentals-sf-mapping.png -------------------------------------------------------------------------------- /assets/rental-event-to-telegram-mapping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/assets/rental-event-to-telegram-mapping.png -------------------------------------------------------------------------------- /camel-k-routes/route-local.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | GET 10 | 11 | 12 | 12c496423ef6a39f63b82af587312447 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Don't you know? ${body[fact]} 22 | 23 | 24 | -466189430 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /camel-k-routes/route.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | GET 10 | 11 | 12 | CHUCK_NORRIS_FACT_API_KEY 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Le saviez vous? ${body[fact]} 21 | 22 | 23 | 24 | TELEGRAM_CHAT_ID 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /camel-k-routes/telegram-route.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | GET 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Did you know? ${body[fact]} 18 | 19 | 20 | -466189430 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /camel-k-routes/test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hello, it's me: Chuck Norris 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /chuck-norris-facts-api-swagger.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | swagger: "2.0" 3 | info: 4 | title: chuck-norris-facts-api 5 | version: 1.0-SNAPSHOT 6 | host: chuck-norris-facts-api-chuck-movie-rental.apps.144.76.24.92.nip.io 7 | basePath: /camel 8 | schemes: 9 | - http 10 | paths: 11 | /restsvc/fact: 12 | get: 13 | tags: 14 | - restsvc 15 | operationId: route2 16 | responses: 17 | 200: 18 | description: facts 19 | schema: 20 | $ref: '#/definitions/fact' 21 | examples: {} 22 | definitions: 23 | fact: 24 | required: 25 | - id 26 | - fact 27 | - date 28 | - vote 29 | - points 30 | type: object 31 | properties: 32 | id: 33 | pattern: ^(.*)$ 34 | type: string 35 | fact: 36 | pattern: ^(.*)$ 37 | type: string 38 | date: 39 | pattern: ^(.*)$ 40 | type: string 41 | vote: 42 | pattern: ^(.*)$ 43 | type: string 44 | points: 45 | pattern: ^(.*)$ 46 | type: string 47 | tags: 48 | - name: restsvc 49 | -------------------------------------------------------------------------------- /chuck-norris-facts-api.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: 3.0.2 3 | info: 4 | title: Chuck Norris facts API 5 | version: 1.0.0 6 | paths: 7 | /api/get: 8 | get: 9 | summary: Get Chuck Norris facts 10 | operationId: GetFacts 11 | parameters: 12 | - name: data 13 | in: query 14 | description: Filter params 15 | required: false 16 | schema: 17 | type: string 18 | responses: 19 | 200: 20 | description: List of requested facts 21 | content: 22 | text/html: 23 | schema: 24 | type: array 25 | items: 26 | $ref: '#/components/schemas/Fact' 27 | examples: 28 | top_facts: 29 | value: 30 | - id: "1" 31 | fact: Chuck Norris a déjà compté jusqu'à 32 | l'infini. Deux fois. 33 | date: "1373297343" 34 | vote: "189404" 35 | points: "866902" 36 | - id: "9355" 37 | fact: Google, c'est le seul endroit où tu peux taper 38 | Chuck Norris... 39 | date: "1373297340" 40 | vote: "136456" 41 | points: "607433" 42 | - id: "2" 43 | fact: Certaines personnes portent un pyjama Superman. Superman 44 | porte un pyjama Chuck Norris. 45 | date: "1373297346" 46 | vote: "128337" 47 | points: "551207" 48 | components: 49 | schemas: 50 | Fact: 51 | title: Root Type for Fact 52 | description: The root of the Fact type's schema. 53 | type: object 54 | properties: 55 | id: 56 | type: string 57 | fact: 58 | type: string 59 | date: 60 | type: string 61 | vote: 62 | type: string 63 | points: 64 | type: string 65 | example: |- 66 | { 67 | "id": "106", 68 | "fact": "Chuck Norris peut gagner une partie de puissance 4 en trois coups.", 69 | "date": "1373297368", 70 | "vote": "107246", 71 | "points": "461431" 72 | } 73 | -------------------------------------------------------------------------------- /chuck-norris-facts-api/.gitignore: -------------------------------------------------------------------------------- 1 | **/target/ 2 | **/.project 3 | **/.settings 4 | **/.classpath 5 | **/*.jar 6 | **/*.log 7 | **/.vscode 8 | **/*.iml 9 | **/.idea 10 | **/log-camel-lsp.out -------------------------------------------------------------------------------- /chuck-norris-facts-api/ReadMe.md: -------------------------------------------------------------------------------- 1 | # Spring Boot with camel and other useful things chuck-norris-facts-api 2 | 3 | ## To build this project use 4 | 5 | ``` 6 | mvn install 7 | ``` 8 | 9 | ## To run this project with Maven use 10 | 11 | ``` 12 | mvn spring-boot:run 13 | ``` 14 | 15 | 16 | ## For testing 17 | 18 | ``` 19 | curl http://localhost:8090/camel/api-docs 20 | curl http://localhost:8090/camel/ping 21 | ``` 22 | 23 | 24 | ## Acces Swagger UI with definition 25 | 26 | ``` 27 | http://localhost:8090/webjars/swagger-ui/3.22.0/index.html?url=/camel/api-docs 28 | ``` 29 | 30 | ## Call the ping rest operation 31 | ``` 32 | curl http://localhost:8090/camel/restsvc/ping 33 | ``` -------------------------------------------------------------------------------- /chuck-norris-facts-api/src/main/java/nexgen/Application.java: -------------------------------------------------------------------------------- 1 | package nexgen; 2 | import org.springframework.boot.SpringApplication; 3 | import org.springframework.boot.autoconfigure.SpringBootApplication; 4 | import org.springframework.boot.web.servlet.ServletRegistrationBean; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.ImportResource; 7 | import org.springframework.beans.factory.annotation.Value; 8 | 9 | import org.apache.camel.component.hystrix.metrics.servlet.HystrixEventStreamServlet; 10 | import org.springframework.boot.web.servlet.ServletRegistrationBean; 11 | 12 | @SpringBootApplication 13 | @ImportResource({"classpath:spring/camel-context.xml"}) 14 | public class Application { 15 | 16 | // must have a main method spring-boot can run 17 | public static void main(String[] args) { 18 | SpringApplication.run(Application.class, args); 19 | } 20 | 21 | @Bean 22 | ServletRegistrationBean hystrixServletRegistrationBean() { 23 | ServletRegistrationBean mapping = new ServletRegistrationBean(); 24 | mapping.setServlet(new HystrixEventStreamServlet()); 25 | mapping.addUrlMappings("/hystrix.stream"); 26 | mapping.setName("HystrixEventStreamServlet"); 27 | 28 | return mapping; 29 | } 30 | 31 | 32 | } -------------------------------------------------------------------------------- /chuck-norris-facts-api/src/main/java/nexgen/MyTransformer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Red Hat, Inc. 3 | *

4 | * Red Hat licenses this file to you under the Apache License, version 5 | * 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. 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 13 | * implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | * 16 | */ 17 | package nexgen; 18 | 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.io.StringWriter; 22 | import java.net.URL; 23 | 24 | import com.google.common.io.Resources; 25 | 26 | import org.apache.commons.io.IOUtils; 27 | import org.springframework.stereotype.Component; 28 | 29 | /** 30 | * A sample transform 31 | */ 32 | @Component(value = "myTransformer") 33 | public class MyTransformer { 34 | 35 | 36 | public String content() throws IOException { 37 | InputStream inputStream = getClass() 38 | .getClassLoader().getResourceAsStream("data/facts.json"); 39 | 40 | StringWriter writer = new StringWriter(); 41 | 42 | IOUtils.copy(inputStream, writer,"UTF-8"); 43 | 44 | return writer.toString(); 45 | } 46 | 47 | public String transform() { 48 | // let's return a random string 49 | StringBuffer buffer = new StringBuffer(); 50 | for (int i = 0; i < 3; i++) { 51 | int number = (int) (Math.round(Math.random() * 1000) % 10); 52 | char letter = (char) ('0' + number); 53 | buffer.append(letter); 54 | } 55 | return buffer.toString(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /chuck-norris-facts-api/src/main/java/nexgen/RestConfigurator.java: -------------------------------------------------------------------------------- 1 | package nexgen; 2 | 3 | import org.apache.camel.builder.RouteBuilder; 4 | import org.apache.camel.model.rest.RestBindingMode; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.core.env.Environment; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class RestConfigurator extends RouteBuilder { 11 | 12 | @Autowired 13 | Environment environment; 14 | 15 | @Override 16 | public void configure() throws Exception { 17 | restConfiguration() 18 | .component("servlet") 19 | .bindingMode(RestBindingMode.json) 20 | .contextPath(environment.getProperty("camelrest.contextPath")) 21 | .port(environment.getProperty("camelrest.port")) 22 | .apiContextPath("/api-docs") 23 | .apiProperty("cors", "true") 24 | .apiProperty("api.title", environment.getProperty("camel.springboot.name")) 25 | .apiProperty("api.version", environment.getProperty("camelrest.apiversion")) 26 | .host(environment.getProperty("camelrest.host")) 27 | .dataFormatProperty("prettyPrint", "true"); 28 | } 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /chuck-norris-facts-api/src/main/resources/api-definitions/basic-api.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | swagger: "2.0" 3 | info: 4 | title: basic-api 5 | version: 1.0.0 6 | paths: 7 | /person: 8 | get: 9 | operationId: getPerson 10 | parameters: 11 | - name: email 12 | in: query 13 | required: true 14 | type: string 15 | responses: 16 | 200: 17 | description: Response ok 18 | schema: 19 | $ref: '#/definitions/Person' 20 | definitions: 21 | Person: 22 | title: Root Type for Person 23 | description: The root of the Person type's schema. 24 | type: object 25 | properties: 26 | firstName: 27 | type: string 28 | lastName: 29 | type: string 30 | email: 31 | type: string 32 | age: 33 | format: int32 34 | type: integer 35 | example: |- 36 | { 37 | "firstName": "John", 38 | "lastName": "Doe", 39 | "email": "john@mail.com", 40 | "age": 21 41 | } -------------------------------------------------------------------------------- /chuck-norris-facts-api/src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | logging.config=classpath:logback.xml 2 | 3 | # the options from org.apache.camel.spring.boot.CamelConfigurationProperties can be configured here 4 | camel.springboot.name=chuck-norris-facts-api 5 | 6 | # lets listen on all ports to ensure we can be invoked from the pod IP 7 | server.address=0.0.0.0 8 | management.address=0.0.0.0 9 | # lets use a different management port in case you need to listen to HTTP requests on 8080 10 | server.port=8090 11 | management.port=8190 12 | 13 | # disable all management enpoints except health 14 | endpoints.enabled = false 15 | endpoints.health.enabled = true 16 | 17 | cxf.path=/services 18 | 19 | camel.component.servlet.mapping.contextPath=/camel/* 20 | 21 | camelrest.host=localhost 22 | camelrest.port=8090 23 | camelrest.contextPath=/camel 24 | camelrest.apiversion=1.0-SNAPSHOT -------------------------------------------------------------------------------- /chuck-norris-facts-api/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | logging.config=classpath:logback.xml 2 | 3 | # the options from org.apache.camel.spring.boot.CamelConfigurationProperties can be configured here 4 | camel.springboot.name=chuck-norris-facts-api 5 | 6 | # lets listen on all ports to ensure we can be invoked from the pod IP 7 | server.address=0.0.0.0 8 | management.address=0.0.0.0 9 | # lets use a different management port in case you need to listen to HTTP requests on 8080 10 | server.port=8080 11 | management.port=8081 12 | 13 | # disable all management enpoints except health 14 | endpoints.enabled = false 15 | endpoints.health.enabled = true 16 | 17 | cxf.path=/services 18 | 19 | camel.component.servlet.mapping.contextPath=/camel/* 20 | 21 | camelrest.host=localhost 22 | camelrest.port=80 23 | camelrest.contextPath=/camel 24 | camelrest.apiversion=1.0-SNAPSHOT -------------------------------------------------------------------------------- /chuck-norris-facts-api/src/main/resources/data/facts.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "1", 4 | "fact": "Chuck Norris a déjà compté jusqu'à l'infini. Deux fois.", 5 | "date": "1373297343", 6 | "vote": "189404", 7 | "points": "866902" 8 | }, 9 | { 10 | "id": "9355", 11 | "fact": "Google, c'est le seul endroit où tu peux taper Chuck Norris...", 12 | "date": "1373297340", 13 | "vote": "136456", 14 | "points": "607433" 15 | }, 16 | { 17 | "id": "10705", 18 | "fact": "Chuck Norris et Superman ont fait un bras de fer, le perdant devait mettre son slip par dessus son pantalon.", 19 | "date": "1373297355", 20 | "vote": "113973", 21 | "points": "501197" 22 | }, 23 | { 24 | "id": "106", 25 | "fact": "Chuck Norris peut gagner une partie de puissance 4 en trois coups.", 26 | "date": "1373297368", 27 | "vote": "107246", 28 | "points": "461431" 29 | }, 30 | { 31 | "id": "73", 32 | "fact": "Jesus Christ est né en 1940 avant Chuck Norris.", 33 | "date": "1373297391", 34 | "vote": "105222", 35 | "points": "445848" 36 | }, 37 | { 38 | "id": "10", 39 | "fact": "Chuck Norris ne porte pas de montre. Il décide de l'heure qu'il est.", 40 | "date": "1373297395", 41 | "vote": "104807", 42 | "points": "435723" 43 | }, 44 | { 45 | "id": "180", 46 | "fact": "La seule chose qui arrive à la cheville de Chuck Norris... c'est sa chaussette.", 47 | "date": "1373297400", 48 | "vote": "102280", 49 | "points": "429332" 50 | }, 51 | { 52 | "id": "246", 53 | "fact": "Chuck Norris fait pleurer les oignons.", 54 | "date": "1373297406", 55 | "vote": "99968", 56 | "points": "418228" 57 | }, 58 | { 59 | "id": "4", 60 | "fact": "Chuck Norris peut diviser par zéro.", 61 | "date": "1373297425", 62 | "vote": "98323", 63 | "points": "405137" 64 | }, 65 | { 66 | "id": "398", 67 | "fact": "Chuck Norris comprend Jean-Claude Van Damme.", 68 | "date": "1373297429", 69 | "vote": "97176", 70 | "points": "403247" 71 | }, 72 | { 73 | "id": "5", 74 | "fact": "Chuck Norris joue à la roulette russe avec un chargeur plein.", 75 | "date": "1373297440", 76 | "vote": "92975", 77 | "points": "397412" 78 | }, 79 | { 80 | "id": "3", 81 | "fact": "Chuck Norris sait parler le braille.", 82 | "date": "1373297434", 83 | "vote": "94132", 84 | "points": "397266" 85 | }, 86 | { 87 | "id": "152", 88 | "fact": "Chuck Norris a un jour avalé un paquet entier de somnifères. Il a cligné des yeux.", 89 | "date": "1373297451", 90 | "vote": "94448", 91 | "points": "394266" 92 | }, 93 | { 94 | "id": "267", 95 | "fact": "Quand Google ne trouve pas quelque chose, il demande à Chuck Norris.", 96 | "date": "1373297445", 97 | "vote": "93800", 98 | "points": "393451" 99 | }, 100 | { 101 | "id": "20", 102 | "fact": "Les suisses ne sont pas neutres, ils attendent de savoir de quel coté Chuck Norris se situe.", 103 | "date": "1373297455", 104 | "vote": "93741", 105 | "points": "390347" 106 | }, 107 | { 108 | "id": "12", 109 | "fact": "Il n'y a pas de théorie de l'évolution. Juste une liste d'espèces que Chuck Norris autorise à survivre.", 110 | "date": "1373297465", 111 | "vote": "88531", 112 | "points": "368188" 113 | }, 114 | { 115 | "id": "3693", 116 | "fact": "Chuck Norris peut encercler ses ennemis. Tout seul.", 117 | "date": "1373297495", 118 | "vote": "83160", 119 | "points": "367329" 120 | }, 121 | { 122 | "id": "127", 123 | "fact": "Chuck Norris a déjà été sur Mars, c'est pour cela qu'il n'y a pas de signes de vie là bas.", 124 | "date": "1373297480", 125 | "vote": "87236", 126 | "points": "364383" 127 | }, 128 | { 129 | "id": "28", 130 | "fact": "Chuck Norris mesure son pouls sur l'échelle de Richter.", 131 | "date": "1373297505", 132 | "vote": "87023", 133 | "points": "359969" 134 | }, 135 | { 136 | "id": "13638", 137 | "fact": "Quand Chuck Norris s\u0092est mis au judo, David Douillet s\u0092est mis aux pièces jaunes.", 138 | "date": "1373297489", 139 | "vote": "82967", 140 | "points": "357421" 141 | }, 142 | { 143 | "id": "35", 144 | "fact": "Chuck Norris connait la dernière décimale de Pi.", 145 | "date": "1373297506", 146 | "vote": "84453", 147 | "points": "350437" 148 | }, 149 | { 150 | "id": "57", 151 | "fact": "Dans une pièce normale, il y a en moyenne 1242 objets avec lesquels Chuck Norris peut vous tuer, en incluant la pièce elle même.", 152 | "date": "1373297535", 153 | "vote": "83529", 154 | "points": "350410" 155 | }, 156 | { 157 | "id": "142", 158 | "fact": "Si Chuck Norris avait été pris dans le film 300 il l'aurait renommé en 1.", 159 | "date": "1373297546", 160 | "vote": "83877", 161 | "points": "349393" 162 | }, 163 | { 164 | "id": "84", 165 | "fact": "Un jour, au restaurant, Chuck Norris a commandé un steak. Et le steak a obéi.", 166 | "date": "1373297555", 167 | "vote": "85209", 168 | "points": "349209" 169 | } 170 | ] -------------------------------------------------------------------------------- /chuck-norris-facts-api/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /chuck-norris-facts-api/src/main/resources/spring/camel-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | random(0,23) 22 | 23 | 24 | ${body[${header.randomFactNb}]} 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | application/json 33 | 34 | 35 | {"msg" : "HELLO"} 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /chuck-norris-facts-api/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 |

3 | chuck-norris-facts-api 4 |
5 | 6 | 7 |

Welcome to the chuck-norris-facts-api interface

8 |

Ping

9 |

Swagger Definition

10 |

Swagger UI

11 | 12 | 13 | -------------------------------------------------------------------------------- /chuck-norris-filter-camel/.gitignore: -------------------------------------------------------------------------------- 1 | **/target/ 2 | **/.project 3 | **/.settings 4 | **/.classpath 5 | **/*.jar 6 | **/*.log 7 | **/.vscode 8 | **/*.iml 9 | **/.idea 10 | **/log-camel-lsp.out -------------------------------------------------------------------------------- /chuck-norris-filter-camel/ReadMe.md: -------------------------------------------------------------------------------- 1 | # Spring Boot with camel and other useful things chuck-norris-filter-camel 2 | 3 | ## To build this project use 4 | 5 | ``` 6 | mvn install 7 | ``` 8 | 9 | ## To run this project with Maven use 10 | 11 | ``` 12 | mvn spring-boot:run 13 | ``` 14 | 15 | 16 | ## For testing 17 | 18 | ``` 19 | curl http://localhost:8090/camel/api-docs 20 | curl http://localhost:8090/camel/ping 21 | ``` 22 | 23 | 24 | ## Acces Swagger UI with definition 25 | 26 | ``` 27 | http://localhost:8090/webjars/swagger-ui/3.22.0/index.html?url=/camel/api-docs 28 | ``` 29 | 30 | ## Call the ping rest operation 31 | ``` 32 | curl http://localhost:8090/camel/restsvc/ping 33 | ``` -------------------------------------------------------------------------------- /chuck-norris-filter-camel/src/main/java/nexgen/Application.java: -------------------------------------------------------------------------------- 1 | package nexgen; 2 | import org.springframework.boot.SpringApplication; 3 | import org.springframework.boot.autoconfigure.SpringBootApplication; 4 | import org.springframework.boot.web.servlet.ServletRegistrationBean; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.ImportResource; 7 | import org.springframework.beans.factory.annotation.Value; 8 | 9 | import org.apache.camel.component.hystrix.metrics.servlet.HystrixEventStreamServlet; 10 | import org.springframework.boot.web.servlet.ServletRegistrationBean; 11 | 12 | @SpringBootApplication 13 | @ImportResource({"classpath:spring/camel-context.xml"}) 14 | public class Application { 15 | 16 | // must have a main method spring-boot can run 17 | public static void main(String[] args) { 18 | SpringApplication.run(Application.class, args); 19 | } 20 | 21 | @Bean 22 | ServletRegistrationBean hystrixServletRegistrationBean() { 23 | ServletRegistrationBean mapping = new ServletRegistrationBean(); 24 | mapping.setServlet(new HystrixEventStreamServlet()); 25 | mapping.addUrlMappings("/hystrix.stream"); 26 | mapping.setName("HystrixEventStreamServlet"); 27 | 28 | return mapping; 29 | } 30 | 31 | 32 | } -------------------------------------------------------------------------------- /chuck-norris-filter-camel/src/main/java/nexgen/MyTransformer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Red Hat, Inc. 3 | *

4 | * Red Hat licenses this file to you under the Apache License, version 5 | * 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. 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 13 | * implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | * 16 | */ 17 | package nexgen; 18 | 19 | import org.springframework.stereotype.Component; 20 | 21 | /** 22 | * A sample transform 23 | */ 24 | @Component(value = "myTransformer") 25 | public class MyTransformer { 26 | 27 | public String transform() { 28 | // let's return a random string 29 | StringBuffer buffer = new StringBuffer(); 30 | for (int i = 0; i < 3; i++) { 31 | int number = (int) (Math.round(Math.random() * 1000) % 10); 32 | char letter = (char) ('0' + number); 33 | buffer.append(letter); 34 | } 35 | return buffer.toString(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /chuck-norris-filter-camel/src/main/java/nexgen/RestConfigurator.java: -------------------------------------------------------------------------------- 1 | package nexgen; 2 | 3 | import org.apache.camel.builder.RouteBuilder; 4 | import org.apache.camel.model.rest.RestBindingMode; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.core.env.Environment; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class RestConfigurator extends RouteBuilder { 11 | 12 | @Autowired 13 | Environment environment; 14 | 15 | @Override 16 | public void configure() throws Exception { 17 | restConfiguration() 18 | .component("servlet") 19 | .bindingMode(RestBindingMode.json) 20 | .contextPath(environment.getProperty("camelrest.contextPath")) 21 | .port(environment.getProperty("camelrest.port")) 22 | .apiContextPath("/api-docs") 23 | .apiProperty("cors", "true") 24 | .apiProperty("api.title", environment.getProperty("camel.springboot.name")) 25 | .apiProperty("api.version", environment.getProperty("camelrest.apiversion")) 26 | .host(environment.getProperty("camelrest.host")) 27 | .dataFormatProperty("prettyPrint", "true"); 28 | } 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /chuck-norris-filter-camel/src/main/resources/api-definitions/basic-api.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | swagger: "2.0" 3 | info: 4 | title: basic-api 5 | version: 1.0.0 6 | paths: 7 | /person: 8 | get: 9 | operationId: getPerson 10 | parameters: 11 | - name: email 12 | in: query 13 | required: true 14 | type: string 15 | responses: 16 | 200: 17 | description: Response ok 18 | schema: 19 | $ref: '#/definitions/Person' 20 | definitions: 21 | Person: 22 | title: Root Type for Person 23 | description: The root of the Person type's schema. 24 | type: object 25 | properties: 26 | firstName: 27 | type: string 28 | lastName: 29 | type: string 30 | email: 31 | type: string 32 | age: 33 | format: int32 34 | type: integer 35 | example: |- 36 | { 37 | "firstName": "John", 38 | "lastName": "Doe", 39 | "email": "john@mail.com", 40 | "age": 21 41 | } -------------------------------------------------------------------------------- /chuck-norris-filter-camel/src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | logging.config=classpath:logback.xml 2 | 3 | # the options from org.apache.camel.spring.boot.CamelConfigurationProperties can be configured here 4 | camel.springboot.name=chuck-norris-filter-camel 5 | 6 | # lets listen on all ports to ensure we can be invoked from the pod IP 7 | server.address=0.0.0.0 8 | management.address=0.0.0.0 9 | # lets use a different management port in case you need to listen to HTTP requests on 8080 10 | server.port=8090 11 | management.port=8190 12 | 13 | # disable all management enpoints except health 14 | endpoints.enabled = false 15 | endpoints.health.enabled = true 16 | 17 | cxf.path=/services 18 | 19 | camel.component.servlet.mapping.contextPath=/camel/* 20 | 21 | camelrest.host=localhost 22 | camelrest.port=8090 23 | camelrest.contextPath=/camel 24 | camelrest.apiversion=1.0-SNAPSHOT 25 | 26 | spring.datasource.driver-class-name = com.mysql.jdbc.Driver 27 | spring.datasource.url = jdbc:mysql://172.17.0.2:3306/mysqldb 28 | spring.datasource.username = chuck 29 | spring.datasource.password = password 30 | 31 | 32 | kafka.topic=dbserver1.inventory.rental 33 | kafka.outtopic=events.rentals 34 | 35 | kafka.broker=localhost:9092 36 | kafka.endpoint.standard = kafka:${kafka.topic}?brokers=${kafka.broker} 37 | kafka.endpoint.outstandard = kafka:${kafka.outtopic}?brokers=${kafka.broker} 38 | -------------------------------------------------------------------------------- /chuck-norris-filter-camel/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | logging.config=classpath:logback.xml 2 | 3 | # the options from org.apache.camel.spring.boot.CamelConfigurationProperties can be configured here 4 | camel.springboot.name=chuck-norris-filter-camel 5 | 6 | # lets listen on all ports to ensure we can be invoked from the pod IP 7 | server.address=0.0.0.0 8 | management.address=0.0.0.0 9 | # lets use a different management port in case you need to listen to HTTP requests on 8080 10 | server.port=8080 11 | management.port=8081 12 | 13 | # disable all management enpoints except health 14 | endpoints.enabled = false 15 | endpoints.health.enabled = true 16 | 17 | cxf.path=/services 18 | 19 | camel.component.servlet.mapping.contextPath=/camel/* 20 | 21 | camelrest.host=localhost 22 | camelrest.port=80 23 | camelrest.contextPath=/camel 24 | camelrest.apiversion=1.0-SNAPSHOT 25 | 26 | spring.datasource.driver-class-name = com.mysql.jdbc.Driver 27 | spring.datasource.url = jdbc:mysql://mysqldebezium.chuck-movie-rental.svc.cluster.local:3306/inventory 28 | spring.datasource.username = root 29 | spring.datasource.password = password 30 | 31 | kafka.topic=dbserver1.inventory.rental 32 | kafka.outtopic=events.rentals 33 | 34 | kafka.broker=my-cluster-kafka-bootstrap.amq-streams.svc.cluster.local:9092 35 | kafka.endpoint.standard = kafka:${kafka.topic}?brokers=${kafka.broker} 36 | kafka.endpoint.outstandard = kafka:${kafka.outtopic}?brokers=${kafka.broker} 37 | -------------------------------------------------------------------------------- /chuck-norris-filter-camel/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /chuck-norris-filter-camel/src/main/resources/spring/camel-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {"schema":{"type":"struct","fields":[{"type":"struct","fields":[{"type":"int64","optional":false,"field":"id"},{"type":"int32","optional":true,"field":"rental_duration"},{"type":"int64","optional":true,"name":"io.debezium.time.Timestamp","version":1,"field":"start_date"},{"type":"int64","optional":true,"field":"customer_id"},{"type":"int64","optional":true,"field":"movie_id"}],"optional":true,"name":"dbserver1.inventory.rental.Value","field":"before"},{"type":"struct","fields":[{"type":"int64","optional":false,"field":"id"},{"type":"int32","optional":true,"field":"rental_duration"},{"type":"int64","optional":true,"name":"io.debezium.time.Timestamp","version":1,"field":"start_date"},{"type":"int64","optional":true,"field":"customer_id"},{"type":"int64","optional":true,"field":"movie_id"}],"optional":true,"name":"dbserver1.inventory.rental.Value","field":"after"},{"type":"struct","fields":[{"type":"string","optional":true,"field":"version"},{"type":"string","optional":true,"field":"connector"},{"type":"string","optional":false,"field":"name"},{"type":"int64","optional":false,"field":"server_id"},{"type":"int64","optional":false,"field":"ts_sec"},{"type":"string","optional":true,"field":"gtid"},{"type":"string","optional":false,"field":"file"},{"type":"int64","optional":false,"field":"pos"},{"type":"int32","optional":false,"field":"row"},{"type":"boolean","optional":true,"default":false,"field":"snapshot"},{"type":"int64","optional":true,"field":"thread"},{"type":"string","optional":true,"field":"db"},{"type":"string","optional":true,"field":"table"},{"type":"string","optional":true,"field":"query"}],"optional":false,"name":"io.debezium.connector.mysql.Source","field":"source"},{"type":"string","optional":false,"field":"op"},{"type":"int64","optional":true,"field":"ts_ms"}],"optional":false,"name":"dbserver1.inventory.rental.Envelope"},"payload":{"before":null,"after":{"id":2,"rental_duration":7,"start_date":1558085647000,"customer_id":2,"movie_id":9},"source":{"version":"0.9.5.Final","connector":"mysql","name":"dbserver1","server_id":0,"ts_sec":0,"gtid":null,"file":"mysql-bin.000003","pos":7943,"row":0,"snapshot":true,"thread":null,"db":"inventory","table":"rental","query":null},"op":"c","ts_ms":1558097283698}} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | application/json 30 | 31 | 32 | {"msg" : "HELLO"} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | $.payload.after 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | ${header.movie[main_actor]} == 'Chuck Norris' 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /chuck-norris-filter-camel/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 |

3 | chuck-norris-filter-camel 4 |
5 | 6 | 7 |

Welcome to the chuck-norris-filter-camel interface

8 |

Ping

9 |

Swagger Definition

10 |

Swagger UI

11 | 12 | 13 | -------------------------------------------------------------------------------- /chuck-norris-filter-camel/src/test/resources/test.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/chuck-norris-filter-camel/src/test/resources/test.json -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .project 3 | .classpath 4 | .settings/ 5 | bin/ 6 | 7 | # IntelliJ 8 | .idea 9 | *.ipr 10 | *.iml 11 | *.iws 12 | 13 | # NetBeans 14 | nb-configuration.xml 15 | 16 | # Visual Studio Code 17 | .vscode 18 | 19 | # OSX 20 | .DS_Store 21 | 22 | # Vim 23 | *.swp 24 | *.swo 25 | 26 | # patch 27 | *.orig 28 | *.rej 29 | 30 | # Maven 31 | target/ 32 | pom.xml.tag 33 | pom.xml.releaseBackup 34 | pom.xml.versionsBackup 35 | release.properties -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present 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 | import java.net.*; 17 | import java.io.*; 18 | import java.nio.channels.*; 19 | import java.util.Properties; 20 | 21 | public class MavenWrapperDownloader { 22 | 23 | private static final String WRAPPER_VERSION = "0.5.5"; 24 | /** 25 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 26 | */ 27 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 28 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 29 | 30 | /** 31 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 32 | * use instead of the default one. 33 | */ 34 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 35 | ".mvn/wrapper/maven-wrapper.properties"; 36 | 37 | /** 38 | * Path where the maven-wrapper.jar will be saved to. 39 | */ 40 | private static final String MAVEN_WRAPPER_JAR_PATH = 41 | ".mvn/wrapper/maven-wrapper.jar"; 42 | 43 | /** 44 | * Name of the property which should be used to override the default download url for the wrapper. 45 | */ 46 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 47 | 48 | public static void main(String args[]) { 49 | System.out.println("- Downloader started"); 50 | File baseDirectory = new File(args[0]); 51 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 52 | 53 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 54 | // wrapperUrl parameter. 55 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 56 | String url = DEFAULT_DOWNLOAD_URL; 57 | if(mavenWrapperPropertyFile.exists()) { 58 | FileInputStream mavenWrapperPropertyFileInputStream = null; 59 | try { 60 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 61 | Properties mavenWrapperProperties = new Properties(); 62 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 63 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 64 | } catch (IOException e) { 65 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 66 | } finally { 67 | try { 68 | if(mavenWrapperPropertyFileInputStream != null) { 69 | mavenWrapperPropertyFileInputStream.close(); 70 | } 71 | } catch (IOException e) { 72 | // Ignore ... 73 | } 74 | } 75 | } 76 | System.out.println("- Downloading from: " + url); 77 | 78 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 79 | if(!outputFile.getParentFile().exists()) { 80 | if(!outputFile.getParentFile().mkdirs()) { 81 | System.out.println( 82 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 83 | } 84 | } 85 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 86 | try { 87 | downloadFileFromURL(url, outputFile); 88 | System.out.println("Done"); 89 | System.exit(0); 90 | } catch (Throwable e) { 91 | System.out.println("- Error downloading"); 92 | e.printStackTrace(); 93 | System.exit(1); 94 | } 95 | } 96 | 97 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 98 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 99 | String username = System.getenv("MVNW_USERNAME"); 100 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 101 | Authenticator.setDefault(new Authenticator() { 102 | @Override 103 | protected PasswordAuthentication getPasswordAuthentication() { 104 | return new PasswordAuthentication(username, password); 105 | } 106 | }); 107 | } 108 | URL website = new URL(urlString); 109 | ReadableByteChannel rbc; 110 | rbc = Channels.newChannel(website.openStream()); 111 | FileOutputStream fos = new FileOutputStream(destination); 112 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 113 | fos.close(); 114 | rbc.close(); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/chuck-norris-filter-kstreams-quarkus/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.2/apache-maven-3.6.2-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar 3 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/README.md: -------------------------------------------------------------------------------- 1 | # chuck-norris-filter-kstreams-quarkus project 2 | 3 | This project uses Quarkus, the Supersonic Subatomic Java Framework. 4 | 5 | If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ . 6 | 7 | Built with: 8 | 9 | ```sh 10 | mvn clean package -Pnative -Dquarkus.native.container-build=true 11 | docker build -f src/main/docker/Dockerfile.native -t quay.io/lbroudoux/chuck-norris-filter-quarkus:new-latest . 12 | docker push quay.io/lbroudoux/chuck-norris-filter-quarkus:new-latest 13 | ``` 14 | 15 | ## Running the application in dev mode 16 | 17 | You can run your application in dev mode that enables live coding using: 18 | ``` 19 | ./mvnw quarkus:dev 20 | ``` 21 | 22 | ## Packaging and running the application 23 | 24 | The application is packageable using `./mvnw package`. 25 | It produces the executable `chuck-norris-filter-kstreams-quarkus-1.0-SNAPSHOT-runner.jar` file in `/target` directory. 26 | Be aware that it’s not an _über-jar_ as the dependencies are copied into the `target/lib` directory. 27 | 28 | The application is now runnable using `java -jar target/chuck-norris-filter-kstreams-quarkus-1.0-SNAPSHOT-runner.jar`. 29 | 30 | ## Creating a native executable 31 | 32 | You can create a native executable using: `./mvnw package -Pnative`. 33 | 34 | Or you can use Docker to build the native executable using: `./mvnw package -Pnative -Dquarkus.native.container-build=true`. 35 | 36 | You can then execute your binary: `./target/chuck-norris-filter-kstreams-quarkus-1.0-SNAPSHOT-runner` 37 | 38 | If you want to learn more about building native executables, please consult https://quarkus.io/guides/building-native-image-guide . -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: chuck-norris-streams-filter-quarkus 6 | name: chuck-norris-streams-filter-quarkus 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: chuck-norris-filter-quarkus 12 | template: 13 | metadata: 14 | labels: 15 | app: chuck-norris-filter-quarkus 16 | spec: 17 | containers: 18 | - name: chuck-norris-filter 19 | image: quay.io/lbroudoux/chuck-norris-filter-quarkus:new-latest 20 | env: 21 | - name: BOOTSTRAP_SERVERS 22 | value: my-cluster-kafka-bootstrap.amq-streams.svc.cluster.local:9092 23 | - name: MOVIES_SOURCE_TOPIC 24 | value: dbserver1.inventory.movie 25 | - name: CUSTOMERS_SOURCE_TOPIC 26 | value: dbserver1.inventory.customer 27 | - name: RENTALS_SOURCE_TOPIC 28 | value: dbserver1.inventory.rental 29 | - name: TARGET_TOPIC 30 | value: rental-chuck-norris -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.github.lbroudoux 6 | chuck-norris-filter-kstreams-quarkus 7 | 1.0-SNAPSHOT 8 | 9 | 3.8.1 10 | true 11 | 1.8 12 | 1.8 13 | UTF-8 14 | UTF-8 15 | 1.2.1.Final 16 | quarkus-universe-bom 17 | io.quarkus 18 | 1.2.1.Final 19 | 2.22.1 20 | 21 | 22 | 23 | 24 | ${quarkus.platform.group-id} 25 | ${quarkus.platform.artifact-id} 26 | ${quarkus.platform.version} 27 | pom 28 | import 29 | 30 | 31 | 32 | 33 | 34 | io.quarkus 35 | quarkus-resteasy 36 | 37 | 38 | io.quarkus 39 | quarkus-resteasy-jackson 40 | 41 | 42 | io.quarkus 43 | quarkus-kafka-streams 44 | 45 | 46 | io.quarkus 47 | quarkus-junit5 48 | test 49 | 50 | 51 | io.rest-assured 52 | rest-assured 53 | test 54 | 55 | 56 | 57 | com.oracle.substratevm 58 | svm 59 | 60 | 61 | 62 | 63 | 64 | io.quarkus 65 | quarkus-maven-plugin 66 | ${quarkus-plugin.version} 67 | 68 | 69 | 70 | build 71 | 72 | 73 | 74 | 75 | 76 | maven-compiler-plugin 77 | ${compiler-plugin.version} 78 | 79 | 80 | maven-surefire-plugin 81 | ${surefire-plugin.version} 82 | 83 | 84 | org.jboss.logmanager.LogManager 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | native 93 | 94 | 95 | native 96 | 97 | 98 | 99 | 100 | 101 | maven-failsafe-plugin 102 | ${surefire-plugin.version} 103 | 104 | 105 | 106 | integration-test 107 | verify 108 | 109 | 110 | 111 | ${project.build.directory}/${project.build.finalName}-runner 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | native 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/src/main/docker/Dockerfile.jvm: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode 3 | # 4 | # Before building the docker image run: 5 | # 6 | # mvn package 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.jvm -t quarkus/chuck-norris-filter-kstreams-quarkus-jvm . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/chuck-norris-filter-kstreams-quarkus-jvm 15 | # 16 | ### 17 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1 18 | 19 | ARG JAVA_PACKAGE=java-1.8.0-openjdk-headless 20 | ARG RUN_JAVA_VERSION=1.3.5 21 | 22 | ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' 23 | 24 | # Install java and the run-java script 25 | # Also set up permissions for user `1001` 26 | RUN microdnf install openssl curl ca-certificates ${JAVA_PACKAGE} \ 27 | && microdnf update \ 28 | && microdnf clean all \ 29 | && mkdir /deployments \ 30 | && chown 1001 /deployments \ 31 | && chmod "g+rwX" /deployments \ 32 | && chown 1001:root /deployments \ 33 | && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \ 34 | && chown 1001 /deployments/run-java.sh \ 35 | && chmod 540 /deployments/run-java.sh \ 36 | && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security 37 | 38 | # Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. 39 | ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" 40 | 41 | COPY target/lib/* /deployments/lib/ 42 | COPY target/*-runner.jar /deployments/app.jar 43 | 44 | EXPOSE 8080 45 | USER 1001 46 | 47 | ENTRYPOINT [ "/deployments/run-java.sh" ] -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/src/main/docker/Dockerfile.multistage: -------------------------------------------------------------------------------- 1 | ## Stage 1 : build with maven builder image with native capabilities 2 | FROM quay.io/quarkus/centos-quarkus-maven:19.3.1-java8 AS build 3 | COPY src /usr/src/app/src 4 | COPY pom.xml /usr/src/app 5 | USER root 6 | RUN chown -R quarkus /usr/src/app 7 | USER quarkus 8 | RUN mvn -f /usr/src/app/pom.xml -Pnative clean package 9 | 10 | ## Stage 2 : create the docker final image 11 | FROM registry.access.redhat.com/ubi8/ubi-minimal 12 | WORKDIR /work/ 13 | COPY --from=build /usr/src/app/target/*-runner /work/application 14 | RUN chmod 775 /work 15 | EXPOSE 8080 16 | CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/src/main/docker/Dockerfile.native: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode 3 | # 4 | # Before building the docker image run: 5 | # 6 | # mvn package -Pnative -Dquarkus.native.container-build=true 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.native -t quarkus/chuck-norris-filter-kstreams-quarkus . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/chuck-norris-filter-kstreams-quarkus 15 | # 16 | ### 17 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1 18 | WORKDIR /work/ 19 | COPY target/*-runner /work/application 20 | 21 | # set up permissions for user `1001` 22 | RUN chmod 775 /work /work/application \ 23 | && chown -R 1001 /work \ 24 | && chmod -R "g+rwX" /work \ 25 | && chown -R 1001:root /work 26 | 27 | EXPOSE 8080 28 | USER 1001 29 | 30 | CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/src/main/java/com/github/lbroudoux/chucknorris/filter/FilterConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter; 2 | 3 | import com.github.lbroudoux.chucknorris.filter.model.CustomerRentalMovieAggregate; 4 | import com.github.lbroudoux.chucknorris.filter.serdes.SerdeFactory; 5 | import org.apache.kafka.common.serialization.Serde; 6 | import org.apache.kafka.common.serialization.Serdes; 7 | import org.apache.kafka.streams.StreamsConfig; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.Properties; 12 | /** 13 | * @author laurent 14 | */ 15 | public class FilterConfig { 16 | 17 | private static final Logger log = LoggerFactory.getLogger(FilterConfig.class.getName()); 18 | 19 | private final String bootstrapServers; 20 | private final String rentalsSourceTopic; 21 | private final String moviesSourceTopic; 22 | private final String customersSourceTopic; 23 | private final String targetTopic; 24 | private final String trustStorePassword; 25 | private final String trustStorePath; 26 | private final String keyStorePassword; 27 | private final String keyStorePath; 28 | private final String username; 29 | private final String password; 30 | 31 | public FilterConfig(String bootstrapServers, String rentalsSourceTopic, String moviesSourceTopic, 32 | String customersSourceTopic, String targetTopic, String trustStorePassword, String trustStorePath, 33 | String keyStorePassword, String keyStorePath, String username, String password) { 34 | this.bootstrapServers = bootstrapServers; 35 | this.rentalsSourceTopic = rentalsSourceTopic; 36 | this.moviesSourceTopic = moviesSourceTopic; 37 | this.customersSourceTopic = customersSourceTopic; 38 | this.targetTopic = targetTopic; 39 | this.trustStorePassword = trustStorePassword; 40 | this.trustStorePath = trustStorePath; 41 | this.keyStorePassword = keyStorePassword; 42 | this.keyStorePath = keyStorePath; 43 | this.username = username; 44 | this.password = password; 45 | } 46 | 47 | public static FilterConfig fromEnv() { 48 | String bootstrapServers = System.getenv("BOOTSTRAP_SERVERS"); 49 | String rentalsSourceTopic = System.getenv("RENTALS_SOURCE_TOPIC"); 50 | String moviesSourceTopic = System.getenv("MOVIES_SOURCE_TOPIC"); 51 | String customersSourceTopic = System.getenv("CUSTOMERS_SOURCE_TOPIC"); 52 | String targetTopic = System.getenv("TARGET_TOPIC"); 53 | String trustStorePassword = System.getenv("TRUSTSTORE_PASSWORD") == null ? null 54 | : System.getenv("TRUSTSTORE_PASSWORD"); 55 | String trustStorePath = System.getenv("TRUSTSTORE_PATH") == null ? null : System.getenv("TRUSTSTORE_PATH"); 56 | String keyStorePassword = System.getenv("KEYSTORE_PASSWORD") == null ? null : System.getenv("KEYSTORE_PASSWORD"); 57 | String keyStorePath = System.getenv("KEYSTORE_PATH") == null ? null : System.getenv("KEYSTORE_PATH"); 58 | String username = System.getenv("USERNAME") == null ? null : System.getenv("USERNAME"); 59 | String password = System.getenv("PASSWORD") == null ? null : System.getenv("PASSWORD"); 60 | 61 | return new FilterConfig(bootstrapServers, rentalsSourceTopic, moviesSourceTopic, customersSourceTopic, 62 | targetTopic, trustStorePassword, trustStorePath, keyStorePassword, keyStorePath, username, password); 63 | 64 | // return new FilterConfig("localhost:9092", "rental.rentals", "rental.movies", 65 | // "rental.customers", "rental.chucknorris", trustStorePassword, trustStorePath, 66 | // keyStorePassword, keyStorePath, username, password); 67 | } 68 | 69 | public String getBootstrapServers() { 70 | return bootstrapServers; 71 | } 72 | 73 | public String getRentalsTopic() { 74 | return rentalsSourceTopic; 75 | } 76 | 77 | public String getMoviesTopic() { 78 | return moviesSourceTopic; 79 | } 80 | 81 | public String getCustomersTopic() { 82 | return customersSourceTopic; 83 | } 84 | 85 | public String getTargetTopic() { 86 | return targetTopic; 87 | } 88 | 89 | public String getTrustStorePassword() { 90 | return trustStorePassword; 91 | } 92 | 93 | public String getTrustStorePath() { 94 | return trustStorePath; 95 | } 96 | 97 | public String getKeyStorePassword() { 98 | return keyStorePassword; 99 | } 100 | 101 | public String getKeyStorePath() { 102 | return keyStorePath; 103 | } 104 | 105 | public String getUsername() { 106 | return username; 107 | } 108 | 109 | public String getPassword() { 110 | return password; 111 | } 112 | 113 | public static Properties getKafkaStreamsProperties(FilterConfig config) { 114 | Properties props = new Properties(); 115 | props.put(StreamsConfig.APPLICATION_ID_CONFIG, "chuck-norris-filter"); 116 | props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, config.getBootstrapServers()); 117 | props.put(StreamsConfig.COMMIT_INTERVAL_MS_CONFIG, 5000); 118 | props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.Integer().getClass()); 119 | props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, 120 | SerdeFactory.CustomerRentalMovieAggregateSerde().getClass()); 121 | 122 | if (config.getTrustStorePassword() != null && config.getTrustStorePath() != null) { 123 | log.info("Configuring truststore"); 124 | props.put("security.protocol", "SSL"); 125 | props.put("ssl.truststore.type", "PKCS12"); 126 | props.put("ssl.truststore.password", config.getTrustStorePassword()); 127 | props.put("ssl.truststore.location", config.getTrustStorePath()); 128 | } 129 | 130 | if (config.getKeyStorePassword() != null && config.getKeyStorePath() != null) { 131 | log.info("Configuring keystore"); 132 | props.put("security.protocol", "SSL"); 133 | props.put("ssl.keystore.type", "PKCS12"); 134 | props.put("ssl.keystore.password", config.getKeyStorePassword()); 135 | props.put("ssl.keystore.location", config.getKeyStorePath()); 136 | } 137 | 138 | if (config.getUsername() != null && config.getPassword() != null) { 139 | props.put("sasl.mechanism", "SCRAM-SHA-512"); 140 | props.put("sasl.jaas.config", "org.apache.kafka.common.security.scram.ScramLoginModule required username=\"" 141 | + config.getUsername() + "\" password=\"" + config.getPassword() + "\";"); 142 | 143 | if (props.get("security.protocol") != null && props.get("security.protocol").equals("SSL")) { 144 | props.put("security.protocol", "SASL_SSL"); 145 | } else { 146 | props.put("security.protocol", "SASL_PLAINTEXT"); 147 | } 148 | } 149 | 150 | return props; 151 | } 152 | } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/src/main/java/com/github/lbroudoux/chucknorris/filter/JNIRegistrationFeature.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter; 2 | 3 | import org.graalvm.nativeimage.hosted.Feature; 4 | import org.rocksdb.RocksDBException; 5 | import org.rocksdb.Status; 6 | 7 | import com.oracle.svm.core.annotate.AutomaticFeature; 8 | import com.oracle.svm.core.jni.JNIRuntimeAccess; 9 | 10 | /** 11 | * Workaround for having Kafka streams run in native-mode. 12 | * See https://github.com/quarkusio/quarkus/issues/7066 13 | * @author laurent 14 | */ 15 | @AutomaticFeature 16 | class JNIRegistrationFeature implements Feature { 17 | 18 | @Override 19 | public void beforeAnalysis(BeforeAnalysisAccess access) { 20 | JNIRuntimeAccess.register(RocksDBException.class); 21 | JNIRuntimeAccess.register(RocksDBException.class.getConstructors()); 22 | JNIRuntimeAccess.register(Status.class); 23 | JNIRuntimeAccess.register(Status.class.getDeclaredConstructors()); 24 | } 25 | } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/src/main/java/com/github/lbroudoux/chucknorris/filter/TopologyProducer.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter; 2 | 3 | import javax.enterprise.context.ApplicationScoped; 4 | import javax.enterprise.inject.Produces; 5 | 6 | import org.apache.kafka.common.serialization.Serde; 7 | import org.apache.kafka.common.serialization.Serdes; 8 | import org.apache.kafka.streams.KeyValue; 9 | import org.apache.kafka.streams.StreamsBuilder; 10 | import org.apache.kafka.streams.Topology; 11 | import org.apache.kafka.streams.kstream.*; 12 | 13 | import io.quarkus.kafka.client.serialization.JsonbSerde; 14 | 15 | import com.github.lbroudoux.chucknorris.filter.model.*; 16 | import com.github.lbroudoux.chucknorris.filter.serdes.SerdeFactory; 17 | 18 | import java.util.Properties; 19 | /** 20 | * @author laurent 21 | */ 22 | @ApplicationScoped 23 | public class TopologyProducer { 24 | 25 | @Produces 26 | public Topology buildTopology() { 27 | 28 | FilterConfig config = FilterConfig.fromEnv(); 29 | Properties kafkaStreamsConfig = FilterConfig.getKafkaStreamsProperties(config); 30 | 31 | // Configure all the Serdes. 32 | final Serde defaultIdSerde = SerdeFactory.createDbzEventJsonPojoSerdeFor(Integer.class, true); 33 | final Serde movieSerde = SerdeFactory.createDbzEventJsonPojoSerdeFor(Movie.class, false); 34 | final Serde userSerde = SerdeFactory.createDbzEventJsonPojoSerdeFor(Customer.class, false); 35 | final Serde rentalSerde = SerdeFactory.createDbzEventJsonPojoSerdeFor(Rental.class, false); 36 | final Serde aggregateSerde = SerdeFactory.createDbzEventJsonPojoSerdeFor(CustomerRentalMovieAggregate.class, false); 37 | 38 | // Getting a streams builder. 39 | StreamsBuilder builder = new StreamsBuilder(); 40 | 41 | // We need to make sure that all data are loaded at every restart => we need to use KTable. 42 | // KStream would continue where it left off, so after restart you will get an empty table. 43 | // The partitioning is not aligned between the different topics. 44 | // Both use the same number of partitions, but not the same key (rental_id versus movie_id versus customer_id) 45 | // => we need to use GlobalKTable to make sure that the application will always have all the data even when use use more than one partition. 46 | // Since we want the GlobalKTable with the integer key, we have to use the right Serde which would decode the ID into Integer. 47 | GlobalKTable usersTableInt = builder.globalTable(config.getCustomersTopic(), Consumed.with(defaultIdSerde, userSerde)); 48 | GlobalKTable moviesTableInt = builder.globalTable(config.getMoviesTopic(), Consumed.with(defaultIdSerde, movieSerde)); 49 | 50 | // Create Stream for rental: we are looking at each changes excepted deletes (rental == null). 51 | KStream rentalsStream = builder.stream(config.getRentalsTopic(), Consumed.with(defaultIdSerde, rentalSerde)) 52 | .filter((rentalId, rental) -> rental != null) 53 | .map((rentalId, rental) -> new KeyValue<>(rental.getMovieId(), rental)); 54 | //rentalsStream.print(Printed.toSysOut()); 55 | 56 | // Now, let the magic happens!! 57 | KStream chuckNorrisRentalsStream = 58 | rentalsStream 59 | // leftJoin would let null right sides through which is hard to handle in what we are doing here 60 | // using join is better because while some rental records might be skipped, we will not get ugly null messages and NPEs 61 | .join(moviesTableInt, 62 | (leftKey, leftValue) -> leftKey, 63 | (rental, movie) -> new CustomerRentalMovieAggregate(rental, movie)) 64 | .filter((movieId, urmAggregate) -> urmAggregate.getMovie().getMainActor().equals("Chuck Norris")) 65 | .map((movieId, urmAggregate) -> new KeyValue<>(urmAggregate.getRental().getCustomerId(), urmAggregate)) 66 | // Same as above, leftJoin would let null right sides through which is hard to handle in what we are doing here 67 | // using join is better because while some rental records might be skipped, we will not get ugly null messages and NPEs 68 | .join(usersTableInt, 69 | (leftKey, leftValue) -> leftKey, 70 | (urmAggregate, customer) -> completeAggregate(urmAggregate, customer)); 71 | chuckNorrisRentalsStream.to(config.getTargetTopic(), Produced.with(Serdes.Integer(), aggregateSerde)); 72 | 73 | chuckNorrisRentalsStream.print(Printed.toSysOut()); 74 | 75 | System.err.println("Starting streams!"); 76 | return builder.build(); 77 | } 78 | 79 | private static CustomerRentalMovieAggregate completeAggregate(CustomerRentalMovieAggregate urmAggregate, Customer customer) { 80 | System.err.println("Completing CustomerRentalMovieAggregate with " + customer.toString()); 81 | urmAggregate.setCustomer(customer); 82 | return urmAggregate; 83 | } 84 | } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/src/main/java/com/github/lbroudoux/chucknorris/filter/model/Customer.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.model; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | 5 | import io.quarkus.runtime.annotations.RegisterForReflection; 6 | /** 7 | * @author laurent 8 | */ 9 | @RegisterForReflection 10 | public class Customer { 11 | 12 | @JsonProperty("_eventType") 13 | private final EventType eventType; 14 | 15 | @JsonProperty("id") 16 | private final Integer id; 17 | @JsonProperty("first_name") 18 | private final String firstname; 19 | @JsonProperty("last_name") 20 | private final String lastname; 21 | @JsonProperty("twitter_handle") 22 | private final String twitterHandle; 23 | 24 | @JsonCreator 25 | public Customer(@JsonProperty("_eventType") EventType _eventType, 26 | @JsonProperty("id") Integer id, 27 | @JsonProperty("first_name") String firstname, 28 | @JsonProperty("last_name") String lastname, 29 | @JsonProperty("twitter_handle") String twitterHandle) { 30 | 31 | this.eventType = _eventType == null ? EventType.UPSERT : _eventType; 32 | this.id = id; 33 | this.firstname = firstname; 34 | this.lastname = lastname; 35 | this.twitterHandle = twitterHandle; 36 | } 37 | 38 | /** 39 | * @return the eventType 40 | */ 41 | public EventType getEventType() { 42 | return eventType; 43 | } 44 | 45 | /** 46 | * @return the id 47 | */ 48 | public Integer getId() { 49 | return id; 50 | } 51 | 52 | /** 53 | * @return the firstName 54 | */ 55 | public String getFirstname() { 56 | return firstname; 57 | } 58 | 59 | /** 60 | * @return the lastName 61 | */ 62 | public String getLastname() { 63 | return lastname; 64 | } 65 | 66 | /** 67 | * @return the twitterHandle 68 | */ 69 | public String getTwitterHandle() { 70 | return twitterHandle; 71 | } 72 | 73 | 74 | @Override 75 | public String toString() { 76 | return "Customer{" + 77 | "_eventType='" + eventType + '\'' + 78 | ", id=" + id + 79 | ", first_name='" + firstname + '\'' + 80 | ", last_name='" + lastname + '\'' + 81 | ", twitter_handle='" + twitterHandle + '\'' + 82 | "}"; 83 | } 84 | } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/src/main/java/com/github/lbroudoux/chucknorris/filter/model/CustomerRentalMovieAggregate.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.model; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | 5 | import io.quarkus.runtime.annotations.RegisterForReflection; 6 | /** 7 | * @author laurent 8 | */ 9 | @RegisterForReflection 10 | public class CustomerRentalMovieAggregate { 11 | 12 | @JsonProperty("movie") 13 | private final Movie movie; 14 | 15 | @JsonProperty("customer") 16 | private Customer customer; 17 | 18 | @JsonProperty("rental") 19 | private final Rental rental; 20 | 21 | 22 | @JsonCreator 23 | public CustomerRentalMovieAggregate( 24 | @JsonProperty("rental") Rental rental, 25 | @JsonProperty("movie") Movie movie) { 26 | this.rental = rental; 27 | this.movie = movie; 28 | if (movie != null && rental != null) { 29 | System.err.println("Building new CustomerRentalMovieAggregate with " + movie.toString() + " and " + rental.toString()); 30 | } 31 | if (movie == null) { 32 | System.err.println("Got null movie..."); 33 | } 34 | if (rental == null) { 35 | System.err.println("Got null rental..."); 36 | } 37 | } 38 | 39 | public Movie getMovie() { 40 | return movie; 41 | } 42 | 43 | public Customer getCustomer() { 44 | return customer; 45 | } 46 | 47 | public void setCustomer(Customer customer) { 48 | this.customer = customer; 49 | } 50 | 51 | public Rental getRental() { 52 | return rental; 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return "CustomerRentalMovieAggregate{" + 58 | "rental=" + rental + 59 | ", movie=" + movie + 60 | ", customer=" + customer + 61 | "}"; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/src/main/java/com/github/lbroudoux/chucknorris/filter/model/DefaultId.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.model; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | import com.fasterxml.jackson.core.JsonGenerator; 5 | import com.fasterxml.jackson.databind.*; 6 | 7 | import java.io.IOException; 8 | 9 | /** 10 | * @author laurent 11 | */ 12 | public class DefaultId { 13 | 14 | @JsonIgnore 15 | private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 16 | 17 | private final Integer id; 18 | 19 | @JsonCreator 20 | public DefaultId(@JsonProperty("id") Integer id) { 21 | this.id = id; 22 | } 23 | 24 | public Integer getId() { 25 | return id; 26 | } 27 | 28 | public static class IdDeserializer extends KeyDeserializer { 29 | 30 | @Override 31 | public DefaultId deserializeKey( 32 | String key, 33 | DeserializationContext ctx) throws IOException { 34 | 35 | return OBJECT_MAPPER.readValue(key, DefaultId.class); 36 | } 37 | } 38 | 39 | public static class IdSerializer extends JsonSerializer { 40 | 41 | @Override 42 | public void serialize(DefaultId key, 43 | JsonGenerator gen, 44 | SerializerProvider serializers) 45 | throws IOException { 46 | 47 | gen.writeFieldName(OBJECT_MAPPER.writeValueAsString(key)); 48 | } 49 | } 50 | 51 | @Override 52 | public boolean equals(Object o) { 53 | if (this == o) { 54 | return true; 55 | } 56 | if (o == null || getClass() != o.getClass()) { 57 | return false; 58 | } 59 | 60 | DefaultId defaultId = (DefaultId) o; 61 | 62 | return id != null ? id.equals(defaultId.id) : defaultId.id == null; 63 | } 64 | 65 | @Override 66 | public int hashCode() { 67 | return id != null ? id.hashCode() : 0; 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | return "DefaultId{" + 73 | "id=" + id + 74 | '}'; 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/src/main/java/com/github/lbroudoux/chucknorris/filter/model/EventType.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.model; 2 | 3 | public enum EventType { UPSERT, DELETE } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/src/main/java/com/github/lbroudoux/chucknorris/filter/model/Movie.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.model; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | 5 | import io.quarkus.runtime.annotations.RegisterForReflection; 6 | /** 7 | * @author laurent 8 | */ 9 | @RegisterForReflection 10 | public class Movie { 11 | 12 | @JsonProperty("_eventType") 13 | private final EventType eventType; 14 | 15 | @JsonProperty("id") 16 | private final Integer id; 17 | @JsonProperty("title") 18 | private final String title; 19 | @JsonProperty("year") 20 | private final Integer year; 21 | @JsonProperty("main_actor") 22 | private final String mainActor; 23 | 24 | @JsonCreator 25 | public Movie(@JsonProperty("_eventType") EventType _eventType, 26 | @JsonProperty("id") Integer id, 27 | @JsonProperty("title") String title, 28 | @JsonProperty("year") Integer year, 29 | @JsonProperty("main_actor") String mainActor) { 30 | 31 | this.eventType = _eventType == null ? EventType.UPSERT : _eventType; 32 | this.id = id; 33 | this.title = title; 34 | this.year = year; 35 | this.mainActor = mainActor; 36 | } 37 | 38 | /** 39 | * @return the eventType 40 | */ 41 | public EventType getEventType() { 42 | return eventType; 43 | } 44 | 45 | /** 46 | * @return the id 47 | */ 48 | public Integer getId() { 49 | return id; 50 | } 51 | 52 | /** 53 | * @return the title 54 | */ 55 | public String getTitle() { 56 | return title; 57 | } 58 | 59 | /** 60 | * @return the year 61 | */ 62 | public Integer getYear() { 63 | return year; 64 | } 65 | 66 | /** 67 | * @return the mainActor 68 | */ 69 | public String getMainActor() { 70 | return mainActor; 71 | } 72 | 73 | @Override 74 | public String toString() { 75 | return "Movie{" + 76 | "_eventType='" + eventType + '\'' + 77 | ", id=" + id + 78 | ", title='" + title + '\'' + 79 | ", main_actor='" + mainActor + '\'' + 80 | ", year='" + year + '\'' + 81 | "}"; 82 | } 83 | } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/src/main/java/com/github/lbroudoux/chucknorris/filter/model/Rental.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.model; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | 5 | import io.quarkus.runtime.annotations.RegisterForReflection; 6 | 7 | import java.util.Date; 8 | /** 9 | * @author laurent 10 | */ 11 | @RegisterForReflection 12 | public class Rental { 13 | 14 | @JsonProperty("_eventType") 15 | private final EventType eventType; 16 | 17 | @JsonProperty("id") 18 | private final Integer id; 19 | @JsonProperty("customer_id") 20 | private final Integer customerId; 21 | @JsonProperty("movie_id") 22 | private final Integer movieId; 23 | @JsonProperty("start_date") 24 | private final Date startDate; 25 | @JsonProperty("rental_duration") 26 | private final Integer rentalDuration; 27 | 28 | @JsonCreator 29 | public Rental(@JsonProperty("_eventType") EventType _eventType, 30 | @JsonProperty("id") Integer id, 31 | @JsonProperty("customer_id") Integer customerId, 32 | @JsonProperty("movie_id") Integer movieId, 33 | @JsonProperty("start_date") Date startDate, 34 | @JsonProperty("rental_duration") Integer rentalDuration) { 35 | 36 | this.eventType = _eventType == null ? EventType.UPSERT : _eventType; 37 | this.id = id; 38 | this.customerId = customerId; 39 | this.movieId = movieId; 40 | this.startDate = startDate; 41 | this.rentalDuration = rentalDuration; 42 | } 43 | 44 | /** 45 | * @return the eventType 46 | */ 47 | public EventType getEventType() { 48 | return eventType; 49 | } 50 | 51 | /** 52 | * @return the id 53 | */ 54 | public Integer getId() { 55 | return id; 56 | } 57 | 58 | /** 59 | * @return the customerId 60 | */ 61 | public Integer getCustomerId() { 62 | return customerId; 63 | } 64 | 65 | /** 66 | * @return the movieId 67 | */ 68 | public Integer getMovieId() { 69 | return movieId; 70 | } 71 | 72 | /** 73 | * @return the startDate 74 | */ 75 | public Date getStartDate() { 76 | return startDate; 77 | } 78 | 79 | /** 80 | * @return the rentalDuration 81 | */ 82 | public Integer getRentalDuration() { 83 | return rentalDuration; 84 | } 85 | 86 | 87 | @Override 88 | public String toString() { 89 | return "Rental{" + 90 | "_eventType='" + eventType + '\'' + 91 | ", id=" + id + 92 | ", customer_id='" + customerId + '\'' + 93 | ", movie_id='" + movieId + '\'' + 94 | ", startDate='" + startDate + '\'' + 95 | "}"; 96 | } 97 | } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/src/main/java/com/github/lbroudoux/chucknorris/filter/serdes/JsonHybridDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.serdes; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.apache.kafka.common.errors.SerializationException; 5 | import org.apache.kafka.common.serialization.Deserializer; 6 | 7 | import com.github.lbroudoux.chucknorris.filter.model.EventType; 8 | 9 | import java.io.IOException; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | public class JsonHybridDeserializer implements Deserializer { 14 | 15 | public static final String DBZ_CDC_EVENT_PAYLOAD_FIELD = "payload"; 16 | public static final String DBZ_CDC_EVENT_AFTER_FIELD = "after"; 17 | 18 | private final static ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 19 | 20 | private Class clazz; 21 | private boolean isKey; 22 | 23 | @SuppressWarnings("unchecked") 24 | @Override 25 | public void configure(Map props, boolean isKey) { 26 | clazz = (Class)props.get("serializedClass"); 27 | this.isKey = isKey; 28 | } 29 | 30 | @Override 31 | public T deserialize(String topic, byte[] bytes) { 32 | 33 | if (bytes == null) 34 | return null; 35 | 36 | T data; 37 | 38 | System.err.println("Looking for deserializing: " + new String(bytes)); 39 | //step1: try to directly deserialize expected class 40 | try { 41 | data = OBJECT_MAPPER.readValue(bytes, clazz); 42 | } catch (IOException e1) { 43 | //System.out.println(e1); 44 | //step2: try to deserialize from DBZ json event 45 | try { 46 | System.err.println("Checking if it's a Debezium event..."); 47 | Map temp = (Map) OBJECT_MAPPER.readValue(new String(bytes), Map.class) 48 | .get(DBZ_CDC_EVENT_PAYLOAD_FIELD); 49 | if (temp == null) { 50 | temp = new HashMap(); 51 | //NOTE: we record a delete event in this case 52 | temp.put("_eventType", EventType.DELETE); 53 | } 54 | // TODO 55 | //data = OBJECT_MAPPER.readValue(OBJECT_MAPPER.writeValueAsBytes(temp), clazz); 56 | 57 | if (isKey) { 58 | System.err.println("Might be a Debezium message for key..."); 59 | // The key is decoded directly into Integer => this is an ugly code, but should work 60 | data = OBJECT_MAPPER.readValue(OBJECT_MAPPER.writeValueAsBytes(temp.get("id")), clazz); 61 | if (data != null) { 62 | System.err.println("Just create a new key: " + data.toString()); 63 | } else { 64 | System.err.println("Create a null key object..."); 65 | } 66 | } else { 67 | System.err.println("Might be a Debezium message for payload..."); 68 | data = OBJECT_MAPPER.readValue(OBJECT_MAPPER.writeValueAsBytes(temp.get(DBZ_CDC_EVENT_AFTER_FIELD)), clazz); 69 | if (data != null) { 70 | System.err.println("Just create a new: " + data.toString()); 71 | } else { 72 | System.err.println("Create a null object..."); 73 | } 74 | } 75 | } catch (IOException e2) { 76 | throw new SerializationException(e2); 77 | } 78 | } 79 | 80 | return data; 81 | } 82 | 83 | @Override 84 | public void close() { } 85 | } 86 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/src/main/java/com/github/lbroudoux/chucknorris/filter/serdes/JsonPojoSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.serdes; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.apache.kafka.common.errors.SerializationException; 5 | import org.apache.kafka.common.serialization.Serializer; 6 | 7 | import java.util.Map; 8 | /** 9 | * @author laurent 10 | */ 11 | public class JsonPojoSerializer implements Serializer { 12 | 13 | protected final static ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 14 | 15 | @Override 16 | public void configure(Map props, boolean isKey) { } 17 | 18 | @Override 19 | public byte[] serialize(String topic, T data) { 20 | 21 | if (data == null) 22 | return null; 23 | 24 | try { 25 | System.err.println("Serializing " + data.toString() + " for " + topic); 26 | System.err.println("Got: " + new String(OBJECT_MAPPER.writeValueAsBytes(data))); 27 | return OBJECT_MAPPER.writeValueAsBytes(data); 28 | } catch (Exception e) { 29 | throw new SerializationException("Error serializing JSON message", e); 30 | } 31 | 32 | } 33 | 34 | @Override 35 | public void close() { } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/src/main/java/com/github/lbroudoux/chucknorris/filter/serdes/SerdeFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.serdes; 2 | 3 | import com.github.lbroudoux.chucknorris.filter.model.CustomerRentalMovieAggregate; 4 | import org.apache.kafka.common.serialization.Deserializer; 5 | import org.apache.kafka.common.serialization.Serde; 6 | import org.apache.kafka.common.serialization.Serdes; 7 | import org.apache.kafka.common.serialization.Serializer; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | /** 12 | * @author laurent 13 | */ 14 | public class SerdeFactory { 15 | 16 | public static Serde createDbzEventJsonPojoSerdeFor(Class clazz, boolean isKey) { 17 | Map serdeProps = new HashMap<>(); 18 | serdeProps.put("serializedClass", clazz); 19 | 20 | Serializer ser = new JsonPojoSerializer<>(); 21 | ser.configure(serdeProps, isKey); 22 | 23 | Deserializer de = new JsonHybridDeserializer<>(); 24 | de.configure(serdeProps, isKey); 25 | 26 | return Serdes.serdeFrom(ser, de); 27 | } 28 | 29 | public static Serde CustomerRentalMovieAggregateSerde() { 30 | return new CustomerRentalMovieAggregateSerde(); 31 | } 32 | 33 | public static class CustomerRentalMovieAggregateSerde implements Serde { 34 | 35 | Serializer ser = new JsonPojoSerializer<>(); 36 | Deserializer de = new JsonHybridDeserializer<>(); 37 | 38 | public CustomerRentalMovieAggregateSerde() { 39 | Map serdeProps = new HashMap<>(); 40 | serdeProps.put("serializedClass", CustomerRentalMovieAggregate.class); 41 | 42 | ser.configure(serdeProps, false); 43 | de.configure(serdeProps, false); 44 | } 45 | 46 | @Override 47 | public void configure(Map map, boolean b) { } 48 | 49 | @Override 50 | public void close() { 51 | 52 | } 53 | 54 | @Override 55 | public Serializer serializer() { 56 | return ser; 57 | } 58 | 59 | @Override 60 | public Deserializer deserializer() { 61 | return de; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/src/main/resources/META-INF/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | chuck-norris-filter-kstreams-quarkus - 1.0-SNAPSHOT 6 | 99 | 100 | 101 | 102 | 105 | 106 |
107 |
108 |

Congratulations, you have created a new Quarkus application.

109 | 110 |

Why do you see this?

111 | 112 |

This page is served by Quarkus. The source is in 113 | src/main/resources/META-INF/resources/index.html.

114 | 115 |

What can I do from here?

116 | 117 |

If not already done, run the application in dev mode using: mvn compile quarkus:dev. 118 |

119 |
    120 |
  • Add REST resources, Servlets, functions and other services in src/main/java.
  • 121 |
  • Your static assets are located in src/main/resources/META-INF/resources.
  • 122 |
  • Configure your application in src/main/resources/application.properties. 123 |
  • 124 |
125 | 126 |

How do I get rid of this page?

127 |

Just delete the src/main/resources/META-INF/resources/index.html file.

128 |
129 |
130 |
131 |

Application

132 |
    133 |
  • GroupId: com.github.lbroudoux
  • 134 |
  • ArtifactId: chuck-norris-filter-kstreams-quarkus
  • 135 |
  • Version: 1.0-SNAPSHOT
  • 136 |
  • Quarkus Version: 1.2.1.Final
  • 137 |
138 |
139 |
140 |

Next steps

141 | 146 |
147 |
148 |
149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # Configuration file 2 | # key = value 3 | 4 | quarkus.kafka-streams.bootstrap-servers=my-cluster-kafka-bootstrap.amq-streams.svc.cluster.local:9092 5 | quarkus.kafka-streams.application-id=chuck-norris-streams-filter-quarkus 6 | quarkus.kafka-streams.application-server=${hostname}:8080 7 | quarkus.kafka-streams.topics=dbserver1.inventory.movie,dbserver1.inventory.customer,dbserver1.inventory.rental 8 | 9 | # pass-through options 10 | kafka-streams.cache.max.bytes.buffering=10240 11 | kafka-streams.commit.interval.ms=1000 12 | kafka-streams.metadata.max.age.ms=500 13 | kafka-streams.auto.offset.reset=earliest 14 | kafka-streams.metrics.recording.level=INFO -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/src/test/java/com/github/lbroudoux/chucknorris/filter/serdes/JsonPojoSerializerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.serdes; 2 | 3 | import com.github.lbroudoux.chucknorris.filter.model.Customer; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static org.junit.jupiter.api.Assertions.*; 11 | /** 12 | * @author laurent 13 | */ 14 | public class JsonPojoSerializerTest { 15 | 16 | @Test 17 | public void testSerialization() { 18 | Map serdeProps = new HashMap<>(); 19 | serdeProps.put("serializedClass", Customer.class); 20 | 21 | JsonPojoSerializer ser = new JsonPojoSerializer<>(); 22 | ser.configure(serdeProps, false); 23 | 24 | Customer customer = new Customer(null, 1, "Laurent", "Broudoux", "@lbroudoux"); 25 | byte[] result = ser.serialize("topic", customer); 26 | String resultStr = new String(result); 27 | System.err.println("Result: " + resultStr); 28 | 29 | assertTrue(resultStr.indexOf("first_name") != -1); 30 | assertTrue(resultStr.indexOf("last_name") != -1); 31 | assertTrue(resultStr.indexOf("twitter_handle") != -1); 32 | } 33 | } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams-quarkus/topic.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kafka.strimzi.io/v1alpha1 2 | kind: KafkaTopic 3 | metadata: 4 | name: rental-chuck-norris 5 | labels: 6 | strimzi.io/cluster: my-cluster 7 | spec: 8 | partitions: 1 9 | replicas: 3 10 | config: 11 | topicName: rental-chuck-norris 12 | cleanup.policy: compact 13 | retention.bytes: 107374182 14 | retention.ms: 86400000 15 | segment.bytes: 107374182 -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | chuck-norris-filter-kstreams 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | 25 | 1600092969864 26 | 27 | 30 28 | 29 | org.eclipse.core.resources.regexFilterMatcher 30 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding//src/test/java=UTF-8 4 | encoding/=UTF-8 5 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/.settings/org.eclipse.jdt.apt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.apt.aptEnabled=false 3 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 3 | org.eclipse.jdt.core.compiler.compliance=1.8 4 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled 5 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 6 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore 7 | org.eclipse.jdt.core.compiler.processAnnotations=disabled 8 | org.eclipse.jdt.core.compiler.release=disabled 9 | org.eclipse.jdt.core.compiler.source=1.8 10 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | 3 | RUN yum -y install java-1.8.0-openjdk-headless openssl && yum -y clean all 4 | 5 | # Set JAVA_HOME env var 6 | ENV JAVA_HOME /usr/lib/jvm/java 7 | 8 | # Copy scripts for starting Java apps 9 | COPY scripts/* /bin/ 10 | 11 | ARG version=latest 12 | ENV VERSION ${version} 13 | 14 | ADD target/chuck-norris-filter-kstreams.jar / 15 | 16 | CMD ["/bin/launch_java.sh", "/chuck-norris-filter-kstreams.jar"] -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "rental": { 3 | "id": 1, 4 | "user_id": 1, 5 | "movie_id": 1, 6 | "start_date": 1558093022000, 7 | "rental_duration": 3 8 | }, 9 | "movie": { 10 | "id": 1, 11 | "title": "The Delta Force", 12 | "year": 1986, 13 | "main_actor": "Chuck Norris" 14 | }, 15 | "customer": { 16 | "id": 1, 17 | "first_name": "Laurent", 18 | "last_name": "Broudoux", 19 | "twitter_handle": "@lbroudoux" 20 | } 21 | } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: chuck-norris-streams-filter 6 | name: chuck-norris-streams-filter 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: chuck-norris-filter 12 | template: 13 | metadata: 14 | labels: 15 | app: chuck-norris-filter 16 | spec: 17 | containers: 18 | - name: chuck-norris-filter 19 | image: quay.io/lbroudoux/chuck-norris-filter:latest 20 | env: 21 | - name: BOOTSTRAP_SERVERS 22 | value: my-cluster-kafka-bootstrap.amq-streams.svc.cluster.local:9092 23 | - name: MOVIES_SOURCE_TOPIC 24 | value: dbserver1.inventory.movie 25 | - name: CUSTOMERS_SOURCE_TOPIC 26 | value: dbserver1.inventory.customer 27 | - name: RENTALS_SOURCE_TOPIC 28 | value: dbserver1.inventory.rental 29 | - name: TARGET_TOPIC 30 | value: rental-chuck-norris -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.github.lbroudoux 7 | chuck-norris-filter-kstreams 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | chuck-norris-filter-kstreams 12 | Chuck Norris location filter using KafkaStreams 13 | 14 | 15 | UTF-8 16 | UTF-8 17 | 1.8 18 | 2.1.1 19 | 3.5.1 20 | 3.1.0 21 | 3.0.2 22 | 23 | 24 | 25 | 26 | org.apache.kafka 27 | kafka-streams 28 | ${kafka.version} 29 | 30 | 31 | org.slf4j 32 | slf4j-simple 33 | 1.7.22 34 | 35 | 36 | org.apache.kafka 37 | kafka-streams-test-utils 38 | ${kafka.version} 39 | test 40 | 41 | 42 | junit 43 | junit 44 | 4.12 45 | test 46 | 47 | 48 | 49 | 50 | 51 | 52 | org.apache.maven.plugins 53 | maven-compiler-plugin 54 | ${mvn.compiler.version} 55 | 56 | ${java.version} 57 | ${java.version} 58 | 59 | 60 | 61 | org.apache.maven.plugins 62 | maven-shade-plugin 63 | ${mvn.shade.version} 64 | 65 | 66 | package 67 | 68 | shade 69 | 70 | 71 | chuck-norris-filter-kstreams 72 | 73 | 74 | 75 | com.github.lbroudoux.chucknorris.filter.Main 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-dependency-plugin 89 | ${mvn.dependency.version} 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/scripts/dynamic_resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | function get_heap_size { 4 | CONTAINER_MEMORY_IN_BYTES=`cat /sys/fs/cgroup/memory/memory.limit_in_bytes` 5 | DEFAULT_MEMORY_CEILING=$((2**60-1)) 6 | if [ "${CONTAINER_MEMORY_IN_BYTES}" -lt "${DEFAULT_MEMORY_CEILING}" ]; then 7 | if [ -z $CONTAINER_HEAP_PERCENT ]; then 8 | CONTAINER_HEAP_PERCENT=0.50 9 | fi 10 | 11 | CONTAINER_MEMORY_IN_MB=$((${CONTAINER_MEMORY_IN_BYTES}/1024**2)) 12 | CONTAINER_HEAP_MAX=$(echo "${CONTAINER_MEMORY_IN_MB} ${CONTAINER_HEAP_PERCENT}" | awk '{ printf "%d", $1 * $2 }') 13 | 14 | echo "${CONTAINER_HEAP_MAX}" 15 | fi 16 | } 17 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/scripts/launch_java.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -x 3 | JAR=$1 4 | shift 5 | 6 | . /bin/dynamic_resources.sh 7 | 8 | MAX_HEAP=`get_heap_size` 9 | if [ -n "$MAX_HEAP" ]; then 10 | JAVA_OPTS="-Xms${MAX_HEAP}m -Xmx${MAX_HEAP}m $JAVA_OPTS" 11 | fi 12 | 13 | export MALLOC_ARENA_MAX=2 14 | 15 | # Make sure that we use /dev/urandom 16 | JAVA_OPTS="${JAVA_OPTS} -Dvertx.cacheDirBase=/tmp -Djava.security.egd=file:/dev/./urandom" 17 | 18 | # Enable GC logging for memory tracking 19 | JAVA_OPTS="${JAVA_OPTS} -XX:NativeMemoryTracking=summary -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps" 20 | 21 | exec java $JAVA_OPTS -jar $JAR $JAVA_OPTS $@ -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/src/main/java/com/github/lbroudoux/chucknorris/filter/Example.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter; 2 | 3 | import org.apache.kafka.common.serialization.Serdes; 4 | import org.apache.kafka.common.utils.Bytes; 5 | import org.apache.kafka.streams.KafkaStreams; 6 | import org.apache.kafka.streams.StreamsBuilder; 7 | import org.apache.kafka.streams.StreamsConfig; 8 | import org.apache.kafka.streams.kstream.*; 9 | import org.apache.kafka.streams.state.KeyValueStore; 10 | 11 | import java.util.Arrays; 12 | import java.util.Properties; 13 | 14 | /** 15 | * @author laurent 16 | */ 17 | public class Example { 18 | 19 | public static void main(final String[] args) { 20 | 21 | Properties props = new Properties(); 22 | props.put(StreamsConfig.APPLICATION_ID_CONFIG, "wordcount-application"); 23 | props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); 24 | props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass()); 25 | props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass()); 26 | 27 | StreamsBuilder builder = new StreamsBuilder(); 28 | KStream textLines = builder.stream("streams-plaintext-input"); 29 | KTable wordCounts = textLines 30 | .flatMapValues(textLine -> Arrays.asList(textLine.toLowerCase().split("\\W+"))) 31 | .groupBy((key, word) -> word) 32 | .count(Materialized.>as("counts-store")); 33 | wordCounts.toStream().to("streams-wordcount-output", Produced.with(Serdes.String(), Serdes.Long())); 34 | 35 | KafkaStreams streams = new KafkaStreams(builder.build(), props); 36 | streams.start(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/src/main/java/com/github/lbroudoux/chucknorris/filter/FilterConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter; 2 | 3 | import com.github.lbroudoux.chucknorris.filter.model.CustomerRentalMovieAggregate; 4 | import com.github.lbroudoux.chucknorris.filter.serdes.SerdeFactory; 5 | import org.apache.kafka.common.serialization.Serde; 6 | import org.apache.kafka.common.serialization.Serdes; 7 | import org.apache.kafka.streams.StreamsConfig; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.Properties; 12 | /** 13 | * @author laurent 14 | */ 15 | public class FilterConfig { 16 | 17 | private static final Logger log = LoggerFactory.getLogger(FilterConfig.class.getName()); 18 | 19 | private final String bootstrapServers; 20 | private final String rentalsSourceTopic; 21 | private final String moviesSourceTopic; 22 | private final String customersSourceTopic; 23 | private final String targetTopic; 24 | private final String trustStorePassword; 25 | private final String trustStorePath; 26 | private final String keyStorePassword; 27 | private final String keyStorePath; 28 | private final String username; 29 | private final String password; 30 | 31 | public FilterConfig(String bootstrapServers, String rentalsSourceTopic, String moviesSourceTopic, String customersSourceTopic, String targetTopic, String trustStorePassword, String trustStorePath, String keyStorePassword, String keyStorePath, String username, String password) { 32 | this.bootstrapServers = bootstrapServers; 33 | this.rentalsSourceTopic = rentalsSourceTopic; 34 | this.moviesSourceTopic = moviesSourceTopic; 35 | this.customersSourceTopic = customersSourceTopic; 36 | this.targetTopic = targetTopic; 37 | this.trustStorePassword = trustStorePassword; 38 | this.trustStorePath = trustStorePath; 39 | this.keyStorePassword = keyStorePassword; 40 | this.keyStorePath = keyStorePath; 41 | this.username = username; 42 | this.password = password; 43 | } 44 | 45 | public static FilterConfig fromEnv() { 46 | String bootstrapServers = System.getenv("BOOTSTRAP_SERVERS"); 47 | String rentalsSourceTopic = System.getenv("RENTALS_SOURCE_TOPIC"); 48 | String moviesSourceTopic = System.getenv("MOVIES_SOURCE_TOPIC"); 49 | String customersSourceTopic = System.getenv("CUSTOMERS_SOURCE_TOPIC"); 50 | String targetTopic = System.getenv("TARGET_TOPIC"); 51 | String trustStorePassword = System.getenv("TRUSTSTORE_PASSWORD") == null ? null : System.getenv("TRUSTSTORE_PASSWORD"); 52 | String trustStorePath = System.getenv("TRUSTSTORE_PATH") == null ? null : System.getenv("TRUSTSTORE_PATH"); 53 | String keyStorePassword = System.getenv("KEYSTORE_PASSWORD") == null ? null : System.getenv("KEYSTORE_PASSWORD"); 54 | String keyStorePath = System.getenv("KEYSTORE_PATH") == null ? null : System.getenv("KEYSTORE_PATH"); 55 | String username = System.getenv("USERNAME") == null ? null : System.getenv("USERNAME"); 56 | String password = System.getenv("PASSWORD") == null ? null : System.getenv("PASSWORD"); 57 | 58 | return new FilterConfig(bootstrapServers, rentalsSourceTopic, moviesSourceTopic, customersSourceTopic, targetTopic, trustStorePassword, trustStorePath, keyStorePassword, keyStorePath, username, password); 59 | 60 | //return new FilterConfig("localhost:9092", "rental.rentals", "rental.movies", 61 | // "rental.customers", "rental.chucknorris", trustStorePassword, trustStorePath, keyStorePassword, keyStorePath, username, password); 62 | } 63 | 64 | public String getBootstrapServers() { 65 | return bootstrapServers; 66 | } 67 | public String getRentalsTopic() { 68 | return rentalsSourceTopic; 69 | } 70 | public String getMoviesTopic() { 71 | return moviesSourceTopic; 72 | } 73 | public String getCustomersTopic() { 74 | return customersSourceTopic; 75 | } 76 | public String getTargetTopic() { 77 | return targetTopic; 78 | } 79 | 80 | public String getTrustStorePassword() { 81 | return trustStorePassword; 82 | } 83 | public String getTrustStorePath() { 84 | return trustStorePath; 85 | } 86 | public String getKeyStorePassword() { 87 | return keyStorePassword; 88 | } 89 | public String getKeyStorePath() { 90 | return keyStorePath; 91 | } 92 | public String getUsername() { 93 | return username; 94 | } 95 | public String getPassword() { 96 | return password; 97 | } 98 | 99 | public static Properties getKafkaStreamsProperties(FilterConfig config) { 100 | Properties props = new Properties(); 101 | props.put(StreamsConfig.APPLICATION_ID_CONFIG, "chuck-norris-filter"); 102 | props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, config.getBootstrapServers()); 103 | props.put(StreamsConfig.COMMIT_INTERVAL_MS_CONFIG, 5000); 104 | props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.Integer().getClass()); 105 | props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, SerdeFactory.CustomerRentalMovieAggregateSerde().getClass()); 106 | 107 | if (config.getTrustStorePassword() != null && config.getTrustStorePath() != null) { 108 | log.info("Configuring truststore"); 109 | props.put("security.protocol", "SSL"); 110 | props.put("ssl.truststore.type", "PKCS12"); 111 | props.put("ssl.truststore.password", config.getTrustStorePassword()); 112 | props.put("ssl.truststore.location", config.getTrustStorePath()); 113 | } 114 | 115 | if (config.getKeyStorePassword() != null && config.getKeyStorePath() != null) { 116 | log.info("Configuring keystore"); 117 | props.put("security.protocol", "SSL"); 118 | props.put("ssl.keystore.type", "PKCS12"); 119 | props.put("ssl.keystore.password", config.getKeyStorePassword()); 120 | props.put("ssl.keystore.location", config.getKeyStorePath()); 121 | } 122 | 123 | if (config.getUsername() != null && config.getPassword() != null) { 124 | props.put("sasl.mechanism","SCRAM-SHA-512"); 125 | props.put("sasl.jaas.config", "org.apache.kafka.common.security.scram.ScramLoginModule required username=\"" + config.getUsername() + "\" password=\"" + config.getPassword() + "\";"); 126 | 127 | if (props.get("security.protocol") != null && props.get("security.protocol").equals("SSL")) { 128 | props.put("security.protocol","SASL_SSL"); 129 | } else { 130 | props.put("security.protocol","SASL_PLAINTEXT"); 131 | } 132 | } 133 | 134 | return props; 135 | } 136 | 137 | } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/src/main/java/com/github/lbroudoux/chucknorris/filter/Main.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter; 2 | 3 | import org.apache.kafka.common.serialization.Serde; 4 | import org.apache.kafka.common.serialization.Serdes; 5 | import org.apache.kafka.common.utils.Bytes; 6 | import org.apache.kafka.streams.KafkaStreams; 7 | import org.apache.kafka.streams.KeyValue; 8 | import org.apache.kafka.streams.StreamsBuilder; 9 | import org.apache.kafka.streams.kstream.*; 10 | 11 | import com.github.lbroudoux.chucknorris.filter.model.*; 12 | import com.github.lbroudoux.chucknorris.filter.serdes.SerdeFactory; 13 | import org.apache.kafka.streams.state.KeyValueStore; 14 | 15 | import java.util.Properties; 16 | 17 | /** 18 | * @author laurent 19 | */ 20 | public class Main { 21 | 22 | public static void main(final String[] args) { 23 | 24 | FilterConfig config = FilterConfig.fromEnv(); 25 | Properties kafkaStreamsConfig = FilterConfig.getKafkaStreamsProperties(config); 26 | 27 | // Configure all the Serdes. 28 | final Serde defaultIdSerde = SerdeFactory.createDbzEventJsonPojoSerdeFor(Integer.class, true); 29 | final Serde movieSerde = SerdeFactory.createDbzEventJsonPojoSerdeFor(Movie.class, false); 30 | final Serde userSerde = SerdeFactory.createDbzEventJsonPojoSerdeFor(Customer.class, false); 31 | final Serde rentalSerde = SerdeFactory.createDbzEventJsonPojoSerdeFor(Rental.class, false); 32 | final Serde aggregateSerde = SerdeFactory.createDbzEventJsonPojoSerdeFor(CustomerRentalMovieAggregate.class, false); 33 | 34 | // Getting a streams builder. 35 | StreamsBuilder builder = new StreamsBuilder(); 36 | 37 | // We need to make sure that all data are loaded at every restart => we need to use KTable. 38 | // KStream would continue where it left off, so after restart you will get an empty table. 39 | // The partitioning is not aligned between the different topics. 40 | // Both use the same number of partitions, but not the same key (rental_id versus movie_id versus customer_id) 41 | // => we need to use GlobalKTable to make sure that the application will always have all the data even when use use more than one partition. 42 | // Since we want the GlobalKTable with the integer key, we have to use the right Serde which would decode the ID into Integer. 43 | GlobalKTable usersTableInt = builder.globalTable(config.getCustomersTopic(), Consumed.with(defaultIdSerde, userSerde)); 44 | GlobalKTable moviesTableInt = builder.globalTable(config.getMoviesTopic(), Consumed.with(defaultIdSerde, movieSerde)); 45 | 46 | // Create Stream for rental: we are looking at each changes. 47 | KStream rentalsStream = builder.stream(config.getRentalsTopic(), Consumed.with(defaultIdSerde, rentalSerde)) 48 | .map((rentalId, rental) -> new KeyValue<>(rental.getMovieId(), rental)); 49 | //rentalsStream.print(Printed.toSysOut()); 50 | 51 | // Now, let the magic happens!! 52 | KStream chuckNorrisRentalsStream = 53 | rentalsStream 54 | // leftJoin would let null right sides through which is hard to handle in what we are doing here 55 | // using join is better because while some rental records might be skipped, we will not get ugly null messages and NPEs 56 | .join(moviesTableInt, 57 | (leftKey, leftValue) -> leftKey, 58 | (rental, movie) -> new CustomerRentalMovieAggregate(rental, movie)) 59 | .filter((movieId, urmAggregate) -> urmAggregate.getMovie().getMainActor().equals("Chuck Norris")) 60 | .map((movieId, urmAggregate) -> new KeyValue<>(urmAggregate.getRental().getCustomerId(), urmAggregate)) 61 | // Same as above, leftJoin would let null right sides through which is hard to handle in what we are doing here 62 | // using join is better because while some rental records might be skipped, we will not get ugly null messages and NPEs 63 | .join(usersTableInt, 64 | (leftKey, leftValue) -> leftKey, 65 | (urmAggregate, customer) -> completeAggregate(urmAggregate, customer)); 66 | chuckNorrisRentalsStream.to(config.getTargetTopic(), Produced.with(Serdes.Integer(), aggregateSerde)); 67 | 68 | chuckNorrisRentalsStream.print(Printed.toSysOut()); 69 | 70 | // Building everything and run it. 71 | KafkaStreams streams = new KafkaStreams(builder.build(), kafkaStreamsConfig); 72 | System.err.println("Starting streams!"); 73 | streams.start(); 74 | } 75 | 76 | private static CustomerRentalMovieAggregate completeAggregate(CustomerRentalMovieAggregate urmAggregate, Customer customer) { 77 | System.err.println("Completing CustomerRentalMovieAggregate with " + customer.toString()); 78 | urmAggregate.setCustomer(customer); 79 | return urmAggregate; 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/src/main/java/com/github/lbroudoux/chucknorris/filter/model/Customer.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.model; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | /** 5 | * @author laurent 6 | */ 7 | public class Customer { 8 | 9 | private final EventType eventType; 10 | 11 | private final Integer id; 12 | private final String firstname; 13 | private final String lastname; 14 | private final String twitterHandle; 15 | 16 | @JsonCreator 17 | public Customer(@JsonProperty("_eventType") EventType _eventType, 18 | @JsonProperty("id") Integer id, 19 | @JsonProperty("first_name") String firstname, 20 | @JsonProperty("last_name") String lastname, 21 | @JsonProperty("twitter_handle") String twitterHandle) { 22 | 23 | this.eventType = _eventType == null ? EventType.UPSERT : _eventType; 24 | this.id = id; 25 | this.firstname = firstname; 26 | this.lastname = lastname; 27 | this.twitterHandle = twitterHandle; 28 | } 29 | 30 | /** 31 | * @return the eventType 32 | */ 33 | public EventType getEventType() { 34 | return eventType; 35 | } 36 | 37 | /** 38 | * @return the id 39 | */ 40 | public Integer getId() { 41 | return id; 42 | } 43 | 44 | /** 45 | * @return the firstName 46 | */ 47 | public String getFirstname() { 48 | return firstname; 49 | } 50 | 51 | /** 52 | * @return the lastName 53 | */ 54 | public String getLastname() { 55 | return lastname; 56 | } 57 | 58 | /** 59 | * @return the twitterHandle 60 | */ 61 | public String getTwitterHandle() { 62 | return twitterHandle; 63 | } 64 | 65 | 66 | @Override 67 | public String toString() { 68 | return "Customer{" + 69 | "_eventType='" + eventType + '\'' + 70 | ", id=" + id + 71 | ", first_name='" + firstname + '\'' + 72 | ", last_name='" + lastname + '\'' + 73 | ", twitter_handle='" + twitterHandle + '\'' + 74 | "}"; 75 | } 76 | } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/src/main/java/com/github/lbroudoux/chucknorris/filter/model/CustomerRentalMovieAggregate.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | /** 7 | * @author laurent 8 | */ 9 | public class CustomerRentalMovieAggregate { 10 | 11 | @JsonProperty("movie") 12 | private final Movie movie; 13 | 14 | @JsonProperty("customer") 15 | private Customer customer; 16 | 17 | @JsonProperty("rental") 18 | private final Rental rental; 19 | 20 | 21 | @JsonCreator 22 | public CustomerRentalMovieAggregate( 23 | @JsonProperty("rental") Rental rental, 24 | @JsonProperty("movie") Movie movie) { 25 | this.rental = rental; 26 | this.movie = movie; 27 | if (movie != null && rental != null) { 28 | System.err.println("Building new CustomerRentalMovieAggregate with " + movie.toString() + " and " + rental.toString()); 29 | } 30 | if (movie == null) { 31 | System.err.println("Got null movie..."); 32 | } 33 | if (rental == null) { 34 | System.err.println("Got null rental..."); 35 | } 36 | } 37 | 38 | public Movie getMovie() { 39 | return movie; 40 | } 41 | 42 | public Customer getCustomer() { 43 | return customer; 44 | } 45 | 46 | public void setCustomer(Customer customer) { 47 | this.customer = customer; 48 | } 49 | 50 | public Rental getRental() { 51 | return rental; 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return "CustomerRentalMovieAggregate{" + 57 | "rental=" + rental + 58 | ", movie=" + movie + 59 | ", customer=" + customer + 60 | "}"; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/src/main/java/com/github/lbroudoux/chucknorris/filter/model/DefaultId.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.model; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | import com.fasterxml.jackson.core.JsonGenerator; 5 | import com.fasterxml.jackson.databind.*; 6 | 7 | import java.io.IOException; 8 | 9 | /** 10 | * @author laurent 11 | */ 12 | public class DefaultId { 13 | 14 | @JsonIgnore 15 | private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 16 | 17 | private final Integer id; 18 | 19 | @JsonCreator 20 | public DefaultId(@JsonProperty("id") Integer id) { 21 | this.id = id; 22 | } 23 | 24 | public Integer getId() { 25 | return id; 26 | } 27 | 28 | public static class IdDeserializer extends KeyDeserializer { 29 | 30 | @Override 31 | public DefaultId deserializeKey( 32 | String key, 33 | DeserializationContext ctx) throws IOException { 34 | 35 | return OBJECT_MAPPER.readValue(key, DefaultId.class); 36 | } 37 | } 38 | 39 | public static class IdSerializer extends JsonSerializer { 40 | 41 | @Override 42 | public void serialize(DefaultId key, 43 | JsonGenerator gen, 44 | SerializerProvider serializers) 45 | throws IOException { 46 | 47 | gen.writeFieldName(OBJECT_MAPPER.writeValueAsString(key)); 48 | } 49 | } 50 | 51 | @Override 52 | public boolean equals(Object o) { 53 | if (this == o) { 54 | return true; 55 | } 56 | if (o == null || getClass() != o.getClass()) { 57 | return false; 58 | } 59 | 60 | DefaultId defaultId = (DefaultId) o; 61 | 62 | return id != null ? id.equals(defaultId.id) : defaultId.id == null; 63 | } 64 | 65 | @Override 66 | public int hashCode() { 67 | return id != null ? id.hashCode() : 0; 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | return "DefaultId{" + 73 | "id=" + id + 74 | '}'; 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/src/main/java/com/github/lbroudoux/chucknorris/filter/model/EventType.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.model; 2 | 3 | public enum EventType { UPSERT, DELETE } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/src/main/java/com/github/lbroudoux/chucknorris/filter/model/Movie.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.model; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | /** 5 | * @author laurent 6 | */ 7 | public class Movie { 8 | 9 | private final EventType eventType; 10 | 11 | private final Integer id; 12 | private final String title; 13 | private final Integer year; 14 | private final String mainActor; 15 | 16 | @JsonCreator 17 | public Movie(@JsonProperty("_eventType") EventType _eventType, 18 | @JsonProperty("id") Integer id, 19 | @JsonProperty("title") String title, 20 | @JsonProperty("year") Integer year, 21 | @JsonProperty("main_actor") String mainActor) { 22 | 23 | this.eventType = _eventType == null ? EventType.UPSERT : _eventType; 24 | this.id = id; 25 | this.title = title; 26 | this.year = year; 27 | this.mainActor = mainActor; 28 | } 29 | 30 | /** 31 | * @return the eventType 32 | */ 33 | public EventType getEventType() { 34 | return eventType; 35 | } 36 | 37 | /** 38 | * @return the id 39 | */ 40 | public Integer getId() { 41 | return id; 42 | } 43 | 44 | /** 45 | * @return the title 46 | */ 47 | public String getTitle() { 48 | return title; 49 | } 50 | 51 | /** 52 | * @return the year 53 | */ 54 | public Integer getYear() { 55 | return year; 56 | } 57 | 58 | /** 59 | * @return the mainActor 60 | */ 61 | public String getMainActor() { 62 | return mainActor; 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return "Movie{" + 68 | "_eventType='" + eventType + '\'' + 69 | ", id=" + id + 70 | ", title='" + title + '\'' + 71 | ", main_actor='" + mainActor + '\'' + 72 | ", year='" + year + '\'' + 73 | "}"; 74 | } 75 | } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/src/main/java/com/github/lbroudoux/chucknorris/filter/model/Rental.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.model; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | import java.util.Date; 5 | /** 6 | * @author laurent 7 | */ 8 | public class Rental { 9 | 10 | private final EventType eventType; 11 | 12 | private final Integer id; 13 | private final Integer customerId; 14 | private final Integer movieId; 15 | private final Date startDate; 16 | private final Integer rentalDuration; 17 | 18 | @JsonCreator 19 | public Rental(@JsonProperty("_eventType") EventType _eventType, 20 | @JsonProperty("id") Integer id, 21 | @JsonProperty("customer_id") Integer customerId, 22 | @JsonProperty("movie_id") Integer movieId, 23 | @JsonProperty("start_date") Date startDate, 24 | @JsonProperty("rental_duration") Integer rentalDuration) { 25 | 26 | this.eventType = _eventType == null ? EventType.UPSERT : _eventType; 27 | this.id = id; 28 | this.customerId = customerId; 29 | this.movieId = movieId; 30 | this.startDate = startDate; 31 | this.rentalDuration = rentalDuration; 32 | } 33 | 34 | /** 35 | * @return the eventType 36 | */ 37 | public EventType getEventType() { 38 | return eventType; 39 | } 40 | 41 | /** 42 | * @return the id 43 | */ 44 | public Integer getId() { 45 | return id; 46 | } 47 | 48 | /** 49 | * @return the customerId 50 | */ 51 | public Integer getCustomerId() { 52 | return customerId; 53 | } 54 | 55 | /** 56 | * @return the movieId 57 | */ 58 | public Integer getMovieId() { 59 | return movieId; 60 | } 61 | 62 | /** 63 | * @return the startDate 64 | */ 65 | public Date getStartDate() { 66 | return startDate; 67 | } 68 | 69 | /** 70 | * @return the rentalDuration 71 | */ 72 | public Integer getRentalDuration() { 73 | return rentalDuration; 74 | } 75 | 76 | 77 | @Override 78 | public String toString() { 79 | return "Rental{" + 80 | "_eventType='" + eventType + '\'' + 81 | ", id=" + id + 82 | ", customer_id='" + customerId + '\'' + 83 | ", movie_id='" + movieId + '\'' + 84 | ", startDate='" + startDate + '\'' + 85 | "}"; 86 | } 87 | } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/src/main/java/com/github/lbroudoux/chucknorris/filter/serdes/JsonHybridDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.serdes; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.apache.kafka.common.errors.SerializationException; 5 | import org.apache.kafka.common.serialization.Deserializer; 6 | 7 | import com.github.lbroudoux.chucknorris.filter.model.EventType; 8 | 9 | import java.io.IOException; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | public class JsonHybridDeserializer implements Deserializer { 14 | 15 | public static final String DBZ_CDC_EVENT_PAYLOAD_FIELD = "payload"; 16 | public static final String DBZ_CDC_EVENT_AFTER_FIELD = "after"; 17 | 18 | private final static ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 19 | 20 | private Class clazz; 21 | private boolean isKey; 22 | 23 | @SuppressWarnings("unchecked") 24 | @Override 25 | public void configure(Map props, boolean isKey) { 26 | clazz = (Class)props.get("serializedClass"); 27 | this.isKey = isKey; 28 | } 29 | 30 | @Override 31 | public T deserialize(String topic, byte[] bytes) { 32 | 33 | if (bytes == null) 34 | return null; 35 | 36 | T data; 37 | 38 | //step1: try to directly deserialize expected class 39 | try { 40 | data = OBJECT_MAPPER.readValue(bytes, clazz); 41 | } catch (IOException e1) { 42 | //System.out.println(e1); 43 | //step2: try to deserialize from DBZ json event 44 | try { 45 | Map temp = (Map) OBJECT_MAPPER.readValue(new String(bytes), Map.class) 46 | .get(DBZ_CDC_EVENT_PAYLOAD_FIELD); 47 | if (temp == null) { 48 | temp = new HashMap(); 49 | //NOTE: we record a delete event in this case 50 | temp.put("_eventType", EventType.DELETE); 51 | } 52 | // TODO 53 | //data = OBJECT_MAPPER.readValue(OBJECT_MAPPER.writeValueAsBytes(temp), clazz); 54 | 55 | if (isKey) { 56 | // The key is decoded directly into Integer => this is an ugly code, but should work 57 | data = OBJECT_MAPPER.readValue(OBJECT_MAPPER.writeValueAsBytes(temp.get("id")), clazz); 58 | if (data != null) { 59 | System.err.println("Just create a new key: " + data.toString()); 60 | } else { 61 | System.err.println("Create a null key object..."); 62 | } 63 | } else { 64 | data = OBJECT_MAPPER.readValue(OBJECT_MAPPER.writeValueAsBytes(temp.get(DBZ_CDC_EVENT_AFTER_FIELD)), clazz); 65 | if (data != null) { 66 | System.err.println("Just create a new: " + data.toString()); 67 | } else { 68 | System.err.println("Create a null object..."); 69 | } 70 | } 71 | } catch(IOException e2) { 72 | throw new SerializationException(e2); 73 | } 74 | } 75 | 76 | return data; 77 | } 78 | 79 | @Override 80 | public void close() { } 81 | } 82 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/src/main/java/com/github/lbroudoux/chucknorris/filter/serdes/JsonPojoSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.serdes; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.apache.kafka.common.errors.SerializationException; 5 | import org.apache.kafka.common.serialization.Serializer; 6 | 7 | import java.util.Map; 8 | /** 9 | * @author laurent 10 | */ 11 | public class JsonPojoSerializer implements Serializer { 12 | 13 | protected final static ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 14 | 15 | @Override 16 | public void configure(Map props, boolean isKey) { } 17 | 18 | @Override 19 | public byte[] serialize(String topic, T data) { 20 | 21 | if (data == null) 22 | return null; 23 | 24 | try { 25 | return OBJECT_MAPPER.writeValueAsBytes(data); 26 | } catch (Exception e) { 27 | throw new SerializationException("Error serializing JSON message", e); 28 | } 29 | 30 | } 31 | 32 | @Override 33 | public void close() { } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/src/main/java/com/github/lbroudoux/chucknorris/filter/serdes/SerdeFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter.serdes; 2 | 3 | import com.github.lbroudoux.chucknorris.filter.model.CustomerRentalMovieAggregate; 4 | import org.apache.kafka.common.serialization.Deserializer; 5 | import org.apache.kafka.common.serialization.Serde; 6 | import org.apache.kafka.common.serialization.Serdes; 7 | import org.apache.kafka.common.serialization.Serializer; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | /** 12 | * @author laurent 13 | */ 14 | public class SerdeFactory { 15 | 16 | public static Serde createDbzEventJsonPojoSerdeFor(Class clazz, boolean isKey) { 17 | Map serdeProps = new HashMap<>(); 18 | serdeProps.put("serializedClass", clazz); 19 | 20 | Serializer ser = new JsonPojoSerializer<>(); 21 | ser.configure(serdeProps, isKey); 22 | 23 | Deserializer de = new JsonHybridDeserializer<>(); 24 | de.configure(serdeProps, isKey); 25 | 26 | return Serdes.serdeFrom(ser, de); 27 | } 28 | 29 | public static Serde CustomerRentalMovieAggregateSerde() { 30 | return new CustomerRentalMovieAggregateSerde(); 31 | } 32 | 33 | public static class CustomerRentalMovieAggregateSerde implements Serde { 34 | 35 | Serializer ser = new JsonPojoSerializer<>(); 36 | Deserializer de = new JsonHybridDeserializer<>(); 37 | 38 | public CustomerRentalMovieAggregateSerde() { 39 | Map serdeProps = new HashMap<>(); 40 | serdeProps.put("serializedClass", CustomerRentalMovieAggregate.class); 41 | 42 | ser.configure(serdeProps, false); 43 | de.configure(serdeProps, false); 44 | } 45 | 46 | @Override 47 | public void configure(Map map, boolean b) { } 48 | 49 | @Override 50 | public void close() { 51 | 52 | } 53 | 54 | @Override 55 | public Serializer serializer() { 56 | return ser; 57 | } 58 | 59 | @Override 60 | public Deserializer deserializer() { 61 | return de; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/src/test/java/com/github/lbroudoux/chucknorris/filter/FilterTest.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.chucknorris.filter; 2 | 3 | import com.github.lbroudoux.chucknorris.filter.model.*; 4 | import com.github.lbroudoux.chucknorris.filter.serdes.SerdeFactory; 5 | import org.apache.kafka.common.serialization.Serde; 6 | import org.apache.kafka.common.serialization.Serdes; 7 | import org.apache.kafka.streams.*; 8 | import org.apache.kafka.streams.kstream.Consumed; 9 | import org.apache.kafka.streams.kstream.KStream; 10 | import org.apache.kafka.streams.kstream.KTable; 11 | import org.apache.kafka.streams.state.KeyValueStore; 12 | import org.apache.kafka.streams.state.StoreBuilder; 13 | import org.apache.kafka.streams.state.Stores; 14 | import org.junit.After; 15 | import org.junit.Before; 16 | import org.junit.Test; 17 | 18 | import java.util.Properties; 19 | 20 | public class FilterTest { 21 | 22 | 23 | private TopologyTestDriver testDriver; 24 | private KeyValueStore store; 25 | 26 | @Before 27 | public void setup() { 28 | 29 | // Configure all the Serdes. 30 | final Serde defaultIdSerde = SerdeFactory.createDbzEventJsonPojoSerdeFor(DefaultId.class, true); 31 | final Serde movieSerde = SerdeFactory.createDbzEventJsonPojoSerdeFor(Movie.class, false); 32 | final Serde userSerde = SerdeFactory.createDbzEventJsonPojoSerdeFor(Customer.class, false); 33 | final Serde rentalSerde = SerdeFactory.createDbzEventJsonPojoSerdeFor(Rental.class, false); 34 | final Serde aggregateSerde = SerdeFactory.createDbzEventJsonPojoSerdeFor(CustomerRentalMovieAggregate.class, false); 35 | 36 | 37 | // Getting a streams builder. 38 | StreamsBuilder builder = new StreamsBuilder(); 39 | 40 | // Create Tables for reference data (we only want latest status). 41 | KTable usersTable = 42 | builder.table("customers", Consumed.with(defaultIdSerde, userSerde)); 43 | KTable moviesTable = 44 | builder.table("movies", Consumed.with(defaultIdSerde, movieSerde)); 45 | 46 | // Deal with Integer as key so re-aggregate the tables. 47 | KTable usersTableInt = usersTable.groupBy((key, customer) -> KeyValue.pair(customer.getId(), customer)) 48 | .aggregate( 49 | () -> null, // Initiate the aggregate value 50 | (userId, customer, aggValue) -> customer, // adder (doing nothing, just passing the user through as the value) 51 | (userId, customer, aggValue) -> customer // subtractor (doing nothing, just passing the user through as the value) 52 | ); 53 | KTable moviesTableInt = moviesTable.groupBy((key, movie) -> KeyValue.pair(movie.getId(), movie)) 54 | .aggregate( 55 | () -> null, // Initiate the aggregate value 56 | (movieId, movie, aggValue) -> movie, // adder (doing nothing, just passing the movie through as the value) 57 | (movieId, movie, aggValue) -> movie // subtractor (doing nothing, just passing the movie through as the value) 58 | ); 59 | 60 | // Create Stream for rental: we are looking at each changes. 61 | KStream rentalsStream = builder.stream("rentals", Consumed.with(defaultIdSerde, rentalSerde)); 62 | 63 | 64 | 65 | KStream chuckNorrisRentalsStream = 66 | rentalsStream 67 | .map((rentalId, rental) -> KeyValue.pair(rental.getMovieId(), rental)) 68 | .leftJoin(moviesTableInt, (rental, movie) -> 69 | rental.getEventType() == EventType.DELETE ? 70 | null : new CustomerRentalMovieAggregate(rental, (Movie) movie)); 71 | 72 | // Build the topology. 73 | Topology topology = builder.build(); 74 | 75 | 76 | // setup test driver 77 | Properties props = new Properties(); 78 | props.setProperty(StreamsConfig.APPLICATION_ID_CONFIG, "test"); 79 | props.setProperty(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "dummy:1234"); 80 | props.setProperty(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass().getName()); 81 | props.setProperty(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.Long().getClass().getName()); 82 | testDriver = new TopologyTestDriver(topology, props); 83 | 84 | // Pre-populate store. 85 | store = testDriver.getKeyValueStore("customers"); 86 | 87 | 88 | store.put("a", 21L); 89 | } 90 | 91 | @After 92 | public void tearDown() { 93 | testDriver.close(); 94 | } 95 | 96 | 97 | } 98 | -------------------------------------------------------------------------------- /chuck-norris-filter-kstreams/topic.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kafka.strimzi.io/v1alpha1 2 | kind: KafkaTopic 3 | metadata: 4 | name: rental-chuck-norris 5 | labels: 6 | strimzi.io/cluster: my-cluster 7 | spec: 8 | partitions: 1 9 | replicas: 3 10 | config: 11 | topicName: rental-chuck-norris 12 | cleanup.policy: compact 13 | retention.bytes: 107374182 14 | retention.ms: 86400000 15 | segment.bytes: 107374182 -------------------------------------------------------------------------------- /debezium-connect.yml: -------------------------------------------------------------------------------- 1 | apiVersion: kafka.strimzi.io/v1beta2 2 | kind: KafkaConnect 3 | metadata: 4 | name: debezium-connect 5 | annotations: 6 | strimzi.io/use-connector-resources: "true" 7 | spec: 8 | bootstrapServers: 'my-cluster-kafka-bootstrap.amq-streams.svc.cluster.local:9092' 9 | image: quay.io/lbroudoux/debezium-connect:1.6.2.Final 10 | replicas: 1 -------------------------------------------------------------------------------- /debezium-connector.yml: -------------------------------------------------------------------------------- 1 | apiVersion: kafka.strimzi.io/v1beta2 2 | kind: KafkaConnector 3 | metadata: 4 | name: debezium-connector 5 | labels: 6 | strimzi.io/cluster: debezium-connect 7 | spec: 8 | class: io.debezium.connector.mysql.MySqlConnector 9 | tasksMax: 1 10 | config: 11 | database.hostname: mysqldebezium.chuck-movie-rental.svc.cluster.local 12 | database.port: 3306 13 | database.user: debezium 14 | database.password: dbz 15 | database.server.id: "184054" 16 | database.server.name: dbserver1 17 | database.whitelist: inventory 18 | database.history.kafka.bootstrap.servers: my-cluster-kafka-bootstrap.amq-streams.svc.cluster.local:9092 19 | database.history.kafka.topic: schema-changes.inventory 20 | #snapshot.mode: schema_only_recovery 21 | -------------------------------------------------------------------------------- /fuse-online-routes/process-chuck-rentals-sf-lbr-export/model-info.json: -------------------------------------------------------------------------------- 1 | {"schemaVersion":27} -------------------------------------------------------------------------------- /fuse-online-routes/rental-event-to-telegram-lbr-export/model-info.json: -------------------------------------------------------------------------------- 1 | {"schemaVersion":27} -------------------------------------------------------------------------------- /fuse-online.md: -------------------------------------------------------------------------------- 1 | 2 | # Connectors setup 3 | 4 | ## Salesforce 5 | 6 | As described on [Connecting to Salesforce](https://access.redhat.com/documentation/en-us/red_hat_fuse/7.3/html-single/connecting_fuse_online_to_applications_and_services/index#connecting-to-sf_connectors), create a new Connector to SalesForce into your Fuse Online/Syndesis instance. 7 | 8 | ## Telegram 9 | 10 | As described on [Connecting to Telegram](https://access.redhat.com/documentation/en-us/red_hat_fuse/7.3/html-single/connecting_fuse_online_to_applications_and_services/index#connecting-to-telegram_connectors), create a new Connector to Telegram into your Fuse Online/Syndesis instance. 11 | 12 | ## Chuck Norris Facts API 13 | 14 | From the `Customizations` page, create a new API Connector by drag-and-dropping the `chuck-norris-facts-api-swagger.yaml` file at the root of this repository. This API will be called `chuck-norris-facts-api`. 15 | 16 | You will have then to create a new `Connection` using this connector and adpting the Host for this API connector depending on the route you create for `chuck-norris-facts-api` component on OpenShift. 17 | 18 | ## Develop & deploy integration routes 19 | 20 | ### Input events model 21 | 22 | As decribed into [README.md](README.md), events for integration routes are coming from the `rental-chuck-norris` Kafka topic and are a simple aggregate of the 3 entities. Here's a sample to use for the routes configuration : 23 | 24 | ``` 25 | { 26 | "rental": { 27 | "id": 1, 28 | "user_id": 1, 29 | "movie_id": 1, 30 | "start_date": "2019-05-16", 31 | "rental_duration": 3 32 | }, 33 | "movie": { 34 | "id": 1, 35 | "title": "The Delta Force", 36 | "year": 1986, 37 | "main_actor": "Chuck Norris" 38 | }, 39 | "customer": { 40 | "id": 1, 41 | "first_name": "Laurent", 42 | "last_name": "Broudoux", 43 | "twitter_handle": "@lbroudoux" 44 | } 45 | } 46 | ``` 47 | 48 | ### Kafka rental-chuck-norris to Salesforce 49 | 50 | Start creating a new integration route from Fuse Online home page. Call it `process-chuck-rentals-sf` for example. You can also import a zip archive built from the `./fuse-online-routes/process-chuck-rentals-sf-lbr-export` directory within this repository. 51 | 52 | This route should have: 53 | * A starting Kafka connection to your broker, listening on `rental-chuck-norris` topic, 54 | * A ending Salesforce connection, creating `Contact` entities, the ID being the email address of contact. 55 | * A DataMapping step between the two, applying the following mapping rules. We have to build a valid Email address for Salesforce object by appending a property: 56 | 57 | ![](./assets/process-chuck-rentals-sf-mapping.png) 58 | 59 | Once completed, you can now publish the integration route. 60 | 61 | 62 | ### Kafka rental-chuck-norris to Telegram 63 | 64 | Then create a new integration route from Fuse Online home page. Call it `rental-event-to-telegram` for example. You can also import a zip archive built from the `./fuse-online-routes/rental-event-to-telegram-lbr-export` directory within this repository. 65 | 66 | This route should have: 67 | * A starting Kafka connection to your broker, listening on `rental-chuck-norris` topic, 68 | * A ending Telegram connection using the specific `Chat ID` you may have collected when creating the connector. 69 | * A Connection to the `chuck-norris-facts-api` you previously created, calling the `GET /restsvc/fact` endpoint, 70 | * A DataMapping step between the two, applying the following mapping rules between `1 - rental` coming from the Kafka topic and `2 - Response` coming from the API connector on the left ; and the simple Telegram object on the right: 71 | 72 | ![](./assets/rental-event-to-telegram-mapping.png) 73 | 74 | Once completed, you can now publish the integration route. 75 | -------------------------------------------------------------------------------- /kamelets/rentals-topic-to-s3.yml: -------------------------------------------------------------------------------- 1 | apiVersion: camel.apache.org/v1alpha1 2 | kind: KameletBinding 3 | metadata: 4 | name: rentals-topic-to-s3 5 | spec: 6 | source: 7 | ref: 8 | kind: Kamelet 9 | apiVersion: camel.apache.org/v1alpha1 10 | name: kafka-source 11 | properties: 12 | bootstrapServers: my-cluster-kafka-bootstrap.amq-streams.svc.cluster.local:9094 13 | topic: rental-chuck-norris 14 | user: kamelet-user 15 | password: V69lNxaQz3sP 16 | securityProtocol: PLAINTEXT 17 | sink: 18 | ref: 19 | kind: Kamelet 20 | apiVersion: camel.apache.org/v1alpha1 21 | name: aws-s3-sink 22 | properties: 23 | accessKey: AKIA2HTUU7RLRCXVVE4X 24 | secretKey: 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 25 | bucketNameOrArn: lbr-thanos-object-storage 26 | region: eu-west-3 27 | -------------------------------------------------------------------------------- /kamelets/telegram-to-s3.yml: -------------------------------------------------------------------------------- 1 | apiVersion: camel.apache.org/v1alpha1 2 | kind: KameletBinding 3 | metadata: 4 | name: telegram-to-s3 5 | spec: 6 | source: 7 | ref: 8 | kind: Kamelet 9 | apiVersion: camel.apache.org/v1alpha1 10 | name: telegram-source 11 | properties: 12 | authorizationToken: 850651874:AAF7-7maHFsKhaTHZE8YlW0MIsODUbzv1vg 13 | sink: 14 | ref: 15 | kind: Kamelet 16 | apiVersion: camel.apache.org/v1alpha1 17 | name: aws-s3-sink 18 | properties: 19 | accessKey: AKIA2HTUU7RLRCXVVE4X 20 | secretKey: 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 21 | bucketNameOrArn: lbr-thanos-object-storage 22 | region: eu-west-3 -------------------------------------------------------------------------------- /kstream-config.md: -------------------------------------------------------------------------------- 1 | 2 | From the 3 topics created by Debezium, we want to produce following message on `rental-chuck-norris` output topic: 3 | 4 | ``` 5 | { 6 | "rental": { 7 | "id": 1, 8 | "user_id": 1, 9 | "movie_id": 1, 10 | "start_date": "2019-05-16", 11 | "rental_duration": 3 12 | }, 13 | "movie": { 14 | "id": 1, 15 | "title": "The Delta Force", 16 | "year": 1986, 17 | "main_actor": "Chuck Norris" 18 | }, 19 | "customer": { 20 | "id": 1, 21 | "first_name": "Laurent", 22 | "last_name": "Broudoux", 23 | "twitter_handle": "@lbroudoux" 24 | } 25 | } 26 | ``` 27 | 28 | ### Create target Topic 29 | 30 | From the `chuck-norris-filter-kstreams` folder, declare topic into the `amq-streams` OpenShift project, where our Strimzi cluster leaves: 31 | 32 | ``` 33 | $ oc apply -f topic.yaml -n amq-streams 34 | ``` 35 | 36 | ### Build a plain Java Kafka Stream Application 37 | 38 | #### Build the Docker Image and push it to registry 39 | 40 | From the `chuck-norris-filter-kstreams` folder (you need to replace `lbroudoux` with your own username): 41 | 42 | ``` 43 | $ mvn clean package 44 | $ docker build -t quay.io/lbroudoux/chuck-norris-filter . 45 | $ docker push quay.io/lbroudoux/chuck-norris-filter:latest 46 | ``` 47 | 48 | #### Deploy the Kafka Streams application 49 | 50 | From the `chuck-movie-rental` OpenShift project (you need to adapt file before with your own image name): 51 | 52 | ``` 53 | $ oc apply -f deployment.yaml -n chuck-movie-rental 54 | ``` 55 | 56 | ### Alternative: Build a Quarkus native Kafka Stream Application 57 | 58 | ### Build the Docker Image and push it to registry 59 | 60 | From the `chuck-norris-filter-kstreams-quarkus` folder (you need to replace `lbroudoux` with your own username): 61 | 62 | ``` 63 | $ docker build --no-cache -f src/main/docker/Dockerfile.multistage -t quay.io/lbroudoux/chuck-norris-filter-quarkus . 64 | $ docker push quay.io/lbroudoux/chuck-norris-filter-quarkus:latest 65 | ``` 66 | 67 | #### Deploy the Kafka Streams application 68 | 69 | From the `chuck-movie-rental` OpenShift project (you need to adapt file before with your own image name): 70 | 71 | ``` 72 | $ oc apply -f deployment.yaml -n chuck-movie-rental 73 | ``` 74 | 75 | #### Differences from plain Java application 76 | 77 | * Model Objects have to be annotated with `@io.quarkus.runtime.annotations.RegisterForReflection` to allow serialization using Jackson, 78 | * When using Jackson JSON serialization, you have to add `@com.fasterxml.jackson.annotation.JsonProperty` on both constructor properties and class properties otherwise field name are used serialization and deserialization can not be done anymore, 79 | * In the current Quarkus version (1.2.1) there's an issue regarding conversion to native app for Kafka Streams. I had to apply workaround provided here: https://github.com/quarkusio/quarkus/issues/7066 (see `JNIRegistrationFeature` class). -------------------------------------------------------------------------------- /rental-service/.gitignore: -------------------------------------------------------------------------------- 1 | **/target/ 2 | **/.project 3 | **/.settings 4 | **/.classpath 5 | **/*.jar 6 | **/*.log 7 | **/.vscode 8 | **/*.iml 9 | **/.idea 10 | **/log-camel-lsp.out -------------------------------------------------------------------------------- /rental-service/ReadMe.md: -------------------------------------------------------------------------------- 1 | # Spring Boot with camel and other useful things rental-service 2 | 3 | ## To build this project use 4 | 5 | ``` 6 | mvn install 7 | ``` 8 | 9 | ## To run this project with Maven use 10 | 11 | ``` 12 | mvn spring-boot:run 13 | ``` 14 | 15 | 16 | ## For testing 17 | 18 | ``` 19 | curl http://localhost:8090/camel/api-docs 20 | curl http://localhost:8090/camel/ping 21 | ``` 22 | 23 | 24 | ## Acces Swagger UI with definition 25 | 26 | ``` 27 | http://localhost:8090/webjars/swagger-ui/3.22.0/index.html?url=/camel/api-docs 28 | ``` 29 | 30 | ## Call the ping rest operation 31 | ``` 32 | curl http://localhost:8090/camel/restsvc/ping 33 | ``` -------------------------------------------------------------------------------- /rental-service/src/main/java/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Class-Path: 3 | 4 | -------------------------------------------------------------------------------- /rental-service/src/main/java/nextgen/Application.java: -------------------------------------------------------------------------------- 1 | package nextgen; 2 | import org.springframework.boot.SpringApplication; 3 | import org.springframework.boot.autoconfigure.SpringBootApplication; 4 | import org.springframework.boot.web.servlet.ServletRegistrationBean; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.ImportResource; 7 | import org.springframework.beans.factory.annotation.Value; 8 | 9 | import org.apache.camel.component.hystrix.metrics.servlet.HystrixEventStreamServlet; 10 | import org.springframework.boot.web.servlet.ServletRegistrationBean; 11 | 12 | @SpringBootApplication 13 | @ImportResource({"classpath:spring/camel-context.xml"}) 14 | public class Application { 15 | 16 | // must have a main method spring-boot can run 17 | public static void main(String[] args) { 18 | SpringApplication.run(Application.class, args); 19 | } 20 | 21 | @Bean 22 | ServletRegistrationBean hystrixServletRegistrationBean() { 23 | ServletRegistrationBean mapping = new ServletRegistrationBean(); 24 | mapping.setServlet(new HystrixEventStreamServlet()); 25 | mapping.addUrlMappings("/hystrix.stream"); 26 | mapping.setName("HystrixEventStreamServlet"); 27 | 28 | return mapping; 29 | } 30 | 31 | 32 | } -------------------------------------------------------------------------------- /rental-service/src/main/java/nextgen/MyTransformer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Red Hat, Inc. 3 | *

4 | * Red Hat licenses this file to you under the Apache License, version 5 | * 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. 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 13 | * implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | * 16 | */ 17 | package nextgen; 18 | 19 | import org.springframework.stereotype.Component; 20 | 21 | /** 22 | * A sample transform 23 | */ 24 | @Component(value = "myTransformer") 25 | public class MyTransformer { 26 | 27 | public String transform() { 28 | // let's return a random string 29 | StringBuffer buffer = new StringBuffer(); 30 | for (int i = 0; i < 3; i++) { 31 | int number = (int) (Math.round(Math.random() * 1000) % 10); 32 | char letter = (char) ('0' + number); 33 | buffer.append(letter); 34 | } 35 | return buffer.toString(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /rental-service/src/main/java/nextgen/RestConfigurator.java: -------------------------------------------------------------------------------- 1 | package nextgen; 2 | 3 | import org.apache.camel.builder.RouteBuilder; 4 | import org.apache.camel.model.rest.RestBindingMode; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.core.env.Environment; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class RestConfigurator extends RouteBuilder { 11 | 12 | @Autowired 13 | Environment environment; 14 | 15 | @Override 16 | public void configure() throws Exception { 17 | restConfiguration() 18 | .component("servlet") 19 | .bindingMode(RestBindingMode.json) 20 | .contextPath(environment.getProperty("camelrest.contextPath")) 21 | .port(environment.getProperty("camelrest.port")) 22 | .apiContextPath("/api-docs") 23 | .apiProperty("cors", "true") 24 | .apiProperty("api.title", environment.getProperty("camel.springboot.name")) 25 | .apiProperty("api.version", environment.getProperty("camelrest.apiversion")) 26 | .host(environment.getProperty("camelrest.host")) 27 | .dataFormatProperty("prettyPrint", "true"); 28 | 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /rental-service/src/main/java/nextgen/apimodel/RentalEdit.java: -------------------------------------------------------------------------------- 1 | package nextgen.apimodel; 2 | 3 | import java.util.Date; 4 | 5 | import javax.persistence.ManyToOne; 6 | 7 | import nextgen.model.Customer; 8 | import nextgen.model.Movie; 9 | 10 | public class RentalEdit { 11 | 12 | private Date startDate; 13 | private Integer rentalDuration; 14 | 15 | private Long movieId; 16 | 17 | private Long customerId; 18 | 19 | public Date getStartDate() { 20 | return startDate; 21 | } 22 | 23 | public void setStartDate(Date startDate) { 24 | this.startDate = startDate; 25 | } 26 | 27 | public Integer getRentalDuration() { 28 | return rentalDuration; 29 | } 30 | 31 | public void setRentalDuration(Integer rentalDuration) { 32 | this.rentalDuration = rentalDuration; 33 | } 34 | 35 | public Long getMovieId() { 36 | return movieId; 37 | } 38 | 39 | public void setMovieId(Long movieId) { 40 | this.movieId = movieId; 41 | } 42 | 43 | public Long getCustomerId() { 44 | return customerId; 45 | } 46 | 47 | public void setCustomerId(Long customerId) { 48 | this.customerId = customerId; 49 | } 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /rental-service/src/main/java/nextgen/controller/AppController.java: -------------------------------------------------------------------------------- 1 | package nextgen.controller; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import javax.servlet.http.HttpSession; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.ui.Model; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.ModelAttribute; 13 | import org.springframework.web.bind.annotation.PathVariable; 14 | import org.springframework.web.bind.annotation.PostMapping; 15 | 16 | import nextgen.model.Customer; 17 | import nextgen.model.Movie; 18 | import nextgen.model.Rental; 19 | import nextgen.repo.CustomerRepo; 20 | import nextgen.repo.MovieRepo; 21 | import nextgen.repo.RentalRepo; 22 | 23 | @Controller 24 | public class AppController { 25 | 26 | @Autowired 27 | CustomerRepo customerRepo; 28 | 29 | @Autowired 30 | MovieRepo movieRepo; 31 | 32 | @Autowired 33 | RentalRepo rentalRepo; 34 | 35 | 36 | @GetMapping("/") 37 | public String main(Model model) { 38 | model.addAttribute("movies",movieRepo.findAll()); 39 | return "index"; 40 | } 41 | 42 | @GetMapping("/rent/{id}") 43 | public String rent(HttpSession session,Model model,@PathVariable("id") Long id) { 44 | Rental rental = new Rental(); 45 | Customer customer = (Customer) session.getAttribute("customer"); 46 | 47 | rental.setStartDate(new Date()); 48 | rental.setRentalDuration(7); 49 | Movie movie = movieRepo.findById(id).get(); 50 | rental.setCustomer(customer); 51 | rental.setMovie(movie); 52 | rentalRepo.save(rental); 53 | model.addAttribute("rentals", rentalRepo.findByCustomerId(customer.getId())); 54 | return "rented"; 55 | 56 | } 57 | 58 | @GetMapping("/rent") 59 | public String rentHistory(HttpSession session,Model model) { 60 | 61 | Customer customer = (Customer) session.getAttribute("customer"); 62 | 63 | 64 | model.addAttribute("rentals", rentalRepo.findByCustomerId(customer.getId())); 65 | 66 | 67 | return "rented"; 68 | 69 | } 70 | 71 | @GetMapping("/login") 72 | public String login(HttpSession session,Model model) { 73 | if (session.getAttribute("customer") == null) { 74 | System.out.println("not logged in"); 75 | model.addAttribute("customer",new Customer()); 76 | } 77 | else 78 | { 79 | System.out.println("logged in as " + session.getAttribute("customer")); 80 | } 81 | return "login"; 82 | } 83 | 84 | @PostMapping("/login") 85 | public String loginSubmit(HttpSession session,@ModelAttribute(value="customer")Customer customerInput) { 86 | 87 | Customer c = customerRepo.findByFirstName(customerInput.getFirstName()); 88 | if (c != null) { 89 | session.setAttribute("customer", c); 90 | } 91 | 92 | return "login"; 93 | } 94 | 95 | @GetMapping("/logout") 96 | private String logout(HttpSession session) { 97 | 98 | session.invalidate(); 99 | 100 | return "logout"; 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /rental-service/src/main/java/nextgen/model/AbstractBaseEntity.java: -------------------------------------------------------------------------------- 1 | package nextgen.model; 2 | 3 | import java.io.Serializable; 4 | 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | import javax.persistence.MappedSuperclass; 9 | 10 | @MappedSuperclass 11 | public abstract class AbstractBaseEntity implements Serializable { 12 | 13 | /** 14 | * 15 | */ 16 | private static final long serialVersionUID = 1L; 17 | 18 | @Id @GeneratedValue(strategy=GenerationType.IDENTITY) 19 | protected Long id; 20 | 21 | 22 | 23 | 24 | public Long getId() { 25 | return id; 26 | } 27 | 28 | 29 | public void setId(Long id) { 30 | this.id = id; 31 | } 32 | 33 | 34 | 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /rental-service/src/main/java/nextgen/model/Customer.java: -------------------------------------------------------------------------------- 1 | package nextgen.model; 2 | 3 | import java.util.Set; 4 | 5 | import javax.persistence.Entity; 6 | import javax.persistence.ManyToOne; 7 | import javax.persistence.OneToMany; 8 | 9 | import com.fasterxml.jackson.annotation.JsonBackReference; 10 | import com.fasterxml.jackson.annotation.JsonManagedReference; 11 | 12 | @Entity 13 | public class Customer extends AbstractBaseEntity { 14 | 15 | /** 16 | * 17 | */ 18 | private static final long serialVersionUID = 1L; 19 | 20 | private String firstName; 21 | private String lastName; 22 | private String twitterHandle; 23 | 24 | @OneToMany(mappedBy="customer") 25 | @JsonBackReference 26 | private Set rentals; 27 | 28 | public String getFirstName() { 29 | return firstName; 30 | } 31 | 32 | public void setFirstName(String firstName) { 33 | this.firstName = firstName; 34 | } 35 | 36 | public String getLastName() { 37 | return lastName; 38 | } 39 | 40 | public void setLastName(String lastName) { 41 | this.lastName = lastName; 42 | } 43 | 44 | public String getTwitterHandle() { 45 | return twitterHandle; 46 | } 47 | 48 | public void setTwitterHandle(String twitterHandle) { 49 | this.twitterHandle = twitterHandle; 50 | } 51 | 52 | public Set getRentals() { 53 | return rentals; 54 | } 55 | 56 | public void setRentals(Set rentals) { 57 | this.rentals = rentals; 58 | } 59 | 60 | 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /rental-service/src/main/java/nextgen/model/Movie.java: -------------------------------------------------------------------------------- 1 | package nextgen.model; 2 | 3 | import java.time.Year; 4 | import java.util.Set; 5 | 6 | import javax.persistence.Entity; 7 | import javax.persistence.OneToMany; 8 | 9 | import com.fasterxml.jackson.annotation.JsonBackReference; 10 | import com.fasterxml.jackson.annotation.JsonManagedReference; 11 | 12 | @Entity 13 | public class Movie extends AbstractBaseEntity { 14 | 15 | /** 16 | * 17 | */ 18 | private static final long serialVersionUID = 1L; 19 | 20 | private String title; 21 | private Integer year; 22 | private String mainActor; 23 | 24 | @OneToMany(mappedBy="movie") 25 | @JsonBackReference 26 | private Set rentals; 27 | 28 | 29 | public Set getRentals() { 30 | return rentals; 31 | } 32 | public void setRentals(Set rentals) { 33 | this.rentals = rentals; 34 | } 35 | public String getTitle() { 36 | return title; 37 | } 38 | public void setTitle(String title) { 39 | this.title = title; 40 | } 41 | public Integer getYear() { 42 | return year; 43 | } 44 | public void setYear(Integer year) { 45 | this.year = year; 46 | } 47 | public String getMainActor() { 48 | return mainActor; 49 | } 50 | public void setMainActor(String mainActor) { 51 | this.mainActor = mainActor; 52 | } 53 | 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /rental-service/src/main/java/nextgen/model/Rental.java: -------------------------------------------------------------------------------- 1 | package nextgen.model; 2 | 3 | import java.util.Date; 4 | 5 | import javax.persistence.Entity; 6 | import javax.persistence.FetchType; 7 | import javax.persistence.ManyToOne; 8 | 9 | import com.fasterxml.jackson.annotation.JsonBackReference; 10 | import com.fasterxml.jackson.annotation.JsonManagedReference; 11 | 12 | @Entity 13 | public class Rental extends AbstractBaseEntity { 14 | 15 | /** 16 | * 17 | */ 18 | private static final long serialVersionUID = 1L; 19 | 20 | 21 | private Date startDate; 22 | private Integer rentalDuration; 23 | 24 | @ManyToOne 25 | @JsonManagedReference 26 | private Movie movie; 27 | 28 | @ManyToOne 29 | @JsonManagedReference 30 | private Customer customer; 31 | 32 | public Date getStartDate() { 33 | return startDate; 34 | } 35 | 36 | public void setStartDate(Date startDate) { 37 | this.startDate = startDate; 38 | } 39 | 40 | public Integer getRentalDuration() { 41 | return rentalDuration; 42 | } 43 | 44 | public void setRentalDuration(Integer rentalDuration) { 45 | this.rentalDuration = rentalDuration; 46 | } 47 | 48 | public Movie getMovie() { 49 | return movie; 50 | } 51 | 52 | public void setMovie(Movie movie) { 53 | this.movie = movie; 54 | } 55 | 56 | public Customer getCustomer() { 57 | return customer; 58 | } 59 | 60 | public void setCustomer(Customer customer) { 61 | this.customer = customer; 62 | } 63 | 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /rental-service/src/main/java/nextgen/processor/AbstractCRUD.java: -------------------------------------------------------------------------------- 1 | package nextgen.processor; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | 5 | import nextgen.repo.CustomerRepo; 6 | import nextgen.repo.MovieRepo; 7 | import nextgen.repo.RentalRepo; 8 | 9 | public abstract class AbstractCRUD { 10 | @Autowired 11 | CustomerRepo customerRepo; 12 | 13 | @Autowired 14 | MovieRepo movieRepo; 15 | 16 | @Autowired 17 | RentalRepo rentalRepo; 18 | } 19 | -------------------------------------------------------------------------------- /rental-service/src/main/java/nextgen/processor/RentalCRUD.java: -------------------------------------------------------------------------------- 1 | package nextgen.processor; 2 | 3 | 4 | import java.util.Optional; 5 | 6 | import org.apache.camel.Body; 7 | import org.apache.camel.language.Simple; 8 | import org.springframework.stereotype.Component; 9 | 10 | import nextgen.apimodel.RentalEdit; 11 | import nextgen.model.Customer; 12 | import nextgen.model.Movie; 13 | import nextgen.model.Rental; 14 | 15 | @Component 16 | public class RentalCRUD extends AbstractCRUD{ 17 | 18 | public Iterable findAll() { 19 | return rentalRepo.findAll(); 20 | } 21 | 22 | public Optional findById( @Simple("headers.id") Long id) { 23 | return rentalRepo.findById(id); 24 | } 25 | 26 | public Rental save(@Body RentalEdit rentalEdit) { 27 | 28 | Long movieId = rentalEdit.getMovieId(); 29 | Long customerId = rentalEdit.getCustomerId(); 30 | Movie movie = movieRepo.findById(movieId).get(); 31 | Customer customer = customerRepo.findById(customerId).get(); 32 | 33 | Rental rental = new Rental(); 34 | rental.setRentalDuration(rentalEdit.getRentalDuration()); 35 | rental.setStartDate(rentalEdit.getStartDate()); 36 | rental.setCustomer(customer); 37 | rental.setMovie(movie); 38 | 39 | rentalRepo.save(rental); 40 | 41 | return rental; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /rental-service/src/main/java/nextgen/processor/RentalPost.java: -------------------------------------------------------------------------------- 1 | package nextgen.processor; 2 | 3 | import org.apache.camel.Exchange; 4 | import org.apache.camel.Processor; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | 8 | import nextgen.apimodel.RentalEdit; 9 | import nextgen.model.Customer; 10 | import nextgen.model.Movie; 11 | import nextgen.model.Rental; 12 | import nextgen.repo.CustomerRepo; 13 | import nextgen.repo.MovieRepo; 14 | import nextgen.repo.RentalRepo; 15 | 16 | 17 | @Component 18 | public class RentalPost extends AbstractCRUD implements Processor{ 19 | 20 | @Autowired 21 | CustomerRepo customerRepo; 22 | 23 | @Autowired 24 | MovieRepo movieRepo; 25 | 26 | @Autowired 27 | RentalRepo rentalRepo; 28 | 29 | @Override 30 | public void process(Exchange exchange) throws Exception { 31 | 32 | RentalEdit rentalApi = exchange.getIn().getBody(RentalEdit.class); 33 | Long movieId = rentalApi.getMovieId(); 34 | Long customerId = rentalApi.getCustomerId(); 35 | Movie movie = movieRepo.findById(movieId).get(); 36 | Customer customer = customerRepo.findById(customerId).get(); 37 | 38 | Rental rental = new Rental(); 39 | rental.setRentalDuration(rentalApi.getRentalDuration()); 40 | rental.setStartDate(rentalApi.getStartDate()); 41 | rental.setCustomer(customer); 42 | rental.setMovie(movie); 43 | 44 | rentalRepo.save(rental); 45 | 46 | exchange.getIn().setBody(rental); 47 | 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /rental-service/src/main/java/nextgen/repo/CustomerRepo.java: -------------------------------------------------------------------------------- 1 | package nextgen.repo; 2 | 3 | import org.springframework.data.repository.PagingAndSortingRepository; 4 | import org.springframework.data.repository.query.Param; 5 | 6 | import nextgen.model.Customer; 7 | 8 | public interface CustomerRepo extends PagingAndSortingRepository{ 9 | 10 | Customer findByFirstName(@Param(value = "firstName")String firstName); 11 | } 12 | -------------------------------------------------------------------------------- /rental-service/src/main/java/nextgen/repo/MovieRepo.java: -------------------------------------------------------------------------------- 1 | package nextgen.repo; 2 | 3 | import org.springframework.data.repository.PagingAndSortingRepository; 4 | 5 | import nextgen.model.Customer; 6 | import nextgen.model.Movie; 7 | import nextgen.model.Rental; 8 | 9 | public interface MovieRepo extends PagingAndSortingRepository{ 10 | 11 | } 12 | -------------------------------------------------------------------------------- /rental-service/src/main/java/nextgen/repo/RentalRepo.java: -------------------------------------------------------------------------------- 1 | package nextgen.repo; 2 | 3 | import org.springframework.data.repository.PagingAndSortingRepository; 4 | import org.springframework.data.repository.query.Param; 5 | 6 | import nextgen.model.Rental; 7 | 8 | public interface RentalRepo extends PagingAndSortingRepository{ 9 | 10 | Iterable findByCustomerId(@Param(value = "id")Long id); 11 | } 12 | -------------------------------------------------------------------------------- /rental-service/src/main/resources/api-definitions/basic-api.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | swagger: "2.0" 3 | info: 4 | title: basic-api 5 | version: 1.0.0 6 | paths: 7 | /person: 8 | get: 9 | operationId: getPerson 10 | parameters: 11 | - name: email 12 | in: query 13 | required: true 14 | type: string 15 | responses: 16 | 200: 17 | description: Response ok 18 | schema: 19 | $ref: '#/definitions/Person' 20 | definitions: 21 | Person: 22 | title: Root Type for Person 23 | description: The root of the Person type's schema. 24 | type: object 25 | properties: 26 | firstName: 27 | type: string 28 | lastName: 29 | type: string 30 | email: 31 | type: string 32 | age: 33 | format: int32 34 | type: integer 35 | example: |- 36 | { 37 | "firstName": "John", 38 | "lastName": "Doe", 39 | "email": "john@mail.com", 40 | "age": 21 41 | } -------------------------------------------------------------------------------- /rental-service/src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | logging.config=classpath:logback.xml 2 | 3 | # the options from org.apache.camel.spring.boot.CamelConfigurationProperties can be configured here 4 | camel.springboot.name=rental-service 5 | 6 | # lets listen on all ports to ensure we can be invoked from the pod IP 7 | server.address=0.0.0.0 8 | management.address=0.0.0.0 9 | # lets use a different management port in case you need to listen to HTTP requests on 8080 10 | server.port=8090 11 | management.port=8190 12 | 13 | # disable all management enpoints except health 14 | endpoints.enabled = false 15 | endpoints.health.enabled = true 16 | 17 | cxf.path=/services 18 | 19 | camel.component.servlet.mapping.contextPath=/camel/* 20 | 21 | camelrest.host=localhost 22 | camelrest.port=8090 23 | camelrest.contextPath=/camel 24 | camelrest.apiversion=1.0-SNAPSHOT 25 | 26 | spring.datasource.driver-class-name = com.mysql.jdbc.Driver 27 | spring.datasource.url = jdbc:mysql://172.17.0.2:3306/mysqldb 28 | spring.datasource.username = chuck 29 | spring.datasource.password = password 30 | spring.jpa.hibernate.ddl-auto=update 31 | spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true 32 | 33 | 34 | spring.data.rest.basePath=/api -------------------------------------------------------------------------------- /rental-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | logging.config=classpath:logback.xml 2 | 3 | # the options from org.apache.camel.spring.boot.CamelConfigurationProperties can be configured here 4 | camel.springboot.name=rental-service 5 | 6 | # lets listen on all ports to ensure we can be invoked from the pod IP 7 | server.address=0.0.0.0 8 | management.address=0.0.0.0 9 | # lets use a different management port in case you need to listen to HTTP requests on 8080 10 | server.port=8080 11 | management.port=8081 12 | 13 | # disable all management enpoints except health 14 | endpoints.enabled = false 15 | endpoints.health.enabled = true 16 | 17 | cxf.path=/services 18 | 19 | camel.component.servlet.mapping.contextPath=/camel/* 20 | 21 | camelrest.host=localhost 22 | camelrest.port=80 23 | camelrest.contextPath=/camel 24 | camelrest.apiversion=1.0-SNAPSHOT 25 | 26 | spring.datasource.driver-class-name = com.mysql.jdbc.Driver 27 | spring.datasource.url = jdbc:mysql://mysqldebezium.chuck-movie-rental.svc.cluster.local:3306/inventory 28 | spring.datasource.username = root 29 | spring.datasource.password = password 30 | spring.jpa.hibernate.ddl-auto=update 31 | spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true 32 | 33 | 34 | spring.data.rest.basePath=/api -------------------------------------------------------------------------------- /rental-service/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /rental-service/src/main/resources/spring/camel-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | application/json 36 | 37 | 38 | {"msg" : "HELLO"} 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /rental-service/src/main/resources/sql/examples.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO movie (main_actor,title,`year`) VALUES ( 2 | 'Jessica Lange','King Kong',1976); 3 | INSERT INTO movie (main_actor,title,`year`) VALUES ( 4 | 'Chuck Norris','Delta Force',1986); 5 | INSERT INTO movie (main_actor,title,`year`) VALUES ( 6 | 'Anthony Hopkins','The Elephant Man',1980); 7 | INSERT INTO movie (main_actor,title,`year`) VALUES ( 8 | 'Chuck Norris','Invasion U.S.A.',1985); 9 | INSERT INTO movie (main_actor,title,`year`) VALUES ( 10 | 'Chuck Norris','An Eye for an Eye',1981); 11 | INSERT INTO movie (main_actor,title,`year`) VALUES ( 12 | 'Roger Miller','Robin Hood',1973); 13 | INSERT INTO movie (main_actor,title,`year`) VALUES ( 14 | 'Charles Chaplin','Charly Chaplin in Wien',1931); 15 | INSERT INTO movie (main_actor,title,`year`) VALUES ( 16 | 'Chuck Norris','The Octagon',1980); 17 | INSERT INTO movie (main_actor,title,`year`) VALUES ( 18 | 'James Stewart','Vertigo',1958); 19 | 20 | 21 | INSERT INTO customer 22 | ( first_name, last_name, twitter_handle) 23 | VALUES( 'alain', 'pham', '@koint'); 24 | INSERT INTO customer 25 | ( first_name, last_name, twitter_handle) 26 | VALUES( 'laurent', 'broudoux', '@lbroudoux'); 27 | 28 | update movie set main_actor='Jessica Lange ' where id =1; 29 | update movie set main_actor='Chuck Norris ' where id =2; 30 | update movie set main_actor='Anthony Hopkins ' where id =3; 31 | update movie set main_actor='Chuck Norris ' where id =4; 32 | update movie set main_actor='Chuck Norris ' where id =5; 33 | update movie set main_actor='Roger Miller ' where id =6; 34 | update movie set main_actor='Charles Chaplin ' where id =7; 35 | update movie set main_actor='Chuck Norris ' where id =8; 36 | update movie set main_actor='James Stewart ' where id =9; 37 | 38 | update customer set last_name='pham ' where id =1; 39 | update customer set last_name='broudoux ' where id =2; 40 | 41 | update movie set main_actor='Jessica Lange' where id =1; 42 | update movie set main_actor='Chuck Norris' where id =2; 43 | update movie set main_actor='Anthony Hopkins' where id =3; 44 | update movie set main_actor='Chuck Norris' where id =4; 45 | update movie set main_actor='Chuck Norris' where id =5; 46 | update movie set main_actor='Roger Miller' where id =6; 47 | update movie set main_actor='Charles Chaplin' where id =7; 48 | update movie set main_actor='Chuck Norris' where id =8; 49 | update movie set main_actor='James Stewart' where id =9; 50 | 51 | update customer set last_name='pham' where id =1; 52 | update customer set last_name='broudoux' where id =2; 53 | 54 | commit; 55 | -------------------------------------------------------------------------------- /rental-service/src/main/resources/static/An Eye for an Eye.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/rental-service/src/main/resources/static/An Eye for an Eye.jpg -------------------------------------------------------------------------------- /rental-service/src/main/resources/static/Charly Chaplin in Wien.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/rental-service/src/main/resources/static/Charly Chaplin in Wien.jpg -------------------------------------------------------------------------------- /rental-service/src/main/resources/static/Delta Force.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/rental-service/src/main/resources/static/Delta Force.jpg -------------------------------------------------------------------------------- /rental-service/src/main/resources/static/Invasion U.S.A..jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/rental-service/src/main/resources/static/Invasion U.S.A..jpg -------------------------------------------------------------------------------- /rental-service/src/main/resources/static/King Kong.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/rental-service/src/main/resources/static/King Kong.jpg -------------------------------------------------------------------------------- /rental-service/src/main/resources/static/Robin Hood.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/rental-service/src/main/resources/static/Robin Hood.jpg -------------------------------------------------------------------------------- /rental-service/src/main/resources/static/The Elephant Man.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/rental-service/src/main/resources/static/The Elephant Man.jpg -------------------------------------------------------------------------------- /rental-service/src/main/resources/static/The Octagon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/rental-service/src/main/resources/static/The Octagon.jpg -------------------------------------------------------------------------------- /rental-service/src/main/resources/static/Vertigo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/rental-service/src/main/resources/static/Vertigo.jpg -------------------------------------------------------------------------------- /rental-service/src/main/resources/static/chuck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/rental-service/src/main/resources/static/chuck.png -------------------------------------------------------------------------------- /rental-service/src/main/resources/static/test.html: -------------------------------------------------------------------------------- 1 | 2 |

3 | rental-service 4 |
5 | 6 | 7 |

Welcome to the rental-service interface

8 |

Ping

9 |

Swagger Definition

10 |

Swagger UI

11 | 12 | 13 | -------------------------------------------------------------------------------- /rental-service/src/main/resources/static/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/chuck-norris-streams/cd0b44020521365f21b6173d4168fa1e332d2703/rental-service/src/main/resources/static/video.png -------------------------------------------------------------------------------- /rental-service/src/main/resources/templates/fragments/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /rental-service/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Chuck Movie Rentals 6 | 8 | 12 | 13 | 14 |
15 | 16 |
17 | 18 |
19 |
20 | Chuck Norris 22 |
23 |
24 |
25 |

Chuck Movie Rentals

26 |

Rent any movie, especially Chuck Norris movies

27 |
28 |
29 | 30 |
31 |

Movie library

32 |
33 |
34 |

35 |

36 | Chuck Norris 38 |

39 |

40 |

41 |

42 |

rent

43 |
44 | 45 |
46 |
47 |
48 | 49 | 50 | 51 | 52 | 55 | 59 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /rental-service/src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Chuck Movie Rentals 6 | 8 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 | 21 |

Login using your name

22 | 23 |
24 | 25 |
26 | 28 |
29 | 30 |
31 |
32 | Logged as 34 |
35 | 36 |
37 |
38 | 39 | 40 | 41 | 44 | 48 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /rental-service/src/main/resources/templates/logout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Chuck Movie Rentals 6 | 8 | 12 | 13 | 14 | 15 |
16 | 17 |
18 | 19 |

You have been logged out

20 |
21 | 22 | 23 | 24 | 27 | 31 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /rental-service/src/main/resources/templates/rented.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Chuck Movie Rentals 6 | 8 | 12 | 13 | 14 |
15 | 16 |
17 | 18 | 19 |

Rented movies

20 |
21 |
22 |

23 |

24 | Chuck Norris 26 |

27 |

28 |

29 |

30 |

31 |
32 |
33 | 34 | 35 | 36 | 37 | 40 | 44 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /rental-service/test/test.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "1", 4 | "fact": "Chuck Norris a déjà compté jusqu'à l'infini. Deux fois.", 5 | "date": "1373297343", 6 | "vote": "189404", 7 | "points": "866902" 8 | }, 9 | { 10 | "id": "9355", 11 | "fact": "Google, c'est le seul endroit où tu peux taper Chuck Norris...", 12 | "date": "1373297340", 13 | "vote": "136456", 14 | "points": "607433" 15 | }, 16 | { 17 | "id": "10705", 18 | "fact": "Chuck Norris et Superman ont fait un bras de fer, le perdant devait mettre son slip par dessus son pantalon.", 19 | "date": "1373297355", 20 | "vote": "113973", 21 | "points": "501197" 22 | }, 23 | { 24 | "id": "106", 25 | "fact": "Chuck Norris peut gagner une partie de puissance 4 en trois coups.", 26 | "date": "1373297368", 27 | "vote": "107246", 28 | "points": "461431" 29 | }, 30 | { 31 | "id": "73", 32 | "fact": "Jesus Christ est né en 1940 avant Chuck Norris.", 33 | "date": "1373297391", 34 | "vote": "105222", 35 | "points": "445848" 36 | }, 37 | { 38 | "id": "10", 39 | "fact": "Chuck Norris ne porte pas de montre. Il décide de l'heure qu'il est.", 40 | "date": "1373297395", 41 | "vote": "104807", 42 | "points": "435723" 43 | }, 44 | { 45 | "id": "180", 46 | "fact": "La seule chose qui arrive à la cheville de Chuck Norris... c'est sa chaussette.", 47 | "date": "1373297400", 48 | "vote": "102280", 49 | "points": "429332" 50 | }, 51 | { 52 | "id": "246", 53 | "fact": "Chuck Norris fait pleurer les oignons.", 54 | "date": "1373297406", 55 | "vote": "99968", 56 | "points": "418228" 57 | }, 58 | { 59 | "id": "4", 60 | "fact": "Chuck Norris peut diviser par zéro.", 61 | "date": "1373297425", 62 | "vote": "98323", 63 | "points": "405137" 64 | }, 65 | { 66 | "id": "398", 67 | "fact": "Chuck Norris comprend Jean-Claude Van Damme.", 68 | "date": "1373297429", 69 | "vote": "97176", 70 | "points": "403247" 71 | }, 72 | { 73 | "id": "5", 74 | "fact": "Chuck Norris joue à la roulette russe avec un chargeur plein.", 75 | "date": "1373297440", 76 | "vote": "92975", 77 | "points": "397412" 78 | }, 79 | { 80 | "id": "3", 81 | "fact": "Chuck Norris sait parler le braille.", 82 | "date": "1373297434", 83 | "vote": "94132", 84 | "points": "397266" 85 | }, 86 | { 87 | "id": "152", 88 | "fact": "Chuck Norris a un jour avalé un paquet entier de somnifères. Il a cligné des yeux.", 89 | "date": "1373297451", 90 | "vote": "94448", 91 | "points": "394266" 92 | }, 93 | { 94 | "id": "267", 95 | "fact": "Quand Google ne trouve pas quelque chose, il demande à Chuck Norris.", 96 | "date": "1373297445", 97 | "vote": "93800", 98 | "points": "393451" 99 | }, 100 | { 101 | "id": "20", 102 | "fact": "Les suisses ne sont pas neutres, ils attendent de savoir de quel coté Chuck Norris se situe.", 103 | "date": "1373297455", 104 | "vote": "93741", 105 | "points": "390347" 106 | }, 107 | { 108 | "id": "12", 109 | "fact": "Il n'y a pas de théorie de l'évolution. Juste une liste d'espèces que Chuck Norris autorise à survivre.", 110 | "date": "1373297465", 111 | "vote": "88531", 112 | "points": "368188" 113 | }, 114 | { 115 | "id": "3693", 116 | "fact": "Chuck Norris peut encercler ses ennemis. Tout seul.", 117 | "date": "1373297495", 118 | "vote": "83160", 119 | "points": "367329" 120 | }, 121 | { 122 | "id": "127", 123 | "fact": "Chuck Norris a déjà été sur Mars, c'est pour cela qu'il n'y a pas de signes de vie là bas.", 124 | "date": "1373297480", 125 | "vote": "87236", 126 | "points": "364383" 127 | }, 128 | { 129 | "id": "28", 130 | "fact": "Chuck Norris mesure son pouls sur l'échelle de Richter.", 131 | "date": "1373297505", 132 | "vote": "87023", 133 | "points": "359969" 134 | }, 135 | { 136 | "id": "13638", 137 | "fact": "Quand Chuck Norris s\u0092est mis au judo, David Douillet s\u0092est mis aux pièces jaunes.", 138 | "date": "1373297489", 139 | "vote": "82967", 140 | "points": "357421" 141 | }, 142 | { 143 | "id": "35", 144 | "fact": "Chuck Norris connait la dernière décimale de Pi.", 145 | "date": "1373297506", 146 | "vote": "84453", 147 | "points": "350437" 148 | }, 149 | { 150 | "id": "57", 151 | "fact": "Dans une pièce normale, il y a en moyenne 1242 objets avec lesquels Chuck Norris peut vous tuer, en incluant la pièce elle même.", 152 | "date": "1373297535", 153 | "vote": "83529", 154 | "points": "350410" 155 | }, 156 | { 157 | "id": "142", 158 | "fact": "Si Chuck Norris avait été pris dans le film 300 il l'aurait renommé en 1.", 159 | "date": "1373297546", 160 | "vote": "83877", 161 | "points": "349393" 162 | }, 163 | { 164 | "id": "84", 165 | "fact": "Un jour, au restaurant, Chuck Norris a commandé un steak. Et le steak a obéi.", 166 | "date": "1373297555", 167 | "vote": "85209", 168 | "points": "349209" 169 | } 170 | ] -------------------------------------------------------------------------------- /rental-service/test/test2.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": { 3 | "type": "struct", 4 | "fields": [ 5 | { 6 | "type": "struct", 7 | "fields": [ 8 | { 9 | "type": "int64", 10 | "optional": false, 11 | "field": "id" 12 | }, 13 | { 14 | "type": "int32", 15 | "optional": true, 16 | "field": "rental_duration" 17 | }, 18 | { 19 | "type": "int64", 20 | "optional": true, 21 | "name": "io.debezium.time.Timestamp", 22 | "version": 1, 23 | "field": "start_date" 24 | }, 25 | { 26 | "type": "int64", 27 | "optional": true, 28 | "field": "customer_id" 29 | }, 30 | { 31 | "type": "int64", 32 | "optional": true, 33 | "field": "movie_id" 34 | } 35 | ], 36 | "optional": true, 37 | "name": "dbserver1.inventory.rental.Value", 38 | "field": "before" 39 | }, 40 | { 41 | "type": "struct", 42 | "fields": [ 43 | { 44 | "type": "int64", 45 | "optional": false, 46 | "field": "id" 47 | }, 48 | { 49 | "type": "int32", 50 | "optional": true, 51 | "field": "rental_duration" 52 | }, 53 | { 54 | "type": "int64", 55 | "optional": true, 56 | "name": "io.debezium.time.Timestamp", 57 | "version": 1, 58 | "field": "start_date" 59 | }, 60 | { 61 | "type": "int64", 62 | "optional": true, 63 | "field": "customer_id" 64 | }, 65 | { 66 | "type": "int64", 67 | "optional": true, 68 | "field": "movie_id" 69 | } 70 | ], 71 | "optional": true, 72 | "name": "dbserver1.inventory.rental.Value", 73 | "field": "after" 74 | }, 75 | { 76 | "type": "struct", 77 | "fields": [ 78 | { 79 | "type": "string", 80 | "optional": true, 81 | "field": "version" 82 | }, 83 | { 84 | "type": "string", 85 | "optional": false, 86 | "field": "name" 87 | }, 88 | { 89 | "type": "int64", 90 | "optional": false, 91 | "field": "server_id" 92 | }, 93 | { 94 | "type": "int64", 95 | "optional": false, 96 | "field": "ts_sec" 97 | }, 98 | { 99 | "type": "string", 100 | "optional": true, 101 | "field": "gtid" 102 | }, 103 | { 104 | "type": "string", 105 | "optional": false, 106 | "field": "file" 107 | }, 108 | { 109 | "type": "int64", 110 | "optional": false, 111 | "field": "pos" 112 | }, 113 | { 114 | "type": "int32", 115 | "optional": false, 116 | "field": "row" 117 | }, 118 | { 119 | "type": "boolean", 120 | "optional": true, 121 | "default": false, 122 | "field": "snapshot" 123 | }, 124 | { 125 | "type": "int64", 126 | "optional": true, 127 | "field": "thread" 128 | }, 129 | { 130 | "type": "string", 131 | "optional": true, 132 | "field": "db" 133 | }, 134 | { 135 | "type": "string", 136 | "optional": true, 137 | "field": "table" 138 | }, 139 | { 140 | "type": "string", 141 | "optional": true, 142 | "field": "query" 143 | } 144 | ], 145 | "optional": false, 146 | "name": "io.debezium.connector.mysql.Source", 147 | "field": "source" 148 | }, 149 | { 150 | "type": "string", 151 | "optional": false, 152 | "field": "op" 153 | }, 154 | { 155 | "type": "int64", 156 | "optional": true, 157 | "field": "ts_ms" 158 | } 159 | ], 160 | "optional": false, 161 | "name": "dbserver1.inventory.rental.Envelope" 162 | }, 163 | "payload": { 164 | "before": null, 165 | "after": { 166 | "id": 2, 167 | "rental_duration": 7, 168 | "start_date": 1558039871000, 169 | "customer_id": 1, 170 | "movie_id": 9 171 | }, 172 | "source": { 173 | "version": "0.8.0.Final", 174 | "name": "dbserver1", 175 | "server_id": 223344, 176 | "ts_sec": 1558039870, 177 | "gtid": null, 178 | "file": "mysql-bin.000003", 179 | "pos": 7756, 180 | "row": 0, 181 | "snapshot": false, 182 | "thread": 15, 183 | "db": "inventory", 184 | "table": "rental", 185 | "query": null 186 | }, 187 | "op": "c", 188 | "ts_ms": 1558039870881 189 | } 190 | } --------------------------------------------------------------------------------