/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the License at the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package org.apereo.portal.layout.dlm; import java.util.LinkedList; import java.util.List; import javax.persistence.Cacheable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Transient; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apereo.portal.layout.dlm.providers.EvaluatorGroup; import org.apereo.portal.security.IPerson; import org.apereo.portal.xml.XmlUtilitiesImpl; import org.dom4j.DocumentHelper; import org.dom4j.Namespace; import org.dom4j.QName; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** @since 2.5 */ @Entity @Cacheable @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) public class FragmentDefinition extends EvaluatorGroup { /** * Still says 'jasig' becasue existing fragment-definition.xml files include it, and because (as * of yet) we still export them with that namespace. */ public static final String NAMESPACE_URI = "http://org.jasig.portal.layout.dlm.config"; public static final Namespace NAMESPACE = DocumentHelper.createNamespace("dlm", NAMESPACE_URI); /** User account whose layout should be copied to create a layout for new fragments. */ private static final String cDefaultLayoutOwnerId = "fragmentTemplate"; private static final Log LOG = LogFactory.getLog(FragmentDefinition.class); @Column(name = "FRAGMENT_NAME") private final String name; @Column(name = "OWNER_ID") private String ownerID = null; @Column(name = "PRECEDENCE") private double precedence = 0.0; // precedence of fragment @Column(name = "DESCRIPTION") private String description; /* These variables are bound to a uP userId later in the life cycle, not managed by hibernate */ @Transient private int index = 0; // index of definition within config file @Transient String defaultLayoutOwnerID = null; /** No-arg constructor required by JPA/Hibernate. */ @SuppressWarnings("unused") private FragmentDefinition() { this.name = null; } /* * For unit testing... */ protected FragmentDefinition(String name) { this.name = name; } /** * This constructor is passed a dlm:fragment element from which this FragmentDefinition instance * gathers its configuration information. * * @param e An Element representing a single <dlm:fragment> * @throws Exception */ public FragmentDefinition(Element e) { NamedNodeMap atts = e.getAttributes(); this.name = loadAttribute("name", atts, true, e); loadFromEelement(e); } public void loadFromEelement(Element e) { final boolean REQUIRED = true; final boolean NOT_REQUIRED = false; NamedNodeMap atts = e.getAttributes(); this.ownerID = loadAttribute("ownerID", atts, REQUIRED, e); this.defaultLayoutOwnerID = loadAttribute("defaultLayoutOwnerID", atts, NOT_REQUIRED, e); this.description = loadAttribute("description", atts, NOT_REQUIRED, e); String precedence = loadAttribute("precedence", atts, REQUIRED, e); try { this.precedence = Double.valueOf(precedence).doubleValue(); } catch (NumberFormatException nfe) { throw new RuntimeException( "Invalid format for precedence attribute " + "of <fragment> in\n'" + XmlUtilitiesImpl.toString(e), nfe); } // Audience Evaluators. // NB: We're about to re-parse the complete set of evaluators, // so we need to remove any that are already present. if (this.evaluators != null) { this.evaluators.clear(); } loadAudienceEvaluators(e.getElementsByTagName("dlm:audience")); } public String getName() { return name; } public String getOwnerId() { return this.ownerID; } public double getPrecedence() { return this.precedence; } public String getDescription() { return description; } public static String getDefaultLayoutOwnerId() { return cDefaultLayoutOwnerId; } public List<Evaluator> getEvaluators() { return this.evaluators; } public int getIndex() { return index; } public void setIndex(int index) { this.index = index; } public boolean isNoAudienceIncluded() { return evaluators == null || evaluators.size() == 0; } private void loadAudienceEvaluators(NodeList nodes) { final String evaluatorFactoryAtt = "evaluatorFactory"; for (int i = 0; i < nodes.getLength(); i++) { Node audience = nodes.item(i); NamedNodeMap atts = audience.getAttributes(); Node att = atts.getNamedItem(evaluatorFactoryAtt); if (att == null || att.getNodeValue().equals("")) throw new RuntimeException( "Required attibute '" + evaluatorFactoryAtt + "' " + "is missing or empty on 'audience' " + " element in\n'" + XmlUtilitiesImpl.toString(audience) + "'"); String className = att.getNodeValue(); /* * For version 5.0, all uPortal sources were repackaged from 'org.jasig.portal' * to 'org.apereo.portal'. *.fragment-definition.xml files exported from earlier * versions of uPortal will contain the old evaluatorFactory name. We can detect * that and intervene here. */ className = className.replace("org.jasig.portal", "org.apereo.portal"); EvaluatorFactory factory = loadEvaluatorFactory(className, audience); addEvaluator(factory, audience); } } private void addEvaluator(EvaluatorFactory factory, Node audience) { Evaluator evaluator = factory.getEvaluator(audience); if (evaluator == null) throw new RuntimeException( "Evaluator factory '" + factory.getClass().getName() + "' failed to " + "return an evaluator for 'audience' element" + " in\n'" + XmlUtilitiesImpl.toString(audience) + "'"); if (evaluators == null) { evaluators = new LinkedList<Evaluator>(); } evaluators.add(evaluator); } private EvaluatorFactory loadEvaluatorFactory(String factoryClassName, Node audience) { Class<?> theClass = null; try { theClass = Class.forName(factoryClassName); } catch (ClassNotFoundException cnfe) { throw new RuntimeException( "java.lang.ClassNotFoundException occurred" + " while loading evaluator factory class '" + factoryClassName + "' (or one of its " + "dependent classes) for 'audience' element " + "in\n'" + XmlUtilitiesImpl.toString(audience) + "'"); } catch (ExceptionInInitializerError eiie) { throw new RuntimeException( "java.lang.ExceptionInInitializerError " + "occurred while " + "loading evaluator factory Class '" + factoryClassName + "' (or one of its " + "dependent classes) for 'audience' element " + "in\n'" + XmlUtilitiesImpl.toString(audience) + "'. \nThis indicates that an exception " + "occurred during evaluation of a static" + " initializer or the initializer for a " + "static variable.", eiie); } catch (LinkageError le) { throw new RuntimeException( "java.lang.LinkageError occurred while " + "loading evaluator factory Class '" + factoryClassName + "' for " + "'audience' element in\n'" + XmlUtilitiesImpl.toString(audience) + "'. \nThis typically means that a " + "dependent class has changed " + "incompatibly after compiling the " + "factory class.", le); } Object theInstance = null; try { theInstance = theClass.newInstance(); } catch (IllegalAccessException iae) { throw new RuntimeException( "java.lang.IllegalAccessException occurred " + "while loading evaluator factory Class '" + factoryClassName + "' (or one of its " + "dependent classes) for 'audience' element " + "in\n'" + XmlUtilitiesImpl.toString(audience) + "' \nVerify that this is a public class " + "and that it contains a public, zero " + "argument constructor.", iae); } catch (InstantiationException ie) { throw new RuntimeException( "java.lang.InstantiationException occurred " + "while loading evaluator factory Class '" + factoryClassName + "' (or one of its " + "dependent classes) for 'audience' element " + "in\n'" + XmlUtilitiesImpl.toString(audience) + "' \nVerify that the specified class is a " + "class and not an interface or abstract " + "class.", ie); } try { return (EvaluatorFactory) theInstance; } catch (ClassCastException cce) { throw new RuntimeException( "java.lang.ClassCastException occurred " + "while loading evaluator factory Class '" + factoryClassName + "' (or one of its " + "dependent classes) for 'audience' element " + "in\n'" + XmlUtilitiesImpl.toString(audience) + "'. \nVerify that the class implements the " + "EvaluatorFactory interface.", cce); } } @Override public boolean isApplicable(IPerson p) { boolean isApplicable = false; if (LOG.isInfoEnabled()) LOG.info( ">>>> calling " + name + ".isApplicable( " + p.getAttribute("username") + " )"); try { if (evaluators == null) { LOG.debug("isApplicable()=false due to evaluators collection being null"); } else { for (Evaluator v : evaluators) { if (v.isApplicable(p)) { isApplicable = true; break; } } } } catch (Exception e) { throw new RuntimeException( "Failed to evaluate whether fragment '" + this.getName() + "' is applicable to user '" + p.getUserName() + "'", e); } if (LOG.isInfoEnabled()) { LOG.info( "---- " + name + ".isApplicable( " + p.getAttribute("username") + " )=" + isApplicable); } return isApplicable; } private String loadAttribute(String name, NamedNodeMap atts, boolean required, Element e) { Node att = atts.getNamedItem(name); if (required && (att == null || att.getNodeValue().equals(""))) throw new RuntimeException( "Missing or empty attribute '" + name + "' required by <fragment> in\n'" + XmlUtilitiesImpl.toString(e) + "'"); if (att == null) return null; return att.getNodeValue(); } @Override public void toElement(org.dom4j.Element parent) { // Assertions. if (parent == null) { String msg = "Argument 'parent' cannot be null."; throw new IllegalArgumentException(msg); } QName q = new QName("fragment", FragmentDefinition.NAMESPACE); org.dom4j.Element rslt = DocumentHelper.createElement(q); rslt.addAttribute("name", this.getName()); rslt.addAttribute("ownerID", this.getOwnerId()); rslt.addAttribute("precedence", Double.toString(this.getPrecedence())); rslt.addAttribute("description", this.getDescription()); // Serialize our children... for (Evaluator v : this.evaluators) { v.toElement(rslt); } parent.add(rslt); } @Override public Class<? extends EvaluatorFactory> getFactoryClass() { String msg = "This method is not necessary for serializing " + "FragmentDefinition instances and should " + "not be invoked."; throw new UnsupportedOperationException(msg); } @Override public String getSummary() { // This method is for audience evaluators... throw new UnsupportedOperationException(); } }