I have a problem with retrofit and XML, I use SimpleXmlConverterFactory, and I have this error : Caused by: "java.lang.IllegalArgumentException: Could not locate ResponseBody converter for java.util.List.
Tried:
* retrofit2.BuiltInConverters * retrofit2.converter.simplexml.SimpleXmlConverterFactory"
gradle:
compile 'com.google.code.gson:gson:2.8.1'
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.okhttp3:okhttp:3.9.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.8.0'
compile ('com.squareup.retrofit2:converter-simplexml:2.3.0') {
exclude group: 'xpp3', module: 'xpp3'
exclude group: 'stax', module: 'stax-api'
exclude group: 'stax', module: 'stax'
}
service :
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(URL)
.client(buildHttpClient())
.addConverterFactory(SimpleXmlConverterFactory.create())
.build();
private OkHttpClient buildHttpClient() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return (new OkHttpClient.Builder())
.addInterceptor(buildDefaultHeadersInterceptor())
.addNetworkInterceptor(buildLogInterceptor())
.build();
}
model :
#Root(name = "item")
public class Item {
#Element(name = "link")
private String link;
#Element(name = "title")
private String title;
#Element(name = "description")
private String description;
public Item() {}
}
xml :
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
<channel xml:lang="fr">
<title>...</title>
<link>http://www.jouars-pontchartrain.fr/</link>
<description>...</description>
<language>fr</language>
<generator>SPIP - www.spip.net</generator>
<image>...</image>
<item xml:lang="fr">
<title>
title here
</title>
<link>
link here
</link>
<description>
description here
</description>
</item>
<item xml:lang="fr">...</item>
<item xml:lang="fr">...</item>
<item xml:lang="fr">...</item>
<item xml:lang="fr">...</item>
</channel>
</rss>
Faced with the same problem. The solution is to make the internal class static (or just create a new separate java class). Also add the "Root" annotation for the "channel" node.
#Root(name = "rss", strict = false)
public class ArticleResponse {
#Element(name = "channel")
private Channel channel;
#Root(name = "channel", strict = false)
static class Channel {
#ElementList(inline = true, name="item")
private List<Article> articles;
}
}
The same with the "enclosure" and "source" nodes - create new files or create static internal classes.
public class Enclosure {
#Attribute(name = "url")
private String url;
#Attribute(name = "length")
private long length;
#Attribute(name = "type")
private String type;
}
Related
Currently Im new to spring and I tried the example (https://spring.io/guides/gs/rest-service/) they have on their website. I think I followed all steps correctly, but with a httpget in postman I only get this response:
"timestamp": "2023-01-04T09:23:03.965+00:00",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/greeting"
Here is the Code:
Greeting.java:
package com.example.restservice;
public class Greeting {
private final long id;
private final String content;
public Greeting(long id, String content) {
this.id = id;
this.content = content;
}
public long getId() {
return id;
}
public String getContent() {
return content;
}
}
GreetingController.java:
package com.example.restservice;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
#GetMapping("/greeting")
public Greeting greeting(#RequestParam(value = "name", defaultValue = "World") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
and here is the build.gradle:
plugins {
id 'java'
id 'org.springframework.boot' version '3.0.1'
id 'io.spring.dependency-management' version '1.1.0'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-web-services'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.session:spring-session-core'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
}
tasks.named('test') {
useJUnitPlatform()
}
I looked through the linked github and saw, that they have a RestServiceApplication.java, which is never created in the tutorial. After I added that, everything works fine now :)
I have a very simple Spring Boot MVC project in which I try to use the DomainClassConverter to load the Entity directly. But it seems that the DomainClassConverter is not found. I have the following error when accessing to accessing to the 'localhost:8080/one/2' url:
Cannot convert value of type 'java.lang.String' to required type 'com.example.test.data.Customer': no matching editors or conversion strategy found
But The DomainClassConverter should be enabled by Spring Boot and manage the conversion.
I tryed also to enable it explicitely via the #EnableSpringDataWebSupport annotation but it didn't work neither.
Here is my controller code:
#Controller
public class TestController {
#Autowired
private CustomerRepository customerRepository;
#GetMapping("/all")
public void all(Model model) {
Iterable<Customer> customers=customerRepository.findAll();
model.addAttribute("customers",customers);
};
#GetMapping("/one/{customer_id}")
public void one(#PathVariable("customer_id") Customer customer, Model model) {
model.addAttribute("customer",customer);
};
}
The Customer coode:
#Entity
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Getter
private Long id;
#Getter
#Setter
private String firstName;
#Getter
#Setter
private String lastName;
protected Customer() {
}
public Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
The CustomerREpositoy:
public interface CustomerRepository extends PagingAndSortingRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
Customer findById(long id);
}
The application :
#SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class);
}
}
And finally the build.graddle:
plugins {
id 'org.springframework.boot' version '2.3.1.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '14'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'org.postgresql:postgresql'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
// testImplementation 'org.springframework.security:spring-security-test'
}
test {
useJUnitPlatform()
}
Any idea?
you try to get the customer_id (path variable) as a Customer object. hence, get the above error when you try to access localhost:8080/one/2.
change the customer_id (path variable) data type to relevant data type (String, int etc.) as follows,
#GetMapping("/one/{customer_id}")
public void one(#PathVariable("customer_id") String customerId, Model model) {
----
};
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<InstanceTree xmlns="http://www.testsite.org/Schemas/xyzSchema">
<Instance id="inst0" depth="1">
<UserData type="Instance">
<userValue title="occurrenceUUID" value="abc/>
</UserData>
<UserData type="Part" name="pqr">
<UserValue title="PartNumber" value="MTG_238_ZB_LACKIERUNG" />
</UserData>
</Instance>
</InstanceTree>
#XmlRootElement(name = "InstanceTree")
public class InstanceTree {
}
#XmlRootElement(name = "Instance")
public class Instance {
private List<Userdata> userdata;
#XmlElement
public List<Userdata> getUserdata() {
return userdata;
}
}
#XmlRootElement(name = "UserValue")
public class UserValue {
private List<UserValue> userValue;
#XmlElement
public List<UserValue> getUserValue() {
return userValue;
}
}
#XmlRootElement(name = "UserData")
public class Userdata {
}
In xml giving NullPointerException for upper case Instance, UserData, UserValue.
Giving error for xmlns="http://www.testsite.org/Schemas/abcSchema". working fine after removing url.
Use #XmlElement(name="Userdata") otherwise jaxb uses the attribute name.
#XmlRootElement(name = "Instance")
public class Instance {
private List<Userdata> userdata;
#XmlElement(name = "Userdata")
public List<Userdata> getUserdata() {
return userdata;
}
}
use also package-info.java
#javax.xml.bind.annotation.XmlSchema(
namespace = "http://www.testsite.org/Schemas/abcSchema",
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED,
xmlns = {
#javax.xml.bind.annotation.XmlNs(prefix = "", namespaceURI = "http://www.testsite.org/Schemas/abcSchema")
}
)
package com.your.package;
I've a service that returns this XML:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<status>success</status>
<result>
<project>
<id>id1</id>
<owner>owner1</owner>
</project>
<project>
<id>id2</id>
<owner>owner2</owner>
</project>
</result>
or
<?xml version="1.0" encoding="UTF-8"?>
<response>
<status>success</status>
<result>
<user>
<id>id1</id>
<name>name1</name>
</user>
<user>
<id>id2</id>
<name>name2</name>
</user>
</result>
I want to unmarshall the retrieved XML using these classes:
Result:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Response<T> {
#XmlElement
protected String status;
#XmlElementWrapper(name = "result")
#XmlElement
protected List<T> result;
}
Project:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Project {
#XmlElement
public String id;
#XmlElement
public String owner;
}
User:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class User {
#XmlElement
public String id;
#XmlElement
public String name;
}
First not working solution
JAXBContext context = JAXBContext.newInstance(Response.class, Project.class, User.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
StreamSource source = new StreamSource(new File("responseProject.xml"));
Response<Project> responseProject = (Response<Project>)unmarshaller.unmarshal(source);
System.out.println(responseProject.getStatus());
for (Project project:responseProject.getResult()) System.out.println(project);
source = new StreamSource(new File("responseUser.xml"));
Response<User> responseUser = (Response<User>)unmarshaller.unmarshal(source);
System.out.println(responseUser.getStatus());
for (User user:responseUser.getResult()) System.out.println(user);
I get an empty list.
Second not working solution
Inspired by this article http://blog.bdoughan.com/2012/11/creating-generic-list-wrapper-in-jaxb.html I've modified the Response class:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Response<T> {
#XmlElement
protected String status;
#XmlAnyElement(lax=true)
protected List<T> result;
}
And then tested it with this code:
Response<Project> responseProject = unmarshal(unmarshaller, Project.class, "responseProject.xml");
System.out.println(responseProject.getStatus());
for (Project project:responseProject.getResult()) System.out.println(project);
private static <T> Response<T> unmarshal(Unmarshaller unmarshaller, Class<T> clazz, String xmlLocation) throws JAXBException {
StreamSource xml = new StreamSource(xmlLocation);
#SuppressWarnings("unchecked")
Response<T> wrapper = (Response<T>) unmarshaller.unmarshal(xml, Response.class).getValue();
return wrapper;
}
And I get this exception reading the response list:
Exception in thread "main" java.lang.ClassCastException: com.sun.org.apache.xerces.internal.dom.ElementNSImpl cannot be cast to org.test.Project
Note: I can't modify the original XML. There are more types other than Project and User.
Thanks to Blaise Doughan and his article I've found the solution.
First we need the Wrapper class provided in the article:
#XmlRootElement
public class Wrapper<T> {
private List<T> items;
public Wrapper() {
items = new ArrayList<T>();
}
public Wrapper(List<T> items) {
this.items = items;
}
#XmlAnyElement(lax=true)
public List<T> getItems() {
return items;
}
}
Then I've modified the Response class in order to use it:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Response<T> {
#XmlElement
protected String status;
#XmlElement
protected Wrapper<T> result;
...
public Response(String status, List<T> result) {
this.status = status;
this.result = new Wrapper<>(result);
}
...
public List<T> getResult() {
return result.getItems();
}
...
}
Finally the unmarshalling code:
JAXBContext context = JAXBContext.newInstance(Response.class, Project.class, User.class, Wrapper.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
StreamSource source = new StreamSource(new File("responseProject.xml"));
Response<Project> responseProject = (Response<Project>)unmarshaller.unmarshal(source);
System.out.println(responseProject.getStatus());
for (Project project:responseProject.getResult()) System.out.println(project);
source = new StreamSource(new File("responseUser.xml"));
Response<User> responseUser = (Response<User>)unmarshaller.unmarshal(source);
System.out.println(responseUser.getStatus());
for (User user:responseUser.getResult()) System.out.println(user);
I've added the Wrapper class to the context class list.
Alternatively you can add this annotation to the Response class:
#XmlSeeAlso({Project.class, User.class})
Using #XmlSeeAlso({Project.class, User.class}) on Response classes has the drawback of generating some garbage information on each entity in the list: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="userAccount"
<resources>
<links>
<link>
<rel>self</rel>
<uri>http://localhost:8080/salonea-1.0/rest/user-accounts?offset=0&limit=2</uri>
</link>
<link>
<rel>prev</rel>
<uri></uri>
</link>
<link>
<rel>next</rel>
<uri>http://localhost:8080/salonea-1.0/rest/user-accounts?offset=2&limit=2</uri>
</link>
</links>
<collection>
<user-account
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="userAccount">
<accountType>user</accountType>
<activationCode>638f502a0e409348ccc2e36c24907f0</activationCode>
<email>michzio#hotmail.com</email>
<login>michzio</login>
<password>sAmPL3#e</password>
<registrationDate>2015-09-03T17:30:03+02:00</registrationDate>
<userId>1</userId>
</user-account>
<user-account
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="userAccount">
<accountType>user</accountType>
<activationCode>334bc79d142a291894bd71881e38a719</activationCode>
<email>alicja#krainaczarow.com</email>
<login>alicja</login>
<password>zAczka!00</password>
<registrationDate>2015-09-03T17:30:03+02:00</registrationDate>
<userId>2</userId>
</user-account>
</collection>
</resources>
I'm having a problem using Moxy to marshal/unmarshal Salesforce outbound message XMLs. The exception that I'm getting is:
javax.xml.bind.JAXBException
- with linked exception:
[Exception [EclipseLink-44] (Eclipse Persistence Services - 2.2.0.v20110202-r8913): org.eclipse.persistence.exceptions.DescriptorException
Exception Description: Missing class indicator field from database row [UnmarshalRecord()].
Descriptor: XMLDescriptor(service.salesforce.model.SObject --> [])]
The classes that I have are:
#XmlSeeAlso({Account.class, Opportunity.class})
#XmlDiscriminatorNode("#type")
public abstract class SObject {
public abstract String getId();
public abstract void setId(String id);
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlDiscriminatorValue("sf:Opportunity")
public class Opportunity extends SObject {
#XmlElement(name = "Id", namespace = "urn:sobject.enterprise.soap.sforce.com")
private String id;
#XmlElement(name = "AccountId", namespace = "urn:sobject.enterprise.soap.sforce.com")
private String accountId;
#XmlElement(name = "StageName", namespace = "urn:sobject.enterprise.soap.sforce.com")
private String stageName;
#XmlElement(name = "Agreement_Date_Signed__c", namespace = "urn:sobject.enterprise.soap.sforce.com")
private Date agreementDateSigned;
#XmlElement(name = "Agreement_Date_Start__c", namespace = "urn:sobject.enterprise.soap.sforce.com")
private Date agreementDateStart;
#XmlElement(name = "Payment_Method__c", namespace = "urn:sobject.enterprise.soap.sforce.com")
private String paymentMethod;
// getters and setters...
}
The XML that I'm trying to unmarshal is:
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:out="http://soap.sforce.com/2005/09/outbound" xmlns:urn="urn:sobject.enterprise.soap.sforce.com">
<soapenv:Header/>
<soapenv:Body>
<out:notifications>
<out:OrganizationId>123456789123456789</out:OrganizationId>
<out:ActionId>123456789123456789</out:ActionId>
<out:SessionId>sessId</out:SessionId>
<out:EnterpriseUrl>http://www.enterpriseexample.com</out:EnterpriseUrl>
<out:PartnerUrl>http://www.partnerexample.com</out:PartnerUrl>
<out:Notification>
<out:Id>987654321987654321</out:Id>
<out:sObject xsi:type="sf:Opportunity" xmlns:sf="urn:sobject.enterprise.soap.sforce.com">
<urn:Id>121212121212121212</urn:Id>
<urn:AccountId>121212121212121212</urn:AccountId>
<urn:Agreement_Date_Signed__c>2012-01-01T08:34:56Z</urn:Agreement_Date_Signed__c>
<urn:Agreement_Date_Start__c>2012-02-01</urn:Agreement_Date_Start__c>
<urn:StageName>Booking</urn:StageName>
</out:sObject>
</out:Notification>
</out:notifications>
</soapenv:Body>
</soapenv:Envelope>
Any idea?
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
Since you are using the xsi:type attribute as your discriminator node as your inheritance indicator, I would recommend using the standard JAXB mechanisms rather than MOXy's #XmlDescriminatorNode/#XmlDescriminatorValue extension.
SObject
package forum987537;
import javax.xml.bind.annotation.XmlSeeAlso;
#XmlSeeAlso({Account.class, Opportunity.class})
public abstract class SObject {
public abstract String getId();
public abstract void setId(String id);
}
Opportunity
package forum987537;
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name="Opportunity")
public class Opportunity extends SObject {
private String id;
#Override
public String getId() {
return id;
}
#Override
public void setId(String id) {
this.id = id;
}
}
Notification
package forum987537;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name="Notification", namespace="http://soap.sforce.com/2005/09/outbound")
public class Notification {
private SObject sObject;
#XmlElement(namespace="http://soap.sforce.com/2005/09/outbound")
public SObject getsObject() {
return sObject;
}
public void setsObject(SObject sObject) {
this.sObject = sObject;
}
}
package-info
#XmlSchema(
namespace="urn:sobject.enterprise.soap.sforce.com",
elementFormDefault=XmlNsForm.QUALIFIED,
xmlns={
#XmlNs(prefix="out", namespaceURI = "http://soap.sforce.com/2005/09/outbound"),
#XmlNs(prefix="sf", namespaceURI = "urn:sobject.enterprise.soap.sforce.com"),
})
package forum987537;
import javax.xml.bind.annotation.*;
Demo
package forum987537;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Notification.class);
Notification notification = new Notification();
Opportunity opportunity = new Opportunity();
opportunity.setId("ABC123");
notification.setsObject(opportunity);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(notification, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8"?>
<out:Notification xmlns:sf="urn:sobject.enterprise.soap.sforce.com" xmlns:out="http://soap.sforce.com/2005/09/outbound">
<out:sObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="sf:Opportunity">
<sf:id>ABC123</sf:id>
</out:sObject>
</out:Notification>
For More Information
http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-xsitype.html
http://blog.bdoughan.com/2012/02/jaxb-and-inheritance-eclipselink-moxy.html