Combining DLM audience conditions for Mobile customizations
I found a couple of entries on the uPortal Mailing list that suggested to me that several people have similar requirements to myself so this is why I am making this available here.
I am using the list based mobile view and would like to have different list folders on the mobile view than I have tabs on the desktop view. Therefore I think I need to be able to combine DLM Evaluators.
For example I want my desktop guest users to see something different to my mobile guest users. So my mobile guest DLM fragment might look something like this:
<?xml version="1.0" encoding="UTF-8"?> <fragment-definition xmlns:dlm="http://org.jasig.portal.layout.dlm.config" script="classpath://org/jasig/portal/io/import-fragment-definition_v3-1.crn"> <dlm:fragment name="Mobile Guests" ownerID="mobile-guest-lo" precedence="80"> <dlm:audience evaluatorFactory="edu.bristol.MustMatchAllAudiencesEvaluatorFactory"> <audience evaluatorFactory="org.jasig.portal.layout.dlm.providers.GuestUserEvaluatorFactory"/> <audience evaluatorFactory="org.jasig.portal.layout.dlm.providers.ProfileEvaluatorFactory"> <paren mode="AND"> <profile value="mobileDefault"/> </paren> </audience> </dlm:audience> </dlm:fragment> </fragment-definition>
Â
A non-mobile guest user fragment would look almost identical to the above XML except that the mode would be "NOT" rather than "AND" for the profile value.
I have therefore created a DLM Evaluator that can combine DLM audience conditions (See attached classes - apologies for the clumsy name). This code works by re-using the FragmentDefinition methods but defines a user as belonging to an audience only if all of the nested audience evaluations are met.
I had to tweak FragmentDefinition.java to make constructor and loadAudienceEvaluators() method protected rather than private. So that my EvaluatorFactory would simply consist of:
public class MustMatchAllAudiencesEvaluatorFactory extends FragmentDefinition implements EvaluatorFactory { public Evaluator getEvaluator(Node node) { Element e = (Element) node; loadAudienceEvaluators(e.getElementsByTagName("audience")); return new MustMatchAllAudiencesEvaluator(evaluators); } }
after which point the Evaluator's core method just needs to conduct the evaluations in order:
public MustMatchAllAudiencesEvaluator(List<Evaluator> evaluators) { this.evaluators = evaluators; } @Override public boolean isApplicable(IPerson ip) { int matches = 0; for (Evaluator e : evaluators) { if (e.isApplicable(ip)) { matches++; } else { break; } } return matches == evaluators.size(); }
I then added my Evaluator into ehcache.xml and hibernate.cfg.xml in order to make the new Evaluator cacheable and persistent. Seems to work.