2010-07-19 12 views
19

Jest brzydki plik XML, który ma zostać unmarshalled:Java/JAXB: wycofać XML atrybuty do konkretnego obiektu Java atrybuty

<?xml version="1.0" ?> 
<configuration> 
    <section name="default_options"> 
     <value name="default_port">8081</value> 
     <value name="log_level">WARNING</value> 
    </section> 
    <section name="custom_options"> 
     <value name="memory">64M</value> 
     <value name="compatibility">yes</value> 
    </section> 
</configuration> 

Powstałe obiekty Java powinny być: odpowiedź

public class DefaultOptions { 
    private int defaultPort; 
    private String logLevel; 
    // etc... 
} 

public class CustomOptions { 
    private String memory; 
    private String compatibility; 
    // etc... 
} 

This pytanie jest bardzo blisko, ale nie mogę znaleźć ostatecznego rozwiązania.

+0

Nie możesz zmienić wynikowych obiektów java? IMHO byłoby znacznie łatwiejsze i bardziej przejrzyste, gdybyś po prostu podążył za strukturą xml (lub zmień ją tak, by podążała za strukturą, której chcesz) –

+0

@Diego Dias, właściwie nie. Musi być takich POJO. –

+0

Czy wspólna super klasa może zostać dodana do DefaultOptions & CustomOptions? –

Odpowiedz

16

jaki temat?

wprowadzenie wspólnego super klasy zwane opcje:

import javax.xml.bind.annotation.XmlAttribute; 

public abstract class Options { 

    private String name; 

    @XmlAttribute 
    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 

} 

Następnie na swojej klasie z listy opcji (konfiguracja w tym przykładzie), określić @XmlJavaTypeAdapter na tej nieruchomości:

import java.util.ArrayList; 
import java.util.List; 

import javax.xml.bind.annotation.XmlRootElement; 
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; 

@XmlRootElement 
public class Configuration { 

    private List<Options> section = new ArrayList<Options>(); 

    @XmlJavaTypeAdapter(OptionsAdapter.class) 
    public List<Options> getSection() { 
     return section; 
    } 

    public void setSection(List<Options> section) { 
     this.section = section; 
    } 

} 

XmlAdapter będzie wyglądał mniej więcej tak:

import javax.xml.bind.annotation.adapters.XmlAdapter; 

public class OptionsAdapter extends XmlAdapter<AdaptedOptions, Options> { 

    @Override 
    public Options unmarshal(AdaptedOptions v) throws Exception { 
     if("default_options".equals(v.name)) { 
      DefaultOptions options = new DefaultOptions(); 
      options.setName(v.getName()); 
      options.setDefaultPort(Integer.valueOf(v.map.get("default_port"))); 
      options.setLogLevel(v.map.get("log_level")); 
      return options; 
     } else { 
      CustomOptions options = new CustomOptions(); 
      options.setName(v.getName()); 
      options.setCompatibility(v.map.get("compatibility")); 
      options.setMemory(v.map.get("memory")); 
      return options; 
     } 
    } 

    @Override 
    public AdaptedOptions marshal(Options v) throws Exception { 
     AdaptedOptions adaptedOptions = new AdaptedOptions(); 
     adaptedOptions.setName(v.getName()); 
     if(DefaultOptions.class == v.getClass()) { 
      DefaultOptions options = (DefaultOptions) v; 
      adaptedOptions.map.put("default_port", String.valueOf(options.getDefaultPort())); 
      adaptedOptions.map.put("log_level", options.getLogLevel()); 
     } else { 
      CustomOptions options = (CustomOptions) v; 
      adaptedOptions.map.put("compatibility", options.getCompatibility()); 
      adaptedOptions.map.put("memory", options.getMemory()); 
     } 
     return adaptedOptions; 
    } 

} 

AdaptedOption s wygląda następująco:

import java.util.ArrayList; 
import java.util.HashMap; 
import java.util.List; 
import java.util.Map; 
import java.util.Map.Entry; 

import javax.xml.bind.Marshaller; 
import javax.xml.bind.Unmarshaller; 
import javax.xml.bind.annotation.XmlAttribute; 
import javax.xml.bind.annotation.XmlElement; 
import javax.xml.bind.annotation.XmlValue; 

public class AdaptedOptions extends Options { 

    @XmlAttribute String name; 
    @XmlElement List<Value> value = new ArrayList<Value>(); 
    Map<String, String> map = new HashMap<String, String>(); 

    public void beforeMarshal(Marshaller marshaller) { 
     for(Entry<String, String> entry : map.entrySet()) { 
      Value aValue = new Value(); 
      aValue.name = entry.getKey(); 
      aValue.value = entry.getValue(); 
      value.add(aValue); 
     } 
    } 

    public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) { 
     for(Value aValue : value) { 
      map.put(aValue.name, aValue.value); 
     } 
    } 

    private static class Value { 
     @XmlAttribute String name; 
     @XmlValue String value; 
    } 

} 
8

może utworzyć oddzielne klasy do reprezentowania struktury swojej XML:

public class Section { 
    @XmlAttribute 
    public String name; 
    @XmlElement(name = "value") 
    public List<Value> values; 
} 

public class Value { 
    @XmlAttribute 
    public String name; 
    @XmlValue 
    public String value; 
} 

a następnie użyć XmlAdapter wykonać konwersję:

public class OptionsAdapter extends XmlAdapter<Section, Options> { 
    public Options unmarshal(Section s) { 
     if ("default_options".equals(s.name)) { 
      ... 
     } else if (...) { 
      ... 
     } 
     ... 
    } 
    ... 
} 

@XmlElement 
public class Configuration { 
    @XmlElement(name = "section") 
    @XmlJavaTypeAdapter(OptionsAdapter.class) 
    public List<Options> options; 
} 

public class DefaultOptions extends Options { ... } 
public class CustomOptions extends Options { ... } 
+0

Ciekawe rozwiązanie. Po pierwsze, dziękuję. Spróbuję tego. Czy to jedyny sposób na rozwiązanie problemu? A może istnieją bardziej eleganckie rozwiązania bez "if (" default_options ".equals (s.name))"? –

+0

@Artyom: Ponieważ JAXB nie może wywnioskować klasy docelowej z wartości atrybutu, wymaga to pewnego rodzaju ręcznego nadpisywania. Oczywiście możesz to zrobić w bardziej abstrakcyjny sposób, np. użyj mapy z wartości atrybutów do opcji fabryk. – axtavt