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

时间:2018-08-10 16:55:42

标签: java xml rest download jax-rs

在一项服务中,我正在创建一个名为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?

但是我不明白如何将其应用于与HttpServletResponse不同的REST服务响应上。

1 个答案:

答案 0 :(得分:2)

如果查看链接到的答案,则会看到使用了StreamResult。答案是将StringWriter传递给构造函数,但是如果您查看Javadoc,则它具有一个重载的构造函数,该构造函数也需要一个OutputStream。因此,如果要返回StreamingOutput,只需将OutputStream方法中的StreamingOutput#write(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();
        }
    }
};

这是我用来测试的完整资源类。请注意,我使用@Produces(MediaType.APPLICATION_XML)。如果数据是XML 1 ,则设置为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头中添加attachment值。为此,我们应该返回StreamingOutput,而不是从方法中返回Response,其中实体将是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

如果您还不知道,则只需返回POJO(或它们的列表),它们就会自动序列化为XML。您无需手动使用DOM类来创建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;
    }
}

然后我们可以将其返回,它将自动序列化为

<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();
    }
}

少了很多麻烦,不是吗?如果要返回用户列表,则需要将其包装在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?