| /* |
| * 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; |
| |
| 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; |
| } |
| } |
| } |