moxy json介绍
因此,这些天我正在为即将推出的Oracle云服务开发新的REST API,因此我需要做的事情之一就是能够为模型中的bean自动生成JSON模式。 我正在使用MOXy从POJO生成JSON,从EclipseLink 2.5.1版本开始,它现在具有从bean模型生成JSON模式的能力。
将来会有一个更加正式的解决方案集成到Jersey 2.x; 但是如果您想尝试一下,此解决方案目前就可以使用。
因此,我们需要设置的第一类是模型处理器,这是非常内部的Jersey类,它允许我们使用额外的方法和资源来修改资源模型。 我们可以向模型中的每个资源添加JsonSchemaHandler
,以完成生成新模式的艰苦工作。 由于这是一个简单的POC,因此这里没有缓存,如果要在生产代码中使用它,请注意这一点。
import com.google.common.collect.Lists;
import example.Bean;
import java.io.IOException;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import javax.inject.Inject;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.JAXBException;
import javax.xml.bind.SchemaOutputResolver;
import javax.xml.transform.Result;
import javax.xml.transform.stream.StreamResult;
import org.eclipse.persistence.jaxb.JAXBContext;
import org.glassfish.jersey.process.Inflector;
import org.glassfish.jersey.server.ExtendedUriInfo;
import org.glassfish.jersey.server.model.ModelProcessor;
import org.glassfish.jersey.server.model.ResourceMethod;
import org.glassfish.jersey.server.model.ResourceModel;
import org.glassfish.jersey.server.model.RuntimeResource;
import org.glassfish.jersey.server.model.internal.ModelProcessorUtil;
import org.glassfish.jersey.server.wadl.internal.WadlResource;
public class JsonSchemaModelProcessor implements ModelProcessor {
private static final MediaType JSON_SCHEMA_TYPE =
MediaType.valueOf("application/schema+json");
private final List<ModelProcessorUtil.Method> methodList;
public JsonSchemaModelProcessor() {
methodList = Lists.newArrayList();
methodList.add(new ModelProcessorUtil.Method("$schema", HttpMethod.GET,
MediaType.WILDCARD_TYPE, JSON_SCHEMA_TYPE,
JsonSchemaHandler.class));
}
@Override
public ResourceModel processResourceModel(ResourceModel resourceModel,
Configuration configuration) {
return ModelProcessorUtil.enhanceResourceModel(resourceModel, true, methodList,
true).build();
}
@Override
public ResourceModel processSubResource(ResourceModel resourceModel,
Configuration configuration) {
return ModelProcessorUtil.enhanceResourceModel(resourceModel, true, methodList,
true).build();
}
public static class JsonSchemaHandler
implements Inflector<ContainerRequestContext, Response> {
private final String lastModified = new SimpleDateFormat(WadlResource.HTTPDATEFORMAT).format(new Date());
@Inject
private ExtendedUriInfo extendedUriInfo;
@Override
public Response apply(ContainerRequestContext containerRequestContext) {
// Find the resource that we are decorating, then work out the
// return type on the first GET
List<RuntimeResource> ms = extendedUriInfo.getMatchedRuntimeResources();
List<ResourceMethod> rms = ms.get(1).getResourceMethods();
Class responseType = null;
found:
for (ResourceMethod rm : rms) {
if ("GET".equals(rm.getHttpMethod())) {
responseType = (Class) rm.getInvocable().getResponseType();
break found;
}
}
if (responseType == null) {
throw new WebApplicationException("Cannot resolve type for schema generation");
}
//
try {
JAXBContext context = (JAXBContext) JAXBContext.newInstance(responseType);
StringWriter sw = new StringWriter();
final StreamResult sr = new StreamResult(sw);
context.generateJsonSchema(new SchemaOutputResolver() {
@Override
public Result createOutput(String namespaceUri, String suggestedFileName)
throws IOException {
return sr;
}
}, responseType);
return Response.ok().type(JSON_SCHEMA_TYPE)
.header("Last-modified", lastModified)
.entity(sw.toString()).build();
} catch (JAXBException jaxb) {
throw new WebApplicationException(jaxb);
}
}
}
}
请注意, JsonSchemaHandler
代码中的非常简单的启发式方法假定每个资源都有1:1映射到单个JSON Schema元素。 当然,这可能不适用于您的特定应用程序。
现在,我们已经在一个已知的位置生成了架构,我们需要将其告知客户,我们要做的第一件事是确保当用户在特定资源上调用OPTIONS时,有一个合适的链接头:
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.UriInfo;
public class JsonSchemaResponseFilter implements ContainerResponseFilter {
@Context
private UriInfo uriInfo;
@Override
public void filter(ContainerRequestContext containerRequestContext,
ContainerResponseContext containerResponseContext) throws IOException {
String method = containerRequestContext.getMethod();
if ("OPTIONS".equals(method)) {
Link schemaUriLink =
Link.fromUriBuilder(uriInfo.getRequestUriBuilder()
.path("$schema")).rel("describedBy").build();
containerResponseContext.getHeaders().add("Link", schemaUriLink);
}
}
}
由于这是JAX-RS 2.x,因此我们正在与之合作,将所有内容捆绑为一个功能:
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
public class JsonSchemaFeature implements Feature {
@Override
public boolean configure(FeatureContext featureContext) {
if (!featureContext.getConfiguration().isRegistered(JsonSchemaModelProcessor.class)) {
featureContext.register(JsonSchemaModelProcessor.class);
featureContext.register(JsonSchemaResponseFilter.class);
return true;
}
return false;
}
}
我不会展示我的整个POJO类集。 但是很快,这就是模式生成代码所需的带有@GET方法的Resource类:
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/bean")
public class BeanResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public Bean getBean() {
return new Bean();
}
}
最后,如果对资源执行GET,将看到以下内容:
GET .../resources/bean
Content-Type: application/json
{
"message" : "hello",
"other" : {
"message" : "OtherBean"
},
"strings" : [
"one",
"two",
"three",
"four"
]
}
和选项:
OPTIONS .../resources/bean
Content-Type: text/plain
Link: <http://.../resources/bean/$schema>; rel="describedBy"
GET, OPTIONS, HEAD
最后,如果您解析模式资源:
GET .../resources/bean/$schema
Content-Type: application/schema+json
{
"$schema" : "http://json-schema.org/draft-04/schema#",
"title" : "example.Bean",
"type" : "object",
"properties" : {
"message" : {
"type" : "string"
},
"other" : {
"$ref" : "#/definitions/OtherBean"
},
"strings" : {
"type" : "array",
"items" : {
"type" : "string"
}
}
},
"additionalProperties" : false,
"definitions" : {
"OtherBean" : {
"type" : "object",
"properties" : {
"message" : {
"type" : "string"
}
},
"additionalProperties" : false
}
}
}
这里有很多工作要做,特别是根据我不久前转发到Jersey 2.xa的声明性链接注释生成超媒体扩展。 但这确实指向解决方案,我们可以运用各种解决方案使某些事情现在起作用。
moxy json介绍