0

I'm trying to find a way to conditionally produce an @Alternative/@Specialized bean in CDI 1.2 if a given JNDI resource is present.

My use case is that I have a default bean which uses an in-memory data structure. However, if a given JNDI resource is present, I want to use that resource instead in a different implementation of my service. My problem is that as soon as I use a @Resource(lookup='jndiName') annotation, Weld throws an exception if the resource is not found.

I wanted to use a producer to conditionally create the resource-based bean, but if I try to inject the @Resource and it is missing, Weld fails.

For example:

interface MyService{
  void doSomething();
}


// In memory implementation that I always want to have available for injection if no other MyService bean implementation is available
@ApplicationScoped
public class InMemory implements MyService{
   Map<String, String> persistence = new HashMap<>();

   public void doSomething(){ persistence.put("now", new Date());}
}


// Bean to be available IFF a JNDI based cache is found
public class JndiPersistence implements MyService{
   Cache<String, String> persistence;

   public JndiPersistence(Cache persistence){ this.persistence = persistence);}
   public void doSomething(){ persistence.put("now", new Date());}
}



// client class which uses the service
public class DataManager(){
   private MyService myservice;

   @Inject
   public DataManager( MyService myservice ){ this.myservice = myservice; }

   // calls the injected service bean
   public void manageMyData(){
     myservice.doSomething();
   }

Finally, the producer:

public class JndiProducer{

   @Resource(lookup="java:comp/env/persistenceCache")
   Cache cache;

   @Produces
   @ApplicationScoped
   public MyService jndiBean(){
      return new JndiPersistence( cache );
   }
   
}

I have tried changing my Resource injection to Instance<Cache> but if the jndi name is missing, it still throws an exception and doesn't even make it to the Producer. Finally, I am not sure how to make the entire Producer conditional on the resource being present and override the initial bean.

What is the correct approach for this in CDI? Is this even feasible, or is there a better approach to use instead?

Eric B.
  • 23,425
  • 50
  • 169
  • 316
  • you shouldn't be using `@Resource` if you don't know if the resource will be there or not, it's meant to fail if it is not present. Do a manual lookup like suggested in the answer. – eis Sep 06 '21 at 08:41
  • @eis Thanks - that's what I wasn't sure about. I worked around it using the manual lookup, but find that it doesn't make the JNDI requirement as explicit/visible when reading through/maintaining the code long term. – Eric B. Sep 06 '21 at 18:35
  • if it's optional dependency then it's not really a requirement? :) you could implement your own annotation for doing an optional JNDI lookup (and doing a manual lookup under the covers) for extra visibility – eis Sep 07 '21 at 08:22

1 Answers1

2

The way I would go is a conditional producer with manual JNDI interactions.

I would start by making sure CDI ignores both implementations of MyService:

@Vetoed
public class InMemory implements MyService { ... }

@Vetoed
public class JndiPersistence implements MyService { ... }

Then go ahead with the producer (please note code is approximate, just to show the general principle, it might require adjustments/fine tuning):

// no real need for scoping the producer, unless the environment/configuration demands it
public class MyServiceProducer {
    private MyService instance;

    @Produces
    @ApplicationScoped // this scoping applies to the produced bean
    public MyService getMyService() {
        // I *think* CDI guarantees that this will not be accessed concurrently,
        // so no synchronization needed
        if (instance == null) {
            try {
                InitialContext ic = new InitialContext();
                Cache cache = (Cache) ic.lookup("java:comp/env/persistenceCache");
                instance = new JndiPersistence(cache);
            } catch (NamingException ne) {
                // you can probably ignore it...
                // use correct judgement though: you may want to log it
                // or even fail altogether (i.e. rethrow it) e.g. if you sense
                // that JNDI should be there but it is malfunctioning
                instance = new InMemory();
            }
        }
        return instance;
    }
}
Nikos Paraskevopoulos
  • 39,514
  • 12
  • 85
  • 90
  • Thanks; that's pretty much the pattern I came up with as well, but I find it hides a bit of the structure. I was looking more for a pattern which would allow me to create both beans in the context, but have the producer only inject the appropriate bean. Perhaps I was just complicating things for nothing. – Eric B. Sep 06 '21 at 18:33
  • I feel that other solutions would indeed add unnecessary complexity. For example, why create 2 beans to use only one in the end? Other ways I can think of would include: (1) a CDI portable extension (fancy, but unnecessary complexity :) ) (2) separate assemblies (e.g. Maven projects) per environment, where the concrete implementation of `MyService` is given in the assembly for that environment. However I have to ask - is there any other requirement that we have missed that perhaps warrants the extra complexity? – Nikos Paraskevopoulos Sep 06 '21 at 20:16
  • No additional requirements tbh. The way the Service is packaged is that it is part of a separate module/lib for which I wanted to provide a default bean/implementation. In the consuming war module, I wanted to override the default one supplied by the lib. However, the idea of writing and extension for this one use case is significant overkill and will make long term maintenance even harder. So I was just looking to see if there was a better/cleaner approach. – Eric B. Sep 07 '21 at 02:38