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.