Java tutorial
/** ** This file is part of Klistret. Klistret is free software: you can ** redistribute it and/or modify it under the terms of the GNU General ** Public License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** Klistret is distributed in the hope that it will be useful, but ** WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** General Public License for more details. You should have received a ** copy of the GNU General Public License along with Klistret. If not, ** see <http://www.gnu.org/licenses/> */ package com.klistret.cmdb.utility.hibernate; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.hibernate.Criteria; import org.hibernate.Session; import org.hibernate.criterion.Criterion; import org.hibernate.criterion.MatchMode; import org.hibernate.criterion.Projection; import org.hibernate.criterion.Projections; import org.hibernate.criterion.Restrictions; import org.hibernate.metadata.ClassMetadata; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.klistret.cmdb.exception.ApplicationException; import com.klistret.cmdb.exception.InfrastructureException; import com.klistret.cmdb.utility.jaxb.CIBean; import com.klistret.cmdb.utility.jaxb.CIContext; import com.klistret.cmdb.utility.jaxb.CIProperty; import com.klistret.cmdb.utility.saxon.AndExpr; import com.klistret.cmdb.utility.saxon.BaseExpression; import com.klistret.cmdb.utility.saxon.ComparisonExpr; import com.klistret.cmdb.utility.saxon.Expr; import com.klistret.cmdb.utility.saxon.FunctionCall; import com.klistret.cmdb.utility.saxon.LiteralExpr; import com.klistret.cmdb.utility.saxon.OrExpr; import com.klistret.cmdb.utility.saxon.PathExpression; import com.klistret.cmdb.utility.saxon.RelativePathExpr; import com.klistret.cmdb.utility.saxon.Step; import com.klistret.cmdb.utility.saxon.StepExpr; /** * XPath criteria takes one or more XPath filters (expressions) and builds a * Hibernate criteria. The basic logic is simple (the code however is a bit * messy). Each XPath expression is mapped to what is called a Hibernate * relative path that either ends in a normal Hibernate property or an XML * column (whereby the rest of the expression is truncated but latent in the * underlying step). The Hibernate relative path despite the overhead makes it * easier to create Hibernate criteria aliases every time the forward direction * in the XPath crosses over a Hibernate entity or association. * * Every filter is evaluated then translated (translate method) into a Hibernate * relative path (process method). Processing expects relative path expressions * only. Paths may be absolute or relative and are processed in relation to * their context (which allows for handling paths within predicates). Afterwards * the Hibernate relative path is built into a Hibernate criteria using aliases * against a single criteria instance (thus the need for the alias cache to * prevent duplicates). * * @author Matthew Young * */ public class XPathCriteria { private static final Logger logger = LoggerFactory.getLogger(XPathCriteria.class); /** * Hibernate session */ private Session session; /** * CI metadata */ private CIContext ciContext = CIContext.getCIContext(); /** * Hibernate Criteria */ private Criteria criteria; /** * Wrapped relative paths around the passed expressions */ private List<HibernateRelativePath> hibernateRelativePaths = new ArrayList<HibernateRelativePath>(); /** * Hibernate property type */ private enum Type { Entity, Association, Property }; /** * Alias mask */ private String aliasMask = "a0"; /** * Alias cache (HibernateStep path is the key, alias is the value) */ private Map<String, String> aliasCache = new HashMap<String, String>(); /** * Hibernate wrapper (inner class) around relative paths (even single steps * within a predicate are wrapped as relative paths). */ @SuppressWarnings("unused") private class HibernateRelativePath { /** * List Hibernate steps */ private List<HibernateStep> hSteps = new ArrayList<HibernateStep>(); /** * Absolute paths are candidates for root Hibernate criteria */ private boolean absolute = false; /** * Root entity name */ private String name; /** * Hibernate relative path may be shorter in depth if they end in a * property that is an XML column */ private boolean truncated = false; /** * Every relative path must have a context */ private HibernateStep context; /** * Get Hibernate steps * * @return List */ public List<HibernateStep> getHiberateSteps() { return this.hSteps; } /** * Does the relative path contain an Hibernate property for an XML * column * * @return boolean */ public boolean hasXML() { for (HibernateStep hStep : hSteps) if (hStep.isXml()) return true; return false; } /** * Is the path absolute * * @return boolean */ public boolean isAbsolute() { return this.absolute; } /** * Set absolute * * @param value */ public void setAbsolute(boolean value) { this.absolute = value; } /** * Has the path been truncated (are there remaining steps beyond the XML * property) * * @return boolean */ public boolean isTruncated() { return this.truncated; } /** * Set truncated * * @param value */ public void setTruncated(boolean value) { this.truncated = value; } /** * Get last HibernateStep, null if the internal array is empty * * @return */ public HibernateStep getLastHibernateStep() { if (hSteps.size() > 0) return hSteps.get(hSteps.size() - 1); return null; } /** * Get first HibernateStep, null if the internal array is empty * * @return */ public HibernateStep getFirstHibernateStep() { if (hSteps.size() > 0) return hSteps.get(0); return null; } /** * Get name of the root entity * * @return */ public String getName() { return this.name; } /** * Set name of the root entity * * @param name */ public void setName(String name) { this.name = name; } /** * Get context * * @return */ public HibernateStep getContext() { return this.context; } /** * Set context * * @param context */ public void setContext(HibernateStep context) { this.context = context; } /** * Display information */ public String toString() { if (hSteps.size() > 0) { String p = null; for (HibernateStep s : hSteps) p = p == null ? s.getName() : String.format("%s.%s", p, s.getName()); return String.format("context [%s], path [%s], absolute [%s], truncated [%s]", context, p, absolute, truncated); } return "empty"; } } /** * Hibernate wrapper (inner class) around steps * */ @SuppressWarnings("unused") private class HibernateStep { private Step step; private CIBean ciBean; private HibernateStep next; private HibernateStep previous; private String path; private String name; private Type type = Type.Property; private boolean xml = false; private BaseExpression baseExpression; public Step getStep() { return this.step; } public void setStep(Step step) { this.step = step; } public CIBean getCIBean() { return this.ciBean; } public void setCIBean(CIBean ciBean) { this.ciBean = ciBean; } public HibernateStep getNext() { return this.next; } public void setNext(HibernateStep next) { this.next = next; } public HibernateStep getPrevious() { return this.previous; } public void setPrevious(HibernateStep previous) { this.previous = previous; } public boolean hasNext() { if (this.next == null) return false; return true; } public String getPath() { return this.path; } public void setPath(String path) { this.path = path; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public Type getType() { return this.type; } public void setType(Type type) { this.type = type; } public void setXml(boolean value) { this.xml = value; } public boolean isXml() { return this.xml; } public BaseExpression getBaseExpression() { return this.baseExpression; } public void setBaseExpression(BaseExpression baseExpression) { this.baseExpression = baseExpression; } /** * Equality is traditional and keyed of the name property */ public boolean equals(Object aOther) { // self comparison if (this == aOther) return true; // non HibernateStep instance if (!(aOther instanceof HibernateStep)) return false; // safe casting HibernateStep other = (HibernateStep) aOther; // Name property must be defined if (other.getPath() == null) return false; // Matches only on name if (other.getPath().equals(this.getPath())) return true; return false; } /** * Display information */ public String toString() { return String.format("path: %s, qname: %s, bean class: %s", path == null ? "unknown" : path, step == null ? "unknown" : step.getQName(), ciBean == null ? "unknown" : ciBean.getJavaClass()); } } /** * Constructor * * @param expression * @param session */ public XPathCriteria(String expression, Session session) { this.session = session; merge(expression); } /** * Constructor * * @param expressions * @param session */ public XPathCriteria(List<String> expressions, Session session) { this.session = session; for (String expression : expressions) merge(expression); } /** * Get Hibernate session * * @return */ public Session getSession() { return this.session; } /** * Merge an expression filter after translating the expression first into * PathExpression and then a Hibernate relative path that can be processed. * * @param expression */ public void merge(String expression) { logger.debug("Merging filter expression [{}]", expression); PathExpression pe = new PathExpression(expression); RelativePathExpr rpe = pe.getRelativePath(); HibernateRelativePath hrp = translate(rpe); if (hibernateRelativePaths.size() > 0 && !(hibernateRelativePaths.get(0).getName().equals(hrp.getName()))) throw new ApplicationException(String.format( "Expression [%s] has another starting Hibernate entity than %s", pe.getXPath(), hrp.getName())); hibernateRelativePaths.add(hrp); } /** * Creates a Hibernate projection from an aggregate expression * * @param expression */ public Projection aggregate(String expression) { logger.debug("Creating projection based on aggregate expression [{}]", expression); FunctionCall fc = new FunctionCall(expression); RelativePathExpr rpe = fc.getRelativePath(); HibernateRelativePath hrp = translate(rpe); /** * Confirm the last Hibernate step is a Hibernate property */ HibernateStep last = hrp.getLastHibernateStep(); if (last.getType() != Type.Property) throw new ApplicationException("Aggregation must act either on a Hibernate property or an XML column"); /** * Property name with alias */ String alias = aliasCache.get(last.getPrevious().getPath()); String propertyName = alias == null ? last.getName() : String.format("%s.%s", alias, last.getName()); /** * Only sum, avg, max, and min supported */ switch (fc.getFunction()) { case sum: return last.isXml() ? new XPathAggregation("sum", propertyName, last.getStep()) : Projections.sum(propertyName); case avg: return last.isXml() ? new XPathAggregation("avg", propertyName, last.getStep()) : Projections.avg(propertyName); case max: return last.isXml() ? new XPathAggregation("max", propertyName, last.getStep()) : Projections.max(propertyName); case min: return last.isXml() ? new XPathAggregation("min", propertyName, last.getStep()) : Projections.min(propertyName); default: throw new InfrastructureException(String.format("Function call [%s] not handled.", fc.getFunction())); } } /** * Translates an absolute relative path either from a path or aggregate * expression * * @param rpe * @return */ private HibernateRelativePath translate(RelativePathExpr rpe) { BaseExpression be = rpe.getBaseExpression(); /** * Every expression has to be absolute (ie. starts with a slash) */ if (!rpe.hasRoot()) throw new ApplicationException(String.format("Expression [%s] has no root", be.getXPath())); /** * The Root expression must be the first Step in the expression */ if (rpe.getExpr(0).getType() != Expr.Type.Root) throw new ApplicationException(String .format("Expression [%s] expects an absolute path (root is not first step)", be.getXPath())); /** * Multiple Root expression may not occur */ if (rpe.hasMultipleRoot()) throw new ApplicationException( String.format("Expression [%s] contains multiple root steps", be.getXPath())); /** * More than the initial steps ("/" or "//") must exist to define a * Hibernate criteria (based on either a class or entity name) plus the * first Step has to be a Step not for example an Irresolute. */ if (!(rpe.getDepth() > 1) && rpe.getExpr(1).getType() == Expr.Type.Step) throw new ApplicationException(String .format("Expression [%s] has no context step (depth greater than signular)", be.getXPath()), new UnsupportedOperationException()); /** * Context step must be an element with valid QName and metadata */ if (rpe.getExpr(1).getType() != Expr.Type.Step) throw new ApplicationException(String.format( "Expression [%s] context step is not Step (rather Irresolute or other)", be.getXPath())); StepExpr step = (StepExpr) rpe.getExpr(1); if (step.getPrimaryNodeKind() != StepExpr.PrimaryNodeKind.Element || step.getQName() == null) throw new ApplicationException(String .format("Expression [%s] context step not an XML Element with a valid QName", be.getXPath())); /** * Is context step a CI Bean? */ CIBean bean = ciContext.getBean(step.getQName()); if (bean == null) throw new ApplicationException( String.format("Expression [%s] context step not a registered CI bean", be.getXPath())); /** * Context step has to have Hibernate metadata */ ClassMetadata cm = session.getSessionFactory().getClassMetadata(bean.getJavaClass()); if (cm == null) throw new ApplicationException(String.format("Context bean [%s] has no Hibernate metadata", bean)); /** * Context Hibernate Step */ HibernateStep hStep = new HibernateStep(); hStep.setStep(step); hStep.setType(Type.Entity); hStep.setCIBean(bean); hStep.setName(step.getQName().getLocalPart()); hStep.setPath(step.getQName().getLocalPart()); hStep.setBaseExpression(be); HibernateRelativePath hrp = process(rpe, hStep); hrp.setName(cm.getEntityName()); return hrp; } /** * Translate a relative path expression into a Hibernate relative path * * @param rpe * @param hStep */ protected HibernateRelativePath process(RelativePathExpr rpe, HibernateStep context) { logger.debug("Processing relative path [{}]", rpe.getXPath()); HibernateRelativePath hRPath = new HibernateRelativePath(); hRPath.setContext(context); /** * Offset to the first step after the context step if the relative path * is an absolute. Assumption is that absolute paths are grounds for the * start of a Hibernate criteria. */ int contextDepth = 0; if (rpe.getFirstExpr().getType() == Expr.Type.Root) { logger.debug("Offsetting processing by 2 adding the passed context [{}] as the first Hibernate step", context); contextDepth = 2; hRPath.setAbsolute(true); hRPath.getHiberateSteps().add(context); } /** * Process each step. Non absolute relative paths take their context * from the passed Hibernate step (likely predicate expressions). */ for (int depth = contextDepth; depth < rpe.getDepth(); depth++) { Step step = (Step) rpe.getExpr(depth); switch (step.getType()) { case Step: if (hRPath.getHiberateSteps().size() > 0) context = hRPath.getLastHibernateStep(); CIBean bean = context.getCIBean(); ClassMetadata cm = session.getSessionFactory().getClassMetadata(bean.getJavaClass()); String property = step.getQName().getLocalPart(); org.hibernate.type.Type propertyType = getPropertyType(cm, property); if (propertyType == null) throw new ApplicationException( String.format("Step [%s] not defined as a property to the Hibernate context [%s]", step, cm.getEntityName())); HibernateStep hStep = new HibernateStep(); hStep.setStep(step); hStep.setName(property); hStep.setPath(String.format("%s.%s", context.getPath(), property)); /** * Type */ if (propertyType.isEntityType()) hStep.setType(Type.Entity); if (propertyType.isAssociationType()) hStep.setType(Type.Association); /** * Store the CIBean corresponding to the property by type not * name. Store as well the alias. */ if (hStep.getType() != Type.Property) { CIProperty prop = bean.getPropertyByName(step.getQName()); /** * Does the property have corresponding CI Bean? */ CIBean other = ciContext.getBean(prop.getType()); if (other == null) throw new ApplicationException( String.format("Step [%s] has no corresponding CI Bean metadata", step)); hStep.setCIBean(other); } if (propertyType.getName().equals(JAXBUserType.class.getName())) { hStep.setXml(true); if (step.getNext() != null) hRPath.setTruncated(true); } /** * Connect together the Hibernate steps */ if (hRPath.getHiberateSteps().size() > 0) { HibernateStep last = hRPath.getLastHibernateStep(); last.setNext(hStep); hStep.setPrevious(last); } /** * Make sure the PathExpression is stored */ hStep.setBaseExpression(context.getBaseExpression()); /** * Add the Hibernate step */ hRPath.getHiberateSteps().add(hStep); break; case Root: throw new ApplicationException(String .format("Only resolute steps valid [Root encountered at depth: %d]", step.getDepth())); case Irresolute: throw new ApplicationException( String.format("Only resolute steps valid [Irresolute encountered at depth: %d]", depth)); default: throw new ApplicationException(String .format(String.format("Undefined expression type [%s] at depth", step.getType(), depth))); } if (hRPath.getLastHibernateStep().getType() == Type.Property) break; } logger.debug("Hibernate relative path: {}", hRPath); return hRPath; } /** * Get Hibernate property type by looking for the property name in the class * metadata for general properties and the identifier property * * @param cm * @param name * @return */ private org.hibernate.type.Type getPropertyType(ClassMetadata cm, String name) { for (String pn : cm.getPropertyNames()) if (pn.equals(name)) return cm.getPropertyType(name); if (cm.getIdentifierPropertyName() != null && cm.getIdentifierPropertyName().equals(name)) return cm.getIdentifierType(); return null; } /** * Get Hibernate criteria based on the Hibernate relative paths representing * the XPath filter expressions. * * @return Criteria */ public Criteria getCriteria() { /** * At least one expression must have be processed and all should have * the same first Hibernate Step */ if (hibernateRelativePaths.size() == 0) throw new InfrastructureException("Somehow the Hibernate relative paths have been drained."); HibernateStep context = hibernateRelativePaths.get(0).getFirstHibernateStep(); /** * Singular root Hibernate criteria based on the root context step * common across all XPath expressions. */ criteria = session.createCriteria(ciContext.getBean(context.getStep().getQName()).getJavaClass()); /** * Build up the criteria from each Hibernate relative path */ for (HibernateRelativePath hRPath : hibernateRelativePaths) { /** * Check that last step has predicates if the Hibernate relative * path isn't truncated (which happens with XML properties only). */ HibernateStep last = hRPath.getLastHibernateStep(); Step step = last.getStep(); if (!hRPath.isTruncated() && !((StepExpr) last.getStep()).hasPredicates()) throw new ApplicationException( "Last Hibernate step does not end in a normal property with predicates"); build(hRPath); /** * XML properties result in a restriction */ if (last.isXml() && last.getPrevious() != null) { String alias = aliasCache.get(last.getPrevious().getPath()); String propertyName = alias == null ? last.getName() : String.format("%s.%s", alias, last.getName()); criteria.add(new XPathRestriction(propertyName, step)); } } return criteria; } /** * Build up the criteria off the Hibernate relative path handling only * entities or associations to entities. * * @param hRPath */ protected void build(HibernateRelativePath hRPath) { /** * Propagate down the Hibernate relative path creating sub criteria if * need and creating restrictions off the predicates unless the step is * a property. */ for (int depth = 0; depth < hRPath.getHiberateSteps().size(); depth++) { HibernateStep hStep = hRPath.getHiberateSteps().get(depth); /** * Only entity or association properties (to other entities) can * have predicates */ if (hStep.getType() != Type.Property) { /** * Aliases (right now) have a one-to-one mapping to paths. If * the path as no associated alias then a sub criteria is * created and the alias is stored. */ HibernateStep context = hStep.getPrevious() == null ? hRPath.getContext() : hStep.getPrevious(); if (context == null) throw new InfrastructureException(String.format("Hibernate Step [%s] has no context", hStep)); if (hStep != context && !aliasCache.containsKey(hStep.getPath())) { String contextAlias = aliasCache.get(context.getPath()); String associationPath = contextAlias == null ? hStep.getName() : String.format("%s.%s", contextAlias, hStep.getName()); criteria.createAlias(associationPath, aliasMask); logger.debug("Adding key: {} and value: {} to the alias cache", hStep.getPath(), aliasMask); aliasCache.put(hStep.getPath(), aliasMask); aliasMask = incrementMask(aliasMask); } /** * Add restrictions for each predicate */ for (Expr predicate : ((StepExpr) hStep.getStep()).getPredicates()) criteria.add(getRestriction(predicate, hStep)); } } } /** * * @param predicate * @return */ protected Criterion getRestriction(Expr predicate, HibernateStep context) { logger.debug("Adding restrictions by predicate to context [{}]", context); switch (predicate.getType()) { case Or: /** * Recursively breaks down the OrExpr by translating on the first * and second operands */ if (((OrExpr) predicate).getOperands().size() != 2) throw new ApplicationException( String.format("OrExpr expression [%s] expects 2 operands", predicate)); return Restrictions.or(getRestriction(((OrExpr) predicate).getOperands().get(0), context), getRestriction(((OrExpr) predicate).getOperands().get(1), context)); case And: /** * Recursively breaks down the AndExpr by translating on the first * and second operands */ if (((AndExpr) predicate).getOperands().size() != 2) throw new ApplicationException( String.format("AndExpr expression [%s] expects 2 operands", predicate)); return Restrictions.and(getRestriction(((AndExpr) predicate).getOperands().get(0), context), getRestriction(((AndExpr) predicate).getOperands().get(1), context)); case Comparison: /** * Find the literal */ LiteralExpr literal = getLiteralOperand(((ComparisonExpr) predicate).getOperands()); /** * Find the relative path making a Hibernate relative path */ RelativePathExpr rpe = getRelativePathOperand(((ComparisonExpr) predicate).getOperands()); HibernateRelativePath hRPath = process(rpe, context); build(hRPath); HibernateStep last = hRPath.getLastHibernateStep(); /** * Property name with alias prefix */ String alias = last.getPrevious() == null ? aliasCache.get(context.getPath()) : aliasCache.get(last.getPrevious().getPath()); String propertyName = alias == null ? last.getName() : String.format("%s.%s", alias, last.getName()); /** * Paths with XML properties (always the last step if present) * return a XPath restriction. */ if (hRPath.hasXML()) { if (!hRPath.isTruncated()) throw new ApplicationException(String.format( "Predicate relative path ending in an XML property [%s] must be truncated", last)); /** * Last Hibernate step of the Hibernate path marks the property * which the restriction acts on. */ StepExpr step = (StepExpr) last.getStep(); /** * A new XPath is created from the last step downwards till the * step prior to the ending step. */ String xpath = null; for (int depth = step.getDepth(); depth < step.getRelativePath().getDepth() - 1; depth++) { Expr expr = step.getRelativePath().getExpr(depth); xpath = xpath == null ? expr.getXPath() : String.format("%s/%s", xpath, expr.getXPath()); } Step ending = (Step) step.getRelativePath().getLastExpr(); /** * A new comparison is generated */ List<Expr> operands = new ArrayList<Expr>(); operands.add(ending); operands.add(literal); xpath = String.format("%s[%s]", xpath, ComparisonExpr.getXPath(((ComparisonExpr) predicate).getOperator(), operands, false)); xpath = String.format("%s%s", context.getBaseExpression().getProlog(), xpath); PathExpression other = new PathExpression(xpath); return new XPathRestriction(propertyName, (Step) other.getRelativePath().getFirstExpr()); } if (((StepExpr) last.getStep()).hasPredicates()) throw new ApplicationException(String.format( "Comparisons against Hibernate properties may not have an underlying step [] with predicates", last.getStep())); logger.debug("Predicate is comparison [{}] against property [{}]", ((ComparisonExpr) predicate).getOperator().name(), propertyName); switch (((ComparisonExpr) predicate).getOperator()) { case ValueEquals: logger.debug("Value: {}", literal.getValue().getJavaValue()); return Restrictions.eq(propertyName, literal.getValue().getJavaValue()); case ValueGreaterThan: logger.debug("Value: {}", literal.getValue().getJavaValue()); return Restrictions.gt(propertyName, literal.getValue().getJavaValue()); case ValueGreaterThanOrEquals: logger.debug("Value: {}", literal.getValue().getJavaValue()); return Restrictions.ge(propertyName, literal.getValue().getJavaValue()); case ValueLessThan: logger.debug("Value: {}", literal.getValue().getJavaValue()); return Restrictions.lt(propertyName, literal.getValue().getJavaValue()); case ValueLessThanOrEquals: logger.debug("Value: {}", literal.getValue().getJavaValue()); return Restrictions.le(propertyName, literal.getValue().getJavaValue()); case ValueNotEquals: logger.debug("Value: {}", literal.getValue().getJavaValue()); return Restrictions.ne(propertyName, literal.getValue().getJavaValue()); case GeneralEquals: /** * If atomic (not a sequence) then the 'in' restriction is not * usable (defaults to 'eq' restriction) since the argument is * an array of objects. */ if (literal.isAtomic()) { logger.debug("Value: {}", literal.getValue().getJavaValue()); return Restrictions.eq(propertyName, literal.getValue().getJavaValue()); } logger.debug("Values: {}", literal.getValues()); Object[] javaValues = new Object[literal.getValues().length]; for (int index = 0; index < literal.getValues().length; index++) javaValues[index] = ((com.klistret.cmdb.utility.saxon.Value) literal.getValues()[index]) .getJavaValue(); return Restrictions.in(propertyName, javaValues); case Matches: if (((ComparisonExpr) predicate).getOperands().size() != 2) throw new ApplicationException( String.format("Matches function [%s] expects 2 operands", predicate)); logger.debug("Value: {}", literal.getValue().getText()); return Restrictions.ilike(propertyName, literal.getValue().getText(), MatchMode.EXACT); case Exists: if (((ComparisonExpr) predicate).getOperands().size() != 1) throw new ApplicationException( String.format("Exists function [%s] expects only 1 operand", predicate)); return Restrictions.isNotNull(propertyName); case Empty: if (((ComparisonExpr) predicate).getOperands().size() != 1) throw new ApplicationException( String.format("Empty function [%s] expects only 1 operand", predicate)); return Restrictions.isNull(propertyName); default: throw new ApplicationException( String.format("Unexpected comparison operator [%s] handling predicates", ((ComparisonExpr) predicate).getOperator())); } default: throw new ApplicationException(String.format("Unexpected expr [%s] type for predicate", predicate)); } } /** * Gets the first Relative Path operand in the list of expressions * * @param operands * @return */ private RelativePathExpr getRelativePathOperand(List<Expr> operands) { for (Expr operand : operands) if (operand.getType() == Expr.Type.RelativePath) return (RelativePathExpr) operand; return null; } /** * Gets the first Literal operand in the list of expressions * * @param operands * @return */ private LiteralExpr getLiteralOperand(List<Expr> operands) { for (Expr operand : operands) if (operand.getType() == Expr.Type.Literal) return (LiteralExpr) operand; return null; } /** * Increment mask * * @param mask * @return */ private String incrementMask(String mask) { char last = mask.charAt(mask.length() - 1); return mask.substring(0, mask.length() - 1) + ++last; } }