Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to stop a java code from running using a stop button

I have a button that calls a method from the backing Bean. This method allows to extract data from parsing html code. While the method is running i have a dialog showing a progress bar and a command button Cancel. I need when the user click the cancel button the method called by the extract button stops.

This is my html code:

<p:commandButton
    value="Start" style="width: 12%;height: 100%"
    update=":confirmPurchase, :confirmPurchaseTest, :mainform" id="extractbutton"
    ajax="true" widgetVar="ButtonExtract"
    actionListener="#{mailMB.searchEmails()}" 
    icon="ui-icon-disk" styleClass="ui-priority-primary"
    onstart="blockUIWidget1.show();" oncomplete=" blockUIWidget1.hide();" />

<p:dialog  widgetVar="blockUIWidget1" header="Hitonclick" modal="true"
    resizable="false" closable="false">
    <table border="0" style="width: 500px">
        <tbody > 
            <tr>  
                <td>
                    <p:graphicImage url="pictures/loading81.gif" width="200" height="200" alt="animated-loading-bar"/>
                </td>
                <td>
                    <h:outputLabel value="Extracting is in progress. Please wait..."/>
                    <div align="center">
                        <p:commandButton value="Cancel" title="Cancel" />
                    </div>
                </td>
            </tr>
            <div align="right">

            </div>
        </tbody>
    </table>
</p:dialog>

And here is my searchEmails method in my sessionScoped Bean

 public void searchEmails() throws Exception {
        idCustomer = (String) session.getAttribute("idCustomer");
        System.out.println(idCustomer + " this is it");
        Customer customer = customerBusinessLocal.findById(idCustomer);
        data = dataBusinessLocal.createData(new Date(), number, keyword, moteur, customer, State.REJECTED);
        mails = mailBusinessLocal.createEmails(keyword, number, moteur, data);
        System.out.println("Method was invoked");    
 }

How can i stop the searchEmails method from running via the cancel command button?

like image 727
junior developper Avatar asked Mar 04 '14 08:03

junior developper


2 Answers

ExecutorService with interrupt friendly tasks

ExecutorService documentation


  • Instead of directly calling the method, convert the method into an ExecutorService's task.

    public class SearchEmailsTask implements Runnable {
    
        private EmailSearcher emailSearcher;
    
        public SearchEmailsTask(EmailSearcher emailSearcher) {
            this.emailSearcher = emailSearcher;
        }
    
        @Override
        public void run() {
            emailSearcher.searchEmails();
        }
    }
    

    You can use Callable<Object> if you want to return something.


  • When you want call the method, submit that task to an ExecutorService.

    ExecutorService executorService = Executors.newFixedThreadPool(4);
    
    SearchEmailsTask searchEmailsTask = new SearchEmailsTask(new EmailSearcher());
    
    Future<?> future = executorService.submit(searchEmailsTask);
    

  • Keep a reference to the task.

    private static Map <String, Future<Object>> results = new HashMap <String, Future<Object>>();
    

    A map should be a good idea to store multiple Future objects. You can of course go for something better if you want.


  • Call cancel on the task whenever required.

    future.cancel(true);
    

Note:
Task should have suitable checks for thread interruption for proper cancellation.
To achieve this, refer to

  1. Future task of ExecutorService not truly cancelling
  2. how to suspend thread using thread's id?

Good luck.

like image 56
Tanmay Patil Avatar answered Nov 08 '22 23:11

Tanmay Patil


With your current architecture it's not going to be easy. By looking at your code it seems that you are calling two big operations that can't be aborted in the middle. You must break the work into small units and make the processing abortable.

The easiest solution that uses your current code could look something like this.

 public class SessionScopedMailBean {
     volatile boolean searchAborted;

     public void searchEmails() throws Exception {
         searchAborted = false;
         while(!searchAborted) {
             // Process a single unit of work
         }
     }

     public void abortSearch() {
         searchAborted = true;
     }
}

In JSF:

<p:commandButton value="Cancel" title="Cancel" actionListener="#{mailMB.abortSearch()}" />

That said I'd recommend using executors and futures based approach for cleaner architecture. Then you can also implement a real progress bar not just an animated GIF spinner.

Another problem with performing a long operation in command handler is that eventually the HTTP request will time out.

A cleaner solution will look like this:

public class SessionScopedBean {
    ExecutorService pool;
    List<Future<Void>> pendingWork;

    @PostConstruct
    public void startPool() {
        pool = Executors.newFixedThreadPool(1);
    }

    @PreDestroy
    public void stopPool() {
        if(executor != null)
            pool.shutdownNow();
    }

    public void startSearch() {
        pendingWork = new ArrayList<Future<Void>>();

        // Prepare units of work (assuming it can be done quickly)
        while(/* no more work found */) {
            final MyType unit = ...; // Determine next unit of work
            Callable<Void> worker = new Callable<Void>() {
                @Override
                public Void call() throws Exception {
                    // Do whatever is needed to complete unit
                }
            };
            pendingWork.add(pool.submit(worker));
        }
    }

    public int getPending() {
        int nPending = 0;

        for(Future<MyResultType> i: pendingWork)
            if(!i.isDone()) nPending++;

        return nPending;
    }

    public void stopSearch() {
        for(Future<Void> i: pendingWork)
            if(!i.isDone()) i.cancel(true);
    }
}

Then use an AJAX polling component to poll getPending to determine when it's done and hide the window.

See also:

  • http://www.vogella.com/tutorials/JavaConcurrency/article.html#futures
  • JSF polling a database from an application bean at timed intervals
  • http://www.mkyong.com/spring/spring-postconstruct-and-predestroy-example/
like image 30
anttix Avatar answered Nov 08 '22 21:11

anttix