2016-01-26 3 views
0

У меня есть фиксированная структура XML, которая уже используется другими приложениями. Некоторые из них являются сторонними, поэтому изменение XML не является вариантом.@XmlJavaTypeAdapter не вызывает TypeAdapter

В XML содержится раздел, который я пытаюсь развязать. Ниже приведена сокращенная версия. Этот элемент является дочерним элементом других элементов.

<premium> 
    <allowInstalments>true</allowInstalments> 
    <annualPremium>2964.23</annualPremium> 

    <!-- other various elements --> 
    <calcElement partname="driver"> 
     <driverXs>300.00</driverXs> 
     <seq>1</seq> 
    </calcElement> 
    <calcElement partname="ratingData"> 
     <baseMiles>6000</baseMiles> 
     <vehicleGroup>15</vehicleGroup> 
     <documentVersion>4</documentVersion> 
    </calcElement> 
</premium> 

Чтобы проверить, что это демаршализует правильно (и маршалом, но я пытаюсь распаковать в данный момент), я написал следующий тест:

@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(classes = {RegressionApplication.class}) 
public class AdaptedCalcElementTest { 


    @Autowired 
    @Qualifier(value = "unmarshaller") 
    private Unmarshaller unmarshaller; 

    @Test 
    public void canUnmarshallIntoDriverCalcElement() throws Exception { 
     String xml = "<wrapper><calcElement partname=\"driver\">" + 
       "<driverXs>300.00</driverXs>" + 
       "<seq>1</seq>" + 
       "</calcElement></wrapper>"; 

     CalcElementWrapper calcElementWrapper = (CalcElementWrapper) unmarshaller.unmarshal(Input.from(xml).build()); 
     assertThat(calcElementWrapper, notNullValue()); 
     assertThat(calcElementWrapper.listElements, notNullValue()); 
     assertThat(calcElementWrapper.listElements, hasSize(1)); 
     CalcElement calcElement = calcElementWrapper.listElements.get(0); 
     assertThat(calcElement, instanceOf(DriverCalcElement.class)); 
    } 

    @XmlAccessorType(XmlAccessType.FIELD) 
    @XmlRootElement(name = "wrapper") 
    public static class CalcElementWrapper { 
     @XmlJavaTypeAdapter(CalcElementAdapter.class) 
     public List<CalcElement> listElements; 
    } 
} 

Мой класс адаптер создает правильный CalcElement расширенный класс на основе атрибута имени пути:

public class CalcElementAdapter extends XmlAdapter<CalcElementAdapter.AdaptedCalcElement, CalcElement> { 

    @Override 
    public CalcElement unmarshal(CalcElementAdapter.AdaptedCalcElement v) throws Exception { 
     if (v.partname.equalsIgnoreCase("driver")) { 
      DriverCalcElement calcElement = new DriverCalcElement(); 
      calcElement.setPartname(v.partname); 
      calcElement.setDriverXs(new BigDecimal(v.driverXs)); 
      calcElement.setSeq(new Integer(v.seq)); 
      return calcElement; 
     } else if (v.partname.equalsIgnoreCase("ratingData")) { 
      RatingDataCalcElement calcElement = new RatingDataCalcElement(); 
      calcElement.setBaseMiles(new Integer(v.baseMiles)); 
      calcElement.setDocumentVersion(new Integer(v.documentVersion)); 
      calcElement.setVehicleGroup(new Integer(v.vehicleGroup)); 
      return calcElement; 
     } 

     return null; 
    } 

    @Override 
    public CalcElementAdapter.AdaptedCalcElement marshal(CalcElement v) throws Exception { 
     return null; 
    } 

    public static class AdaptedCalcElement { 

     @XmlAttribute 
     public String partname; 

     public String driverXs; 
     public String seq; 
     public String baseMiles; 
     public String vehicleGroup; 
     public String documentVersion; 
    } 
} 

и в CalcElement и производные классы определяются следующим образом:

public abstract class CalcElement { 

    private String partname; 

    @XmlAttribute 
    public String getPartname() { 
     return partname; 
    } 

    public void setPartname(String partname) { 
     this.partname = partname; 
    } 
} 

public class DriverCalcElement extends CalcElement { 
    private BigDecimal driverXs; 
    private Integer seq; 

    public BigDecimal getDriverXs() { 
     return driverXs; 
    } 

    public void setDriverXs(BigDecimal driverXs) { 
     this.driverXs = driverXs; 
    } 

    public Integer getSeq() { 
     return seq; 
    } 

    public void setSeq(Integer seq) { 
     this.seq = seq; 
    } 
} 

public class RatingDataCalcElement extends CalcElement { 
    private Integer baseMiles; 
    private Integer vehicleGroup; 
    private Integer documentVersion; 

    public Integer getBaseMiles() { 
     return baseMiles; 
    } 

    public void setBaseMiles(Integer baseMiles) { 
     this.baseMiles = baseMiles; 
    } 

    public Integer getVehicleGroup() { 
     return vehicleGroup; 
    } 

    public void setVehicleGroup(Integer vehicleGroup) { 
     this.vehicleGroup = vehicleGroup; 
    } 

    public Integer getDocumentVersion() { 
     return documentVersion; 
    } 

    public void setDocumentVersion(Integer documentVersion) { 
     this.documentVersion = documentVersion; 
    } 
} 

unmarshaller сконфигурирован как:

@Bean(name = "unmarshaller") 
Unmarshaller getUnmarshaller() { 
    return getJaxb2Marshaller(); 
} 

private Jaxb2Marshaller getJaxb2Marshaller() { 
    Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); 
    marshaller.setPackagesToScan("com.itb.lego.regression"); 
    return marshaller; 
} 

Я пытался следовать блог вывешивать @ Блез-doughan, но я не могу видеть то, что мне не хватает.

Однако тест не работает, поскольку он не вызывает адаптер для элементов calcElement. Что мне не хватает?

ответ

0

Я обнаружил, что мне нужно идти намного дальше, чем просто сообщить JaxB, как развязать CalcElement.

Мой первый шаг состоял в том, чтобы создать класс, который можно было бы развязать с любого типа CalcElement.

@XmlRootElement(name = "calcElement") 
public class AdaptableCalcElement { 

    @XmlAttribute 
    public String partname; 

    @XmlElement 
    public String driverXs; 
    @XmlElement 
    public String seq; 
    @XmlElement 
    public String baseMiles; 
    @XmlElement 
    public String vehicleGroup; 
    @XmlElement 
    public String documentVersion; 
} 

Все элементы являются строками, потому что это всего лишь переходный класс, но будет содержать все доступные данные, чтобы позволить мне написать логику, чтобы создать специфические CalcElement производные классы.

Поскольку элементы calcElement не заключены в содержащийся элемент, а разница между двумя содержащимися классами является непосредственным дочерним элементом. Это означает, что мне понадобилось 2 разных класса-оболочки. Один для хранения списка AdaptableCalcElement, а затем окончательной версии для хранения списка CalcElement.

Это дало мне следующие 2 класса:

@XmlAccessorType(XmlAccessType.PROPERTY) 
@XmlRootElement(name = "premium") 
public class AdaptablePremium extends BasePremium { 

    private List<AdaptableCalcElement> calcElements; 

    @XmlElement(name = "calcElement") 
    public List<AdaptableCalcElement> getCalcElements() { 
     return calcElements; 
    } 

    public void setCalcElements(List<AdaptableCalcElement> calcElements) { 
     this.calcElements = calcElements; 
    } 
} 

и

public class Premium extends BasePremium { 

    List<CalcElement> calcElements; 

    public List<CalcElement> getCalcElements() { 
     return calcElements; 
    } 

    public void setCalcElements(List<CalcElement> calcElements) { 
     this.calcElements = calcElements; 
    } 
} 

оба они простирались от BasePremium, который содержит все поля, которые являются общими для обоих.

На этом этапе я могу развязать XML в AdaptablePremium. Премиальный XML действителен только в содержащем XML-документе, который позволяет нам указать, что контейнер (Details) содержит премиум, и как его размонтировать.

@XmlAccessorType(XmlAccessType.FIELD) 
@XmlRootElement(name = "detail") 
public class Detail { 
    // All other fields for this class 

    @XmlJavaTypeAdapter(PremiumAdapter.class) 
    private Premium premium; 
} 

Это означает, что теперь, когда я распаковать детали элемент, он будет использовать этот класс и пытаться распаковать премиум элемент, используя класс PremiumAdapter.Класс PremiumAdapter определяет, как преобразовать AdaptablePremium в Premium. BasePremium имеет много членов (очень плохая структура XML, но она исправлена), поэтому вместо копирования каждого члена я использовал отражение, чтобы скопировать все, кроме CalcElements, а затем сделал это явно. Я остался со следующим XMLAdapter класса

@Component 
public class PremiumAdapter extends XmlAdapter<AdaptablePremium, Premium> { 

    @Override 
    public Premium unmarshal(AdaptablePremium adaptablePremium) throws Exception { 
     Premium premium = new Premium(); 
     Class<?> premiumClass = premium.getClass(); 
     Class<?> adaptablePremiumClass = adaptablePremium.getClass(); 

     ReflectionUtils.doWithFields(Premium.class, 
       field -> { 
        String fieldName = capitalise(field.getName()); 
        Class<?> type = field.getType(); 
        try { 
         Method getter = adaptablePremiumClass.getMethod("get" + fieldName); 
         Method setter = premiumClass.getMethod("set" + fieldName, type); 
         String value = (String) ReflectionUtils.invokeMethod(getter, adaptablePremium); 
         if (type.equals(String.class)) { 
          ReflectionUtils.invokeMethod(setter, premium, value); 
         } else if (type.equals(BigDecimal.class)) { 
          ReflectionUtils.invokeMethod(setter, premium, new BigDecimal(value)); 
         } 
        } catch (NoSuchMethodException e) { 
         e.printStackTrace(); 
        } 
       }, 
       field -> !field.getName().equalsIgnoreCase("calcElements") 
     ); 

     adaptCalcElements(adaptablePremium, premium); 

     return premium; 
    } 

    public String capitalise(String name) { 
     return name.substring(0, 1).toUpperCase() + name.substring(1); 
    } 

    public void adaptCalcElements(AdaptablePremium adaptablePremium, Premium premium) { 
     List<CalcElement> calcElements = new ArrayList<>(); 

     for(AdaptableCalcElement adaptableCalcElement : adaptablePremium.getCalcElements()) { 
      if (adaptableCalcElement.partname.equalsIgnoreCase("driver")) { 
       DriverCalcElement calcElement = new DriverCalcElement(); 
       calcElement.setPartname(adaptableCalcElement.partname); 
       calcElement.setDriverXs(new BigDecimal(adaptableCalcElement.driverXs)); 
       calcElement.setSeq(new Integer(adaptableCalcElement.seq)); 
       calcElements.add(calcElement); 
      } else if (adaptableCalcElement.partname.equalsIgnoreCase("ratingData")) { 
       RatingDataCalcElement calcElement = new RatingDataCalcElement(); 
       calcElement.setPartname(adaptableCalcElement.partname); 
       calcElement.setBaseMiles(new Integer(adaptableCalcElement.baseMiles)); 
       calcElement.setDocumentVersion(new Integer(adaptableCalcElement.documentVersion)); 
       calcElement.setVehicleGroup(new Integer(adaptableCalcElement.vehicleGroup)); 
       calcElements.add(calcElement); 
      } 
     } 
     premium.setCalcElements(calcElements); 
    } 

    @Override 
    public AdaptablePremium marshal(Premium v) throws Exception { 
     // Currently unimplemented 
     return null; 
    } 
} 

У меня есть некоторые дополнительные рефакторинга, чтобы сделать, но это теперь будет правильно распаковать XML-в правильных типов.

Смежные вопросы