Creating a reusable Reader class in SpringBoot - java

I'm making a small learning project. In it i'm reading some JSON from a restservice and persisting it in a postgres DB. The restservice has several JSON models that are available from different URL's. I have created a simple reader class that reads the JSON from a url and sends it to the DB. This works with the Model and Service classes hardcoded in the Reader class. I am trying to make it more generic however so i can use the reader class and pass in the model class and service class (that has an add method that calls the save method form the repository for that Model). I tried this with the models implementing an interface and having the interface as parameter in the constructor of the reader class or passing the model classname as a string to the Reader constructor and it being a parameter for a Classloader.
I haven't gotten it to work and probably it is a silly syntax problem but maybe somebody here can help out. Below are the classes:
#Slf4j
#Service
#AllArgsConstructor
public class Reader {
private MapService mapService;
public void readStream(String url) {
try (InputStream is = new URL(url).openStream()) {
JsonReader reader = new JsonReader(new InputStreamReader(is, "UTF-8"));
Gson gson = new GsonBuilder().create();
reader.beginArray();
while (reader.hasNext()) {
MapInputModel map = gson.fromJson(reader, MapInputModel.class);
mapService.add(map);
}
reader.close();
} catch (MalformedURLException e) {
log.error("MalformedURLException" + e.getMessage());
} catch (IOException e) {
log.error("IOException" + e.getMessage());
}
}
}
.
#Service
#RequiredArgsConstructor
#Transactional(propagation = Propagation.REQUIRED)
public class MapService {
private final MapModelMapper mapModelMapper;
private final MapEntityRepository mapEntityRepository;
public void add(final MapInputModel mapInputModel) {
mapEntityRepository.save(mapModelMapper.toEntity(mapInputModel));
}
.
#Getter
#AllArgsConstructor
public class MapInputModel {
private String name;
private List<String> translations;
}
The Mapservice and MapInputModel need to be replaceable with similar classes, like:
#Getter
#AllArgsConstructor
public class IconModel {
Map<String, String> icon;
}
I'm using SpringBoot and Lombok in this project.
-------- edit: updated classmodels ---------
#Getter
#AllArgsConstructor
public class MapInputModel implements InputModel {
private String name;
private List<String> translations;
}
public interface IService<T>{
void add(final T model);
}
#Service
#RequiredArgsConstructor
#Transactional(propagation = Propagation.REQUIRED)
public class MapService implements IService<MapInputModel> {
private final MapModelMapper mapModelMapper;
private final MapEntityRepository mapEntityRepository;
#Override
public void add(final MapInputModel mapInputModel) {
mapEntityRepository.save(mapModelMapper.toEntity(mapInputModel));
}
}
#Slf4j
public class Reader {
private IService<InputModel> service;
private InputModel model;
public Reader(IService<InputModel> service, InputModel model) {
this.service = service;
this.model = model;
}
// code
}

Related

Spring boot rest service duplicated response body

I'm using Spring Boot framework and my response returns duplicated with reversed backward.
I'm asking for advices, how can i solve this situation?
Controller End Point
#GetMapping("/getPDetailsW/{W}")
public PPreviewW getPDetailsPreviewW(#PathVariable String W) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
LocalDateTime now = LocalDateTime.now();
String nullCheckBill = "bos";
PPreviewW response = new PromoPreviewWGsm();
PPreviewInfo pPreviewInfo = listRepository.findPByW(W);
log.info(dtf.format(now) + "|getPDetailsPreviewW|" + W+ "|için istek atıldı.");
response.setDisplayId(pPreviewInfo.getDISPLAY_ID());
response.setAccountCode(pPreviewInfo.getACCOUNT_CODE());
if (pPreviewInfo.W!= "bos") {
nullCheckBill = promoPreviewInfo.W;
}
if (nullCheckBill == "bos") {
response.setNEXTFLAG(Boolean.FALSE);
} else {
response.setNEXTFLAG(Boolean.TRUE);
}
return response;
}
I have #RestController annotation at top of my Controller class.
PPreviewW response class
#Getter
#Setter
public class PPreviewW implements Serializable {
public String DisplayId;
public String AccountCode;
}
I'm using lombok getters and setters
Repository Class
public PPreviewInfo findPByW(String W) {
PPreviewInfo list = jdbcTemplate
.queryForObject(QueryConstants.GET_P_PREVIEW_INFOW, new Object[]{W}, new PPreviewInfoMapper());
return list;
}
#Repository and #Autowired annotations at top of Repository class.
and QueryContants includes my sql which returns correct size for example like XXXX and YYYY
PPreviewInfo Class
#Getter
#Setter
public class PPreviewInfo {
public String CUSTOMER_ID;
public String BILLING_ACCOUNT_CODE;
}
PPreviewInfoMapper Class
public class PPreviewInfoMapper implements RowMapper<PPreviewInfo> {
#Override
public PPreviewInfo mapRow(ResultSet rs, int i) throws SQLException {
PPreviewInfo pbn = new PPreviewInfo();
pbn.setDISPLAY_ID(rs.getString("DISPLAY_ID"));
pbn.setACCOUNT_CODE(rs.getString("ACCOUNT_CODE"));
return pbn;
}
}
response
{"DisplayId":"XXXX","AccountCode":"YYYYYY","NEXTFLAG":true,"nextflag":true,"displayId":"XXXX","accountCode":"YYYYYY"}
The visibility of the attributes might be causing the issue, you can see if changing them to private might help in resolution:
#Getter
#Setter
public class PPreviewW implements Serializable {
private String DisplayId;

How to change JSON key from camelCase to snake case for POJO?

I am doing testing with Wiremock and I have following example for POJO:
#Data
public class DataResponse {
private Supply supply;
private static class Supply {
private List<TimeSeries> timeSeries;
}
#JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public static class TimeSeries {
private LocalDate date;
}
}
And I am stubbing some external API which should response with this POJO as json:
DataResponse dataResponse = DataResponse.builder()
.supply(DataResponse.Supply.builder()
.timeSeries(List.of(
DataResponse.TimeSeries.builder()
.date("2021-11-16")
build()
))
.build()
.build()
String jsonDataResponse = mapper.writeValueAsString(dataResponse); //mapper is instance of ObjectMapper
stubFor(WireMock.get(urlEqualTo("/someUrl"))
.willReturn(aResponse()
.withBody(jsonDataResponse)));
The issue is when I do serialization with ObjectMapper I get "timeSeries" instead of "time_series". Is there any way to get the right JSON string format only with changes in the test file?
timeSeries is field of Supply, you should change place of #JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class), move to Supply class like below
#Data
public class DataResponse {
private Supply supply;
#JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
private static class Supply {
private List<TimeSeries> timeSeries;
}
public static class TimeSeries {
private LocalDate date;
}
}

Automatic deserialization of String to Object with Jackson

Context
Say you have:
public class Dto {
private String name;
private String List<Custom> customs;
// getters and setters...
}
and
public class Custom {
private String something;
private String else;
// getters and setters...
}
Your Spring MVC RestController receives a list of Dto:
#PostMapping
public String create(#RequestBody #Valid List<Dto> dtos) {
return myService.process(features);
}
Input
However, you know that the client-side service which will send data to your controller will send something like this:
[
{
"name": "Bob",
"customs": [
"{\n \"something\": \"yes\",\n \"else\": \"no\"\n }"
]
}
]
Notice how the List<Custom> actually ends up being received as a List<String>. Please assume this cannot be changed on the client-side and we have to deal with it on the server-side.
Question
Is there a Jackson annotation which would automagically take the input String and try to serialize it into a Custom class?
Attempts
A few things that didn't work, including:
#JsonSerialize(using = ToStringSerializer.class)
private List<Custom> customs;
along with
public Custom(String json) {
try {
new ObjectMapper().readerFor(Custom.class).readValue(json);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
As it is, we have had to change the customs type to List<String> and add a utility method which converts a String into a Custom using an ObjectMapper. This is rather dissatisfying.
You need to implement custom deserialiser or converter which would be used to convert given payload to required type. One trick, you could use is to create new ObjectMapper and use it for internal deserialisation.
Example usage:
class CustomConverter extends StdConverter<String, Custom> {
private final ObjectMapper mapper = new ObjectMapper();
#Override
public Custom convert(String value) {
try {
return mapper.readValue(value, Custom.class);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(value);
}
}
}
class Dto {
private String name;
#JsonDeserialize(contentConverter = CustomConverter.class)
private List<Custom> customs;
}
You need to create a custom Deserializer.
public class CustomDeserializer extends StdDeserializer<Custom> {
public CustomDeserializer() {
this(null);
}
public CustomDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Custom deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int id = (Integer) ((IntNode) node.get("id")).numberValue();
String name = node.get("name").asText();
...
return new Custom(id, name, ...);
}
}
and register the deserializer on the Custom class:
#JsonDeserialize(using = CustomDeserializer.class)
public class Custom {
...
}

Deserialize YAML to JAVA [duplicate]

This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 3 years ago.
I have a YAML File with following data and i want to Deserialize that into java Object.
connector:
provider: ABCD
nodes:
- 1.1.2.1
- 1.2.2.8
maxconnection: 1
minconnection: 1
1.First i created a class called Connector where i have defined all the variable like Provider, Nodes, maxconnections, Minconnections.
2.Then i created a class called group and call the Connector class.
3.then in the main function tried to load the yaml file and call it.
But i got Null pointer exception Error
#Connector Class
public static class connector {
private String provider;
private String[] nodes;
private int max_connections;
private int min_connections;
public String getprovider() {
return provider;
}
public void setprovider(String provider) {
this.provider = provider;
}
public String[] getnodes() {
return nodes;
}
public void setnodes( String[] nodes) {
this.nodes = nodes;
}
public int getmax_connections() {
return max_connections;
}
public void setmax_connections(int max_connections) {
this.max_connections = max_connections;
}
public int getmin_connections() {
return min_connections;
}
public void setmin_connections(int min_connections) {
this.min_connections = min_connections;
}
#Override
public String toString() {
return "connector: {provider: " + this.provider + ", nodes: " + this.nodes + ",max_connections: " + this.max_connections +",min_connections: " + this.min_connections +"}";
}
#Group class
public static class Group {
private ArrayList<connector> connector;
public ArrayList<connector> getconnector() {
return connector;
}
public void setconnector(ArrayList<connector> connector) {
this.connector = connector;
}
}
#main Class
public static void main(String[] args) {
final URL resource = YamlConf.class.getResource("demo.yaml");
final Constructor peopleContructor = new Constructor(Group.class);
final TypeDescription peopleDescription = new TypeDescription(connector.class);
peopleDescription.putMapPropertyType("connector", connector.class, Object.class);
peopleContructor.addTypeDescription(peopleDescription);
final Yaml yaml = new Yaml(peopleContructor);
try {
final Group group = (Group) yaml.load(resource.openStream());
for (final connector connector : group.getconnector()) {
System.out.println(connector);
}
} catch (IOException e) {
e.printStackTrace();
}
}
As you are using Jackson, you could use ObjectMapper with the YAMLFactory. Under the hood, it will use the SnakeYAML library. For the required dependencies, refer to the jackson-dataformats-text project documentation.
You could define your classes as follows:
#Getter
#Setter
#NoArgsConstructor
public class Content {
private Connector connector;
}
#Getter
#Setter
#NoArgsConstructor
public class Connector {
private String provider;
private List<String> nodes;
#JsonProperty("maxconnection")
private Integer maxConnections;
#JsonProperty("minconnection")
private Integer minConnections;
}
The #Getter, #Setter and #NoArgsConstructor annotations are from the Lombok project. They will generate getters, setters and the default constructor for your. If you don't want to use Lombok, simply implement those methods.
Then read your YAML file:
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
Content content = mapper.readValue(yamlFile, Content.class);
Depending on your needs, you could consider a Map<String, Object> to hold the values:
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
TypeReference<Map<String, Object>> type = new TypeReference<Map<String, Object>>() {};
Map<String, Object> content = mapper.readValue(yamlFile, type);

org.springframework.core.convert.converter.Converter convert some classes to one(or class+parameter to one class)

I have org.springframework.core.convert.converter.Converter
#Component
public class CatalogConverter implements Converter<ServiceCatalogType, Catalog> {
#Override
public Catalog convert(ServiceCatalogType source) {
Catalog catalog = new Catalog();
//convert
return catalog;
}
}
And I register this converter:
#Configuration
public class ConvertersConfig {
private final CatalogConverter catalogConverter;
#Autowired
public ConvertersConfig(CatalogConverter catalogConverter) {
this.catalogConverter = catalogConverter;
}
#Bean(name="conversionService")
ConversionService conversionService() {
ConversionServiceFactoryBean factoryBean = new ConversionServiceFactoryBean();
HashSet<Converter> converters = new HashSet<>();
converters.add(catalogConverter);
factoryBean.setConverters(converters);
factoryBean.afterPropertiesSet();
return factoryBean.getObject();
}
}
But I need pass some parameter to my custom converter. I have some ways:
Pass it in constructor - but how can register this converter?
Use wrapper
class Wrapper{
private ServiceCatalogType catalog;
private String uuid;
}
and change converter like this:
implements Converter<ServiceCatalogType, Wrapper>
Or maybe Spring has another way?
EDIT
I need next.
in service:
pulic void send() {
ServiceCatalogType cs = getServiceCatalogType();//get from net
User user = getUser();//get from db
//convert cs+user to Catalog(all cs fields+ some user fields to catalog)
Catalog catalog = conversionService.convert(cs, user, Catalog.class);
}
EDIT2
Wrapper implementation:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class CatalogWrapper {
private ServiceCatalogType serviceCatalogType;
private User user;
}
CatalogWrapper wrapper = new CatalogWrapper(getServiceCatalog(), getUser);
catalog = conversionService.convert(wrapper, Catalog.class);

Categories