Sebastian Wramba
Sebastian Wramba

Reputation: 10127

Replace factory-based object creation with CDI mechanism

I wanted to introduce CDI (Weld) to our project and now having some trouble with objects that are manually constructed.

So we have some classes implementing the IReport interface, which have a field that should be injected. This is null at runtime because all of those classes are being generated by the ReportFactory in a class ReportController.

private Map<String,Object> generateReport(ReportInfo ri, ...) {
// some input validation
    IReport report = ReportControllerFactory.getReportInstance( ri.getClassName() );
// ...
}

I am aware that I can use the @Produces annotation together with another custom annotation in the ReportControllerFactory, but how do I use the @Inject for a variable that can only be created, after some validation was done, inside a method? And how would I submit the parameter ri.getClassName()? The object ri is not known when the ReportController is constructed.

Thank you very much!

Kind regards, Sebastian

Edit at Jul 08, 2011 (10:00):

ReportFactory class:

public static IReport getReportInstance( String className ) throws ReportException {

    IReport report = null;

    try {
        Class<?> clazz = Class.forName( className );
        report = (IReport) clazz.newInstance();
    }
    catch ( Exception e ) { … }        

    return report;
}

Edit 2 (Selection of the right report implementation)

The report instance is selected by some paths that go from the JSF frontend to the ReportController. The ManagedBean calls a session bean, which has several methods, depending on which button was pressed where. All those methods set the report name and call the more generic method sendOrGetReport. This method selects the unique key for the specified report from the database and decides whether to send an e-mail or immediately deliver the report. Let's assume it should be delivered.

Then the ReportController comes into play. He fetches a ReportInfo object based upon the unique key and other information provided by the methods above and calls the ReportFactory to create a report of type ri.getClassName().

Fancy, huh? I think this whole section might need some refactoring. If you don't see any easy spot, I skip the @Inject in the report implementation and do a JDNI lookup for that resource.

Upvotes: 2

Views: 11770

Answers (3)

yoda
yoda

Reputation: 173

In order to dynamically create the right report class a small change to the accepted answer will solve the problem. The solution is the fact that Instance<...> gives you the list of all beans that are of a certain type. A produces annotation is not needed.

Make a factory class that can select between the injected instances at runtime

public class ReportFactory {

@Inject Instance<IReport> availableReports;

public IReport createReport(String type) {

   for (IReport report: availableReports) {
      if (report.getType().equals(type)) { //or whatever test you need
         return report;
      }
   }
   return null;
}

Now the class that needs a dynamically selected report, can use this factory.

public class ReportCreator {

    @Inject
    private ReportFactory reportFactory;

    public void createReport(String type) {
        IReport report = reportFactory.createReport(type);
        report.execute();
    }
 }

Upvotes: 12

Jan Groth
Jan Groth

Reputation: 14636

The idea behind CDI (and other DI-frameworks) in order to manage dependencies is to take over control of the managed beans lifecycle. This implies - among other things - that you cannot interfere with the creation of managed beans if you want it to be managed by the container.

Certainly this does not mean that your scenario is unsolvable, you just have to change your perspective a bit ;-)

The idea is to work with managed beans (obviously), but let your own logic decide which instance of all available instances is the correct.

...
@Inject Instance<IReport> availableReports;
...
@Produces
public IReport createReport() {
   IReport result;
   for (IReport report: availableReports) {
      // choose correct instance, you might want to query the injection
      // point or any attached qualifier a bit more in order to 
      // determine which is the correct instance
      if ...
         result = report;
      ...
   }
   return result;
}
...

With as many beans of beantype IReport as you like

public class AReport implements IReport {
...
@Inject
...
}

public class BReport implements IReport {
...
@Inject
...
}

And an automagical usage like this

public class MyStuff {
...
@Inject
IReport myReport;
...
}

See here and here for more information.

If I did not misunderstand your problem, this should bring you forward - feel free to post further questions / comments.

UPDATE:

Everything might be just dead simple if something like this fits your requirements:

@AReport
public class AReport implements IReport {
...
@Inject
...
}

@BReport
public class BReport implements IReport {
...
@Inject
...
}

With the usage like this

public class MyStuff {
...
@Inject
@AReport
IReport myAReport;
...
@Inject
@BReport
IReport myBReport;
...
}

Upvotes: 8

phtrivier
phtrivier

Reputation: 13357

Ok, so if I'm not too wrong, based on the doc ( Is this the proper framework ? http://docs.jboss.org/seam/3/latest/reference/en-US/html/solder-beanmanagerprovider.html ), your factory would need to get a handle to the BeanManager singleton (either get it injected, or call some accessor from the framework) , and do something along the lines of

Class<?> clazz = Class.forName( className );
report = beanManager.getBean(clazz);

Assuming your CDI has been configured to handle each of the possible class name, you should get the correct bean. Now this might always be the same instance ; I don't know if this is what you need, sorry.

Sorry If I'm mistaken ; hoping it helps.

Upvotes: 1

Related Questions