Reputation: 10127
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
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
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
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