Pipelines need Valves
In which I articulate preliminary design for uPortal rendering pipeline plugins to enable branching and terminating in redirects.
Update
This solution is now coded and deployed to MyUW test tiers and configured to apparent good effect.
The generic, re-usable, composable components in this solution potentially applicable across many uPortal adopters are now proposed for inclusion in the uPortal codebase for uPortal 4.2, though some issue tracker issues (which each link Pull Requests with actual code).
- UP-4259 : Add
BranchingRenderingPipeline
. - UP-4260 : Add
ProfileFNamePredicate
. - UP-4261 : Add
FocusedOnOnePortletPredicate
. - UP-4263 : Add
RedirectRenderingPipelineTerminator
.
What problem are you trying to solve?
When the user is using the Bucky theme (via an associated bucky
profile), when the Rendering Pipeline is called upon to render a not-just-one-portlet-maximized experience, the request handling needs to escape the confines of the Rendering Pipeline and simply redirect the browser to go back to that lovely new /web
path.
Background
In the MyUW redesign, there's an awesome new responsively-designed web UI implemented in AngularJS. This UI replaces the traditional invocation of the uPortal rendering pipeline to render a bunch of portlets in NORMAL
window state on your portal landing page, instead implementing that landing page in straight up HTML
/ CSS
/ JavaScript
that consumes JSON
web services for its dynamism.
In the new world, there's a /web
path that is this awesome new AngularJS stuff, and there's a /portal
path that is the classic uPortal rendering pipeline portlets stuff. And hey, there are some great portlets in this portal and some interactions from the AngularJS front end navigate to them, rendering them one-at-a-time in MAXIMIZED
window state.
So, there will be a period of time in which the portal is both supporting a Classic experience using Universality
/ mUniversality
and none of this AngularJS stuff (very conservative, same experience you may know and love in MyUW today) and a Beta experience using a new forked-from-Respondr
theme (named "Bucky
"). Within the Beta experience, some interactions are handled client-side by awesome new AngularJS stuff, and many interactions are simply to render a traditional portlet, maximized.
However, no intended interactions under Bucky
involve using the uPortal rendering pipeline to do anything other than render a single portlet at a time. Any request to render a dashboard-style multiple-normal-window-state portlets page under Bucky
is running amok and should be redirected back to the lovely new AngularJS landing page implementation.
Solution sketch
Invent a BranchingRenderingPipeline
that either continues the pipeline processing down the traditional pipeline or branches to a RedirectRenderingPipelineTerminator
, depending upon whether the Request
represents one for the new Bucky theme or not AND on whether the Request
represents one for a mosaic/dashboard of portlets or one for a single portlet.
Things to Code
BranchingRenderingPipeline
Implements IPortalRenderingPipeline.
Injectable properties:
truePipe
(an IPortalRenderingPipeline)falsePipe
(also an IPortalRenderingPipeline)predicates
(aSet<
Predicate<HttpServletRequest> >
) :Set
ofPredicate
s to be consulted to determine whether to proceed with thetruePipe
or thefalsePipe
. If all of thePredicate
s returntrue
, then proceed down thetruePipe
, otherise proceed down thefalsePipe
.
ProfileFNamePredicate
Implements Predicate<HttpServletRequest>
.
Injectable property:
targetProfileFName
(aString
)
(May need other dependencies for parsing the theme name out of the Request
.)
Returns true
if the active profile fname
for the user session in the Request
has exactly the injected targetProfileFName
, false
otherwise.
(It turns out you can read the active profile fname out of the request via a UserPreferencesManager
which can be handily @Autowired
injected by Spring.)
FocusedOnOnePortletPredicate
Implements Predicate<HttpServletRequest>
.
Returns true
if the request addresses just one portlet, false
otherwise.
(It turns out you can figure out the focused-ness of a request by asking a UrlSyntaxProvider
about its PortalRequestInfo
UrlState
, and that that Provider can be handily @Autowired
in.)
RedirectRenderingPipelineTerminator
Implements IPortalRenderingPipeline.
Issues a redirect.
Injectable property:
redirectTo
(aString
): the path to which to redirect.
Configuration
Locally in MyUW, we pop in this new BranchingRenderingPipeline
into our locally customized renderingPipelineContext.xml Spring configuration, plugging in a ProfileFNamePredicate
configured to predicate on the profile having the fname
"bucky" and a FocusedOnOnePortletPredicate
, with the logic properly wired up through the static factory methods in Predicates
, and then routing to the existing rendering pipeline as one pipe and to the newly added RedirectRenderingPipelineTerminator
(redirecting to /web
) as the other pipe.
Criticism
Advantages
- These objects are eminently unit testable.
- All of these Java classes are suitable for inclusion in Apereo uPortal itself and can be useful to other uPortal adopters. While this can (will!) be configured to Solve the Local Problem, the technology isn't Bucky-specific and becomes part of the rendering pipeline plumber's toolbox for solving the next problem, quite possibly without having to touch any of this Java.
- This solution requires zero implementation-local changes to uPortal Java files, which means no changes (to framework Java files) to hang out in the MyUW repo and require future merging.
- This should Just Work in a pretty deep way, in that edge cases (specifying a different profile via parameter at login? Addressing a portlet by fname? Using a print window state?) eventually amount to asking the Rendering Pipeline to render and so all those edge cases result in either simply proceeding through the traditional rendering pipeline or can be shunted back to AngularJS where they belong.
Disadvantages
- a locally forked Spring context file. That's probably a necessary reality -- those files are configuration after all.
- I'm probably supposed to be thinking in terms of rendering pipeline
Component
s rather than whole rendering pipelines, but it's more straightforward doing this logic before things get turned into streams and events and all that stuff.
This design does feel a wee bit like