#704: HibernateCollectionConverter.java

projectforge-business/src/main/java/org/projectforge/framework/persistence/xstream/HibernateCollectionConverter.java XStream converter — converts Hibernate persistent collections to JDK collections during marshalling, projectforge-business/src/main/java/org/projectforge/framework/persistence/xstream/HibernateCollectionConverter.java 130 lines · 69 code · 45 comments · 16 blank
Implements com.thoughtworks.xstream.converters.Converter to intercept every PersistentCollection instance before XStream serializes it. Forces initialization of lazy-loaded collections, extracts the underlying java.util collection (dealing with Hibernate's peculiar internal representation of sets as maps), and delegates the actual XML writing to the appropriate standard XStream converter. This is the value-level converter — the HibernateCollectionsMapper handles name-level mapping, and this converter handles the actual collection data.

Architecture

Converter Registration

XStream converters form a chain. When XStream encounters an object to serialize, it iterates registered converters calling canConvert(Class). HibernateCollectionConverter claims any class assignable to org.hibernate.collection.spi.PersistentCollection — this covers PersistentBag, PersistentList, PersistentSet, PersistentSortedSet, PersistentMap, and PersistentSortedMap. Because XStream checks converters in registration order, this converter must be registered before default converters. This is done by HibernateMapper when configuring XStream instances.

Constructor: Converter Caching

The constructor accepts a ConverterLookup and immediately resolves and caches five converter references:

FieldConverted TypePurpose
listSetConverterArrayList.classHandles List and Set output
mapConverterHashMap.classHandles Map output
treeMapConverterTreeMap.classHandles sorted map output
treeSetConverterTreeSet.classHandles sorted set output
defaultConverterObject.classFallback for unrecognized types

marshal() Algorithm

  1. Cast to PersistentCollection: Checks if source instanceof PersistentCollection. If so, calls forceInitialization() to trigger lazy loading, then retrieves getStoredSnapshot() — the underlying JDK collection that Hibernate uses internally. (Note: a TODO comment references getCollectionSnapshot() as a potential alternative.)
  2. PersistentSortedSet workaround: Hibernate stores sorted sets internally as a HashMap where keys are the set elements. The converter extracts .values() and wraps them in a new TreeSet to restore proper set semantics.
  3. PersistentSet workaround: Same problem — stored as HashMap internally. Extracts .values() and wraps in a new HashSet.
  4. Converter dispatch: Tests the final collection class against the cached converters in order: listSetConverter (for ArrayList/HashSet/TreeSet), mapConverter, treeMapConverter, treeSetConverter. Falls back to defaultConverter.

unmarshal() — Not Implemented

unmarshal() returns null. This converter is unidirectional — it only handles serialization (Java→XML). Deserialization (XML→Java) produces standard JDK collections, which are then assigned to entity fields. Hibernate replaces them with persistent wrappers when the entity is later attached to a session.

Integration Points

Design Decisions & Gotchas

Git History

868d6abb7 2025 -> 2026
63081666f Source file headers: 2024-> 2025.
c1d14ecdb Migration stuff in progress...
b6092df09 Copyright 2023 -> 2024
ab45d51fa Copyright 2001-2022 -> 2001-2023.
5f7ef41b8 Copyright 2021 -> 2022
ceb63e8a1 Source code header: (C) 2001-2021.
7c79f1922 Copyright of source header -> 2020.
32f634b88 Optimize imports
dd5ca38ac CopyRight of all java file-header updated or created.
9ebb88522 Initial commit