Java JAX-RS Web服务:在线程完成时向JAXB XML结果添加节点

时间:2011-07-24 15:58:23

标签: java xml ajax web-services http

我已经使用Jersey编写了一个JAX-RS Web服务,用于查询来自不同网站的价格,并通过JAXB注释类将结果作为XML返回。不幸的是,有些网站需要15秒才能回复,因此我使用多个线程来查询这些价格。

我想现在为这个网络服务编写一个客户端,我的网络用户在点击“搜索”结果后不想等待30秒,所以我的想法是动态更新结果表,因为结果来自我的JAX-RS网络服务回来了。

30秒后,我的网络服务应该超时并关闭<result> - 元素或完成所有线程后。

现在我的webservice运行所有线程并在所有trheads完成后返回结果,我想动态地将结果添加到XML输出中,我该如何实现呢? < / p>

XML响应的结构是:

<result>
  <articles>
    <article>
    content of article
    </article>
  </articles>
  As the webservice gets results from websites it adds new articles to the XML
</result>

RequestController.java

@Path("/request")
public class RequestController {

    @GET
    @Produces("application/xml")
    public Response getRequest(@QueryParam("part") String part) {
        response = new Response();
        driverController = new DriverController(this.response, this.part);
        this.response = driverController.query();
        return this.response;
    }
}

DriverController.java

public class DriverController {


    public Response query() {
        CompletionService<Deque<Article>> completionService = new ExecutorCompletionService<Deque<Article>>(
                Worker.getThreadPool());
        final Deque<Article> articleQueue = new LinkedList<Article>();

        int submittedTasks = 0;

        // This threadwill take about 4 seconds to finish
        Driver driverA = new DriverA(this.part,
                this.currency, this.language);

        // This thread will take about 15 seconds to finish
        Driver driverN = new DriverN(this.part,
                this.currency, this.language);

        completionService.submit(driverA);
        submittedTasks++;
        completionService.submit(driverN);
        submittedTasks++;

        for (int i = 0; i < submittedTasks; i++) {
            log.info("Tasks: " + submittedTasks);
            try {
                Future<Deque<Article>> completedFuture = completionService.take();
                try {
                    Deque<Article> articleQueueFromThread = completedFuture.get();
                    if (articleQueueFromThread != null) {
                        articleQueue.addAll(articleQueueFromThread);
                        response.setStatus("OK");
                    }
                } catch (ExecutionException e) {
                    log.error(e.getMessage());
                    e.printStackTrace();
                }
            } catch (InterruptedException e) {
                log.error(e.getMessage());
                e.printStackTrace();
            }
        }
        for (Article article : articleQueue) {
            this.response.addArticle(article);
        }
        return this.response;
    }
}

Response.java

@XmlRootElement
public class Response {

    Queue<Article> queue = new ConcurrentLinkedQueue<Article>();
    private String status;
    private String code;
    private String message;
    private List<Article> articles = new ArrayList<Article>();

    public Response(){

    }

    public void setMessage(String message) {
        this.message = message;
    }
    @XmlAttribute
    public String getMessage() {
        return message;
    }
    public void setStatus(String status) {
        this.status = status;
    }
    @XmlAttribute
    public String getStatus() {
        return status;
    }
    public void setCode(String code) {
        this.code = code;
    }
    @XmlAttribute
    public String getCode() {
        return code;
    }

    public void addArticle(Article article) {
        this.articles.add(article);
        System.out.println("Response: ADDED ARTICLE TO RESPONSE");
    }
    @XmlElement(name = "article")
    @XmlElementWrapper(name = "articles")
    public List<Article> getArticles() {
        return articles;
    }

}

2 个答案:

答案 0 :(得分:2)

我开始调整你的代码来完成它,但我认为更容易编写一个独立的例子。该示例启动了一个Grizzly + Jersey服务器,其中包含一个资源类。资源上的GET会产生三个线程,在返回某些对象之前会延迟2,4和6秒。服务器启动后,另一个线程向服务器发出请求。当您运行它时,您可以清楚地看到请求者在各个线程完成其在服务器中的工作时接收到大量的XML。它没有做的一件事是在单个根元素中包装单独交付的XML块,因为这应该是相对微不足道的。

整个可执行文件源在下面,如果你有maven和git,你可以从github克隆它并运行它:

git clone git://github.com/zzantozz/testbed.git tmp
cd tmp
mvn compile exec:java -Dexec.mainClass=rds.jersey.JaxRsResource -pl jersey-with-streaming-xml-response

来源:

import com.sun.grizzly.http.SelectorThread;
import com.sun.jersey.api.container.grizzly.GrizzlyWebContainerFactory;
import javax.ws.rs.*;
import javax.ws.rs.core.StreamingOutput;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;

@Path("/streaming")
public class JaxRsResource {
    private static ExecutorService executorService = Executors.newFixedThreadPool(4);
    private static int fooCounter;
    private Marshaller marshaller;

    public JaxRsResource() throws JAXBException {
        marshaller = JAXBContext.newInstance(Foo.class).createMarshaller();
        marshaller.setProperty("jaxb.fragment", Boolean.TRUE);
    }

    @GET
    @Produces("application/xml")
    public StreamingOutput streamStuff() {
        System.out.println("Got request for streaming resource; starting delayed response threads");
        final List<Future<List<Foo>>> futureFoos = new ArrayList<Future<List<Foo>>>();
        futureFoos.add(executorService.submit(new DelayedFoos(2)));
        futureFoos.add(executorService.submit(new DelayedFoos(4)));
        futureFoos.add(executorService.submit(new DelayedFoos(6)));
        return new StreamingOutput() {
            public void write(OutputStream output) throws IOException {
                for (Future<List<Foo>> futureFoo : futureFoos) {
                    writePartialOutput(futureFoo, output);
                    output.write("\n".getBytes());
                    output.flush();
                }
            }
        };
    }

    private void writePartialOutput(Future<List<Foo>> futureFoo, OutputStream output) {
        try {
            List<Foo> foos = futureFoo.get();
            System.out.println("Server sending a chunk of XML");
            for (Foo foo : foos) {
                marshaller.marshal(foo, output);
            }
        } catch (JAXBException e) {
            throw new IllegalStateException("JAXB couldn't marshal. Handle it.", e);
        } catch (InterruptedException e) {
            throw new IllegalStateException("Task was interrupted. Handle it.", e);
        } catch (ExecutionException e) {
            throw new IllegalStateException("Task failed to execute. Handle it.", e);
        }
    }

    class DelayedFoos implements Callable<List<Foo>> {
        private int delaySeconds;

        public DelayedFoos(int delaySeconds) {
            this.delaySeconds = delaySeconds;
        }

        public List<Foo> call() throws Exception {
            Thread.sleep(delaySeconds * 1000);
            return Arrays.asList(new Foo(fooCounter++), new Foo(fooCounter++), new Foo(fooCounter++));
        }
    }

    public static void main(String[] args) throws IOException {
        System.out.println("Starting Grizzly with the JAX-RS resource");
        final String baseUri = "http://localhost:9998/";
        final Map<String, String> initParams = new HashMap<String, String>();
        initParams.put("com.sun.jersey.config.property.packages", "rds.jersey");
        SelectorThread threadSelector = GrizzlyWebContainerFactory.create(baseUri, initParams);
        System.out.println("Grizzly started");
        System.out.println("Starting a thread to request the streamed XML");
        executorService.submit(new HttpRequester(baseUri + "streaming"));
    }
}

@XmlRootElement
class Foo {
    @XmlElement
    private int id;

    Foo() {}

    public Foo(int id) {
        this.id = id;
    }
}

class HttpRequester implements Runnable {
    private String url;

    public HttpRequester(String url) {
        this.url = url;
    }

    public void run() {
        try {
            System.out.println("Doing HTTP GET on " + url);
            HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
            BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                System.out.println("Client got: " + line);
            }
            System.exit(0);
        } catch (IOException e) {
            throw new IllegalStateException("Some bad I/O happened. Handle it.", e);
        }
    }
}

需要注意的重点/差异:

  1. 从资源方法返回响应表示整个响应包含在该对象中,并且不允许对响应进行增量更新。返回StreamingOutput而不是。这告诉泽西岛你将发回一个数据流,你可以随意附加,直到你完成。 StreamingOutput使您可以访问OutputStream,这是您用来发送增量更新的,并且是整个事情的关键。当然,这意味着你必须自己处理编组。如果你马上回复整个回复,泽西岛只能进行编组。
  2. 由于OutputStream是您一次发送一点数据的方式,您必须在JAX-RS资源中执行线程处理或将OutputStream传递给DriverController并在那里写入。
  3. 如果要强制它立即发送数据,请务必在OutputStream上调用flush()。否则,在填充内部缓冲区之前,不会向客户端发送任何内容。请注意,调用flush()会自行规避缓冲区的用途并使您的应用程序更加健谈。
  4. 总而言之,要将它应用于您的项目,首要的事情是更改资源方法以返回StreamingOutput实现并从该实现内部调用DriverController,将OutputStream传递给DriverController。然后在DriverController中,当你从一个线程中获取一些文章时,不是将它添加到队列中以供以后使用,而是立即将它写入OutputStream。

答案 1 :(得分:0)

@Ryan Stewart:我们如何在基于SOAP2.x SOAP的Web服务类环境和HTML页面中解决同样的问题作为Web客户端。 我认为DriverController可以将Future对象保留在会话中并返回第一个可用的响应(文章),其中包含一个唯一的会话标识符给客户端....然后客户端可以进行另一个webservice调用(最好通过Ajax + jquery)传递保存的会话标识符将触发DriverController搜索更多结果并发回....这是一个可行的解决方案吗?它也适用于上述环境。