Spring - ResponseEntity not being used - java

New to Spring, so I want to return either a 400 or a 201 (Created) with a REST endpoint, but right now it only returns an empty 200 response via Postman.
SampleService.java
#Service
public class SampleService {
private SampleRepository sampleRepository;
#Autowired
public SampleService(SampleRepository sampleRepository) {
this.sampleRepository = sampleRepository;
}
public ResponseEntity<Response> registerData(Data data) {
Optional<SampleData> dataOptional = sampleRepository.findDataByName(data.getName());
if(dataOptional.isPresent()) {
// Returns blank 200 response
return new ResponseEntity<>(
new Error(
"Bad Request",
"Data already exists."
),
HttpStatus.BAD_REQUEST
);
}
sampleRepository.save(data);
// Returns blank 200 response
return ResponseEntity.status(HttpStatus.CREATED)
.body(
// I haven't got far to creating a response, so do not mind the "Error".
new Error("Created", "Stand-in until created proper response")
);
}
}
Response.java
public interface Response {}
Error.java
public class Error implements Response {
private String errorType;
private String errorMessage;
public Error(String errorType, String errorMessage) {
this.errorType = errorType;
this.errorMessage = errorMessage;
}
public String getErrorType() {
return errorType;
}
public void setErrorType(String errorType) {
this.errorType = errorType;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}
If anyone needs anything else, let me know, but this should be enough to cover.

As I stated, new to Spring, and forgot to change the return type for my controller layer:
SampleController.java
#PostMapping
public ResponseEntity<Response> createData(#RequestBody Data data) {
return this.sampleService.registerData(data);
}

Related

How to deserialize to map or array class where map structure varies in Java?

I need to deserialize the response of an external REST API which is usually a JSON object but if an error occurred it will be an array of JSON objects. The API is lacking in documentation so the way I determine if error occurred or not is by the HTTP Status of the response. The problem is that non-error responses have different structure per API route (user response, product response etc.). Another problem is in my application we use an abstract class for external API response with such fields as error, hasError. I solved the problem as follows:
public abstract class AbsractApiResponse {
public abstract String getError();
public abstract void setError(String error);
public abstract String getDescription();
public abstract void setDescription(String error);
}
public class ErrorResponse {
private String errorCode; // getters/setters
private String message; // getters/setters
}
public class UserResponse extends AbsractApiResponse {
private String error; // getters/setters
private boolean hasError; // getters/setters
private boolean description; // getters/setters
private String userName;
private String userEmail;
}
public <R extends AbsractApiResponse> R deserializeResponse(
String apiResponse, Class<R> responseType, boolean isHttpError)
throws JsonProcessingException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
R response;
Object deserialized = objectMapper.readValue(apiResponse, Object.class);
if (isHttpError && deserialized instanceof List) {
TypeReference<List<ErrorResponse>> errorResponseType = new TypeReference<>() {};
List<ErrorResponse> responseErrors = objectMapper.convertValue(deserialized,
errorResponseType);
Constructor<R> constructor = responseType.getDeclaredConstructor();
response = constructor.newInstance();
ErrorResponse firstError = responseErrors.get(0);
String errorDescription = responseErrors.stream().map(ErrorResponse::toString).collect(Collectors.joining());
response.setError(firstError.getMessage());
response.setDescription(errorDescription);
} else {
response = objectMapper.convertValue(deserialized, responseType);
}
return response;
}
With this approach I would have to add fields like error/hasError etc. to every class which represents a response which isn't that bad I guess. Another red flag for me is the use of reflection (responseType.getDeclaredConstructor()) and the 4 checked exceptions that go with it. I'm wondering, if there's a better way to solve this?
I do not recommend to merge error response together with business objects. You can return given response class in case of success and throw an exception in case of error. This is what I think would be the cleanest way.
If you do not want to throw an exception you can implement wrapper class which contains response and error objects. In case error field is set we know there was a problem. It could look like below:
interface ApiResponse {
}
#Data
class ResponseWrapper<R extends ApiResponse> {
private R response;
private Error error;
public boolean hasErrors() {
return Objects.nonNull(error);
}
}
#Data
class Error {
private String error;
private String description;
}
#Data
class ErrorResponse {
private String errorCode;
private String message;
}
#Data
class UserResponse implements ApiResponse {
private String userName;
private String userEmail;
}
And generic implementation of that method could look like:
class JsonDecoder {
private final ObjectMapper objectMapper = ...;
public <R extends ApiResponse> ResponseWrapper<R> deserializeResponse(String apiResponse, Class<R> responseType, boolean isHttpError)
throws JsonProcessingException {
ResponseWrapper<R> response = new ResponseWrapper<>();
if (isHttpError) {
response.setError(deserializeError(apiResponse));
} else {
response.setResponse(objectMapper.readValue(apiResponse, responseType));
}
return response;
}
private Error deserializeError(String apiResponse) throws JsonProcessingException {
final TypeReference<List<ErrorResponse>> errorResponseType = new TypeReference<>() {};
List<ErrorResponse> errors = objectMapper.readValue(apiResponse, errorResponseType);
ErrorResponse firstError = errors.get(0);
String errorDescription = errors.stream().map(ErrorResponse::toString).collect(Collectors.joining());
Error error = new Error();
error.setError(firstError.getMessage());
error.setDescription(errorDescription);
return error;
}
}

Parsing retrofit response into custom object containing custom object on Android

I have a problem with parsing my custom response because the I have a response with Localization properties.
I am recieving a response that looks something like this:
[
{
"id": "dummyID1",
"name.en_US": "dummyNameEn1",
"name.fi_FI": "dummyNameFi1"
},
{
"id": "dummyID2",
"name.en_US": "dummyNameEn2",
"name.fi_FI": "dummyNameFi2"
},
{
"id": "dummyID3",
"name.en_US": "dummyNameEn3",
"name.fi_FI": "dummyNameFi3"
}...
]
And to parse that I have created a custom class Device.java:
public class Device {
public String id;
public LocalizedString name;
public Device(String id, LocalizedString name) {
this.id = id;
this.name = name;
}
//Getters and setters
}
Now here we have a custom object named LocalizedString.java:
public class LocalizedString implements Parcelable {
public static final Creator<LocalizedString> CREATOR = new Creator<LocalizedString>() {
#Override
public LocalizedString createFromParcel(Parcel in) {
return new LocalizedString(in);
}
#Override
public LocalizedString[] newArray(int size) {
return new LocalizedString[size];
}
};
private String en_US;
private String fi_FI;
public LocalizedString(String en, String fi) {
this.en_US = en;
this.fi_FI = fi;
}
protected LocalizedString(Parcel in) {
en_US = in.readString();
fi_FI = in.readString();
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(en_US);
dest.writeString(fi_FI);
}
//Getters, setters
}
Now in my response I want to create a list of Device's but it does not seem to understand how the ´LocalizedString´ works. Since my request is returning a <List<Device>> I cannot really customly parse it either.
Here is how I try to parse it:
Call<List<Device>> call = getMainActivity().getRestClient().getDevices();
call.enqueue(new Callback<List<Device>>() {
#Override
public void onResponse(Call<List<Device>> call, Response<List<Device>> response) {
if (isAttached()) {
if (response.isSuccessful()) {
// get data
List<Device> items = response.body();
}
}
}
#Override
public void onFailure(Call<List<Device>> call, Throwable t) {
if (isAttached()) {
Logger.debug(getClass().getName(), "Could not fetch installation document devices past orders", t);
getMainActivity().showError(R.string.error_network);
}
}
});
And:
#GET("document/devices")
Call<List<Device>> gettDevices();
What am I supposed to do in this situation to bind the name to the Device and later be able to either get en_US or fi_FI.
Better you can write it like this
public class Device {
#SerializedName("id")
public String id;
#SerializedName("name.en_US")
public String en;
#SerializedName("name.fi_FI")
public String fi;
public Device(String id, String english, String fi) {
this.id = id;
this.en = english;
this.fi = fi;
}
//Getters and setters
}
If you can control the source of the JSON, then a modification of that JSON structure is easy to solve your problem.
If you can not, the one way we can use to solve your problem is to use Jackson and custom deserializer:
public class DeviceDeserializer extends StdDeserializer<Device> {
public DeviceDeserializer() {
this(null);
}
public DeviceDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Device deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
String id = getStringValue(node, "id");
String en = getStringValue(node, "name.en_EN");
String fi = getStringValue(node, "name.fi_FI");
LocalizedString localized = new LocalizedString(en, fi);
return new Device(id, localizedString);
}
private String getStringValue(JsonNode node, String key) {
// Throws exception or use null is up to you to decide
return Optional.ofNullable(node.get("id"))
.map(JsonNode::asText)
.orElse(null);
}
}
Manually register the deserializer yourself or using the annotation:
#JsonDeserialize(using = DeviceDeserializer.class)
public class Device {
...
Note that you must enable retrofit jackson converter plugin: (see the Retrofit Configuration part)
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(JacksonConverterFactory.create())
.build();
Read this: Get nested JSON object with GSON using retrofit

How to get data from object in JSON response in Fragment

How to get "description" from GET method in retrofit android with Fragment or xml and Retrofit interface and retrofit client ,full code actually i've face some issues.
I'm trying some time but i can't fix this API.
JSON response.
{
"statuscode": 200,
"status": "true",
"cmsDetails": {
"id": 2,
"title": "Privacy Policy",
"description": "<p>We, Devoid Technologies Pvt. Ltd., incorporated."
}
}
Retrofit Interface
#GET("retrieve/policy")
Call<CMSDetails> getDescription();
I want get Description in cmsDetails object.
Create a model for the JSON Response like this :
public class CMS
{
#SerializedName("statuscode")
private String statuscode;
#SerializedName("status")
private String status;
#SerializedName("cmsDetails")
private CmsDetails cmsDetails;
public CmsDetails getCmsDetails()
{
return cmsDetails;
}
}
Now create another model for CmsDetails:
public class CmsDetails
{
#SerializedName("id")
private int id;
#SerializedName("title")
private String title;
#SerializedName("description")
private String description;
public String getDescription()
{
return description;
}
}
Now create your endpoint like this:
public interface EndPoints {
#GET("retrieve/policy")
Call<CMS> getDescriptionCms();
}
Now create the client as follows :
public class RetroFitCMSClient {
private static Retrofit retrofit;
private static OkHttpClient okClient;
private static final String BASE_URL = "http://test.test;
public static Retrofit getRetrofitInstance() {
okClient = new OkHttpClient
.Builder()
.build();
if (retrofit == null) {
retrofit = new retrofit2.Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
Now you can make your call retrofit call and get the description like this :
EndPoints service = RetroFitCMSClient.getRetrofitInstance().create(EndPoints.class);
call = service.getDescriptionCms();
call.enqueue(new Callback<CMS>() {
#Override
public void onResponse(Call<CMS> call, Response<CMS> response) {
if(response.isSuccessful()) {
String description = response.body().getCmsDetails().getDescription();
}
}
#Override
public void onFailure(Call<CMS> call, Throwable throwable) {
}
}

Retrofit POST request using #Body returns empty response body

I am trying to send data to server with end point like this:
https://api.test.com/sales/taken?key=1a2b3c&sales_number=abc&taken[0][id_product]=123&taken[0][qty]=123&taken[1][id_product]=123&taken[1][qty]=123
According to Android Retrofit POST ArrayList, best way to send a list is by using #Body instead of #Field.
So i made a model to suit the end point like this:
public class Model {
#SerializedName("key")
private String key;
#SerializedName("sales_number")
private String sales_number;
#SerializedName("taken")
private List<Taken> taken;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getSales_number() {
return sales_number;
}
public void setSales_number(String sales_number) {
this.sales_number = sales_number;
}
public List<NotaAmbilDetailModel> getTaken() {
return taken;
}
public void setTaken(List<NotaAmbilDetailModel> taken) {
this.taken = taken;
}
}
and
public class Taken {
#SerializedName("id_product")
private int id_product;
#SerializedName("qty")
private int qty;
public int getId_product() {
return id_product;
}
public void setId_product(int id_product) {
this.id_product = id_product;
}
public int getQty() {
return qty;
}
public void setQty(int qty) {
this.qty = qty;
}
}
My post interface looks like this:
#POST("/sales/taken")
Call<ResponseModel> sendData(#Body Model model);
Response status is:
Response{protocol=h2, code=200, message=, url=https://api.test.com/sales/taken}.
As you can see, response code is 200 but when i tried to get the response body, java.lang.NullPointerException occurred.
My error log is:
W/System.err: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String ResponseModel.getCode()' on a null object reference
D/Response: LOG: Response{protocol=h2, code=200, message=, url=https://api.test.com/sales/taken}
Response Body: ResponseModel#bb5e4cf
Your api request expecting query params instead of request body.
Try like this
Request
#POST("/sales/taken")
Call<ResponseModel> sendData(#QueryMap Map<String, String> queryMap);
Params for api
Map<String, String> queryMap = new HashMap<>();
queryMap.put("key", "value_for_key");
queryMap.put("sales_number", "value_for_sales_number");
queryMap.put("taken[0][id_product]", "value_for_taken[0][id_product]");
queryMap.put("taken[0][qty]", "value_for_taken[0][qty]");
queryMap.put("taken[1][id_product]", "value_for_taken[1][id_product]");
queryMap.put("taken[1][qty]", "value_for_taken[1][qty]");
....

How send json object to rest services from angular?

I try to send the json object to rest services but I get some error like this:
http://localhost:8080/api/v1/cardLimit 400 (Bad Request);
Wrap to JSON
public class GameLimit implements Serializable {
private static final long serialVersionUID = 1L;
private LimitType firstLimit;
private LimitType secondLimit;
public LimitType getFirstLimit() {
return firstLimit;
}
public void setFirstLimit(LimitType firstLimit) {
this.firstLimit = firstLimit;
}
public LimitType getSecondLimit() {
return secondLimit;
}
public void setSecondLimit(LimitType secondLimit) {
this.secondLimit = secondLimit;
}
}
public class LimitType implements Serializable{
private static final long serialVersionUID = 1L;
private BigDecimal limit;
private String type;
private String status;
public BigDecimal getLimit() {
return limit;
}
public void setLimit(BigDecimal limit) {
this.limit = limit;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
Limit request object
public class LimitReq extends GameLimit {
private String key;
public String getKey() {
return key;
}
}
RestController:
#RequestMapping(value = "/GameLimit", method = RequestMethod.POST)
public Response setCardLimit(#RequestBody GameLimitReq request) throws Exception {
return limitService.updateGameLimit(request);
}
TypeScript client:
changeLimits(firstLimit: IWidgetLimit, secondLimit: IWidgetLimit, key: string): ng.IPromise<any> {
return this.$http.post(this.apiPrefix + '/GameLimit', {
'firstLimit': {
limit: firstLimit.limit,
type: firstLimit.type,
status: firstLimit.status
},
'secondLimit': {
limit: secondLimit.limit,
type: secondLimit.type,
status: secondLimit.status,
},
key: key
}).then(function (response: any) {
return response.data;
}.bind(this));
}
Seeing this question and answer a 400 error indicates that your json is malformed.
From your code snippets, it seems that the line limitService.updateGameLimit(request); should provide the JSON, yet it is not included in the code snipets. Once you have the output of that method, you can run it through JsonLint to check the syntax. Then repairs can be made from there.
From your typescript client it seems that this is supplying invalid json. While I am not totally versed in typescript this certainly has some malformed JSON even if there are implied quotes. At the very least there should be double quotes around firstLimit, secondLimit, and key.
It's because your json is not being formed properly.
And there are multiple reasons for that
Your Keys are supposed to be strings, and wrapped in quotes. eg: use "type" instead of type.
You have a comma at the end of the line
status: secondLimit.status,
Remove that comma.
After you are done with it, validate a sample output on jsonlint.com or a similar service. It will help you figure out errors.

Categories

Resources