Forums de discussion

Inject declarative services in JSF beans, validators, ...

thumbnail
Peter Mesotten, modifié il y a 7 années.

Inject declarative services in JSF beans, validators, ...

Junior Member Publications: 45 Date d'inscription: 04/02/09 Publications récentes
Neil,

We're currently in the process of evaluating the latest version of the bridge. In our 6.2 portlets, we've used the JSF + CDI (backed by Spring) combination to inject custom written components on our beans and validators. This worked greatly. We worked the same way for Liferay services, which we exposed using a custom applicationContext.xml. So we were effectively able to do something like this:

@Named @Scope("view")
public class CustomerOverviewBean implements Serializable {

	@Inject private Portal portal;
	@Inject private GroupLocalService groupService;
	@Inject private MyCustomService myCustomService;

	private List<customer> customers;

	@PostConstruct
	public void init() {
		List<group> groups = groupService.getCompanyGroups(portal.getDefaultCompanyId());
		customers = myCustomService.convertToCustomers(groups);
	}

	public List<customer> getCustomers() {
		return customers;
	}
}
</customer></group></customer>


Looks great, and is very easy to unit test. We completely removed the need to work with the ugly static util classes.

In DXP, we would like to use a similar approach. We've created a separate plain OSGi module that exposes a declarative service using the @Component tag. Now we would like to inject this service onto a managed bean of our JSF portlet, which resides in its own WAR. Similarly, we would like to be able to inject OSGi services of Liferay (GroupLocalService, Portal, ...). Again, we want to avoid using static util classes at all cost.

As we see it, we can currently not inject these services using the @Reference tag. For the moment, beans and validators cannot be written as OSGi components, because they are managed by JSF.

So we were looking for alternatives. One solution we've found is to expose OSGi services to the JSF context using a custom EL resolver. This way, we can inject these OSGi services (coming from either custom OSGi bundles or the Liferay API) using @ManagedProperty annotations. So the example above is converted to this:

@ManagedBean @ViewScoped
public class CustomerOverviewBean implements Serializable {

	@ManagedProperty(value = "#{portal}") private Portal portal;
	@ManagedProperty(value = "#{groupService}") private GroupLocalService groupService;
	@ManagedProperty(value = "#{myCustomService}") private MyCustomService myCustomService;

	private List<customer> customers;

	@PostConstruct
	public void init() {
		List<group> groups = groupService.getCompanyGroups(portal.getDefaultCompanyId());
		customers = myCustomService.convertToCustomers(groups);
	}

	public List<customer> getCustomers() {
		return customers;
	}
}
</customer></group></customer>


You can see that the solutions remains more or less the same, apart from the annotations. The only pesky thing about this is that we had to write a custom EL resolver that looks for services in the bundle context.

I now have a few questions:
1. What do you think of this approach in general?
2. Does something like our custom EL resolver already exists in the LiferayFaces project? If not, why not? ;-)
2. Do you see a problem in using a custom EL resolver for our JSF portlets? Will this conflict with the EL resolver that the LiferayFaces bridge provides itself?
3. Do you see another solution?

Thank you so much for your feedback.
Peter
thumbnail
Neil Griffin, modifié il y a 7 années.

RE: Inject declarative services in JSF beans, validators, ...

Liferay Legend Publications: 2655 Date d'inscription: 27/07/05 Publications récentes
Hi Peter,

Thanks so much for posting this in the forums, because this is a design question that many developers will be facing.

Peter Mesotten:
1. What do you think of this approach in general?


Defining a service in your separate plain OSGi module (that exposes a declarative service using the @Component tag) is an excellent design choice and fits very well with Liferay 7 and OSGi.

However, using an ELResolver to inject the service using @ManagedProperty misses one of the main benefits of OSGi which is dynamicity. Instead, you need to anticipate failure, since your OSGi service could go away or appear at any time. This can be accomplished with a ServiceTracker (see more on that below).

Peter Mesotten:
2. Does something like our custom EL resolver already exists in the LiferayFaces project? If not, why not? ;-)


We considered developing an ELResolver for the Liferay Faces project that does something similar to what you described, but we decided against it for two reasons:
1. It did not take dynamicity into account
2. Looking forward to Portlet 3.0 (with its heavy reliance on CDI), we didn't want to create a JSF-centric solution.

Peter Mesotten:
3. Do you see a problem in using a custom EL resolver for our JSF portlets? Will this conflict with the EL resolver that the LiferayFaces bridge provides itself?


The custom ELResolver should not conflict with the ELResolver provided by the bridge, but I still would not recommend that approach.

Peter Mesotten:
4. Do you see another solution?


The solution we are recommending for Java EE type portlets (JSF, Spring Portlet MVC, etc.) is to use a ServiceTracker because it can take advantage of OSGi dynamicity and fits better with the scope/lifecycle of beans that are managed by CDI, Spring, or the JSF managed-bean facility. The ServiceTracker can be acquired and opened in a bean's @PostConstruct method, and then closed in the bean's @PreDestroy method.

Our primefaces-users-portlet demonstrates how to do this with UserLocalService. For more details, see the UsersModelBean.postConstruct() and UsersModelBean.preDestroy() methods. See also the UsersModelBean.getDataModel() method to see how a ServiceTracker can be used to anticipate failure.

We also have a Liferay 7 tutorial titled Services in JSF that explains this concept.

Looking forward Portlet 3.0, (with its heavy reliance on CDI) we will need to integrate CDI with the OSGi Service Registry, possibly by implementing OSGi RFC 193.

Best Regards,

Neil
thumbnail
Peter Mesotten, modifié il y a 7 années.

RE: Inject declarative services in JSF beans, validators, ...

Junior Member Publications: 45 Date d'inscription: 04/02/09 Publications récentes
Hi Neil,

Thank you for your swift response!

We indeed looked into the matter of ServiceTrackers. Although they are OSGi conform and thus might be the recommended way of accessing OSGi services from a WAR module, we see one big issue: verbosity and boilerplate code.

As an example, I converted the example above in a ServiceTracker approach, assuming that I've already defined ServiceTracker wrappers for UserLocalService, Portal and MyCustomService as proposed here. This is the result:

@ManagedBean @ViewScoped
public class CustomerOverviewBean implements Serializable {

	private transient PortalServiceTracker portalServiceTracker;
	private transient GroupLocalServiceTracker groupServiceTracker;
	private transient MyCustomServiceTracker myCustomServiceTracker;

	private List<customer> customers;

	@PostConstruct
	public void init() {
		portalServiceTracker = new PortalServiceTracker(this);
		groupServiceTracker = new GroupServiceTracker(this);
		myCustomServiceTracker = new MyCustomServiceTracker(this);

		portalServiceTracker.open();
		groupServiceTracker.open();
		myCustomServiceTracker.open();

		if (!myCustomServiceTracker.isEmpty()) {
			// We assume that Portal and UserLocalService are always deployed
			Portal portal = portalServiceTracker.getService();
			GroupLocalService groupLocalService = groupServiceTracker.open();

			List<group> groups = groupService.getCompanyGroups(portal.getDefaultCompanyId());

			customers = myCustomService.convertToCustomers(groups);
		} else {
			customers = Collections.emptyList();
		}
	}

	public List<customer> getCustomers() {
		return customers;
	}

	@PreDestruct
	public void destroy() {
		portalServiceTracker.close();
		groupServiceTracker.close();
		myCustomServiceTracker.close();
	}
}
</customer></group></customer>


Now that's really a lot of code compared to where we came from in 6.2 or in our custom ELResolver approach. Let alone that we have to duplicate this boilerplate for every bean that uses OSGi services in our portlet. Also, I don't see how the ServiceTracker wrappers can be reused across different WAR projects, so we'd have to redefine similar wrappers in every single JSF portlet...

I understand that one of the benefits of using ServiceTrackers is dynamicity, allowing you to anticipate failure.

In my opinion, the same applies for our custom ELResolver. When retrieving a service using an EL expression, we let our ELResolver return null if the service is not found. This means we can anticipate the fact that e.g. a bundle with a service we depend on has stopped using a simple null check. In our example:

@ManagedBean @ViewScoped
public class CustomerOverviewBean implements Serializable {

	@ManagedProperty(value = "#{portal}") private Portal portal;
	@ManagedProperty(value = "#{groupService}") private GroupLocalService groupService;
	@ManagedProperty(value = "#{myCustomService}") private MyCustomService myCustomService;

	private List<customer> customers;

	@PostConstruct
	public void init() {
		List<group> groups = groupService.getCompanyGroups(portal.getDefaultCompanyId());

		if (myCustomService != null) {
			customers = myCustomService.convertToCustomers(groups);
		} else {
			customers = Collections.emptyList();
		}
	}

	public List<customer> getCustomers() {
		return customers;
	}
}
</customer></group></customer>


I just verified this on my local example. If the bundle that contains MyCustomService is stopped, the ELResolver returns null and the fallback scenario falls into place.

Neil, please understand that I certainly don't want to pretend that our custom ELResolver solution is almighty. But currently for me, the drawback of it being a JSF centric solution doesn't outweigh the verbose character of the ServiceTracker approach, which seem to have only minor added value in this particular case.

At our company, we've embraced and advocated JSF as the portlet framework of choice for several years because it gives us the best combination of productivity, stability and code compactness. So it is our secret hope that it can stay that way ;-)

I'll be happy to hear your thoughts on this too!

Thanks again,
Peter
thumbnail
Neil Griffin, modifié il y a 7 années.

RE: Inject declarative services in JSF beans, validators, ...

Liferay Legend Publications: 2655 Date d'inscription: 27/07/05 Publications récentes
Hi Peter,

I'm happy to know that your organization sees JSF as the portlet framework of choice because you believe that it has the best combination of productivity, stability and code compactness. emoticon

Regarding the ServiceTracker approach, I agree that it causes extra verbosity and boilerplate code. Perhaps we can work together to find a better way.

I think that the best case would be to keep your CDI code style that you posted at first:
@Named @Scope("view")
 public class CustomerOverviewBean implements Serializable {
 
 @Inject private Portal portal;
 @Inject private GroupLocalService groupService;
 @Inject private MyCustomService myCustomService;
}


Perhaps you could experiment by creating a CDI producer (or a dynamic producer) that gets a service from the OSGi service registry. You might also want to experiment with a CDI disposer. I recommend that you take a look at the new JSF 2.3 FacesContextProducer.java class for ideas.

BTW, this also reminds me of a thread in which community member Tobias Liefke used CDI to inject Liferay class instances like ThemeDisplay into his JSF backing beans.

Neil