I have problem with REST service I'm trying to make. I use GlassFish 4.1 and Jersay 2.1 which is built-in.
#Path("/driver")
#RequestScoped
public class DriverResource {
private static Logger logger = LogManager.getLogger(DriverResource.class);
#Inject
private DriverManager driverManager;
private SharedResponseFactory responseFactory = new SharedResponseFactory();
#GET
#Path("/login/{pesel}/{password}")
#Produces("application/json")
public Response logIn(#PathParam("pesel") String pesel, #PathParam("password") String password) {
try {
Driver driver = driverManager.logIn(pesel, password);
logger.debug("Zalogowano kierowcę: " + driver.getFullName());
return responseFactory.getSuccesResponse(driver);
} catch (ErrorDAOException e) {
logger.catching(e);
return responseFactory.getFailureResponse(e);
} catch (NoDataFoundDAOException e) {
logger.catching(e);
return responseFactory.getFailureResponse(e);
} catch (Exception e) {
logger.catching(e);
return responseFactory.getFailureResponse(e);
}
}
}
When I'm trying to return my Entity I get response like this:
{}
In my Entities there are many cyclic references and I don't operate on actual implementation but interfaces. I need to make it the way Retrofit in my Android application could deserialize it.
Glassfish's logs are empty, there are no errors related to rest. I have no idea how to make it working.
I tried to use #JsonIdentityInfo to handle cyclic references and #JsonTypeInfo to make interfaces possible to desserialize.
I think there's a small trick that will make it working but unfortunately I don't know it...
I found a relatively easy way to test for cyclic references. If you use JAXB (which is included with glassfish), you can try marshalling your entity to XML. A JAXBException is thrown if any cyclic references are found.
Here is a method to marshall an object to XML:
public static <T> String marshalToXml(T instance) throws javax.xml.bind.JAXBException {
JAXBContext jaxbContext = JAXBContext.newInstance(instance.getClass());
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter writer = new StringWriter();
jaxbMarshaller.marshal(instance, writer);
return writer.toString();
}
and the service can test the object like this:
#Path("/driver")
#RequestScoped
public class DriverResource {
private static Logger logger = LogManager.getLogger(DriverResource.class);
#Inject
private DriverManager driverManager;
private SharedResponseFactory responseFactory = new SharedResponseFactory();
#GET
#Path("/login/{pesel}/{password}")
#Produces("application/json")
public Response logIn(#PathParam("pesel") String pesel, #PathParam("password") String password) {
try {
Driver driver = driverManager.logIn(pesel, password);
marshalToXml(driver); //remember to import static method
logger.debug("Zalogowano kierowcę: " + driver.getFullName());
return responseFactory.getSuccesResponse(driver);
} catch (ErrorDAOException e) {
logger.catching(e);
return responseFactory.getFailureResponse(e);
} catch (NoDataFoundDAOException e) {
logger.catching(e);
return responseFactory.getFailureResponse(e);
} catch (JAXBException e) {
//view error message to see cyclic reference
logger.catching(e);
return responseFactory.getFailureResponse(e);
} catch (Exception e) {
logger.catching(e);
return responseFactory.getFailureResponse(e);
}
}
}
Related
This is the response I get from the API.
{"get":"statistics","parameters":{"country":"romania"},"errors":[],"results":1,"response":[{"continent":"Europe","country":"Romania","population":19016885,"cases":{"new":"+4521","active":156487,"critical":431,"recovered":2606660,"1M_pop":"148707","total":2827936},"deaths":{"new":"+35","1M_pop":"3407","total":64789},"tests":{"1M_pop":"1149381","total":21857638},"day":"2022-03-24","time":"2022-03-24T07:30:04+00:00"}]}
#RestController
public class CovidTrackerRestController {
#GetMapping("/hello")
public String showCovidInformation() {
// connect to a covid database
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://covid-193.p.rapidapi.com/statistics?country=romania"))
.header("X-RapidAPI-Host", "covid-193.p.rapidapi.com")
.header("X-RapidAPI-Key", "mykey")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = null;
try {
response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// get the information
String responseString = response.body();
System.out.println(responseString);
Response romaniaData = null;
try {
romaniaData = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readValue(responseString, Response.class);
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// format the information
System.out.println(romaniaData);
// send the information to html page
return "/tracker";
}
}
And this is my Bean class which is annotated with #Bean in the configurator class alonside the RestTemplate bean. Other properties such as Cases, Deaths etc are configured same as Response class except being declared as #Bean in the configurator because from what I know once I declare a class #Bean then other references contained automatically become beans as well.
#JsonIgnoreProperties(ignoreUnknown = true)
public class Response {
#JsonProperty("country")
private String country;
#JsonProperty("cases")
private Cases cases;
#JsonProperty("deaths")
private Deaths deaths;
#JsonProperty("day")
private String day;
#JsonProperty("time")
private String time;
#JsonProperty("test")
private Tests tests;
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
Your java class needs to be exact representation of received json. Let's call it Wrapper:
public class Wrapper {
#JsonProperty("response")
private List<Response> responses;
public List<Response> getResponses() {
return this.responses;
}
public void setResponses(List<Response> responses) {
this.responses = responses;
}
#Override
public String toString() {
return "Wrapper{" +
"responses=" + responses +
'}';
}
}
I am omiting some properties - get, results, etc. It looks you don't need them. Then deserialization will look like this:
Wrapper data = null;
try {
data = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readValue("json", Wrapper.class);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(data);
Few notes:
If json property name matches field name in class, there is no need for #JsonProperty
For tests field annotation should be - #JsonProperty("tests"). Property is tests, not test
If you really want to throw the rest of the data, and only need response property, then you need to write custom deserializer and work the json tree. You can see how to do it in my answer here, or this guide, for example. Like this you can parse the response json to your class, even if their structures do not match.
Yes, your class should be like this:
public class ResponseWrapper {
public List<Response> response;
public setResponse(List<Response> response) {
this.response= response;
}
public List<Response> getResponse() {
return response;
}
}
And class Response is your class as you published it. Your class have to have the same structure as JSON
I am working on sftp outbound adapter with Spring Integration 4.3 release.
I am able to successfully send the file to sftp location but i want to update the database record to complete status.
I am looking ExpressionEvaluatingRequestHandlerAdvice for an option but not able to figure it out how to call a method from setOnSuccessExpressionString.
Tried with below option.
#Bean
public ExpressionEvaluatingRequestHandlerAdvice afterPut() {
ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice();
StandardEvaluationContext context = new StandardEvaluationContext();
///context.setBeanResolver((BeanResolver) new SftpPutBean());
ExpressionParser parser = new SpelExpressionParser();
try {
context.registerFunction("mymethod", SftpPutBean.class.getDeclaredMethod("mymethod", new Class[] { String.class }));
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
//String abc
String helloWorldReversed = parser.parseExpression("#mymethod(headers['transmissionId'])").getValue(advice, String.class);
advice.setOnSuccessExpressionString("helloWorldReversed");
advice.setPropagateEvaluationFailures(true);
return advice;
}
public void mymethod(String id) {
try {
TransmissionQueue abc = transmissionQueueDataService.findById(Integer.parseInt(id));
abc.setStatus("COMPLETED");
transmissionQueueDataService.saveTransmissionQueue(abc);
} catch (Exception e) {
e.printStackTrace();
}
}
You need to register your function with the Spring Integration evaluation context factory. Instructions here.
To provide a SpEL Function via Java Configuration, you should declare a SpelFunctionFactoryBean bean for each function. The sample above can be configured as follows:
#Bean
public SpelFunctionFactoryBean xpath() {
return new SpelFunctionFactoryBean(XPathUtils.class, "evaluate");
}
I've made a generic method that is convertor class for complex classes and 2nd one for enums. I have Recipe class that is complex so I used #DynamoDBTypeConverted(converter = ObjectConverter.class)
This is my converter class:
public class ObjectConverter<T extends Object> implements DynamoDBTypeConverter<String, T> {
ObjectMapper objectMapper = new ObjectMapper();
#Override
public String convert(T object) {
try {
return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
throw new IllegalArgumentException("Unable to parse JSON");
}
#Override
public T unconvert(String object) {
try {
T unconvertedObject = objectMapper.readValue(object, new TypeReference<T>() {
});
return unconvertedObject;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
This is convertor class for enums:
public class EnumConverter<T extends Object> implements DynamoDBTypeConverter<String, List<T>> {
#Override
public String convert(List<T> objects) {
//Jackson object mapper
ObjectMapper objectMapper = new ObjectMapper();
try {
String objectsString = objectMapper.writeValueAsString(objects);
return objectsString;
} catch (JsonProcessingException e) {
//do something
}
return null;
}
#Override
public List<T> unconvert(String objectsString) {
ObjectMapper objectMapper = new ObjectMapper();
try {
List<T> objects = objectMapper.readValue(objectsString, new TypeReference<List<T>>() {
});
return objects;
} catch (JsonParseException e) {
//do something
} catch (JsonMappingException e) {
//do something
} catch (IOException e) {
//do something
}
return null;
}
The problem is when I try to test CRUDs methods.. I have addProduct method and this one works fine, I created addRecipe method and it looks almost the same, but here I have problem while posting in Postman i got an error: "Bad request, unable to parse JSON".
And information from log file:
"Can not deserialize instance of java.util.ArrayList out of START_OBJECT token at [Source: {"id":null,"name":"test","labels":["GLUTEN_FREE"],"author":{"name":"Plejer Annołn","id":"testID2"},"media":{"name":"heheszki","url":"http://blabla.pl","mediaType":"IMAGE"},"recipeElements":{"product":{"id":927c3ed3-400b-433d-9da0-1aa111dce584,"name":"bąkiKacpraNieŚmierdzą","calories":1000,"fat":400.0,"carbo":20.0,"protein":40.0,"productKinds":["MEAT"],"author":{"name":"Plejer Annołn","id":"testID2"},"media":{"name":"heheszki","url":"http://blabla.pl","mediaType":"IMAGE"},"approved":false},"weight":"100"},"approved":false}; line: 1, column: 190] (through reference chain: pl.javamill.model.kitchen.Recipe["recipeElements"])"
What can be wrong?
The methods in the converter class are always returning a value even if exceptions are thrown (unless they are RuntimeExceptions), though they may not be correctly marshaling/unmarshaling the Product in RecipeElement. A better alternative is to annotate the getRecipeElement() method in your class with #DynamoDBTypeConvertedJson, that provides out-of-the-box JSON marshaling/unmarshaling. It may be something to do with the HTTP request you are sending in Postman too. You should add more information on the getProduct(), setProduct() methods and the actual postman request (without any sensitive information).
I have a Controller like this example:
#RestController
#RequestMapping(value = "/risk", produces = {MediaType.APPLICATION_JSON_VALUE})
public class CalculationController {
#RequestMapping(value = "/calculate", method = RequestMethod.POST)
public CalculationResult calculate(InputFields i) {
try {
return calcService.calculate(i);
} catch (CustomException custEx) {
throw new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR,
null,
null,
getReportLogAsBytes(custEx), //for some reason not working when serialized in Json
Charset.defaultCharset());
}
}
private byte[] getReportLogAsBytes(CustomException e) {
try {
return objectMapper.writeValueAsBytes(e.getReportLog()); //
} catch (JsonProcessingException e1) {
throw new RuntimeException("Unable to serialize Simulation report log to bytes ", e1);
}
}
class CustomException extends Exception {
private List<String> reportLog;
public CustomException(List<String> reportLog) {
super();
this.setReportLog(reportLog);
}
public List<String> getReportLog() {
return reportLog;
}
public void setReportLog(List<String> reportLog) {
this.reportLog = reportLog;
}
}
}
When posting the inputs to the controller and a CustomException occurs, I instantiate HttpServerErrorException using the constructor that accepts a byte array for responseBody. I basically give it a list of String error messages converted to byte array.
The problem is the response body still does not show the list of errors messages I passed to it. I tried looking for examples on using HttpServerErrorException with response body but cannot seem to find any... Any help would be greatly appreciated.
You throw your HttpServerErrorException but don't handle it in the proper way.
Read this: https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
I am creating a basic POST JSON api endoint. I would like to unit test it, and want to make sure I am doing it appropriately in the Play framework. So far I am using Guice for dependency injection and JUnit for my unit testing library.
Here is my controller code:
public class NotificationController extends Controller {
private RabbitQueueService _rabbitQueueService;
#Inject
public NotificationController(RabbitQueueService service) {
_rabbitQueueService = service;
}
#BodyParser.Of(BodyParser.Json.class)
public Result post() {
ObjectMapper mapper = new ObjectMapper();
Notification notification;
try {
JsonNode notificationJsonNode = Controller.request().body().asJson();
notification = mapper.readValue(notificationJsonNode.toString(),
Notification.class);
_rabbitQueueService.push(notification);
return Results.created(notificationJsonNode, "UTF-8");
} catch (JsonParseException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return Results.badRequest();
}
}
My RabbitQueueService code:
public class RabbitQueueService {
private Channel _channel;
private Connection _connection;
public RabbitQueueService() {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(Config.RABBITMQ_HOST);
try {
_connection = factory.newConnection();
_channel = _connection.createChannel();
_channel.queueDeclare(Config.RABBITMQ_QUEUE, false, false, false, null);
_channel.exchangeDeclare(Config.RABBITMQ_EXCHANGE, "fanout");
_channel.queueBind(Config.RABBITMQ_QUEUE, Config.RABBITMQ_EXCHANGE, "");
} catch (IOException e) {
e.printStackTrace();
}
}
public void push(Notification notification) {
try {
_channel.basicPublish(Config.RABBITMQ_EXCHANGE, "", null, notification.getBytes());
_channel.close();
_connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public void pop() {
}
}
My MockQueueService code:
public class MockQueueService extends RabbitQueueService {
#Override
public void push(Notification notification) {
/* Do nothing because you know... thats what I do */
}
#Override
public void pop() {
/* Do nothing because you know... thats what I do */
}
}
and finally my current unit test code:
public class ApplicationTest {
#Test
public void addMessageToQueue() {
running(fakeApplication(), new Runnable() {
public void run() {
FakeRequest request = new FakeRequest("/POST", "/api/v1/notifications");
Notification notification = new Notification(UUID.randomUUID(),
new NotificationType(UUID.randomUUID(),
"Critical"),
"Test notification message");
try {
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(notification);
JsonNode node;
node = mapper.readTree(json);
request.withJsonBody(node);
} catch (IOException e) {
e.printStackTrace();
}
route(request);
}
});
}
}
This all works fine when making a curl request to test my endpoint through play run. My main question is: how do I use the MockQueueService in my unit test? I don't see anyway to do it with fakeApplication() helper. I could instantiate it directly like
NotificationController nc = new NotificationController(new MockQueueService());
nc.post();
but the problem is I need to override the body of the play request with an appropriate request body and I think I need a FakeRequest for that.
Any help, samples, or advice would be helpful.
UPDATE
I have posted a gist example with the necessary example files. The things specifically that I did to get it working:
Setup a new GlobalUnitTest file that I passed into the fakeApplication helper
Changed NotificationController to be a singleton. This allowed me to pull in the NotificationController instance so I could check the QueueService count as part of the assertion.
FakeApplication takes a bunch of arguments that you could use to inject your new service. You could use a combination of any of these:
additionalPlugins
additionalConfiguration
withGlobal
They each let you specify some additional configuration you could use only during testing. Another thing you could do is have a separate Global object just for testing, that is used to create your controllers. The Global object is used to return your controller instance when you use # in your route definition. Then, you can create a separate application.test.conf that refers to GlobalTest that is loaded when you run play test.