Callback interfaces for integrating with JAXB marshalling and unmarshalling lifecycle events, enabling custom processing during XML binding operations. These interfaces allow JAXB-generated classes to execute custom logic at specific points in the marshalling/unmarshalling process.
Interfaces for executing custom logic before and after marshalling operations.
interface BeforeMarshallCallback {
void beforeMarshall(Marshaller marshaller);
}
interface AfterMarshallCallback {
void afterMarshall(Marshaller marshaller);
}Interfaces for executing custom logic before and after unmarshalling operations.
interface BeforeUnmarshallCallback {
void beforeUnmarshal(Unmarshaller unmarshaller, Object parent);
}
interface AfterUnmarshallCallback {
void afterUnmarshal(Unmarshaller unmarshaller, Object parent);
}Interface for classes that need to be aware of their JAXB context path.
interface ContextPathAware {
String getContextPath();
}Custom adapters for common data transformations during marshalling/unmarshalling.
class CommaDelimitedStringAdapter extends XmlAdapter<String, List<String>> {
public String marshal(List<String> value) throws Exception;
public List<String> unmarshal(String text) throws Exception;
}import org.jvnet.jaxb2_commons.xml.bind.*;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
@XmlRootElement
public class Customer implements BeforeMarshallCallback, AfterMarshallCallback,
BeforeUnmarshallCallback, AfterUnmarshallCallback {
private String name;
private String email;
private Date lastModified;
@Override
public void beforeMarshall(Marshaller marshaller) {
System.out.println("About to marshal Customer: " + name);
// Update last modified timestamp before marshalling
this.lastModified = new Date();
}
@Override
public void afterMarshall(Marshaller marshaller) {
System.out.println("Successfully marshalled Customer: " + name);
// Perform cleanup or logging after marshalling
}
@Override
public void beforeUnmarshal(Unmarshaller unmarshaller, Object parent) {
System.out.println("About to unmarshal Customer with parent: " + parent);
// Initialize default values or prepare for unmarshalling
}
@Override
public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
System.out.println("Successfully unmarshalled Customer: " + name);
// Perform post-processing, validation, or setup
if (this.email != null) {
this.email = this.email.toLowerCase().trim();
}
}
// Standard getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Date getLastModified() { return lastModified; }
public void setLastModified(Date lastModified) { this.lastModified = lastModified; }
}import org.jvnet.jaxb2_commons.xml.bind.ContextPathAware;
import javax.xml.bind.JAXBContext;
@XmlRootElement
public class ConfigurableEntity implements ContextPathAware {
private String contextPath;
@Override
public String getContextPath() {
if (contextPath == null) {
// Determine context path based on package or configuration
contextPath = this.getClass().getPackage().getName();
}
return contextPath;
}
public void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
// Use context path for dynamic behavior
public JAXBContext createContext() throws JAXBException {
return JAXBContext.newInstance(getContextPath());
}
}import org.jvnet.jaxb2_commons.xml.bind.*;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.util.concurrent.atomic.AtomicLong;
@XmlRootElement
public class AuditableEntity implements BeforeMarshallCallback, AfterUnmarshallCallback {
private static final AtomicLong VERSION_COUNTER = new AtomicLong(0);
private String id;
private Long version;
private Date createdDate;
private Date modifiedDate;
private transient boolean isNew = true;
@Override
public void beforeMarshall(Marshaller marshaller) {
// Update modification timestamp and version before marshalling
this.modifiedDate = new Date();
if (this.version == null) {
this.version = VERSION_COUNTER.incrementAndGet();
}
// Set marshaller properties based on entity state
try {
if (isNew) {
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
}
} catch (Exception e) {
// Handle marshaller property setting errors
System.err.println("Could not set marshaller properties: " + e.getMessage());
}
}
@Override
public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
// Mark entity as existing (not new) after unmarshalling
this.isNew = false;
// Set creation date if not present
if (this.createdDate == null) {
this.createdDate = new Date();
}
// Perform parent relationship setup
if (parent instanceof EntityContainer) {
EntityContainer container = (EntityContainer) parent;
container.addEntity(this);
}
// Validate unmarshalled data
validateEntity();
}
private void validateEntity() {
if (id == null || id.trim().isEmpty()) {
throw new IllegalStateException("Entity ID cannot be null or empty");
}
if (version == null || version < 0) {
throw new IllegalStateException("Entity version must be non-negative");
}
}
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public Long getVersion() { return version; }
public void setVersion(Long version) { this.version = version; }
public Date getCreatedDate() { return createdDate; }
public void setCreatedDate(Date createdDate) { this.createdDate = createdDate; }
public Date getModifiedDate() { return modifiedDate; }
public void setModifiedDate(Date modifiedDate) { this.modifiedDate = modifiedDate; }
public boolean isNew() { return isNew; }
}import org.jvnet.jaxb2_commons.xml.bind.annotation.adapters.CommaDelimitedStringAdapter;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.util.List;
import java.util.Arrays;
@XmlRootElement
public class ProductInfo {
@XmlJavaTypeAdapter(CommaDelimitedStringAdapter.class)
private List<String> tags;
@XmlJavaTypeAdapter(CommaDelimitedStringAdapter.class)
private List<String> categories;
// Constructor
public ProductInfo() {
this.tags = new ArrayList<>();
this.categories = new ArrayList<>();
}
// Getters and setters
public List<String> getTags() { return tags; }
public void setTags(List<String> tags) { this.tags = tags; }
public List<String> getCategories() { return categories; }
public void setCategories(List<String> categories) { this.categories = categories; }
}
// Usage example:
ProductInfo product = new ProductInfo();
product.setTags(Arrays.asList("electronics", "mobile", "smartphone"));
product.setCategories(Arrays.asList("phones", "accessories"));
// When marshalled, produces XML like:
// <productInfo>
// <tags>electronics,mobile,smartphone</tags>
// <categories>phones,accessories</categories>
// </productInfo>
// When unmarshalled, converts comma-delimited strings back to List<String>import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class LocalDateTimeAdapter extends XmlAdapter<String, LocalDateTime> {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
@Override
public LocalDateTime unmarshal(String value) throws Exception {
if (value == null || value.trim().isEmpty()) {
return null;
}
return LocalDateTime.parse(value, FORMATTER);
}
@Override
public String marshal(LocalDateTime value) throws Exception {
if (value == null) {
return null;
}
return value.format(FORMATTER);
}
}
// Usage in entity class
@XmlRootElement
public class Event implements AfterUnmarshallCallback {
@XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
private LocalDateTime eventDateTime;
@Override
public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
// Validate that event date is not in the past
if (eventDateTime != null && eventDateTime.isBefore(LocalDateTime.now())) {
System.out.println("Warning: Event date is in the past: " + eventDateTime);
}
}
public LocalDateTime getEventDateTime() { return eventDateTime; }
public void setEventDateTime(LocalDateTime eventDateTime) { this.eventDateTime = eventDateTime; }
}@XmlRootElement
public class OrderContainer implements AfterUnmarshallCallback {
@XmlElement(name = "order")
private List<Order> orders = new ArrayList<>();
@Override
public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
// Set up parent-child relationships after unmarshalling
for (Order order : orders) {
order.setContainer(this);
}
System.out.println("Unmarshalled " + orders.size() + " orders");
}
public List<Order> getOrders() { return orders; }
public void setOrders(List<Order> orders) { this.orders = orders; }
}
@XmlType
public class Order implements BeforeMarshallCallback, AfterUnmarshallCallback {
private String orderId;
private transient OrderContainer container;
@Override
public void beforeMarshall(Marshaller marshaller) {
System.out.println("Marshalling order: " + orderId);
}
@Override
public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
System.out.println("Unmarshalled order: " + orderId + " with parent: " + parent.getClass().getSimpleName());
// Parent relationship will be set by OrderContainer's afterUnmarshal
}
public String getOrderId() { return orderId; }
public void setOrderId(String orderId) { this.orderId = orderId; }
public OrderContainer getContainer() { return container; }
public void setContainer(OrderContainer container) { this.container = container; }
}The lifecycle callbacks integrate seamlessly with JAXB's standard marshalling and unmarshalling process:
beforeMarshall() → JAXB marshalling → afterMarshall()beforeUnmarshal() → JAXB unmarshalling → afterUnmarshal()marshal() during marshalling, unmarshal() during unmarshallingThese callbacks provide hooks for custom validation, data transformation, relationship setup, auditing, and other cross-cutting concerns that need to execute during XML binding operations.