Synchronous access to REST web service - java

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.

Related

Manipulate with cache as with collection in Spring

I looked a lot of stuff on on internet but I don't found any solution for my needs.
Here is a sample code which doesn't work but show my requirements for better understanding.
#Service
public class FooCachedService {
#Autowired
private MyDataRepository dataRepository;
private static ConcurrentHashMap<Long, Object> cache = new ConcurrentHashMap<>();
public void save(Data data) {
Data savedData = dataRepository.save(data);
if (savedData.getId() != null) {
cache.put(data.getRecipient(), null);
}
}
public Data load(Long recipient) {
Data result = null;
if (!cache.containsKey(recipient)) {
result = dataRepository.findDataByRecipient(recipient);
if (result != null) {
cache.remove(recipient);
return result;
}
}
while (true) {
try {
if (cache.containsKey(recipient)) {
result = dataRepository.findDataByRecipient(recipient);
break;
}
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return result;
}
}
and data object:
public class Data {
private Long id;
private Long recipient;
private String payload;
// getters and setters
}
As you can see in code above I need implement service which will be stored new data into database and into cache as well.
Whole algorithm should looks something like that:
Some userA create POST request to my controller to store data and it fire save method of my service.
Another userB logged in system send request GET to my controller which fire method load of my service. In this method is compared logged user's id which sent request with recipients' ids in map. If map contains data for this user they are fetched with repository else algorithm check every second if there are some new data for that user (this checking will be some timeout, for example 30s, and after 30s request return empty data, and user create new GET request and so on...)
Can you tell me if it possible do it with some elegant way and how? How to use cache for that or what is the best practice for that? I am new in this area so I will be grateful for any advice.

AEM Reverse Replication with two publisher

I Have two Publisher and one Author Server.
I use reverse Replication for Usergenerated Content, for modified Content.
So I have also a Forward Replication (Selfmade, because AEM havent).
The Problem is, that the Replication between the Publisher going in an endless loop. So they replicate like playing ping pong.
What can I do about the ping pong play of the publishers?
I'm Using CQ 6.1, Java 1.7
I found a Solution.
Set Replication Options --> watch printscreen
Adapt WorkflowSession in Session class to replicate
Close Sessionafter replication
Never close Workflow Session
Clean outbox on publishers in crx (/var/replication/outbox) [every time, if something fails]
Create Launcher for Reverse replication Create & Modified (with condition: cq:distribute!=)
Create Launcher for Forward replication Create & Modified (code example)
Because I have more than one Forward Replicator, I made an Abstract
Class. If you don't use it, put all in one class
Attention: with ReplicationOption setSynchronous(true), the
replication was fine to replicate from publisher to publisher. But
because I have an administration page on author, I have to unncomment
this attribute. Because the changes on Auhtor were not replicated to
the publishe
#Component(immediate = true)
#Service(value = WorkflowProcess.class)
public class ReplicateUsergeneratedContentToPublishWorkflow extends AbstractAuthorToPublishWorkflow implements WorkflowProcess{
// OSGI properties
#Property(value = "This workflow replicate usergenerated content from author to publisher")
static final String DESCRIPTION = Constants.SERVICE_DESCRIPTION;
#Property(value = "Titel")
static final String VENDOR = Constants.SERVICE_VENDOR;
#Property(value = "Replicate the usergenerated content from one publisher via author to the ohter publisher")
static final String LABEL = "process.label";
private static final Logger LOGGER = LoggerFactory.getLogger(ReplicateUsergeneratedContentToPublishWorkflow.class);
#Reference
private ResourceResolverFactory resolverFactory;
#Reference
protected Replicator replicator;
#Reference
private SlingRepository repository;
#Reference
SlingSettingsService slingSettingsService;
#Override
public void execute(WorkItem workItem, WorkflowSession workflowSession, MetaDataMap metaDataMap) throws WorkflowException {
Session session = null;
SimpleCredentials administrator = new SimpleCredentials("username", "password".toCharArray());
try {
java.util.Set<String> runModes = slingSettingsService.getRunModes();
session = repository.login(administrator);
//the replication need to check the payload
String payload = workItem.getWorkflowData().getPayload().toString();
Node node = null;
if (session.itemExists(payload)) {
node = (Node) session.getItem(payload);
}
activateNode(node, workflowSession, replicator);
//save all changes
session.save();
} catch (PathNotFoundException e) {
LOGGER.error("path not found", e);
workflowSession.terminateWorkflow(null);
} catch (ReplicationException e) {
LOGGER.error("error replicating content node", e);
workflowSession.terminateWorkflow(null);
} catch (RepositoryException e) {
LOGGER.error("error reading path to content node", e);
workflowSession.terminateWorkflow(null);
}finally{
if(session != null){
session.logout();
}
}
}
}
public abstract class AbstractAuthorToPublishWorkflow implements WorkflowProcess {
protected void activateNode(Node node, WorkflowSession workflowSession, Replicator replicator) throws RepositoryException, ReplicationException {
ReplicationOptions replicationOptions = new ReplicationOptions();
replicationOptions.setSuppressStatusUpdate(true);
replicationOptions.setSuppressVersions(true);
//replicationOptions.setSynchronous(true);
//the property cq:distribute is settet if the node should be replicated from publisher to author (set it in your own code)
if (node != null) {
node.setProperty("cq:distribute", (Value) null);
//important use WorkflowSession and adapt it to Session class, replication is going to an endless loop, if you doing it without WorkflowSession
replicator.replicate(workflowSession.adaptTo(Session.class), ReplicationActionType.ACTIVATE, node.getPath(), replicationOptions);
}
}
}
Special for User And Group forward Replication, don't interfere the deactivate action from useradmin on author
//Important that you don't interfer the Deactivate Action from useradmin
//do nothing if the action is deactivate!
if( !userNode.getProperty("cq:lastReplicationAction").getString().equals("Deactivate")) {
activateNode(userNode, workflowSession, replicator);
//save all changes
session.save();
}
And for the the codepart were I modifie a node in author, I add this
//quickfix
//FrameworkUtil.getBundle(NodeManageDAO.class).getBundleContext()
BundleContext bundleContext = FrameworkUtil.getBundle(PhotoNodeManagerDAO.class).getBundleContext();
ServiceReference serviceReference = bundleContext.getServiceReference(SlingSettingsService.class.getName( ));
SlingSettingsService slingSettingsService = (SlingSettingsService)bundleContext.getService(serviceReference);
Set<String> runmode= slingSettingsService.getRunModes();
//just in author mode
if(runmode.contains("author")) {
//attention replication from author is not working without nullable / delete the cq:distribute property
node.setProperty("cq:distribute", (Value)null);
}
If you have a updated your workflow model, than you have to restart the worklflow and clean the failures and the cadaverous from old replication configs. Clean on author and on each publisher seperated, go to crx under /etc/workflow/launcher/config.
For the reverse replicator on publisher, set also the condition: cq:distribute!=
and on each part in the code where you change the nodes, add the following three properties
node.setProperty("cq:distribute", ValueFactoryImpl.getInstance().createValue("true"));
node.setProperty("cq:lastModifiedBy", ValueFactoryImpl.getInstance().createValue(session.getUserID()));
node.setProperty("cq:lastModified", ValueFactoryImpl.getInstance().createValue(Calendar.getInstance()));
session.save();
Sample of the Launchers [authorserver]/etc/workflow.html --> launchers

Hibernate "The resource type Session does not implement java.lang.AutoCloseable"

I want to use construction
import org.hibernate.Session;
...
try (Session session){
}
How can I do that?
Because "The resource type Session does not implement java.lang.AutoCloseable"
I know, that I need to extend Session and override AutoCloseable method, but when I've try to do that, there is error "The type Session cannot be the superclass of SessionDAO; a superclass must be a class"
Update
I've wrote my own DAO framework, but will be use Spring for that
First, you should use a much more solid session/transaction handling infrastructure, like Spring offers you. This way you can use the Same Session across multiple DAO calls and the transaction boundary is explicitly set by the #Transactional annotation.
If this is for a test project of yours, you can use a simple utility like this one:
protected <T> T doInTransaction(TransactionCallable<T> callable) {
T result = null;
Session session = null;
Transaction txn = null;
try {
session = sf.openSession();
txn = session.beginTransaction();
result = callable.execute(session);
txn.commit();
} catch (RuntimeException e) {
if ( txn != null && txn.isActive() ) txn.rollback();
throw e;
} finally {
if (session != null) {
session.close();
}
}
return result;
}
And you can call it like this:
final Long parentId = doInTransaction(new TransactionCallable<Long>() {
#Override
public Long execute(Session session) {
Parent parent = new Parent();
Child son = new Child("Bob");
Child daughter = new Child("Alice");
parent.addChild(son);
parent.addChild(daughter);
session.persist(parent);
session.flush();
return parent.getId();
}
});
Check this GitHub repository for more examples like this one.

how to use JPA life-cycle events to get entity data

I have a RESTful API that makes use of an entity class annotated with #EntityListners. And in the EntityListner.java, I have a method annotated with #PostPersist. So, when that event fires, I want to extract all the information regarding the entity that just got persisted to the database. But when I try to do that, Glassfish is generating an exception and the method in EntityListner class is not executing as expected. Here is the code
public class EntityListner {
private final static String QUEUE_NAME = "customer";
#PostUpdate
#PostPersist
public void notifyOther(Customer entity){
CustomerFacadeREST custFacade = new CustomerFacadeREST();
Integer customerId = entity.getCustomerId();
String custData = custFacade.find(customerId).toString();
String successMessage = "Entity added to server";
try{
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// channel.basicPublish("", QUEUE_NAME, null, successMessage .getBytes());
channel.basicPublish("", QUEUE_NAME, null, custData.getBytes());
channel.close();
connection.close();
}
catch(IOException ex){
}
finally{
}
}
}
If I send the commented out successMessage message instead of custData, everything works fine.
http://www.objectdb.com/java/jpa/persistence/event says the following regarding the entity lifecycle methods, and I am wondering if that is the situation here.
To avoid conflicts with the original database operation that fires the entity lifecycle event (which is still in progress) callback methods should not call EntityMan­ager or Query methods and should not access any other entity objects
Any ideas?
As that paragraph says, the standard does not support calling entity manager methods from inside entity listeners. I strongly recommend building custData from the persisted entity, as Heiko Rupp says in his answer. If that is not feasible, consider:
notifying asynchronously. I do not really recommend this as it probably depends on timing to work properly:
public class EntityListener {
private final static String QUEUE_NAME = "customer";
private ScheduledExecutorService getExecutorService() {
// get asynchronous executor service from somewhere
// you will most likely need a ScheduledExecutorService
// instance, in order to schedule notification with
// some delay. Alternatively, you could try Thread.sleep(...)
// before notifying, but that is ugly.
}
private void doNotifyOtherInNewTransaction(Customer entity) {
// For all this to work correctly,
// you should execute your notification
// inside a new transaction. You might
// find it easier to do this declaratively
// by invoking some method demarcated
// with REQUIRES_NEW
try {
// (begin transaction)
doNotifyOther(entity);
// (commit transaction)
} catch (Exception ex) {
// (rollback transaction)
}
}
#PostUpdate
#PostPersist
public void notifyOther(final Customer entity) {
ScheduledExecutorService executor = getExecutorService();
// This is the "raw" version
// Most probably you will need to call
// executor.schedule and specify a delay,
// in order to give the old transaction some time
// to flush and commit
executor.execute(new Runnable() {
#Override
public void run() {
doNotifyOtherInNewTransaction(entity);
}
});
}
// This is exactly as your original code
public void doNotifyOther(Customer entity) {
CustomerFacadeREST custFacade = new CustomerFacadeREST();
Integer customerId = entity.getCustomerId();
String custData = custFacade.find(customerId).toString();
String successMessage = "Entity added to server";
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicPublish("", QUEUE_NAME, null, custData.getBytes());
channel.close();
connection.close();
}
catch(IOException ex){
}
finally {
}
}
}
registering some post-commit trigger (my recommendation if Heilo Rupp answer is not feasible). This is not timing dependant because it is guaranteed to execute after you have flushed to database. Furthermore, it has the added benefit that you don't notify if you end up rolling back your transaction. The way to do this depends on what you are using for transaction management, but basically you create an instance of some particular instance and then register it in some registry. For example, with JTA it would be:
public class EntityListener {
private final static String QUEUE_NAME = "customer";
private Transaction getTransaction() {
// get current JTA transaction reference from somewhere
}
private void doNotifyOtherInNewTransaction(Customer entity) {
// For all this to work correctly,
// you should execute your notification
// inside a new transaction. You might
// find it easier to do this declaratively
// by invoking some method demarcated
// with REQUIRES_NEW
try {
// (begin transaction)
doNotifyOther(entity);
// (commit transaction)
} catch (Exception ex) {
// (rollback transaction)
}
}
#PostUpdate
#PostPersist
public void notifyOther(final Customer entity) {
Transaction transaction = getTransaction();
transaction.registerSynchronization(new Synchronization() {
#Override
public void beforeCompletion() { }
#Override
public void afterCompletion(int status) {
if (status == Status.STATUS_COMMITTED) {
doNotifyOtherInNewTransaction(entity);
}
}
});
}
// This is exactly as your original code
public void doNotifyOther(Customer entity) {
CustomerFacadeREST custFacade = new CustomerFacadeREST();
Integer customerId = entity.getCustomerId();
String custData = custFacade.find(customerId).toString();
String successMessage = "Entity added to server";
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicPublish("", QUEUE_NAME, null, custData.getBytes());
channel.close();
connection.close();
}
catch(IOException ex){
}
finally {
}
}
}
If you are using Spring transactions, the code will be very similar, with just some class name changes.
Some pointers:
ScheduledExecutorService Javadoc, for triggering asynchronous actions.
transaction synchronization with JTA: Transaction Javadoc and Synchronization Javadoc
EJB transaction demarcation
the Spring equivalents: TransactionSynchronizationManager Javadoc and TransactionSynchronization Javadoc.
And some Spring documentation on Spring transactions
I guess you may be seeing a NPE, as you may be violating the paragraph you were citing:
String custData = custFacade.find(customerId).toString();
The find seems to implicitly querying for the object (as you describe), which may not be fully synced to the database and thus not yet accessible.
In his answer, gpeche noted that it's fairly straightforward to translate his option #2 into Spring. To save others the trouble of doing that:
package myapp.entity.listener;
import javax.persistence.PostPersist;
import javax.persistence.PostUpdate;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import myapp.util.ApplicationContextProvider;
import myapp.entity.NetScalerServer;
import myapp.service.LoadBalancerService;
public class NetScalerServerListener {
#PostPersist
#PostUpdate
public void postSave(final NetScalerServer server) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
#Override
public void afterCommit() { postSaveInNewTransaction(server); }
});
}
private void postSaveInNewTransaction(NetScalerServer server) {
ApplicationContext appContext =
ApplicationContextProvider.getApplicationContext();
LoadBalancer lbService = appContext.getBean(LoadBalancerService.class);
lbService.updateEndpoints(server);
}
}
The service method (here, updateEndpoints()) can use the JPA EntityManager (in my case, to issue queries and update entities) without any issue. Be sure to annotate the updateEndpoints() method with #Transaction(propagation = Propagation.REQUIRES_NEW) to ensure that there's a new transaction to perform the persistence operations.
Not directly related to the question, but ApplicationContextProvider is just a custom class to return an app context since JPA 2.0 entity listeners aren't managed components, and I'm too lazy to use #Configurable here. Here it is for completeness:
package myapp.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
#Override
public void setApplicationContext(ApplicationContext appContext)
throws BeansException {
applicationContext = appContext;
}
}

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