Search code examples
javaejbjavabeanscdi

Injectind Implementation automatically on runtime over CDI


I am trying to figure out CDI and the best method that suits my needs. I have an service(TcpServiceImpl) that interacts with plain tcp communication. Now this service has some points where it needs to inform somebody that something happened. For this informations I have the Interface TcpConnection which needs to be CDI injected to the correct implementation. Another problem is that the service TcpServiceImpl itself is injected in a job (TcpConnectionJob) that executes periodically and calls the service to do things. This means that the service TcpServiceImpl will exist multiple times. Each having another tcp connection it handles and having another device that needs another driver/protocol to be injected in the Interface TcpConnection.

Let me show the three Elements taking part in this scenario:

Here is the Interface that will get multiple implementations:

public interface TcpConnection
{

  /**
   * Connected.
   *
   * @throws NGException the NG exception
   */
  public void connected() throws NGException;

  /**
   * This method will send the received data from the InputStream of the connection.
   *
   * @param data the received data
   * @throws NGException the  NG exception
   */
  public void received( byte[] data ) throws NGException;

  /**
   * Usable for the protocol to send data to the device.
   *
   * @param data the data to send to the device ( Will be converted to byte[] with getBytes() )
   * @throws NGException the  NG exception
   */
  public void send( String data ) throws NGException;

  /**
   * Usable for the protocol to send data to the device.
   *
   * @param data the data to send to the device ( Will be send as is )
   * @throws NGException the NG exception
   */
  public void send( byte[] data ) throws NGException;

  /**
   * This method will inform the protocol that the connection got closed.
   *
   * @throws NGException the NG exception
   */
  public void closed() throws NGException;
}

Also here is a example snippet of when this will be called in my existing service:

public class TCPServiceImpl implements TCPService, Runnable
{
/** The callback. */
private TcpConnection callback;
private void disconnect()
{
  connection.disconnect();
  if ( !getStatus( jndiName ).equals( ConnectionStatus.FAILURE ) )
  {
     setStatus( ConnectionStatus.CLOSED );
  }
  /* TODO: Tell driver connection is closed! */
  callback.closed();
}
}

Below is the class that calls the service,which then needs to dynamically inject the correct implementation for the interface.

public class TcpConnectionJob implements JobRunnable
{
  /** The service. */
  private TCPService service;

  public void execute()
  {
    service.checkConnection( connection );
  }
}

The service injection callback has to be linked to the implementation of the correct "protocol" or "driver" that will translate the data or handle the logic for the device. There will be multiple driver implementations of the interface acting different and I need to inject the correct one. A qualifier for this decision could be the name of the device. Now I looked at the following links:

Understanding the necessity of type Safety in CDI

How to programmatically lookup and inject a CDI managed bean where the qualifier contains the name of a class

How to use CDI qualifiers with multiple class implementations?

Question:

But I am still unsure about which way/method to use and what is the correct way. Any help would be appreciated.

My first thought was about copying my interface to an Qualifier Interface and appending this one with the possibility to enter the qualifier. Is that a good idea?


Solution

  • So this is my solution I came up with. The only problem now is to get a callback to work, but that's something different. Heres the solution that worked for me:

    /**
     * The Qualifier interface TcpDriver. The value of this annotation is the name the implementation
     * is found under. Please only enter values that are configured in the wildfly config as the name of
     * the device.
     */
    @Documented
    @Qualifier
    @Retention( RUNTIME )
    @Target( { TYPE, FIELD, METHOD, PARAMETER } )
    public @interface TcpDriver
    {
    
      /**
       * Value.
       *
       * @return the string
       */
      String value();
    }
    

    The default Implementation just for the Qualifier interface:

    /**
     * The Class TcpDriverImpl.
     */
    public class TcpDriverImpl extends AnnotationLiteral<TcpDriver> implements TcpDriver
    {
    
      /** The Constant serialVersionUID. */
      private static final long serialVersionUID = 1L;
    
      /** The name. */
      private final String name;
    
      /**
       * Instantiates a new tcp driver impl.
       *
       * @param name the name
       */
      public TcpDriverImpl( final String name )
      {
        this.name = name;
      }
    
      /** {@inheritDoc} */
      @Override
      public String value()
      {
        return name;
      }
    
    }
    

    Now a test implementation to test it:

    @TcpDriver( "terminal1" )
    @Dependent
    public class TestDriverImpl implements TcpConnection
    {
    
      /** The log. */
      private Log log;
    
      @Inject
      public void init( Log log )
      {
        this.log = log;
      }
    
      @Override
      public void connected() throws NGException
      {
        // TODO Auto-generated method stub
        log.info( "IT WORKS!!" );
      }
    
      @Override
      public void received( byte[] data ) throws NGException
      {
        // TODO Auto-generated method stub
    
      }
    
      @Override
      public void send( String data ) throws NGException
      {
        // TODO Auto-generated method stub
    
      }
    
      @Override
      public void send( byte[] data ) throws NGException
      {
        // TODO Auto-generated method stub
    
      }
    
      @Override
      public void closed() throws NGException
      {
        // TODO Auto-generated method stub
        log.info( "BYE BYE" );
      }
    

    }

    Last but not least, the way I injected all this in my service:

      /** The callback Instance for the driver to find. */
      @Inject
      @Any
      private Instance<TcpConnection> callback;
    
      private TcpConnection driver;
      /**
      * Inject driver.
      */
      private void injectDriver()
      {
        final TcpDriver driver = new TcpDriverImpl( name );
        this.driver = callback.select( driver ).get();
      }
    

    I hope this helps somebody having the same requirements I had.

    PS: A little log to show it works if you check the log outputs in the test implementation and then look at the log :)

    2017-02-28 08:37:00,011 INFO  starting TCPConnection: TcpDevice1 with status: NOT_CONNECTED
    2017-02-28 08:37:00,018 INFO  initializing terminal1
    2017-02-28 08:37:00,019 INFO  Creating socket for: terminal1 with port: XXXXX
    2017-02-28 08:37:00,023 INFO  Updated Status to CONNECTED for connection TcpDevice1
    2017-02-28 08:37:00,024 INFO  opened connection to terminal1
    2017-02-28 08:37:00,026 INFO  (terminal1) IT WORKS!!
    2017-02-28 08:37:00,038 INFO  (terminal1) terminal1: In threaded method run
    2017-02-28 08:37:00,039 INFO  (terminal1) waiting for data...
    2017-02-28 08:39:00,045 INFO  (terminal1) Socket closed!
    2017-02-28 08:39:00,045 INFO  (terminal1) BYE BYE