Search code examples
javaswinguser-interfacejavafxblocking

JavaFX Block the UI (and the code) until a server call is finished


This topic is around in countless articles of "not blocking the JavaFX UI during a long lasting call into something time consuming (server)". I know that and I googled and tried a lot. Most of the "Internet" explains that long lasting calls caused by JavaFX events need to be made in an extra thread. After the thread is finished, you update the FX UI with the results via Platform.runLater(). Most of the FX Libraries encapsulate this with sophisticated code constructs (really cool stuff). My current problem with it is: We are migrating a Swing rich client to JavaFX. It is a huge one, so we have to constantly include/replace JavaFX UI into it, until it is a full FX client. There is some functionality in the client that does a server call and has to wait before the user can continue with his work. The server uses JEE6 with stateless session beans and interfaces. The interfaces are known to the client and with a little library of our own, we implemented a little proxy hiding away the communication layer from the client. The client just creates a "RemoteProxy" with the library then just calling the remote interface and the library propagates the call to the server. The method is called and the result or Exception transported back to the client. For the client this appears like a local call. Here is the problem. A typical code fragment looks like this:

...
ServerRemoteInterface myServerRemoteProxy = Helper.getProxyForInterface(ServerRemoteInterface.class) ;
...
ComplexResult result = myServerRemoteProxy.doThingsOnServer(SerializableParameter p1 ...)
doSomethingUsefull() ;

The call to the server is triggered in the Swing UI thread (by a listener). It stops the execution of the program (Listener Code) although it is done in an extra thread. "doSomethingUsefull()" is called after the server got back. The developer does not have to take care about threading here. How is it accomplished? By using the "Spin Library" (http://spin.sourceforge.net/). It does some clever tricks with the Swing EDT. An alternative would be to use a modal Dialog, but we decided not not have an extra window, but have a glasspane disabling some UI components instead.

So long explanation and short question... Is there something similar for JavaFX helping us to seamlessly call a server, stop the program execution until it got back and NOT blocking the JavaFX UI? Best would be if it can work together with Java Swing parts of code.

EDIT... Adding an very compressed example for demonstration of the use with hidden JDialog.

We need the server remote interface. Any interface will do.

public interface ServerRemoteInterface
{
   public String method1() ; // String result in our case for test purposes
   public String method2() ; // Exceptions would also be possible.
}

Then we need the Proxy Invocation Handler

public class ServerProxy implements InvocationHandler
{
   public Object result;
   JDialog modalDialog = new JDialog((Frame)null, true);

   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
   {
      ServerCallHelper serverThread = new ServerCallHelper(args, method) ;
      modalDialog.setLocation(4000, 5000); // as if using Spin library. Here we use the dialog to block in a location off the screen.
      serverThread.start();
      modalDialog.setVisible(true) ;
      return result;
   }

   class ServerCallHelper extends Thread
   {
      Object[] args;
      Method method;

      public ServerCallHelper(Object[] args, Method method)
      {
         this.args = args;
         this.method = method;
      }

      public void run()
      {
         // do a server call via rmi or tunnel it via http, REST or whatever and provide the call parameters. On the server side there must be a receiver propagating the call to the wanted session bean.
         // here we just do a simulation
         try
         {
            Thread.sleep(3000);
         } catch (InterruptedException e)
         {
            // interupt is ok here.
         }

         // now hand the result from the call back. we simulate a fix result
         // We also could have caught the Exception and deal with it.
         result = "Simulated Result" ;
         // Since we are in the worker thread => inform EDT To close the dialog.
         SwingUtilities.invokeLater(()->modalDialog.setVisible(false));
      }

   }

}

And finally some code to show the functionality

public class SampleUI extends JFrame
{
   JButton serverCallButton = new JButton("Call server") ;
   JLabel resultLabel = new JLabel("No result so far") ;
   public SampleUI()
   {
      JPanel cont = new JPanel() ;
      cont.add(serverCallButton) ;
      cont.add(resultLabel) ;
      this.setContentPane(cont);

      serverCallButton.addActionListener((e)->processServerButtonCall());

   }

   private void processServerButtonCall()
   {
      ServerRemoteInterface serverAccess = (ServerRemoteInterface) Proxy.newProxyInstance(SampleUI.class.getClassLoader(), new Class[]{ServerRemoteInterface.class}, new ServerProxy());

      String result = serverAccess.method1() ;
      resultLabel.setText(result);


   }

   public static void main(String[] args)
   {
      SampleUI sampleUI = new SampleUI() ;
      sampleUI.pack();
      sampleUI.setVisible(true);
   }


}

The example is very compressed but should show the principle. As a developer I do not have to take care that the call to the server is really a server call. To me it's like a local call. I do not even have to take care that I am in the EDT Thread, because i just am. As I said it would work the same way in FX with a modal stage. I tried to set it to opaque 0.0 => It is not drawn anyway. This works.

The question remains: Are there ways to get around the extra JDialog or Stage ?


Solution

  • Like Tomas said the solution is a nested Event Loop. Currectly Java FX already has such an implementation:

    com.sun.javafx.tk.Toolkit.getToolkit()

    provides the methods enterNestedEventLoop and exitNestedEventLoop.

    From the package name you can tell that it is sun(oracle) specific and should not be used. I read that people already asked Oracle to move it it "Platform" because it is a very useful feature.

    Maybe we use it anyway :-)