#712: XStreamMarshallingStrategy.java

projectforge-business/src/main/java/org/projectforge/framework/persistence/xstream/XStreamMarshallingStrategy.java XStream marshalling strategy — factory that wires Hibernate proxy-aware XPath marshalling into XStream's tree serialization pipeline, projectforge-business/src/main/java/org/projectforge/framework/persistence/xstream/XStreamMarshallingStrategy.java 49 lines · 18 code · 25 comments · 6 blank
Extends XStream's ReferenceByXPathMarshallingStrategy to serve as the top-level plumbing that connects the Hibernate proxy-aware marshaller to XStream's serialization engine. This is the strategy class that XStream is configured to use — it overrides the factory method createMarshallingContext() to replace the default XPath marshaller with HibernateProxyXPathMarshaller, injecting Hibernate proxy awareness into every XStream serialization operation. This is the single configuration point that makes the entire XStream+Hibernate bridge layer operational. Without this strategy, none of the converter, mapper, or marshaller classes in this package would be activated during XStream serialization.

Architecture

Class Hierarchy

MarshallingStrategy (interface)
  └── AbstractTreeMarshallingStrategy
       ├── ReferenceByXPathMarshallingStrategy  (XPath-based circular reference handling)
       │    └── XStreamMarshallingStrategy       (this class — swaps in HibernateProxyXPathMarshaller)
       └── ReferenceByIdMarshallingStrategy      (ID/IDREF-based circular reference handling)
            └── ProxyIdRefMarshallingStrategy     (#709 — swaps in ProxyIdRefMarshaller)

Constructor

public XStreamMarshallingStrategy(int mode) {
    super(mode);
}

Accepts an XPath reference mode constant (RELATIVE, ABSOLUTE, SINGLE_NODE, etc.) and passes it to the superclass. The mode controls how XPath expressions are generated for back-references in circular entity graphs.

createMarshallingContext() — The Factory Method

@Override
protected TreeMarshaller createMarshallingContext(
    HierarchicalStreamWriter writer,
    ConverterLookup converterLookup,
    Mapper mapper)
{
    return new HibernateProxyXPathMarshaller(writer, converterLookup, mapper, RELATIVE);
}

This is the single overridden method — a textbook Template Method pattern. The superclass ReferenceByXPathMarshallingStrategy calls createMarshallingContext() whenever it needs a TreeMarshaller to serialize an object graph. Instead of returning the default ReferenceByXPathMarshaller, this override returns a HibernateProxyXPathMarshaller instance.

Hardcoded RELATIVE mode: Despite the constructor accepting a mode parameter, the createMarshallingContext() override hardcodes RELATIVE rather than using the instance's mode. This means the constructor parameter is effectively ignored for marshaller creation. If a different XPath mode were needed, this class would need modification or a subclass with a different override.

Integration Points

Strategy Comparison: XPath vs. ID/IDREF

XStreamMarshallingStrategy (this class)ProxyIdRefMarshallingStrategy
Reference mechanismXPath expressions (e.g., reference="../../task")ID/IDREF attributes (e.g., id="1", reference="1")
Depends on document structureYes — XPath paths break if XML structure changesNo — IDs are document-structure independent
Human readabilityLower — XPath paths are structural, not semanticHigher — sequential numeric IDs are easy to follow
Proxy unwrappingIn marshaller only (HibernateProxyXPathMarshaller)In marshaller (ProxyIdRefMarshaller) + CGLIB stripping
Spring CGLIB supportNoYes (strips Enhancer proxies)

How the Entire Bridge Layer Fits Together

When XStream serializes a JPA entity graph in ProjectForge, the following pipeline executes:

  1. XStreamMarshallingStrategy (this class) is invoked to create a marshalling context.
  2. It instantiates HibernateProxyXPathMarshaller as the tree marshaller.
  3. As the marshaller traverses the object graph, it calls convertAnother(), which unwraps HibernateProxy instances before delegating to XStream's reference tracking.
  4. When XStream needs to convert a specific class, HibernateMapper resolves the class name (substituting proxy names with real entity names, and Hibernate collection names with JDK collection names).
  5. When XStream encounters a PersistentCollection, HibernateCollectionConverter forces initialization and delegates to the appropriate JDK collection converter.
  6. When XStream encounters a HibernateProxy, HibernateProxyConverter unwraps it to the real entity before serialization.

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.
dd5ca38ac CopyRight of all java file-header updated or created.
9ebb88522 Initial commit