lunes, 15 de abril de 2019

14. VaadinServiceInitListener: Preventing non authorised access.

updated on April 15, 2019

0. Introduction

Vaadin provides a BeforeEnterEvent in forms that can be used to detect unauthorised login attempts and enables us to redirect to a login form.

1. Declaring a VaadinServiceInitListener

We should:
  1. Create a folder called "META-INF" in the src/main/resources folder.
  2. Create a folder called "services" in the previously created META-INF folder.
  3. Add a text file called "com.vaadin.flow.server.VaadinServiceInitListener", with this content (that is the absolute class name if the 


openadmin.listeners.LoggedInListener

Here is a schema:

-->src
   |-->main
      |-->resources
         |-->META-INF
            |-->services
               |-->com.vaadin.flow.server.VaadinServiceInitListener

2. The VaadinServiceInitListener interface

A class that extends this interface is created and:
  1. Add a BeforeEventListener to the UI that is requested by the user
  2. If the session attribute "CURRENT_USER" is null then the user is redirected to the "LoginForm".
  3. So the user cannot access any other form in the application unless the CURRENT_USER is set to not NULL value.
  4. I have named this class LoggedInInstener (for detecting if a user is logged in), and must coincide with the content of the file "com.vaadin.flow.server.VaadinServiceInitListener" while declaring the VaadinServiceInitListener in the previous point.
  5. Note that we cannot inject session scoped beans in this listener!!!! But... we can access the session!.
Here is the code:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package openadmin.listeners;

import com.vaadin.flow.server.ServiceInitEvent;
import com.vaadin.flow.server.VaadinServiceInitListener;

import openadmin.ui.LoginForm;
import openadmin.utils.VaadinUtils;


/**
 * This class is used to listen to BeforeEnter event of all UIs in order to
 * check whether a user is signed in or not before allowing entering any page.
 * It is registered in a file named
 * com.vaadin.flow.server.VaadinServiceInitListener in META-INF/services.
 * 
 * NOTE that a SessionScopped object cannot be injected so, to know if the user is logged in
 *   we need to make use of information stored in the Session object with keys "CURRENT_USER" 
 */
@SuppressWarnings("serial")
public class LoggedInListener implements VaadinServiceInitListener {
 
 public LoggedInListener() {
  System.out.println("A new LoggedInListener has been created....");
  
 }
 
 
 @Override
    public void serviceInit(ServiceInitEvent initEvent) {
     initEvent
      .getSource()  // gets the object that raised the event
      .addUIInitListener( // Adds a listener that gets notified when a new UI has been initialized.
       uiInitEvent -> { // A UIInitListener
        uiInitEvent
         .getUI() // Get the initialized UI for this initialization event
         .addBeforeEnterListener( //Add a listener that will be informed when a new set of components are going to be attached
          enterEvent -> { // a BeforeEnterEvent
           // If the Session attribute "CURRENT_USER" is not set, then redirect to LoginForm class
           if (VaadinUtils.getSessionAttribute("CURRENT_USER")==null) { 
            if (!LoginForm.class.equals(enterEvent.getNavigationTarget())) {
             enterEvent.rerouteTo(LoginForm.class); 
             System.out.println("LoggedInListener: Session attribute 'CURRENT_USER' is null ....");
            } 
           }
         });
      });
    }
}



13. Vaadin i18n (1)

updated on April 15, 2019

0. Introduction 

To manage i18n (internationalization) in Vaadin we need to:
  • Inform Vaadin which class will manage i18n. 
  • Create this i18n class (should implement the i18NProvider interface)
  • Create resource bundles for each defined language
  • Optionally create a language enumeration of all the languages used.

1. Informing Vaadin about the i18n responsible class

In the previous post, we saw the class that informed Vaadin about the use of CDI and also about the class that managed the i18n. As a reminder, Vaadin uses a class that extends VaadinServlet or CdiVaadinServlet (only for CDI) and the i18 manager class is defined in the @WebInitParam of the @WebServlet annotation of this class.

Here is again the source code of this class


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package openadmin.listeners;

import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;

import com.vaadin.cdi.CdiVaadinServlet;
import com.vaadin.flow.server.Constants;
//import com.vaadin.flow.server.VaadinServlet;
import com.vaadin.flow.server.VaadinServletConfiguration;

/**
 * Information about app servlet 
 * 
 * Necessary for defining i18n Provider
 * @author ximo 
 *
 */
@SuppressWarnings("serial")
@WebServlet(
 urlPatterns = "/*", 
 name = "slot", 
 asyncSupported = true, 
 initParams = {
  // I18N Provider for translation of labels 
        @WebInitParam( 
         name = Constants.I18N_PROVIDER, value = "openadmin.i18n.MyI18nProvider") 
        
 })

@VaadinServletConfiguration(productionMode = false)

//public class ApplicationServlet extends VaadinServlet {
public class ApplicationServlet extends CdiVaadinServlet {
}



2. Creating the i18n managing class


The class for managing i18 is MyI18nProvider, it must implement I18NProvider interface and here is the code. Note how the prefix to access the resource bundle is defined in the key passed as a parameter in the getTranslation procedure.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package openadmin.i18n;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.stream.Collectors;

import com.vaadin.flow.i18n.I18NProvider;

@SuppressWarnings("serial")
public class MyI18nProvider implements I18NProvider {
 //List of all the available locales
 @Override
 public List<Locale> getProvidedLocales() {
  
  return Collections.unmodifiableList(
   Arrays.stream(LangEnum.values())
    .map(item -> new Locale(item.name()))
    .collect(Collectors.toList()));
  
 }

 /** 
  * key -> bundle.key
  */
 @Override
 public String getTranslation(String key, Locale locale, Object... params) {
  String loc=locale.getLanguage();
  String[] bundle_key = key.split("\\."); 
  ResourceBundle rsBundle = 
   ResourceBundle.getBundle(
    "i18n." + bundle_key[0] + "_" + loc);
  if (! rsBundle.containsKey(bundle_key[1])) {
   System.out.println("missing resource key (i18n) " + key);
   //logger().info("missing resource key (i18n) " + key);
      return bundle_key[1] + " - " + locale;
  } else {
   return (rsBundle.containsKey(bundle_key[1])) ? rsBundle.getString(bundle_key[1]) : bundle_key[1];
  }
 }
 
 public MyI18nProvider () {
  
 }
}



3. The resource bundles for the language


The resource bundles are stored in the src/main/resources/i18n folder (in this project, but not necessarily) , and there are several files, each file is named by a prefix followed by an underscore ("_"), the 2 digits of the locale, and the suffix ".properties". The prefixes are used for splitting a big property file into smaller and easier to manage ones. Here are some filename examples:
  • "login_es.properties": Stores keys used for login and the Spanish translation
  • "login_en.properties": Stores keys used for login and the English translation
  • "messages_es.properties": Stores keys used for messages and the Spanish translation
  • "messages_en.properties": Stores keys used for messages and the English translation
Here is the content of the login_es.properties:



user = Usuario
password = Contraseña
login = Acceder
invalid_credentials = Credenciales no válidas


4. The language enumeration


Let's create a Java "enum" with the country (of the Locale) abbreviation and a description.

Methods are provided for getting the LangEnum from the description and getting the LangEnum from the Locale.

Here is the code


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package openadmin.i18n;

import java.util.Locale;
import lombok.Getter;


public enum LangEnum {
 es ("Español"), 
 ca ("Valencià"),
 en ("English"),
 fr ("Francaise"),
 de ("Deustch"),
 it ("Italiano"),
 ro ("Românesc");
 
 @Getter 
 private final String definition;
 
 
 private LangEnum(String definition) {
  this.definition=definition;
 }
 
  
 /**
  * Return the LangEnum that matches the definition
  * @param loc
  * @return
  */
 public static LangEnum getLangEnum(String definition) {
  LangEnum lEnum=LangEnum.ca;
  for (LangEnum item: LangEnum.values())
   if(item.getDefinition().equalsIgnoreCase(definition))
    lEnum=item;
   
  return lEnum;
 }
 
 /**
  * Return the LangEnum that matches the locale
  * @param loc
  * @return
  */
 public static LangEnum getFromLocale(Locale loc) {
  LangEnum lEnum=LangEnum.ca;
  for (LangEnum item: LangEnum.values())
   if(item.name().equalsIgnoreCase(loc.getLanguage()))
    lEnum=item;
  return lEnum;
 }
 
 public static void main (String[] args) {
  /**
  Arrays.stream(LangEnum.values())
   .map(LangEnum::getDefinition)
   .collect(Collectors.toList());
  */
  LangEnum le=LangEnum.getFromLocale(new Locale("es")) ;
  System.out.println(le.name());
  le=LangEnum.getFromLocale(new Locale("ca")) ;
  System.out.println(le.name());
  le=LangEnum.getFromLocale(new Locale("en")) ;
  System.out.println(le.name());
 }
}

12. Vaadin Session, CDI and CdiVaadinServlet

updated April 15, 2019

0. Introduction

We need to store information in sessions. This can be achieved basically in 2 ways:
  1. Adding attributes to the session. The session has a map of attributes.
  2. Using session-scoped beans. Using CDI is a good choice.

1. Adding parameters to the Session.

As seen in the previous post,  the session can be accessed.  Here is an example of accessing the session and getting or setting an attribute. The utility class "VaadinUtils" is used. The attribute has a name and an object type value.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package stuff;
import openadmin.utils.VaadinUtils;

public class TestAttributes {

 public static void main(String[] args) {
  VaadinUtils.setSessionAttribute("CONNECTED_USER", "Ximo");
  System.out.println(VaadinUtils.getSessionAttribute("CONNECTED_USER").toString()); }

}

It is necessary knowing who is the connected user. So a session attribute whose name is "CONNECTED_USER" will be created. This attribute will be used to allow or deny the user to use the application.

2. Using CDI and session-scoped beans


To use CDI in Vaadin let's follow these 4 steps:

2.1 Create a class that extends CdiVaadinServlet

This class is informing about:
  1. The use of CDI (as it extends CdiVaadinServlet instead of VaadinServlet)
  2. The class that manages the i18n (internationalization) of the application. We will review this step later in another post.
  3. If the production mode is activated (by means of annotations)
Her is the code (in the "openadmin.listeners" package)


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package openadmin.listeners;

import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;

import com.vaadin.cdi.CdiVaadinServlet;
import com.vaadin.flow.server.Constants;
//import com.vaadin.flow.server.VaadinServlet;
import com.vaadin.flow.server.VaadinServletConfiguration;

/**
 * Information about app servlet 
 * 
 * Necessary for defining i18n Provider
 * @author ximo 
 *
 */
@SuppressWarnings("serial")
@WebServlet(
 urlPatterns = "/*", 
 name = "slot", 
 asyncSupported = true, 
 initParams = {
  // I18N Provider for translation of labels 
        @WebInitParam( 
         name = Constants.I18N_PROVIDER, value = "openadmin.i18n.MyI18nProvider") 
        
 })

@VaadinServletConfiguration(productionMode = false)

//public class ApplicationServlet extends VaadinServlet {
public class ApplicationServlet extends CdiVaadinServlet {
}



Note that:

  1. The class only informs. No procedures are defined.
  2. The class "openadmin.i18n.MyI18nProvider" is the class that manages "i18n".
  3. The @VaadinServletConfiguration annotations is used to inform that we are not in production mode.
  4. If no CDI is used, then this class should extend "VaadinServlet".

2.2 Create a the session-scoped bean

Now a simple class with only one attribute is created as an example. Note that attributes will be added in the future. Note that:

  1. CDI specific annotations (@Named and @SessionScoped) are no longer compatible. The @VaadinSessionScoped is used instead!
  2. To avoid "NullPointerException" when accessing the injected instance of this class, a method annotated with @PostConstruct and this method should initialize information of the class. This method is used to remove the "CONNECTED_USER" attribute from the session. 


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package openadmin.session;

import javax.annotation.PostConstruct;
//import javax.enterprise.context.SessionScoped;
//import javax.inject.Named;

import com.vaadin.cdi.annotation.VaadinSessionScoped;
import openadmin.utils.VaadinUtils;

import lombok.Getter;
import lombok.Setter;

//@Named @SessionScoped
@VaadinSessionScoped
public class SessionData {
 @Getter @Setter
 private String something=null;
 
 @PostConstruct
 private void init() {
  something="something else!";
  System.out.println("SessionData.init() called.");
  // Remove the session attribute that informs the user, if exists
  VaadinUtils.getWrappedSession().removeAttribute("CONNECTED_USER");
 }
}

2.3 Inject the session bean into another class


Here is the sample code using the @Inject annotation


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package openadmin.ui;

import javax.inject.Inject;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.PWA;

import openadmin.session.SessionData;

/**
 * The main view contains a button and a click listener.
 */
@SuppressWarnings("serial")
@Route("")
@PWA(name = "Project Base for Vaadin Flow", shortName = "Project Base")
public class MainForm extends VerticalLayout {

 @Inject 
 private SessionData mySesData;
 
    public MainForm() {
     mySesData.setSomething("Something again");
        var button = new Button("Click me. Right Now!",
                event -> Notification.show("Clicked! Silly Boy v.04.1" + mySesData.getSomething()));
        add(button);
    }
}

2.4 Create the file beans.xml


Follow these steps:

  1. In Eclipse go to "Deployed Resources" folder
  2. Go to "webapp" folder
  3. Create the "WEB-INF" folder if not exists
  4. Create the empty file beans.xml




miércoles, 10 de abril de 2019

11. Vaadin: Getting information from the "web environment"

updated April 15, 2019

I have created a utility class for getting information from the web environment. To simplify, all the methods are static, so no class creation es necessary.

We can access to the browser, request, response, address, session. Here is the code, and enjoy it

In the comments of the code, you can see where I have got some of the ideas so that you can acquire more information.

I have got a package called openadmin.utils for storing all utility classes


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package openadmin.utils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import com.vaadin.flow.server.VaadinRequest;
import com.vaadin.flow.server.VaadinResponse;
import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.VaadinSession;
import com.vaadin.flow.server.WebBrowser;
import com.vaadin.flow.server.WrappedSession;

public class VaadinUtils {
 
 public static WebBrowser getWebBrowser() {
  return new WebBrowser();
 }
 
 public static VaadinRequest getRequest () {
  return VaadinService.getCurrentRequest();
 }
 
 public static VaadinResponse getResponse() {
  return VaadinService.getCurrentResponse();
 }
 
 public static String getClientAddress() {
  return getWebBrowser().getAddress();
 }
 /**
  * Gets the remote address from a HttpServletRequest object. It prefers the 
  * `X-Forwarded-For` header, as this is the recommended way to do it (user 
  * may be behind one or more proxies).
  *
  * Taken from https://stackoverflow.com/a/38468051/778272
  *
  * @param request - the request object where to get the remote address from
  * @return a string corresponding to the IP address of the remote machine
  */
 
 
 public static String getClientAddress(HttpServletRequest request) {
  if (request==null) return null;
     String ipAddress = request.getHeader("X-FORWARDED-FOR");
     if (ipAddress != null) {
         // cares only about the first IP if there is a list
         ipAddress = ipAddress.replaceFirst(",.*", "");
     } else {
         ipAddress = request.getRemoteAddr();
     }
     return ipAddress;
 }
 
 /*
 public static String getClientAddress() {
  return getClientAddress(getRequest());
 }
 */
 /**
  * Get the session object
  * @param request
  * @return
  */
 public static HttpSession getSession(HttpServletRequest request) {
  if (request==null) return null;
  return request.getSession();
 }
 
 public static WrappedSession getWrappedSession() {
  return getRequest().getWrappedSession();
 }
 
 public static String getSessionId() {
  return getWrappedSession().getId();
 }
 
 
 public static String getUser() {
  return getRequest().getRemoteUser();
 }
 
 
 public static String getUserAgent(HttpServletRequest request) {
  if (request==null) return null;
  return request.getHeader("User-Agent");

 }
 
 public static String getUserAgent() {
  return ((HttpServletRequest)getRequest()).getHeader("User-Agent");

 }
 
 public static void setSessionAttribute(String attributeName, Object obj) {
  getWrappedSession().setAttribute(attributeName, obj);
 }
 
 public static Object getSessionAttribute(String attributeName) {
  return getWrappedSession().getAttribute(attributeName);
 }
 
 public static VaadinSession getVaadinSession() {
  return VaadinSession.getCurrent();
 }
 
 public static void invalidateSession() {
  getWrappedSession().invalidate();
 }

 public static void main(String[] args) {
  // TODO Auto-generated method stub

 }


}

10. A project from scratch

updated on April 15, 2019

1. Introduction

It is required :

  • Open JDK 10.x
  • Lombok
  • Eclipse Photon (4.9.0)
  • Tomcat 9.08 

2. Installation

3. Create an Eclipse Maven Project

1) File > New > Maven Project

2) Check only:
    🗹 Create a simple project (skip archetype selection).
    🗹 Use default Workspace location.
    Press Next

3) Fill these fields (I have used these values. But don't forget to select war packaging!)
    Group Id: ximodante
    Artifact Id: VaadinJava04
    Packaging : war
    Name : OpenWebVaadin02
    Description: CDI-Vaadin-Maven
    Press Finish

4. Edit the pom.xml file so that we can use:
  • Vaadin (version 12.x, as 13.x, is not working)
  • CDI
  • Hibernate 
  • XML and other stuff that has been omitted in Java>8
  • posgresql
  • yaml
  • Lombok...


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>openadmin</groupId>
  <artifactId>OpenWebVaadin02</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>OpenWebVaadin02</name>
  <description>CDI-Vaadin-Maven</description>
  
  <properties>
    <!-- changed from 1.8 to 10 -->
    <maven.compiler.source>10</maven.compiler.source>
    <maven.compiler.target>10</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <failOnMissingWebXml>false</failOnMissingWebXml>

    <vaadin.version>13.0.3</vaadin.version>
    <vaadin-cdi.version>10.1.0</vaadin-cdi.version>
    <lombok.version>1.18.6</lombok.version>
    <servlet.version>4.0.1</servlet.version>
    <weld-cdi.version>3.1.0.Final</weld-cdi.version>
   
    <hibernate.version>5.2.16.Final</hibernate.version>
    <jaxb.version>2.3.0</jaxb.version>
    <activation.version>1.2.0</activation.version>
    <jackson.version>2.9.5</jackson.version>
    
  </properties>
    
  <dependencies>
    <!-- https://mvnrepository.com/artifact/com.vaadin/vaadin-core -->
    <dependency>
      <groupId>com.vaadin</groupId>
      <artifactId>vaadin-core</artifactId>
      <version>${vaadin.version}</version>
    </dependency>
    
    
       
    <!-- https://mvnrepository.com/artifact/com.vaadin/vaadin-cdi -->
    <dependency>
      <groupId>com.vaadin</groupId>
      <artifactId>vaadin-cdi</artifactId>
      <version>${vaadin-cdi.version}</version>
    </dependency>
   
    
    <!-- 2019.02  -->
    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>${lombok.version}</version>
      <scope>provided</scope>
    </dependency>
    
    <!-- 2018-04 Java Servlet API-->  
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <!-- <version>3.1.0</version> -->
      <version>${servlet.version}</version>
      <scope>provided</scope>
    </dependency>
    
    <!-- 2019.02 Weld CDI for Tomcat with all dependencies included (does not fulfill all capabilities !!!) -->
    <!-- https://mvnrepository.com/artifact/org.jboss.weld.servlet/weld-servlet-shaded -->
    <dependency>
      <groupId>org.jboss.weld.servlet</groupId>
      <artifactId>weld-servlet-shaded</artifactId>
      <version>${weld-cdi.version}</version>
    </dependency>
    
  </dependencies>
  
  
  
</project>

4. Additional files:

1. Create a new folder into webapp folder (src/main/webapp) called WEB-INF

2. Create an empty file beans.xml in the WEB-INF folder. This file may be required by CDI (Context and dependency injection). For more information see BalusC.

3. (Optional)In future posts we will need including local jar files (dependencies) that are not in Maven Central repository. So in the previous WEB-INF folder let's create the folder lib, that will contain these jars. (There are other options to solve this problem, but it is good for me. See Roufid for more accepted solutions)

4. (Optional) Also in the future, we will be using JNDI data sources in Tomcat (and will be referenced in JPA). So we need to create the META-INF folder (in the webapp folder). We will include in this folder the empty file context.xml.


5. Additional files in the src/main/resources folder

(Optional) In a future we will put this files in the src/main resources:

1.  META_INF/persistence.xml (for JPA)
2.  properties/application.properties (for properties files)
3.  bundles/language.properties (for resources bundles)
4.  Other stuff

6. Simple page

1. Let's create the package openadmin.ui into src/main/java. I have decided to save all my user interface in "ui" folder
2. Create the java class MainView.java 


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package openadmin.ui;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.PWA;

/**
 * The main view contains a button and a click listener.
 */
@SuppressWarnings("serial")
@Route("")
@PWA(name = "Project Base for Vaadin Flow", shortName = "Project Base")
public class MainView extends VerticalLayout {

    public MainView() {
        var button = new Button("Click me. Right Now!",
                event -> Notification.show("Clicked! Silly Boy v.04.1"));
        add(button);
    }
}

Note:

  • Only one @Route annotation with a constant non-empty parameter should be used as indicates the bootstrap class.
  • We are using CDI and Vaadin, each system has its own annotations.
  • In CDI all beans should implement Serializable interface or else Tomcat won't start.
  • We are using Lombok annotations @Getter and @Setter.3. Let's create (optionally) the folder "pages" into the folder "webapp".
  • @PWA annotation let us introduce to the Progressive Web Applications. 

6. Project structure

This is the proposed project structure. Take into account that the strictly needed files for this example are displayed in bold style.

Java Resources (src/main/java folder)
  |--src/main/java 
     |--openadmin.ui
        |-- MainView.java

  |--src/main/resources

     |--META-INF
        |-- persistence.xml
     |--properties
        |--application.properties
     |--i18n
        |--language.properties
     |--other stuff
  
Deployed Resources (src/main folder)
   |--webapp
      |--WEB-INF
         |--beans.xml
         |--lib
            |--additional local jars 
      |--frontend
         |--css
         |--icon
         |--img
         |--js
      |--META-INF
         |--context.xml
      |--other stuff like css, icons ..
     


7. Executing the page


Right-click on test02-login-page.xhtml and select Run As - Run on Serve

viernes, 4 de enero de 2019

05. Data Providers

01. Introduction

Listings are components that display one or more attributes from a list of items.

DataProviders provide the list of items to the Listing components.

Direct memory loading or lazy loading are the mechanisms for loading the list into the listing components.

Callbacks are used in the listing components for defining how the attributes are displayed in the listing elements. Remember the grid.addColumn(Person:getName) method.

setItem() method enables to load data into the listing component


// Sets items as a collection
comboBox.setItems(EnumSet.allOf(Status.class));

// Sets items using varargs
grid.setItems(
        new Person("George Washington", 1732),
        new Person("John Adams", 1735),
        new Person("Thomas Jefferson", 1743),
        new Person("James Madison", 1751)
);

ListDataProvider is a supplier of data that can be used simultaneously by several list components.

02. In-memory data

02.01. Sorting data directly on the grid (listing component)

To sort elements by a given attribute, this attribute should implement Comparable interface.
We can supply a Comparator. Note this method does not work in lazy loading


grid.addColumn(Person::getName).setHeader("Name")
        // Override default natural sorting
        .setComparator(Comparator
                .comparing(person -> person.getName().toLowerCase()));

02.02. Sorting elements directly in the DataProvider

Note that when you sort the DataProvider directly, all the listing components attached to it will update themselves.


ListDataProvider<Person> dataProvider =
        DataProvider.ofCollection(persons);

dataProvider.setSortOrder(Person::getName, SortDirection.ASCENDING);

Grid<Person> grid = new Grid<>(Person.class);
// The grid shows the persons sorted by name
grid.setDataProvider(dataProvider);

// Makes the combo box show persons in descending order
button.addClickListener(event -> {
    dataProvider.setSortOrder(Person::getName, SortDirection.DESCENDING);
});

02.03 Filtering (in-memory data)

The methods addFilter and setFilter can be used to filter data in the DataProvider; addFilter can be stacked several times while setFilter can only be used once.
In this example, the combo shows only persons from a selected department.


ListDataProvider<Person> dataProvider =
        DataProvider.ofCollection(persons);

ComboBox<Person> comboBox = new ComboBox<>();
comboBox.setDataProvider(dataProvider);

departmentSelect.addValueChangeListener(event -> {
    Department selectedDepartment = event.getValue();
    if (selectedDepartment != null) {
        dataProvider.setFilterByValue(Person::getDepartment, selectedDepartment);
    } else {
        dataProvider.clearFilters();
    }
});
// Makes the combo box show persons in descending order
button.addClickListener(event -> {
    dataProvider.setSortOrder(Person::getName, SortDirection.DESCENDING);
});

The methods refreshAll() and refreshItems() enables to notify to listing components when any data has been changed.


ListDataProvider<Person> dataProvider =
        new ListDataProvider<>(persons);

Button addPersonButton = new Button("Add person",
        clickEvent -> {
            persons.add(new Person("James Monroe", 1758));
            dataProvider.refreshAll();
        });

Button modifyPersonButton = new Button("Modify person",
        clickEvent -> {
            Person personToChange = persons.get(0);
            personToChange.setName("Changed person");
            dataProvider.refreshItem(personToChange);
        });


03. Lazy Loading
This is somewhat complicated