I have been doing some work with apache CXF(version 2.2.2) JAX-RS. I am trying to introduce data validation layer in CXF request handler before business method be invoked. Fortunately :), I am encountering an issue on input parameter handling in request handler(DataValidationHandler). I can read the JSON Object manually by following code lines in request handler. But it's duplicated with JSONProvider registered in CXF framework. Because JSON object input stream only can be read once, otherwise we will meet exception "java.io.EOFException: No content to map to Object due to end of input". Moreover, duplicated JSON object deserializing will impacts performance. Following code is sample for your reference.
Read JSON Object from HTTP body manually:
OperationResourceInfo ori = paramMessage.getExchange().get(OperationResourceInfo.class);
MultivaluedMap<String, String> values = new MetadataMap<String, String>();
List<Object> objList = JAXRSUtils.processParameters(ori, values, paramMessage);
Register JSONProvider in CXF JAX-RS framework:
<bean id="JSONProvider" class="com.accela.govxml2.jaxrs.util.JSONProvider"></bean>
Read JSON Object to Java Object from input stream:
public Object readFrom(......){
ObjectMapper objectMapper = new ObjectMapper();
Object result = objectMapper.readValue(entityStream, TypeFactory.defaultInstance().constructType(genericType));
Return result;
}
I am dealing with Path Parameter manually by following code lines.
OperationResourceInfo ori = paramMessage.getExchange().get(OperationResourceInfo.class);
URITemplate t1 = ori.getClassResourceInfo().getURITemplate();
URITemplate t2 = ori.getURITemplate();
UriInfo uriInfo = new UriInfoImpl(paramMessage, null);
MultivaluedMap<String, String> map = new MetadataMap<String, String>();
t1.match(uriInfo.getPath(), map);
String str = map.get(URITemplate.FINAL_MATCH_GROUP).get(0);
t2.match(str, map);
String pathParameter= null;
if (map.containsKey("pathParam") && !ValidationUtil.isEmpty(map.get("pathParam")))
{
pathParameter= map.get("pathParam").get(0);
}
My questions are here:
How to deal with POST/PUT input parameter of http body in request handler in general?
How to avoid performance issue to read input parameter efficiently?
Is there any way to inject the validation (handler/interceptor) layer between parameter reading by CXF(JSONProvider) and business method invoking?
Is there any elegant way to deal with path parameter?
Thanks for your help. Any comments & suggestions will be appreciated.
Regards,
Dylan
I have found another way to inject DataValidation Interceptor into reading parameter phase. We can reuse deserialized input model from message content, which be deserialized by JSONProvider registered in framework. It can improve performance, because only deserialize input model once.
public class DataValidationInInterceptor extends AbstractPhaseInterceptor<Message>
{
public DataValidationInInterceptor()
{
super(Phase.READ);
}
#Override
public void handleMessage(Message message)
{
OperationResourceInfo ori = message.getExchange().get(OperationResourceInfo.class);
Method method = ori.getMethodToInvoke();
Class<?>[] types = method.getParameterTypes();
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (int i = 0; i < types.length; i++)
{
Class<?> type = types[i];
List obj = (List) message.getContent(List.class);
System.out.println(obj);
System.out.println(type);
}
}
}
After researching, I can read the input stream twice based on the following question's answer ( Read stream twice ).
However, JSON object deserializing performance is still my concern. Who has better solution for it?
Intercept request and change message content from CoyoteInputStream to ByteArrayInputStream, so I can read the InputStream twice.
InputStream in = message.getContent(InputStream.class);
if (in != null)
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IOUtils.copy(in, baos);
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
message.setContent(InputStream.class, bais);
}
Reset ByteArrayInputStream before Read JSON Object to Java Object from input stream:
public Object readFrom(......){
ObjectMapper objectMapper = new ObjectMapper();
if (entityStream.markSupported())
{
entityStream.reset();
}
Object result = objectMapper.readValue(entityStream, TypeFactory.defaultInstance().constructType(genericType));
return result;
}
Related
I am trying to read a response from an API endpoint. It works in Postman and appears to work when debugging my java code.
However it will not read any objects that are in the nested array.
I've followed this solution (in the comments someone lays out their solution to accessing objects in a nested array) yet that does not seem to have solved anything as the mapper consistently shows nothing inside the json list.
Here's my code:
try {
response = httpClient.execute(httpPost);
InputStream inputStream = response.getEntity().getContent();
final ObjectMapper mapper = new ObjectMapper();
final JsonNode node = mapper.readTree(inputStream);
final JsonNode value = node.get("value");
final JsonNode index = value.get(1);
final String status = index.get("status").asText();
return status;`
}
Here's the response that I'm trying to unpack. I'm trying to get the value of "status"
{
"value": [
{
"activityRunEnd": "2023-01-23T23:22:01.4234985Z",
},
{
"status": "Succeeded"
}
]
}
This is the response that I'm currently getting when I readTree(inputStream)
{"value":[]}
InputStream does not seem to read any values inside the list
I want to store the request body into the database. I am storing the headers and request into the audit trail table.
For conversion of headers, I am creating a map and then storing the json into the database like this:
String method = httpServletRequest.getMethod();
Enumeration<String> values = httpServletRequest.getHeaderNames();
Map<String,String> headers = new HashMap<String, String>();
while (values.hasMoreElements()) {
String key = values.nextElement();
String innerValue = (String)httpServletRequest.getHeader(key);
headers.put(key,innerValue);
}
// converting object to Array
auditTrail.setHeaders(appUtility.objectToString(headers));
How can I store the request body as json into the database (ignore files for now)? The request body can be one object, array of objects or a combination.
Right now, I have written an ASPECT which will store all the incoming requests into the database. It's easy to store the json of the object when I know the Object but how can we make it generic?
If you know that content is JSON and want to read data from stream you can do it on this way:
String jsonBody = request.getReader().lines()
.collect(Collectors.joining(System.lineSeparator()));
If you need to validate is jsonBody really JSON you can use Jackson:
public static boolean isJSONValid(String jsonInString ) {
try {
final ObjectMapper mapper = new ObjectMapper();
mapper.readTree(jsonInString);
return true;
} catch (IOException e) {
return false;
}
}
If you have a body as the object you can use Jackson.
Example:
SomeObject body = ...; // Your body
ObjectMapper objectMapper = new ObjectMapper();
String bodyAsString = objectMapper.writeValueAsString(body); // To JSON
body = objectMapper.readValue(bodyAsString, SomeObject.class); // From JSON
Jakson with maven:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
I am trying to write a test case where I want to stream json objects from a json file separated by new line into Java.
I want to stream one event object in Java and serialize it.
The json file is of the form:
{"event":[{"D49-64":0,"Bezeichnung":"A 41","D33-48":0}]}
{"event":[{"D49-64":1,"Bezeichnung":"A 41","D33-48":0}]}
Any suggestions to stream the objects in Java will be beneficial.
The blob that you have posted is not a valid JSONObject, but two individual objects.
To stream this, you would end up with something like the following:
String pathToFile = "/path/to/something.txt";
BufferedReader someReader = new BufferedReader( new FileReader( pathToFile ));
String someData;
while (( someData = someReader.readLine() ) != null ) {
JSONObject o = new JSONObject( someData );
doSomethingWith( o );
}
The library I generally use for JSON manipulation is org.json
I was solving the same problem: reading data from file which just has sequence of json objects in it. I am using com.fasterxml.jackson library for json manipulation. While it does not have direct methods for exactly this, the solution is still quite simple:
// InputStream in - input stream with your data
ObjectMapper mapper = new ObjectMapper();
JsonParser parser = mapper.getFactory().createParser(in);
ObjectNode nextObject;
do {
nextObject = mapper.readTree(parser); // returns null when end of stream is reached
// process your object here
} while(nextObject != null);
I've created a custom command to retrieve multiple objects in the same request (in order to solve some performance issues), instead of using the folder method .getMessage(..) which in my case retrieved an ImapMessage object:
Argument args = new Argument();
args.writeString(Integer.toString(start) + ":" + Integer.toString(end));
args.writeString("BODY[]");
FetchResponse fetch;
BODY body;
MimeMessage mm;
ByteArrayInputStream is = null;
Response[] r = protocol.command("FETCH", args);
Response status = r[r.length-1];
if(status.isOK()) {
for (int i = 0; i < r.length - 1; i++) {
...
}
}
Currently I'm validating if the object is a ImapResponse like this:
if (r[i] instanceof IMAPResponse) {
IMAPResponse imr = (IMAPResponse)r[i];
My question is, how can I turn this response into an ImapMessage?
Thank you.
Are you trying to download the entire message content for multiple messages at once? Have you tried using IMAPFolder.FetchProfileItem.MESSAGE? That will cause Folder.fetch to download the entire message content, which you can then access using the Message objects.
I haven't succeeded yet to convert it into a IMAPMessage but I'm now able transform it into a MIME Message. It isn't perfect but I guess it will have to work for now:
FetchResponse fetch = (FetchResponse) r[i];
BODY body = (BODY) fetch.getItem(0);
ByteArrayInputStream is = body.getByteArrayInputStream();
MimeMessage mm = new MimeMessage(session, is);
Then, it can be used to get information like this:
String contentType = mm.getContentType();
Object contentObject = mm.getContent();
There are also other methods to get information like the sender, date, etc.
When I want retrieve the http body of a post in my Request Verifier it kinda resets my entity and I get a nullpointer exception when I want to get the http body in my resource class.
Verifier:
JsonRepresentation jsonrep;
try {
Representation entity = request.getEntity();
jsonrep = new JsonRepresentation(entity);
//bug: entity resets when getJsonObject is being called.
JSONObject jsonobj = jsonrep.getJsonObject();
if(companyId != jsonobj.getInt("id_companies")){
return Verifier.RESULT_INVALID;
}
...
AppResource:
#Post
public Representation addApp(Representation rep) throws Exception{
//rep is null
JsonRepresentation jsonrep = new JsonRepresentation(rep);
When I dont't call:
JSONObject jsonobj = jsonrep.getJsonObject();
it just works fine.
Is anybody facing the same issue or got a solution for it?
Thanks in advance!
In fact, by default, the representation is an InputRepresentation that doesn't store the representation content.
In your case, the simplest way is to wrap the representation into a StringRepresentation within your verifier:
Representation entity = request.getEntity();
StringRepresentation sEntity = new StringRepresentation(entity);
request.setEntity(sEntity);
JsonRepresentation jsonrep = new JsonRepresentation(sEntity);
Then, the string representation will be automatically provided to your server resource method...
Hope it helps you.
Thierry