viernes, 18 de agosto de 2017

JEE & JSF 6th Part: WELD selecting the injected beans programmatically

0. Introduction


To show formatted text Hilite is used

I can not conceive CDI without facilities to inject dependencies into at runtime.


I have seen these interesting related two questions at StackOverflow (the first from several participants and the second from Harald Wellmann)

This is a little tricky post so, let's try to understand the steps followed to achieve this goal:


  1. Create the main interface whose method should be implemented by the candidate classes
  2. Create an annotation (interface) with a parameter to distinguish classes that use this annotation
  3. Create the classes that implement the main interface and are annotated with the previous annotation with different parameters (in this post two classes are created)
  4. Create a tool class that implements the annotation interface used for managing the parameters of the annotation interface (this is the first tricky step)
  5. Use the type Instance<main interface> to retrieve all implementations of the main interface (this is the second tricky step)
  6. Use the method "select" from the Instance<main interface> to retrieve the desired injection class (passing the parameter of the annotation assigned in the class)

As mentioned in the previous post related to creating a menu programmatically our goal is to read the tree structure of the menu from different sources. In that post, this configuration was read from a file that was located in a relative path from src/main/resources folder by means of a class, now, let's create another class to locate and read this file by means of the absolute path of the file. Both classes will implement the same interface.

The way to select one or the other class is by means of a property "menuBeanReaderType" in the file application.properties. The way to retrieve properties was explained in the previous post for accessing properties


1. Creating the main interface


This interface is IMenuBeanReader 


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package org.ximodante.jsf.menu;

import java.io.IOException;
import org.primefaces.model.menu.MenuModel;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;

/**
 * Interface for reading info to build a menu.
 * @author Ximno Dante
 *
 */
public interface IMenuBeanReader {
 public void readMenu  (String source, MenuModel menuModel)
  throws JsonParseException, JsonMappingException, IOException, MenuJsfException;
}

The methid readMenu must be implemented by our classes.


2. The annotation (interface)


The annotation is ImenuBeanReaderType

This annotation has a parameter "type" that should distinguish the classes that implements the main interface (IMenuBeanReader)



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.ximodante.jsf.menu;


import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.inject.Qualifier;

/**
 * For distinguishing different classes that implements the IMenuBeanReader interface
 * @author Ximo Dante
 *
 */
@Qualifier @Retention(RUNTIME) @Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface IMenuBeanReaderType {
 
    String type() default "MenuBeanReaderJSONRelativePath";
}

NOTE: As it can be seen, the default value of type is MenuBeanReaderJSONRelativePath that is coincident with the name of a class that we will create later. This coincidence IS NOT NECESSARY the value of type paramenter could have been for instance "option 1".


3. The classes to be injected

Two classes have been created:

  1. MenuBeanReaderJSONRelativePath
  2. MenuBeanReaderJSONAbsolutePath
It is important to see that these two classes:

  • Implement IMenuBeanReader interface
  • Have been annotated with @IMenuBeanReaderType and have different types (for simplicity the name of each class has been used, but not necessarily)
  • Only the first class has the annotation @Default (it is recommended)
  • Annotations @Named and their scopes have been applied.


Let's see MenuBeanReaderJSONRelativePath 


 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
package org.ximodante.jsf.menu;

import java.io.IOException;
import java.util.Map;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Default;
import javax.inject.Named;

import org.primefaces.model.menu.MenuModel;
import org.ximodante.utils.json.JsonUtils;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;

@Named
@ApplicationScoped
@Default
@IMenuBeanReaderType(type="MenuBeanReaderJSONRelativePath")
public class MenuBeanReaderJSONRelativePath implements IMenuBeanReader {
    
 /**
  * Reads the menu from a JSON object as a Map where the source (file name) is relative to src/main/resource folder
  * @param fileName
  * @param menu
  * @throws JsonParseException
  * @throws JsonMappingException
  * @throws IOException
  * @throws MenuJsfException
  */
 
 @Override
 public void readMenu(String source, MenuModel menuModel)
  throws JsonParseException, JsonMappingException, IOException, MenuJsfException {
  
  Map<String,Object> map=JsonUtils.readMap(true, source );

  MenuFromMap.getMenuFromMap(map, menuModel, null, 0);
  
 }
}

The other one MenuBeanReaderJSONAbsolutePath is



 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
package org.ximodante.jsf.menu;

import java.io.IOException;
import java.util.Map;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;

import org.primefaces.model.menu.MenuModel;
import org.ximodante.utils.json.JsonUtils;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;

@Named
@ApplicationScoped
@IMenuBeanReaderType(type="MenuBeanReaderJSONAbsolutePath")
public class MenuBeanReaderJSONAbsolutePath implements IMenuBeanReader {
    
 /**
  * Reads the menu from a JSON object as a Map where the source (file name) is an absolute path
  * @param fileName
  * @param menu
  * @throws JsonParseException
  * @throws JsonMappingException
  * @throws IOException
  * @throws MenuJsfException
  */
 
 @Override
 public void readMenu(String source, MenuModel menuModel)
  throws JsonParseException, JsonMappingException, IOException, MenuJsfException {
  
  Map<String,Object> map=JsonUtils.readMap(false, source );
    
  MenuFromMap.getMenuFromMap(map, menuModel, null, 0);
  
 }
} 

You can think that it is disproportionate the fact of creating a new class that is practically the same and this could be done adding a new boolean parameter to the readMenu method for accepting relative or absolute paths, but this is a learning example.

The application scoped is used as a singleton.


3. Creating the tool class

The purpose of this tool class is managing the parameter (type) of the annotation so that we can extract the desired dependency from all the possible candidates that implements the main interface.

This class is MenuBeanReaderTypeDescription 


 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
package org.ximodante.jsf.menu;

import javax.enterprise.util.AnnotationLiteral;

/**
 * Class that is a tool to manage the paramemeter "type" from the Annotation IMenuBeanReaderType so that
 * the desired candidate class for injection is selected
 * 
 * @see https://stackoverflow.com/questions/33583032/dynamically-injecting-instances-via-cdi
 * @see https://stackoverflow.com/questions/24798529/how-to-programmatically-inject-a-java-cdi-managed-bean-into-a-local-variable-in
 * 
 * @author Ximo Dante
 *
 */
public class MenuBeanReaderTypeDescriptor extends AnnotationLiteral<IMenuBeanReaderType> implements IMenuBeanReaderType{
 private static final long serialVersionUID = 1L;
 
 private String type;
    
 public MenuBeanReaderTypeDescriptor(String type) {
        this.type = type;
    }
 

 @Override
    public String type() {
         return type;
    }
}


4. Using the injection into a bean

Let's use the MenuBean class from a former post about programmatic menu

Important points:

  1. In the property file, the property menuReaderTypeKey contains the parameter of the annotation IMenuBeanReaderType that chooses the class to be injected (in this case the value is "MenuBeanReaderJSONRelativePath")
  2. The singleton property reader is injected in the bean (the attribute appProps)
  3. The injected Instance<IMenuBeanReader> contains registration of all classes that implements the interface IMenuBeanReader. 
  4. By means of "select" method of the Instance<interface>  object we can retrive the desired injected class passing the parameter (type) of the 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
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
import javax.inject.Named;

import org.primefaces.model.menu.DefaultMenuModel;
import org.primefaces.model.menu.MenuModel;
import org.ximodante.utils.property.ApplicationProperties;

import lombok.Getter;


/**
 * Implements the menu structure as a bean
 * If a bean does not implements Serializable, Tomcat crashes
 * MenuModel is the attribute to implement the men structure
 * initialMenuConfig is the name of the file to read the initial menu structure
 * 
 * @author Ximo Dante
 *
 */
@Named
@ViewScoped
public class MenuBean implements Serializable {
 
 private static final long serialVersionUID = 1L;
 private static final String menuReaderTypeKey="menuBeanReaderType";
 
 @Getter  
 private MenuModel menuModel;
 
 @Inject 
 ApplicationProperties appProps;
 
 @Inject
 Instance<IMenuBeanReader> unqualifiedMenuBeanReader;
 
 private IMenuBeanReader myMenuBeanReader;
 
 @PostConstruct
        public void init() {
        
       String myReaderType=this.appProps.getProperty(menuReaderTypeKey);
     
       // get desired implementation of menuBeanReader by injection 
       myMenuBeanReader = unqualifiedMenuBeanReader.select(new MenuBeanReaderTypeDescriptor(myReaderType)).get();
     
 }
 
    /**
     * Reads the menu structure from a Json file using a class for that purpose.
     * @param isRelativeToResourceFolder (if relative Paths are used to access the config file)
     * @param fileName (Name of the file where configuration is stored)
     */
    public void readMenu(boolean isRelativeToResourceFolder, String fileName) {
     
     menuModel = new DefaultMenuModel();
     System.out.println("MenuBean.readMenu(" + isRelativeToResourceFolder + "," + fileName + ")" );
     
     try {
   //new MenuBeanReaderJSONRelativePath().readMenu(fileName, menuModel);
      myMenuBeanReader.readMenu(fileName, menuModel);
      
      
  } catch (IOException | MenuJsfException e) {
   
   e.printStackTrace();
  }
    }
}    
    

The property file application.properties is the same as in the previous post


1
2
3
4
5
webEnvironment=true
menuBeanReaderType=MenuBeanReaderJSONRelativePath
comment=This is a test commennt
kk=Other property
greet.first=Good Morning!

So the injected class is MenuBeanReaderJSONRelativePath, so the the attribute myMenuBeanReader will be transformed to this class instance by the sentence

myMenuBeanReader = unqualifiedMenuBeanReader.select(new MenuBeanReaderTypeDescriptor(myReaderType)).get();

That's all for now. I hope it wil be clearer

No hay comentarios:

Publicar un comentario

JEE & JSF16th Part: Creating an abstraction view layer to JSF components and Forms (5/5). Frequent problems

1. ERROR #1: Using a bean that does not exists In the previos entry we used this facelet file: 1 2 3 4 5 6 7 8 9 10 11 1...