org.eclipse.persistence.sdo.helper.extension.XPathHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction f2b9fc5
/*
* Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.sdo.helper.extension;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.persistence.exceptions.ConversionException;
import org.eclipse.persistence.sdo.SDODataObject;
import org.eclipse.persistence.sdo.SDOProperty;
import org.eclipse.persistence.sdo.helper.SDOXMLHelper;
import org.eclipse.persistence.sdo.helper.XPathEngine;
import commonj.sdo.DataObject;
import commonj.sdo.Property;
/**
* This singleton provides support for querying DataObjects
* via xpath expression where the result is a List containing
* one or more simple types or DataObjects.
*/
public class XPathHelper {
private static XPathHelper instance;
static final int GTE = 4;
static final int LTE = 5;
static final int GT = 6;
static final int LT = 7;
static final int EQ = 8;
static final int NEQ = 9;
static final int AND = 10;
static final int OR = 11;
private static final String GTE_STR = ">=";
private static final String LTE_STR = "<=";
private static final String GT_STR = ">";
private static final String LT_STR = "<";
private static final String EQ_STR = "=";
private static final String NEQ_STR = "!=";
private static final String AND_STR = "and";
private static final String OR_STR = "or";
/**
* Returns the one and only instance of this singleton.
*/
public static XPathHelper getInstance() {
if (instance == null) {
instance = new XPathHelper();
}
return instance;
}
/**
* Create and return an XPathExpression, using the provided
* string to create the expression.
*
* @param expression
* @return
*/
public XPathExpression prepareExpression(String expression) {
return new XPathExpression(expression);
}
/**
* Evaluate an XPath expression in the specified context and return a List
* containing any types or DataObjects that match the search criteria.
*
* @param expression
* @param dataObject
* @return List containing zero or more entries
*/
public List evaluate(String expression, DataObject dataObject) {
List results = new ArrayList();
// call into XPathEngine until all functionality is implemented
if (shouldCallXPathEngine(expression)) {
return addResultsToList(XPathEngine.getInstance().get(expression, dataObject), results);
}
return evaluate(expression, dataObject, results);
}
/**
* Evaluate an XPath expression in the specified context and populate
* the provided List with any types or DataObjects that match the
* search criteria.
*
* @param expression
* @param dataObject
* @param results
* @return
*/
private List evaluate(String expression, DataObject dataObject, List results) {
Object result;
int index = expression.indexOf('/');
if (index > -1) {
if (index == (expression.length() - 1)) {
return addResultsToList(processFragment(expression.substring(0, index), dataObject), results);
} else {
result = processFragment(expression.substring(0, index), dataObject);
if (result instanceof DataObject) {
return evaluate(expression.substring(index + 1, expression.length()), (DataObject)result, results);
} else if (result instanceof List) {
// loop over each result, executing the remaining portion of the expression
for (Iterator resultIt = ((List) result).iterator(); resultIt.hasNext();) {
evaluate(expression.substring(index + 1, expression.length()), (DataObject)resultIt.next(), results);
}
return results;
} else {
return null;
}
}
}
return addResultsToList(processFragment(expression, dataObject), results);
}
/**
* Process an XPath expression fragment.
*
* @param xpFrag
* @param dataObject
* @return
*/
private Object processFragment(String xpFrag, DataObject dataObject) {
// handle self expression
if (xpFrag.equals(".")) {
return dataObject;
}
// handle containing DataObject expression
if (xpFrag.equals("..")) {
return dataObject.getContainer();
}
// ignore '@'
xpFrag = getPathWithAtRemoved(xpFrag);
// handle positional '[]' expression
int idx = xpFrag.indexOf('[');
if (idx > -1) {
return processBracket(xpFrag, dataObject, idx);
}
// handle non-positional expression
Property prop = dataObject.getInstanceProperty(xpFrag);
try {
return dataObject.get(prop);
} catch (IllegalArgumentException e) {
return null;
}
}
/**
* Process a positional or query XPath expression fragment. This method
* determines if the brackets represent a query or a positional path,
* and calls into the appropriate methods accordingly.
*
* @param xpFrag
* @param dataObject
* @param idx
* @return
*/
private Object processBracket(String xpFrag, DataObject dataObject, int idx) {
int closeIdx = xpFrag.indexOf(']');
if (closeIdx == -1 || closeIdx < idx) {
return null;
}
String contents = xpFrag.substring(idx + 1, closeIdx);
// check for integer index
if (contents.matches("[1-9][0-9]*")) {
return processIndex(xpFrag, dataObject, idx, Integer.parseInt(contents) - 1);
}
// check for float index
if (contents.matches("[1-9].[0-9]*")) {
Float num = Float.valueOf(contents);
return processIndex(xpFrag, dataObject, idx, num.intValue() - 1);
}
// check for simple/complex queries
String reference = xpFrag.substring(0, idx);
if (contents.indexOf(AND_STR) != -1 || contents.indexOf(OR_STR) != -1) {
return processComplexQuery(dataObject, reference, contents);
}
return processSimpleQuery(dataObject, reference, contents);
}
/**
* Process a positional XPath expression fragment.
*
* @param xpFrag
* @param dataObject
* @param idx
* @param idxValue
* @return
*/
private Object processIndex(String xpFrag, DataObject dataObject, int idx, int idxValue) {
try {
Property prop = dataObject.getInstanceProperty(xpFrag.substring(0, idx));
List dataObjects = dataObject.getList(prop);
if (idxValue < dataObjects.size()) {
return dataObjects.get(idxValue);
} else {
// out of bounds
throw new IndexOutOfBoundsException();
}
} catch (IllegalArgumentException e) {
return null;
}
}
/**
* Evaluate the query represented by the XPath Expression fragment against
* the DataObject. A complex query contains logical operators.
*
* @param dataObject
* @param reference
* @param bracketContents
* @return
*/
private Object processComplexQuery(DataObject dataObject, String reference, String bracketContents) {
// convert the expression to postfix notation
OPStack opStack = new OPStack();
List expressionParts = opStack.processExpression(bracketContents);
ArrayList queryParts = new ArrayList();
// first iteration, create QueryParts from the expression, keeping the
// position of any 'and' / 'or'
for (int i=0; i= 2) {
Boolean b1 = (Boolean) booleanValues.get(k-1);
Boolean b2 = (Boolean) booleanValues.get(k-2);
int logicalOp = getOperandFromString((String) booleanValues.get(k));
booleanValues.remove(k);
booleanValues.remove(k-1);
booleanValues.set(k-2, evaluate(b1, b2, logicalOp));
k=0;
}
}
}
// if there isn't a single result something went wrong...
if (booleanValues.size() == 1) {
if ((Boolean)booleanValues.get(0)) {
valuesToReturn.add(cur);
}
}
}
return valuesToReturn;
}
private boolean evaluate(boolean b1, boolean b2, int op) {
switch (op) {
case AND:
return b1 && b2;
case OR:
return b1 || b2;
}
return false;
}
/**
* Evaluate the query represented by the XPath Expression fragment against
* the DataObject. A 'simple' query has not logical operators.
*
* @param dataObject
* @param reference
* @param query
* @return
*/
private Object processSimpleQuery(DataObject dataObject, String reference, String query) {
Property prop = dataObject.getInstanceProperty(reference);
List objects;
if (prop.isMany()) {
objects = dataObject.getList(prop);
} else {
objects = new ArrayList();
DataObject obj = dataObject.getDataObject(prop);
if (obj != null) {
objects.add(obj);
}
}
List valuesToReturn = new ArrayList();
QueryPart queryPart = new QueryPart(query);
Iterator iterObjects = objects.iterator();
while (iterObjects.hasNext()) {
SDODataObject cur = (SDODataObject)iterObjects.next();
if (queryPart.evaluate(cur)) {
valuesToReturn.add(cur);
}
}
return valuesToReturn;
}
// ----------------------------- Convenience Methods ----------------------------- //
/**
* Convenience method that will add the provided object to the 'results' list
* if the object is non-null. If the object represents a list, each non-null
* entry will be added to the results list.
*
* @param obj
* @param results
* @return
*/
protected List addResultsToList(Object obj, List results) {
if (obj != null) {
if (obj instanceof List) {
for (Iterator resultIt = ((List) obj).iterator(); resultIt.hasNext();) {
Object nextResult = resultIt.next();
if (nextResult != null) {
results.add(nextResult);
}
}
} else {
results.add(obj);
}
}
return results;
}
/**
* Convenience method that strips off '@' portion, if
* one exists.
*
* @param expression
* @return
*/
protected String getPathWithAtRemoved(String expression) {
int index = expression.indexOf('@');
if (index > -1) {
if (index > 0) {
StringBuffer sbuf = new StringBuffer(expression.substring(0, index));
sbuf.append(expression.substring(index + 1, expression.length()));
return sbuf.toString();
}
return expression.substring(index + 1, expression.length());
}
return expression;
}
/**
* Convenience method that strips off 'ns0:' portion, if
* one exists.
*
* @param expression
* @return
*/
protected String getPathWithPrefixRemoved(String expression) {
int index = expression.indexOf(':');
if (index > -1) {
return expression.substring(index + 1, expression.length());
}
return expression;
}
private int getOperandFromString(String op) {
if (op.equals(EQ_STR)) {
return EQ;
}
if (op.equals(NEQ_STR)) {
return NEQ;
}
if (op.equals(GT_STR)) {
return GT;
}
if (op.equals(LT_STR)) {
return LT;
}
if (op.equals(GTE_STR)) {
return GTE;
}
if (op.equals(LTE_STR)) {
return LTE;
}
if (op.equals(AND_STR)) {
return AND;
}
if (op.equals(OR_STR)) {
return OR;
}
return -1;
}
private String getStringFromOperand(int op) {
switch(op) {
case EQ:
return EQ_STR;
case NEQ:
return NEQ_STR;
case GTE:
return GTE_STR;
case LTE:
return LTE_STR;
case GT:
return GT_STR;
case LT:
return LT_STR;
case AND:
return AND_STR;
case OR:
return OR_STR;
}
return "";
}
/**
* Convenience method for determining if XPathEngine should be
* called, i.e. the XPath expression contains functionality
* not yet supported.
*
* @param expression
* @return
*/
protected boolean shouldCallXPathEngine(String expression) {
return false;
}
// ----------------------------- Inner Classes ----------------------------- //
/**
* A QueryPart knows the name of the property to be queried against on a
* given DataObject, as well as the value to be used in the comparison.
*/
public class QueryPart {
String propertyName, queryValue;
int relOperand;
int logOperand;
/**
* This constructor breaks the provided query into
* property name and query value parts.
*
* @param query
*/
public QueryPart(String query) {
processQueryContents(query);
}
/**
* This constructor sets a logical operator and breaks
* the provided query into property name and query
* value parts.
*/
public QueryPart(String property, String value, int op) {
relOperand = op;
propertyName = property;
queryValue = formatValue(value);
}
/**
* Breaks the provided query into property name and
* query value parts
*/
private void processQueryContents(String query) {
int idx = -1, operandLen = 1;
relOperand = 1;
if ((idx = query.indexOf(GTE_STR)) != -1) {
relOperand = GTE;
operandLen = 2;
} else if ((idx = query.indexOf(LTE_STR)) != -1) {
relOperand = LTE;
operandLen = 2;
} else if ((idx = query.indexOf(NEQ_STR)) != -1) {
relOperand = NEQ;
operandLen = 2;
} else if ((idx = query.indexOf(GT_STR)) != -1) {
relOperand = GT;
} else if ((idx = query.indexOf(LT_STR)) != -1) {
relOperand = LT;
} else if ((idx = query.indexOf(EQ_STR)) != -1) {
relOperand = EQ;
}
propertyName = query.substring(0, idx).trim();
queryValue = formatValue(query.substring(idx + operandLen));
}
private String formatValue(String value) {
int openQIdx = value.indexOf('\'');
int closeQIdx = value.lastIndexOf('\'');
if (openQIdx == -1 && closeQIdx == -1) {
openQIdx = value.indexOf('\"');
closeQIdx = value.lastIndexOf('\"');
}
if (openQIdx != -1 && closeQIdx != -1 && openQIdx < closeQIdx) {
value = value.substring(openQIdx + 1, closeQIdx);
} else {
// if the value is not enclosed on quotes, trim off any whitespace
value = value.trim();
}
return value;
}
/**
* Indicate if the query represented by this QueryPart evaluates to
* true or false when executed on a given DataObject.
*
* @param dao
* @return
*/
public boolean evaluate(SDODataObject dao) {
Object queryVal = queryValue;
Object actualVal = null;
SDOProperty prop = dao.getInstanceProperty(propertyName);
try {
SDOXMLHelper sdoXMLHelper = (SDOXMLHelper) dao.getType().getHelperContext().getXMLHelper();
queryVal = sdoXMLHelper.getXmlConversionManager().convertObject(queryValue, prop.getType().getInstanceClass());
} catch (ConversionException e) {
// do nothing
}
List values;
if (!prop.isMany()) {
values = new ArrayList();
values.add(dao.get(prop));
} else {
values = dao.getList(prop);
}
Iterator iterValues = values.iterator();
while (iterValues.hasNext()) {
actualVal = iterValues.next();
if (actualVal == null) {
continue;
}
int resultOfComparison;
try {
resultOfComparison = ((Comparable)actualVal).compareTo(queryVal) ;
} catch (Exception x) {
continue;
}
// check the result against the logical operand
switch (relOperand) {
case EQ:
if (resultOfComparison == 0) {
return true;
}
break;
case NEQ:
if (resultOfComparison != 0) {
return true;
}
break;
case GTE:
if (resultOfComparison >= 0) {
return true;
}
break;
case LTE:
if (resultOfComparison <= 0) {
return true;
}
break;
case GT:
if (resultOfComparison > 0) {
return true;
}
break;
case LT:
if (resultOfComparison < 0) {
return true;
}
break;
}
}
return false;
}
@Override
public String toString() {
return "QueryPart {" + propertyName + " " + getStringFromOperand(relOperand) + " " + queryValue + "}";
}
}
}