diff options
author | elmot <elmot@vaadin.com> | 2015-09-25 16:40:44 +0300 |
---|---|---|
committer | elmot <elmot@vaadin.com> | 2015-09-25 16:40:44 +0300 |
commit | a1b265c318dbda4a213cec930785b81e4c0f7d2b (patch) | |
tree | b149daf5a4f50b4f6446c906047cf86495fe0433 /documentation/advanced/advanced-cdi.asciidoc | |
parent | b9743a48a1bd0394f19c54ee938c6395a80f3cd8 (diff) | |
download | vaadin-framework-a1b265c318dbda4a213cec930785b81e4c0f7d2b.tar.gz vaadin-framework-a1b265c318dbda4a213cec930785b81e4c0f7d2b.zip |
Framework documentation IN
Change-Id: I767477c1fc3745f9e1f58075fe30c9ac8da63581
Diffstat (limited to 'documentation/advanced/advanced-cdi.asciidoc')
-rw-r--r-- | documentation/advanced/advanced-cdi.asciidoc | 997 |
1 files changed, 997 insertions, 0 deletions
diff --git a/documentation/advanced/advanced-cdi.asciidoc b/documentation/advanced/advanced-cdi.asciidoc new file mode 100644 index 0000000000..d5d135ca1f --- /dev/null +++ b/documentation/advanced/advanced-cdi.asciidoc @@ -0,0 +1,997 @@ +--- +title: Vaadin CDI Add-on +order: 17 +layout: page +--- + +[[advanced.cdi]] += Vaadin CDI Add-on + +((("Contexts and Dependency Injection", id="term.advanced.cdi.cdilong", range="startofrange"))) + + +((("CDI", id="term.advanced.cdi.cdi", range="startofrange"))) + + +((("Vaadin CDI Add-on", id="term.advanced.cdi.cdiaddon", range="startofrange"))) + + +Vaadin CDI add-on makes it easier to use contexts and dependency injection (CDI) +in Vaadin applications. CDI is a Java EE feature especially targeted for web +applications, which have well-defined contextual scopes, such as sessions, +views, requests, and so forth. The lifecycle of objects, such as beans, can be +managed by binding their lifecycles to such contexts. Vaadin CDI enables these +features with two additional kinds of Vaadin-specific contextual scopes: UIs and +navigation views. + +To learn more about Vaadin CDI, the link:[Vaadin CDI Tutorial] gives a hands-on +introduction. The source code of the CDI Tutorial demo is available for browsing +or cloning at https://github.com/vaadin-samples/cdi-tutorial. + +[[advanced.cdi.cdi]] +== CDI Overview + +Contexts and dependency injection, defined in the JSR-299 standard, is a Java EE +feature that, through a set of services, helps in improving application +architecture by decoupling the management of service object lifecycles from +client objects using them. The lifecycle of objects stored in a CDI container is +defined by a context. The managed objects or beans are accessed using dependency +injection. + +CDI builds on the Java concept of beans, but with somewhat different definition +and requirements. + +Regarding general CDI topics, such as use of qualifiers, interceptors, +decorators, event notifications, and other CDI features, we refer you to CDI +documentation. + +ifdef::web[] +* link:http://jaxenter.com/tutorial-introduction-to-cdi-contexts-and-dependency-injection-for-java-ee-jsr-299-104536.html[Introduction +to CDI]. Pete Muir and Mark Struberg, JAXenter. + +* link:http://docs.jboss.org/weld/reference/latest/en-US/html_single/[Weld - CDI +Reference Implementation] + +* link:http://cdi-spec.org/[CDI Specification] + +* link:https://vaadin.com/wiki?p_p_id=36&p_p_lifecycle=0&p_p_state=normal&p_p_mode=view&p_p_col_id=row-1&p_p_col_pos=1&p_p_col_count=3&p_r_p_185834411_title=Vaadin+CDI&p_r_p_185834411_nodeName=vaadin.com+wiki&_36_struts_action=%2Fwiki%2Fview[Vaadin +CDI Tutorial] + +endif::web[] + +[[advanced.cdi.cdi.injection]] +=== Dependency Injection + +__Dependency injection__ is a way to pass dependencies (service objects) to +dependent objects (clients) by injecting them in member variables or initializer +parameters, instead of managing the lifecycle in the clients or passing them +explicitly as parameters. In CDI, injection of a service object to a client is +specified by the [classname]#@Inject# annotation. + +For example, if we have a UI view that depends on user data, we could inject the +data in the client as follows: + + +[source, java] +---- +public class MainView extends CustomComponent implements View { + @Inject + User user; + + ... + @Override + public void enter(ViewChangeEvent event) { + greeting.setValue("Hello, " + user.getName()); + } +} +---- + +In addition to injecting managed beans with the annotation, you can query for +them from the bean manager. + + +[[advanced.cdi.cdi.contexts]] +=== Contexts and Scopes + +__Contexts__ in CDI are services that manage the lifecycle of objects and handle +their injection. Generally speaking, a context is a situation in which an +instance is used with a unique identity. Such objects are essentially +"singletons" in the context. While conventional singletons are application-wide, +objects managed by a CDI container can be "singletons" in a more narrow +__scope__: a user session, a particular UI instance associated with the session, +a view within the UI, or even just a single request. Such a context defines the +lifecycle of the object: its creation, use, and finally its destruction. + +As a very typical example in a web application, you would have a user data +object associated with a user session. + + +[source, java] +---- +@SessionScoped +public class User { + private String name; + + public void setName(String name) {this.name = name;} + public String getName() {return name;} +} +---- + +Now, when you need to refer to the user, you can use CDI injection to inject the +session-scoped "singleton" to a member variable or a constructor parameter. + + +[source, java] +---- +public class MainView extends CustomComponent implements View { + @Inject + User user; + + ... + + @Override + public void enter(ViewChangeEvent event) { + greeting.setValue("Hello, " + user.getName()); + } +} +---- + + + +[[advanced.cdi.installation]] +== Installing Vaadin CDI Add-on + +Vaadin CDI requires a Java EE 7 compatible servlet container, such as Glassfish +or Apache TomEE Web Profile, as mentioned for the reference toolchain in +<<dummy/../../../framework/getting-started/getting-started-environment#getting-started.environment,"Setting +up the Development Environment">>. + +To install the Vaadin CDI add-on, either define it as an Ivy or Maven dependency +or download it from the Vaadin Directory add-on page at +<<,vaadin.com/directory#addon/vaadin-cdi>>. See +<<dummy/../../../framework/addons/addons-overview.asciidoc#addons.overview,"Using +Vaadin Add-ons">> for general instructions for installing and using Vaadin +add-ons. + +The Ivy dependency is as follows: + +[subs="normal"] +---- + <dependency org="com.vaadin" name="vaadin-cdi" + rev="[replaceable]#latest.release#"/> +---- +The Maven dependency is as follows: + +[subs="normal"] +---- + <dependency> + <groupId>com.vaadin</groupId> + <artifactId>vaadin-cdi</artifactId> + <version>[replaceable]#LATEST#</version> + </dependency> + <dependency> + <groupId>javax.enterprise</groupId> + <artifactId>cdi-api</artifactId> + <version>[replaceable]#1.2#</version> + </dependency> +---- + +[[advanced.cdi.peparing]] +== Preparing Application for CDI + +A Vaadin application that uses CDI must have a file named [filename]#beans.xml# +in the [filename]#WEB-INF# directory. The file can be completely empty (it has +content only in certain limited situations), but it must be present. + +The application should not have a servlet extending [classname]#VaadinServlet#, +as Vaadin servlet has its own [classname]#VaadinCDIServlet# that is deployed +automatically. If you need multiple servlets or need to customize the Vaadin CDI +servlet, see <<advanced.cdi.deployment>>. + + +[[advanced.cdi.cdiui]] +== Injecting a UI with [classname]#@CDIUI# + +((("[classname]#@CDIUI#", id="term.advanced.cdi.cdiui", range="startofrange"))) + + +Vaadin CDI offers an easier way to instantiate UIs and to define the URL mapping +for them than the usual ways described in +<<dummy/../../../framework/application/application-environment#application.environment,"Deploying +an Application">>. To define a UI class that should be instantiated for a given +URL, you simply need to annotate the class with [classname]#@CDIUI#. It takes an +optional URL path as parameter. + + +[source, java] +---- +@CDIUI("myniceui") +@Theme("valo") +public class MyNiceUI extends UI { + ... +---- + +Giving empty UI path maps the UI to the root of the application context. + + +[source, java] +---- +@CDIUI("") +---- + +If the optional UI path is not given, the path is determined automatically from +the class name by removing a possible "-UI" suffix in the class name, making it +lower-case, and for capitalized letters, a hyphen is added. For example, a UI +with class name [classname]#MyNiceUI# would have path [literal]#++my-nice++#. +The URL consists of the server address, application context, and the UI path. +For example, when running a Vaadin application in a development workstation, you +would have URL such as http://localhost:8080/myproject/my-nice. + +UI path mappings are reported in the server log during deployment. + +See <<advanced.cdi.deployment>> for how to handle servlet URL mapping of CDI UIs +when working with multiple servlets in the same web application. + +(((range="endofrange", startref="term.advanced.cdi.cdiui"))) + +[[advanced.cdi.scopes]] +== Scopes + +((("CDI", "scopes", id="term.advanced.cdi.scopes", range="startofrange"))) + + +As in programming languages, where a variable name refers to a unique object +within the scope of the variable, a CDI scope is a context in which an object +has unique identity. In CDI, objects to be injected are identified by their type +and any qualifiers they may have. The scope can be defined as an annotation to +the service class as follows: + + +[source, java] +---- +@SessionScoped +public class User { + ... +---- + +CDI defines a number of scopes. Note that the standard CDI scopes are defined +under the [package]#javax.enterprise.context# package and Vaadin CDI scopes +under [package]#com.vaadin.cdi#, while JSF scopes are defined in +[package]#javax.faces.bean#. + +[[advanced.cdi.scopes.ui]] +=== UI Scope + +UI-scoped beans are uniquely identified within a UI instance, that is, a browser +window or tab. + +Vaadin CDI provides two annotations for the UI scope, differing in how they +enable proxies, as explained later. + +[classname]#@UIScoped#([package]#com.vaadin.cdi#):: ((("[classname]#@UIScoped#", id="term.advanced.cdi.scopes.uiscoped", range="startofrange"))) + + ++ +Injection with this annotation will create a direct reference to the bean rather +than a proxy. There are some limitations when not using proxies. Circular +references (injecting A to B and B to A) will not work, and neither do CDI +interceptors and decorators. + +(((range="endofrange", startref="term.advanced.cdi.scopes.uiscoped"))) +[classname]#@NormalUIScoped#([package]#com.vaadin.cdi#):: As [classname]#@UIScoped#, but injecting a managed bean having this annotation +injects a proxy for the bean instead of a direct reference. This is the normal +behaviour with CDI, as many CDI features utilize the proxy. + + + +Defining a CDI view (annotated with [classname]#@CDIView# as described later) as +[classname]#@UIScoped# makes the view retain the same instance when the user +navigates away and back to the view. + + +[[advanced.cdi.scopes.view]] +=== View Scopes + +The lifecycle of a view-scoped bean starts when the user navigates to a view +referring to the object and ends when the user navigates out of the view (or +when the UI is closed or expires). + +Vaadin CDI provides two annotations for the view scope, differing in how they +enable proxies, as explained later. + +[classname]#@ViewScoped#([package]#com.vaadin.cdi#):: Injection with this annotation will create a direct reference to the bean rather +than a proxy. There are some limitations when not using proxies. Circular +references (injecting A to B and B to A) will not work, and neither do CDI +interceptors and decorators. + +[classname]#@NormalViewScoped#([package]#com.vaadin.cdi#):: As [classname]#@NormalScoped#, except that injecting with this annotation will +create a proxy for the contextual instance rather than provide the contextual +instance itself. See the explanation of proxies below. + + + + +[[advanced.cdi.scopes.cdi]] +=== Standard CDI Scopes + +[classname]#@ApplicationScoped#:: ((("[classname]#@ApplicationScoped#", id="term.advanced.cdi.scopes.applicationscoped", range="startofrange"))) + + ++ +Application-scoped beans are shared by all servlets in the web application, and +are essentially equal to singletons.//TODO This is just a guess - is it +true? +Note that referencing application-scoped beans is not thread-safe and access +must be synchronized. + +(((range="endofrange", startref="term.advanced.cdi.scopes.applicationscoped"))) +[classname]#@SessionScoped#:: ((("[classname]#@SessionScoped#", id="term.advanced.cdi.scopes.sessionscoped", range="startofrange"))) + + ++ +The lifecycle and visibility of session-scoped beans is bound to a HTTP or user +session, which in Vaadin applications is associated with the +[classname]#VaadinSession# (see +<<dummy/../../../framework/application/application-lifecycle#application.lifecycle.session,"User +Session">>). This is a very typical scope to store user data, as is done in many +examples in this section, or database connections. The lifecycle of +session-scoped beans starts when a user opens the page for a UI in the browser, +and ends when the session expires after the last UI in the session is closed. + +(((range="endofrange", startref="term.advanced.cdi.scopes.sessionscoped"))) + + + +[[advanced.cdi.scopes.proxies]] +=== Proxies vs Direct References + +CDI uses proxy objects to enable many of the CDI features, by hooking into +message-passing from client to service beans. Under the hood, a proxy is an +instance of an automatically generated class that extends the proxied bean type, +so communicating through a proxy occurs transparently, as it has the same +polymorphic type as the actual bean. Whether proxying is enabled or not is +defined in the scope: CDI scopes are either __normal scopes__, which can be +proxied, or __pseudoscopes__, which use direct references to injected beans. + +The proxying mechanism creates some requirements for injecting objects in normal +scope: + +* The objects may not be primitive types or arrays + +* The bean class must not be final + +* The bean class must not have final methods + + +Beans annotated with [classname]#@UIScoped# or [classname]#@ViewScoped# use a +pseudoscope, and are therefore injected with direct references to the bean +instances, while [classname]#@NormalUIScoped# and [classname]#@NormalViewScoped# +beans will use a proxy for communicating with the beans. + +When using proxies, be aware that it is not guaranteed that the +[methodname]#hashCode()# or [methodname]#equals()# will match when comparing a +proxy to its underlying instance. It is imperative to be aware of this when, for +example, adding proxies to a [interfacename]#Collection#. + +You should avoid using normal scopes with Vaadin components, as proxies may not +work correctly within the Vaadin framework. If Vaadin CDI plugin detects such +use, it displays a warning such as the following: + + +---- +INFO: The following Vaadin components are injected +into normal scoped contexts: + @NormalUIScoped org.example.User +This approach uses proxy objects and has not been +extensively tested with the framework. Please report +any unexpected behavior. Switching to a pseudo-scoped +context may also resolve potential issues. +---- + + +(((range="endofrange", startref="term.advanced.cdi.scopes"))) + +[[advanced.cdi.deployment]] +== Deploying CDI UIs and Servlets + +Vaadin CDI hooks into Vaadin framework by using a special +[classname]#VaadinCDIServlet#. As described earlier, you do not need to map an +URL path to a UI, as it is handled by Vaadin CDI. However, in the following, we +go through some cases where you need to customize the servlet or use CDI with +non-CDI servlets and UIs in a web application. + +[[advanced.cdi.deployment.urlmapping]] +=== Defining Servlet Root with [classname]#@URLMapping# + +CDI UIs are managed by a CDI servlet ( [classname]#VaadinCDIServlet#), which is +by default mapped to the root of the application context. For example, if the +name of a CDI UI is " [literal]#++my-cdi++#" and application context is +[literal]#++/myproject++#, the UI would by default have URL " +[literal]#++/myproject/my-cdi++#". If you do not want to have the servlet mapped +to context root, you can use the [classname]#@URLMapping# annotation to map all +CDI UIs to a sub-path. The annotation must be given to only one CDI UI, usually +the one with the default ("") path. + +For example, if we have a root UI and another: + + +[source, java] +---- +@CDIUI("") // At CDI servlet root +@URLMapping("mycdiuis") // Define CDI Servlet root +public class MyCDIRootUI extends UI {...} + +@CDIUI("another") +public class AnotherUI extends UI {...} +---- + +These two UIs would have URLs /myproject/mycdiuis and +/myproject/mycdiuis/another, respectively. + +You can also map the CDI servlet to another URL in servlet definition in +[filename]#web.xml#, as described the following. + + +[[advanced.cdi.servlets.mixing]] +=== Mixing With Other Servlets + +The [classname]#VaadinCDIServlet# is normally used as the default servlet, but +if you have other servlets in the application, such as for non-CDI UIs, you need +to define the CDI servlet explicitly in the [filename]#web.xml#. You can map the +servlet to any URL path, but perhaps typically, you define it as the default +servlet as follows, and map the other servlets to other URL paths: + +[subs="normal"] +---- +<web-app> + ... + + <servlet> + <servlet-name>Default</servlet-name> + <servlet-class> + com.vaadin.cdi.internal.VaadinCDIServlet + </servlet-class> + </servlet> + + <servlet-mapping> + <servlet-name>Default</servlet-name> + <url-pattern>[replaceable]#/mycdiuis/*#</url-pattern> + </servlet-mapping> + + <servlet-mapping> + <servlet-name>Default</servlet-name> + <url-pattern>/VAADIN/*</url-pattern> + </servlet-mapping> +</web-app> +---- +With such a setting, paths to CDI UIs would have base path +[filename]#/myapp/mycdiuis#, to which the (optional) UI path would be appended. +The [filename]#/VAADIN/*# only needs to be mapped to the servlet if there are no +other Vaadin servlets. + + +[[advanced.cdi.servlets.custom]] +=== Custom Servlets + +When customizing the Vaadin servlet, as outlined in +<<dummy/../../../framework/application/application-lifecycle#application.lifecycle.servlet-service,"Vaadin +Servlet, Portlet, and Service">>, you simply need to extend +[classname]#com.vaadin.cdi.internal.VaadinCDIServlet# instead of +[classname]#com.vaadin.servlet.VaadinServlet#. + +The custom servlet must not have [classname]#@WebServlet# annotation or +[classname]#@VaadinServletConfiguration#, as you would normally with a Vaadin +servlet, as described in +<<dummy/../../../framework/application/application-environment#application.environment,"Deploying +an Application">>. + + + +ifdef::web[] +[[advanced.cdi.navigation]] +== View Navigation + +Vaadin CDI extends the navigation framework in Vaadin, described in +<<dummy/../../../framework/advanced/advanced-navigator#advanced.navigator,"Navigating +in an Application">>. It manages CDI views with a special view provider and +enables view scoping. + +[[advanced.cdi.navigation.ui]] +=== Preparing the UI + +You can define navigation for any single-component container, as described in +<<dummy/../../../framework/advanced/advanced-navigator#advanced.navigator.navigating,"Setting +Up for Navigation">>, but typically you set up navigation for the entire UI +content. To use Vaadin CDI views, you need to inject a +[classname]#CDIViewProvider# in the UI and add it as a provider for the +navigator. + + +[source, java] +---- +@CDIUI("mycdiui") +public class MyCDIUI extends UI { + @Inject + CDIViewProvider viewProvider; + + @Override + protected void init(VaadinRequest request) { + Navigator navigator = new Navigator(this, this); + navigator.addProvider(viewProvider); + + // Navigate to start view + navigator.navigateTo(""); + } +} +---- + + +[[advanced.cdi.navigation.view]] +=== The View + +A view managed by Vaadin CDI only needs to have the [classname]#@CDIView# +annotation. + + +[source, java] +---- +@CDIView("main") +public class MainView extends CustomComponent implements View { + ... +---- + +The annotation can have the following optional paramers: + +value (optional):: Specifies the view name by which it can be accessed programmatically and by the +URI fragment. + + ++ +[source, java] +---- +@CDIView("main") +---- ++ +If other optional parameters are given, the value must be given by the named +[parameter]#value# parameter. + ++ +If the view name is not given, it is derived from the class name by removing a +possible "View" suffix, making it lower case, and using a dash ("-") to separate +words originally denoted by capital letters. Thereby, a view class such as +[classname]#MyFunnyView# would have name " [literal]#++my-funny++#". + +supportsParameters:: Specifies whether view parameters can be passed to the view as a suffix to the +name in the navigation state, that is, in the form of +[literal]#++viewname+viewparameters++#. The view name is merely a prefix and +there is no separator nor format for the parameters, but those are left for the +view to handle. The parameter support mode is disabled by default. + + ++ +[source, java] +---- +@CDIView(value="myview", supportsParameters=true) +---- ++ +You could then navigate to the state with a URI fragment such as +[literal]#++#!myview/someparameter++# or programmatically with: + + ++ +[source, java] +---- +getUI().getNavigator().navigateTo("myview/someparameter"); +---- ++ +The [methodname]#enter()# method of the view gets the URI fragment as parameter +as is and can interpret it in any application-defined way. + ++ +Note that in this mode, matching a navigation state to a view is done by the +prefix of the fragment! Thereby, no other views may start with the name of the +view as prefix. For example, if the view name is " [literal]#++main++#", you +must not have a view named " [literal]#++maintenance++#". + +uis:: If the application has multiple UIs that use [classname]#CDIViewProvider#, you +can use this parameter to specify which UIs can show the view. + + ++ +[source, java] +---- +@CDIView(value="myview", uis={MyCDIUI.class}) +---- ++ +If the list contains [parameter]#UI.class#, the view is available to all UIs. + + ++ +[source, java] +---- +@CDIView(value="myview", uis={UI.class}) +---- + + +In the following, we have a login view that accesses a session-scoped user +object. Here, we use a constant to define the view name, so that we can use the +constant when navigating to it. + + +[source, java] +---- +@CDIView(LoginView.VIEWNAME) +public class LoginView extends CustomComponent + implements View { + public final static String VIEWNAME = ""; + + // Here we inject to the constructor and actually do + // not store the injected object to use it later + @Inject + public LoginView(User user) { + VerticalLayout layout = new VerticalLayout(); + + // An input field for editing injected data + BeanItem<User> item = new BeanItem<User>(user); + TextField username = new TextField("User name", + item.getItemProperty("name")); + username.setNullRepresentation(""); + layout.addComponent(username); + + // Login button (authentication omitted) / Java 8 + layout.addComponent(new Button("Login", e -> + getUI().getNavigator(). + navigateTo(MainView.VIEWNAME))); + + setCompositionRoot(layout); + } + + @Override + public void enter(ViewChangeEvent event) {} +} +---- + +You could now navigate to the view from any other view in the UI with: + + +[source, java] +---- +getUI().getNavigator().navigateTo(LoginView.VIEWNAME); +---- + + +endif::web[] + +ifdef::web[] +[[advanced.cdi.events]] +== CDI Events + +((("CDI", "events", id="term.advanced.cdi.events", range="startofrange"))) + + +CDI events can be used for many purposes in Vaadin applications, such as passing +messages between different parts of a view, between views, between UIs, or +between users. Some cases require special consideration, such as when +communicating between UIs and how injected components should be scoped. + +[[advanced.cdi.events.intro]] +=== Observing Events + +Let us consider a case where changes in one part of the UI (or view) require +updating other parts of the UI. This is typical in master-detail views, for +updating the master view after editing details, or when handling input from a +sub-window. While you can handle such a situation with a custom call-back +listener, CDI event mechanism simplifies the task. + +Let us consider the following simple UI containing two panels. The input panel +will send events, which are received by other parts of the UI, in this case a +display panel. The panels need to be injected to enable CDI event passing in +them. + + +[source, java] +---- +@CDIUI("cdievents") +@Theme("valo") +public class CDIEventUI extends UI { + @Inject + InputPanel inputPanel; + + @Inject + DisplayPanel displayPanel; + + @Override + protected void init(VaadinRequest request) { + Layout content = + new HorizontalLayout(inputPanel, displayPanel); + setContent(content); + } +} +---- + +Now, let us look closer at the sending panel. To send messages, it needs to +inject a [classname]#javax.enterprise.event.Event# object. As we are injecting +the event to a component class, we need to specify the full package name to +avoid confusion with Vaadin [classname]#Component.Event#. + + +[source, java] +---- +class InputPanel extends Panel { + @Inject + private javax.enterprise.event.Event<MyEvent> event; + + public InputPanel() { + super("Input"); + + TextField editor = new TextField(); + Button save = new Button("Save", e -> // Java 8 + event.fire(new MyEvent(editor.getValue()))); + + setContent(new VerticalLayout(editor, save)); + } +} +---- + +Firing an event is done with the [methodname]#fire()# method on the injected +event object. In our example, the event is as follows: + + +[source, java] +---- +public class MyEvent implements Serializable { + private String text; + + public MyEvent(String text) { + this.text = text; + } + + public String getName() { + return text; + } +} +---- + +The event is received by any method (in an injected object) marked by +[classname]#@Observes# annotation for the event parameter to observe the event +type. + + +[source, java] +---- +@UIScoped +class DisplayPanel extends Panel { + Label display = new Label("-nothing to display-"); + + public DisplayPanel() { + super("Display"); + setContent(display); + } + + void myEventObserver(@Observes MyEvent event) { + display.setValue("Observed: " + event.getName()); + } +} +---- + +Such a component that observes events from other components must be scoped to +the UI or view, as otherwise it will be request-scoped and a new instance is +created for receiving each event. + +The UI with interaction is shown in <<figure.advanced.cdi.events.intro>>. + +[[figure.advanced.cdi.events.intro]] +.Observing CDI Events +image::img/cdi-events-observing.png[] + +Any injection qualifiers defined for the event object in the sender are matched +in the observers, which feature we will use later to avoid receiving unwanted +events. + + +[[advanced.cdi.events.broadcasting]] +=== Communicating Between UIs + +((("broadcasting", id="term.advanced.cdi.events.broadcasting", range="startofrange"))) + + +CDI events are not propagated to inactive contexts, and only the context of the +currently processed UI is active. Further, as explained in +<<dummy/../../../framework/advanced/advanced-push#advanced.push.running,"Accessing +UI from Another Thread">>, other Vaadin UIs may not be accessed without proper +synchronization, as their requests are processed concurrently in different +server threads. Therefore, you need to pass the events through an +application-scoped messaging service and synchronize the access to other UIs by +using the [methodname]#access()# method. + +In +<<dummy/../../../framework/advanced/advanced-push#advanced.push.pusharound,"Broadcasting +to Other Users">> we looked into how to pass messages to all other UIs using a +broadcasting service. In that example, we used static variables and methods to +store references and to access the service. With CDI, we can let the context +manage its lifecycle, access it by injection, and pass messages by CDI events. +By scoping the messaging service to application, we essentially make it a +singleton. + + +[source, java] +---- +@ApplicationScoped +public class CDIBroadcaster implements Serializable { +---- + +As we can not let CDI deliver the messages, the messaging service needs to keep +book of the messaging clients (UIs) waiting to receive messages. + + +[source, java] +---- + private Collection<UI> uis = new HashSet<UI>(); + + public synchronized void register(UI listener) { + uis.add(listener); + } + + public synchronized void unregister(UI listener) { + uis.remove(listener); + } +---- + +The main logic of the messaging service is to observe messages and fire them in +the recipient UIs. As we are broadcasting to all UIs here, we again use an +executor service to execute the code. To lock on the session when accessing the +UIs, we use the [methodname]#access()# method. + + +[source, java] +---- + // Inject event to be fired + @Inject + private javax.enterprise.event.Event<BroadcastMessage> + messageEvent; + + ExecutorService executorService = + Executors.newSingleThreadExecutor(); + + // Observe messages (only from clients) + @SuppressWarnings("unused") + private synchronized void observeMessage( + @Observes @OriginalSender + final BroadcastMessage message) { + for (final UI listener: uis) + executorService.execute(() -> + listener.access(()-> + messageEvent.fire(message))); + } +} +---- + +Here we use a [classname]#@OriginalSender# qualifier to receive events only from +a client (original sender), not from the messaging service itself, which would +cause an infinite event loop. The qualifier is defined as follows: + +((("CDI", "qualifiers"))) + +[source, java] +---- +@Qualifier +@Retention(RUNTIME) +@Target({PARAMETER, FIELD}) +public @interface OriginalSender {} +---- + +The message type is a simple POJO as follows: + + +[source, java] +---- +public class BroadcastMessage { + private String text; + private Object sender; // For checking if sent by self + + ... constructor, getters, and setters ... +} +---- + +Let us take a look at the UI class, which manages both the messaging service and +the client components. The UI just needs to register itself in the messaging +service and build the UI, including the UI components doing messaging. We could, +of course, do that also at view level. + +((("[classname]#@Push#"))) + +[source, java] +---- +@CDIUI("cdichat") +@Push +public class CDIChatUI extends UI { + @Inject + CDIBroadcaster broadcaster; + + @Inject + ChatBox chatbox; + + @Override + protected void init(VaadinRequest request) { + setContent(chatbox); + + // Register to receive broadcasts + broadcaster.register(this); + } + + // Must also unregister when the UI expires or is closed + @Override + public void detach() { + broadcaster.unregister(this); + super.detach(); + } +} +---- + +Now for an actual messaging client, we look at the chat box component. Most of +the UI code is omitted from the example. As noted earlier, the component +receiving events must be scoped to the UI, to avoid creation of invalid +instances. + +((("[classname]#@UIScoped#"))) + +[source, java] +---- +@UIScoped +class ChatBox extends CustomComponent { + VerticalLayout messages = new VerticalLayout(); + + public ChatBox(CDIChatUI cdiChatUI) { + ... build the composite ... + + TextField input = new TextField(); + + Button send = new Button("Send", e -> { // Java 8 + // Broadcast the input + broadcast(input.getValue()); + addMessage(input.getValue()); // Add to self + }); + ... + } + + @Inject + @OriginalSender + private javax.enterprise.event.Event<BroadcastMessage> + messageEvent; + + // Sends a message + private void broadcast(String msg) { + messageEvent.fire(new BroadcastMessage(msg, this)); + } + + // Receives messages + @SuppressWarnings("unused") + private void observeMessage( + @Observes BroadcastMessage event) { + if (event.getSender() != this) + addMessage(event.getText()); + } + + private void addMessage(String msg) { + messages.addComponent(new Label(msg)); + } +} +---- + +((("CDI", "qualifiers"))) +Note that the client object is completely unaware of the fact that the messages +are delivered through a messaging service; we have successfully decoupled the +messaging logic required by Vaadin UIs from the component. Only the requirement +for using the event qualifier remains (notice that its use is not checked at +compile time). + +(((range="endofrange", startref="term.advanced.cdi.events.broadcasting"))) + +(((range="endofrange", startref="term.advanced.cdi.events"))) +endif::web[] + +(((range="endofrange", startref="term.advanced.cdi.cdilong"))) +(((range="endofrange", startref="term.advanced.cdi.cdi"))) +(((range="endofrange", startref="term.advanced.cdi.cdiaddon"))) + + |