From c0a05449237c4af7cb8e67e708d084f303d1ba1c Mon Sep 17 00:00:00 2001 From: Yan Date: Sun, 6 Apr 2025 16:00:49 +0800 Subject: [PATCH] v3.2.0 Added inspection client and email service --- .gitignore | 1 + pom.xml | 17 +- .../com/example/sbcamel/init/AppConfig.java | 41 ++++- .../com/example/sbcamel/init/CamelRouter.java | 12 +- .../example/sbcamel/init/SecurityConfig.java | 6 +- .../processor/InspectionProcessor.java | 75 ++++---- .../sbcamel/processor/TestProcessor.java | 12 +- .../example/sbcamel/service/EmailService.java | 70 ++++++++ .../sbcamel/service/InspectionClient.java | 161 ++++++++++++++++++ .../com/example/sbcamel/service/Session.java | 6 +- .../sbcamel/service/SessionService.java | 4 +- src/main/resources/schedule.xml | 19 +++ 12 files changed, 355 insertions(+), 69 deletions(-) create mode 100644 src/main/java/com/example/sbcamel/service/EmailService.java create mode 100644 src/main/java/com/example/sbcamel/service/InspectionClient.java create mode 100644 src/main/resources/schedule.xml diff --git a/.gitignore b/.gitignore index 789da79..185ccfb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /conf/ /conf2/ /bruno/ +/backup/ diff --git a/pom.xml b/pom.xml index 9253941..8161b0e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.example camel-springboot-activemq6-example - 3.1.0 + 3.2.0 camel-springboot-activemq6-example @@ -19,6 +19,7 @@ 33.1.0-jre 3.14.0 1.19.1 + 1.6.2 @@ -145,6 +146,20 @@ jsoup ${jsoup.version} + + org.apache.httpcomponents.client5 + httpclient5 + + + javax.mail + javax.mail-api + ${java.mail.version} + + + com.sun.mail + javax.mail + ${java.mail.version} + diff --git a/src/main/java/com/example/sbcamel/init/AppConfig.java b/src/main/java/com/example/sbcamel/init/AppConfig.java index 92fb308..47b5150 100644 --- a/src/main/java/com/example/sbcamel/init/AppConfig.java +++ b/src/main/java/com/example/sbcamel/init/AppConfig.java @@ -1,25 +1,62 @@ package com.example.sbcamel.init; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; + +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportResource; import com.example.sbcamel.processor.InspectionProcessor; import com.example.sbcamel.processor.TestProcessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider; @Configuration +@ImportResource("classpath:schedule.xml") public class AppConfig { + @Value("${app.inspection-client.javamail-config}") + private String javamailCfg; + + @Bean + public Properties javaMailProperties() throws IOException { + Properties javaMailProperties = new Properties(); + FileInputStream fis = new FileInputStream(javamailCfg); + javaMailProperties.load(fis); + fis.close(); + return javaMailProperties; + } + + @Bean + public CloseableHttpClient httpClient() { + return HttpClients.createDefault(); + } + + @Bean("objectMapper") + public ObjectMapper objectMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + mapper.registerModule(new Jdk8Module()); + return mapper; + } + @Bean public JacksonJsonProvider jaxrsProvider() { return new JacksonJsonProvider(); } - + @Bean public InspectionProcessor inspectionProcessor() { return new InspectionProcessor(); } - + @Bean public TestProcessor testProcessor() { return new TestProcessor(); diff --git a/src/main/java/com/example/sbcamel/init/CamelRouter.java b/src/main/java/com/example/sbcamel/init/CamelRouter.java index d0f72ff..9475d50 100644 --- a/src/main/java/com/example/sbcamel/init/CamelRouter.java +++ b/src/main/java/com/example/sbcamel/init/CamelRouter.java @@ -70,20 +70,18 @@ public class CamelRouter extends RouteBuilder { exchange.getIn().getHeader(CxfConstants.OPERATION_NAME.toLowerCase()))) .to("log:activemq?showAll=true").transacted("propagationRequired").choice() .when(header(BeanConstants.BEAN_METHOD_NAME).isEqualTo("updateSession")) - .to("mybatis:" + SessionMapper.class.getName() + ".updateSession?statementType=Update&outputHeader=X-UpdateResult") + .to("mybatis:" + SessionMapper.class.getName() + + ".updateSession?statementType=Update&outputHeader=X-UpdateResult") .process(exchange -> { exchange.getMessage().setBody(exchange.getIn().getBody()); - }) - .when(header(BeanConstants.BEAN_METHOD_NAME).isEqualTo("findSessions")) + }).when(header(BeanConstants.BEAN_METHOD_NAME).isEqualTo("findSessions")) .to("mybatis:" + SessionMapper.class.getName() + ".findSessions?statementType=SelectList") .when(header(BeanConstants.BEAN_METHOD_NAME).isEqualTo("findSession")) .to("mybatis:" + SessionMapper.class.getName() + ".findSession?statementType=SelectOne") .when(header(BeanConstants.BEAN_METHOD_NAME).isEqualTo("inspect")) .to("mybatis:" + SessionMapper.class.getName() + ".findSession?statementType=SelectOne") - .process(inspectionProcessor) - .when(header(BeanConstants.BEAN_METHOD_NAME).isEqualTo("test")) - .process(testProcessor) - .end().marshal().json(JsonLibrary.Jackson); + .process(inspectionProcessor).when(header(BeanConstants.BEAN_METHOD_NAME).isEqualTo("test")) + .process(testProcessor).end().marshal().json(JsonLibrary.Jackson); } } \ No newline at end of file diff --git a/src/main/java/com/example/sbcamel/init/SecurityConfig.java b/src/main/java/com/example/sbcamel/init/SecurityConfig.java index 94bc962..5e58fa3 100644 --- a/src/main/java/com/example/sbcamel/init/SecurityConfig.java +++ b/src/main/java/com/example/sbcamel/init/SecurityConfig.java @@ -35,8 +35,8 @@ public class SecurityConfig { public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests((authorize) -> authorize.requestMatchers(HttpMethod.GET, "/**") .hasAuthority(ROLE_BACKEND).requestMatchers(HttpMethod.POST, "/**").hasAuthority(ROLE_SERVER) - .requestMatchers(HttpMethod.PUT, "/**").permitAll()) - .httpBasic(Customizer.withDefaults()).csrf(csrf -> csrf.disable()); + .requestMatchers(HttpMethod.PUT, "/**").permitAll()).httpBasic(Customizer.withDefaults()) + .csrf(csrf -> csrf.disable()); return http.build(); } @@ -56,5 +56,5 @@ public class SecurityConfig { factory.setUserSearchFilter(userSearchFilter); factory.setLdapAuthoritiesPopulator(authorities); return factory.createAuthenticationManager(); - } + } } \ No newline at end of file diff --git a/src/main/java/com/example/sbcamel/processor/InspectionProcessor.java b/src/main/java/com/example/sbcamel/processor/InspectionProcessor.java index aee970a..42be66e 100644 --- a/src/main/java/com/example/sbcamel/processor/InspectionProcessor.java +++ b/src/main/java/com/example/sbcamel/processor/InspectionProcessor.java @@ -1,5 +1,7 @@ package com.example.sbcamel.processor; +import java.io.IOException; +import java.net.MalformedURLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -7,6 +9,7 @@ import java.util.Map; import org.apache.camel.Exchange; import org.apache.camel.Processor; +import org.htmlunit.FailingHttpStatusCodeException; import org.htmlunit.WebClient; import org.htmlunit.html.HtmlButton; import org.htmlunit.html.HtmlForm; @@ -54,69 +57,34 @@ public class InspectionProcessor implements Processor { @Override public void process(Exchange exchange) throws Exception { Session session = (Session) exchange.getMessage().getBody(); - logger.info("session: {}", session); + logger.debug("session: {}", session); try (final WebClient webClient = new WebClient()) { final HtmlPage loginPage = webClient.getPage(loginUrl); - logger.info("loginPage: {}", loginPage.asXml()); + logger.debug("loginPage: {}", loginPage.asXml()); final HtmlForm form = loginPage.getForms().get(0); final HtmlTextInput usernameField = form.getInputByName("username"); usernameField.type(session.getUsername()); - logger.info("entered username: {}", session.getUsername()); + logger.debug("entered username: {}", session.getUsername()); final HtmlPasswordInput passwordField = form.getInputByName("password"); passwordField.type(session.getPassword()); - logger.info("entered password: {}", session.getPassword()); + logger.debug("entered password: {}", session.getPassword()); HtmlButton loginBtn = loginPage.querySelector(loginBtnSelector); - logger.info("loginBtn type: {}", loginBtn.getType()); + logger.debug("loginBtn type: {}", loginBtn.getType()); loginBtn.click(); Thread.sleep(loginWaitMillis); - - HtmlPage userPage = webClient.getPage(userPageUrl); - logger.info("userPage hashcode: {}", userPage.hashCode()); - - Map> reply1 = new HashMap<>(); - HtmlPage vip1Page = webClient.getPage(vip1PageUrl); - Document doc = Jsoup.parse(vip1Page.asXml()); - for (String selector : List.of(session.getSelector1(), session.getSelector2(), session.getSelector3())) { - Elements elements = doc.select(selector); - List selectedList = new ArrayList<>(); - for (Element element : elements) { - logger.info("[vip1Page] {} -> {}", selector, element.html()); - selectedList.add(element.html()); - } - reply1.put(selector, selectedList); - } - Map> reply2 = new HashMap<>(); - HtmlPage vip2Page = webClient.getPage(vip2PageUrl); - doc = Jsoup.parse(vip2Page.asXml()); - for (String selector : List.of(session.getSelector1(), session.getSelector2(), session.getSelector3())) { - Elements elements = doc.select(selector); - List selectedList = new ArrayList<>(); - for (Element element : elements) { - logger.info("[vip2Page] {} -> {}", selector, element.html()); - selectedList.add(element.html()); - } - reply2.put(selector, selectedList); - } + HtmlPage userPage = webClient.getPage(userPageUrl); + logger.debug("userPage hashcode: {}", userPage.hashCode()); - Map> reply3 = new HashMap<>(); - HtmlPage vip3Page = webClient.getPage(vip3PageUrl); - doc = Jsoup.parse(vip3Page.asXml()); - for (String selector : List.of(session.getSelector1(), session.getSelector2(), session.getSelector3())) { - Elements elements = doc.select(selector); - List selectedList = new ArrayList<>(); - for (Element element : elements) { - logger.info("[vip3Page] {} -> {}", selector, element.html()); - selectedList.add(element.html()); - } - reply3.put(selector, selectedList); - } + Map> reply1 = fillResponseMap(webClient, session, vip1PageUrl); + Map> reply2 = fillResponseMap(webClient, session, vip2PageUrl); + Map> reply3 = fillResponseMap(webClient, session, vip3PageUrl); webClient.getPage(logoutUrl); exchange.getMessage().setBody(Map.of("vip1Page", reply1, "vip2Page", reply2, "vip3Page", reply3)); @@ -126,4 +94,21 @@ public class InspectionProcessor implements Processor { } } + private Map> fillResponseMap(WebClient webClient, Session session, String url) + throws FailingHttpStatusCodeException, MalformedURLException, IOException { + Map> reply = new HashMap<>(); + HtmlPage vipPage = webClient.getPage(url); + Document doc = Jsoup.parse(vipPage.asXml()); + for (String selector : List.of(session.getSelector1(), session.getSelector2(), session.getSelector3())) { + Elements elements = doc.select(selector); + List selectedList = new ArrayList<>(); + for (Element element : elements) { + logger.debug("[fillResponseMap] {} -> {}", selector, element.html()); + selectedList.add(element.html()); + } + reply.put(selector, selectedList); + } + return reply; + } + } diff --git a/src/main/java/com/example/sbcamel/processor/TestProcessor.java b/src/main/java/com/example/sbcamel/processor/TestProcessor.java index 4a32876..1e1affa 100644 --- a/src/main/java/com/example/sbcamel/processor/TestProcessor.java +++ b/src/main/java/com/example/sbcamel/processor/TestProcessor.java @@ -17,28 +17,28 @@ import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.annotation.Value; public class TestProcessor implements Processor { - + private static final Logger logger = LoggerFactory.getLogger(TestProcessor.class); @Value("${app.jsoup.html-file}") private String htmlFilePath; - + @SuppressWarnings("unchecked") @Override - public void process(Exchange exchange) throws Exception { + public void process(Exchange exchange) throws Exception { logger.info("in class: {}", exchange.getIn().getBody().getClass().getName()); MessageContentsList mcl = (MessageContentsList) exchange.getIn().getBody(); List selectors = (List) mcl.get(0); logger.info("selectors: {}", selectors); - + File htmlFile = new File(htmlFilePath); String html = FileUtils.readFileToString(htmlFile, StandardCharsets.UTF_8); Document doc = Jsoup.parse(html); Map> reply = new HashMap<>(); - + for (String selector : selectors) { Elements elements = doc.select(selector); List selectedList = new ArrayList<>(); diff --git a/src/main/java/com/example/sbcamel/service/EmailService.java b/src/main/java/com/example/sbcamel/service/EmailService.java new file mode 100644 index 0000000..66048dd --- /dev/null +++ b/src/main/java/com/example/sbcamel/service/EmailService.java @@ -0,0 +1,70 @@ +package com.example.sbcamel.service; + +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + +import javax.mail.Message; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class EmailService { + + private static final Logger logger = LoggerFactory.getLogger(EmailService.class); + + @Autowired + private Properties javaMailProperties; + + public boolean sendEmail(String fromAddress, String smtpPassword, String[] toAddresses, String subject, + String content) { + try { + logger.debug("[sendEmail] fromAddress: {}, toAddresses: {}", fromAddress, toAddresses); + if (StringUtils.isEmpty(subject) || StringUtils.isEmpty(smtpPassword) || StringUtils.isEmpty(fromAddress) + || toAddresses.length == 0) { + logger.warn("[sendEmail] subject, smtp-password, source-address or target-address is not set"); + return false; + } + String emailHost = javaMailProperties.getProperty("mail.smtp.host"); + logger.debug("[sendEmail] {} -> {}, mail.smtp.host: {}", fromAddress, List.of(toAddresses), emailHost); + if (StringUtils.isNotBlank(emailHost) && StringUtils.isNotBlank(fromAddress) && toAddresses.length > 0) { + logger.debug("[sendEmail] starting to send message from: {}", fromAddress); + Session session = Session.getInstance(javaMailProperties); + + MimeMessage message = new MimeMessage(session); + message.setContent(content, "text/html; charset=utf-8"); + message.setFrom(new InternetAddress(fromAddress)); + + Arrays.asList(toAddresses).forEach(toAddress -> { + try { + message.addRecipient(Message.RecipientType.TO, new InternetAddress(toAddress)); + } catch (Exception e) { + logger.error("sendEmail error!", e); + } + }); + message.setSubject(subject); + + Transport transport = session.getTransport("smtp"); + transport.connect(emailHost, fromAddress, smtpPassword); + transport.sendMessage(message, message.getAllRecipients()); + transport.close(); + logger.debug("sendEmail to: {}", Arrays.asList(toAddresses)); + + return true; + } else { + logger.warn("[sendEmail] missing either from-field, to-field or host address!"); + } + } catch (Exception e) { + logger.error("sendEmail error!", e); + } + return false; + } +} diff --git a/src/main/java/com/example/sbcamel/service/InspectionClient.java b/src/main/java/com/example/sbcamel/service/InspectionClient.java new file mode 100644 index 0000000..4d78a2b --- /dev/null +++ b/src/main/java/com/example/sbcamel/service/InspectionClient.java @@ -0,0 +1,161 @@ +package com.example.sbcamel.service; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.MapDifference; +import com.google.common.collect.MapDifference.ValueDifference; +import com.google.common.collect.Maps; + +public class InspectionClient { + + private static final Logger logger = LoggerFactory.getLogger(InspectionClient.class); + + @Autowired + private CloseableHttpClient httpClient; + + @Autowired + private EmailService emailService; + + @Autowired + private ObjectMapper objectMapper; + + @Value("${app.inspection-client.email-password:}") + private String smtpPassword; + + @Value("${app.inspection-client.email-sender:}") + private String fromAddress; + + @Value("${app.inspection-client.email-recipient:}") + private String[] toAddresses; + + @Value("${app.inspection-client.http-url}") + private String url; + + @Value("${app.inspection-client.http-method}") + private String httpMethod; + + @Value("${app.inspection-client.expected-result-file-path}") + private String expectedResultFilePath; + + @Value("${app.inspection-client.alert-subject}") + private String alertSubject; + + @Value("${app.inspection-client.http-username}") + private String username; + + @Value("${app.inspection-client.http-password}") + private String password; + + private static final String getBasicAuthenticationHeader(String username, String password) { + String valueToEncode = username + ":" + password; + return "Basic " + Base64.getEncoder().encodeToString(valueToEncode.getBytes()); + } + + public void sendMessage() { + try { + File jsonFile = new File(expectedResultFilePath); + if (!jsonFile.exists()) { + logger.info("file does not exist: {}", expectedResultFilePath); + return; + } + String jsonString = FileUtils.readFileToString(jsonFile, StandardCharsets.UTF_8); + logger.info("expected result: {}", jsonString); + HashMap expectedMap = convertStringToMap(jsonString); + logger.info("calling {} {}", httpMethod, url); + + HttpUriRequestBase req = new HttpUriRequestBase(httpMethod, URI.create(url)); + req.addHeader("Authorization", getBasicAuthenticationHeader(username, password)); + Pair sendEmailResult = httpClient.execute(req, + new HttpClientResponseHandler>() { + + @Override + public Pair handleResponse(ClassicHttpResponse result) + throws HttpException, IOException { + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + result.getEntity().writeTo(baos); + String responseText = new String(baos.toByteArray(), StandardCharsets.UTF_8); + logger.info("actual result: {}", responseText); + + if (StringUtils.isEmpty(responseText)) { + logger.error("actual result is empty!"); + return Pair.of(Boolean.FALSE, Boolean.FALSE); + } + baos.close(); + try { + HashMap resultMap = convertStringToMap(responseText); + MapDifference diff = Maps.difference(expectedMap, resultMap); + logger.info("diff.areEqual: {}", diff.areEqual()); + StringBuilder contentBuilder = new StringBuilder(); + if (!diff.areEqual()) { + Map> entriesDiffering = diff.entriesDiffering(); + entriesDiffering.entrySet().stream().forEach(entry -> { + logger.info("entriesDiffering key: {}", entry.getKey()); + logger.info("entriesDiffering expected: {}", entry.getValue().leftValue()); + logger.info("entriesDiffering actual: {}", entry.getValue().rightValue()); + contentBuilder.append(entry.getKey() + "\n"); + contentBuilder.append("EXPECTED: " + entry.getValue().leftValue() + "\n"); + contentBuilder.append("ACTUAL: " + entry.getValue().rightValue() + "\n"); + contentBuilder.append("\n"); + }); + return Pair.of(Boolean.TRUE, emailService.sendEmail(fromAddress, smtpPassword, + toAddresses, alertSubject, contentBuilder.toString())); + } + } catch (IOException e) { + logger.error("Error occurred while parsing message", e); + } + return Pair.of(Boolean.FALSE, Boolean.FALSE); + } + + }); + logger.info("send email result: {}", sendEmailResult); + + } catch (Exception e) { + logger.error("Error occurred while preparing to send message", e); + } + } + + public HashMap convertStringToMap(String jsonString) throws IOException { + TypeReference> ref = new TypeReference<>() { + }; + return new HashMap<>(objectMapper.readValue(jsonString, ref)); + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getHttpMethod() { + return httpMethod; + } + + public void setHttpMethod(String httpMethod) { + this.httpMethod = httpMethod; + } +} diff --git a/src/main/java/com/example/sbcamel/service/Session.java b/src/main/java/com/example/sbcamel/service/Session.java index 0e431f0..878262b 100644 --- a/src/main/java/com/example/sbcamel/service/Session.java +++ b/src/main/java/com/example/sbcamel/service/Session.java @@ -24,13 +24,13 @@ public class Session implements Serializable { @NotNull @Size(min = 3, max = 40) private String password; - + @NotNull private String selector1; - + @NotNull private String selector2; - + @NotNull private String selector3; diff --git a/src/main/java/com/example/sbcamel/service/SessionService.java b/src/main/java/com/example/sbcamel/service/SessionService.java index a8e5f3f..f0da502 100644 --- a/src/main/java/com/example/sbcamel/service/SessionService.java +++ b/src/main/java/com/example/sbcamel/service/SessionService.java @@ -33,12 +33,12 @@ public interface SessionService { @Path("/session") @Produces(MediaType.APPLICATION_JSON) Collection findSessions(); - + @POST @Path("/inspect/{sessionId}") @Produces(MediaType.APPLICATION_JSON) Map> inspect(@PathParam("sessionId") UUID sessionId); - + @POST @Path("/test") @Produces(MediaType.APPLICATION_JSON) diff --git a/src/main/resources/schedule.xml b/src/main/resources/schedule.xml new file mode 100644 index 0000000..1f11a2f --- /dev/null +++ b/src/main/resources/schedule.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file