0
votes

I'm trying to deserialize an xml using apache camel jackson xml and something strange happens which I don't have an explanation for. Here's the xml I'm trying to deserialize(it's simple xmltv xml file):

<?xml version="1.0" encoding="UTF-8"?>
<tv>
  <programme start="20210102000300 +0000" stop="20210102003000 +0000" channel="XTV100005403" recordable="Y" npvrenable="Y" id="SH032096260000.20210102000300.44345" type="program">
     <title lang="en">Barbados Ninja Throwdown</title>
     <desc lang="en">Contestants compete to complete a challenging obstacle course the fastest.</desc>
     <credits>
        <actor nameId="1288485">Wayne Simmons</actor>
     </credits>
     <date>20190427</date>
     <category>Reality</category>
     <category>Action sports</category>
     <category>Action</category>
     <category>Adventure</category>
     <releaseType>TV</releaseType>
     <extentionInfo>
        <key>releaseStatus</key>
        <value>1</value>
     </extentionInfo>
  </programme>
</tv>

Here are my POJOs:

Tv.java

@XmlRootElement(name = "tv")
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
public class Tv {

  @XmlElement(name = "programme")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<Programme> programmes = new ArrayList<>();

}

Programme.java

@JsonIgnoreProperties(ignoreUnknown = true)
@Data
public class Programme {

  @XmlAttribute(name = "id")
  @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
  private String id;

  @XmlAttribute(name = "type")
  @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
  private String type;

  @XmlAttribute(name = "start", required = true)
  @JsonDeserialize(using = OffsetDateTimeDeserializer.class)
  private OffsetDateTime start;

  @XmlAttribute(name = "stop")
  @JsonDeserialize(using = OffsetDateTimeDeserializer.class)
  private OffsetDateTime stop;

  @XmlAttribute(name = "pdc-start")
  @JsonDeserialize(using = OffsetDateTimeDeserializer.class)
  private OffsetDateTime pdcStart;

  @XmlAttribute(name = "vps-start")
  @JsonDeserialize(using = OffsetDateTimeDeserializer.class)
  private OffsetDateTime vpsStart;

  @XmlAttribute(name = "showview")
  @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
  private String showview;

  @XmlAttribute(name = "videoplus")
  @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
  private String videoplus;

  @XmlAttribute(name = "channel", required = true)
  @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
  private String channel;

  @XmlAttribute(name = "clumpidx")
  @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
  private String clumpidx;

  @XmlElement(name = "title", required = true)
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<Label> titles;

  @XmlElement(name = "sub-title")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<Label> subTitle;

  @XmlElement(name = "desc")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<Label> descriptions;

  @XmlElement(name = "credits")
  private Credits credits;

  private String date;

  @XmlElement(name = "category")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<Label> categories;

  @XmlElement(name = "keyword")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<Label> keywords;

  private Label language;

  @XmlElement(name = "orig-language")
  private Label origLanguage;

  private Length length;

  @XmlElement(name="icons")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<Icon> icons;

  @XmlElement(name = "url")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<String> urls;

  @XmlElement(name = "country")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<Label> countries;

  @XmlElement(name = "episode-num")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<EpisodeNum> episodeNumbers;

  private Video video;

  private Audio audio;

  @XmlElement(name = "previously-shown")
  private PreviouslyShown previouslyShown;

  private Label premiere;

  @XmlElement(name = "last-chance")
  private Label lastChance;

  @XmlElement(name = "new")
  private Label isNew;

  @JacksonXmlElementWrapper(useWrapping = false)
  private List<Subtitles> subtitles;

  @XmlElement(name = "rating")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<Rating> ratings;

  @XmlElement(name = "star-rating")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<StarRating> starRatings;

  @XmlElement(name = "review")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<Review> reviews;
  
}

Credits.java

@Data
public class Credits {
  @XmlElement(name = "director")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<String> directors;

  @XmlElement(name = "actor")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<String> actors;

  @XmlElement(name = "writer")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<String> writers;

  @XmlElement(name = "adapter")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<String> adapters;

  @XmlElement(name = "producer")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<String> producers;

  @XmlElement(name = "composer")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<String> composers;

  @XmlElement(name = "editor")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<String> editors;

  @XmlElement(name = "presenter")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<String> presenters;

  @XmlElement(name = "commentator")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<String> commentators;

  @XmlElement(name = "guest")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<String> guests;
}

The other POJOs are not important. So here is the route configuration:

@Override
  public void configure() throws Exception {
    final JacksonXMLDataFormat dataFormat = new JacksonXMLDataFormat();
    dataFormat.setUnmarshalType(Tv.class);

    from("file:///someLocation")
        .routeId(getClass().getSimpleName())
        .autoStartup(true)
        .unmarshal(dataFormat)
        .process("someProcessorBean")

  }

With this setup and the xml I provided at the beginning the following exception occurs:

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of Programme (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('20190427') at [Source: (BufferedInputStream); line: 9, column: 20] (through reference chain: com.azdio.mdw.ingest.epg.xmltv.common.domain.Tv["programme"]->java.util.ArrayList[1]) at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63) at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1429) at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1059) at com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:371) at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromString(StdValueInstantiator.java:323) at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1373) at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:171) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:161) at com.fasterxml.jackson.dataformat.xml.deser.WrapperHandlingDeserializer.deserialize(WrapperHandlingDeserializer.java:114) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:286) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:245) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:27) at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129) at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151) at com.fasterxml.jackson.dataformat.xml.deser.WrapperHandlingDeserializer.deserialize(WrapperHandlingDeserializer.java:114) at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4202) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3242) at org.apache.camel.component.jacksonxml.JacksonXMLDataFormat.unmarshal(JacksonXMLDataFormat.java:193) at org.apache.camel.processor.UnmarshalProcessor.process(UnmarshalProcessor.java:69) at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:548) at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:201) at org.apache.camel.processor.Pipeline.process(Pipeline.java:138) at org.apache.camel.processor.Pipeline.process(Pipeline.java:101) at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:201) at org.apache.camel.component.file.GenericFileConsumer.processExchange(GenericFileConsumer.java:454) at org.apache.camel.component.file.GenericFileConsumer.processBatch(GenericFileConsumer.java:223) at org.apache.camel.component.file.GenericFileConsumer.poll(GenericFileConsumer.java:187) at org.apache.camel.impl.ScheduledPollConsumer.doRun(ScheduledPollConsumer.java:174) at org.apache.camel.impl.ScheduledPollConsumer.run(ScheduledPollConsumer.java:101)

It took me a lot of time to understand what this means. So it seems that it accepts the closing tag as end of the tag and after that it tries to create a new Programme instance out of the tag value and it fails because it doesn't know how to do it. In order to prove that for myself I moved the tag at the end of the tag so the xml looked like the following:

<?xml version="1.0" encoding="UTF-8"?>
<tv>
  <programme start="20210102000300 +0000" stop="20210102003000 +0000" channel="XTV100005403" recordable="Y" npvrenable="Y" id="SH032096260000.20210102000300.44345" type="program">
     <title lang="en">Barbados Ninja Throwdown</title>
     <desc lang="en">Contestants compete to complete a challenging obstacle course the fastest.</desc>
     <date>20190427</date>
     <category>Reality</category>
     <category>Action sports</category>
     <category>Action</category>
     <category>Adventure</category>
     <releaseType>TV</releaseType>
     <extentionInfo>
        <key>releaseStatus</key>
        <value>1</value>
     </extentionInfo>
     <credits>
        <actor nameId="1288485">Wayne Simmons</actor>
     </credits>
  </programme>
</tv>

This time when I run the application the xml is correctly deserialized without any exceptions. So can someone explain me why it's acting like that and did I make a mistake somewhere?

I'm using apache camel 2.25.3 and respectively the same version of camel-jacksonxml.

Edit:

I succeeded to isolate the problem just to jackson:

 JacksonXmlModule jacksonXmlModule = new JacksonXmlModule();
 jacksonXmlModule.setDefaultUseWrapper(false);
 ObjectMapper objectMapper = new XmlMapper(jacksonXmlModule);
 objectMapper.registerModule(new JaxbAnnotationModule());
 objectMapper.readValue(new File("xmlFilePath"), Tv.class);

This leads to the same exception.

1

1 Answers

0
votes

So I found out what is causing this exception although I'm not completely sure why it's made that way. After a debugging session of the jackson's object mapper I found that the problem is in the nameId attribute of the actor tag. And it seems the deserializer can't decide how to do it when I have:

  @XmlElement(name = "actor")
  @JacksonXmlElementWrapper(useWrapping = false)
  private List<String> actors;

So a solution I found(which I'm not sure that is the most correct one) is to create another pojo CreditsElement.java:

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class CreditsElement {

  @XmlValue
  private String value;
}

I don't include the nameId attribute, because I don't need it. Apart from that solution is there any better way to map it without creating another class and use the List property?