From ab63ee35d401358d793402591a3e7b9b9cbb1353 Mon Sep 17 00:00:00 2001 From: Tobias Preuss Date: Sun, 20 Dec 2020 00:16:35 +0100 Subject: [PATCH] Migrate from "id" to "guid". !!! ATTENTION !!! This change is NOT compatible to UPDATE exiting apps! + As of this commit the "id" XML attribute is no longer used as the unique token because its value cannot be guaranteed to be unique by the provider of the schedule XML. See https://github.com/voc/schedule/issues/63. + Instead the "guid" XML attribute is used which guarantees unique values. + To keep this migration to a minimum only the value of the Session#sessionId field is changed. The value of the new primary key (auto-incrementing, unique through the "guid" column) is written into the "sessionId" field when a Session is queried from the database. See SessionsDatabaseRepository. + By this the value of the "id" XML attribute becomes unused hence the deprecation of the SESSION_ID database column. --- .../dataconverters/SessionExtensions.kt | 3 ++ .../fahrplan/congress/models/Session.java | 8 ++- .../congress/repositories/AppRepository.kt | 6 +-- .../congress/serialization/ScheduleChanges.kt | 2 +- .../dataconverters/SessionExtensionsTest.kt | 3 ++ .../fahrplan/congress/models/SessionTest.kt | 1 + .../serialization/ScheduleChangesTest.kt | 7 ++- .../extensions/SessionExtensionsTest.kt | 2 + .../database/contract/FahrplanContract.java | 2 + .../database/extensions/SessionExtensions.kt | 2 + .../eventfahrplan/database/models/Session.kt | 1 + .../SessionsDatabaseRepository.kt | 50 +++++++++++-------- .../SessionsDBOpenHelper.java | 2 + .../eventfahrplan/network/models/Session.kt | 2 + .../network/serialization/FahrplanParser.java | 4 +- 15 files changed, 68 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/nerd/tuxmobil/fahrplan/congress/dataconverters/SessionExtensions.kt b/app/src/main/java/nerd/tuxmobil/fahrplan/congress/dataconverters/SessionExtensions.kt index 6d0943633d..6c052894d2 100644 --- a/app/src/main/java/nerd/tuxmobil/fahrplan/congress/dataconverters/SessionExtensions.kt +++ b/app/src/main/java/nerd/tuxmobil/fahrplan/congress/dataconverters/SessionExtensions.kt @@ -34,6 +34,7 @@ fun Session.toHighlightDatabaseModel() = HighlightDatabaseModel( fun Session.toSessionDatabaseModel() = SessionDatabaseModel( sessionId = sessionId, + guid = guid, abstractt = abstractt, date = date, dateUTC = dateUTC, @@ -75,6 +76,7 @@ fun Session.toSessionDatabaseModel() = SessionDatabaseModel( fun SessionDatabaseModel.toSessionAppModel(): Session { val session = Session(sessionId) + session.guid = guid session.abstractt = abstractt session.date = date @@ -119,6 +121,7 @@ fun SessionDatabaseModel.toSessionAppModel(): Session { fun SessionNetworkModel.toSessionAppModel(): Session { val session = Session(sessionId) + session.guid = guid session.abstractt = abstractt session.date = date diff --git a/app/src/main/java/nerd/tuxmobil/fahrplan/congress/models/Session.java b/app/src/main/java/nerd/tuxmobil/fahrplan/congress/models/Session.java index fac0b0c276..cef8609354 100644 --- a/app/src/main/java/nerd/tuxmobil/fahrplan/congress/models/Session.java +++ b/app/src/main/java/nerd/tuxmobil/fahrplan/congress/models/Session.java @@ -22,6 +22,9 @@ */ public class Session { + public String sessionId; + public String guid = ""; + public String title; public String subtitle; public String url; @@ -46,7 +49,6 @@ public class Session { public String speakers; public String track; - public String sessionId; public String type; public String lang; public String slug; @@ -80,6 +82,7 @@ public class Session { private static final boolean RECORDING_OPTOUT_OFF = false; public Session(String sessionId) { + guid = ""; title = ""; subtitle = ""; day = 0; @@ -119,6 +122,7 @@ public Session(String sessionId) { } public Session(@NonNull Session session) { + this.guid = session.guid; this.title = session.title; this.subtitle = session.subtitle; this.url = session.url; @@ -208,6 +212,7 @@ public boolean equals(Object o) { if (!ObjectsCompat.equals(date, session.date)) return false; if (!ObjectsCompat.equals(lang, session.lang)) return false; if (!sessionId.equals(session.sessionId)) return false; + if (!guid.equals(session.guid)) return false; if (!ObjectsCompat.equals(recordingLicense, session.recordingLicense)) return false; if (!ObjectsCompat.equals(room, session.room)) return false; if (!ObjectsCompat.equals(speakers, session.speakers)) return false; @@ -232,6 +237,7 @@ public int hashCode() { result = 31 * result + ObjectsCompat.hashCode(speakers); result = 31 * result + ObjectsCompat.hashCode(track); result = 31 * result + sessionId.hashCode(); + result = 31 * result + guid.hashCode(); result = 31 * result + ObjectsCompat.hashCode(type); result = 31 * result + ObjectsCompat.hashCode(lang); result = 31 * result + ObjectsCompat.hashCode(date); diff --git a/app/src/main/java/nerd/tuxmobil/fahrplan/congress/repositories/AppRepository.kt b/app/src/main/java/nerd/tuxmobil/fahrplan/congress/repositories/AppRepository.kt index cdeed383eb..158022ac2c 100644 --- a/app/src/main/java/nerd/tuxmobil/fahrplan/congress/repositories/AppRepository.kt +++ b/app/src/main/java/nerd/tuxmobil/fahrplan/congress/repositories/AppRepository.kt @@ -398,9 +398,9 @@ object AppRepository { private fun updateSessions(toBeUpdatedSessions: List, toBeDeletedSessions: List = emptyList()) { val toBeUpdatedSessionsDatabaseModel = toBeUpdatedSessions.toSessionsDatabaseModel() - val toBeUpdated = toBeUpdatedSessionsDatabaseModel.map { it.sessionId to it.toContentValues() } - val toBeDeleted = toBeDeletedSessions.map { it.sessionId } - sessionsDatabaseRepository.updateSessions(toBeUpdated, toBeDeleted) + val toBeUpdated = toBeUpdatedSessionsDatabaseModel.map { it.guid to it.toContentValues() } + val toBeDeleted = toBeDeletedSessions.map { it.guid } + sessionsDatabaseRepository.upsertSessions(toBeUpdated, toBeDeleted) } /** diff --git a/app/src/main/java/nerd/tuxmobil/fahrplan/congress/serialization/ScheduleChanges.kt b/app/src/main/java/nerd/tuxmobil/fahrplan/congress/serialization/ScheduleChanges.kt index c943be7e69..abd9d6792e 100644 --- a/app/src/main/java/nerd/tuxmobil/fahrplan/congress/serialization/ScheduleChanges.kt +++ b/app/src/main/java/nerd/tuxmobil/fahrplan/congress/serialization/ScheduleChanges.kt @@ -41,7 +41,7 @@ data class ScheduleChanges private constructor( var sessionIndex = 0 while (sessionIndex < newSessions.size) { val newSession = newSessions[sessionIndex] - val oldSession = oldNotCanceledSessions.singleOrNull { oldNotCanceledSession -> newSession.sessionId == oldNotCanceledSession.sessionId } + val oldSession = oldNotCanceledSessions.singleOrNull { oldNotCanceledSession -> newSession.guid == oldNotCanceledSession.guid } if (oldSession == null) { sessionsWithChangeFlags += SessionAppModel(newSession).apply { changedIsNew = true } foundChanges = true diff --git a/app/src/test/java/nerd/tuxmobil/fahrplan/congress/dataconverters/SessionExtensionsTest.kt b/app/src/test/java/nerd/tuxmobil/fahrplan/congress/dataconverters/SessionExtensionsTest.kt index c002482725..eb530ef2b0 100644 --- a/app/src/test/java/nerd/tuxmobil/fahrplan/congress/dataconverters/SessionExtensionsTest.kt +++ b/app/src/test/java/nerd/tuxmobil/fahrplan/congress/dataconverters/SessionExtensionsTest.kt @@ -18,6 +18,7 @@ class SessionExtensionsTest { fun sessionDatabaseModel_toSessionAppModel_toSessionDatabaseModel() { val session = SessionDatabaseModel( sessionId = "7331", + guid = "11111111-1111-1111-1111-111111111111", abstractt = "Lorem ipsum", dayIndex = 3, date = "2015-08-13", @@ -63,6 +64,7 @@ class SessionExtensionsTest { fun sessionNetworkModel_toSessionAppModel() { val sessionNetworkModel = SessionNetworkModel( sessionId = "7331", + guid = "11111111-1111-1111-1111-111111111111", abstractt = "Lorem ipsum", dayIndex = 3, date = "2015-08-13", @@ -102,6 +104,7 @@ class SessionExtensionsTest { changedTrack = true ) val sessionAppModel = SessionAppModel("7331").apply { + guid = "11111111-1111-1111-1111-111111111111" abstractt = "Lorem ipsum" day = 3 date = "2015-08-13" diff --git a/app/src/test/java/nerd/tuxmobil/fahrplan/congress/models/SessionTest.kt b/app/src/test/java/nerd/tuxmobil/fahrplan/congress/models/SessionTest.kt index 60e9b6261b..d92e05d076 100644 --- a/app/src/test/java/nerd/tuxmobil/fahrplan/congress/models/SessionTest.kt +++ b/app/src/test/java/nerd/tuxmobil/fahrplan/congress/models/SessionTest.kt @@ -10,6 +10,7 @@ class SessionTest { companion object { fun createSession() = Session("s1").apply { + guid = "11111111-1111-1111-1111-111111111111" title = "Lorem ipsum" subtitle = "Gravida arcu ac tortor" day = 3 diff --git a/app/src/test/java/nerd/tuxmobil/fahrplan/congress/serialization/ScheduleChangesTest.kt b/app/src/test/java/nerd/tuxmobil/fahrplan/congress/serialization/ScheduleChangesTest.kt index e0f0399aaf..342b179eaf 100644 --- a/app/src/test/java/nerd/tuxmobil/fahrplan/congress/serialization/ScheduleChangesTest.kt +++ b/app/src/test/java/nerd/tuxmobil/fahrplan/congress/serialization/ScheduleChangesTest.kt @@ -276,6 +276,11 @@ class ScheduleChangesTest { assertThat(scheduleChanges.foundChanges).isTrue() } - private fun createSession(sessionId: String = "1", block: Session.() -> Unit = {}) = Session(sessionId).apply(block) + private fun createSession(sessionId: String = "1", block: Session.() -> Unit = {}): Session { + return Session(sessionId).apply { + guid = sessionId // shortcut for testing purpose only! + block() + } + } } diff --git a/database/src/androidTest/java/info/metadude/android/eventfahrplan/database/extensions/SessionExtensionsTest.kt b/database/src/androidTest/java/info/metadude/android/eventfahrplan/database/extensions/SessionExtensionsTest.kt index 356e9717db..6834fe8342 100644 --- a/database/src/androidTest/java/info/metadude/android/eventfahrplan/database/extensions/SessionExtensionsTest.kt +++ b/database/src/androidTest/java/info/metadude/android/eventfahrplan/database/extensions/SessionExtensionsTest.kt @@ -14,6 +14,7 @@ class SessionExtensionsTest { fun toContentValues() { val session = Session( sessionId = "7331", + guid = "11111111-1111-1111-1111-111111111111", abstractt = "Lorem ipsum", dayIndex = 3, date = "2015-08-13", @@ -53,6 +54,7 @@ class SessionExtensionsTest { ) val values = session.toContentValues() assertThat(values.getAsInteger(SESSION_ID)).isEqualTo(7331) + assertThat(values.getAsString(GUID)).isEqualTo("11111111-1111-1111-1111-111111111111") assertThat(values.getAsString(ABSTRACT)).isEqualTo("Lorem ipsum") assertThat(values.getAsInteger(DAY)).isEqualTo(3) assertThat(values.getAsString(DATE)).isEqualTo("2015-08-13") diff --git a/database/src/main/java/info/metadude/android/eventfahrplan/database/contract/FahrplanContract.java b/database/src/main/java/info/metadude/android/eventfahrplan/database/contract/FahrplanContract.java index 441dbd630d..4e55a7cbe3 100644 --- a/database/src/main/java/info/metadude/android/eventfahrplan/database/contract/FahrplanContract.java +++ b/database/src/main/java/info/metadude/android/eventfahrplan/database/contract/FahrplanContract.java @@ -89,6 +89,7 @@ interface SessionsTable { interface Columns extends BaseColumns { + @Deprecated // Value is unused. Query primary key _ID column instead. /* 00 */ String SESSION_ID = "event_id"; // Keep column name to avoid database migration. /* 01 */ String TITLE = "title"; /* 02 */ String SUBTITLE = "subtitle"; @@ -124,6 +125,7 @@ interface Columns extends BaseColumns { /* 32 */ String SLUG = "slug"; /* 33 */ String URL = "url"; /* 34 */ String TIME_ZONE_OFFSET = "time_zone_offset"; + /* 35 */ String GUID = "guid"; } interface Defaults { diff --git a/database/src/main/java/info/metadude/android/eventfahrplan/database/extensions/SessionExtensions.kt b/database/src/main/java/info/metadude/android/eventfahrplan/database/extensions/SessionExtensions.kt index cc9e5bd41d..de49b68d53 100644 --- a/database/src/main/java/info/metadude/android/eventfahrplan/database/extensions/SessionExtensions.kt +++ b/database/src/main/java/info/metadude/android/eventfahrplan/database/extensions/SessionExtensions.kt @@ -21,6 +21,7 @@ import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.Se import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.DAY import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.DESCR import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.DURATION +import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.GUID import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.LANG import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.LINKS import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.REC_LICENSE @@ -44,6 +45,7 @@ import info.metadude.android.eventfahrplan.database.models.Session fun Session.toContentValues() = contentValuesOf( SESSION_ID to sessionId, + GUID to guid, ABSTRACT to abstractt, DAY to dayIndex, DATE to date, diff --git a/database/src/main/java/info/metadude/android/eventfahrplan/database/models/Session.kt b/database/src/main/java/info/metadude/android/eventfahrplan/database/models/Session.kt index 1f06107714..e5ae0b176b 100644 --- a/database/src/main/java/info/metadude/android/eventfahrplan/database/models/Session.kt +++ b/database/src/main/java/info/metadude/android/eventfahrplan/database/models/Session.kt @@ -6,6 +6,7 @@ package info.metadude.android.eventfahrplan.database.models data class Session( val sessionId: String, + val guid: String, val abstractt: String = "", val dayIndex: Int = 0, val date: String = "", diff --git a/database/src/main/java/info/metadude/android/eventfahrplan/database/repositories/SessionsDatabaseRepository.kt b/database/src/main/java/info/metadude/android/eventfahrplan/database/repositories/SessionsDatabaseRepository.kt index c70fb9527d..5c6599970f 100644 --- a/database/src/main/java/info/metadude/android/eventfahrplan/database/repositories/SessionsDatabaseRepository.kt +++ b/database/src/main/java/info/metadude/android/eventfahrplan/database/repositories/SessionsDatabaseRepository.kt @@ -26,6 +26,7 @@ import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.Se import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.DAY import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.DESCR import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.DURATION +import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.GUID import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.LANG import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.LINKS import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.REC_LICENSE @@ -33,7 +34,6 @@ import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.Se import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.REL_START import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.ROOM import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.ROOM_IDX -import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.SESSION_ID import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.SLUG import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.SPEAKERS import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.START @@ -43,6 +43,7 @@ import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.Se import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.TRACK import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.TYPE import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns.URL +import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Columns._ID import info.metadude.android.eventfahrplan.database.contract.FahrplanContract.SessionsTable.Values.REC_OPT_OUT_OFF import info.metadude.android.eventfahrplan.database.extensions.delete import info.metadude.android.eventfahrplan.database.extensions.getInt @@ -88,36 +89,39 @@ class SessionsDatabaseRepository( /** - * Updates or inserts sessions based on the given [contentValuesBySessionId]. - * Removes all sessions identified by their [session IDs][toBeDeletedSessionIds]. + * Updates or inserts sessions based on the given [contentValuesByGuid]. + * Removes all sessions identified by their [session GUIDs][toBeDeletedSessionGuids]. */ - fun updateSessions( - contentValuesBySessionId: List>, - toBeDeletedSessionIds: List + fun upsertSessions( + contentValuesByGuid: List>, + toBeDeletedSessionGuids: List ) = with(sqLiteOpenHelper) { writableDatabase.transaction { - contentValuesBySessionId.forEach { (sessionId, contentValues) -> - upsertSession(sessionId, contentValues) + contentValuesByGuid.forEach { (guid, contentValues) -> + upsertSession(guid, contentValues) } - toBeDeletedSessionIds.forEach { toBeDeletedSessionId -> - deleteSession(toBeDeletedSessionId) + toBeDeletedSessionGuids.forEach { toBeDeletedSessionGuid -> + deleteSession(toBeDeletedSessionGuid) } } } /** - * Updates a session with the given [contentValues]. A row is matched by its [sessionId]. + * Updates a session with the given [contentValues]. A row is matched by its [guid]. * If no row was affected by the update operation then an insert operation is performed * assuming that the session does not exist in the table. * + * The [guid] column is used for matching an existing row because this column holds unique values + * and to avoid an unneeded database SELECT just to retrieve the primary key of a row. + * * This function must be called in the context of a [transaction] block. */ - private fun SQLiteDatabase.upsertSession(sessionId: String, contentValues: ContentValues) { + private fun SQLiteDatabase.upsertSession(guid: String, contentValues: ContentValues) { val affectedRowsCount = updateRow( tableName = SessionsTable.NAME, contentValues = contentValues, - columnName = SESSION_ID, - columnValue = sessionId + columnName = GUID, + columnValue = guid ) if (affectedRowsCount == 0) { insert( @@ -128,19 +132,21 @@ class SessionsDatabaseRepository( } /** - * Delete the session identified by the given [sessionId] from the table. + * Delete the session identified by the given [sessionGuid] from the table. */ - private fun SQLiteDatabase.deleteSession(sessionId: String) = delete( + private fun SQLiteDatabase.deleteSession(sessionGuid: String) = delete( tableName = SessionsTable.NAME, - columnName = SESSION_ID, - columnValue = sessionId + columnName = GUID, + columnValue = sessionGuid ) fun querySessionBySessionId(sessionId: String): Session { return try { query { read(SessionsTable.NAME, - selection = "$SESSION_ID=?", + // The value of "sessionId" is replaced with the value of the "_id" + // column when the session is queried initially to guarantee a unique value. + selection = "$_ID=?", selectionArgs = arrayOf(sessionId)) }.first() } catch (e: NoSuchElementException) { @@ -192,7 +198,11 @@ class SessionsDatabaseRepository( Session.RECORDING_OPT_OUT_ON Session( - sessionId = cursor.getString(SESSION_ID), + // The value of the auto-incrementing primary key "_id" column is written as + // the value of the "sessionId" field as the first step to rely on the "guid" + // column. It has a unique value which is not guaranteed by "sessionId". + sessionId = "${cursor.getLong(_ID)}", + guid = cursor.getString(GUID), abstractt = cursor.getString(ABSTRACT), date = cursor.getString(DATE), dateUTC = cursor.getLong(DATE_UTC), diff --git a/database/src/main/java/info/metadude/android/eventfahrplan/database/sqliteopenhelper/SessionsDBOpenHelper.java b/database/src/main/java/info/metadude/android/eventfahrplan/database/sqliteopenhelper/SessionsDBOpenHelper.java index 5570f48c3b..e885aa388d 100644 --- a/database/src/main/java/info/metadude/android/eventfahrplan/database/sqliteopenhelper/SessionsDBOpenHelper.java +++ b/database/src/main/java/info/metadude/android/eventfahrplan/database/sqliteopenhelper/SessionsDBOpenHelper.java @@ -21,7 +21,9 @@ public class SessionsDBOpenHelper extends SQLiteOpenHelper { private static final String SESSIONS_TABLE_CREATE = "CREATE TABLE " + SessionsTable.NAME + " (" + + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + Columns.SESSION_ID + " TEXT, " + + Columns.GUID + " TEXT NOT NULL UNIQUE, " + Columns.TITLE + " TEXT, " + Columns.SUBTITLE + " TEXT, " + Columns.DAY + " INTEGER, " + diff --git a/network/src/main/java/info/metadude/android/eventfahrplan/network/models/Session.kt b/network/src/main/java/info/metadude/android/eventfahrplan/network/models/Session.kt index 1fd467fc1f..cc18d77984 100644 --- a/network/src/main/java/info/metadude/android/eventfahrplan/network/models/Session.kt +++ b/network/src/main/java/info/metadude/android/eventfahrplan/network/models/Session.kt @@ -9,6 +9,8 @@ import info.metadude.android.eventfahrplan.network.serialization.FahrplanParser data class Session( var sessionId: String = "", + var guid: String = "", + var abstractt: String = "", var dayIndex: Int = 0, var date: String = "", diff --git a/network/src/main/java/info/metadude/android/eventfahrplan/network/serialization/FahrplanParser.java b/network/src/main/java/info/metadude/android/eventfahrplan/network/serialization/FahrplanParser.java index 59b3a921c8..a5d2ea627d 100644 --- a/network/src/main/java/info/metadude/android/eventfahrplan/network/serialization/FahrplanParser.java +++ b/network/src/main/java/info/metadude/android/eventfahrplan/network/serialization/FahrplanParser.java @@ -13,8 +13,8 @@ import java.util.List; import info.metadude.android.eventfahrplan.commons.logging.Logging; -import info.metadude.android.eventfahrplan.network.models.Session; import info.metadude.android.eventfahrplan.network.models.Meta; +import info.metadude.android.eventfahrplan.network.models.Session; import info.metadude.android.eventfahrplan.network.serialization.exceptions.MissingXmlAttributeException; import info.metadude.android.eventfahrplan.network.temporal.DateParser; import info.metadude.android.eventfahrplan.network.validation.DateFieldValidation; @@ -173,8 +173,10 @@ private Boolean parseFahrplan(String fahrplan, String eTag) { } if (name.equalsIgnoreCase("event")) { String id = parser.getAttributeValue(null, "id"); + String guid = parser.getAttributeValue(null, "guid"); Session session = new Session(); session.setSessionId(id); + session.setGuid(guid); session.setDayIndex(day); session.setRoom(room); session.setDate(date);