Soap request object comes as null - spring ws - soap

While trying from soap UI, Iam getting null values in request object even when I send values in req object. what would be the reason? Please help
Below is my endpoint
#Endpoint
public class SummaryEndPoint {
#PayloadRoot(namespace = "http://online.mysite.no", localPart ="getSummary")
public #ResponsePayload JAXBElement<EInvoicesObjects> getSummary(#RequestPayload SummaryObject summaryObject) {
//Here summaryObject.getzDocId() returns null.
return null;
}
}
Soap request:
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:onl="http://online.mysite.no">
<soapenv:Header/>
<soapenv:Body>
<onl:getSummary soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<in0 xsi:type="onl:SummaryObject">
<ZDocId xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">asdasdsa</ZDocId>
<amountDue xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">sadadsadsa</amountDue>
</in0>
</onl:getSummary>
</soapenv:Body>
</soapenv:Envelope>
Request Payload Object:
package com.nets.online2adapter.endpoints;
import javax.xml.bind.annotation.*;
import org.xmlsoap.schemas.soap.encoding.String;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "SummaryObject", propOrder = {
"zDocId",
"amountDue"
},namespace="http://online.mysite.no")
public class SummaryObject {
#XmlElement(name = "ZDocId", required = true, nillable = true)
protected String zDocId;
#XmlElement(required = true, nillable = true)
protected String amountDue;
/**
* Gets the value of the zDocId property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getZDocId() {
return zDocId;
}
/**
* Sets the value of the zDocId property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setZDocId(String value) {
this.zDocId = value;
}
/**
* Gets the value of the amountDue property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getAmountDue() {
return amountDue;
}
/**
* Sets the value of the amountDue property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setAmountDue(String value) {
this.amountDue = value;
}
}

I had the same issue. After deep analysing my code I found that it's because namespaces. (See the example at https://www.baeldung.com/spring-boot-soap-web-service).
In your example, the parameters <ZDocID> and <amountDue> should have been included in their parent's namespace, as like: <onl:ZDocId> and <onl:amountDue>.

Your request class should have 'request' in the name.
Please rename your request class and try with 'SummaryObjectRequest'.

Related

Spring Webflux : Help in converting code to non-blocking

In a Spring Webflux application, I have the below "AuthenticationManager" implementation. On a high level, below is what it is trying to do -
Certain url paths are configured in prop file as requiring basic auth while others require bearer token.
For basic auth, BcryptPasswordEncoder.matches method is used to compare incoming password with encoded one stored in prop file.
For bearer token auth, io.jsonwebtoken library is being used to validate token.
#Component
public class AuthenticationManager implements ReactiveAuthenticationManager {
/** The jwt util. */
#Autowired
private JWTUtil jwtUtil;
#Override
public Mono<Authentication> authenticate(Authentication authobj) {
return Mono.just(authobj).flatMap(authentication -> {
String credentials = authentication.getCredentials().toString();
String path = authentication.getPrincipal().toString();
BasicAuthPath basicAuthPath = jwtUtil.isBasicAuthUrl(path);
if (jwtUtil.isBasicScheme(credentials) || jwtUtil.isBearerScheme(credentials)) {
if (jwtUtil.isBasicScheme(credentials) && basicAuthPath.isState()) {
credentials = credentials.substring(5);
if (jwtUtil.isBasicAuthSuccess(credentials.trim(), basicAuthPath.getPath())) {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(credentials, null, null);
return Mono.just(auth);
} else {
return Mono.empty();
}
} else if (jwtUtil.isBearerTokenAuthUrl(path) && jwtUtil.isBearerScheme(credentials)) {
credentials = credentials.substring(7);
String username;
try {
username = jwtUtil.getUsernameFromToken(credentials);
} catch (Exception e) {
username = null;
}
if (jwtUtil.validateToken(credentials)) {
Claims claims = jwtUtil.getAllClaimsFromToken(credentials);
List<String> rolesMap = claims.get("role", List.class);
List<Role> roles = new ArrayList<>();
for (String rolemap : rolesMap) {
roles.add(Role.valueOf(rolemap));
}
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null,
roles.stream().map(authority -> new SimpleGrantedAuthority(authority.name())).collect(Collectors.toList()));
return Mono.just(auth);
} else {
return Mono.empty();
}
} else {
return Mono.empty();
}
}
return Mono.empty();
});
}
}
JwtUtil Code below
#Component
#Slf4j
public class JWTUtil implements Serializable {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
/** The env. */
#Autowired
private Environment env;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private JwtParser jwtParser;
#Autowired
private Key signingKey;
private static final PathPatternParser DEFAULT_PATTERN_PARSER = new PathPatternParser();
/**
* Checks if is basic auth success.
*
* #param inputCredentials the input credentials
* #param path the path
* #return true, if is basic auth success
*/
public boolean isBasicAuthSuccess(String inputCredentials, String path) {
String pathtoprop = pathtoprop(path);
String configpwd = env.getProperty(pathtoprop);
if (StringUtils.isBlank(configpwd)) {
configpwd = env.getProperty(pathtoprop.substring(0, pathtoprop.lastIndexOf(".")));
}
return passwordEncoder.matches(new String(Base64.getDecoder().decode(inputCredentials)), configpwd);
//return true;
}
/**
* Checks if is basic auth url.
*
* #param path the path
* #return true, if is basic auth url
*/
public BasicAuthPath isBasicAuthUrl(String path) {
BasicAuthPath basicAuthPath = new BasicAuthPath();
if (StringUtils.isNotBlank(env.getProperty("gateway.basicauth.urls"))) {
return pathMatcherBasic(StringUtils.split(env.getProperty("gateway.basicauth.urls"), ","), path);
}
basicAuthPath.setState(false);
return basicAuthPath;
}
/**
* pathMatcher
*
* #param pathPatterns
* #param path
* #return
*/
private BasicAuthPath pathMatcherBasic(String[] pathPatterns, String path) {
BasicAuthPath basicAuthPath = new BasicAuthPath();
for (String pathPattern : pathPatterns) {
PathPattern pattern = DEFAULT_PATTERN_PARSER.parse(pathPattern);
if (pattern.matches(PathContainer.parsePath(path))) {
basicAuthPath.setState(true);
basicAuthPath.setPath(pathPattern);
return basicAuthPath;
}
}
basicAuthPath.setState(false);
return basicAuthPath;
}
/**
* Checks if is bearer token auth url.
*
* #param path the path
* #return true, if is bearer token auth url
*/
public boolean isBearerTokenAuthUrl(String path) {
if (StringUtils.isNotBlank(env.getProperty("gateway.bearertoken.urls"))
&& pathMatcher(StringUtils.split(env.getProperty("gateway.bearertoken.urls"), ","), path)) {
return true;
}
return false;
}
/**
* pathMatcher
*
* #param pathPatterns
* #param path
* #return
*/
private boolean pathMatcher(String[] pathPatterns, String path) {
for (String pathPattern : pathPatterns) {
PathPattern pattern = DEFAULT_PATTERN_PARSER.parse(pathPattern);
if (pattern.matches(PathContainer.parsePath(path))) {
return true;
}
}
return false;
}
/**
* isBasicScheme
*
* #param credentials
* #return
*/
public boolean isBasicScheme(String credentials) {
if (StringUtils.isNotBlank(credentials) && credentials.startsWith(RestUriConstants.BASIC_TOKE_PREFIX)) {
return true;
}
return false;
}
/**
* isBearerScheme
*
* #param credentials
* #return
*/
public boolean isBearerScheme(String credentials) {
if (StringUtils.isNotBlank(credentials) && credentials.startsWith(RestUriConstants.BEARER_TOKE_PREFIX)) {
return true;
}
return false;
}
/**
* Gets the all claims from token.
*
* #param token the token
* #return the all claims from token
*/
public Claims getAllClaimsFromToken(String token) {
return jwtParser.parseClaimsJws(token).getBody();
}
/**
* Gets the username from token.
*
* #param token the token
* #return the username from token
*/
public String getUsernameFromToken(String token) {
return getAllClaimsFromToken(token).getSubject();
}
/**
* Gets the expiration date from token.
*
* #param token the token
* #return the expiration date from token
*/
public Date getExpirationDateFromToken(String token) {
return getAllClaimsFromToken(token).getExpiration();
}
/**
* Checks if is token expired.
*
* #param token the token
* #return the boolean
*/
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
/**
* Generate token.
*
* #param user the user
* #return the string
*/
public String generateToken(User user) {
Map<String, Object> claims = new HashMap<>();
claims.put("role", user.getRoles());
return doGenerateToken(claims, user.getUsername());
}
/**
* Do generate token.
*
* #param claims the claims
* #param username the username
* #return the string
*/
private String doGenerateToken(Map<String, Object> claims, String username) {
Long expirationTimeLong = Long.parseLong(env.getProperty("gateway.apollo.token.encryption.exp")); // in second
final Date createdDate = new Date();
final Date expirationDate = new Date(createdDate.getTime() + expirationTimeLong * 1000);
return Jwts.builder().setClaims(claims).setSubject(username).setIssuedAt(createdDate).setExpiration(expirationDate).signWith(signingKey)
.compact();
}
/**
* Validate token.
*
* #param token the token
* #return the boolean
*/
public Boolean validateToken(String token) {
Boolean result = false;
try {
result = !isTokenExpired(token);
} catch (Exception e) {
log.error("Exception in validateToken - {}",token);
}
return result;
}
private String pathtoprop(String path) {
return "gateway.basicauth.credentials" + path.replace('/', '.');
}
}
During PST done with high number of parallel threads, high cpu utilization is being observed. I think it may be because a lot of the above code is blocking. If that is true, could anyone help with providing pointers on how to refactor this code in a reactive way? Should I be using a thread pool like below for better performance -
Mono.just(authobj).publishOn(Schedulers.newParallel("password-encoder", Schedulers.DEFAULT_POOL_SIZE, true);).......
I have something similar, except I am using Jose4J. What you have isn't technically blocking code but the nature of JWT signatures is CPU intensive.
What I do though is publishOn another scheduler so the rest of the gateway can still do the processing of other calls that do not involve signing.
I would also recommend adding a .timeout on the method that is being called by the user so that it will free up the thread soon in case it gets overloaded. You can add Resilience4J TimeLimitter as well, but that may be overkill.

Get the id, not the object in a ManyToOne relationship to send it from an API Rest

I've got an entity I send in a datatable (webix) thanks to an API rest. In this entity I have several ManyToOne relationeships which I don't know how to display or edit.
In my datatable, when I try to display the field which is in a relathinship I've got this instead of the id
[object Object]
When I try to edit this field by adding the id I've got this error message
Type error: Argument 1 passed to ErpBundle\Entity\Trial::setGSponsor() must be an instance of ErpBundle\Entity\Sponsor, string given
I understand what I get in my datatable is an object but I don't know how to get the Id of the name instead (and being able to chose it from the datatable).
Do I have to edit the getter? The repository?
I've tried to edit my getter like that but of course it's not working:
/**
* Get gSponsor
*
* #return \ErpBundle\Entity\Sponsor
*/
public function getGSponsor()
{
return $this->gSponsor->getId();
}
Below a part of my code:
In the controller, I'm just rendering the template for the view. Webix just take the url as an argument to get the data (url: "rest->{{ path('erp_trialapi_cgetload') }}") The datatable is displayed automatically from what I send in my ApiController
Part of my entity
class Trial
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
*
* #ORM\ManyToOne(targetEntity="ErpBundle\Entity\Sponsor")
*/
private $gSponsor;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set gSponsor
*
* #param \ErpBundle\Entity\Sponsor $gSponsor
*
* #return Trial
*/
public function setGSponsor(\ErpBundle\Entity\Sponsor $gSponsor = null)
{
$this->gSponsor->get = $gSponsor;
return $this;
}
/**
* Get gSponsor
*
* #return \ErpBundle\Entity\Sponsor
*/
public function getGSponsor()
{
return $this->gSponsor;
}
My API
class TrialApiController extends FOSRestController
{
/**
* #Rest\Get("/api_t/get")
*/
public function cgetLoadAction()
{
$em = $this->getDoctrine()->getManager();
$repo = $em->getRepository('ErpBundle:Trial');
$trials = $repo->findAll();
return $trials;
}
I'd appreciate any kind of help.
Thank you very much!

Rest Webservices Exception return stack trace

We have rest webservices using spring and cxf. When a checked exception is thrown we get the correct localized exception via soap ui.
But when a unchecked runtime exception is thrown we simply get a stack trace stating the webservice could not loaded.
#XmlType
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement
public class WebServiceExceptionWrapper extends Exception {
/** Type (class name). */
private String type;
/** Message. */
private String message;
/** Stack trace. */
private StackTraceElement[] stackTrace;
/** Cause. */
private Throwable cause;
/** The localized message. */
private String localeErrorCode;
/**
* Construct a wrapper around an exception.
*
* #param e exception
*/
public WebServiceExceptionWrapper(final Exception e) {
this.type = e.getClass().getName();
this.message = StringEscapeUtils.escapeHtml(e.getLocalizedMessage());
this.stackTrace = e.getStackTrace();
this.cause = e.getCause();
if(e instanceof DetailedException) {
this.localeErrorCode = StringEscapeUtils.escapeHtml(((DetailedException) e).getLocaleErrorCode());
}
}
/**
* Gets the type.
*
* #return the type
*/
#XmlElement(nillable = true)
public String getType() {
return this.type;
}
/**
* Sets the type.
*
* #param type the type to set
*/
public void setType(final String type) {
this.type = type;
}
/**
* Gets the message.
*
* #return the message
*/
#XmlElement(nillable = true)
public String getMessage() {
return this.message;
}
/**
* Sets the message.
*
* #param message the message to set
*/
public void setMessage(final String message) {
this.message = message;
}
/**
* Gets the stackTrace.
*
* #return the stackTrace
*/
#XmlElement(nillable = true)
public StackTraceElement[] getStackTrace() {
return this.stackTrace;
}
/**
* Sets the stackTrace.
*
* #param stackTrace the stackTrace to set
*/
public void setStackTrace(final StackTraceElement[] stackTrace) {
this.stackTrace = stackTrace;
}
/**
* Gets the cause.
*
* #return the cause
*/
#XmlElement(nillable = true)
public Throwable getCause() {
return this.cause;
}
/**
* Sets the cause.
*
* #param cause the cause to set
*/
public void setCause(final Throwable cause) {
this.cause = cause;
}
/**
* #return the localeErrorCode
*/
#XmlElement(nillable = true)
public String getLocaleErrorCode() {
return this.localeErrorCode;
}
/**
* #param localeErrorCode the localeErrorCode to set
*/
public void setLocaleErrorCode(final String localeErrorCode) {
this.localeErrorCode = localeErrorCode;
}
How I do prevent a stack trace from being returned. I just want a response code and a reponse message.
The simplest way to handle exception flows in JAX-RS webservices using Apache-CXF is through the javax.ws.rs.ext.ExceptionMapper class marked with an #Provider annotation (or in the Provider section of your jaxrs:server (jaxrs:providers). You can then trap specific exceptions that you want to return specific status codes for instance:
#Provider
public class FooExceptionMapper implements ExceptionMapper<FooException> {
#Override
public Response toResponse(FooException exception) {
... do your work you want to maybe do here
return Response.status(Response.Status.BAD_REQUEST).entity(...whatever...).build();
}
}
Now you create a bean for this mapper (or use annotation driven), tie it to your service method, and then catch your exceptions. In your catch block simply throw this and let the interceptor framework of cxf take over.

spring-form:options tag with enum

I'm having troubles displaying a dropdown list with the proper values. I'm using the <spring-form:select>, <spring-form:options> and <spring-form:option> tags, and I just can't get it to display the correct options. With the following code, I should only be listing "Option 2", "Option 7", and "Option 8".
*Note - I do NOT want to display every possible Enum value, but for some reason Spring seems to want to display them all. It appears to be completely ignoring the list that is provided to the <spring-form:options> tag.
JSP Tags
<spring-form:select path="selectOptions">
<spring-form:option value="" label="*** Select Option ***" />
<spring-form:options path="${availableOptions}" />
</spring-form:select>
Enum
public enum SelectOptions {
// CHECKSTYLE_OFF: LineLength
/**
* Option 1.
*/
OPTION_1(1, "Option 1"),
/**
* Option 2.
*/
OPTION_2(2, "Option 2"),
/**
* Option 3.
*/
OPTION_3(3, "Option 3"),
/**
* Option 4.
*/
OPTION_4(4, "Option 4"),
/**
* Option 5.
*/
OPTION_5(5, "Option 5"),
/**
* Option 6.
*/
OPTION_6(6, "Option 6"),
/**
* Option 7.
*/
OPTION_7(7, "Option 7"),
/**
* Option 8.
*/
OPTION_8(8, "Option 8"),
/**
* Option 9.
*/
OPTION_9(9, "Option 9"),
/**
* Option 10.
*/
OPTION_10(10, "Option 10");
private int id;
private String description;
/**
* Constructor.
*
* #param id the id
* #param description the description
*/
private SelectOptions(final int id, final String description) {
this.id = id;
this.description = description;
}
/**
* Retrieves the {#link SelectOptions} associated with the passed in id.
*
* #param id the id associated with the SelectOptions
* #return the SelectOptions
*/
public static SelectOptions getInstance(final int id) {
for (final SelectOptions selectOptions : SelectOptions.values()) {
if (selectOptions.id == id) {
return selectOptions;
}
}
throw new IllegalArgumentException("SelectOptions could not be determined with id [" + id + "]");
}
/**
* Retrieves the {#link SelectOptions} associated with the passed in description.
*
* #param description the description associated with the SelectOptions
* #return the SelectOptions
*/
public static SelectOptions getInstance(final String description) {
for (final SelectOptions selectOptions : SelectOptions.values()) {
if (selectOptions.description == description) {
return selectOptions;
}
}
throw new IllegalArgumentException("SelectOptions could not be determined with description [" + description + "]");
}
/**
* Simple Getter.
*
* #return the id
*/
public int getId() {
return this.id;
}
/**
* Simple Setter.
*
* #param id the id to set
*/
public void setId(final int id) {
this.id = id;
}
/**
* Simple Getter.
*
* #return the description
*/
public String getDescription() {
return this.description;
}
/**
* Simple Setter.
*
* #param description the description to set
*/
public void setDescription(final String description) {
this.description = description;
}
}
Controller
public class SpringController implements SpringControllerInterface {
/**
* /WEB-INF/jsp/myJSP.jsp.
*/
private static final String PAGE = "/WEB-INF/jsp/myJSP.jsp";
/**
* {#inheritDoc}
*/
#Override
public ModelAndView load(final Model model) {
final ModelAndView mav = new ModelAndView(PAGE);
final List<SelectOptions> availableOptions = this.getAvailableOptions();
mav.addObject("availableOptions", availableOptions);
return mav;
}
/**
* {#inheritDoc}
*/
#Override
public ModelAndView save(final Model model) {
final ModelAndView mav = new ModelAndView(PAGE);
// TODO
return mav;
}
/**
* {#inheritDoc}
*/
#Override
public Model getModel() {
return new ModelImpl();
}
/**
* #return a list of available options
*/
public List<SelectOptions> getAvailableOptions() {
final List<SelectOptions> availableOptions = Lists.newArrayList();
availableOptions.add(SelectOptions.OPTION_1);
availableOptions.add(SelectOptions.OPTION_7);
availableOptions.add(SelectOptions.OPTION_8);
return availableOptions;
}
}
Model
public class ModelImpl implements Model {
private SelectOptions selectOptions;
/**
* Simple Getter.
*
* #return the selectOptions
*/
#Override
public SelectOptions getSelectOptions() {
return this.selectOptions;
}
/**
* Simple Setter.
*
* #param selectOptions the selectOptions to set
*/
#Override
public void setSelectOptions(final SelectOptions selectOptions) {
this.selectOptions = selectOptions;
}
}
If you created a spring controller and you want to pass an enum to your jsp page, you can do it like this:
Enum example:
public enum Coin {
HEADS("Heads", "heads"),
TAILS("Tails", "tails");
private final String fullName;
private final String shortName;
private Coin(String fullName, String shortName) {
this.fullName = fullName;
this.shortName = shortName;
}
public String getFullName() {
return fullName;
}
public String getShortName() {
return shortName;
}
}
Passing this enum to your model:
model.addObject("coins", Coin.values());
Using it in your jsp page with form:
<form:select path="selection">
<form:options items="${coins}" itemValue="shortName" itemLabel="fullName" />
</form:select>
It looks like the solution to this problem was that I was using the attribute "path" in the <spring-form:options> tag. It should have been "items", not "path".
the corrected JSP fragment:
<spring-form:select path="selectOptions">
<spring-form:option value="" label="*** Select Option ***" />
<spring-form:options items="${availableOptions}" />
</spring-form:select>
You do not even need to use items attribute if you are using <spring-form:options> tag.
The options tag:
Renders a list of HTML 'option' tags. Sets 'selected' as appropriate based on bound value.
References Spring form tld.
So for your need I guess falling back to JSTL core with <spring-form:option/> will suffice.

JAXB Jackson - How to handle maps?

I have an web application in which I generate POJOs from my domain objects. One of my domain objects contain a map and JAXB generates the following schema:
<xs:element name="persons">
<xs:complexType>
<xs:sequence>
<xs:element name="entry" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="key" minOccurs="0" type="xs:string"/>
<xs:element name="value" minOccurs="0" type="person"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
This is generated from HashMap<String, Person> persons:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "personConfiguration", propOrder = {
"persons",
})
#XmlRootElement(name = "personConfiguration")
public class PersonConfiguration
{
#XmlElement(required = true)
protected PersonConfiguration.Persons persons;
/**
* Gets the value of the persons property.
*
* #return
* possible object is
* {#link PersonConfiguration.Persons }
*
*/
public PersonConfiguration.Persons getPersons() {
return persons;
}
/**
* Sets the value of the persons property.
*
* #param value
* allowed object is
* {#link PersonConfiguration.Persons }
*
*/
public void setPersons(PersonConfiguration.Persons value) {
this.persons = value;
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"entry"
})
public static class Persons
{
protected List<PersonConfiguration.Persons.Entry> entry;
/**
* Gets the value of the entry property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the entry property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getEntry().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {#link PersonConfiguration.Persons.Entry }
*
*
*/
public List<PersonConfiguration.Persons.Entry> getEntry() {
if (entry == null) {
entry = new ArrayList<PersonConfiguration.Persons.Entry>();
}
return this.entry;
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"key",
"value"
})
public static class Entry
{
protected String key;
protected Person value;
/**
* Gets the value of the key property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getKey() {
return key;
}
/**
* Sets the value of the key property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setKey(String value) {
this.key = value;
}
/**
* Gets the value of the value property.
*
* #return
* possible object is
* {#link Person }
*
*/
public Person getValue() {
return value;
}
/**
* Sets the value of the value property.
*
* #param value
* allowed object is
* {#link Person }
*
*/
public void setValue(Person value) {
this.value = value;
}
}
}
}
As one can see JAXB added this extra level of indirection entry -> key, value. Other pieces of the puzzle are Spring MVC, REST call using JSON objects.
Now XML based REST Calls work fine with object schema above but when send the same call with JSON message with the same schema I get a JSONMappingException.
Any ideas of why this might be happening?
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
When using different XML and JSON-binding providers it is difficult to keep the XML and JSON representations consistent. Below is and example of how this can be simplified using MOXy as both your XML and JSON provider with all mapping information provided as JAXB annotations.
Root
Below is a sample domain object in which the persons field would generate the XML schema fragment from your question.
package forum13784163;
import java.util.Map;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Root {
Map<String, Person> persons;
}
Person
Below is a sample of what your Person class might look like. Note how I have mapped the id field to an XML attribute.
package forum13784163;
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class Person {
#XmlAttribute
int id;
String name;
int age;
}
jaxb.properties
To use MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
input.json
Below is what the JSON representation would look like if MOXy is used as your JSON binding provider.
{
"persons" : {
"entry" : [ {
"key" : "Jane",
"value" : {
"id" : 123,
"name" : "Jane",
"age" : 30
}
} ]
}
}
Demo
In the demo code below the JSON is unmarshalled into objects and then those same objects are marshalled to XML. This is done from one JAXBContext that contains one set of metadata.
package forum13784163;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, "application/json");
unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
StreamSource json = new StreamSource("src/forum13784163/input.json");
Root root = unmarshaller.unmarshal(json, Root.class).getValue();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
Output
Below is the resulting XML.
<?xml version="1.0" encoding="UTF-8"?>
<root>
<persons>
<entry>
<key>Jane</key>
<value id="123">
<name>Jane</name>
<age>30</age>
</value>
</entry>
</persons>
</root>
For More Information
http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html