I am trying to build an application that uses the Roku Tv ECP api commands. The specific call I am trying to make gets a list of all installed channels and returns it in XML format. Whenever I call this api, I get an error while extracting the response.
Here is how I am making my api call:
public class ConsumeXMLResponse {
private RestTemplate restTemplate;
private final Logger log = LoggerFactory.getLogger(ConsumeXMLResponse.class);
public ConsumeXMLResponse(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public ResponseEntity<Apps> get() {
this.restTemplate = new RestTemplateBuilder().build();
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
messageConverters.add(converter);
this.restTemplate.setMessageConverters(messageConverters);
log.info("This worked!");
return this.restTemplate.getForEntity("http://10.0.0.229:8060/query/apps",
Apps.class);
}
}
@Controller
@RequestMapping("View")
public class RestXMLResponseController {
private RestTemplate restTemplate;
// Annotation
@GetMapping(path = "/get", produces = {"application/xml"})
public String
getXMLOutput(Model model)
{
ConsumeXMLResponse response
= new ConsumeXMLResponse(restTemplate);
ResponseEntity<Apps> responseEntity
= response.get();
//Apps entity = responseEntity.getBody();
//HttpHeaders headers = responseEntity.getHeaders();
//model.addAttribute("xml", entity);
//model.addAttribute("XMLheaders", headers);
return "View";
}
}
The object I am trying to recreate from the response:
@XmlRootElement(name = "apps")
@XmlAccessorType(XmlAccessType.FIELD)
public class Apps {
@XmlElement(name = "app")
private List<App> app;
}
@XmlAccessorType(XmlAccessType.FIELD)
public class App {
@XmlAttribute(name = "id")
public String id;
@XmlAttribute(name = "type")
public String type;
@XmlAttribute(name = "version")
public String version;
}
Here is an example of what the XML looks like when calling the api directly in postman:
<?xml version="1.0" encoding="UTF-8" ?>
<apps>
<app id="tvinput.hdmi3" type="tvin" version="1.0.0">firestick</app>
<app id="tvinput.hdmi1" type="tvin" version="1.0.0">Xbox</app>
<app id="tvinput.hdmi2" type="tvin" version="1.0.0">Nintendo Switch</app>
<app id="tvinput.dtv" type="tvin" version="1.0.0">Live TV</app>
<app id="12" type="appl" version="5.1.120088002">Netflix</app>
<app id="61322" type="appl" version="55.2.0">Max</app>
<app id="13" type="appl" version="14.1.2023092022">Prime Video</app>
<app id="2285" type="appl" version="6.75.2">Hulu</app>
<app id="291097" type="appl" version="1.37.2023101000">Disney Plus</app>
<app id="34376" type="appl" version="4.8.2023051800">ESPN</app>
<app id="151908" type="appl" version="9.3.10">The Roku Channel</app>
<app id="13842" type="appl" version="1.1.9">Vudu</app>
<app id="46041" type="appl" version="8.96.12089">Sling TV - Live Sports, News, Shows + Freestream</app>
<app id="45706" type="appl" version="1.0.60">Roku TV Intro</app>
<app id="552944" type="appl" version="1.2.64">Roku Tips & Tricks</app>
<app id="562661" type="appl" version="1.2.28">Zoom TV for Kids</app>
<app id="86398" type="appl" version="9.4.0">SYFY</app>
<app id="593099" type="appl" version="4.11.12">Peacock TV</app>
<app id="41468" type="appl" version="3.0.2">Tubi - Free Movies & TV</app>
<app id="31440" type="appl" version="8.8.202310302">Paramount Plus</app>
<app id="dev" type="appl" version="1.0.1">MovieBoxPro</app>
</apps>
I tried to call the api that returns an XML response, then convert it to a Java object that maps to Apps the class. The application throws an error whenever it tries to parse the response.
Here is the error:
2023-11-06T17:06:46.430-05:00 ERROR 76726 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.web.client.RestClientException: Error while extracting response for type [class com.TelevisionRemote.SmartTelevisionRemote.component.Apps] and content type [text/xml;charset="utf-8"]] with root cause
com.fasterxml.jackson.core.JsonParseException: Unexpected character ('<' (code 60)): expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 2]
at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:2418) ~[jackson-core-2.14.2.jar:2.14.2]
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:749) ~[jackson-core-2.14.2.jar:2.14.2]
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportUnexpectedChar(ParserMinimalBase.java:673) ~[jackson-core-2.14.2.jar:2.14.2]
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._handleUnexpectedValue(UTF8StreamJsonParser.java:2784) ~[jackson-core-2.14.2.jar:2.14.2]
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._nextTokenNotInObject(UTF8StreamJsonParser.java:907) ~[jackson-core-2.14.2.jar:2.14.2]
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser.nextToken(UTF8StreamJsonParser.java:793) ~[jackson-core-2.14.2.jar:2.14.2]
at com.fasterxml.jackson.databind.ObjectReader._initForReading(ObjectReader.java:357) ~[jackson-databind-2.14.2.jar:2.14.2]
at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2095) ~[jackson-databind-2.14.2.jar:2.14.2]
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1481) ~[jackson-databind-2.14.2.jar:2.14.2]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:395) ~[spring-web-6.0.6.jar:6.0.6]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:354) ~[spring-web-6.0.6.jar:6.0.6]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:103) ~[spring-web-6.0.6.jar:6.0.6]
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1132) ~[spring-web-6.0.6.jar:6.0.6]
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1115) ~[spring-web-6.0.6.jar:6.0.6]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:865) ~[spring-web-6.0.6.jar:6.0.6]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:764) ~[spring-web-6.0.6.jar:6.0.6]
at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:405) ~[spring-web-6.0.6.jar:6.0.6]
at com.TelevisionRemote.SmartTelevisionRemote.controller.ConsumeXMLResponse.get(ConsumeXMLResponse.java:38) ~[classes/:na]
at com.TelevisionRemote.SmartTelevisionRemote.controller.RestXMLResponseController.getXMLOutput(RestXMLResponseController.java:28) ~[classes/:na]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:578) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207) ~[spring-web-6.0.6.jar:6.0.6]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152) ~[spring-web-6.0.6.jar:6.0.6]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-6.0.6.jar:6.0.6]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884) ~[spring-webmvc-6.0.6.jar:6.0.6]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-6.0.6.jar:6.0.6]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.0.6.jar:6.0.6]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081) ~[spring-webmvc-6.0.6.jar:6.0.6]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974) ~[spring-webmvc-6.0.6.jar:6.0.6]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011) ~[spring-webmvc-6.0.6.jar:6.0.6]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.0.6.jar:6.0.6]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:705) ~[tomcat-embed-core-10.1.5.jar:6.0]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.0.6.jar:6.0.6]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814) ~[tomcat-embed-core-10.1.5.jar:6.0]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-10.1.5.jar:10.1.5]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.0.6.jar:6.0.6]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.6.jar:6.0.6]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.0.6.jar:6.0.6]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.6.jar:6.0.6]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.0.6.jar:6.0.6]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.6.jar:6.0.6]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:177) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:859) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1734) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at java.base/java.lang.Thread.run(Thread.java:1623) ~[na:na]
Turns out that adding @JacksonXmlElementWrapper
to my Apps class on the app list attribute solved the issue.
@JacksonXmlElementWrapper(useWrapping = false)
private List<App> app;
I also did not need the message converter.
public Apps getXMLOutput() {
ResponseEntity<Apps> responseEntity
= this.restTemplate.getForEntity("http://10.0.0.229:8060/query/apps",
Apps.class);
Apps apps = responseEntity.getBody();
log.info(apps.toString());
return appsRepository.save(apps);
}
The result in postman:
{
"id": 1,
"app": [
{
"id": "tvinput.hdmi3",
"type": "tvin",
"version": "1.0.0"
},
{
"id": "tvinput.hdmi1",
"type": "tvin",
"version": "1.0.0"
},
{
"id": "tvinput.hdmi2",
"type": "tvin",
"version": "1.0.0"
},
{
"id": "tvinput.dtv",
"type": "tvin",
"version": "1.0.0"
},
{
"id": "12",
"type": "appl",
"version": "5.1.120088002"
},
{
"id": "61322",
"type": "appl",
"version": "55.2.0"
},
{
"id": "13",
"type": "appl",
"version": "14.1.2023092022"
},
{
"id": "2285",
"type": "appl",
"version": "6.75.2"
},
{
"id": "291097",
"type": "appl",
"version": "1.37.2023101000"
},
{
"id": "34376",
"type": "appl",
"version": "4.8.2023051800"
},
{
"id": "151908",
"type": "appl",
"version": "9.3.10"
},
{
"id": "13842",
"type": "appl",
"version": "1.1.9"
},
{
"id": "46041",
"type": "appl",
"version": "8.97.12290"
},
{
"id": "45706",
"type": "appl",
"version": "1.0.60"
},
{
"id": "552944",
"type": "appl",
"version": "1.2.64"
},
{
"id": "562661",
"type": "appl",
"version": "1.2.28"
},
{
"id": "86398",
"type": "appl",
"version": "9.4.0"
},
{
"id": "593099",
"type": "appl",
"version": "4.11.12"
},
{
"id": "41468",
"type": "appl",
"version": "3.0.2"
},
{
"id": "31440",
"type": "appl",
"version": "8.8.202310302"
},
{
"id": "dev",
"type": "appl",
"version": "1.0.1"
}
]
}