Flexible marshalling with JAXB

2019-02-26 23:02发布

I'm hoping to have a flexible way of marshalling objects. A verbose version for single objects and a less-verbose version for multiple object versions.

For example, consider my department model:

GET /locations/1:

  <location id='1'>
    <link rel="self" href="http://example.com/locations/1"/>
    <link rel="parent" href="http://example.com/serviceareas/1"/>
    <name>location 01</name>
    <departments>
      <department id='1'>
        <link rel="self" href="http://example.com/departments/1"/>
        <name>department 01</name>
      </department>
      <department id='2'>
        <link rel="self" href="http://example.com/departments/2"/>
        <name>department 02</name>
      </department>
      <department id='3'>
        <link rel="self" href="http://example.com/departments/3"/>
        <name>department 03</name>
      </department>
    </departments>
  </location>

GET /department/1:

<department id='1'>
  <link rel="self" href="http://example.com/departments/1"/>
  <link rel="parent" href="http://example.com/locations/1"/>
  <name>department 01</name>
  <abbr>dept 01</abbr>
  ....
  <specialty>critical care</specialty>
</department>

Is there a way to do this? Would I need to have separate entity objects? One that references the table for CRUD operations and another for lists?

2条回答
The star\"
2楼-- · 2019-02-26 23:23

Note: I'm the EclipseLink JAXB (MOXy) lead, and a member of the JAXB 2 (JSR-222) expert group.

Your question is tagged EclipseLink, if you are using EclipseLink JAXB (MOXy) you can take advantage of the external binding document to apply a second mapping to the Department class.


ContextResolver

In a JAX-RS environment you can leverage MOXy's external binding document through a ContextResolver:

import java.io.*;
import java.util.*;     
import javax.ws.rs.Produces;
import javax.ws.rs.ext.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextFactory;

@Provider
@Produces({"application/xml", "application/json"})
public class DepartmentContextResolver implements ContextResolver<JAXBContext> {

    private JAXBContext jc;

    public DepartmentContextResolver() {
        try {
            Map<String, Object> props = new HashMap<String, Object>(1);
            props.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "example/bindings.xml");
            jc = JAXBContext.newInstance(new Class[] {Department.class} , props);
        } catch(JAXBException e) {
            throw new RuntimeException(e);
        } 
    }

    public JAXBContext getContext(Class<?> clazz) {
        if(Department.class == clazz) {
            return jc;
        }
        return null;
    }


} 

For More Information


External Binding Document

By default MOXy's external binding document is used to augment the annotated model, but if you set the xml-mapping-metadata-complete flag it will completely override the annotations allowing you to apply a completely different mapping:

<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="example"
    xml-mapping-metadata-complete="true">
    ...
</xml-bindings>

For More Information

UPDATE

This update is to address a number of questions you asked in one of your comments:

1 . Should/can each ContentResolver have its own binding file?

Yes each ContextResolver should have its own binding file. The main reason for introducing a new ContextResolver is to represent a secondary mapping.

2 . Can I have more than one for each ContentResolver (this would give me a number of renderings of the same class, creating a 'view' of sorts), perhaps specifying its location in the constructor?

For a single ContextResolver you can express the metadata across multiple binding files, but they will be combined into a single set of mappings. This means that a single ContentResolver cannot have multiple views of a single class. A separate ContextResolver is used to represent a secondary mapping.

3 . Where should the binding files reside?

I recommend loading the metadata file from the class path.

4 . I can see how the ContentResolver could be easily specified in a Resource's GET method, but how would this be done if the object is embedded in another (JPA) object? In the embedded object's getter/setter?

Your JAX-RS implementation should pick up your ContextResolver because it is annotated with @Provider. The ContextResolver used for a class will depend on how you implement the getContext method:

public JAXBContext getContext(Class<?> clazz) {
    if(Customer.class == clazz) {
        return jc;
    }
    return null;
}
查看更多
祖国的老花朵
3楼-- · 2019-02-26 23:37

Here comes another idea. Could be a bad idea but somewhat easy.

class Department {

    @XmlElement(required = true)
    public Link getSelf() {
        return self;
    }

    @XmlElement(required = false) // default
    public Link getParent() {
        if (verbose) {
            return parent;
        }
        return null;
    }

    @XmlElement(required = false) // default
    public String getSpecialty() {
        if (verbose) {
            return specialty;
        }
        return null;
    }

    @XmlTransient
    private boolean verbose;
}
查看更多
登录 后发表回答