使用 jax-rs 从 REST 服务下载 xml 而无需本地存储文件

Download xml from REST service using jax-rs without locally storing the file

在一项服务中,我正在创建一个名为 doc 的 XML 文档,我希望用户收到下载该文档的提示,而不必将其保存在本地(就像说打开或保存文件)。

但是,我无法找到应该如何构建将要返回的响应,甚至无法找到@produce 的类型。

到目前为止我有这个:

@GET
@Path("/getXML")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public StreamingOutput getXML(
        @FormParam("id") int id) {
    UserDB userDao = new UserDB();
    entities.User userd = userDao.getById(id);

    DocumentBuilderFactory icFactory = DocumentBuilderFactory.newInstance();
    DocumentBuilder icBuilder;

    try {
        icBuilder = icFactory.newDocumentBuilder();
        Document doc = icBuilder.newDocument();

        Element rootElement = doc.createElement("Users");
        doc.appendChild(rootElement);

        rootElement.appendChild(getUser(doc, "1", "asd", "adas"));
        rootElement.appendChild(getUser(doc, "2", "bbb", "ccc"));

        //Here I should return the doc that is going to be downloaded
    }
    catch (Exception e) {
        e.printStackTrace();
    }

}

EDIT1:我的主要问题是我找不到如何构建将要返回的响应。我找到的答案下载了本地存储的现有文件。

最接近回答的话题是:How to make an XML document downloadable without intermediate file storage?

但我不明白如何将它应用于我的 REST 服务响应,这与 HttpServletResponse 不同。

如果您查看链接到的答案,您会看到使用了 StreamResult。在答案中,一个 StringWriter 被传递给构造函数,但是如果你查看 Javadoc,它有一个重载的构造函数,它也接受一个 OutputStream。因此,如果您 returning a StreamingOutput,只需将 StreamingOutput#write(OutputStream) 方法中的 OutputStream 传递给 StreamResult 构造函数。答案中的其他所有内容都应该相同。

return new StreamingOutput() {
    @Override
    public void write(OutputStream out)
            throws IOException, WebApplicationException {
        try {
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            StreamResult result = new StreamResult(out);
            DOMSource source = new DOMSource(doc);
            transformer.transform(source, result);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
};

这是我用来测试的完整资源class。请注意,我使用 @Produces(MediaType.APPLICATION_XML)。如果数据为 XML1.

,则设置为 application/octet-stream 毫无意义
@Path("dom")
public class DomXmlResource {

    @GET
    @Produces(MediaType.APPLICATION_XML)
    public StreamingOutput getXml() throws Exception {

        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = docFactory.newDocumentBuilder();

        Document doc = docBuilder.newDocument();
        Element rootElement = doc.createElement("company");
        doc.appendChild(rootElement);

        Element staff = doc.createElement("Staff");
        rootElement.appendChild(staff);

        staff.setAttribute("id", "1");

        Element firstname = doc.createElement("firstname");
        firstname.appendChild(doc.createTextNode("yong"));
        staff.appendChild(firstname);

        return new StreamingOutput() {
            @Override
            public void write(OutputStream out)
                    throws IOException, WebApplicationException {
                try {
                    Transformer transformer = TransformerFactory.newInstance()
                            .newTransformer();
                    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
                    StreamResult result = new StreamResult(out);
                    DOMSource source = new DOMSource(doc);
                    transformer.transform(source, result);
                    out.flush();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
    }
}

更新

要自动下载文件(而不是显示 XML 结果),我们实际上需要添加 Content-Disposition header 和 attachment 值。为此,我们应该 return Response,而不是从方法中 returning StreamingOutput,实体将是 StreamingOutput

@Path("dom")
public class DomXmlResource {

    @GET
    @Produces(MediaType.APPLICATION_XML)
    public Response getXml() throws Exception {
        ...
        StreamingOutput entity = new StreamingOutput() {
            @Override
            public void write(OutputStream out)
                    throws IOException, WebApplicationException {
                ...
            }
        };
        return Response.ok(entity)
                .header(HttpHeaders.CONTENT_DISPOSITION, 
                        "attachment;filename=file.xml")
                .build();
    }
}

更新 2

如果您还不知道,您可以简单地 return 您的 POJO(或它们的列表),它们将自动序列化为 XML。您不需要手动使用 DOM classes 来创建 XML 结构。已经有 Entity Providers 将为我们处理从 POJO 到 XML 的转换。比如我们有如下POJO(需要注解为@XmlRootElement

@XmlRootElement
public class User {
    private String name;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

那么我们就可以return它,它会自动序列化为

<user><name>footer</name></user>

这是一个例子

@Path("pojo")
public class PojoXmlResource {

    @GET
    @Produces("application/xml")
    public Response getXml() {
        User user = new User();
        user.setName("Jane Doe");

        return Response.ok(user)
                .header(HttpHeaders.CONTENT_DISPOSITION,
                        "attachment;filename=user.xml")
                .build();
    }
}

不那么乱了,不是吗?如果你想 return 用户列表,那么你需要将它包装在 GenericEntity

List<User> users = Arrays.asList(user1, user2, user3);
GenericEntity<List<User>> entity = new GenericEntity<List<User>>(users){};
return Response.ok(entity)
        ...
        .build();

1.参见:Do I need Content-Type: application/octet-stream for file download?