Jetspeed currently doesn't support the (optional) custom-portlet-mode and custom-window-state features for portlet applications as specified by JSR-168.
To support this, I'm going to provide the following new features:
1) Handle the optional portlet.xml tags custom-portlet-mode and custom-window-state
This requires two new database tables: CUSTOM_PORTLET_MODE and CUSTOM_WINDOW_STATE with a reference to PORTLET_APPLICATION.
2) As the JSR-168 describes, a defined custom-portlet-mode or custom-window-state needs to be mapped at deploy time to one of
the (internal) supported portal portlet modes and window states.
Only mapped custom portlet modes and window states will be available for the portlets defined by the portlet application
through PortalContext.getSupportedPortletModes() and PortalContext.getSupportedWindowStates().
Now, this is a tricky requirement which can be implemented in two different ways:
a) Only support custom portlet modes and window states which exactly match internal (portal) supported portlet modes and window states
This is rather easy but also very limiting on portability.
Consider a portlet needing a custom window state called "solo".
Different portals already support such custom window state, but might use a different (internal) name like "popup" (Liferay) or "detached" (WAP).
If you want to run the same portlet on one of the other portals, you will have to modify every usage of "solo" to the name used by the
target portal if it doesn't support mapping of custom modes and states to internal modes and states.
b) Support mapping of custom portlet modes and window states to the internal (portal) supported portlet modes and window states.
This is (very) much more complicated to do.
But: it does support migrating a third party portlet application wanting to use a custom mode or state with a different name as the Jetspeed internal modes and states.
Note: it won't help migrating from Jetspeed to another portal if that one can't map custom modes or state (and most don't).
As you might expect: I've chosen solution b)
To support this, mapping custom modes and states can be done in the jetspeed specific deployment descriptor jetspeed-portlet.xml.
<!-- map custom "popup" window state to Jetspeed-2 internal "solo" state -->
<!-- map custom "admin" portlet mode to Jetspeed-2 internal "config" mode -->
I will provide an actual implementation of a "solo" WindowState for Jetspeed-2, and demo this through the demo application using a "popup"
custom WindowState. That feature (supporting "print" mode in "solo" state) will be recorded under a separate JIRA issue.
3) To properly support mapping of custom modes and states as described in 2.b), some rather large (but mostly Jetspeed internal) refactoring
a) "fixing" Pluto's improper modeling of the PortalContext as a static information resource.
This issue really is a design error of the Pluto PortletContainer.
Because Pluto doesn't implement (most of) the optional features of the JSR-168 specification, some elements aren't that properly designed...
The PortalContext handling is one example of that.
In Pluto, this component is regarded as a static information provider and as such the Pluto provided factory interfaces don't account for
the context in which a PortalContext is accessed: PortletRequest.getPortalContext().
But as I've explained above, PortalContext.getAllowedPortletModes() and PortalContext.getAllowedWindowStates() really are PortletApplication
specific methods. It really depends on what possible custom modes and/or states are defined as well as how these are mapped (or not).
Now, "fixing" this Pluto design error really can't be done "cleanly" without a big refactoring of Pluto itself
So, I'll have to fall back to the old "dirty" trick of using a ThreadLocal to provide the PortletApplication context I need...
For this, I'll create a commons PortletRequestContext which will store the current Portlet, PortletDefinition as well as the PortletRequest and PortletResponse
in a ThreadLocal for each PortletInvoker call.
b) JetspeedPortalContext currently loads as well as stores the supported modes and states as defined in WEB-INF/conf/jetspeed.properties.
But now, the actual supported modes and states needs to be determined by the PortletApplication in context.
In jetspeed-api interface o.a.j.JetspeedActions already defined supporting constants for the standard (and some, not really used custom) modes
and states. Some security features (like ResourcePermission) as well as the decoration classes uses the JetspeedAction constants but handled
standard and custom modes or states logic themselves.
I decided to centralize all these usages of both standard as well as extended (internal) portlet modes and window states within o.a.j.JetspeedActions.
For this, I'll change the interface to a real class, and provide access to and usage of all the standard and supported extended modes and states through
On initialization of the Jetspeed Engine, the JetspeedPortalContext still will load the extended modes and states from jetspeed.properties (ignoring
any standard modes and states as those have to be supported anyway), but then store those inside a new JetspeedActions singleton.
After the Engine initialization, all usages of standard, and especially extended, modes and state must be done through JetspeedActions.
c) The PortletApplication will be extended to:
- provide access to the supported PortletModes and WindowStates, taking care of checking and validating custom modes and states and if they are mapped
on internal portal modes and states
- provide translation of mapped (portal internal) modes and states to standard/custom (portal application level) modes and states, and visa versa
d) NavigationalState processing also needs to be adjusted to handle custom modes and states, while internally it needs to validate and store these as
internal mapped modes and states.
And then, these internal mapped modes and states need to be translated back to the custom modes and states when accessed by the related portlet.
So, AbstractNavigationalState is going to be extended to provide mapping between internal and custom modes and states too.
e) Finally, the portlet decorators also use modes and states to render clickable links (DecoratorActions) allowing a user to change a portlet
window its state and/or mode.
The current implementation only supports the standard modes and states, and in a way not easily customized.
Once custom modes and states are being provided, their should also be an easy (or at least easier) way to render portlet decoration links for changing them.
For this, I'll refactor the creation and usage of DecoratorActions in the DecoratorValve.
I'll provide a new DecoratorActionFactory interface and a light-weight DecoratorActionTemplate class providing context information for creating a DecoratorAction,
The DefaultDecoratorActionFactory will simply implement the same features as currently provided.
But, by defining a custom factory in a Portlet decorator decorator.properties, it becomes rather easy to provide and manipulate a customized set of DecoratorActions
for the portlet decorator to be rendered.
As I've a requirement to provide a "print" action, which will open the selected portlet in a separate popup ("solo" state) and in "print" mode, I'll
provide an example PrintSoloDecoratorActionFactory later too (on a separate JIRA issue).
f) Additionally, while further investigating the implementation and usage of PortalContextProvider (Pluto interface), o.a.j.PortalContext and o.a.j.JetspeedPortalContext
I discovered a lot of redundant wrapping and useless instantiating of Pluto PortalContextImpl.
As these are used solely internally, I decided to at least "cleanup" this part of the mess, resulting in a lot less overhead.
The Pluto PortalContextProvider now isn't used at all anymore nor should it as its "dynamic" usage concerned mainly getting the allowedPortletModes and
allowedWindowStates which now is (to be) handled by JetspeedActions and the PortletApplication implementation.
The above described refactorings concern for the most part handling translating (possible) custom modes and states to internal mapped modes and states.
I've (already) done this as transparent as possible, and in most situations when using PortletModes and WindowStates this is really transparent.
When handling of all allowed modes or states is needed, as in rendering Portlet Decorator Actions,
or when validating of modes and states is needed, as in NavigationalState processing or maybe AJAX interactions,
you need to make sure only using standard modes and states or properly handling translating between custom and mapped modes and states.