diff --git a/spring-boot-modules/spring-boot-3-3/README.md b/spring-boot-modules/spring-boot-3-3/README.md new file mode 100644 index 000000000000..65f3ce387643 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-3/README.md @@ -0,0 +1,5 @@ +## Relevant Articles + +insert here + +- More articles: [[<-- prev]](/spring-boot-modules/spring-boot-3-2) diff --git a/spring-boot-modules/spring-boot-3-3/pom.xml b/spring-boot-modules/spring-boot-3-3/pom.xml new file mode 100644 index 000000000000..c6f43a93c522 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-3/pom.xml @@ -0,0 +1,69 @@ + + 4.0.0 + spring-boot-3-3 + 0.0.1-SNAPSHOT + spring-boot-3-3 + Demo project for Spring Boot + + com.baeldung.spring-boot-modules + spring-boot-modules + 1.0.0-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-web + + + org.apache.camel.springboot + camel-spring-boot-starter + ${apache-camel.version} + + + org.apache.camel.springboot + camel-http-starter + ${apache-camel.version} + + + org.apache.camel + camel-jackson + ${apache-camel.version} + + + dev.langchain4j + langchain4j-core + ${langchain4j.version} + + + dev.langchain4j + langchain4j-ollama + ${langchain4j.version} + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + -parameters + + + + + + + 3.2.4 + 4.7.0 + 0.33.0 + + diff --git a/spring-boot-modules/spring-boot-3-3/src/main/java/com/baeldung/chatbot/ChatbotApplication.java b/spring-boot-modules/spring-boot-3-3/src/main/java/com/baeldung/chatbot/ChatbotApplication.java new file mode 100644 index 000000000000..5c4688c18a20 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-3/src/main/java/com/baeldung/chatbot/ChatbotApplication.java @@ -0,0 +1,13 @@ +package com.baeldung.chatbot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ChatbotApplication { + + public static void main(String[] args) { + SpringApplication.run(ChatbotApplication.class, args); + } + +} diff --git a/spring-boot-modules/spring-boot-3-3/src/main/java/com/baeldung/chatbot/controller/ChatbotController.java b/spring-boot-modules/spring-boot-3-3/src/main/java/com/baeldung/chatbot/controller/ChatbotController.java new file mode 100644 index 000000000000..5dbf2bd7a2ce --- /dev/null +++ b/spring-boot-modules/spring-boot-3-3/src/main/java/com/baeldung/chatbot/controller/ChatbotController.java @@ -0,0 +1,18 @@ +package com.baeldung.chatbot.controller; + +import com.baeldung.chatbot.service.ChatbotService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ChatbotController { + @Autowired + private ChatbotService chatbotService; + + @GetMapping("/api/chatbot/send") + public String getChatbotResponse(@RequestParam String question) { + return chatbotService.getResponse(question); + } +} diff --git a/spring-boot-modules/spring-boot-3-3/src/main/java/com/baeldung/chatbot/controller/WhatsAppController.java b/spring-boot-modules/spring-boot-3-3/src/main/java/com/baeldung/chatbot/controller/WhatsAppController.java new file mode 100644 index 000000000000..d7ec841952ec --- /dev/null +++ b/spring-boot-modules/spring-boot-3-3/src/main/java/com/baeldung/chatbot/controller/WhatsAppController.java @@ -0,0 +1,38 @@ +package com.baeldung.chatbot.controller; + +import com.baeldung.chatbot.service.WhatsAppService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.*; + +@RestController +public class WhatsAppController { + + @Value("${whatsapp.verify_token}") + private String verifyToken; + + @Autowired + private WhatsAppService whatsAppService; + + @PostMapping("/api/whatsapp/send") + public String sendWhatsAppMessage(@RequestParam String to, @RequestParam String message) { + whatsAppService.sendWhatsAppMessage(to, message); + return "Message sent"; + } + + @GetMapping("/webhook") + public String verifyWebhook(@RequestParam("hub.mode") String mode, + @RequestParam("hub.verify_token") String token, + @RequestParam("hub.challenge") String challenge) { + if ("subscribe".equals(mode) && verifyToken.equals(token)) { + return challenge; + } else { + return "Verification failed"; + } + } + + @PostMapping("/webhook") + public void receiveMessage(@RequestBody String payload) { + whatsAppService.processIncomingMessage(payload); + } +} diff --git a/spring-boot-modules/spring-boot-3-3/src/main/java/com/baeldung/chatbot/service/ChatbotService.java b/spring-boot-modules/spring-boot-3-3/src/main/java/com/baeldung/chatbot/service/ChatbotService.java new file mode 100644 index 000000000000..8beda21222ab --- /dev/null +++ b/spring-boot-modules/spring-boot-3-3/src/main/java/com/baeldung/chatbot/service/ChatbotService.java @@ -0,0 +1,58 @@ +package com.baeldung.chatbot.service; + +import java.time.Duration; + +import org.apache.hc.core5.http.HttpStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +import dev.langchain4j.model.ollama.OllamaChatModel; +import jakarta.annotation.PostConstruct; + +@Service +public class ChatbotService { + + private static final Logger logger = LoggerFactory.getLogger(ChatbotService.class); + + @Value("${ollama.api_url}") + private String apiUrl; + + @Value("${ollama.model}") + private String modelName; + + @Value("${ollama.timeout}") + private int timeout; + + @Value("${ollama.max_response_length}") + private int maxResponseLength; + + private OllamaChatModel ollamaChatModel; + + @PostConstruct + public void init() { + this.ollamaChatModel = OllamaChatModel.builder() + .baseUrl(apiUrl) + .modelName(modelName) + .timeout(Duration.ofSeconds(timeout)) + .numPredict(maxResponseLength) + .build(); + } + + public String getResponse(String question) { + logger.debug("Sending to Ollama: {}", question); + String answer = ollamaChatModel.generate(question); + logger.debug("Receiving from Ollama: {}", answer); + if (answer != null && !answer.isEmpty()) { + return answer; + } else { + logger.error("Invalid Ollama response for:\n\n" + question); + throw new ResponseStatusException( + HttpStatus.SC_INTERNAL_SERVER_ERROR, + "Ollama didn't generate a valid response", + null); + } + } +} diff --git a/spring-boot-modules/spring-boot-3-3/src/main/java/com/baeldung/chatbot/service/WhatsAppService.java b/spring-boot-modules/spring-boot-3-3/src/main/java/com/baeldung/chatbot/service/WhatsAppService.java new file mode 100644 index 000000000000..f3097424ff0b --- /dev/null +++ b/spring-boot-modules/spring-boot-3-3/src/main/java/com/baeldung/chatbot/service/WhatsAppService.java @@ -0,0 +1,92 @@ +package com.baeldung.chatbot.service; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.jackson.JacksonDataFormat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.annotation.PostConstruct; + +@Service +public class WhatsAppService { + + private static final Logger logger = LoggerFactory.getLogger(WhatsAppService.class); + + @Value("${whatsapp.api_url}") + private String apiUrl; + + @Value("${whatsapp.access_token}") + private String apiToken; + + @Autowired + private CamelContext camelContext; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private ProducerTemplate producerTemplate; + + @Autowired + private ChatbotService chatbotService; + + @PostConstruct + public void init() throws Exception { + camelContext.addRoutes(new RouteBuilder() { + @Override + public void configure() { + JacksonDataFormat jacksonDataFormat = new JacksonDataFormat(); + jacksonDataFormat.setPrettyPrint(true); + + from("direct:sendWhatsAppMessage") + .setHeader("Authorization", constant("Bearer " + apiToken)) + .setHeader("Content-Type", constant("application/json")) + .marshal(jacksonDataFormat) + .process(exchange -> { + logger.debug("Sending JSON: {}", exchange.getIn().getBody(String.class)); + }).to(apiUrl).process(exchange -> { + logger.debug("Response: {}", exchange.getIn().getBody(String.class)); + }); + } + }); + } + + public void sendWhatsAppMessage(String toNumber, String message) { + Map body = new HashMap<>(); + body.put("messaging_product", "whatsapp"); + body.put("to", toNumber); + body.put("type", "text"); + + Map text = new HashMap<>(); + text.put("body", message); + body.put("text", text); + + producerTemplate.sendBody("direct:sendWhatsAppMessage", body); + } + + public void processIncomingMessage(String payload) { + try { + JsonNode jsonNode = objectMapper.readTree(payload); + JsonNode messages = jsonNode.at("/entry/0/changes/0/value/messages"); + if (messages.isArray() && messages.size() > 0) { + String receivedText = messages.get(0).at("/text/body").asText(); + String fromNumber = messages.get(0).at("/from").asText(); + logger.debug(fromNumber + " sent the message: " + receivedText); + this.sendWhatsAppMessage(fromNumber, chatbotService.getResponse(receivedText)); + } + } catch (Exception e) { + logger.error("Error processing incoming payload: {} ", payload, e); + } + } +} diff --git a/spring-boot-modules/spring-boot-3-3/src/main/resources/application.properties b/spring-boot-modules/spring-boot-3-3/src/main/resources/application.properties new file mode 100644 index 000000000000..a2c75b9bcc65 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-3/src/main/resources/application.properties @@ -0,0 +1,14 @@ +# WhatsApp API configuration +whatsapp.verify_token=BaeldungDemo-Verify-Token +whatsapp.api_url=https://graph.facebook.com/v20.0/PHONE_NUMBER_ID/messages +whatsapp.access_token=ACCESS_TOKEN + +# Ollama API configuration +ollama.api_url=http://localhost:11434/ +ollama.model=qwen2:1.5b +ollama.timeout=30 +ollama.max_response_length=1000 + +# Logging configuration +logging.level.root=INFO +logging.level.com.baeldung.chatbot=DEBUG diff --git a/spring-boot-modules/spring-boot-3-3/src/test/java/com/baeldung/chatbot/ChatbotApplicationTests.java b/spring-boot-modules/spring-boot-3-3/src/test/java/com/baeldung/chatbot/ChatbotApplicationTests.java new file mode 100644 index 000000000000..4f7c2a7496c0 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-3/src/test/java/com/baeldung/chatbot/ChatbotApplicationTests.java @@ -0,0 +1,15 @@ +package com.baeldung.chatbot; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +class ChatbotApplicationTests { + + @Test + void contextLoads() { + assertThat(true).isTrue(); + } +}