I'm trying to create an internet checker class, that will check the connection to a certain url and update the status property accordingly. To avoid ui freeze i want to use a thread, and a timer to recheck after a certain interval. Problem is the CHECK method call in the timeline keyframe is called from the FX thread still. How can i use a timeline inside a thread?
CODE:
public class InternetChecker {
private String baseUrl;
/***Properties***/
private ObjectProperty<Status> status = new SimpleObjectProperty<>(Status.ACTIVE);
/****************************************************************
********** CONSTRUCTORS ************
****************************************************************/
public InternetChecker(String baseUrl) {
this(baseUrl, 1000);
}
public InternetChecker(String baseUrl, int millisCheckInterval) {
this.baseUrl = baseUrl;
new Thread(() -> {
Timeline timelineCheck = new Timeline();
timelineCheck.getKeyFrames().add(
new KeyFrame(Duration.millis(millisCheckInterval), e -> {
check();
}));
timelineCheck.setCycleCount(Animation.INDEFINITE);
timelineCheck.play();
}).start();
}
/*******************************
* Will check if there is an internet connection present
* and update the status accordingly
*******************************/
public void check() {
// Check if base internet connection
// is working, if it is we continue
// to see if domain connection is working
try {
if ("127.0.0.1".equals(InetAddress.getLocalHost().getHostAddress())) {
setStatus(Status.INTERNET_DISCONNECTED);
return;
}
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
// Check if base domain connection is working
try {
final URL url = new URL(baseUrl);
final URLConnection conn = url.openConnection();
conn.connect();
conn.getInputStream().close();
setStatus(Status.ACTIVE);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (IOException e) {
setStatus(Status.BASE_URL_UNREACHABLE);
}
}
/****************************************************************
********** ACCESSORS ************
****************************************************************/
public Status getStatus() {
return status.get();
}
public ObjectProperty<Status> statusProperty() {
return status;
}
private void setStatus(Status status) {
this.status.set(status);
}
/*******************************
* ACTIVE (Base url reachable)
* BASE_URL_UNREACHABLE (Internet available, but base url is unreachable)
* INTERNET_DISCONNECTED (Internet is not available)
********************************/
public enum Status {
ACTIVE,
BASE_URL_UNREACHABLE,
INTERNET_DISCONNECTED;
}
}
Since you need to do a periodic background task that communicates with the JavaFX Application Thread it would be better to use a ScheduledService. This class executes a (new) Task periodically using an Executor that can be defined by the developer. Note that ScheduledService extends javafx.concurrent.Service.
Here is a skeleton example of what you'd need to do to implement this:
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
public class ConnectionStatusService extends ScheduledService<Status> {
// Property allows you to change the "baseUrl" between executions
private final StringProperty baseUrl = new SimpleStringProperty(this, "baseUrl");
// property getter and setters omitted...
#Override
protected Task<Status> createTask() {
// creates a new Task and gives the current "baseUrl"
// as an argument. This is called every cycle
return new ConnectionStatusTask(getBaseUrl());
}
private static class ConnectionStatusTask extends Task<Status> {
// A Task is a one-shot thing and its initial state should be
// immutable (or at least encapsulated from external modification).
private final String baseUrl;
private ConnectionStatusTask(String baseUrl) {
this.baseUrl = baseUrl;
}
#Override
protected Status call() throws Exception {
// Do what you need to determine connection status
return computedStatus;
}
}
}
Then you'd listen/bind to the lastValue property.
public void initService() {
ConnectionStatusService service = new ConnectionStatusService();
service.setBaseUrl(/* your URL */);
service.setPeriod(Duration.seconds(1)); // run every 1 seconds
service.lastValueProperty().addListener(/* your listener */); // or bind to this property
// you may also want to add EventHandlers/Listeners to handle when the
// service fails and such.
service.start();
}
It's important you observe the lastValue property and not the value property. The reason is given in the Javadoc of lastValue:
The last successfully computed value. During each iteration, the
"value" of the ScheduledService will be reset to null, as with any
other Service. The "lastValue" however will be set to the most
recently successfully computed value, even across iterations. It is
reset however whenever you manually call reset or restart.
I recommend reading the Javadoc of Task, Service, and ScheduledService for more information. All three of these classes implement the javafx.concurrent.Worker interface.
You only want a single statement to be executed on the JavaFX application thread and that is status.set(status);. Since you're planing to run this statement with some delay in between, you can simply use Platform.runLater to do this.
As for repeatedly executing the check: ScheduledExecutorService is designed for this purpose.
public class InternetChecker implements Runnable {
private final String baseUrl;
/***Properties***/
// use readonly wrapper here to restrict outside access to the property
private final ReadOnlyObjectWrapper<Status> status = new ReadOnlyObjectWrapper<>(Status.ACTIVE);
/****************************************************************
********** CONSTRUCTORS ************
****************************************************************/
public InternetChecker(String baseUrl) {
this.baseUrl = baseUrl;
}
/*******************************
* Will check if there is an internet connection present
* and update the status accordingly
*******************************/
#Override
public void run() {
// Check if base internet connection
// is working, if it is we continue
// to see if domain connection is working
try {
if ("127.0.0.1".equals(InetAddress.getLocalHost().getHostAddress())) {
setStatus(Status.INTERNET_DISCONNECTED);
return;
}
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
// Check if base domain connection is working
try {
final URL url = new URL(baseUrl);
final URLConnection conn = url.openConnection();
conn.connect();
conn.getInputStream().close();
setStatus(Status.ACTIVE);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (IOException e) {
setStatus(Status.BASE_URL_UNREACHABLE);
}
}
/****************************************************************
********** ACCESSORS ************
****************************************************************/
public Status getStatus() {
return status.get();
}
public ReadOnlyObjectProperty<Status> statusProperty() {
return status.getReadOnlyProperty​();
}
private void setStatus(final Status status) {
Platform.runLater(() -> this.status.set(status));
}
/*******************************
* ACTIVE (Base url reachable)
* BASE_URL_UNREACHABLE (Internet available, but base url is unreachable)
* INTERNET_DISCONNECTED (Internet is not available)
********************************/
public enum Status {
ACTIVE,
BASE_URL_UNREACHABLE,
INTERNET_DISCONNECTED;
}
}
InternetChecker checker = new InternetChecker(url);
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor​();
// use delay here to avoid race condition
executorService.scheduleAtFixedDelay(checker, 0, millisCheckInterval, TimeUnit.MILLISECONDS);
Note that you need to shut down the service "manually" or use a ThreadFactory returning daemon threads:
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor​(r -> {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
});
Related
I have a problem. I created this class that opens a websocket stream with Binance:
public class AccountStream extends Driver {
private Integer agentId;
private String API_KEY;
private String SECRET;
private String listenKey;
private Order newOrder;
private String LOG_FILE_PATH;
public AccountStream(Integer agentId) {
this.agentId = agentId;
// Load binance config
HashMap<String, String> binanceConfig = MainDriver.getBinanceConfig(agentId);
API_KEY = binanceConfig.get("api_key");
SECRET = binanceConfig.get("secret");
startAccountEventStreaming();
setConnectionCheckScheduler();
}
private void startAccountEventStreaming() {
BinanceApiClientFactory factory = BinanceApiClientFactory.newInstance(API_KEY, SECRET);
BinanceApiRestClient client = factory.newRestClient();
// First, we obtain a listenKey which is required to interact with the user data stream
listenKey = client.startUserDataStream();
// Then, we open a new web socket client, and provide a callback that is called on every update
BinanceApiWebSocketClient webSocketClient = factory.newWebSocketClient();
// Listen for changes in the account
webSocketClient.onUserDataUpdateEvent(listenKey, response -> {
System.out.println(response);
});
// Ping the datastream every 30 minutes to prevent a timeout
ScheduledExecutorService keepAliveUserDataStreamPool = Executors.newScheduledThreadPool(1);
Runnable pingUserDataStream = () -> {
client.keepAliveUserDataStream(listenKey);
};
keepAliveUserDataStreamPool.scheduleWithFixedDelay(pingUserDataStream, 0, 30, TimeUnit.MINUTES);
}
private void setConnectionCheckScheduler() {
ScheduledExecutorService checkConnectionPool = Executors.newScheduledThreadPool(1);
Runnable checkConnectionTask = () -> {
if (!MainDriver.connected) {
// DESTORY ENTIRE CLASS HERE
}
};
checkConnectionPool.scheduleWithFixedDelay(checkConnectionTask, 0, 1, TimeUnit.SECONDS);
}
}
Now the setConnectionCheckScheduler() schedules a check for a specific variable, which gets set to true if the program lost connection to the internet, but the function also has to stop the code from continuing with pinging the API. Actually I want to do some kind of return which quits the entire class code, so I need to create a new instance of the class to start the API stream again.
How can I cancel all the code (actually destroy the class) in this class from within the Runnable checkConnectionTask?
Basically you need to close/clean up the resources/running threads and run the logic from the beginning, wrapping start logic to separate method and add one for clean up will help:
private void start() {
startAccountEventStreaming();
setConnectionCheckScheduler();
}
private void shutdown() {
// possibly more code to make sure resources closed gracefully
webSocketClient.close();
keepAliveUserDataStreamPool.shutdown();
checkConnectionPool.shutdown();
}
private void restart() {
shutdown();
start();
}
so you just call restart in your checker:
Runnable checkConnectionTask = () -> {
if (!MainDriver.connected) {
restart();
}
};
I have an application which requires a login and does not allow multiple logins of same account at a given time. So i have upto 60 such testcases. Currently i am handling their parallel execution as follows:
Login in the #BeforeSuite with CredzSet1 by initializing a driver object. Save the cookies.
In the #BeforeMethod set the cookies to the new driver object created and refresh. this displays the homepage of the application ie successfull login
I have come up with this method to avoid login/logout step for each testcase and to make parallel execution possible with a single credential set.
Now this method works just fine but i was thinking if there is any other alternate way as one thread gets occupied by the #BeforeSuite driver object for the entire execution time as i close that object only at the end of execution.?? want to utilize all the threads for parallel execution.
Here is the code:
public class TestConfigs {
protected static SelectEnv loadedEnv;
protected static BrowserManager factory;
protected static String url = null;
private static Set<Cookie> cookies;
#BeforeSuite(alwaysRun = true)
public void startSuite() {
try{
loadedEnv = ConfigFactory.create(SelectEnv.class);
//Initiate browser
factory = BrowserManager.getInstance();
factory.setAnyDriver("chrome");
factory.getDriver().get(ConfigFactory.getProperty("url"));
Login login = new Login(factory.getDriver(), db);
//Logging in
login.verifyLogin(loadedEnv.usernameAU(), loadedEnv.password(),
loadedEnv.accesscode());
Thread.sleep(5000);
//Saving cookies
cookies = factory.getDriver().manage().getCookies();
}catch(Exception e) {
System.out.println("Exception in #BeforeSuite method");
e.printStackTrace();
}
}
#BeforeMethod(alwaysRun = true)
public synchronized void testSetup(Method method, Object[] obj) {
try {
System.out.println("********* In Before Method***************");
//Initiate browser
factory = BrowserManager.getInstance();
// Initiate browser
factory.setAnyDriver("chrome");
factory.getDriver().get(url);
if(cookies!=null) {
cookies.forEach( cookie -> factory.getDriver().manage().addCookie( cookie ));
factory.getDriver().get(url);
}
} catch (Exception e) {
e.printStackTrace();
}
}
#AfterMethod(alwaysRun = true)
public void cleanUp() {
factory.getDriver().close();
factory.getDriver().quit();
}
#AfterSuite(alwaysRun = true)
public void afterSuiteCleanup() {
if(factory!=null && factory.getDriver() !=null) {
factory.getDriver().close();
}
}
}
You can create a synchronized method as explained in https://www.baeldung.com/java-synchronized.
With a synchronized method only one thread can use the method at a time. Thus ensuring that only one logon occurs at a time.
So if you convert login.verifyLogin() to a synchronized method this should work.
Here is my DataClientFactory class.
public class DataClientFactory {
public static IClient getInstance() {
return ClientHolder.INSTANCE;
}
private static class ClientHolder {
private static final DataClient INSTANCE = new DataClient();
static {
new DataScheduler().startScheduleTask();
}
}
}
Here is my DataClient class.
public class DataClient implements IClient {
private ExecutorService service = Executors.newFixedThreadPool(15);
private RestTemplate restTemplate = new RestTemplate();
// for initialization purpose
public DataClient() {
try {
new DataScheduler().callDataService();
} catch (Exception ex) { // swallow the exception
// log exception
}
}
#Override
public DataResponse getDataSync(DataKey dataKeys) {
DataResponse response = null;
try {
Future<DataResponse> handle = getDataAsync(dataKeys);
response = handle.get(dataKeys.getTimeout(), TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
// log error
response = new DataResponse(null, DataErrorEnum.CLIENT_TIMEOUT, DataStatusEnum.ERROR);
} catch (Exception e) {
// log error
response = new DataResponse(null, DataErrorEnum.ERROR_CLIENT, DataStatusEnum.ERROR);
}
return response;
}
#Override
public Future<DataResponse> getDataAsync(DataKey dataKeys) {
Future<DataResponse> future = null;
try {
DataTask dataTask = new DataTask(dataKeys, restTemplate);
future = service.submit(dataTask);
} catch (Exception ex) {
// log error
}
return future;
}
}
I get my client instance from the above factory as shown below and then make a call to getDataSync method by passing DataKey object. DataKey object has userId and Timeout values in it. Now after this, call goes to my DataTask class to call method as soon as handle.get is called.
IClient dataClient = DataClientFactory.getInstance();
long userid = 1234l;
long timeout_ms = 500;
DataKey keys = new DataKey.Builder().setUserId(userid).setTimeout(timeout_ms)
.remoteFlag(false).secondaryFlag(true).build();
// call getDataSync method
DataResponse dataResponse = dataClient.getDataSync(keys);
System.out.println(dataResponse);
Here is my DataTask class which has all the logic -
public class DataTask implements Callable<DataResponse> {
private DataKey dataKeys;
private RestTemplate restTemplate;
public DataTask(DataKey dataKeys, RestTemplate restTemplate) {
this.restTemplate = restTemplate;
this.dataKeys = dataKeys;
}
#Override
public DataResponse call() {
DataResponse dataResponse = null;
ResponseEntity<String> response = null;
int serialId = getSerialIdFromUserId();
boolean remoteFlag = dataKeys.isRemoteFlag();
boolean secondaryFlag = dataKeys.isSecondaryFlag();
List<String> hostnames = new LinkedList<String>();
Mappings mappings = ClientData.getMappings(dataKeys.whichFlow());
String localPrimaryAdress = null;
String remotePrimaryAdress = null;
String localSecondaryAdress = null;
String remoteSecondaryAdress = null;
// use mappings object to get above Address by using serialId and basis on
// remoteFlag and secondaryFlag populate the hostnames linked list
if (remoteFlag && secondaryFlag) {
hostnames.add(localPrimaryHostIPAdress);
hostnames.add(localSecondaryHostIPAdress);
hostnames.add(remotePrimaryHostIPAdress);
hostnames.add(remoteSecondaryHostIPAdress);
} else if (remoteFlag && !secondaryFlag) {
hostnames.add(localPrimaryHostIPAdress);
hostnames.add(remotePrimaryHostIPAdress);
} else if (!remoteFlag && !secondaryFlag) {
hostnames.add(localPrimaryHostIPAdress);
} else if (!remoteFlag && secondaryFlag) {
hostnames.add(localPrimaryHostIPAdress);
hostnames.add(localSecondaryHostIPAdress);
}
for (String hostname : hostnames) {
// If host name is null or host name is in local block host list, skip sending request to this host
if (hostname == null || ClientData.isHostBlocked(hostname)) {
continue;
}
try {
String url = generateURL(hostname);
response = restTemplate.exchange(url, HttpMethod.GET, dataKeys.getEntity(), String.class);
// make DataResponse
break;
} catch (HttpClientErrorException ex) {
// make DataResponse
return dataResponse;
} catch (HttpServerErrorException ex) {
// make DataResponse
return dataResponse;
} catch (RestClientException ex) {
// If it comes here, then it means some of the servers are down.
// Add this server to block host list
ClientData.blockHost(hostname);
// log an error
} catch (Exception ex) {
// If it comes here, then it means some weird things has happened.
// log an error
// make DataResponse
}
}
return dataResponse;
}
private String generateURL(final String hostIPAdress) {
// make an url
}
private int getSerialIdFromUserId() {
// get the id
}
}
Now basis on userId, I will get the serialId and then get the list of hostnames, I am suppose to make a call depending on what flag is passed. Then I iterate the hostnames list and make a call to the servers. Let's say, if I have four hostnames (A, B, C, D) in the linked list, then I will make call to A first and if I get the data back, then return the DataResponse back. But suppose if A is down, then I need to add A to block list instantly so that no other threads can make a call to A hostname. And then make a call to hostname B and get the data back and return the response (or repeat the same thing if B is also down).
I have a background thread as well which runs every 10 minutes and it gets started as soon we get the client instance from the factory and it parses my another service URL to get the list of block hostnames that we are not supposed to make a call. Since it runs every 10 minutes so any servers which are down, it will get the list after 10 minutes only, In general suppose if A is down, then my service will provide A as the block list of hostnames and as soon as A becomes up, then that list will be updated as well after 10 minutes.
Here is my background thread code DataScheduler-
public class DataScheduler {
private RestTemplate restTemplate = new RestTemplate();
private static final Gson gson = new Gson();
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public void startScheduleTask() {
scheduler.scheduleAtFixedRate(new Runnable() {
public void run() {
try {
callDataService();
} catch (Exception ex) {
// log an error
}
}
}, 0, 10L, TimeUnit.MINUTES);
}
public void callDataService() throws Exception {
String url = null;
// execute the url and get the responseMap from it as a string
parseResponse(responseMap);
}
private void parseResponse(Map<FlowsEnum, String> responses) throws Exception {
// .. some code here to calculate partitionMappings
// block list of hostnames
Map<String, List<String>> coloExceptionList = gson.fromJson(response.split("blocklist=")[1], Map.class);
for (Map.Entry<String, List<String>> entry : coloExceptionList.entrySet()) {
for (String hosts : entry.getValue()) {
blockList.add(hosts);
}
}
if (update) {
ClientData.setAllMappings(partitionMappings);
}
// update the block list of hostnames
if (!DataUtils.isEmpty(responses)) {
ClientData.replaceBlockedHosts(blockList);
}
}
}
And here is my ClientData class which holds all the information for block list of hostnames and partitionMappings details (which is use to get the list of valid hostnames).
public class ClientData {
private static final AtomicReference<ConcurrentHashMap<String, String>> blockedHosts = new AtomicReference<ConcurrentHashMap<String, String>>(
new ConcurrentHashMap<String, String>());
// some code here to set the partitionMappings by using CountDownLatch
// so that read is blocked for first time reads
public static boolean isHostBlocked(String hostName) {
return blockedHosts.get().contains(hostName);
}
public static void blockHost(String hostName) {
blockedHosts.get().put(hostName, hostName);
}
public static void replaceBlockedHosts(List<String> blockList) {
ConcurrentHashMap<String, String> newBlockedHosts = new ConcurrentHashMap<>();
for (String hostName : blockList) {
newBlockedHosts.put(hostName, hostName);
}
blockedHosts.set(newBlockedHosts);
}
}
Problem Statement:-
When all the servers are up (A,B,C,D as an example) above code works fine and I don't see any TimeoutException happening at all from the handle.get but if let's say one server (A) went down which I was supposed to make a call from the main thread then I start seeing lot of TimeoutException, by lot I mean, huge number of client timeouts happening.
And I am not sure why this is happening? In general this won't be happening right since as soon as the server goes down, it will get added to blockList and then no thread will be making a call to that server, instead it will try another server in the list? So it should be smooth process and then as soon as those servers are up, blockList will get updated from the background thread and then you can start making a call.
Is there any problem in my above code which can cause this problem? Any suggestions will be of great help.
In general, what I am trying to do is - make a hostnames list depending on what user id being passed by using the mappings object. And then make a call to the first hostname and get the response back. But if that hostname is down, then add to the block list and make a call to the second hostname in the list.
Here is the Stacktrace which I am seeing -
java.util.concurrent.TimeoutException\n\tat java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:258)
java.util.concurrent.FutureTask.get(FutureTask.java:119)\n\tat com.host.client.DataClient.getDataSync(DataClient.java:20)\n\tat
NOTE: For multiple userId's, we can have same server, meaning server A can get resolve to multiple userId's.
In DataClient class, at the below line:
public class DataClient implements IClient {
----code code---
Future<DataResponse> handle = getDataAsync(dataKeys);
//BELOW LINE IS PROBLEM
response = handle.get(dataKeys.getTimeout(), TimeUnit.MILLISECONDS); // <--- HERE
} catch (TimeoutException e) {
// log error
response = new DataResponse(null, DataErrorEnum.CLIENT_TIMEOUT, DataStatusEnum.ERROR);
} catch (Exception e) {
// log error
response = new DataResponse(null, DataErrorEnum.ERROR_CLIENT, DataStatusEnum.ERROR);
----code code-----
You have assigned a timeout to handle.get(...), which is timing out before your REST connections can respond. The rest connections themselves may or may not be timing out, but since you are timing out of get method of future before the completion of the execution of the thread, the blocking of hosts has no visible effect, while the code inside the call method of DataTask may be performing as expected. Hope this helps.
You asked about suggestions, so here are some suggestions:
1.) Unexpected return value
Method returns unexpectedly FALSE
if (ClientData.isHostBlocked(hostname)) //this may return always false! please check
2.) Exception-Handling
Are you really sure, that a RestClientException occurs?
Only when this exception occured, the host will be added to blocked list!
Your posted code seems to ignore logging (it is commented out!)
...catch (HttpClientErrorException ex) {
// make DataResponse
return dataResponse;
} catch (HttpServerErrorException ex) {
// make DataResponse
return dataResponse;
} catch (RestClientException ex) {
// If it comes here, then it means some of the servers are down.
// Add this server to block host list
ClientData.blockHost(hostname);
// log an error
} catch (Exception ex) {
// If it comes here, then it means some weird things has happened.
// log an error
// make DataResponse
}
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.
I want to load an image at periodic intervals to an imageitem. My outer class is generating the URL and I need to pass it to the innerclass. How do I achieve this?
public class MapTimer extends TimerTask{
public void run() {
System.out.println("Map starting...");
String URL=null,serverquery=null;
try {
sendMessage(this.message);
item.setLabel(item.getLabel()+"start");
serverquery=receiveMessage();
item.setLabel(item.getLabel()+"stop");
URL = getURL(serverquery); // my url to be passed to innerclass
System.out.println("URl is "+serverquery);
item.setLabel(URL+item.getLabel());
Thread t = new Thread() {
public void run() {
item.setLabel(item.getLabel()+"6");
try {
Image image = loadImage(URL); // using url
System.out.println("GEtting image....");
item = new ImageItem(null, image, 0, null);
form.append(item);
display.setCurrent(form);
} catch (IOException ioe) {
item.setLabel("Error1");
}
catch (Exception ioe) {
item.setLabel("Error1");
}
}
};
t.start(); // write post-action user code here
}
catch(Exception e){
System.out.println("Error3"+e);
}
}
}
How do I pass the URL to my innerthread class?
You have to declare the variable final or don't use a variable but a field in the class.
public class YourClass {
private String url;
public void yourMethod {
url = getURL(serverquery);
System.out.println("URl is "+serverquery);
item.setLabel(URL+item.getLabel());
Thread t = new Thread() {
public void run() {
item.setLabel(item.getLabel()+"6");
try {
Image image = loadImage(url); // using url
System.out.println("GEtting image....");
item = new ImageItem(null, image, 0, null);
form.append(item);
display.setCurrent(form);
} catch (IOException ioe) {
item.setLabel("Error1");
}
catch (Exception ioe) {
item.setLabel("Error1");
}
}
};
t.start(); // write post-action user code here
}
}
It should work if your URL is contained in a final reference. This means that the reference won't move. Consequently, you can't initialize it to null and then call getURL, you need to declare it at this point:
final String URL = getURL(serverquery);
I found that there are (at least) 2 major ways of doing this on whever your thread gets the information or recieves the information (in your case URL)
1) gets the informations:
what you want to do is keep a reference of the object containing the data you need (like URL) in your Thread, and when your ready the Thread gets the next item/URL to load.
public class MapTimer extends TimerTask{
public void run() {
...
}
private URLGenerator urlGenerator = null;
public MapTimer(URLGenerator urlGen){
...
}
This will give you the option to get the next URL when your thread is free (with a if(urlGen != null) of course)
2) Receives Information
Works better for cases where the thread is called only when the rest of the program wants to (user inputing data etc)
public class MapTimer extends TimerTask{
public void run() {
...
}
private URL urlToLoad = null;
public void setURL(URL urlToLoad){
...
//store and wake up thread
}
this way your thread recieve with a setter the data it needs to run, processes it then waits for the next data to be send to it (of course you need to be careful of thread issues like multiple calls etc.)
Hope this helps you
Jason
Just make the field a static variable belonging to the class and not any object instance of the class, like this:
public YourClass {
private static String url;
//...
}
Please note however that the static variable will be shared among all object instances of the class.