# JAXB-Unmarshalling ignoriert/löscht Einträge aus XML - oder lässt sie verschwinden



## dzim (8. Apr 2011)

Hallo zusammen,

ich arbeite bereits seit einiger Zeit mit JAXB und hatte eigentlich nie gravierende Probleme damit, seit gestern aber nervt mich ein Problem mit einem lokalen Modell.

Ich habe eine mittels JAXB annotierte Klasse, die eine recht komplexe Konfiguration einließt. Vor zwei Tagen fing ich an einen Teil zu verbessern und wollte eine bestehende Struktur abändern.
Hier mal ein vereinfachtes Beispiel der Struktur des XML:

```
<root>
  <settingsList>
    <settings>
      <name>test-setting-1</name> <!-- id -->
      <times>
        <settingTimes>
          <day>MONDAY</day>
          <days>4</days>
        <settingTimes>
        </settingTimes>
          <day>Wednesday</day>
          <days>2</days>
        </settingTimes>
      </times>
      <token>t1</token>
    </settings>
    <settings>
      <name>test-setting-2</name>
      <times>
      <settingTimes>
        <day>Tuesday</day>
        <days>1</days>
      </settingTimes>
      </times>
      <token>t2</token>
    </settings>
[noch mehr settings...]
  </settingsList>
  <selection>
    <settings>test-setting-1</settings> <!-- idref -->
  </selection>
[noch mehr selection-Elemente...]
</root>
```

ich habe in die Klasse die das settingTimes-Element darstellt eine afterUnmarshal-Methode hinzugefügt, um herauszufinden, ob die Daten überhaupt gelesen werden und konnte per Debug feststellen, dass dem so ist.
Sobald das Unmarshalling des gesamten XMLs aber abgeschlossen ist, ist das Set, dass die settingTimes repräsentiert, wieder _null_!
Das Lustige ist - naja, wobei, mir ist das Lachen schon vergangen - dass das Speichern von Daten klappt. Kurzum: Ich kann zur Laufzeit des Programms die Daten manipulieren und dann auch abspeichern und es entsteht die gewünschte Struktur. Nur das Laden klappt nicht.

Hier mal der Quellcode für die settingTimes:

```
@XmlType
@XmlAccessorType(XmlAccessType.FIELD)
public class Times {

	void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
		if (settingTimes != null) {
			initialSettingTimes = Collections
					.unmodifiableSet(new TreeSet<SettingTime>(
							settingTimes));
		}
	}

	@XmlTransient
	public static final String SETTING_TIME_CHANGED = "ssd.retention-time.changed"; //$NON-NLS-1$

	@XmlTransient
	public final static int SETTING_RETENTION_TIME = -1;

	@XmlTransient
	private final PropertyChangeSupport changeSupport = new PropertyChangeSupport(
			this);

	public void addPropertyChangeListener(PropertyChangeListener listener) {
		changeSupport.addPropertyChangeListener(listener);
	}

	public void removePropertyChangeListener(PropertyChangeListener listener) {
		changeSupport.removePropertyChangeListener(listener);
	}

	@XmlEnum
	public static enum Day {

		MONDAY("MONDAY"),
		TUESDAY("TUESDAY"),
		WEDNESDAY("WEDNESDAY"),
		THURSDAY("THURSDAY"),
		FRIDAY("FRIDAY"),
		SATURDAY("SATURDAY"),
		SUNDAY("SUNDAY");

		private final String name;

		private Day() {
			this.name = name;
		}

		public String getName() {
			return name;
		}

		public static Day getDayByName(String name) {
			for (Day d : values()) {
				if (d.name.equals(name)) {
					return d;
				}
			}
			return null;
		}

		@Override
		public String toString() {
			return name;
		}

	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime
				* result
				+ ((settingTimes == null) ? 0 : settingTimes
						.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Times other = (Times) obj;
		if (settingTimes == null) {
			if (other.settingTimes != null)
				return false;
		} else if (!settingTimes.equals(other.settingTimes))
			return false;
		return true;
	}

	@XmlElement
	private Set<SettingTime> settingTimes;
	@XmlTransient
	private Set<SettingTime> initialSettingTimes;

	public Set<SettingTime> getSettingTimes() {
		if (settingTimes == null) {
			if (initialSettingTimes != null) {
				settingTimes = new TreeSet<SettingTime>(
						initialSettingTimes);
			} else {
				settingTimes = new TreeSet<SettingTime>();
			}
		}
		return settingTimes;
	}

	public InvestigationTime getSettingTime(Day day) {
		for (SettingTime rt : getSettingTimes()) {
			if (rt.day == day) {
				return rt;
			}
		}
		return null;
	}

	public void setSettingTime(SettingTime time) {

		final Set<SettingTime> old = new TreeSet<SettingTime>(
				investigationTimes);

		if (time.days == null && time.hours == null && time.startHour == null) {
			for (SettingTime rt : settingTimes) {
				if (!rt.day.equals(time.day)) {
					continue;
				}
				settingTimes.remove(rt);
				break;
			}
		} else {
			SettingTime oldIt = getSettingTime(time.day);
			if (oldIt != null) {
				if (time.days != null && oldIt.days != null
						&& time.days != oldIt.days) {
					oldIt.days = time.days;
				}
				if (time.days != null && oldIt.days != null
						&& time.hours != oldIt.hours) {
					oldIt.hours = time.hours;
				}
				if (time.days != null && oldIt.days != null
						&& time.startHour != oldIt.startHour) {
					oldIt.startHour = time.startHour;
				}
			} else {
				settingTimes.add(time);
			}
		}

		changeSupport.firePropertyChange(SETTING_TIME_CHANGED, old,
				settingTimes);
	}

	@XmlType
	@XmlAccessorType(XmlAccessType.FIELD)
	public static class SettingTime implements
			Comparable<SettingTime> {

		@XmlElement
		protected Day day;

		@XmlElement
		protected Integer days;
		@XmlElement
		protected Integer hours;
		@XmlElement
		protected Integer startHour;

		public SettingTime() {
		}

		public SettingTime(Day day) {
			this.day = day;
		}

		public Day getDay() {
			return day;
		}

		public void setDays(Integer days) {
			this.days = days;
		}

		public Integer getDays() {
			return days;
		}

		public void setHours(Integer hours) {
			this.hours = hours;
		}

		public Integer getHours() {
			return hours;
		}

		public void setStartHour(Integer startHour) {
			this.startHour = startHour;
		}

		public Integer getStartHour() {
			return startHour;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((day == null) ? 0 : day.hashCode());
			result = prime * result + ((days == null) ? 0 : days.hashCode());
			result = prime * result + ((hours == null) ? 0 : hours.hashCode());
			result = prime * result
					+ ((startHour == null) ? 0 : startHour.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			InvestigationTime other = (InvestigationTime) obj;
			if (day != other.day)
				return false;
			if (days == null) {
				if (other.days != null)
					return false;
			} else if (!days.equals(other.days))
				return false;
			if (hours == null) {
				if (other.hours != null)
					return false;
			} else if (!hours.equals(other.hours))
				return false;
			if (startHour == null) {
				if (other.startHour != null)
					return false;
			} else if (!startHour.equals(other.startHour))
				return false;
			return true;
		}

		@Override
		public int compareTo(InvestigationTime o) {
			int result = 0;
			if (day.ordinal() < o.day.ordinal()) {
				result = -1;
			} else if (day.ordinal() > o.day.ordinal()) {
				result = 1;
			}
			return result;
		}

		private static final String SETTING_TIME_FORMAT = "%s / %s"; //$NON-NLS-1$

		@Override
		public String toString() {
			Integer days = this.days != null ? this.days != RetentionTimes.RESET_RETENTION_TIME ? this.days
					: 0
					: 0;
			Integer hours = this.hours != null ? this.hours != RetentionTimes.RESET_RETENTION_TIME ? this.hours
					: 0
					: 0;
			return String.format(SETTING_TIME_FORMAT, days, hours);
		}

		public SettingTime getClone() {
			SettingTime clone = new SettingTime(day);
			clone.days = days != null ? new Integer(days) : null;
			clone.hours = hours != null ? new Integer(hours) : null;
			clone.startHour = startHour != null ? new Integer(startHour) : null;
			return clone;
		}
	}
}
```

Ich sehe hier allerdings keinen Hinweis darauf, warum die Daten wieder nach dem Laden verschwinden.

Die einzige Möglichkeit - was ich aber fast nicht glauben kann/will - ist, dass vielleicht die Klasse, die die settings-Elemente beschreibt, irgendwo falsche oder JAXB irreführende Annotationen enthält.

Ich glaube nicht, das ihr mir hier mit einem direkten Fingerzeig helfen könnt, hoffe aber, dass der eine oder andere vielleicht mir eine Richtung zeigen kann.
Und: Nein, EMF steht hier nicht zur Wahl...

Viele Grüße,
Daniel


----------



## dzim (8. Apr 2011)

Noch ein kleiner Hinweis am Rande:
"SettingTime"-Objekte lassen sich sich an anderer Stelle problemlos Speichern und Laden! Das bestärkt mich in meinem Verdacht, dass es vielleicht am Übergeordnetem Typ "Settings" liegen könnte...


----------



## Gelöschtes Mitglied 5909 (9. Apr 2011)

Hast du mal den "klassichen" Ansatz probiert?
XML Schema schreiben -> mit xjc die pojos generieren lassen.
Das ist imo sowieso sinvoller


----------



## dzim (14. Apr 2011)

Ui - hab ich fast vergessen zu schließen!

Nach ewigen Suchen und Debugen ist der Übeltäter gefunden: In der zu dem Element "settings" gehörenden Klasse, wird ein einzelnes Objekt der Times-Klasse beim Anlegen des Settings-Objekts instantiiert.
Dieses Objekt war auf "final" gesetzt. Warum? Keine Ahnung! Das war nicht auf meinem Mist gewachsen...

Offenbar wurde also die XML geparst, die Times-Objekte erstellt, aber dann konnten sie nicht mit dem bereits existierenden und auf final gesetzten Objekt "gemerged" werden. So jedenfalls leite ich es mir her.

Fakt ist: Das Schlüsselwort "final" war von vornherein sinnlos - warum es da war ist mir und selbst dem Verursacher nicht so ganz klar (er konnte seine Intention selbst nicht mehr nachvollziehen).
Fakt ist aber auch: Ich hätte in dem Fall irgend ein Handling von JAXB erwartet. Eine Exception. Irgend etwas. Das finde ich auch nicht so sehr gelungen.


----------

