Getting a '404 Not Found' error, when testing serialised JSON-B output - bash

I'm trying to build a simple JSON-B program, which converts a java object to a JSON string. The programme also uses the #JsonbProperty annotation on the pojo, to map an unmatched json property to the java object.
The string is then sent to a JMS queue, for a JMS consumer to pick up. I'm using a bash test script, to print the serialised json output in the console. The test script and .json files have been provided, and i'm just assuming they're correct.
When I run the bash script I'm getting a '404 Not Found' error, with no output in the console. I've attached the java code, the .json file, the test scripts and the error messages.
Any help would be greatly appreciated.
Thanks in advance..
Bash Script
curl -X POST -H "Content-Type: application/json" -d #order.json http://localhost:8080/hsports-catalog-jax/hsports/api/order
Bash Script Terminal Error
[21:27] ~/IdeaProjects/JEE8_Essential_Training/HSports_CatalogProject/jaxrs_module/src/resources Marc's Mac >> ./test.sh
<html><head><title>Error</title></head><body>404 - Not Found</body></html>[21:27] ~/IdeaProjects/JEE8_Essential_Training/HSports_CatalogProject/jaxrs_module/src/resources Marc's Mac >>
JAX-RS Endpoint
#RequestScoped
#Path("/order")
#Produces("application/json")
#Consumes("application/json")
public class OrderEndpoint {
// injects JMS producer from ejb module
#Inject
private JmsService jmsService;
// method for placing an order
// accepts Order object JAX-RS resource
// method will convert it to json
#POST
public void placeOrder(Order order) {
Jsonb jsonb = JsonbBuilder.create(); // json builder object
String json = jsonb.toJson(order); // converts order object to json representation
System.out.println(json);
jmsService.send(json);
}
}
JMS Producer
#ApplicationScoped
public class JmsService {
// injects JMS queue we want to send the message to
#Resource(mappedName = "java:/jms/queue/HsportsQueue") // JNDI name
private Queue hsportsQueue;
#Inject
#JMSConnectionFactory("java:/ConnectionFactory")
private JMSContext context;
// code that sends the message to the consumer
public void send(String message) {
try {
TextMessage textMessage = context.createTextMessage(message); // message object
context.createProducer().send(hsportsQueue, textMessage); // producer object
System.out.println("Message sent to JMS queue"); // console println
} catch (Exception e) {
e.printStackTrace();
}
}
}
JMS Consumer
#MessageDriven(
activationConfig = {
#ActivationConfigProperty(
propertyName = "destination", propertyValue = "/jms/queue/HsportsQueue"),
#ActivationConfigProperty(
propertyName = "destinationType", propertyValue = "javax.jms.Queue")
},
mappedName = "/jms/queue/HsportsQueue")
public class JmsConsumerBean implements javax.jms.MessageListener {
public JmsConsumerBean() {
}
// defines what the consumer does, when msg is received from jms queue
#Override
public void onMessage(Message message) {
System.out.println("Message received, JMS Consumer message-driven bean");
try {
System.out.println(message.getBody(String.class));
} catch (JMSException e) {
e.printStackTrace();
}
}
}
Java Pojo
public class Order {
private Long orderId;
private String storeName;
private Customer customer; // new class created
private List<InventoryItem> items;
public Long getOrderId() {
return orderId;
}
public void setOrderId(Long orderId) {
this.orderId = orderId;
}
public String getStoreName() {
return storeName;
}
public void setStoreName(String storeName) {
this.storeName = storeName;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
public List<InventoryItem> getItems() {
return items;
}
// maps json-inventoryItems to java-item
#JsonbProperty("inventoryItems")
public void setItems(List<InventoryItem> items) {
this.items = items;
}
}
JSON File Content
{
"orderId": 1,
"storeName": "Franklin Park",
"customer": {
"customerId": 1,
"firstName": "Kevin",
"lastName": "Bowersox"
},
"inventoryItems": [
{
"inventoryItemId": 1,
"catalogItemId": 1,
"name": "Sneakers",
"quantity": 4
}
]
}

Ah! Problem solved. I had the wrong module name in the URI...idiot!

Related

ParameterResolutionException when using multiple test method parameter

I have the following test case:
#ParameterizedTest
#JsonFileSource(resources = {"/poc/person.json", "/poc/person2.json"})
void test_jsonfilesource_2(#ConvertWith(DataConverter.class) Person person,
#ConvertWith(DataConverter.class) Person person2){
assertEquals("James", person.getName());
assertEquals("David", person2.getName());
}
This is my DataConverter class:
public class DataConverter extends SimpleArgumentConverter {
private static final ObjectMapper objectMapper = new ObjectMapper();
#Override
protected Object convert(Object source, Class<?> targetType) throws ArgumentConversionException {
if(source == null){
return null;
}
if (targetType == null){
throw new IllegalArgumentException("bla-bla");
}
Object x = null;
try {
x = objectMapper.readValue(source.toString(), targetType);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
return x;
}
}
this is my Person.java:
#Getter
#Setter
public class Person {
private String name;
private int age;
}
File 1:
{
"name": "James",
"age": 10
}
File 2:
{
"name": "David",
"age": 10
}
Files on the right place, I can access them.
I'm using junit 5; net.joshka v5.6.2; org.glassfish 1.1.4.
When I try to execute the test above I get this error:
org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter [poc.Person person2] in method [void DataLoaderPOC.test_jsonfilesource_2(poc.Person, poc.Person)].
I've tried to apply different data converter on the second parameter, but the result is the same.
Any idea?
---UPDATE---
I just figured out that this notation:
#JsonFileSource(resources = {"/poc/person.json", "/poc/person2.json"})
isn't about multiple parameters but run the test with both sources. So it is for looping the test run based on input files. But my question still remains: is it possibile to add multiple parameters from file source?

Getting invalid SpanContext in mongo extension for java OpenTelemetry automatic instrumentation

I am writing an extension of OpenTelemetry to input traceId as comment in mongo query. For this I have put an Advice on the find method in com.mongodb.client.MongoCollection. Inside the Advice when I call spanContext.isValid() it returns false, also spanId and traceId have all zeros in them.
MongoQueryInstrumentation.java
public class MongoQueryInstrumentation implements TypeInstrumentation {
#Override
public ElementMatcher<TypeDescription> typeMatcher() {
return implementsInterface(named("com.mongodb.client.MongoCollection"));
}
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("find").and(takesArgument(0, Bson.class)).and(ElementMatchers.isPublic()),
AdvicesFind.class.getName());
}
#SuppressWarnings("unused")
public static class AdvicesFind {
#Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(#Advice.Return(readOnly = false) FindIterable<?> result) {
SpanContext spanContext = Java8BytecodeBridge.currentSpan().getSpanContext();
System.out.println("traceId:" + spanContext.getTraceId());
System.out.println("VALID :" + spanContext.isValid());
result.comment(spanContext.getTraceId());
}
}
}
MongoInstrumentationModule.java
#AutoService(InstrumentationModule.class)
public final class MongoInstrumentationModule extends InstrumentationModule {
public MongoInstrumentationModule() {
super("mongo-ext", "mongo-4.0");
}
#Override
public int order() {
return 1;
}
#Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
return hasClassesNamed("com.mongodb.internal.async.SingleResultCallback");
}
#Override
public List<TypeInstrumentation> typeInstrumentations() {
return Collections.singletonList(new MongoQueryInstrumentation());
}
}
OUTPUT:
traceId:00000000000000000000000000000000
VALID :false
I am exporting the logs to zipkin, there db.statement is
{"find": "sampleCollection", "filter": {"title": "MongoDB"}, "comment": "00000000000000000000000000000000", "$db": "myDb", "lsid": {"id": {"$binary": {"base64": "nOpqMCwHRWe2h+qmVEgGIQ==", "subType": "04"}}}}
I tried doing similar thing in JDBC by adding a comment containing traceId and spanId and there it worked as expected. There I patched sendQueryString method of NativeProtocolInstrumentation.
There Java8BytecodeBridge.currentSpan().getSpanContext() returned a valid spanId but in mongo it does not.

Handler Goblal Exceptions Spring - add data when sending exception

I have a doubt about how to pass more data to throw an exception, I want to pass more data at the time of launching it, to put that data in the service response ..
I have an exception handler class labeled #ControllerAdvice in spring, but I don't know the best way to pass the data.
This is the code I have
throw new OcspException("Exception OCSP");
public class OcspException extends RuntimeException {
private static final long serialVersionUID = 1L;
public OcspException(String businessMessage) {
super(businessMessage);
}
public OcspException(String businessMessage, Throwable throwable) {
super(businessMessage, throwable);
}
}
#ExceptionHandler(OcspException.class)
public ResponseEntity<Object> exception(OcspException exception,HttpServletRequest request) {
ResponseException response = new ResponseException();
response.setCode("404");
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
I have the idea to do it, but I don't know if it is a good practice ... in the OcspException class to create attributes with their setter and getters, and create the constructor that receives this data, to then extract the data in exception controller
throw new OcspException("Exception OCSP","Hello");
public class OcspException extends RuntimeException {
private static final long serialVersionUID = 1L;
private String m;
public OcspException(String businessMessage) {
super(businessMessage);
}
public OcspException(String businessMessage, Throwable throwable) {
super(businessMessage, throwable);
}
public OcspException(String businessMessage, String message) {
super(businessMessage);
setM(message);
}
public String getM() {
return m;
}
public void setM(String m) {
this.m = m;
}
}
#ExceptionHandler(OcspException.class)
public ResponseEntity<Object> exception(OcspException exception,HttpServletRequest request) {
ResponseException response = new ResponseException();
response.setCode("404");
response.setDetails(exception.getM() );
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
Try making an model called ErrorDetails which will hold a timestamp, message, and details.
It may look like this:
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class ErrorDetails {
private LocalDateTime timeStamp;
private String message;
private String details;
}
Here's a sample of what my custom exceptions usually look like:
#Data
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public class OrderNotFoundException extends RuntimeException {
private final String message;
public OrderNotFoundException(String message) {
super(message);
this.message = message;
}
}
Then for the #ExceptionHandler:
#ExceptionHandler(OrderNotFoundException.class)
public ResponseEntity<ErrorDetails>
orderNotFoundException(OrderNotFoundException ex, WebRequest request) {
ErrorDetails errorDetails = ErrorDetails.builder()
.timeStamp(LocalDateTime.now())
.message(ex.getMessage())
.details(request.getDescription(false))
.build();
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
The error response for an order not found ends up being this:
{
"timeStamp": "2019-10-07T21:31:37.186",
"message": "Order with id 70 was not found.",
"details": "uri=/api/v1/order"
}
This way you can add whatever extra details in the ErrorDetails object. I hope that helps!

How to correctly handle exceptions from the service (spring boot rest)

When building a rest api using spring boot what is the best way to handle exceptions from the service level and pass them to the controller, so the client gets a custom json error message.
{
"message": "some error"
}
Endpoint from controller
#PostMapping("/login")
public String login(#RequestBody #Valid LoginDto loginDto) {
return gson.toJson(userService.login(loginDto.getUsername(), loginDto.getPassword()));
}
Service level code
public LoginResponseDto login(String username, String password) {
try {
//performs some checks
...
return new LoginResponseDto(token.get());
} catch (AuthenticationException e){
LOGGER.info("Log in failed for user {}", username);
}
return new LoginResponseDto("login failed");
}
LoginResponseDto class
String token;
String message;
public LoginResponseDto(String message) {
this.message = message;
}
Currently it is obviously returning the correctly message but not the correct status code, it will show status 200 with the error message in json.
You have some options:
1) Returning a message:
If you want to return a message something like this,
{
"message": "some error"
}
What you can do is:
Option 1: Create a custom POJO class for error message and return the reference to the object of that POJO class.
Something like this:
ErrorMessage.java
package org.example;
public class ErrorMessage {
private String message;
public ErrorMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Request Handler Method in Controller:
#GetMapping("/login{?username, password}")
public ErrorMessage isUserAuthenticated(#RequestParam String username, #RequestParam String password) {
if (username.toLowerCase().contentEquals("root") && password.contentEquals("system")) {
return new ErrorMessage("authenticated");
}
return null;
}
Option 2: Create a Map and insert key-value pairs that you want to have in the message.
Like this:
#GetMapping("/login{?username, password}")
public Map<String, String> isUserAuthenticated(#RequestParam String username, #RequestParam String password) {
Map<String, String> message = new HashMap<>();
if (username.toLowerCase().contentEquals("root") && password.contentEquals("system")) {
message.put("message", "authenticated");
}
return message;
}
2) Returning an error status code (highly recommended by me):
You may use ResponseEntity for this purpose.
#GetMapping("/login{?username, password}")
public ResponseEntity<?> isUserAuthenticated(#RequestParam String username, #RequestParam String password) {
if (username.toLowerCase().contentEquals("root") && password.contentEquals("system")) {
return new ResponseEntity<>(HttpStatus.OK);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}

How to make Spring MVC return CSV as convenient as return JSON

I know Spring MVC could return a model in format of Json easily; however, I have tried quite different approaches to return a model in CSV format (via Jackson), but could not make it successfully.
What should I do?
I enclose my model code, controller code, and gradle.build as following:
Thanks a lot!
Model:
#JsonPropertyOrder({ "staffName", "name" })
public class Greeter
{
String name;
String staffName[];
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String[] getStaffName()
{
return staffName;
}
public void setStaffName(String[] staffName)
{
this.staffName = staffName;
}
}
Controller:
#Controller
public class GreetingController {
#RequestMapping(value = "/greeter/json", method = RequestMethod.GET)
public #ResponseBody
Greeter getGreeterInJSON() {
Greeter greeter = new Greeter();
greeter.setName("default");
greeter.setStaffName(new String[] { "ye", "lichi" });
return greeter;
}
#RequestMapping(value = "/greeter/csv", method = RequestMethod.GET, consumes = "text/csv")
public #ResponseBody
Greeter getGreeterInCSV(HttpServletResponse response) {
Greeter greeter = new Greeter();
greeter.setName("default");
greeter.setStaffName(new String[] { "ye", "lichi" });
CsvMapper mapper = new CsvMapper();
CsvSchema schema = mapper.schemaFor(Greeter.class);
ObjectWriter writer = mapper.writer(schema.withLineSeparator("\n"));
File greeterCSV = new File("greeterCSV.csv");
try {
writer.writeValue(greeterCSV, greeter);
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return greeter;
}
}
build.gradle dependencies:
dependencies {
compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
compile ('org.springframework:spring-context:4.0.0.RELEASE')
compile("org.springframework.boot:spring-boot-starter-web:0.5.0.M6")
compile("org.thymeleaf:thymeleaf-spring3:2.0.17")
// compile "org.codehaus.jackson:jackson-mapper-asl:1.9.13"
compile 'com.fasterxml.jackson.core:jackson-databind:2.3.0'
compile 'com.fasterxml.jackson.core:jackson-core:2.3.0'
compile 'com.fasterxml.jackson.core:jackson-annotations:2.3.0'
compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-csv:2.3.0'
testCompile group: 'junit', name: 'junit', version: '4.+'
testCompile "org.mockito:mockito-all:1.9.5"
}
Edit:
Tomcat error:
HTTP Status 415 -
type Status report
message
description The server refused this request because the request entity is in a format not supported by the requested resource for the requested method.
You want not to consume but produce csv. The way you have it, the service expects the input to be provided in csv format, that's why it complains about 'request entity is in a format not supported'. Key is 'request' here - it expects some input in csv format.
Changing 'consume' to 'produces' should fix your problem.
I was looking for an answer to this question for a while and sadly found none, so I will give and example here.
You need to add a CSV message converter. This #Configuration does this:
#Configuration
public class CsvConfiguration {
#Bean
JacksonToCsvHttpMessageConverter csvMessageConverter() {
CsvMapper mapper = new CsvMapper();
// configure mapper here
mapper.registerModule(new JavaTimeModule());
return new JacksonToCsvHttpMessageConverter(mapper);
}
}
And a converter itself:
Note that this simplified version does not support filters nor views. However, if you study the overridden method writeInternal you should be able to add these features, should you need them. Additionally, empty collections are not supported, but you can use an empty array, if you want to support empty series.
public class JacksonToCsvHttpMessageConverter extends AbstractJackson2HttpMessageConverter {
protected JacksonToCsvHttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, MediaType.parseMediaType("text/csv"));
}
#Override
protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
if (object instanceof MappingJacksonValue) object = ((MappingJacksonValue) object).getValue();
CsvMapper mapper = (CsvMapper) getObjectMapper();
Class<?> klass = getType(object);
CsvSchema schema = mapper.schemaFor(klass).withHeader();
JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
try (CsvGenerator generator = mapper.getFactory().createGenerator(outputMessage.getBody(), encoding)) {
ObjectWriter writer = mapper.writer().with(schema);
writer.writeValue(generator, object);
}
}
private Class<?> getType(Object object) {
if (object instanceof Collection) {
Collection<?> c = (Collection<?>) object;
return c.iterator().next().getClass();
}
if (object.getClass().isArray()) return object.getClass().getComponentType();
return object.getClass();
}
}
This will enable all traditional controller methods to return CSV by the way of Accept header. Accept: application/json will yield JSON while Accept: text/csv will yield CSV. Of course all CsvMapper constraints will apply, so no nested objects etc.
#RestController
public class CsvController {
#GetMapping("/records*")
public CsvRecord[] getRecords() {
return new CsvRecord[] { new CsvRecord(), new CsvRecord() };
}
#GetMapping("/record")
public CsvRecord getRecord() {
return new CsvRecord();
}
#JsonAutoDetect(fieldVisibility = Visibility.PUBLIC_ONLY)
public static class CsvRecord {
public String name = "aa";
}
}
Using this configuration will cause your app to return CSV for Accept: */* requests. This is rarely intended, so you can set the default content type to JSON by adding this configuration:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON);
}
}
you are using GET so you are expecting something in response which is CSV so make sure to use produces = "text/csv" instead of consumes = "text/csv")
here :-
#RequestMapping(value = "/greeter/csv", method = RequestMethod.GET, consumes = "text/csv")
public #ResponseBody
we use consume when we send some

Resources