Jackson xml map attribute value to property - java

I am integrating with an old system and have a need to parse the following xml into my object. I am trying to do this with jackson but I can't get the mapping to work. Anyone know how to map the following xml to the pojo?
#JacksonXmlRootElement(localName = "properties")
#Data
public class Example {
private String token;
private String affid;
private String domain;
}
xml example:
<properties>
<entry key="token">rent</entry>
<entry key="affid">true</entry>
<entry key="domain">checking</entry>
</properties>
I have tried adding
#JacksonXmlProperty(isAttribute = true, localName = "key")
to the properties but this of course doesn't work and I do not see another way to get this to work. Any ideas?
I am using the mapper like so...
ObjectMapper xmlMapper = new XmlMapper();
dto = xmlMapper.readValue(XML_STRING, Example .class);
I am using the following dependencies
compile('org.springframework.boot:spring-boot-starter-web')
runtime('org.springframework.boot:spring-boot-devtools')
compileOnly('org.projectlombok:lombok')
testCompile('org.springframework.boot:spring-boot-starter-test')
compile('org.apache.commons:commons-lang3:3.5')
compile('com.fasterxml.jackson.dataformat:jackson-dataformat-xml')
compile('com.squareup.okhttp3:okhttp:3.10.0')

This does work.
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;
public class XmlParserDemo {
public static void main(String[] args) throws IOException, XMLStreamException {
String xmlString = "<properties>\n" +
" <entry key=\"token\">rent</entry>\n" +
" <entry key=\"affid\">true</entry>\n" +
" <entry key=\"domain\">checking</entry>\n" +
"</properties>";
XMLStreamReader sr = null;
sr = XMLInputFactory.newFactory().createXMLStreamReader(new StringReader(xmlString));
sr.next();
XmlMapper mapper = new XmlMapper();
List<Entry> entries = mapper.readValue(sr, new TypeReference<List<Entry>>() {
});
sr.close();
entries.forEach(e ->
System.out.println(e.key + ":" + e.value));
}
public static class Entry {
#JacksonXmlProperty(isAttribute = true, localName = "key")
private String key;
#JacksonXmlText
private String value;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}
Output is:
token:rent
affid:true
domain:checking

I have looked through Jackson thoroughly and it doesn't seem that there is a way to accomplish this. However, I will share my solution here in case it is useful to someone else.
package com.example.config;
import com.example.dto.Example;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
public class Converter extends AbstractHttpMessageConverter<Example> {
private static final XPath XPATH_INSTANCE = XPathFactory.newInstance().newXPath();
private static final StringHttpMessageConverter MESSAGE_CONVERTER = new StringHttpMessageConverter();
#Override
protected boolean supports(Class<?> aClass) {
return aClass == Example.class;
}
#Override
protected Example readInternal(Class<? extends LongFormDTO> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
String responseString = MESSAGE_CONVERTER.read(String.class, httpInputMessage);
Reader xmlInput = new StringReader(responseString);
InputSource inputSource = new InputSource(xmlInput);
Example dto = new Example();
Node xml;
try {
xml = (Node) XPATH_INSTANCE.evaluate("/properties", inputSource, XPathConstants.NODE);
} catch (XPathExpressionException e) {
log.error("Unable to parse response", e);
return dto;
}
log.info("processing populate application response={}", responseString);
dto.setToken(getString("token", xml));
dto.setAffid(getInt("affid", xml, 36));
dto.domain(getString("domain", xml));
xmlInput.close();
return dto;
}
private String getString(String propName, Node xml, String defaultValue) {
String xpath = String.format("//entry[#key='%s']/text()", propName);
try {
String value = (String) XPATH_INSTANCE.evaluate(xpath, xml, XPathConstants.STRING);
return StringUtils.isEmpty(value) ? defaultValue : value;
} catch (XPathExpressionException e) {
log.error("Received error retrieving property={} from xml", propName, e);
}
return defaultValue;
}
private String getString(String propName, Node xml) {
return getString(propName, xml, null);
}
private int getInt(String propName, Node xml, int defaultValue) {
String stringValue = getString(propName, xml);
if (!StringUtils.isEmpty(stringValue)) {
try {
return Integer.parseInt(stringValue);
} catch (NumberFormatException e) {
log.error("Attempted to parse value={} as integer but received error", stringValue, e);
}
}
return defaultValue;
}
private int getInt(String propName, Node xml) {
return getInt(propName, xml,0);
}
private boolean getBoolean(String propName, Node xml) {
String stringValue = getString(propName, xml );
return Boolean.valueOf(stringValue);
}
#Override
protected void writeInternal(Example dto, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
throw new UnsupportedOperationException("Responses of type=" + MediaType.TEXT_PLAIN_VALUE + " are not supported");
}
}
I chose to hide this in a message converter so I don't have to look at it again but you can apply these steps where you see fit. If you choose this route, you will need to configure a rest template to use this converter. If not, it is important to cache the xml into a Node object as regenerating each time will be very costly.
package com.example.config;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.MediaType;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
#Configuration
public class RestConfig {
#Bean
#Primary
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
#Bean
public RestTemplate restTemplateLe(RestTemplateBuilder builder) {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
ExampleConverter exampleConverter = new ExampleConverter();
exampleConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.TEXT_PLAIN));
messageConverters.add(exampleConverter);
return builder.messageConverters(messageConverters)
.requestFactory(new OkHttp3ClientHttpRequestFactory())
.build();
}
}

Related

ProducerTemplate. Camel. How to add attachment

Can someone tell me how to add an attachment using ProducerTemplate?
I have been searching but I can not find an answer to my case.
I am using Camen 2.1 and I have these three clases:
MailSender2.java
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePattern;
import org.apache.camel.ProducerTemplate;
public class MailSender2 extends TypeMail{
private static final ResourceBundle RES = ResourceBundle.getBundle("mail");
protected static final String MAIL_NOTIFICATION_ENDPOINT=RES.getString("mail.host.location").trim()+":"+RES.getString("mail.port").trim();
private Map<String, Object> header;
public MailSender2() {
this.header=new HashMap<>();
}
public void send(ProducerTemplate template) {
this.header.put("From", this.getT_from());
this.header.put("To", this.getT_to());
this.header.put("Subject", this.getT_subject());
this.header.put(Exchange.CONTENT_TYPE, "text/html; charset=UTF-8");
//this.getF_ficher() <-- I have here the file to attach
//this.getT_ficnon() <-- I have here the name ot the file
//this.getT_ficext() <-- I have here the extension ot the file
template.sendBodyAndHeaders(MAIL_NOTIFICATION_ENDPOINT, this.getT_mensaje(), header);
}
}
TypeMail.java:
public class TypeMail {
private String t_id;
private String t_from;
private String t_to;
private String t_subject;
private String t_mensaje;
private byte[] f_ficher;
private String t_ficnon;
private String t_ficext;
public String getT_id() {
return t_id;
}
public void setT_id(String t_id) {
this.t_id = t_id;
}
public String getT_from() {
return t_from;
}
public void setT_from(String t_from) {
this.t_from = t_from;
}
public String getT_to() {
return t_to;
}
public void setT_to(String t_to) {
this.t_to = t_to;
}
public String getT_subject() {
return t_subject;
}
public void setT_subject(String t_subject) {
this.t_subject = t_subject;
}
public String getT_mensaje() {
return t_mensaje;
}
public void setT_mensaje(String t_mensaje) {
this.t_mensaje = t_mensaje;
}
public byte[] getF_ficher() {
return f_ficher;
}
public void setF_ficher(byte[] f_ficher) {
this.f_ficher = f_ficher;
}
public String getT_ficnon() {
return t_ficnon;
}
public void setT_ficnon(String t_ficnon) {
this.t_ficnon = t_ficnon;
}
public String getT_ficext() {
return t_ficext;
}
public void setT_ficext(String t_ficext) {
this.t_ficext = t_ficext;
}
}
MailCommunicationTransformer.java:
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.ProducerTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ws.soap.client.SoapFaultClientException;
public class MailCommunicationTransformer {
MailSender2 mailSender = null;
static Logger logger = LoggerFactory.getLogger(MailCommunicationTransformer.class);
public MailCommunicationTransformer()
{
}
public MailLog transform(Object actualMessage, Exchange exchange, CamelContext context)
{
mailSender = exchange.getIn().getBody(MailSender2.class);
try {
MailSenderDAO mailSenderDAO = (MailSenderDAO)context.getRegistry().lookup("MailSenderDAO");
mailSenderDAO.validarInput(mailSender);
if (mailSender!=null) {
ProducerTemplate template=exchange.getContext().createProducerTemplate();
try {
mailSender.send(template);
}
catch (Throwable ex) {
ex.printStackTrace();
exchange.setProperty(Exchange.EXCEPTION_CAUGHT,ex);
}
}
}catch (MailException me) {
me.printStackTrace();
exchange.setProperty(Exchange.EXCEPTION_CAUGHT,me);
}
Throwable e = exchange.getProperty(Exchange.EXCEPTION_CAUGHT,
Throwable.class);
String response = "OK";
if (e != null) {
StringBuffer mensaje = new StringBuffer();
if (e instanceof SoapFaultClientException) {
mensaje.append("MAIL fault exception: CLIENT. ");
} else {
mensaje.append("MAIL fault exception: MAIL. ");
}
logger.info("MailCommunicationTransformer",e);
while (e != null) {
e.printStackTrace();
mensaje.append(e.getMessage());
e = e.getCause();
}
response = mensaje.toString();
}
MailLog log = new MailLog(mailSender, response); //, protocolo
return log;
}
}
In TypeMail I have the file in f_ficher, and the fileName (t_ficnon) and extension (t_ficext), but I can not find how to attach this file in MailSender2 before template.sendBodyAndHeaders(.....)
Any help would be very appreciated.
Regards.
Perhaps I don't fully understand your question, but the ProducerTemplate don't know about the message type.
You just send a body and perhaps also headers to an endpoint.
Therefore the body just needs to be a fully constructed MimeMessage object as documented in the Camel Mail docs.
You can simply construct the mail message with Java and then use the object with the ProducerTemplate (what you already do).
template.sendBodyAndHeaders("your-smtp-endpoint", yourMimeMessageInstance, yourHeaderMap);
Thanks for the answer!
But, finally, I could do it this way:
new class EmailProcessor.java
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.util.Objects;
import java.util.ResourceBundle;
import javax.activation.DataHandler;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.Processor;
import org.apache.commons.codec.binary.Base64;
public class EmailProcessor implements Processor {
// Atributos de la clase
private TypeMail typeMail;
public EmailProcessor(TypeMail typeMail) {
this.typeMail = typeMail;
}
#Override
public void process(Exchange exchange) throws Exception {
Message ms = exchange.getIn();
ms.setHeader("From", this.typeMail.getT_from());
ms.setHeader("To", this.typeMail.getT_to());
ms.setHeader("Subject", this.typeMail.getT_subject());
ms.setHeader(Exchange.CONTENT_TYPE, "text/html; charset=UTF-8");
ms.setBody("<p style='font-family: Calibri;'>" + this.typeMail.getT_mensaje() + "</p>");
if (this.typeMail.getF_ficher() != null) {
String mimeType = "application/pdf";
if ("zip".equals(typeMail.getT_ficext())) {
mimeType = "application/zip";
}
ms.addAttachment(typeMail.getT_ficnom() + "." + typeMail.getT_ficext(), new DataHandler(typeMail.getF_ficher(), mimeType));
}
}
}
MailSender.java:
import java.util.ResourceBundle;
import org.apache.camel.ExchangePattern;
import org.apache.camel.ProducerTemplate;
public class MailSender extends TypeMail{
private static final ResourceBundle RES = ResourceBundle.getBundle("mail");
protected static final String MAIL_NOTIFICATION_ENDPOINT=RES.getString("mail.host.location").trim()+":"+RES.getString("mail.port").trim();
public MailSender() {
}
public void send(ProducerTemplate template) {
template.send(MAIL_NOTIFICATION_ENDPOINT, ExchangePattern.InOnly, new EmailProcessor(this));
}
}

Writing a custom Deserializer for homogenuous Collections with unbounded generics

I'm struggling to deserialize a Collection<Collection<?>> using Jackson. When deserializing the serialized object Jackson converts them into a LinkedHashMap instead of Item:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class JsonTest {
ObjectMapper mapper = new ObjectMapper();
#Test
void main() throws JsonProcessingException {
Set<Integer> firstSet = Set.of(1, 2, 3);
Set<Item> secondSet = Set.of(new Item("abc"), new Item("123"));
Root root = new Root(List.of(firstSet, secondSet));
String json = mapper.writeValueAsString(root);
System.out.println(json);
Root parsed = mapper.readValue(json, Root.class);
assertEquals(firstSet, parsed.data().get(0));
assertEquals(secondSet, parsed.data().get(1));
// the assertion above fails:
// Expected :[Item[id=abc], Item[id=123]]
// Actual :[{id=abc}, {id=123}]
}
}
record Root(List<Set<?>> data) {}
record Item(String id) {}
// build.gradle
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
test {
useJUnitPlatform()
}
My first idea was to replace the Set<?> with a custom container class that contains an additional type hint and write a custom deserializer:
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class JsonTest {
ObjectMapper mapper = new ObjectMapper();
#Test
void main() throws JsonProcessingException {
TypedList<Item> secondSet = new TypedList<>(Item.class, List.of());
Root root = new Root(secondSet);
String json = mapper.writeValueAsString(root);
System.out.println(json);
Root parsed = mapper.readValue(json, Root.class);
assertEquals(secondSet, parsed.data());
}
}
class TypedCollectionDeserializer<T> extends StdDeserializer<TypedList<T>> {
public TypedCollectionDeserializer() {
super(TypedList.class);
}
#Override
public TypedList<T> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
Class<T> valueType = getTypeClass(p);
String dataKey = p.nextFieldName();
if (!dataKey.equals("data")) {
throw new IllegalStateException();
}
List<T> list = deserializeData(p, valueType);
// skip END_ARRAY
p.nextToken();
return new TypedList<>(valueType, list);
}
private Class<T> getTypeClass(JsonParser p) throws IOException {
String typeKey = p.nextFieldName();
if (!typeKey.equals("type")) {
throw new IllegalStateException();
}
String typeValue = p.nextTextValue();
try {
return (Class<T>) Class.forName(typeValue);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("unexpected type " + typeValue, e);
}
}
private List<T> deserializeData(JsonParser p, Class<T> valueType) throws IOException {
List<T> list = new ArrayList<>();
JsonToken jsonToken = p.nextToken();
if (jsonToken != JsonToken.START_ARRAY) {
throw new IllegalArgumentException();
}
JsonToken maybeStart = p.nextToken();
if (maybeStart == JsonToken.START_OBJECT) {
do {
T t = p.readValueAs(valueType);
if (t != null) {
list.add(t);
}
} while (p.nextToken() == JsonToken.START_OBJECT);
}
return list;
}
}
record Root(#JsonDeserialize(using = TypedCollectionDeserializer.class) TypedList<?> data) {}
record Item(String id) {}
#JsonPropertyOrder({"type", "data"})
record TypedList<T>(Class<T> type, List<T> data) {}
But this looks like I'm re-doing Jackson's own code to deserialize Collections. Is there perhaps a more idiomatic way?

Non-blocking FileWalkTree file search with Webflux

I need to create a non-blocking functionality where I search text files within a given folder and it returns the count of search terms found in it.
I am able to execute the test in a blocking manner. I am wondering if anyone could help me transform it into a non-blocking task so that whenever a file is finished being scanned the result delivered without waiting for all the files to be scanned.
The main point is: I don't want to wait for all files to be scanned to start delivering the result to the client (Angular app).
public interface SearchService {
List<SearchResponse> search(SearchRequest searchRequest);
}
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
#ToString
#Getter
#RequiredArgsConstructor(staticName = "of")
public class SearchResponse implements Serializable {
private final String server;
private final String filePath;
private final long count;
private final boolean errorReadingFile;
}
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
#ToString
#Getter
#RequiredArgsConstructor(staticName = "of")
public class SearchRequest implements Serializable {
#NotNull
private final String server;
#NotNull
private final String rootPath;
#NotNull
private final String searchTerm;
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
#Slf4j
#Service
public class FileSearchService implements SearchService {
#Override
public List<SearchResponse> search(SearchRequest searchRequest) {
Path start = Paths.get(searchRequest.getRootPath());
EnumSet<FileVisitOption> opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
int maxDepth = Integer.MAX_VALUE;
SearchTermFileVisitor visitor = new SearchTermFileVisitor(searchRequest, new ArrayList<>());
try {
Files.walkFileTree(start,opts,maxDepth, visitor);
return visitor.getSearchResponseList();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
#Slf4j
#Getter
#RequiredArgsConstructor
public class SearchTermFileVisitor extends SimpleFileVisitor<Path> {
private final SearchRequest searchRequest;
private final List<SearchResponse> searchResponseList;
private SearchResponse searchFileContent(Path path, SearchRequest searchRequest) {
SearchResponse response;
try (BufferedReader br = Files.newBufferedReader(path)) {
response = SearchResponse.of(
searchRequest.getServer(),
Paths.get(path.toUri()).toString(),
countWordsInFile(searchRequest.getSearchTerm(), br.lines()),
false);
} catch (Exception e) {
response = SearchResponse.of(
searchRequest.getServer(),
path.toString(),
0,
true);
}
log.debug(response.toString());
return response;
}
private int countWordsInFile(String searchTerm, Stream<String> linesStream) {
return linesStream
.parallel()
.map(line -> countWordsInLine(line, searchTerm))
.reduce(0, Integer::sum);
}
private int countWordsInLine(String line, String searchTerm) {
Pattern pattern = Pattern.compile(searchTerm.toLowerCase());
Matcher matcher = pattern.matcher(line.toLowerCase());
int count = 0;
int i = 0;
while (matcher.find(i)) {
count++;
i = matcher.start() + 1;
}
return count;
}
private boolean isTextFile(Path path) throws IOException {
String type = Files.probeContentType(path);
if (type == null) {
//type couldn't be determined, assume binary
return false;
} else if (type.startsWith("text")) {
return true;
} else {
//type isn't text
return false;
}
}
#Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
log.debug("Visited: " + (Path) dir);
return FileVisitResult.CONTINUE;
}
#Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
return FileVisitResult.CONTINUE;
}
#Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (attrs.isRegularFile()
&& !attrs.isDirectory()
&& !attrs.isSymbolicLink()
&& isTextFile(file)) {
searchResponseList.add(searchFileContent(file, searchRequest));
}
return FileVisitResult.CONTINUE;
}
#Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
return FileVisitResult.CONTINUE;
}
}
The test case:
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.net.URISyntaxException;
import java.nio.file.Paths;
import java.util.List;
class FileSearchServiceTest {
private SearchService searchService = new FileSearchService();
#Test
void test_search_window_root_c_path() {
SearchRequest sr = SearchRequest.of("localhost", "c:\\", "a");
final List<SearchResponse> searchResult = searchService.search(sr);
Assertions.assertNotNull(searchResult.size());
}
}
I want to use WebFlux to receive the results one by one without waiting for all the files to be scanned.
Consider (1) performing the search in a separate thread, (2) using the observer pattern for receiving intermediate results back to the thread created code (references below), and (3) join the search thread to the implementing code so you can return the result list once it is finished. It means you need to pass 'this' to the thread so it has a reference to call back to a separate method for intermediate results. The references below contain sample code.
Think of this like a GUI. You can run the GUI in a separate thread, and for every button click it calls back to the controller code (including a 'finish' once Exit is clicked).
Re: https://dzone.com/articles/the-observer-pattern-using-modern-java
https://en.wikipedia.org/wiki/Observer_pattern

Obtain Spring actuator 'health' metrics

How to obtain Spring actuator /health metrics that already working?
For example, to push them to Grafana. Thus I need them as objects, not text.
You can inject properties which are exposed through actuator endpoints (like /health) by injecting instance of endpoint class using actuator API.
This stackoverflow answer explains about it in detail:
Does Spring Boot Actuator have a Java API?
Take a look on this example
package ru.formatko;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.prometheus.client.Collector;
import io.prometheus.client.exporter.common.TextFormat;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Data;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.Status;
import org.springframework.stereotype.Component;
/**
* Example:
* # HELP health_status HealthCheck result in prometheus's response format
* # TYPE health_status gauge
* health_status{application="java-service",type="main",} 1.0
* health_status{application="java-service",type="db",database="PostgreSQL",validationQuery="isValid()",} 1.0
* health_status{application="java-service",type="diskSpace",total="506332180480",exists="true",threshold="10485760",free="412188921856",} 1.0
* health_status{application="java-service",type="ping",} 1.0
*/
#Component
#Endpoint(id = "health-check")
public class HeathPrometheusEndpoint {
private static final String APPLICATION = "application";
private static final String TYPE = "type";
public static final String SAMPLE_HEALTH_STATUS = "health_status";
private final HealthEndpoint healthEndpoint;
private final String appName;
private final ObjectMapper mapper;
private final HttpCodeStatusMapper httpCodeStatusMapper;
public HeathPrometheusEndpoint(HealthEndpoint healthEndpoint,
ObjectMapper mapper,
#Value("${spring.application.name:}") String appName,
HttpCodeStatusMapper httpCodeStatusMapper) {
this.healthEndpoint = healthEndpoint;
this.mapper = mapper;
this.appName = appName;
this.httpCodeStatusMapper = httpCodeStatusMapper;
}
#ReadOperation(produces = TextFormat.CONTENT_TYPE_004)
public WebEndpointResponse<String> healthPrometheus() {
StatusDto status = createStatusDto();
List<Collector.MetricFamilySamples.Sample> samples = new ArrayList<>();
samples.add(createMainSample(status));
samples.addAll(createComponentSamples(status));
return createStringWebEndpointResponse(status, createMetricFamily(samples));
}
#SneakyThrows
private StatusDto createStatusDto() {
return mapper.readValue(mapper.writeValueAsString(healthEndpoint.health()), StatusDto.class);
}
private Collector.MetricFamilySamples.Sample createMainSample(StatusDto status) {
Labels labels = new Labels();
labels.add(APPLICATION, appName);
labels.add(TYPE, "main");
return createSample(SAMPLE_HEALTH_STATUS, labels, status.getStatus());
}
private List<Collector.MetricFamilySamples.Sample> createComponentSamples(StatusDto status) {
List<Collector.MetricFamilySamples.Sample> list = new ArrayList<>();
for (Map.Entry<String, StatusDto> entry : status.components.entrySet()) {
Labels labels = new Labels();
labels.add(APPLICATION, appName);
labels.add(TYPE, entry.getKey());
StatusDto statusDto = entry.getValue();
Map<String, Object> details = statusDto.getDetails();
if (details != null && !details.isEmpty()) {
details.forEach((k, v) -> labels.add(k, String.valueOf(v)));
}
list.add(createSample(SAMPLE_HEALTH_STATUS, labels, statusDto.getStatus()));
}
return list;
}
private Collector.MetricFamilySamples.Sample createSample(String name, Labels labels, Status status) {
double v = Status.UP.equals(status) ? 1 : 0;
return new Collector.MetricFamilySamples.Sample(name, labels.getLabels(), labels.getValues(), v);
}
private Collector.MetricFamilySamples createMetricFamily(List<Collector.MetricFamilySamples.Sample> s) {
return new Collector.MetricFamilySamples(
"health_status", Collector.Type.GAUGE,
"HealthCheck result in prometheus's response format", s);
}
private WebEndpointResponse<String> createStringWebEndpointResponse(
StatusDto status, Collector.MetricFamilySamples metricFamilySamples
) {
try {
Writer writer = new StringWriter();
TextFormat.write004(writer,
Collections.enumeration(Collections.singletonList(metricFamilySamples)));
return wrapResponse(writer.toString(), status);
} catch (IOException ex) {
// This actually never happens since StringWriter::write() doesn't throw any
// IOException
throw new RuntimeException("Writing metrics failed", ex);
}
}
private WebEndpointResponse<String> wrapResponse(String body, StatusDto status) {
if (body == null || body.isEmpty()) {
return new WebEndpointResponse<>("", 500);
} else {
int statusCode = httpCodeStatusMapper.getStatusCode(status.getStatus());
return new WebEndpointResponse<>(body, statusCode);
}
}
public static class Labels {
private final Map<String, String> map = new HashMap<>();
public void add(String label, String value) {
if (value != null && !value.isEmpty()) {
map.put(label, value);
}
}
public List<String> getLabels() {
return new ArrayList<>(map.keySet());
}
public List<String> getValues() {
return new ArrayList<>(map.values());
}
}
#Data
public static class StatusDto {
private Status status;
private Map<String, StatusDto> components;
private Map<String, Object> details;
}
}

Internal Server Error when trying to send pojo class object in response on rest using jersey

I have a ResponseModel class, I want to send it's object in response to rest. But I am getting this error: A message body writer for Java class com.tcs.srl.message.model.ResponseModel, and Java type class com.tcs.srl.message.model.ResponseModel, and MIME media type application/json was not found
package com.tcs.DataShare.WebService;
import java.io.InputStream;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.ResponseBuilder;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.tcs.DataShare.Client.DataShareServiceClient;
import com.tcs.DataShare.Client.GetFilesOnLocal;
import com.tcs.DataShare.Constants.DataShareConstants;
import com.tcs.srl.message.model.ResponseModel;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
#Path("/ConfigureAndrew")
public class ConfigureAndrew {
private static final Logger LOGGER = LoggerFactory
.getLogger(ConfigureAndrew.class);
private ClientResponse response;
#Path("/configureAndrew")
#POST
//#Produces("application/octet-stream")
// #Produces("application/xml")
#Produces(MediaType.APPLICATION_JSON)
public Response configureAndrew(#Context HttpServletRequest requestObj,
String serviceData) {
System.out.println("methods");
LOGGER.debug("Inside Configure Andrew web service: ");
JSONParser parser = new JSONParser();
ResponseModel result = new ResponseModel();
// JSONObject result=null;
String isSuccess = "false";
try {
String headerString = requestObj.getHeader("header");
JSONObject serviceJSON = (JSONObject) parser.parse(serviceData);
String datashareURL = (String) serviceJSON
.get(DataShareConstants.DATASHARE_URL);
String datasharePort = (String) serviceJSON
.get(DataShareConstants.DATASHARE_PORT);
System.out.println("datasharePort: " + datasharePort);
String servicename = (String) serviceJSON
.get(DataShareConstants.SERVICE_NAME);
String appname = (String) serviceJSON
.get(DataShareConstants.APP_NAME);
String version = (String) serviceJSON
.get(DataShareConstants.APP_VERSION);
String path = (String) serviceJSON.get(DataShareConstants.APP_PATH);
DataShareServiceClient dataShareServiceClient = new DataShareServiceClient(
datashareURL, datasharePort, servicename);
InputStream inputStream = dataShareServiceClient
.zipFolderWithSubsequestFolder(path);
if (inputStream.available() != 0) {
System.out.println("In the input stream");
GetFilesOnLocal getFilesOnLocal = new GetFilesOnLocal();
result = getFilesOnLocal.getFiles(inputStream, result);
LOGGER.debug("Input stream contains data");
} else {
LOGGER.error("Input stream is empty");
System.out.println("error in input stream");
}
LOGGER.debug("DatashareServices :completed configure Andrew");
isSuccess = "true";
result.setSuccess("true");
result.setErrors(null);
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("DatashareServices :configureAndrew service failed "
+ e);
isSuccess = "false";
result.getErrors().add("Error in configureAndrew method");
}
return Response.status(200).entity(result).build();
}
}
Response Model POJO class::
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class ResponseModel implements Serializable{
String success;
List errors;
public String getSuccess() {
return success;
}
public void setSuccess(String success) {
this.success = success;
}
public List getErrors() {
return errors;
}
public void setErrors(List errors) {
this.errors = errors;
}
public List getWarnings() {
return warnings;
}
public void setWarnings(List warnings) {
this.warnings = warnings;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
List warnings;
Object data;
String transactionId;
public String getTransactionId() {
return transactionId;
}
public void setTransactionId(String transactionId) {
this.transactionId = transactionId;
}

Categories