blob: 4c830970615caf209f5bf7a934381fa4ccdfa535 [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;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import commonj.sdo.Property;
import commonj.sdo.Sequence;
import javax.xml.namespace.QName;
import org.eclipse.persistence.sdo.SDODataObject;
import org.eclipse.persistence.sdo.helper.ListWrapper;
import org.eclipse.persistence.sdo.helper.SDOTypeHelper;
import org.eclipse.persistence.core.mappings.CoreMapping;
import org.eclipse.persistence.exceptions.SDOException;
import org.eclipse.persistence.internal.oxm.XPathFragment;
import org.eclipse.persistence.oxm.NamespaceResolver;
import org.eclipse.persistence.oxm.XMLConstants;
import org.eclipse.persistence.oxm.XMLDescriptor;
import org.eclipse.persistence.oxm.XMLField;
import org.eclipse.persistence.oxm.XMLRoot;
import org.eclipse.persistence.oxm.mappings.XMLCollectionReferenceMapping;
import org.eclipse.persistence.oxm.mappings.XMLObjectReferenceMapping;
import org.eclipse.persistence.oxm.sequenced.Setting;
import org.eclipse.persistence.mappings.DatabaseMapping;
public class SDOSequence implements Sequence {
private static final String TEXT_XPATH = "text()";
private SDODataObject dataObject;
private List<Setting> settings;
private Map<Key, Setting> valuesToSettings;
public SDOSequence(SDODataObject dataObject) {
// catch a null dataObject early before we get NPE on any update
// operations during add/remove
if (null == dataObject) {
throw SDOException.sequenceDataObjectInstanceFieldIsNull();
}
this.dataObject = dataObject;
this.settings = new ArrayList<Setting>();
this.valuesToSettings = new HashMap<Key, Setting>();
}
public SDODataObject getDataObject() {
return dataObject;
}
public List<Setting> getSettings() {
return settings;
}
protected Map<Key, Setting> getValuesToSettings() {
return valuesToSettings;
}
@Override
public void add(int index, int propertyIndex, Object value) {
Property property = dataObject.getInstanceProperty(propertyIndex);
add(index, property, value);
}
@Override
public boolean add(int propertyIndex, Object value) {
Property property = dataObject.getInstanceProperty(propertyIndex);
return add(property, value);
}
@Override
public void add(int index, Property property, Object value) {
if (!isAllowedInSequence(property)) {
return;
}
if (property != null && property.isOpenContent() && dataObject.getType().isOpen()) {
dataObject.addOpenContentProperty(property);
}
// Update the data object
if (property.isMany()) {
ListWrapper listWrapper = (ListWrapper) dataObject.getList(property);
if (value instanceof List) {
// iterate list
for (Iterator i = ((List) value).iterator(); i.hasNext();) {
// add a setting to the end of the sequence
Object aValue = i.next();
Setting setting = convertToSetting(property, aValue);
valuesToSettings.put(new Key(property, aValue), setting);
settings.add(index++, setting);
// no need to check updateContainment flag -
// ListWrapper.add() will not pass an entire List here
listWrapper.add(aValue, false);
}
} else {
// set individual list item
// add a setting to the end of the sequence
Setting setting = convertToSetting(property, value);
valuesToSettings.put(new Key(property, value), setting);
settings.add(index, setting);
int listIdx = getIndexInList(property, value);
if (listIdx != -1) {
listWrapper.add(listIdx, value, false);
} else {
listWrapper.add(value, false);
}
}
} else {
dataObject.setPropertyInternal((SDOProperty) property, value, false);
// Update the settings
Setting setting = convertToSetting(property, value);
valuesToSettings.put(new Key(property, value), setting);
settings.add(index, setting);
}
}
private boolean isAllowedInSequence(Property property) {
// Disallow the addition of a null Property
if (null == property) {
return false;
}
// Disallow the addition of a read only Property
if (property.isReadOnly()) {
return false;
}
// Disallow the addition of a Properties representing an XML attribute
if (dataObject.getType().getHelperContext().getXSDHelper().isAttribute(property)) {
throw SDOException.sequenceAttributePropertyNotSupported(property.getName());
}
// Disallow an open Property on a closed Type
if (property.isOpenContent() && !dataObject.getType().isOpen()) {
return false;
}
// Disallow the addition of an isMany==false Property that is already
// set
if (property.isMany()) {
return true;
}
if (dataObject.isSet(property)) {
throw SDOException.sequenceDuplicateSettingNotSupportedForComplexSingleObject(getIndexForProperty(property), property.getName());
}
return true;
}
@Override
public void add(int index, String propertyName, Object value) {
Property property = dataObject.getInstanceProperty(propertyName);
if (property == null) {
// Property with given name does not exist - create an open content
// property
property = dataObject.defineOpenContentProperty(propertyName, value);
((SDOProperty) property).setMany(true);
}
add(index, property, value);
}
@Override
public void add(int index, String text) {
addText(index, text);
}
@Override
public boolean add(Property property, Object value) {
if (addSettingWithoutModifyingDataObject(property, value)) {
if (property != null && property.isOpenContent() && dataObject.getType().isOpen()) {
dataObject.addOpenContentProperty(property);
}
if (value instanceof XMLRoot) {
value = ((XMLRoot) value).getObject();
}
if (property.isMany()) {
ListWrapper listWrapper = (ListWrapper) dataObject.getList(property);
listWrapper.add(value, false);
} else {
dataObject.setPropertyInternal((SDOProperty) property, value, false);
}
return true;
}
return false;
}
@Override
public boolean add(String propertyName, Object value) {
Property property = dataObject.getInstanceProperty(propertyName);
if (property == null) {
// Property with given name does not exist - create an open content
// property
property = dataObject.defineOpenContentProperty(propertyName, value);
((SDOProperty) property).setMany(true);
}
return add(property, value);
}
@Override
public void add(String text) {
addText(text);
}
@Override
public void addText(int index, String text) {
// Trigger store of original sequence
dataObject._setModified(true);
Setting textSetting = new Setting(null, TEXT_XPATH);
textSetting.setObject(dataObject);
textSetting.setValue(text, false);
settings.add(index, textSetting);
}
@Override
public void addText(String text) {
// Trigger store of original sequence
dataObject._setModified(true);
Setting textSetting = new Setting(null, TEXT_XPATH);
textSetting.setObject(dataObject);
textSetting.setValue(text, false);
settings.add(textSetting);
}
@Override
public SDOProperty getProperty(int index) {
try {
return getProperty(settings.get(index));
} catch (IndexOutOfBoundsException iobex) {
throw SDOException.invalidIndex(iobex, index);
}
}
public SDOProperty getProperty(Setting setting) {
CoreMapping mapping = setting.getMapping();
if (null == mapping) {
List<Setting> children = setting.getChildren();
if (null != children && children.size() > 0) {
return getProperty(children.get(0));
}
} else {
SDOProperty property = null;
if (null == setting.getName()) {
Object value = setting.getValue();
if (value instanceof SDODataObject) {
SDOProperty containmentProp = ((SDODataObject) value).getContainmentProperty();
if (containmentProp != null) {
property = dataObject.getInstanceProperty(containmentProp.getName());
}
if (property == null) {
XMLDescriptor desc = ((SDODataObject) value).getType().getXmlDescriptor();
String qualifiedName = desc.getDefaultRootElement();
int index = qualifiedName.indexOf(':');
String localName = null;
if (index > -1) {
localName = qualifiedName.substring(index + 1, qualifiedName.length());
} else {
localName = qualifiedName;
}
property = dataObject.getInstanceProperty(localName);
}
} else {
XMLRoot xmlRoot = (XMLRoot) value;
if (null != xmlRoot) {
property = dataObject.getInstanceProperty(xmlRoot.getLocalName());
}
}
} else {
property = dataObject.getInstanceProperty(mapping.getAttributeName());
}
return property;
}
return null;
}
@Override
public Object getValue(int index) {
try {
return getValue(settings.get(index));
} catch (IndexOutOfBoundsException iobex) {
throw SDOException.invalidIndex(iobex, index);
}
}
private Object getValue(Setting setting) {
if (null != setting.getMapping() || (setting.getName() != null && setting.getName().equals(TEXT_XPATH))) {
Object value = setting.getValue();
if (value instanceof XMLRoot) {
value = ((XMLRoot) value).getObject();
}
return value;
}
if (null == setting.getChildren() || setting.getChildren().size() == 0) {
return null;
}
return getValue(setting.getChildren().get(0));
}
@Override
public void move(int toIndex, int fromIndex) {
if (toIndex == fromIndex) {
return;
}
// verify indexes are in range
int size = settings.size();
if (fromIndex < 0 || fromIndex >= size) {
throw SDOException.invalidIndex(null, fromIndex);
}
if (toIndex < 0 || toIndex >= size) {
throw SDOException.invalidIndex(null, toIndex);
}
// trigger deep copy of sequence (if applicable)
dataObject._setModified(true);
Setting setting = settings.remove(fromIndex);
settings.add(toIndex, setting);
SDOProperty prop = getProperty(setting);
if (prop != null && prop.isMany()) {
ListWrapper lw = (ListWrapper) dataObject.getList(prop);
Object value = getValue(setting);
int currentIndexInLw = lw.indexOf(value);
lw.remove(currentIndexInLw, false);
int newIndexInLw = getIndexInList(prop, value);
lw.add(newIndexInLw, value, false);
}
}
@Override
public void remove(int index) {
Setting setting = settings.get(index);
remove(setting);
settings.remove(setting);
}
private void remove(Setting setting) {
CoreMapping mapping = setting.getMapping();
if (null != mapping) {
Property property = null;
if (null == setting.getName()) {
XMLRoot xmlRoot = (XMLRoot) setting.getValue();
if (null != xmlRoot) {
property = dataObject.getInstanceProperty(xmlRoot.getLocalName());
valuesToSettings.remove(new Key(property, setting.getValue()));
}
} else {
property = dataObject.getInstanceProperty(mapping.getAttributeName());
valuesToSettings.remove(new Key(property, setting.getValue()));
}
if (property.isMany()) {
ListWrapper listWrapper = (ListWrapper) dataObject.getList(property);
listWrapper.remove(setting.getValue(), false, false);
} else {
dataObject.unset(property, false, false);
}
} else if (setting.getName() != null && setting.getName().equals(TEXT_XPATH)) {
// Handle "text()"
dataObject._setModified(true);
}
List<Setting> children = setting.getChildren();
if (null != children) {
int childrenSize = children.size();
for (int x = 0; x < childrenSize; x++) {
remove(children.get(x));
}
}
}
/**
* INTERNAL:
*
* @param setting
*/
public void addValueToSettings(Setting setting) {
valuesToSettings.put(new Key(getProperty(setting), getValue(setting)), setting);
}
/**
* INTERNAL:
*
* @param setting
*/
public void removeValueToSettings(Setting setting) {
valuesToSettings.remove(new Key(getProperty(setting), getValue(setting)));
}
@Override
public Object setValue(int index, Object value) {
return setValue(settings.get(index), value);
}
private Object setValue(Setting setting, Object value) {
if (null == setting.getMapping()) {
if (setting.getName() != null && setting.getName().equals(TEXT_XPATH)) {
dataObject._setModified(true);
Object oldValue = setting.getValue();
setting.setValue(value, false);
return oldValue;
}
List<Setting> children = setting.getChildren();
if (null != children && children.size() > 0) {
return setValue(children.get(0), value);
}
return null;
}
SDOProperty property = getProperty(setting);
Object oldValue = setting.getValue();
if (property.isMany()) {
ListWrapper listValue = (ListWrapper) dataObject.getList(property);
int valueIndex = listValue.indexOf(oldValue);
listValue.remove(oldValue, property.isContainment(), false);
listValue.add(valueIndex, value, false);
setting.setValue(value, false);
} else {
if (dataObject.isSet(property)) {
updateSettingWithoutModifyingDataObject(property, dataObject.get(property), value);
setting.setValue(value);
} else {
addSettingWithoutModifyingDataObject(property, value);
}
dataObject.setPropertyInternal(property, value, false);
}
return oldValue;
}
@Override
public int size() {
return settings.size();
}
private Setting convertToSetting(Property property, Object value) {
SDOProperty sdoProperty = (SDOProperty) property;
DatabaseMapping mapping = sdoProperty.getXmlMapping();
Setting setting = new Setting();
SDOType sdoType = dataObject.getType();
XMLDescriptor xmlDescriptor = sdoType.getXmlDescriptor();
if (null == mapping) {
setting.setObject(dataObject);
mapping = xmlDescriptor.getMappingForAttributeName("openContentProperties");
setting.setMapping(mapping);
XMLRoot xmlRoot = new XMLRoot();
if (value instanceof XMLRoot) {
xmlRoot.setLocalName(((XMLRoot) value).getLocalName());
xmlRoot.setNamespaceURI(((XMLRoot) value).getNamespaceURI());
xmlRoot.setObject(((XMLRoot) value).getObject());
} else {
xmlRoot.setLocalName(sdoProperty.getName());
xmlRoot.setNamespaceURI(sdoProperty.getUri());
xmlRoot.setObject(value);
// do not set schema type for global properties
SDOTypeHelper hlpr = (SDOTypeHelper) dataObject.getType().getHelperContext().getTypeHelper();
if (hlpr.getOpenContentProperty(sdoProperty.getUri(), sdoProperty.getName()) == null) {
QName schemaTypeQName = hlpr.getXSDTypeFromSDOType(property.getType());
if (schemaTypeQName != null && schemaTypeQName != XMLConstants.STRING_QNAME) {
xmlRoot.setSchemaType(schemaTypeQName);
}
}
}
setting.setValue(xmlRoot, false);
} else {
setting = convertToSetting(mapping, value);
}
return setting;
}
private Setting convertToSetting(DatabaseMapping mapping, Object value) {
XMLDescriptor xmlDescriptor = (XMLDescriptor) mapping.getDescriptor();
NamespaceResolver nsResolver = xmlDescriptor.getNamespaceResolver();
Setting rootSetting = new Setting();
XMLField xmlField = (XMLField) mapping.getField();
if (xmlField == null) {
if (mapping instanceof XMLObjectReferenceMapping) {
xmlField = (XMLField) ((XMLObjectReferenceMapping) mapping).getFields().get(0);
} else if (mapping instanceof XMLCollectionReferenceMapping) {
xmlField = (XMLField) ((XMLCollectionReferenceMapping) mapping).getFields().get(0);
}
}
Setting setting = rootSetting;
if (xmlField != null) {
XPathFragment xPathFragment = xmlField.getXPathFragment();
rootSetting = convertToSetting(xPathFragment, nsResolver);
setting = rootSetting;
while (xPathFragment.getNextFragment() != null) {
xPathFragment = xPathFragment.getNextFragment();
Setting childSetting = convertToSetting(xPathFragment, nsResolver);
setting.addChild(childSetting);
setting = childSetting;
}
}
setting.setObject(dataObject);
setting.setMapping(mapping);
setting.setValue(value, false);
return rootSetting;
}
private Setting convertToSetting(XPathFragment xPathFragment, NamespaceResolver nsResolver) {
Setting setting = new Setting();
String name = xPathFragment.getLocalName();
if (null == name) {
name = xPathFragment.getShortName();
}
setting.setName(name);
if (xPathFragment.hasNamespace()) {
setting.setNamespaceURI(nsResolver.resolveNamespacePrefix(xPathFragment.getPrefix()));
}
return setting;
}
public SDOSequence copy() {
SDOSequence copy = new SDOSequence(dataObject);
for (int index = 0, size = settings.size(); index < size; index++) {
Setting settingCopy = settings.get(index).copy();
copy.getSettings().add(settingCopy);
copy.getValuesToSettings().put(new Key(getProperty(settingCopy), getValue(settingCopy)), settingCopy);
}
return copy;
}
/**
* INTERNAL: Add a setting to the list at the specified index. The owning
* DataObject will not be made aware of this addition.
*
* @param index
* the index at which to add the new Setting in the Settings list
* @param property
* @param value
* @return true if the a Setting was successfully added to the list,
* otherwise false
*/
public boolean addSettingWithoutModifyingDataObject(int index, Property property, Object value) {
Setting setting = convertToSetting(property, value);
valuesToSettings.put(new Key(property, value), setting);
if (index >= 0) {
settings.add(index, setting);
} else {
settings.add(setting);
}
return true;
}
/**
* INTERNAL:
*/
public boolean addSettingWithoutModifyingDataObject(Property property, Object value) {
return addSettingWithoutModifyingDataObject(property, value, true);
}
/**
* INTERNAL:
*/
public boolean addSettingWithoutModifyingDataObject(Property property, Object value, boolean checkAllowed) {
if (checkAllowed && !isAllowedInSequence(property)) {
return false;
}
Setting setting = convertToSetting(property, value);
valuesToSettings.put(new Key(property, value), setting);
settings.add(setting);
return true;
}
/**
* INTERNAL:
*/
public void updateSettingWithoutModifyingDataObject(Property property, Object oldValue, Object newValue) {
Key key = new Key(property, oldValue);
Setting setting = valuesToSettings.get(key);
valuesToSettings.remove(key);
valuesToSettings.put(new Key(property, newValue), setting);
// set the new value on the appropriate setting
while (setting.getMapping() == null) {
List<Setting> children = setting.getChildren();
if (children != null && children.size() > 0) {
setting = children.get(0);
}
}
setting.setValue(newValue, false);
}
/**
* INTERNAL:
*/
public void removeSettingWithoutModifyingDataObject(Property property, Object value) {
settings.remove(valuesToSettings.remove(new Key(property, value)));
}
/**
* INTERNAL:
*/
public void removeSettingWithoutModifyingDataObject(Property property) {
List<Key> keys = new ArrayList<Key>(valuesToSettings.keySet());
int size = valuesToSettings.keySet().size();
for (int i = size - 1; i >= 0; i--) {
Key nextKey = keys.get(i);
if (nextKey.getProperty() == property) {
settings.remove(valuesToSettings.remove(nextKey));
}
}
}
/**
* INTERNAL: Convenience method that returns the index of the Setting
* associated with a given property in the Settings list
*
* @param property
* @return index of the Setting associated with a given property in the
* Settings list or -1 if not found
*/
public int getIndexForProperty(Property property) {
List<Key> keys = new ArrayList<Key>(valuesToSettings.keySet());
for (int i = 0; i < keys.size(); i++) {
Key nextKey = keys.get(i);
if (nextKey.getProperty() == property) {
return settings.indexOf(valuesToSettings.get(nextKey));
}
}
return -1;
}
/**
* INTERNAL: Convenience method that, given a many property and a value,
* returns the associated Setting's index in the Settings list. For example,
* if a sequence contains many properties "letters" and "numbers", such as
* [A, 1, C, 2, B, D], and we are looking for the letter B, this method will
* return 2. Although B is at index 4 of the Settings list, it is at index 2
* of the list of "letters" - [A, C, B, D].
*
* @return index of the value's Setting in the list relative to a given
* property or -1 if not found.
*/
private int getIndexInList(Property manyProp, Object value) {
int returnIndex = -1;
for (int i = 0; i < settings.size(); i++) {
Setting nextSetting = settings.get(i);
SDOProperty prop = getProperty(nextSetting);
if (prop.equals(manyProp)) {
returnIndex++;
if (value.equals(getValue(nextSetting))) {
return returnIndex;
}
}
}
return returnIndex;
}
/**
* INTERNAL: Get the root Setting for a given Setting.
*
* @param setting
* @return the root Setting or this Setting if it is a root
*/
public static Setting getRootSetting(Setting setting) {
Setting rootSetting = setting;
while (rootSetting.getParent() != null) {
rootSetting = rootSetting.getParent();
}
return rootSetting;
}
/**
* INTERNAL: Ensure that each Setting in the settings list is also present
* in the valuesToSettings map
*/
public void afterUnmarshal() {
for (Iterator<Setting> setIt = getSettings().iterator(); setIt.hasNext();) {
addValueToSettings(setIt.next());
}
}
private static class Key {
private Property property;
private Object value;
public Key(Property property, Object value) {
this.property = property;
this.value = value;
}
protected Property getProperty() {
return this.property;
}
protected Object getValue() {
return this.value;
}
@Override
public boolean equals(Object object) {
try {
Key key = (Key) object;
return property == key.getProperty() && value == key.getValue();
} catch (ClassCastException e) {
return false;
}
}
@Override
public int hashCode() {
if (value == null) {
return 0;
}
return value.hashCode();
}
}
}