blob: 60b9da0998b9fa3056e17a848b3d41e2b4996551 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 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;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.eclipse.persistence.exceptions.ConversionException;
import org.eclipse.persistence.exceptions.SDOException;
import org.eclipse.persistence.internal.oxm.XMLConversionManager;
import org.eclipse.persistence.oxm.XMLConstants;
import org.eclipse.persistence.sdo.SDOConstants;
import org.eclipse.persistence.sdo.SDODataObject;
import commonj.sdo.DataObject;
import commonj.sdo.Property;
import commonj.sdo.Sequence;
import commonj.sdo.helper.DataHelper;
import commonj.sdo.impl.HelperProvider;
/**
* <p><b>Purpose</b>: Perform operations based on SDO XPath against DataObjects.
* </p>
*/
public class XPathEngine {
private static final int SET = 1;
private static final int ISSET = 2;
private static final int UNSET = 3;
protected static XPathEngine defaultXPathEngine;
private XPathEngine() {
}
public static XPathEngine getInstance() {
if (defaultXPathEngine == null) {
defaultXPathEngine = new XPathEngine();
}
return defaultXPathEngine;
}
/**Handle queries about setting status of a property through path base access.
*
* @param path the String representation of path based access
* @param caller the DataObject that pass path information in
* @return true if queried property is set, otherwise false
*/
public boolean isSet(String path, DataObject caller) {
try {
return processPath(path, null, caller, false, ISSET);
// return lastDataObject.isSet(lastProperty);// spec. did not mention exception
} catch (Exception e) {
return false;// spec. did not mention exception
}
}
/**Unset the value of a property through the path base access.
*
* @param path the String representation of path based access
* @param caller the DataObject that pass path information in
*/
public void unset(String path, DataObject caller) {
processPath(path, null, caller, false, UNSET);
}
/**Set a property's value through the path base access.
*
* @param path the String representation of path based access
* @param value the value to be set as the target property's value
* @param caller the DataObject that pass path information in
* @param convertValue boolean used for set if we should convert the value
*/
public void set(String path, Object value, DataObject caller, boolean convertValue) {
processPath(path, value, caller, convertValue, SET);
}
/**When accessing values corresponding to properties of DataObject by path base accessors,
* the accessed getters will pass informations to this method to process information and
* acquire wanted values.
*
* @param path the String representation of path based access
* @param caller the DataObject that pass path information in
* @return the value gotten by accessing through path
*/
public Object get(String path, DataObject caller) {// path like "a/b/c"
if ((path == null) || path.equals(SDOConstants.EMPTY_STRING)) {
return null;
}
if (path.equals("..")) {
return caller.getContainer();
}
if (path.equals(SDOConstants.SDO_XPATH_TO_ROOT)) {
return ((SDODataObject)caller).getRootObject();
}
path = getLocalName(path);
int index = path.indexOf('/');
if (index > -1) {
int openBracketIndex = path.indexOf('[');
int closeBracketIndex = path.indexOf(']');
if(index > openBracketIndex && index < closeBracketIndex) {
return getValueForFragment(path, caller);
}
if (index == (path.length() - 1)) {
return getValueForFragment(path.substring(0, index), caller);
} else {
Object value = getValueForFragment(path.substring(0, index), caller);
DataObject currentDataObject = (DataObject) value;
return get(path.substring(index + 1, path.length()), currentDataObject);
}
}
return getValueForFragment(path, caller);
}
/**extract wanted fragment from the string representation of path and pass processed result to
* method setIsSetUnSet for further operation.
*
* @param path the String representation of path based access
* @param value the value to be set as the target property's value
* @param caller the DataObject that pass path information in
* @param convertValue boolean used for set if we should convert the value
* @param _case an int value indicating what kind of operation to use: set, isset or unset.
* @return true if operation is isset and property's value is set, otherwise false.
*/
private boolean processPath(String path, Object value, DataObject caller, boolean convertValue, int _case) {
path = getLocalName(path);
int lastSlashIndex = path.lastIndexOf('/');
String frag;
if (-1 < lastSlashIndex) {// case 1: a/b/c
frag = path.substring(lastSlashIndex + 1);
return setIsSetUnSet(frag, path, caller, value, lastSlashIndex, convertValue, _case);
} else {// case 2 : "a"
frag = path;
return setIsSetUnSet(frag, path, caller, value, lastSlashIndex, convertValue, _case);
}
}
/**According to the requirement, correspondingly perform isset, unset or set function.
*
* @param frag one string fragment in the path
* @param path the String representation of path based access
* @param caller the DataObject that pass path information in
* @param value the value to be set as the target property's value
* @param lastSlashIndex the last index of '/' in the path string
* @param convertValue boolean used for set if we should convert the value
* @param _case an int value indicating what kind of operation to use: set, isset or unset.
* @return true if operation is isset and property's value is set, otherwise false.
*/
private boolean setIsSetUnSet(String frag, String path, DataObject caller, Object value, int lastSlashIndex, boolean convertValue, int _case) {
int indexOfDot = frag.lastIndexOf('.');
int indexOfOpenBracket = frag.lastIndexOf('[');
int indexOfCloseBracket = frag.lastIndexOf(']');
int numInLastProperty = getNumberInFrag(frag, indexOfDot, indexOfOpenBracket, indexOfCloseBracket);
String lastPropertyName = getPropertyNameInFrag(frag, numInLastProperty, indexOfDot, indexOfOpenBracket);// get last property name on path for case 1
DataObject lastDataObject;
if (-1 < lastSlashIndex) {
Object lastObject = get(path.substring(0, lastSlashIndex), caller);// get last dataobject on path
// If trying to access a list element from a null list, this object will be
// an instance of ListWrapper, not DataObject, but the error message is the same
// as if it was a null DataObject
if (lastObject == null || lastObject instanceof ListWrapper) {
throw SDOException.cannotPerformOperationOnProperty(lastPropertyName, path);
}
lastDataObject = (SDODataObject) lastObject;
} else {
lastDataObject = caller;
}
Property lastProperty = lastDataObject.getInstanceProperty(lastPropertyName);// get property of this dataobject
switch (_case) {
case SET:
if (lastProperty == null) {
lastProperty = ((SDODataObject)lastDataObject).defineOpenContentProperty(lastPropertyName, value);
}if(lastProperty != null){
set(lastProperty, lastDataObject, numInLastProperty, value,convertValue);
}
return false;
case ISSET:
if(lastProperty == null){
return false;
}
return isSet(lastProperty, lastDataObject);
case UNSET:
if(lastProperty == null){
return false;
}
unSet(lastProperty, lastDataObject, numInLastProperty);
return false;
default:
return false;
}
}
/** Method that returns whether a property is set.
*
* @param lastProperty the property to queries.
* @param lastDataObject the DataObject, owner of the queried property
* @return return true, if property's value is set, otherwise false.
*/
private boolean isSet(Property lastProperty, DataObject lastDataObject) {
return lastDataObject.isSet(lastProperty);
}
/** Method that unset a certain property's value.
*
* @param lastProperty the property to queries.
* @param lastDataObject the DataObject, owner of the queried property
*/
private void unSet(Property lastProperty, DataObject lastDataObject, int numInLastProperty) {
if (numInLastProperty == -1) {
lastDataObject.unset(lastProperty);
} else {
List objects = lastDataObject.getList(lastProperty);
if (numInLastProperty <= objects.size()) {
objects.remove(numInLastProperty);
}
}
}
/** Set a property's value.
*
* @param lastProperty the property to queries.
* @param lastDataObject the DataObject, owner of the queried property
* @param numInLastProperty the index number in the value list of the above property
* @param value the value to be set as the target property's value
* @param convertValue boolean used for set if we should convert the value
*/
private void set(Property lastProperty, DataObject lastDataObject, int numInLastProperty, Object value, boolean convertValue) {
if (numInLastProperty == -1) {
if (lastDataObject != null) {
if(convertValue){
DataHelper dataHelper = ((SDODataObject)lastDataObject).getType().getHelperContext().getDataHelper();
value = dataHelper.convert(lastProperty, value);
}
lastDataObject.set(lastProperty, value);
} else {
throw new IllegalArgumentException("lastDataObject is null");
}
} else {// case like set("a/b.1", List) will cause a list be added into a existed list
List objects = lastDataObject.getList(lastProperty);
if (convertValue) {
DataHelper dataHelper = ((SDODataObject)lastDataObject).getType().getHelperContext().getDataHelper();
value = dataHelper.convert(lastProperty.getType(), value);
}
Sequence seq = lastDataObject.getSequence();
if (seq != null) {
seq.setValue(numInLastProperty, value);
} else {
objects.set(numInLastProperty, value);
}
}
}
private String getLocalName(String qualifiedName) {
int index = qualifiedName.indexOf(':');
if (index > -1) {
int bracketIndex = qualifiedName.indexOf('[');
if(bracketIndex > -1 && bracketIndex < index) {
return qualifiedName;
}
String local = qualifiedName.substring(index + 1, qualifiedName.length());
return local;
} else {
return qualifiedName;
}
}
/** Process the passed in fragment, extract the position information if available,
* acquire the property name hidden in this fragment and check if this fragment is
* actually query base. Then perform corresponding actions to access values.
*
* @param frag one string fragment in the path
* @param caller a DataObject that originally took the path information
* @return values to be accessed
*/
private Object getValueForFragment(String frag, DataObject caller) {
int indexOfDot = frag.lastIndexOf('.');
int indexOfOpenBracket = frag.indexOf('[');
int indexOfCloseBracket = frag.lastIndexOf(']');
int position = getNumberInFrag(frag, indexOfDot, indexOfOpenBracket, indexOfCloseBracket);
String propertyName = getPropertyNameInFrag(frag, position, indexOfDot, indexOfOpenBracket);
int equalSignIndex = isQueryPath(frag, indexOfOpenBracket, indexOfCloseBracket);
if (equalSignIndex == -1) {// not query path, note:
return getObjectByFragment(propertyName, position, caller);
}
return getDataObjectFromQuery(frag, indexOfOpenBracket, indexOfCloseBracket, equalSignIndex, caller, propertyName);
}
/** Extract the property name hidden in a fragment of path
*
* @param frag one string fragment in the path
* @param position the index of values to be accessed
* @param indexOfDot the index of . in the fragment
* @param indexOfOpenBracket the indexof [ in the fragment
* @return the property name hidden in this fragment
*/
private String getPropertyNameInFrag(String frag, int position, int indexOfDot, int indexOfOpenBracket) {
int startIndex = 0;
int atIndex = frag.indexOf('@');
if (atIndex != -1 && (indexOfOpenBracket == -1 || atIndex < indexOfOpenBracket)) {
startIndex += 1;
}
if (indexOfOpenBracket != -1) {
return frag.substring(startIndex, indexOfOpenBracket);
}
if ((indexOfDot != -1) && (position != -1)) {
return frag.substring(startIndex, indexOfDot);
}
return frag.substring(startIndex);
}
/** Judge if positional path belongs to bracket case or dot case, then perform
* different actions.
*
* @param frag one string fragment in the path
* @param indexOfDot the index of . in the fragment
* @param indexOfOpenBracket the index of [ in the fragment
* @param indexOfCloseBracket the index of ] in the fragment
* @return the position hidden in fragment
*/
private int getNumberInFrag(String frag, int indexOfDot, int indexOfOpenBracket, int indexOfCloseBracket) {
if ((indexOfOpenBracket != -1) && (indexOfCloseBracket != -1) && (indexOfOpenBracket < indexOfCloseBracket)) {
return acquireNumberInBrackets(frag, indexOfOpenBracket, indexOfCloseBracket);
}
if (indexOfDot != -1) {
return acquireNumberAtDot(frag, indexOfDot);
}
return -1;
}
/**check if information in brackets is qury or not.
*
* @param frag a fragment in path
* @param openBracketIndex index of open bracket in fragment
* @param closeBracketIndex index of close bracket in fragment
* @return
*/
private int isQueryPath(String frag, int openBracketIndex, int closeBracketIndex) {
if ((openBracketIndex != -1) && (closeBracketIndex != -1) && (openBracketIndex < closeBracketIndex)) {
return frag.substring(openBracketIndex, closeBracketIndex).indexOf('=');
}
return -1;
}
// extract value from query and acquire dataobject that meets this requirement
/**Access the DataObject value by using the fragment containing query informations.
*
* @param frag one string fragment in the path
* @param openBracketIndex the index of open bracket in a fragment
* @param closeBracketIndex the index of close bracket in a fragment
* @param equalsignIndex the index of equalsign in string fragment quoted by brackets
* @param caller the DataObject that passes the path information in
* @param callerProperty the name of the property
* @return the DataObject as value of the property having name as the above callerProperty
*/
private DataObject getDataObjectFromQuery(String frag, int openBracketIndex, int closeBracketIndex, int equalsignIndex, DataObject caller, String callerProperty) {
try {
// trim off any whitespace for property names
String propertyNameOfQryDataObject = frag.substring(openBracketIndex + 1, equalsignIndex + openBracketIndex).trim();
List objects = caller.getList(caller.getInstanceProperty(callerProperty));
String query = frag.substring(equalsignIndex + openBracketIndex + 1, closeBracketIndex);
String value = null;
int firstQuoteIndex = query.indexOf('\'');
int lastQuoteIndex = query.lastIndexOf('\'');
if ((firstQuoteIndex == -1) && (lastQuoteIndex == -1)) {// !! note: case: [number=1'23'] is assume not to happen !!
firstQuoteIndex = query.indexOf('\"');
lastQuoteIndex = query.lastIndexOf('\"');
}
if ((firstQuoteIndex != -1) && (lastQuoteIndex != -1) && (firstQuoteIndex < lastQuoteIndex)) {// quoted string existed
value = query.substring(firstQuoteIndex + 1, lastQuoteIndex);
} else {
// if the value is not enclosed on quotes, trim off any whitespace
value = query.trim();
}
Iterator iterObjects = objects.iterator();
Object queryValue = value;
Object actualValue = null;
while (iterObjects.hasNext()) {
DataObject cur = (DataObject)iterObjects.next();
Property p = cur.getInstanceProperty(propertyNameOfQryDataObject);
if(p != null){
try {
queryValue = XMLConversionManager.getDefaultXMLManager().convertObject(queryValue, p.getType().getInstanceClass());
} catch (ConversionException e) {
//do nothing, skip
}
if (!p.isMany()) {
actualValue = cur.get(p);
if (actualValue.equals(queryValue)) {
return cur;
}
} else {// case p is many type
List values = cur.getList(p);
Iterator iterValues = values.iterator();
while (iterValues.hasNext()) {
actualValue = iterValues.next();
if (actualValue.equals(queryValue)) {
return cur;
}
}
}
}
}
return null;
} catch (IllegalArgumentException e) {
return null;
}
}
// process a portion of path, find out if it is positional or not.
// if it is, then check it is using '.' or '[ ]' and perform corresponding
// process.
/** Check if position exists, then either acquire value through position or directly.
*
* @param propertyName the name of property of the caller
* @param position the index of values to be accessed
* @param caller the DataObject containing property with the passed in property name
* @return the values to be accessed
*/
private Object getObjectByFragment(String propertyName, int position, DataObject caller) {
Property prop = caller.getInstanceProperty(propertyName);
if (prop == null){
return null;
}
if (prop.isMany() && position > -1) {
return caller.getList(prop).get(position);
} else {
return caller.get(prop);
}
}
/** Extract position (index) from a bracket fragment of path
*
* @param frag a fragment of path
* @param openBracketIndex the index of [ in path
* @param closeBracketIndex the index of ] in path
* @return the index hidden in fragment
*/
private int acquireNumberInBrackets(String frag, int openBracketIndex, int closeBracketIndex) {
String number = frag.substring(openBracketIndex + 1, closeBracketIndex);
if (number.matches("[1-9][0-9]*")) {
return Integer.parseInt(number) - 1;
//value = number;
}
return -1;// throw Illegal path?
}
/**Extract the position (index) from a dot fragment of path
*
* @param frag a fragment of path
* @param dotIndex the index of . in path
* @return the index hidden in the fragment
*/
private int acquireNumberAtDot(String frag, int dotIndex) {
int value = -1;
String position = frag.substring(dotIndex + 1);
if (position.matches("[0-9]+")) {
value = Integer.parseInt(position);
//value = position;
}
return value;
}
// dataobject a's property a has value dataobject b, dataobject b's property b has value dataobject c,
//dataobject c's property c has value boolean,
/** access the wanted values through path and convert it into required java class.
* If conversion is not supported, exception is thrown.
*
* @param path string representation of accessing path
* @param cls the java class that accessed value is to be converted to
* @param caller the DataObject that pass the path in
* @return values to be accessed
* @throws ClassCastException
*/
public Object convertObjectToValueByPath(String path, Class<?> cls, DataObject caller) throws ClassCastException {
if (null == path || XMLConstants.EMPTY_STRING.equals(path)) {
throw new ClassCastException("Attempting null value conversion.");
}
try {
int lastSlashIndex = path.lastIndexOf('/');
SDODataObject lastDataObject;
String lastPropertyName;
Property lastProperty;
int numInLastProperty = -1;
// to do: if "/" or ".." lastDataObject = container or root
if (-1 < lastSlashIndex) {// case 1 "a/b/c"
String frag = path.substring(lastSlashIndex + 1);
int indexOfDot = frag.lastIndexOf('.');
int indexOfOpenBracket = frag.lastIndexOf('[');
int indexOfCloseBracket = frag.lastIndexOf(']');
numInLastProperty = getNumberInFrag(frag, indexOfDot, indexOfOpenBracket, indexOfCloseBracket);
lastPropertyName = getPropertyNameInFrag(frag, numInLastProperty, indexOfDot, indexOfOpenBracket);//getPropertyNameFromFragment(path.substring(lastSlashIndex + 1));// get last property name on path for case 1
lastDataObject = (SDODataObject)caller.getDataObject(path.substring(0, lastSlashIndex));// get last dataobject on path
if(lastDataObject == null){
return null;
}
lastProperty = lastDataObject.getInstanceProperty(lastPropertyName);// get property of this dataobject
} else {// case 2 "a"
//to do: call eextractPositionAndPropertyName() here
String frag = path;
int indexOfDot = frag.lastIndexOf('.');
int indexOfOpenBracket = frag.lastIndexOf('[');
int indexOfCloseBracket = frag.lastIndexOf(']');
numInLastProperty = getNumberInFrag(frag, indexOfDot, indexOfOpenBracket, indexOfCloseBracket);
lastPropertyName = getPropertyNameInFrag(frag, numInLastProperty, indexOfDot, indexOfOpenBracket);//getPropertyNameFromFragment(path);// get last property name on path for case 1
lastDataObject = (SDODataObject)caller;
if(lastDataObject == null){
return null;
}
lastProperty = caller.getInstanceProperty(lastPropertyName);// get property of this dataobject
}
if ((lastProperty != null) && (cls == Date.class) && lastProperty.getType().equals(SDOConstants.SDO_STRING)) {// in case getDate, for string property, use DataHelper
DataHelper dHelper = HelperProvider.getDefaultContext().getDataHelper();
String dateString;
if (numInLastProperty == -1) {
dateString = (String)lastDataObject.get(lastProperty);
} else {
dateString = (String)(lastDataObject.getList(lastProperty)).get(numInLastProperty);
}
return dHelper.toDate(dateString);
}
return lastDataObject.convertObjectToValue(lastProperty, numInLastProperty, cls);
} catch (IllegalArgumentException e) {// according to exception table 1st row, when get(Property) and get(Index) throw IllegalArgument, get(String) return null
throw new ClassCastException("Conversion is not supported.");
//return null;
}
}
}