如何在不改变模型的情况下向Jersey / Moxy / JAXB XML添加结构链接

时间:2014-06-26 05:43:09

标签: rest jaxb jersey moxy

我的意思是HATEOAS /超媒体API意义上的“结构链接”。更一般的问题是如何使用依赖于正在编组的实体以及环境(在这种情况下,至少是绝对URL)的数据来扩充生成的XML。

我使用Jersey 2.9和Moxy 2.5作为JAXB提供商。

从这个模型:

package testing;

import java.util.ArrayList;
import java.util.List;

public class Planet {

    private int id = 1;
    private String name = "test";
    private double radius = 3.0;
    private String href;

    private List<Moon> moons = new ArrayList<Moon>(0);

    public void addMoon(Moon moon) {
        moons.add(moon);
    }
}

...plus Moon class

我希望得到类似这样的XML(和等效的JSON):

<planet href="http://mytestserver/rest/planets/test">
    <name>test</name>
    <radius>3.0</radius>
    <moons>
        <moon href="http://mytestserver/rest/moons/moon1">
            <name>moon1</name>
        </moon>
        <moon href="http://mytestserver/rest/moons/moon2">
            <name>moon2</name>
        </moon>
    </moons>
</planet>

模型没有“href”字段,也不能添加。理想情况下,我可以使用UriBuilder直接从资源类中获取这些路径。

到目前为止,我已经提出了几个可能性。我可以请你考虑哪个(如果有的话)最多的话,然后你将如何解决这个方法的缺点?

1。使用AspectJ(或Javassist)扩充模型。

然后使用Jersey中现有的声明性链接机制,所有这些机制都依赖于模型中的字段来接收生成的链接。如果您在构建过程中没有AspectJ和/或对字节码操作等异乎寻常的技术犹豫不决,这显然是行不通的。

2。对生成的XML和JSON进行后处理

例如,在MessageBodyWriter:

ContextResolver<JAXBContext> resolver = providers.getContextResolver(JAXBContext.class, mediaType);
JAXBContext context = resolver.getContext(type);
Marshaller m = context.createMarshaller();
<--- here, marshall to e.g. a DOM then transform that
<--- then manipulate the JSON structures

我完全不知道如何做到这一点,因此缺乏代码。可能有其他方法可以挂钩XML生成过程,但据我所知,Jersey或JAXB的事件处理程序或拦截器实际上都不允许您操作生成的XML / JSON。

3。使用Moxy XMLTransformationMapping

例如:

XML binding:
<java-type name="Planet" xml-customizer="testing.HrefCustomizer">

Customizer:
public class HrefCustomizer implements DescriptorCustomizer {

    @Override
    public void customize(ClassDescriptor descriptor) throws Exception {
        XMLTransformationMapping xtm = new XMLTransformationMapping();
        xtm.addFieldTransformer("@href", new HrefWriter());

        descriptor.addMapping(xtm);

    }

}

Transformer:
public class HrefWriter implements FieldTransformer {

    @Override
    public Object buildFieldValue(Object instance, String fieldName,
            Session session) {
        return "href";  // constant value just for proof-of-concept
    }

    @Override
    public void initialize(AbstractTransformationMapping mapping) {
        // TODO Auto-generated method stub

    }

}

这种方法有两个问题:

  1. 很难找到任何关于它的文档,我想知道它是否实际上是不受支持的用法。
  2. 我无法看到变压器将如何使用UriBuilder。至少它需要其余服务的根URL。
  3. 4。略有不同的Moxy xml-transform方法

    如果我们决定在实例化时无法为变换器提供任何有意义的上下文,那么定制器就不会添加任何值,我们可以将上述内容简化为:

        <java-type name="Planet">
            <xml-root-element/>            
            <java-attributes>
                <xml-transformation java-attribute="name">
                    <xml-write-transformer transformer-class="testing.HrefWriter" xml-path="@href"/>
                </xml-transformation>
                <xml-element java-attribute="name"/>
    

    我们将变压器悬挂在另一个区域(在本例中为“名称”),这有点奇怪。

    5。 ?????

    或者,我正在咆哮错误的树。帮助!

1 个答案:

答案 0 :(得分:0)

AspectJ方法

概要

  • 使用AspectJ向模型类添加字段(在此示例中称为&#34; href&#34;)
  • 将Jersey @InjectLink注释添加到该字段
  • Jersey将使用资源类
  • 定义的正确URL填充该字段
  • 使用外部映射文件指定href字段的编组。

您还可以通过相同的AspectJ intertype declaration机制向其添加JAXB注释来指定href的编组。

示例代码

这些是信息量最大的比特。有关完整示例,请参阅http://lagod.id.au/blog/?p=494

方面

package testing;

import org.glassfish.jersey.linking.InjectLink;
import org.glassfish.jersey.linking.Binding;

public aspect HrefInjector {

    private String Planet.href;
    declare @field : * Planet.href : @InjectLink(
                                        resource=Services.class, 
                                        style=InjectLink.Style.ABSOLUTE
                                    ) ;

    private String Moon.href;
    declare @field : * Moon.href : @InjectLink(
                                        resource=Services.class,
                                        method="moon",
                                        bindings={@Binding(
                                                name="moonid", value="${instance.name}"
                                                )},
                                        style=InjectLink.Style.ABSOLUTE
                                    ) ;

}

模型类

没有特定于REST的POJO。请参阅Jersey + Moxy + JAXB - how to marshal XML without annotations

package testing;

import java.util.ArrayList;
import java.util.List;

public class Planet {

    private int id = 1;
    private String name = "test";
    private double radius = 3.0;

    private List<Moon> moons = new ArrayList<Moon>(0);

    public void addMoon(Moon moon) {
        moons.add(moon);
    }
}


package testing;

public class Moon {

    private String name;

    // No-arg constructor is a requirement of JAXB
    public Moon() {
    }

    public Moon(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

}

资源类

这是标准的JAX-RS资源类。出于演示目的,我们只是返回刚刚实例化的模型实例。

package testing;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/services")
@Produces({MediaType.APPLICATION_XML,MediaType.APPLICATION_JSON})
public class Services {

    private Planet initPlanet() {
        Planet p = new Planet();
        p.addMoon(new Moon("moon1"));
        p.addMoon(new Moon("moon2"));
        return p;
    }

    @GET
    public Planet planet () {
        return initPlanet();
    }

    @GET @Path("/moons/{moonid}")
    public Moon moon (@PathParam("moonid") String name) {
        return new Moon(name);
    }

}

Moxy映射文件

请注意,无论是否要实际封送href字段,都可以选择任何给定类型。实际上,通过使用多个映射文件,您可以在某些表示中包含href字段,而不在其他表示中。

<?xml version="1.0"?>
<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="testing"
    xml-mapping-metadata-complete="true"
    xml-accessor-type="NONE">
    <java-types>
        <java-type name="Planet">
            <xml-root-element/>
            <java-attributes>
                <xml-attribute java-attribute="href"/>
                <xml-element java-attribute="name"/>
                <xml-element java-attribute="radius"/>
                <xml-element java-attribute="moons" name="moon">
                    <xml-element-wrapper name="moons"/>
                </xml-element>    
            </java-attributes>
        </java-type>
        <java-type name="Moon">
            <xml-root-element/>
            <java-attributes>
                <xml-attribute java-attribute="href"/>
                <xml-element java-attribute="name"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

样本输出

钽DAH!结构链接自动从JAX-RS资源类派生而不改变模型源代码。因为我们正在使用Moxy,所以我们也免费获得JSON。

<planet href="http://localhost:8080/reststructlinks/rest/services">
    <name>test</name>
    <radius>3.0</radius>
    <moons>
        <moon href="http://localhost:8080/reststructlinks/rest/services/moons/moon1">
            <name>moon1</name>
        </moon>
        <moon href="http://localhost:8080/reststructlinks/rest/services/moons/moon2">
            <name>moon2</name>
        </moon>
    </moons>
</planet>