0

So I've looked a little bit around but still couldn't find anything that could help me. So this is my code:

public ExtendedBaseDAO getCorrespondingDAO(String tableName) throws DAOException {
        log.info("BaseService.getCorrespondingDAO(...): Getting DAO correspondant to table with name " + tableName
                + "...");
        try {
            Optional<EntityType<?>> entityFound = entityManager.getMetamodel().getEntities().stream()
                    .filter(entity -> ((Table) entity.getJavaType().getAnnotation(Table.class)).name().equalsIgnoreCase(tableName)).findFirst();
            log.info("Found entity with name " + entityFound.get().getJavaType().getSimpleName() + " mapped to " + tableName);
            Reflections reflections = new Reflections("eu.unicredit.fit.fit_core.dao");
            Optional<Class<? extends ExtendedBaseDAO>> daoClassFound = reflections.getSubTypesOf(ExtendedBaseDAO.class)
                    .stream().filter(daoClass -> daoClass.getSimpleName().replaceAll("DAO", "")
                            .equals(entityFound.get().getJavaType().getSimpleName()))
                    .findFirst();
            
            log.info("The correspondant DAO found is " + daoClassFound.get().getSimpleName() + ". Instantiating it...");
            return daoClassFound.get().getConstructor().newInstance();
        } catch (Exception e) {
            throw new DAOException("It was not possible to find the DAO associated with the table " + tableName
                    + "! Error: " + e.getLocalizedMessage());
        }
    }

As you can see I'm returning an instance of the corresponding DAO found using the 'tablename'. I need this method due to the fact that I'll know which table to interrogate at runtime through some parameters. The only problem is that when I'm calling the 'findById' method it's just going to give me a null pointer exception because the EntityManager for that dao is null.

Now... the EntityManager works fine. Here's the class calling that method

public class WizardFieldConfigService extends BaseService {

    @Inject
    private WizardFieldConfigDAO wizardFieldConfigDAO;

    /**
     * Retrieves the field data from the specific table requested for. To specify
     * the table use the fieldDataRequest.
     * 
     * @param fieldDataRequest The DTO to be used as input
     * @return a {@link FieldDataResponseDTO} object with a map containing the
     *         requested values
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public FieldDataResponseDTO getFieldData(FieldDataRequestDTO fieldDataRequest) {
        log.info("WizardFieldConfigService.getFieldData(...): Retrieving field data for field with id "
                + fieldDataRequest.getFieldID() + "...");
        
        WizardFieldConfig fieldBaseData = wizardFieldConfigDAO.findByID((long)fieldDataRequest.getFieldID());
        log.info("Found field data: " + fieldBaseData.toString());
        
        List<BaseEntity> response = getCorrespondingDAO(fieldBaseData.getDomainName())
                .findWithConditions(fieldDataRequest.getConditions());
        
        return new FieldDataResponseDTO().setPlaceHolder(fieldBaseData.getPlaceholder())
                .setLabel(fieldBaseData.getFieldName()).setRegex(fieldBaseData.getRegex())
                .setValueList((Map<? extends Serializable, String>) response.stream()
                        .collect(Collectors.toMap(BaseEntity::getId, BaseEntity::getDescription)));
    }

}

So here the first one 'findById' related to the Injected DAO is working fine, while the other DAO no matter what will return a null pointer for any method called because of the entity manager being null. I suppose it is because of it not being a injected bean, is there a way I can get through this problem and fix the entity manager being null?

EDIT: I forgot to mention that I'm doing it without Spring and just with plain CDI. Anyways it could be useful to share the DAO classes structure as mentioned in the comments:

This is ExtendedDAO which extends BaseDAO containing some default query methods:

@Slf4j public abstract class ExtendedBaseDAO<T extends BaseEntity, ID extends Serializable> extends BaseDao<T, ID>{

@PersistenceContext(unitName = "fit-core-em")
private EntityManager em;

protected ExtendedBaseDAO(Class<T> type) {
    super(type);
}

public List<T> findWithConditions(List<ConditionDTO> conditions) {
    //...
}


@Override
protected EntityManager getEntityManager() {
    return this.em;
}

}

Any DAO class would extend this one and therefore have access to the EntityManager. This in fact works perfectly fine for the Injected DAO within the service method

L_Cleo
  • 1,073
  • 1
  • 10
  • 26
  • 1
    Uh, inject `ApplicationContext` and get the bean from there? Of course you can't use reflection for it. How would Spring know to initialize it properly. – Kayaman Feb 16 '21 at 19:04
  • 1
    Are you in control of the DAO classes? Can you add CDI qualifier annotations on them? If so, you can retrieve them dynamically using CDI's `Instance`. If you want to (or must) use reflection and you know that your DAOs depend only on the `EntityManager`, you can add a constructor (or setter) and invoke that on reflective construction. If you can add a method `ExtendedBaseDAO.setEntityManager`, it would greatly simplify things. – Nikos Paraskevopoulos Feb 16 '21 at 23:04
  • @NikosParaskevopoulos so yeah I am in control of the DAO classes. How can I retrieve them dynamically? Can you answer the stack question so I'll mark it as accepted if it actually works. Also the second suggestion, I thought about that, but wouldn't it be structurally wrong? I mean ofc I could Inject the EntityManager within the class containing getCorrespondingDAO and then pass that entity manager to the setter. Is it a good idea? – L_Cleo Feb 17 '21 at 14:17
  • @Kayaman I am not using spring unfortunately. I understand tho the confusion and there fore added an edit that explains that – L_Cleo Feb 17 '21 at 14:17

1 Answers1

1

Since you are in control of the DAO classes, I think the simplest solution to dynamically translate a string to a bean is using CDI qualifiers with binding members (see CDI 2.0 spec. section 5.2.6).

So you already have:

public abstract class ExtendedBaseDAO<T extends BaseEntity, ID extends Serializable> extends BaseDao<T, ID>{ {
    ...
}

@ApplicationScoped
public class WizardFieldConfigDAO extends ExtendedBaseDAO<...> {
    ...
}

@ApplicationScoped
public class OtherDAO extends ExtendedBaseDAO<...> {
    ...
}

Define a qualifier annotation with a binding member:

@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface MyDao {
    String tableName();
}

Add it to your beans:

@MyDao(tableName="other")
@ApplicationScoped
public class WizardFieldConfigDAO extends ExtendedBaseDAO<...> {...}

@MyDao(tableName="other")
@ApplicationScoped
public class OtherDAO extends ExtendedBaseDAO<...> {...}

Create a utility annotation literal:

import javax.enterprise.util.AnnotationLiteral;

public class MyDaoQualifier extends AnnotationLiteral<MyDao> implements MyDao {
    private String tableName;

    public MyDaoQualifier(String tableName) {
        this.tableName = tableName;
    }

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

Get it like:

@Inject @Any
private Instance<ExtendedBaseDAO<?,?>> instance;

public ExtendedBaseDAO getCorrespondingDAO(String tableName) throws DAOException {
    try {
        return instance.select(new MyDaoQualifier(tableName)).get();
    } catch (ResolutionException re) {
        throw new DAOException(re);
    }
}

Here is a nice article describing this.

BEWARE of dynamically creating @Dependent-scoped beans, see this!!! TL;DR: Better define an explicit normal scope to your DAOs (e.g. @ApplicationScoped).

Nikos Paraskevopoulos
  • 39,514
  • 12
  • 85
  • 90
  • 1
    Thank you so much. Just tested it and it perfectly works. CDI actually has some pretty nice hidden features I guess, couldn't find much online. – L_Cleo Feb 18 '21 at 10:01