An idea for an IChannel that delegates all of its methods to a Spring-configured IChannel instance. This allows IChannels to receive dependencies (DataSources, IPersonAttributeDao instances, other service beans) via Dependency Injection.
Singletons and Prototypes
Spring allows you to specify not just singleton beans but also prototypes. On each request for a prototype bean the Spring BeanFactory will provide a new instance of that bean, configured as specified. This is important because the IChannel API is such that we have one IChannel instance per channel-in-layout per user session. No two users share an IChannel instance. No two instances of a multiply instantiated channel (say, the user has the RSS Reader channel twice) are the same IChannel instance.
Of course, if a particular IChannel relied upon no configuration, was, say, the boringest of idempotent read-only channels (think, the footer channel), then it wouldn't have to be a prototype in the Spring configuration, and everyone could use the same instance.
Code sketch
This is a code skectch for an IChannel that reads a StaticChannelData parameter to determine the name of the Spring-configured bean to which it should delegate.
package org.jasig.portal.channels;
import org.jasig.portal.ChannelRuntimeData;
import org.jasig.portal.ChannelRuntimeProperties;
import org.jasig.portal.ChannelStaticData;
import org.jasig.portal.IChannel;
import org.jasig.portal.PortalEvent;
import org.jasig.portal.PortalException;
import org.jasig.portal.spring.PortalApplicationContextFacade;
import org.xml.sax.ContentHandler;
/**
* This IChannel delegates to a Spring-configured instance of
* IChannel. Expected usage is to configure a prototype IChannel instance via
* Spring behind the PortalApplicationContextFacade with a particular name
* and then configure this ChannelToBeanProxy instance with that name as
* the value of the ChannelStaticData parameter 'targetBeanName'.
*
* Note that the word "Proxy" is used in the name of this class in the same
* spirit as the Acegi FilterToBeanProxy, the Struts ActionToBeanProxy, etc -
* we aren't talking about Java Proxies in the sense of the Reflection API.
*
* @version $Revision:$ $Date:$
*/
public class ChannelToBeanProxy
implements IChannel {
/**
* The name of the ChannelStaticData parameter we will read to
* get the name of the bean from the PortalApplicationContextFacade to
* which we should delegate.
*/
public static final String TARGET_BEAN_NAME_SD_PARAM = "targetBeanName";
/**
* Delegate IChannel which we are proxying.
*/
private IChannel delegate;
public void setStaticData(ChannelStaticData sd) throws PortalException {
String beanName = sd.getParameter(TARGET_BEAN_NAME_SD_PARAM);
if (beanName == null) {
throw new IllegalStateException("ChannelToBeanProxy requires the static data parameter ["
+ TARGET_BEAN_NAME_SD_PARAM
+ "]; this parameter was not set in the static data we received [" + sd + "]");
}
this.delegate = (IChannel) PortalApplicationContextFacade.getPortalApplicationContext().getBean(beanName, IChannel.class);
this.delegate.setStaticData(sd);
}
public void setRuntimeData(ChannelRuntimeData rd) throws PortalException {
this.delegate.setRuntimeData(rd);
}
public void receiveEvent(PortalEvent ev) {
this.delegate.receiveEvent(ev);
}
public ChannelRuntimeProperties getRuntimeProperties() {
return this.delegate.getRuntimeProperties();
}
public void renderXML(ContentHandler out) throws PortalException {
this.delegate.renderXML(out);
}
}
This kind of class could nicely handle the use-case behind IMultithreaded. I'm thinking the Proxy should also implement IPrivledged (so we can get the PCS to grab a handle to a real ApplicationContext), ICacheable (we can check instance of on the delegate and return false if it isn't), and maybe IMimeResponse?
Maybe we should call it SpringConfiguredChannel instead of ChannelToBeanProxy? Seems like it would be more descriptive.
I'm pretty enamoured with the name "ChannelToBeanProxy" because of the parallels with the StrutsToBeanProxy, FilterToBeanProxy, etc. That said, I'm more excited about the idea of anyone else running with this, so if the name needs to change, so be it.
I like the idea of implementing IPrivileged so as to get at a real ApplicationContext and the other non-multithreaded channel methods and delegating appropriately.
I'd also like to see an ICharacterResponse channel solution here – I suspect we need two parallel ChannelToBeanProxy flavors, since IChannel and ICharacterChannel have fundamentally different response behaviors, even though ICharacterChannels are currently required to also implement the basic IChannel render method.
Pick up these methods, and then we could refactor the JSR-168 adaptor channel to be Spring-wired, which might be a very good thing since that channel is an ever more frequently identified pain point in need of refactoring. Wisc hacked it seriously to get where they needed to be, and there are still Portlet support bugs. Eric and others have eloquently expressed the view point that the way to get really good JSR-168 support is to get to uP3 rather than trying to get it fixed in uP2, and I don't necessarily disagree, but I think the reality is we're going to meet in the middle – some amount of continued improvement will be required on the uP2 JSR-168 adaptor channel and getting it to be Spring wired could be a great way to make that refactoring more feasible.
Perhaps - I'm concerned that the cost of migration to uP3 is going to be too high for most poeple to justify improved portlet support - hence my hesitancy at our positioning with regards to the advantages of the code base. I'm intrigued at the thought of refactoring the Portlet adaptor to sit on top of this, seems like it might give us the opportunity to slip in that additional layer of portlet preferences Eric's been feeling pain for - kind of ugly, but it should work.
ICharacterResponse... I started writing one, but then got back to needing to implement renderXML anyway (even minimally) and some scary notes from Peter about character caching having to be turned on?
Should we be pulling this channel into portal HEAD for development?
I'll look at it this week. I'm going to take some time to try fleshing out a quick requirements & JIRA set first (unless you want to take a stab at it...
Gotta try to do the right thing...