I'm trying to use Apache Camel to parse XML to POJO and have problem with printing out the actual POJO. Instead I get regular XML as if no convertation not happening. When I pass for example Customer insted of Customers its working okay. Also printing Customers class to sout in bean warks perfectly.
MyRoute
#Autowired
private MyBean mb;
#Override
public void configure() throws Exception {
from("file:{{customer.path}}?noop=true")
.bean(mb)
.to("stream:out");
}
MyBean
#Handler
public Customers whatIsInBody(Customers body) {
return body;
}
POJO classes:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"customer"
})
#XmlRootElement(name = "customers")
public #Data
class Customers {
#XmlElement(required = true, nillable = true)
protected List<Customer> customer;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "customer", propOrder = {
"id",
"name",
"adress",
"countryCode",
"products"
})
public #Data
class Customer {
protected long id;
#XmlElement(required = true)
protected String name;
#XmlElement(required = true)
protected String adress;
#XmlElement(required = true)
protected String countryCode;
#XmlElement(required = true, nillable = true)
protected List<Product> products;
}
Output example:
<customers>
<customer>
<id>12345</id>
<name>str1234</name>
<adress>str1234</adress>
<countryCode>str1234</countryCode>
<products>
<id>12345</id>
<name>str1234</name>
</products>
</customer>
Desired output:
Customers(customer=[Customer(id=12345, name=str1234, adress=str1234, countryCode=str1234, products=[Product(id=12345, name=str1234)]),
When you have camel-jaxb on the classpath, then a POJO class with JAXB annotations is type converted to XML when you convert it to a String type, which is what the stream:out would do.
You can enable the tracer to see what the message contains during routing
http://camel.apache.org/tracer
So if you really want to stream:out the toString of the message body, you would need to transform it manually first, by calling its toString method
.transform(simple("${body.toString()}"))
Related
I would like to ask on how to marshal an object with property order from a nested object.
#XmlRootElement(name = "sample")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(propOrder = {"title", "code"})
public class SampleObject {
#XmlAttribute(name = "title")
private String title;
#XmlAttribute(name = "code")
private String code;
}
I have a wrapperList to set that object:
#XmlRootElement(name = "listWrapper")
#XmlAccessorType(XmlAccessType.FIELD)
public class WrapperObject {
#XmlAnyElement(lax=true)
private List<SampleObject> objectList;
}
And i want to set the list on this object. This object is the one who is being marshalled.
#XmlRootElement(name = "marshaller")
#XmlAccessorType(XmlAccessType.FIELD)
public class MarshallerObject {
#XmlElement(name = "wrapperList")
private WrapperObject objectList;
}
This is the output that i'm aiming for:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<marshaller>
<wrapperList>
<sample title="sampleTitle code"001"/>
</wrapperList>
</marshaller>
</soap:Envelope>
Thanks in advance!
I was asked to create a request that will list customers based on their genders. However the request method has to be POST and I was adviced to use a dto and a mapper to achieve this goal. I'll give some examples to further explain my problem.
My Customer entity is as follows:
#Data
#Entity
#Table(name = "customer", schema = "public")
public class Customer implements Serializable {
#Id
#Column(name = "id_", unique = true)
private Integer id_;
#Column(name = "name_", nullable = false)
private String name_;
#Column(name = "surname", nullable = false)
private String surname;
#Column(name = "phone", nullable = false)
private String phone;
#Column(name = "email", nullable = false)
private String email;
#Column(name = "gender", columnDefinition = "text", nullable = false)
private String gender;
#JsonBackReference
#OneToMany(mappedBy = "customer")
Set<PurchaseOrder> purchaseOrder = new HashSet();
public Customer() {
}
This is an example for customer stream based on my code:
{
"id_": 1,
"name_": "Laura",
"surname": "Blake",
"phone": "95334567865",
"email": "Bulvar 216 PF Changs",
"gender": "W"
}
I am asked to give this stream as input:
{ "gender": "W" }
As an output I am expected to receive a list of customer entities with gender 'W'. So, I have created a CustomerDto class:
#Data
public class CustomerDto {
private String gender;
}
This is the method I'm going to use defined in CustomerRepository:
#Repository
public interface CustomerRepository extends JpaRepository<Customer, Integer> {
List<Customer> findAllByGender(Customer customer);
}
This is what I have on my controller and service, respectively:
#RequestMapping(method= RequestMethod.POST, value="/customers/gender")
public List<Customer> getCustomersByStream(#RequestBody #Valid Customer customer) {
return service.getCustomersByGender(customer);
}
public List<Customer> getCustomersByGender(Customer customer) {
return repo.findAllByGender(customer);
}
I added ModelMapper to my dependencies and I tried several methods both with customer and customerDto inputs. But I failed to successfully list customers by gender. I'd appreciate a code answer with proper explanations so that I can understand what's going on.
EDIT:
This is the answer without using ModelMapper. In case anyone is searching for a solution:
Controller:
#RequestMapping(method= RequestMethod.POST, value="/customers/gender")
public List<Customer> getCustomersByStream(#RequestBody #Valid CustomerDto dto) {
String gender = dto.getGender();
return service.getCustomersByGender(gender);
}
Repository:
#Repository
public interface CustomerRepository extends JpaRepository<Customer, Integer> {
List<Customer> findAllByGender(String gender);
}
Service:
public List<Customer> getCustomersByGender(String gender) {
return repo.findAllByGender(gender);
}
Okay, that's pretty simple.
In Your Controller
//Pass a parameter geneder = someValue and you will get that in the gender variable
#RequestMapping(method= RequestMethod.POST, value="/customers/gender")
public List<Customer> getCustomersByStream(#RequestBody AnotherDTO gender) {
return service.getCustomersByGender(anotherDTO.getGender());
}
DTO's are meant to be used to accept a request body if they have a large data payload or for casting custom responses. As I think your superior would have asked to use a DTO for customizing response. Put only those varibles which you want to be there in the response.
ResponseDTO.java
public class CustomerDto {
private String gender;
private Long id;
private String name;
private String surname;
private String phone;
private String email;
//Standard Setters and getters
//Also, you need to make sure that the variable name in the Entity should
//be exactly same as your Entity. In DTO you just need to put those
//variables which you want to be in response.
}
Your Repo
#Repository
public interface CustomerRepository extends JpaRepository<Customer, Integer> {
List<Customer> findAllByGender(String gender);
}
Your Business layer. Some Java class.
public List<Customer> getCustomersByGender(String gender) {
List<Customer> response = new ArrayList<>();
List<Customer> list = repo.findAllByGender(gender);
//Autowire Object mapper of manually create a new instance.
ObjectMapper mapper = new ObjectMapper();
list.forEach(customer ->{
YourDTO ref = mapper.map(list, YourDTO.class);
response.add(ref);
});
return response;
}
Now, you can simply return the response that you've received from the service layer.
Exception :
javax.xml.bind.UnmarshalException: unexpected element (uri:"http://www.cleartrip.com/hotel/hotel-search-response", local:"hotel-search-response"). Expected elements are (none)
MyTestFile.txt(XML) :
<hotel-search-response xmlns="http://www.cleartrip.com/hotel/hotel-search-response" xmlns:hotel-info="http://www.cleartrip.com/places/hotel-info" xmlns:common="http://www.cleartrip.com/hotel/common">
<search-criteria>
<booking-date>2018-12-12+05:30</booking-date>
<check-in-date>2018-12-14+05:30</check-in-date>
<check-out-date>2018-12-16+05:30</check-out-date>
<number-of-rooms>1</number-of-rooms>
<number-of-nights>2</number-of-nights>
<number-of-room-nights>2</number-of-room-nights>
<city>Bangalore</city>
<country>IN</country>
</search-criteria>
<currency>INR</currency>
JAXB Class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "hotel-search-response", propOrder = {
"searchCriteria",
"additionalCurrency",
"currency",
"baseUrl",
"hotels",
"pgfeeJson",
"pgchargeJson"
})
public class HotelSearchResponse {
#XmlElement(name = "search-criteria", required = false)
protected SearchCriteria searchCriteria;
#XmlElement(name = "additional-currency",required= false)
protected AdditionalCurrency additionalCurrency;
#XmlElement(required = false)
protected String currency;
#XmlElement(name = "base-url", required = false)
protected String baseUrl;
#XmlElement(required = false)
protected Hotels hotels;
#XmlElement(name = "pgfee-json", required = false)
protected String pgfeeJson;
#XmlElement(name = "pgcharge-json", required = false)
protected String pgchargeJson;
}
Java main class:
JAXBContext jc= JAXBContext.newInstance(HotelSearchResponse.class);
javax.xml.bind.Unmarshaller ums = jc.createUnmarshaller();
HotelSearchResponse emp=(HotelSearchResponse) ums.unmarshal(new File("E:/MyTestFile.txt"));
First, you need the have a #XmlRootElement. Second, you need to specify the namespace (since you use namespaces).
#XmlRootElement(name = "hotel-search-response", namespace = "http://www.cleartrip.com/hotel/hotel-search-response")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "hotel-search-response", propOrder = { "currency", "baseUrl",
"pgfeeJson", "pgchargeJson" }, namespace = "http://www.cleartrip.com/hotel/hotel-search-response")
public class HotelSearchResponse
Above, the propOrder is simplified.
I'm trying to figure out how to get the list of elements (all the names in the 'propOrder') from an auto-generated Java file that was created using JAXB. I want something along the lines of:
List String> elements = getXMLElements(ExampleInfo.class);
I can't edit the generated java class because the XSD schema might be changed. I'm trying to get the elements which would be startTime, stopTime, and id.
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "exampleInfo", propOrder = {
"startTime",
"stopTime",
"id",
...
})
public class ExampleInfo
extends TypeInfo
{
#XmlElement(name = "StartTime")
#XmlSchemaType(name = "dateTime")
protected XMLGregorianCalendar startTime;
#XmlElement(name = "StopTime")
#XmlSchemaType(name = "dateTime")
protected XMLGregorianCalendar stopTime;
#XmlElement(name = "id")
...
}
You can do it quite easily with the help of method Class.getAnnotation(Class<A>):
public static List<String> getXMLElements(Class<?> clazz) {
XmlType xmlType = clazz.getAnnotation(XmlType.class);
if (xmlType == null)
return null;
String[] propOrder = xmlType.propOrder();
return Arrays.asList(propOrder);
}
Then use it like this:
List<String> elements = getXMLElements(ExampleInfo.class);
<?xml version='1.0'?>
<info>
<contract>
<symbol>IBM</symbol>
<sectype>STK</sectype>
<exchange>SMART</exchange>
<currency>USD</currency>
</contract>
<order>
<action>SELL</action>
<quantity>100</quantity>
<ordertype>LMT</ordertype>
<imtprice>imtprice</imtprice>
<transmit>false</transmit>
</order>
</info>
I want to use jaxb annotations with existing java classes to create above XML input but i don't know how create nested xml structure based on Java classes
Try this:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(propOrder =
{"contract", "order"}) public class Info
{ #XmlElement(required =
true) private Contract
contract; #XmlElement(required = true) private Order order;
// Getters and setters }
Another class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(propOrder = {"symbol",
"sectype", "exchange",
"currency"}) public class
Contract { #XmlElement(required
= true) private String symbol; #XmlElement(required =
true) private String
sectype; #XmlElement(required =
true) private String
exchange; #XmlElement(required
= true) private String currency; //Getters and
setters}
Create an order class the same way.