I'm trying to achieve these 2 things:
Method getData() to return nicely formed JSON like getById() & getBetween return already.
How to get rid of those ugly brackets (original csv file I'm reading from comes with them) around ID so I could return UUID for Id, rather than a String as I currently do.
Here's the code:
import java.io.InputStreamReader;
import java.time.LocalDate;
import java.util.List;
import java.util.stream.Collectors;
import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.CsvToBeanBuilder;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.foobar.hotovo.Domain.PricePaid;
import com.foobar.hotovo.Exceptions.PriceNotFoundException;
#Service
public class MainService {
private static final String URL = "http://prod.publicdata.landregistry.gov.uk.s3-website-eu-west-1.amazonaws.com/pp-monthly-update-new-version.csv";
private List<PricePaid> getData() {
RestTemplate restTemplate = new RestTemplate();
return restTemplate.execute(URL, HttpMethod.GET, null, clientHttpResponse -> {
InputStreamReader reader = new InputStreamReader(clientHttpResponse.getBody());
CsvToBean<PricePaid> csvToBean = new CsvToBeanBuilder<PricePaid>(reader)
.withType(PricePaid.class)
.withSeparator(',')
.withIgnoreLeadingWhiteSpace(true)
.build();
return csvToBean.stream().collect(Collectors.toList());
});
}
private boolean isBetween(PricePaid pricePaid, LocalDate from, LocalDate to) {
LocalDate date = pricePaid.getDateOfTransfer();
return date.isAfter(from) && date.isBefore(to);
}
public List<PricePaid> getAll() {
return getData();
}
public PricePaid getById(String id) throws PriceNotFoundException {
return getData().stream()
.filter(pricePaid -> pricePaid.getId().equals(id))
.findFirst()
.orElseThrow(() -> new PriceNotFoundException("Item not found!"));
}
public List<PricePaid> getBetween(LocalDate from, LocalDate to) {
return getData().stream()
.filter(pricePaid -> isBetween(pricePaid, from, to))
.collect(Collectors.toList());
}
}
Here's an example of the output from getData():
[{"id":"{9DBAD221-7F8E-6EB3-E053-6B04A8C0F257}","price":96000,"dateOfTransfer":"2002-05-31","postCode":"SA62 3JW","propertyType":"F","oldNew":"Y","duration":"L","paon":"THE COACH HOUSE, 29","saon":"FLAT 5","street":"ENFIELD ROAD","locality":"BROAD HAVEN","city":"HAVERFORDWEST","district":"PEMBROKESHIRE","county":"PEMBROKESHIRE","ppd":"A","recordStatus":"A"},{"id":"{9DBAD221-8683-6EB3-E053-6B04A8C0F257}","price":59500,"dateOfTransfer":"2002-12-05","postCode":"EX31 2BS","propertyType":"F","oldNew":"N","duration":"L","paon":"16","saon":"FLAT 3","street":"STICKLEPATH HILL","locality":"STICKLEPATH","city":"BARNSTAPLE","district":"NORTH DEVON","county":"DEVON","ppd":"A","recordStatus":"A"},{"id":"{9DBAD221-B238-6EB3-E053-6B04A8C0F257}","price":240000,"dateOfTransfer":"2002-02-15","postCode":"GL7 5BL","propertyType":"S","oldNew":"N","duration":"F","paon":"76","saon":"","street":"CHURCH ROAD","locality":"QUENINGTON","city":"CIRENCESTER","district":"COTSWOLD","county":"GLOUCESTERSHIRE","ppd":"A","recordStatus":"A"},{"id":"{9DBAD221-CBFB-6EB3-E053-6B04A8C0F257}","price":292500,"dateOfTransfer":"2002-09-27","postCode":"WR11 8QH","propertyType":"T","oldNew":"N","duration":"F","paon":"BIG BARN","saon":"","street":"","locality":"ULLINGTON","city":"EVESHAM","district":"WYCHAVON","county":"WORCESTERSHIRE","ppd":"A","recordStatus":"A"},{"id":"{9DBAD221-E1D3-6EB3-E053-6B04A8C0F257}","price":145000,"dateOfTransfer":"2002-01-25","postCode":"PE12 8SN","propertyType":"D","oldNew":"N","duration":"F","paon":"SILVERCROFT","saon":"","street":"RAVENS DROVE","locality":"HOLBEACH FEN","city":"SPALDING","district":"SOUTH HOLLAND","county":"LINCOLNSHIRE","ppd":"A","recordStatus":"A"},{"id":"{9DBAD222-5429-6EB3-E053-6B04A8C0F257}","price":79950,"dateOfTransfer":"2002-07-17","postCode":"NE29 6XJ","propertyType":"S","oldNew":"N","duration":"F","paon":"54","saon":"","street":"BLUCHER ROAD","locality":"","city":"NORTH SHIELDS","district":"NORTH TYNESIDE","county":"TYNE AND WEAR","ppd":"A","recordStatus":"A"},{"id":"{9DBAD222-5451-6EB3-E053-6B04A8C0F257}","price":65000,"dateOfTransfer":"2002-11-13","postCode":"NE6 5XY","propertyType":"F","oldNew":"N","duration":"L","paon":"71","saon":"","street":"KING JOHN TERRACE","locality":"","city":"NEWCASTLE UPON TYNE","district":"NEWCASTLE UPON TYNE","county":"TYNE AND WEAR","ppd":"A","recordStatus":"A"},{"id":"{9DBAD222-5F1E-6EB3-E053-6B04A8C0F257}","price":17000,"dateOfTransfer":"2002-07-01","postCode":"SY23 5NJ","propertyType":"D","oldNew":"N","duration":"F","paon":"TYNEWYDD","saon":"","street":"","locality":"BETHANIA","city":"LLANON","district":"CEREDIGION","county":"CEREDIGION","ppd":"A","recordStatus":"A"},{"id":"{9DBAD222-7BB6-6EB3-E053-6B04A8C0F257}","price":33000,"dateOfTransfer":"2002-01-31","postCode":"HD3 4QJ","propertyType":"T","oldNew":"N","duration":"L","paon":"26 - 28","saon":"","street":"SCAR LANE","locality":"","city":"HUDDERSFIELD","district":"KIRKLEES","county":"WEST YORKSHIRE","ppd":"A","recordStatus":"A"},{"id":"{9DBAD221-5212-6EB3-E053-6B04A8C0F257}","price":127000,"dateOfTransfer":"2002-11-28","postCode":"BS6 5QZ","propertyType":"T","oldNew":"N","duration":"F","paon":"200","saon":"","street":"CHELTENHAM ROAD","locality":"","city":"BRISTOL","district":"CITY OF BRISTOL","county":"CITY OF BRISTOL","ppd":"A","recordStatus":"A"},{"id":"{9DBAD221-65C4-6EB3-E053-6B04A8C0F257}","price":105000,"dateOfTransfer":"2002-11-01","postCode":"PE19 7BH","propertyType":"D","oldNew":"Y","duration":"F","paon":"287A","saon":"","street":"GREAT NORTH ROAD","locality":"EATON FORD","city":"ST NEOTS","district":"HUNTINGDONSHIRE","county":"CAMBRIDGESHIRE","ppd":"A","recordStatus":"A"},{"id":"{9DBAD221-79A7-6EB3-E053-6B04A8C0F257}","price":365000,"dateOfTransfer":"2002-05-31","postCode":"LA23 2HB","propertyType":"D","oldNew":"N","duration":"F","paon":"30","saon":"","street":"CRAIG WALK","locality":"","city":"WINDERMERE","district":"SOUTH LAKELAND","county":"CUMBRIA","ppd":"A","recordStatus":"A"},{"id":"{9DBAD221-8270-6EB3-E053-6B04A8C0F257}","price":58000,"dateOfTransfer":"2002-05-13","postCode":"SA62 3JW","propertyType":"F","oldNew":"Y","duration":"L","paon":"THE COACH HOUSE, 29","saon":"FLAT 3","street":"ENFIELD ROAD","locality":"BROAD HAVEN","city":"HAVERFORDWEST","district":"PEMBROKESHIRE","county":"PEMBROKESHIRE","ppd":"A","recordStatus":"A"},{"id":"{9DBAD221-9E52-6EB3-E053-6B04A8C0F257}","price":173000,"dateOfTransfer":"2002-09-16","postCode":"BN27 4BP","propertyType":"D","oldNew":"N","duration":"F","paon":"SUNNYSIDE","saon":"","street":"HACKHURST LANE","locality":"LOWER DICKER","city":"HAILSHAM","district":"WEALDEN","county":"EAST SUSSEX","ppd":"A","recordStatus":"A"},{"id":"{9DBAD221-A6F1-6EB3-E053-6B04A8C0F257}","price":110200,"dateOfTransfer":"2002-02-28","postCode":"RM16 6LW","propertyType":"F","oldNew":"Y","duration":"L","paon":"5","saon":"","street":"SAN MARCOS DRIVE","locality":"CHAFFORD HUNDRED","city":"GRAYS","district":"THURROCK","county":"THURROCK","ppd":"A","recordStatus":"A"},{"id":"{9DBAD221-B31F-6EB3-E053-6B04A8C0F257}","price":475000,"dateOfTransfer":"2002-03-28","postCode":"GL50 4LB","propertyType":"D","oldNew":"Y","duration":"F","paon":"PARK WAY","saon":"","street":"WEST DRIVE","locality":"","city":"CHELTENHAM","district":"CHELTENHAM","county":"GLOUCESTERS
You could use a library like Gson or Jackson to pretty print the json string:
Gson method
Jackson method
I have a spring boot app with a HTTP post request handler. It accepts a payload that I parse and outputs a JSON. I have handled it that it needs to accept a payload of certain parameters(18).
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.google.gson.Gson;
#Validated
#RestController
public class MockController {
#Autowired
MockConfig mockconfig;
private static final Logger LOGGER = LoggerFactory.getLogger(MockController.class);
#RequestMapping(value = "/", method = RequestMethod.GET)
public String index() {
return "hello!";
}
String[] parse;
#PostMapping(value = "/")
public String payloader(#RequestBody String params ) throws IOException{
LOGGER.debug("code is hitting");
parse = params.split("\\|");
String key;
String value;
String dyn;
Map<String, String> predictionFeatureMap = mockconfig.getPredictionFeatureMap();
if(parse.length!=18) {
key = "Not_enough_parameters";
value = predictionFeatureMap.get(key);
Map<?, ?> resultJsonObj = new Gson().fromJson(value, Map.class);
}
else {
key = params;
value = predictionFeatureMap.get(key);
}
return value;
}
}
My config class is where I get the input and output from a file and put them into a hashmap.
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import org.springframework.context.annotation.Configuration;
#Configuration
public class MockConfig {
private Map<String, String> predictionFeatureMap = new HashMap<String, String>();
public Map<String,String> getPredictionFeatureMap() throws IOException {
return predictionFeatureMap;
}
public MockConfig() throws IOException {
init();
}
private Map<String, String> init() throws IOException {
Scanner sc = new Scanner (new File("src/test/resources/Payload1.txt"));
int counter = 1;
String key = "";
while (sc.hasNextLine()) {
if(counter % 2 != 0) {
key = sc.nextLine();
}else {
predictionFeatureMap.put(key, sc.nextLine());
}
counter++;
}
sc.close();
return predictionFeatureMap;
}
}
This is the key and value in the file that I'm trying to work with specifically.
Not_enough_parameters
{"status": false, "errorMessage": "Payload has incorrect amount of parts: expecting: 18, actual:8", "version": "0.97", "coreName": "Patient_Responsibility"}
(The JSON string is the response to too much or too little parameters... the paramter length should be 18.)
Example input:
ncs|56-2629193|1972-03-28|20190218|77067|6208|3209440|self|-123|-123|-123|0.0|0.0|0.0|0.0|0.0|0.0|0.0
This input would pass because it has 18 parameters...
What I want to do is if a user curls for example 5 parameters
ncs|56-2629193|1972-03-28|20190218|77067
I want the value(JSON error message) to have the 'actual' field updated like:
{"status": false, "errorMessage": "Payload has incorrect amount of parts: expecting: 18, actual:5", "version": "0.97", "coreName": "Patient_Responsibility"}
without hardcoding it into the txt file or hashmap...
I have tried getting the index of the string and replacing the '8' character with parse.length() and casting it as a char but it just gives me this output:
{"status": false, "errorMessage": "Payload has incorrect amount of parts: expecting:1 actual:", "version": "0.97", "coreName": "Nulogix_Patient_Responsibility"}
How do I parse or index the JSON to update this value? Or is there a hashmap method to deal with this?
When working with a framework, you usually handle errors using the frameworks way of handling errors.
To handle errors in spring boot you typically use a controller advice that will assist in handling errors. This is created by annotating a class with #ControllerAdvice.
There you can catch thrown exceptions and build responses that will be returned to the calling client.
#PostMapping(value = "/")
public String payloader(#RequestBody String params ) throws IOException{
LOGGER.debug("code is hitting");
parse = params.split("\\|");
String key;
String value;
String dyn;
Map<String, String> predictionFeatureMap = mockconfig.getPredictionFeatureMap();
if(parse.length!=18) {
// Here you throw a custom runtime exception and pass what
// parameters you want that will help you build your error
// message you are passing to your client.
final String errorMessage = String.format(
"Payload has incorrect amount of parts: expecting:%d actual:%d",
predictionFeatureMap.size(),
parse.length);
throw new MyException(errorMessage);
}
else {
key = params;
value = predictionFeatureMap.get(key);
}
return value;
}
Then in a controller advice class
#ControllerAdvice
public class Foo extends ResponseEntityExceptionHandler {
#ExceptionHandler(MyException.class)
public ResponseEntity<MyCustomErrorBody> handleControllerException(HttpServletRequest request, MyException ex) {
// Build your error response here and return it.
// Create a class that will represent the json error body
// and pass it as body and jackson will deserialize it for
// you into json automatically.
final MyCustomErrorBody body = new MyCustomErrorBody(false, ex.getMessage(), "0.97", "Patient_Responsibility")
return ResponseEntity.unprocessableEntity().body(myCustomErrorBody).build();
}
}
public class MyCustomErrorBody {
private boolean status;
private String errorMessage;
private String version;
private String coreName;
// constructor and getters setters ommitted
}
Links about spring boot error handling:
Official spring boot documentation
Baeldung exception-handling-for-rest-with-spring
I am working with small app that uses spring-hibernate but I am a newcomer to field of spring MVC, I have some questions:
1) using single controller for multiple pages is good practice or should I create separate cont. class for each page.
2) I don't want to use form tag of spring I'm using html forms.
My controller is as follows:
package com.servlets.controllers;
import com.utils.generalUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
#Controller
public class signin{
#RequestMapping(value = "/login", method={RequestMethod.POST,RequestMethod.GET})
public ModelAndView loginForm(HttpServletRequest req,HttpServletResponse response){
HashMap<String,String> lsMsg = new HashMap<String,String>();
lsMsg = generalUtils.getInstance().LoginCheck(req);
for (Map.Entry<String, String> entry : lsMsg.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(" key -- "+key+" value -- "+value);
}
if((lsMsg.get("Authorized")).equals("true")){
return new ModelAndView("landing", "message", lsMsg);
}
return new ModelAndView("login", "message", lsMsg);
}
#RequestMapping(value = "/fergot", method={RequestMethod.POST,RequestMethod.GET})
public ModelAndView fergotForm(HttpServletRequest req) { // Not implemented yet
HashMap<String,String> lsMsg = new HashMap<String,String>();
return new ModelAndView("fergot", "message", lsMsg);
}
#RequestMapping("/register")
public ModelAndView registerForm(HttpServletRequest req,HttpServletResponse response) throws IOException{
HashMap<String,String> lsMsgs = new HashMap<String,String>();
lsMsgs.put("Authorized", "false");
lsMsgs = generalUtils.getInstance().addUser(req);
if((lsMsgs.get("Authorized")).equals("true")){
response.sendRedirect("login.html");
}
return new ModelAndView("register", "message", lsMsgs);
}
}
1) Both way are ok. But you want best practise, so my suggestion is create separate controller class for each page . This is common choice in most cases. On the other hand, if your app is very very small, even if you using single controller is also not a big problem.
Chose which way's core problem is which way can maintain easier in the future and which way is more readable for others.
2) You say you don't want to use spring form tag, ofcourse you can. Take it easy, You can not use any part of spring you don't want to use, it is ok.