Pipelines need Valves

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 (a Set<Predicate<HttpServletRequest> >) : Set of Predicates to be consulted to determine whether to proceed with the truePipe or the falsePipe. If all of the Predicates return true, then proceed down the truePipe, otherise proceed down the falsePipe.

ProfileFNamePredicate

Implements Predicate<HttpServletRequest>.

Injectable property:

  • targetProfileFName (a String)

(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 (a String): 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 Components 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

xkcd comic 974 : The General Problem

(Comic CC-BY-NC.)

(Cover photo of Alaska pipeline CC-BY-NC-ND ).