So my project demonstrates the results of a k-means
clustering algorithm using servlets.
When I cluster for the first time, everything works well and my program relocates the user to the results.jsp
page.
But because k-means
is a bit random at selecting centroids, I added a re-cluster
feature in my code in the results.jsp
which re-clusters my data with the settings originally used.
My servlet code for re-clustering looks like this:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
do stuff...
//reload results page
String htmlResponse = "<html><header><meta http-equiv=\"refresh\" content=\"0; URL='results.jsp\" /></header></html>";
writer.println(htmlResponse);
//but this doesnt work...
}
So after the clustering is done, I want to reload the resutls.jsp
page. I tried adding a JavaScript
to my jsp
page since refresh
is a client side operation. But the problem with that approach is that refresh
happens before the clustering is fully pleted. So I get Exceptions
when trying to print the results. Any advice?
My refresh
script looks like this:
<script type="text/javascript">
function refresh() {
location.reload(true);
}
</script>
<script type="text/javascript">
function reCluster(cluster_id) {
recluster();
}
</script>
and on the .jsp
i'm calling this like this:
<form class="form-horizontal" action="generateArff" method="GET" enctype="multipart/form-data">
<button type="button" class="btn btn-default btn-lg" onclick="reCluster(); refresh();">
<span class="glyphicon glyphicon-refresh"></span> Re-Cluster
</button>
<button type="submit" class="btn btn-info btn-lg">
<span class="glyphicon glyphicon-download"></span> Generate file
</button>
</form>
Can you advice on the matter?
So my project demonstrates the results of a k-means
clustering algorithm using servlets.
When I cluster for the first time, everything works well and my program relocates the user to the results.jsp
page.
But because k-means
is a bit random at selecting centroids, I added a re-cluster
feature in my code in the results.jsp
which re-clusters my data with the settings originally used.
My servlet code for re-clustering looks like this:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
do stuff...
//reload results page
String htmlResponse = "<html><header><meta http-equiv=\"refresh\" content=\"0; URL='results.jsp\" /></header></html>";
writer.println(htmlResponse);
//but this doesnt work...
}
So after the clustering is done, I want to reload the resutls.jsp
page. I tried adding a JavaScript
to my jsp
page since refresh
is a client side operation. But the problem with that approach is that refresh
happens before the clustering is fully pleted. So I get Exceptions
when trying to print the results. Any advice?
My refresh
script looks like this:
<script type="text/javascript">
function refresh() {
location.reload(true);
}
</script>
<script type="text/javascript">
function reCluster(cluster_id) {
recluster();
}
</script>
and on the .jsp
i'm calling this like this:
<form class="form-horizontal" action="generateArff" method="GET" enctype="multipart/form-data">
<button type="button" class="btn btn-default btn-lg" onclick="reCluster(); refresh();">
<span class="glyphicon glyphicon-refresh"></span> Re-Cluster
</button>
<button type="submit" class="btn btn-info btn-lg">
<span class="glyphicon glyphicon-download"></span> Generate file
</button>
</form>
Can you advice on the matter?
Share
Improve this question
edited Feb 24, 2017 at 18:21
Jack
asked Feb 23, 2017 at 22:35
JackJack
7223 gold badges9 silver badges26 bronze badges
10
- Sorry, to me it is not clear where the exceptions are ing from - are they thrown by code inside the jsp you are trying to refresh? Do they get thrown the first time too or only after refresh? – Marco Bolis Commented Feb 23, 2017 at 22:38
- You could do call the service from the client using AJAX or similar, and when the job is done, receive the callback. – José Quinto Zamora Commented Feb 23, 2017 at 22:41
-
@MarcoBolis for sanity checks, I setted up the clusters so initially they have an
index of -1
so if something was going on during my implementation I would pick up the error. For the re-cluster process, I 'm reseting all the existing clusters to their original settings. akaindex -1
so when I print them because they are stored in anArrayList
, well you can imagine what will happen withindex of -1
– Jack Commented Feb 24, 2017 at 11:45 -
@JoseQuintoZamora I'm already doing that. I'm calling the service from
AJAX
. Thing is that after the service is called, the client goes to therefresh
service very fast, before there is enough time to calculate thenew
clusters – Jack Commented Feb 24, 2017 at 11:47 - @Jack if you are calling the service through AJAX, then why are you refreshing? Sorry, I'm trying to better understand the issue – Marco Bolis Commented Feb 24, 2017 at 12:05
2 Answers
Reset to default 4So, @MarcoBolis provided a very good and informative answer, and definitely gets a +1 from me.
However, I think that the most simple solutions are the most effective ones.
As I see your code, I can only assume that you are using an AJAX
request to your servlet
because I see 1 html form
with 2 buttons that do different things. So, what I would do, is go to your ReClusterServlet
and add the following:
public class ReClusterServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter writer = response.getWriter();
//Call your Cluster class and methods...
//wait for it...
//return something to your AJAX call.
writer.print("refresh page");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
now, go to your AJAX
request.
I assume it will be in the recluster()
function I see you are calling in the head
of your http
code.
so it must be something like this:
function recluster() {
$.ajax({
url:'ReClusterServlet',
type:'POST'
});
}
This thing, is calling the servlet
but did you know that when your servlet was done with the putation it returned a success
message to it??? But you are ignoring it!!!
it's this line writer.print("refresh page");
Now re-write the function as follows:
function recluster() {
$.ajax({
url:'ReClusterServlet',
type:'POST',
success:function() { //handle the successful return by reloading the current page
location.reload();
}
});
}
Again, these are speculations because they seem a bit inplete, so feel free to correct me if I'm wrong, but I have a strong feeling that you need to study a bit more on the client-server architecture
, JQuery
and AJAX
I understand you are issuing an AJAX call to the server to start an asynchronous task, performing the clustering.
You could notify the client of task pletion by means of a message returned through the XHR:
function reCluster() {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'ReClusterServlet', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
// Refresh if everything went OK
if (xhr.responseText === 'Done') refresh();
}
};
}
The tricky part is handling the notification Java-side.
If you are using Servlet API 3.x+, then you are lucky: just make your request an async one:
public class ReClusterServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// Defer the request to this HTTP request to later
AsyncContext asyncCtx = request.startAsync(request, response);
asyncCtx.setTimeout(0);
// Start the long running putation in another thread,
// passing the AsyncContext
new MyLongRunningTask(asyncCtx).start();
}
}
Then plete it in the other thread:
public class MyLongRunningTask {
private final AsyncContext asyncCtx;
public MyLongRunningTask(AsyncContext asyncCtx) {
this.asyncCtx = asyncCtx;
}
// ...
private void finish() {
// Send the response asynchronously
Writer out = this.asyncCtx.getResponse().getWriter();
out.println("Done");
out.flush();
this.asyncContext.plete();
}
}
If instead you are using an older version of the Servlet API, you will have to make the serving thread (the one on which the doGet
method gets called) block until the putation is finished.
If you have a reference to the thread on which the putation is performed available, you can call its join
method to await termination:
public class ReClusterServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Thread async = new MyLongRunningThread();
async.start();
// Await end of putation
try {
async.join();
} catch (InterruptedException e) {
// Computation was interrupted...
}
response.getWriter().println("Done");
}
}
If instead the putation task is queued for execution, for example by an ExecutorService
, you have to use wait/notify
:
public class ReClusterServlet extends HttpServlet {
private ExecutorService executor;
// ...
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
MyLongRunningRunnable task = new MyLongRunningRunnable();
synchronized (task) {
executor.execute(task);
// Put current thread to sleep until task is no more RUNNING
// (assuming state is a property of MyLongRunningRunnable)
while (task.getState() == State.RUNNING) {
try {
task.wait();
} catch (InterruptedException e) {
// Computation was interrupted...
}
}
}
response.getWriter().println("Done");
}
}
Assuming that the async task calls notify
when done:
public class MyLongRunningRunnable implements Runnable {
private State state = State.RUNNING;
// ...
private synchronized void finish() {
this.state = State.DONE;
this.notify();
}
}
NOTE that these two solutions that block the request thread may cause performance issues if you have lots of ining requests and the clustering job takes very long.