How to write java.util.stream.Stream to StreamingResponseBody output stream - java

I am working to build a REST API where a large amount of data from the Oracle database can be sent in chunks via streaming to client application (like a file download or direct stream).
I am getting Stream from JpaRepository as given below -
#Query("select u from UsersEntity u")
Stream<UsersEntity> findAllByCustomQueryAndStream();
But now challenge comes to write this stream to StreamingResponseBody Output stream
I tried by many ways but no success -
First Approach -
Stream<UsersEntity> usersResultStream = usersRepository.findAllByCustomQueryAndStream();
StreamingResponseBody stream = outputStream -> {
Iterator<UsersEntity> iterator = usersResultStream.iterator();
try (ObjectOutputStream oos = new ObjectOutputStream(outputStream)) {
while (iterator.hasNext()) {
oos.write(iterator.next().toString().getBytes());
}
}
};
Got Error -
java.sql.SQLException: Closed Resultset: next
at oracle.jdbc.driver.InsensitiveScrollableResultSet.next(InsensitiveScrollableResultSet.java:565) ~[ojdbc7-12.1.0.2.jar:12.1.0.2.0]
Second Approach -
StreamingResponseBody stream = new StreamingResponseBody() {
#Transactional(readOnly = true)
#Override
public void writeTo(OutputStream outputStream) throws IOException {
Stream<UsersEntity> usersResultStream = usersRepository.findAllByCustomQueryAndStream();
try (ObjectOutputStream oos = new ObjectOutputStream(outputStream)) {
usersResultStream.forEach(user->{
try {
oos.write(user.toString().getBytes());
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
};
Got Error -
org.springframework.dao.InvalidDataAccessApiUsageException: You're trying to execute a streaming query method without a surrounding transaction that keeps the connection open so that the Stream can actually be consumed. Make sure the code consuming the stream uses #Transactional or any other way of declaring a (read-only) transaction.
I have uploaded practice code at the below-given link -
Sample POC Link
I don't have any experience with the streaming related task so please help me with this.
If I am in the wrong direction than suggest any other approach to do this within Spring Framework. Please share any reference links if available.

Finally, I resolved the problem by using the service layer. Initially, I was writing the complete logic in Controller Class which was creating the issue.
Controller Class -
#RestController
#RequestMapping("/api")
public class UsersController {
#Autowired
private UserService service;
#GetMapping(value = "/userstream")
public ResponseEntity<StreamingResponseBody> fetchUsersStream() {
StreamingResponseBody stream = this::writeTo;
return new ResponseEntity<>(stream, HttpStatus.OK);
}
private void writeTo(OutputStream outputStream) {
service.writeToOutputStream(outputStream);
}
}
Service Class -
#Service
public class UserService {
#Autowired
private UsersRepository usersRepository;
#Transactional(readOnly = true)
public void writeToOutputStream(final OutputStream outputStream) {
try (Stream<UsersEntity> usersResultStream = usersRepository.findAllByCustomQueryAndStream()) {
try (ObjectOutputStream oos = new ObjectOutputStream(outputStream)) {
usersResultStream.forEach(emp -> {
try {
oos.write(emp.toString().getBytes());
} catch (IOException e) {
e.printStackTrace();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Complete Code is available at github - https://github.com/bagesh2050/HttpResponseStreamingDemo
Still, I am willing for suggestions related to Http Streaming. Please provide if you have better ideas.

No sample shows "such complex" usage of StreamingResponseBody, and I fear it is "not possible" (at least I couldn't manage/fix it, with StreamingResponseBody and Stream query)
...but, what was possible:
Use findAll() (the normal unstreamed List-repo method) within StreamingResponseBody.
(But I understand the "need" of doing the web request asynchronously... and the db request "streamed"...)
Use Callable (async web request) and an #Async CompletableFuture<..> (async db request):
#RestController
#RequestMapping("/api")
public class UsersController {
#Autowired
private UsersRepository usersRepository;
#GetMapping(value = "/async/users")
public Callable<List<UsersEntity>> fetchUsersAsync() {
Callable callable = () -> {
return usersRepository.readAllBy().get();
};
return callable;
}
}
..and a Repository like:
#Repository
public interface UsersRepository extends JpaRepository<UsersEntity, Integer> {
#Async
CompletableFuture<List<UsersEntity>> readAllBy();
}
(see spring-samples)
.. don't forget to #EnableAsync on your application/configuration:
#org.springframework.scheduling.annotation.EnableAsync
#SpringBootApplication
public class Application { ... }
Sorry, it is not even an answer, but my findings - too long for a comment.
The asynchronous web request can be achieved in various ways. (see https://spring.io/blog/2012/05/10/spring-mvc-3-2-preview-making-a-controller-method-asynchronous/, https://niels.nu/blog/2016/spring-async-rest.html, and even not mentioned "reactive" api)

Related

How should Blocking Operations in a ContainerRequestFilter be handled Quarkus/Vert.x

Background:
We are implementing a signed request mechanism for communication between services. Part of that process generates a digest on the contents of the request body. To validate the body on receipt, we re-generate the digest at the receiver and compare. It's pretty straight-forward stuff.
#PreMatching
#Priority(Priorities.ENTITY_CODER)
public class DigestValidationFilter implements ContainerRequestFilter {
private final DigestGenerator generator;
#Inject
public DigestValidationFilter(DigestGenerator generator) {
this.generator = generator;
}
#Override
public void filter(ContainerRequestContext context) throws IOException {
if (context.hasEntity() && context.getHeaderString(Headers.DIGEST) != null) {
String digest = context.getHeaderString(Headers.DIGEST);
ByteArrayOutputStream body = new ByteArrayOutputStream();
try (InputStream stream = context.getEntityStream()) {
stream.transferTo(body); // <-- This is line 36 from the provided stack-trace
}
String algorithm = digest.split("=", 2)[0];
try {
String calculated = generator.generate(algorithm, body.toByteArray());
if (digest.equals(calculated)) {
context.setEntityStream(new ByteArrayInputStream(body.toByteArray()));
} else {
throw new InvalidDigestException("Calculated digest does not match supplied digest. Request body may have been tampered with.");
}
} catch (NoSuchAlgorithmException e) {
throw new InvalidDigestException(String.format("Unsupported hash algorithm: %s", algorithm), e);
}
}
}
}
The above filter is made available to services as a java-lib. We also supply a set of RequestFilters that can be used with various Http clients, i.e., okhttp3, apache-httpclient, etc. These clients only generate digests when the body is "repeatable", i.e., not streaming.
The Issue:
In Jersey services and Spring Boot services, we do not run into issues. However, when we use Quarkus, we receive the following stack-trace:
2022-09-02 15:18:25 5.13.0 ERROR A blocking operation occurred on the IO thread. This likely means you need to use the #io.smallrye.common.annotation.Blocking annotation on the Resource method, class or javax.ws.rs.core.Application class.
2022-09-02 15:18:25 5.13.0 ERROR HTTP Request to /v1/policy/internal/policies/72575947-45ac-4358-bc40-b5c7ffbd3f35/target-resources failed, error id: c79aa557-c742-43d7-93d9-0e362b2dff79-1
org.jboss.resteasy.reactive.common.core.BlockingNotAllowedException: Attempting a blocking read on io thread
at org.jboss.resteasy.reactive.server.vertx.VertxInputStream$VertxBlockingInput.readBlocking(VertxInputStream.java:242)
at org.jboss.resteasy.reactive.server.vertx.VertxInputStream.readIntoBuffer(VertxInputStream.java:120)
at org.jboss.resteasy.reactive.server.vertx.VertxInputStream.read(VertxInputStream.java:82)
at java.base/java.io.InputStream.transferTo(InputStream.java:782)
at com.###.ciam.jaxrs.DigestValidationFilter.filter(DigestValidationFilter.java:36)
at org.jboss.resteasy.reactive.server.handlers.ResourceRequestFilterHandler.handle(ResourceRequestFilterHandler.java:47)
at org.jboss.resteasy.reactive.server.handlers.ResourceRequestFilterHandler.handle(ResourceRequestFilterHandler.java:8)
at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:141)
at org.jboss.resteasy.reactive.server.handlers.RestInitialHandler.beginProcessing(RestInitialHandler.java:49)
at org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveVertxHandler.handle(ResteasyReactiveVertxHandler.java:17)
at org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveVertxHandler.handle(ResteasyReactiveVertxHandler.java:7)
at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1212)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:163)
at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
at io.quarkus.vertx.http.runtime.StaticResourcesRecorder$2.handle(StaticResourcesRecorder.java:67) ... elided ...
I completely understand why Vert.x would like to prevent long-running I/O operations on the request processing threads. That said, the advice provided in the exception only accounts for I/O operations at the end of the request processing, i.e., it assumes the I/O is happening in the endpoint. Although we do control the filter code, it is in an external library, making it almost like a 3rd party library.
My Question:
What is the right way to handle this?
I've been scouring documentation, but haven't stumbled on the answer yet (or haven't recognized the answer). Is there a set of recommended docs I should review?
https://quarkus.io/guides/resteasy-reactive#request-or-response-filters
https://smallrye.io/smallrye-mutiny/1.7.0/guides/framework-integration/
#RequestScoped
class Filter(
private val vertx: Vertx
) {
// you can run blocking code on mutiny's Infrastructure defaultWorkerPool
#ServerRequestFilter
fun filter(requestContext: ContainerRequestContext): Uni<RestResponse<*>> {
return Uni.createFrom().item { work() }
.map<RestResponse<*>> { null }
.runSubscriptionOn(Infrastructure.getDefaultWorkerPool())
}
// or use vertx.executeBlocking api
#ServerRequestFilter
fun filter(requestContext: ContainerRequestContext): Uni<RestResponse<*>> {
return vertx.executeBlocking(
Uni.createFrom().item { work() }
.map { null }
)
}
private fun work(){
Log.info("filter")
Thread.sleep(3000)
}
}
In the end, the advice in the exception lead me to simply annotating a delegate ContainerRequestFilter:
public class DigestValidationFilterBlocking implements ContainerRequestFilter {
private final DigestValidationFilter delegate;
public DigestValidationFilterBlocking(DigestValidationFilter delegate) {
this.delegate = delegate;
}
#Blocking // <-- This annotation allowed Vert.x to accept the I/O operation
#Override
public void filter(ContainerRequestContext context) throws IOException {
delegate.filter(context);
}
}
I had the same problem. You can try using this in your #ServerRequestFilter:
#Context
HttpServerRequest request;

SolrHealthIndicator without deprecated CompositeHealthIndicator

I've tried to upgrade Spring Boot to 2.2.4.RELEASE version. Everzthing if fine exept problem with CompositeHealthIndicator which is deprecated.
I have this bean method
#Autowired
private HealthAggregator healthAggregator;
#Bean
public HealthIndicator solrHealthIndicator() {
CompositeHealthIndicator composite = new CompositeHealthIndicator(
this.healthAggregator);
composite.addHealthIndicator("solr1", createHealthIndicator(firstHttpSolrClient()));
composite.addHealthIndicator("solr2", createHealthIndicator(secondHttpSolrClient()));
composite.addHealthIndicator("querySolr", createHealthIndicator(queryHttpSolrClient()));
return composite;
}
private CustomSolrHealthIndicator createHealthIndicator(SolrClient source) {
try {
return new CustomSolrHealthIndicator(source);
} catch (Exception ex) {
throw new IllegalStateException("Unable to create helthCheckIndicator for solr client instance.", ex);
}
}
That registers HealthIndicator for 3 instances of SOLR (2 indexing, 1 for query). Everything worked fine until Spring Boot update. After update the method CompositeHealthIndicator.addHealthIndicator is not present, the whole class is marked as Deprecated.
The class which is created in createHealthIndicator is like this:
public class CustomSolrHealthIndicator extends SolrHealthIndicator {
private final SolrClient solrClient;
public CustomSolrHealthIndicator(SolrClient solrClient) {
super(solrClient);
this.solrClient = solrClient;
}
#Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
if (!this.solrClient.getClass().isAssignableFrom(HttpSolrClient.class)) {
super.doHealthCheck(builder);
}
HttpSolrClient httpSolrClient = (HttpSolrClient) this.solrClient;
if (StringUtils.isBlank(httpSolrClient.getBaseURL())) {
return;
}
super.doHealthCheck(builder);
}
}
Is there any easy way to transform the old way how to register the instances of SOLR i want to check if they are up or down at Spring Boot version 2.2.X?
EDIT:
I have tried this:
#Bean
public CompositeHealthContributor solrHealthIndicator() {
Map<String, HealthIndicator> solrIndicators = Maps.newLinkedHashMap();
solrIndicators.put("solr1", createHealthIndicator(firstHttpSolrClient()));
solrIndicators.put("solr2", createHealthIndicator(secondHttpSolrClient()));
solrIndicators.put("querySolr", createHealthIndicator(queryHttpSolrClient()));
return CompositeHealthContributor.fromMap(solrIndicators);
}
private CustomSolrHealthIndicator createHealthIndicator(SolrClient source) {
try {
return new CustomSolrHealthIndicator(source);
} catch (Exception ex) {
throw new IllegalStateException("Unable to create healthCheckIndicator for solr client instance.", ex);
}
}
The CustomSolrHealthIndicator has no changes against start state.
But I cannot create that bean. When calling createHealthIndicator I am getting NoClassDefFoundError
Does anyone know where the problem is?
Looks like you can just use CompositeHealthContributor. It's not much different from what you have already. It appears something like this would work. You could override the functionality to add them one at a time if you'd like, also, which might be preferable if you have a large amount of configuration. Shouldn't be any harm with either approach.
#Bean
public HealthIndicator solrHealthIndicator() {
Map<String, HealthIndicator> solrIndicators;
solrIndicators.put("solr1", createHealthIndicator(firstHttpSolrClient()));
solrIndicators.put("solr2", createHealthIndicator(secondHttpSolrClient()));
solrIndicators.put("querySolr", createHealthIndicator(queryHttpSolrClient()));
return CompositeHealthContributor.fromMap(solrIndicators);
}
Instead of deprecated CompositeHealthIndicator#addHealthIndicator use constructor with map:
#Bean
public HealthIndicator solrHealthIndicator() {
Map<String, HealthIndicator> healthIndicators = new HashMap<>();
healthIndicators.put("solr1", createHealthIndicator(firstHttpSolrClient()));
healthIndicators.put("solr2", createHealthIndicator(secondHttpSolrClient()));
healthIndicators.put("querySolr", createHealthIndicator(queryHttpSolrClient()));
return new CompositeHealthIndicator(this.healthAggregator, healthIndicators);
}

spring boot execution of two dependent methods

I am developping an application using spring boot as framework. I have 2 methods the first one is deleting data from the database and the other one is deleting folder from the disk, so if i delete from the database and i can't delete from the disc all the operation will fail. So how can i do that with springboot ?
#Override
public ResponseEntity<?> delete(Long id) {
return libelleRepository.findById(id).map(libelle -> {
libelleRepository.delete(libelle);
return ResponseEntity.ok().build();
}).orElseThrow(() -> new GeneralResourceNotFoundException("Libelle not found with id " + id));
}
You can use the Spring's #Transactional for doing this.
Here is the sample code what I have tried. It performs a Database operation followed by a file operation in my example I'm trying to create a file. First am creating the file before performing the database operation and used TransactionSynchronizationAdapter to make sure the transaction is complete before commiting.
Code:
#Autowired
private UserService userService;
#Transactional
public String doFileOperation() {
File testFile = new File("C:\\test.txt");
TxnListener transactionListener = new TxnListener(testFile);
TransactionSynchronizationManager.registerSynchronization(transactionListener);
// DB Operation
userService.addUser();
// File Operation
List<String> lines = Arrays.asList("1st line", "2nd line");
try {
Files.write(Paths.get(testFile.getPath()),
lines,
StandardCharsets.UTF_8,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND);
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
public class TxnListener extends TransactionSynchronizationAdapter {
private File outputFile;
public TxnListener(File outputFile) {
this.outputFile = outputFile;
}
#Override
public void afterCompletion(int status) {
if (STATUS_COMMITTED != status) {
if (outputFile.exists()) {
if (!outputFile.delete()) {
System.out.println("Could not delete File" + outputFile.getPath() + " after failed transaction");
}
}
}
}
}
In case of exception during the Database operation afterCompletion will be called and the file will be deleted.
This way you can maintain the atomicity of the operation.

Synchronous access to REST web service

i m in trouble with a simple REST service using this code :
#GET
#Path("next/{uuid}")
#Produces({"application/xml", "application/json"})
public synchronized Links nextLink(#PathParam("uuid") String uuid) {
Links link = null;
try {
link = super.next();
if (link != null) {
link.setStatusCode(5);
link.setProcessUUID(uuid);
getEntityManager().flush();
Logger.getLogger("Glassfish Rest Service").log(Level.INFO, "Process {0} request url : {1} #id {2} at {3} #", new Object[]{uuid, link.getLinkTxt(), link.getLinkID(), Calendar.getInstance().getTimeInMillis()});
}
} catch (NoResultException ex) {
} catch (IllegalArgumentException ex) {
}
return link;
}
this should provide a link object, and mark it as used (setStatusCode(5)) to prevent next access to service to send the same object. the probleme, is that when there s a lot of fast clients accessing to the web service, this one provides 2 or 3 times the same link object to different clients. how can i solve this ??
here is the resquest using to :
#NamedQuery(name = "Links.getNext", query = "SELECT l FROM Links l WHERE l.statusCode = 2")
and the super.next() methode :
public T next() {
javax.persistence.Query q = getEntityManager().createNamedQuery("Links.getNext");
q.setMaxResults(1);
T res = (T) q.getSingleResult();
return res;
}
thx
The life-cycle of a (root) JAX-RS resource is per request, so the (otherwise correct) synchronized keyword on the nextLink method is sadly ineffectual.
What you need is a mean to synchronize the access/update.
This could be done in many ways:
I) You could synchronize on an external object, injected by a framework (example: a CDI injected #ApplicationScoped) as in:
#ApplicationScoped
public class SyncLink{
private ReentrantLock lock = new ReentrantLock();
public Lock getLock(){
return lock;
}
}
....
public class MyResource{
#Inject SyncLink sync;
#GET
#Path("next/{uuid}")
#Produces({"application/xml", "application/json"})
public Links nextLink(#PathParam("uuid") String uuid) {
sync.getLock().lock();
try{
Links link = null;
try {
link = super.next();
if (link != null) {
link.setStatusCode(5);
link.setProcessUUID(uuid);
getEntityManager().flush();
Logger.getLogger("Glassfish Rest Service").log(Level.INFO, "Process {0} request url : {1} #id {2} at {3} #", new Object[]{uuid, link.getLinkTxt(), link.getLinkID(), Calendar.getInstance().getTimeInMillis()});
}
} catch (NoResultException ex) {
} catch (IllegalArgumentException ex) {
}
return link;
}finally{
sync.getLock().unlock();
}
}
}
II) You could be lazy and synchronize on the class
public class MyResource{
#Inject SyncLink sync;
#GET
#Path("next/{uuid}")
#Produces({"application/xml", "application/json"})
public Links nextLink(#PathParam("uuid") String uuid) {
Links link = null;
synchronized(MyResource.class){
try {
link = super.next();
if (link != null) {
link.setStatusCode(5);
link.setProcessUUID(uuid);
getEntityManager().flush();
Logger.getLogger("Glassfish Rest Service").log(Level.INFO, "Process {0} request url : {1} #id {2} at {3} #", new Object[]{uuid, link.getLinkTxt(), link.getLinkID(), Calendar.getInstance().getTimeInMillis()});
}
} catch (NoResultException ex) {
} catch (IllegalArgumentException ex) {
}
}
return link;
}
}
III) You could synchronize using the database. In that case you would investigate the pessimistic locking available in JPA2.
You need to use some form of locking, most likely optimistic version locking. This will ensure only one transaction succeeds, the other will fail.
See,
http://en.wikibooks.org/wiki/Java_Persistence/Locking
Depending on how frequent you believe the contention will be in creating new Links you should choose either Optimistic locking using a #Version property or Pessimistic locking.
My guess is optimistic locking will work out better for you. In any case let your Resource class act as a Service Facade and place the model related code into a Stateless Session Bean EJB and handle any OptimisticLockExceptions with a simply retry.
I noticed you mentioned you are having trouble catching locking related exceptions and it also looks like you are using Eclipselink. In that case you could try something like this:
#Stateless
public class LinksBean {
#PersistenceContext(unitName = "MY_JTA_PU")
private EntityManager em;
#Resource
private SessionContext sctx;
public Links createUniqueLink(String uuid) {
Links myLink = null;
shouldRetry = false;
do {
try
myLink = sctx.getBusinessObject(LinksBean.class).createUniqueLinkInNewTX(uuid);
}catch(OptimisticLockException olex) {
//Retry
shouldRetry = true;
}catch(Exception ex) {
//Something else bad happened so maybe we don't want to retry
log.error("Something bad happened", ex);
shouldRetry = false;
} while(shouldRetry);
return myLink;
}
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public Links createUniqueLinkInNewTX(uuid) {
TypedQuery<Links> q = em.createNamedQuery("Links.getNext", Links.class);
q.setMaxResults(1);
try {
myLink = q.getSingleResult();
}catch(NoResultException) {
//No more Links that match my criteria
myLink = null;
}
if (myLink != null) {
myLink.setProcessUUID(uuid);
//If you change your getNext NamedQuery to add 'AND l.uuid IS NULL' you
//could probably obviate the need for changing the status code to 5 but if you
//really need the status code in addition to the UUID then:
myLink.setStatusCode(5);
}
//When this method returns the transaction will automatically be committed
//by the container and the entitymanager will flush. This is the point that any
//optimistic lock exception will be thrown by the container. Additionally you
//don't need an explicit merge because myLink is managed as the result of the
//getSingleResult() call and as such simply using its setters will be enough for
//eclipselink to automatically merge it back when it commits the TX
return myLink;
}
}
Your JAX-RS/Jersey Resource class should then look like so:
#Path("/links")
#RequestScoped
public class MyResource{
#EJB LinkBean linkBean;
#GET
#Path("/next/{uuid}")
#Produces({"application/xml", "application/json"})
public Links nextLink(#PathParam("uuid") String uuid) {
Links link = null;
if (uuid != null) {
link = linkBean.createUniqueLink(uuid);
Logger.getLogger("Glassfish Rest Service").log(Level.INFO, "Process {0} request url : {1} #id {2} at {3} #", new Object[]{uuid, link.getLinkTxt(), link.getLinkID(), Calendar.getInstance().getTimeInMillis()});
}
return link;
}
}
That's a semi-polished example of one approach to skin this cat and there's a lot going on here. Let me know if you have any questions.
Also, from the REST end of things you might consider using #PUT for this resource instead of #GET because your endpoint has the side effect of updating (UUID and/or statusCode) the state of the resource not simply fetching it.
When using JAX-RS which is a Java EE feature, in my understanding you should not manage threads in Java SE style like using synchronized block.
In Java EE you can provide synchronized access to your method with singleton EJB:
#Path("")
#Singleton
public class LinksResource {
#GET
#Path("next/{uuid}")
#Produces({"application/xml", "application/json"})
public Links nextLink(#PathParam("uuid") String uuid) {
By default this will use #Lock(WRITE) which allows only one request at a time to your method.

Java JAX-RS web service: adding nodes to JAXB XML result as threads complete

I have programmed a JAX-RS web service with Jersey that queries prices from different websites and gives the result back as XML through JAXB annotated classes. Unfortunately some websites take up to 15 seconds to respond so I am using multiple threads to inquire those prices.
I would like to write a client to this webservice now and my web users will not want to wait for 30 seconds after they hit 'search' for the result to come so my idea is dynamically updating the result table as the results from my JAX-RS webservice come back.
After 30 seconds my webservice should time out and close the <result>-Element or after all threads completed.
Right now my webservice runs all threads and gives back the result after all trheads are completed, I would like to dynamically add results to the XML output as they come, how can I accomplish that?
The structure of the XML response is:
<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;
}
}
I started to adapt your code to do it, but I decided it was easier to work up an independent example. The example starts a Grizzly+Jersey server with a single resource class in it. A GET on the resource spawns three threads that delay for 2, 4, and 6 seconds before returning some objects. After the server starts, another thread makes a request to the server. When you run it, you can plainly see that the requester receives chunks of XML as the respective threads finish their work in the server. The one thing it doesn't do is wrap separately-delivered XML chunks in a single root element since that should be relatively trivial.
The entire executable source is below, and if you have maven and git, you can clone it from github and run it with:
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
Source:
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);
}
}
}
Important points/differences to take note of:
Returning a Response from your resource method indicates that the entire response is contained in that object and doesn't allow for incremental updates to the response. Return a StreamingOutput instead. That tells Jersey that you'll be sending back a stream of data, which you can append to at will until you're done. The StreamingOutput gives you access to an OutputStream, which is what you use to send incremental updates and is the key to this whole thing. Of course, that means you have to handle the marshaling yourself. Jersey can only do the marshaling if you're returning the entire response at once.
Since the OutputStream is how you send back the data a little at a time, you either have to do the threading in your JAX-RS resource or pass the OutputStream down to your DriverController and write to it there.
Be sure to invoke flush() on the OutputStream if you want to force it to send out data immediately. Otherwise, nothing will be sent to the client until whatever internal buffer is filled up. Note that invoking flush() yourself circumvents the purpose of the buffer and makes your app more chatty.
All in all, to apply this to your project, the primary thing to do is change your resource method to return a StreamingOutput implementation and invoke your DriverController from inside that implementation, passing the OutputStream to the DriverController. Then in the DriverController, when you get some Articles back from a thread, instead of adding it to a queue for later, write it to the OutputStream immediately.
#Ryan Stewart: how would we resolve same issue in axis2.x SOAP based web service kind of environment and HTML page as web client.
What I think is DriverController can keep Future objects in session and returns very first available response(article) with a unique session identifier to client....then client can make another webservice call (preferably thru Ajax+jquery) passing saved session identifier which would trigger DriverController to search more results and send back....is it a viable solution? Would it applicable for above environment too.

Categories

Resources