diff options
Diffstat (limited to 'documentation/advanced/advanced-push.asciidoc')
-rw-r--r-- | documentation/advanced/advanced-push.asciidoc | 417 |
1 files changed, 417 insertions, 0 deletions
diff --git a/documentation/advanced/advanced-push.asciidoc b/documentation/advanced/advanced-push.asciidoc new file mode 100644 index 0000000000..df9279c6bb --- /dev/null +++ b/documentation/advanced/advanced-push.asciidoc @@ -0,0 +1,417 @@ +--- +title: Server Push +order: 16 +layout: page +--- + +[[advanced.push]] += Server Push + +When you need to update a UI from another UI, possibly of another user, or from +a background thread running in the server, you usually want to have the update +show immediately, not when the browser happens to make the next server request. +For this purpose, you can use __server push__ that sends the data to the browser +immediately. Push is based on a client-server connection, usually a WebSocket +connection, that the client establishes and the server can then use to send +updates to the client. + +The server-client communication is done by default with a WebSocket connection +if the browser and the server support it. If not, Vaadin will fall back to a +method supported by the browser. Vaadin Push uses a custom build of the +link:https://github.com/Atmosphere/atmosphere[Atmosphere framework] for +client-server communication. + +[[advanced.push.installation]] +== Installing the Push Support + +The server push support in Vaadin requires the separate Vaadin Push library. It +is included in the installation package as [filename]#vaadin-push.jar#. + +[[advanced.push.installation.ivy]] +=== Retrieving with Ivy + +With Ivy, you can get it with the following declaration in the +[filename]#ivy.xml#: + + +[source, xml] +---- +<dependency org="com.vaadin" name="vaadin-push" + rev="&vaadin.version;" conf="default->default"/> +---- + +In some servers, you may need to exlude a [literal]#++sl4j++# dependency as +follows: + + +[source, xml] +---- +<dependency org="com.vaadin" name="vaadin-push" + rev="&vaadin.version;" conf="default->default"> + <exclude org="org.slf4j" name="slf4j-api"/> +</dependency> +---- + +Pay note that the Atmosphere library is a bundle, so if you retrieve the +libraries with Ant, for example, you need to retrieve +[literal]#++type="jar,bundle"++#. + + +[[advanced.push.installation.maven]] +=== Retrieving with Maven + +In Maven, you can get the push library with the following dependency in the POM: + + +[source, xml] +---- +<dependency> + <groupId>com.vaadin</groupId> + <artifactId>vaadin-push</artifactId> + <version>${vaadin.version}</version> +</dependency> +---- + + + +[[advanced.push.enabling]] +== Enabling Push for a UI + +To enable server push, you need to define the push mode either in the deployment +descriptor or with the [classname]#@Push# annotation for the UI. + +[[advanced.push.enabling.pushmode]] +=== Push Modes + +You can use server push in two modes: [literal]#++automatic++# and +[literal]#++manual++#. The automatic mode pushes changes to the browser +automatically after access() finishes. With the manual mode, you can do the push +explicitly with [methodname]#push()#, which allows more flexibility. + + +[[advanced.push.enabling.pushmode]] +=== The [classname]#@Push# annotation + +You can enable server push for a UI with the [classname]#@Push# annotation as +follows. It defaults to automatic mode ( [parameter]#PushMode.AUTOMATIC#). + + +[source, java] +---- +@Push +public class PushyUI extends UI { +---- + +To enable manual mode, you need to give the [parameter]#PushMode.MANUAL# +parameter as follows: + + +[source, java] +---- +@Push(PushMode.MANUAL) +public class PushyUI extends UI { +---- + + +[[advanced.push.enabling.servlet]] +=== Servlet Configuration + +You can enable the server push and define the push mode also in the servlet +configuration with the [parameter]#pushmode# parameter for the servlet in the +[filename]#web.xml# deployment descriptor. If you use a Servlet 3.0 compatible +server, you also want to enable asynchronous processing with the +[literal]#++async-supported++# parameter. Note the use of Servlet 3.0 schema in +the deployment descriptor. + + +[subs="normal"] +---- +<?xml version="1.0" encoding="UTF-8"?> +<web-app + id="WebApp_ID" version="**3.0**" + xmlns="**http://java.sun.com/xml/ns/javaee**" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="**http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd**"> + <servlet> + <servlet-name>Pushy UI</servlet-name> + <servlet-class> + com.vaadin.server.VaadinServlet</servlet-class> + + <init-param> + <param-name>UI</param-name> + <param-value>**com.example.my.PushyUI**</param-value> + </init-param> + + <!-- Enable server push --> + <init-param> + <param-name>pushmode</param-name> + <param-value>**automatic**</param-value> + </init-param> + <async-supported>**true**</async-supported> + </servlet> +</web-app> +---- + +[[advanced.push.running]] +== Accessing UI from Another Thread + +Making changes to a [classname]#UI# object from another thread and pushing them +to the browser requires locking the user session when accessing the UI. +Otherwise, the UI update done from another thread could conflict with a regular +event-driven update and cause either data corruption or deadlocks. Because of +this, you may only access an UI using the [methodname]#access()# method, which +locks the session to prevent conflicts. It takes a [interfacename]#Runnable# +which it executes as its parameter. + +For example: + + +[source, java] +---- +ui.access(new Runnable() { + @Override + public void run() { + series.add(new DataSeriesItem(x, y)); + } +}); +---- + +In Java 8, where a parameterless lambda expression creates a runnable, you could +simply write: + + +[source, java] +---- +ui.access(() -> + series.add(new DataSeriesItem(x, y))); +---- + +If the push mode is [literal]#++manual++#, you need to push the pending UI +changes to the browser explicitly with the [methodname]#push()# method. + + +[source, java] +---- +ui.access(new Runnable() { + @Override + public void run() { + series.add(new DataSeriesItem(x, y)); + ui.push(); + } +}); +---- + +Below is a complete example of a case where we make UI changes from another +thread. + + +[source, java] +---- +public class PushyUI extends UI { + Chart chart = new Chart(ChartType.AREASPLINE); + DataSeries series = new DataSeries(); + + @Override + protected void init(VaadinRequest request) { + chart.setSizeFull(); + setContent(chart); + + // Prepare the data display + Configuration conf = chart.getConfiguration(); + conf.setTitle("Hot New Data"); + conf.setSeries(series); + + // Start the data feed thread + new FeederThread().start(); + } + + class FeederThread extends Thread { + int count = 0; + + @Override + public void run() { + try { + // Update the data for a while + while (count < 100) { + Thread.sleep(1000); + + access(new Runnable() { + @Override + public void run() { + double y = Math.random(); + series.add( + new DataSeriesItem(count++, y), + true, count > 10); + } + }); + } + + // Inform that we have stopped running + access(new Runnable() { + @Override + public void run() { + setContent(new Label("Done!")); + } + }); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} +---- +See the http://demo.vaadin.com/book-examples-vaadin7/book#advanced.push.simple[on-line example, window="_blank"]. + +When sharing data between UIs or user sessions, you need to consider the +message-passing mechanism more carefully, as explained next. + + +[[advanced.push.pusharound]] +== Broadcasting to Other Users + +Broadcasting messages to be pushed to UIs in other user sessions requires having +some sort of message-passing mechanism that sends the messages to all UIs that +register as recipients. As processing server requests for different UIs is done +concurrently in different threads of the application server, locking the threads +properly is very important to avoid deadlock situations. + +[[advanced.push.pusharound.broadcaster]] +=== The Broadcaster + +The standard pattern for sending messages to other users is to use a +__broadcaster__ singleton that registers the UIs and broadcasts messages to them +safely. To avoid deadlocks, it is recommended that the messages should be sent +through a message queue in a separate thread. Using a Java +[classname]#ExecutorService# running in a single thread is usually the easiest +and safest way. + + +[source, java] +---- +public class Broadcaster implements Serializable { + static ExecutorService executorService = + Executors.newSingleThreadExecutor(); + + public interface BroadcastListener { + void receiveBroadcast(String message); + } + + private static LinkedList<BroadcastListener> listeners = + new LinkedList<BroadcastListener>(); + + public static synchronized void register( + BroadcastListener listener) { + listeners.add(listener); + } + + public static synchronized void unregister( + BroadcastListener listener) { + listeners.remove(listener); + } + + public static synchronized void broadcast( + final String message) { + for (final BroadcastListener listener: listeners) + executorService.execute(new Runnable() { + @Override + public void run() { + listener.receiveBroadcast(message); + } + }); + } +} +---- +See the http://demo.vaadin.com/book-examples-vaadin7/book#advanced.push.pusharound[on-line example, window="_blank"]. + +In Java 8, you could use lambda expressions for the listeners instead of the +interface, and a parameterless expression to create the runnable: + + +[source, java] +---- +for (final Consumer<String> listener: listeners) + executorService.execute(() -> + listener.accept(message)); +---- +See the http://demo.vaadin.com/book-examples-vaadin7/book#advanced.push.pusharound[on-line example, window="_blank"]. + + +[[advanced.push.pusharound.receiving]] +=== Receiving Broadcasts + +The receivers need to implement the receiver interface and register to the +broadcaster to receive the broadcasts. A listener should be unregistered when +the UI expires. When updating the UI in a receiver, it should be done safely as +described earlier, by executing the update through the [methodname]#access()# +method of the UI. + + +[source, java] +---- +@Push +public class PushAroundUI extends UI + implements Broadcaster.BroadcastListener { + + VerticalLayout messages = new VerticalLayout(); + + @Override + protected void init(VaadinRequest request) { + ... build the UI ... + + // Register to receive broadcasts + Broadcaster.register(this); + } + + // Must also unregister when the UI expires + @Override + public void detach() { + Broadcaster.unregister(this); + super.detach(); + } + + @Override + public void receiveBroadcast(final String message) { + // Must lock the session to execute logic safely + access(new Runnable() { + @Override + public void run() { + // Show it somehow + messages.addComponent(new Label(message)); + } + }); + } +} +---- +See the http://demo.vaadin.com/book-examples-vaadin7/book#advanced.push.pusharound[on-line example, window="_blank"]. + + +[[advanced.push.pusharound.sending]] +=== Sending Broadcasts + +To send broadcasts with a broadcaster singleton, such as the one described +above, you would only need to call the [methodname]#broadcast()# method as +follows. + + +[source, java] +---- +final TextField input = new TextField(); +sendBar.addComponent(input); + +Button send = new Button("Send"); +send.addClickListener(new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + // Broadcast the message + Broadcaster.broadcast(input.getValue()); + + input.setValue(""); + } +}); +---- +See the http://demo.vaadin.com/book-examples-vaadin7/book#advanced.push.pusharound[on-line example, window="_blank"]. + + + + + |