scetix
scetix

Reputation: 813

Transferring CDI qualifier to injected field

I'm working on a Java EE 7 application in a Wildfly 8.2 container which contains some entites that exist in two data sources. Example:

I have a jar with Setting entity:

@Entity
public class Setting {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @NotNull
    private String name;
    private String value;

    getters/setters...
}

and a bean which has a couple of methods for retrieving and saving this entity from database via criteria query:

@Stateless
public class SettingRepository {
    @Inject
    private Logger logger;

    @Inject
    private EntityManager entityManager;

    public Setting findByName(@NotNull String name) {
        logger.trace("Getting setting by name: name=" + name);
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Setting> cq = cb.createQuery(Setting.class);
        Root<Setting> table = cq.from(Setting.class);
        cq.where(cb.equal(table.get(Setting_.name), name));

        TypedQuery<Setting> query = entityManager.createQuery(cq);
        List<Setting> results = query.getResultList();
        Setting setting = null;

        if (results.size() > 0)
            setting = results.get(0);

        logger.trace("Got setting: " + setting);
        return setting;
    }

    ...
}

I wan't to provide EntityManager and Logger instances via @Producer in a application which includes this jar in classpath like this:

@Produces @DataSource1 @PersistenceContext(unitName = "pu1")
private EntityManager entityManager1;

@Produces @DataSource2 @PersistenceContext(unitName = "pu2")
private EntityManager entityManager2;

@Produces
private Logger produceLogger(InjectionPoint injectionPoint) {
    return LogManager.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
}

Is there any way to configure SettingRepository at injection point and tell it to use specific entity manager (@DataSource1 or @DataSource2)?

Similar to this:

@Inject @DataSource1
private SettingRepository settingRepository;

Upvotes: 2

Views: 476

Answers (2)

scetix
scetix

Reputation: 813

The route I chose to take is this one:

I created a qualifier

@Qualifier
@Retention(RUNTIME)
@Target({ METHOD, FIELD, PARAMETER })
public @interface DataSource {
    @Nonbinding DataSourceName value() default DataSourceName.D1;
}

Note the @Nonbinding annotation which tells container I don't have to specify this parameter when annotating producer method (especially this one, sine I can implement generic producer, more on that later) or injection point.

And DataSourceName enumeration, which simply lists all data sources:

public enum DataSourceName {
    D1, D2
}

I also changed SettingRepository implementation and added a public initialize method.

@Dependent
public class SettingRepository {
    @Inject
    private Logger logger;

    private EntityManager entityManager;

    public void initialize(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    ...
}

Note that EntityManager is no longer injected by container.

When I wan't to inject SettingRepository, I simply decorate it with this qualifier like so:

@Inject @DataSource(DataSourceName.D1)
private SettingRepository settingRepository;

All I need now is to define a producer for SettingRepository:

@Stateless
public class TestResourcesForSettings {
    @PersistenceContext(unitName = "pu1")
    private EntityManager entityManager1;

    @PersistenceContext(unitName = "pu2")
    private EntityManager entityManager2;

    @Inject
    private SettingRepository settingRepository;

    @Produces @DataSource
    public SettingRepository produceSettingRepository(InjectionPoint ip) {
        DataSource annotation = ip.getAnnotated().getAnnotation(DataSource.class);

        if (annotation.value() == DataSourceName.D1)
            settingRepository.initialize(entityManager1);
        else if (annotation.value() == DataSourceName.D2)
            settingRepository.initialize(entityManager2);

        return settingRepository;
    }

And voila, I can use two data sources with one repository implementation (note: data sources have to be XA). If anyone finds any problems with this choice please tell me.

Upvotes: 1

Abhishek
Abhishek

Reputation: 1225

Yes !

In fact you have done all the hard work already as far as producer methods and qualifiers are concerned :-) All you need to do is use your qualifier (@DataSource1 etc) on the injected EntityManager instance

@Inject @DataSource1
private EntityManager entityManager;

This will ensure that JPA entity manager attached to persistence unit 'pu1' is injected by the container.

Upvotes: 0

Related Questions