#709: HibernateProxyXPathMarshaller.java

projectforge-business/src/main/java/org/projectforge/framework/persistence/xstream/HibernateProxyXPathMarshaller.java XStream marshaller — XPath-based reference marshaller that unwraps Hibernate proxies during entity graph traversal, projectforge-business/src/main/java/org/projectforge/framework/persistence/xstream/HibernateProxyXPathMarshaller.java 56 lines · 25 code · 25 comments · 6 blank
Extends XStream's ReferenceByXPathMarshaller to intercept the convertAnother() method — the central dispatch point where XStream traverses from one object to another during XML serialization of an entity graph. Before delegating to the parent class for XPath-based reference tracking, it unwraps any HibernateProxy to its real entity implementation. This ensures XStream's reference tracking operates on the real entity identity rather than proxy identity, preventing duplicate serialization of the same entity appearing through both direct and proxy references. This is a critical component for serializing JPA entity graphs that contain bidirectional associations with lazy loading.

Architecture

Class Hierarchy

HibernateProxyXPathMarshaller extends com.thoughtworks.xstream.core.ReferenceByXPathMarshaller, which itself extends AbstractReferenceMarshaller (which extends TreeMarshaller). The XStream marshalling hierarchy is:

TreeMarshaller
  └── AbstractReferenceMarshaller       (adds object reference tracking)
       ├── ReferenceByXPathMarshaller   (uses XPath expressions for references)
       │    └── HibernateProxyXPathMarshaller  (this class — adds proxy unwrapping)
       └── ReferenceByIdMarshaller      (uses ID/IDREF attributes for references)
            └── ProxyIdRefMarshaller    (#708 — alternative ID-based strategy)
Both HibernateProxyXPathMarshaller and ProxyIdRefMarshaller solve the same problem (proxy-aware marshalling) but use different XStream reference modes: XPath vs. ID/IDREF. The choice between them is made by XStreamMarshallingStrategy (uses XPath) and the ProxyIdRefMarshallingStrategy (uses ID/IDREF).

Constructor

Takes four parameters matching the ReferenceByXPathMarshaller constructor signature:

ParameterTypePurpose
writerHierarchicalStreamWriterXML output writer
converterLookupConverterLookupXStream converter registry
mapperMapperXStream class name mapper
modeintXPath reference mode (RELATIVE, ABSOLUTE, etc.)

convertAnother(Object item, Converter converter)

This is the only overridden method — the single point where proxy unwrapping is injected into the XPath marshalling flow:

  1. Proxy detection: Checks HibernateProxy.class.isAssignableFrom(item.getClass()). Uses isAssignableFrom rather than instanceof because item may be passed in a non-null context where the static type is broader.
  2. Unwrap: Calls ((HibernateProxy) item).getHibernateLazyInitializer().getImplementation() to get the real entity. This triggers lazy loading if the proxy hasn't been initialized.
  3. Delegate: Passes the unwrapped toConvert object to super.convertAnother(toConvert, converter), which handles XPath reference tracking and XML writing.

XPath Reference Mode

XStream's ReferenceByXPathMarshaller handles circular references by writing XPath expressions (e.g., <task reference="../../../task">) instead of serializing the same object twice. The mode parameter controls whether these XPath expressions are relative or absolute. Relative paths make the XML more resilient to structural changes and are generally preferred for document-oriented XML.

Integration Points

Design Decisions & Gotchas

Git History

868d6abb7 2025 -> 2026
63081666f Source file headers: 2024-> 2025.
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