| /* |
| * 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 commonj.sdo.DataObject; |
| import commonj.sdo.Property; |
| import commonj.sdo.Sequence; |
| import commonj.sdo.helper.HelperContext; |
| import commonj.sdo.helper.TypeHelper; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import org.eclipse.persistence.sdo.SDOChangeSummary; |
| import org.eclipse.persistence.sdo.SDOConstants; |
| import org.eclipse.persistence.sdo.SDODataObject; |
| import org.eclipse.persistence.sdo.SDOProperty; |
| import org.eclipse.persistence.sdo.SDOSequence; |
| import org.eclipse.persistence.exceptions.SDOException; |
| import org.eclipse.persistence.internal.oxm.XMLConversionManager; |
| import org.eclipse.persistence.oxm.XMLRoot; |
| import org.eclipse.persistence.oxm.XMLUnmarshaller; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| |
| /** |
| * <p><b>Purpose</b>: Implementation of XMLUnmarshalListener used when unmarshalling XML to XMLDocuments |
| * <p><b>Responsibilities</b>:<ul> |
| * <li> When creating a DataObject we need to call setType and setHelperContext with the appropriate values |
| * <li> When we are finished Unmarshalling the root object we need to set up the ChangeSummary objects. |
| * ChangeSummaries have xpaths to other parts of the documents so the rest of the objects need to be built before we process the ChangeSummaries |
| * </ul> |
| */ |
| public class SDOUnmarshalListener extends SDOCSUnmarshalListener { |
| private List<SDOChangeSummary> changeSummaries; |
| |
| public SDOUnmarshalListener(HelperContext aContext) { |
| super(aContext); |
| } |
| |
| @Override |
| public void beforeUnmarshal(Object target, Object parent) { |
| //Setting the helperContext and type on the DataObject or ChangeSummary |
| super.beforeUnmarshal(target, parent); |
| } |
| |
| /** |
| * @param target assumed to be non-null |
| * @param parent may be null, indicating target is root object |
| */ |
| @Override |
| public void afterUnmarshal(Object target, Object parent) { |
| SDODataObject targetDataObject; |
| // assume target is DataObject or ChangeSummary |
| try { |
| targetDataObject = (SDODataObject)target; |
| } catch (ClassCastException ccex) { |
| // each time we hit a ChangeSummary store it to process later and set its root |
| // object - this is because we can't fully process the cs's because they have |
| // references that can't be resolved until the full tree is built |
| SDOChangeSummary sdoChangeSummary = (SDOChangeSummary) target; |
| sdoChangeSummary.setRootDataObject((DataObject)parent); |
| getChangeSummaries().add(sdoChangeSummary); |
| return; |
| } |
| |
| // if getType is sequenced, then update values to settings map |
| if (targetDataObject.getType().isSequenced()) { |
| targetDataObject.getSequence().afterUnmarshal(); |
| } |
| |
| // if parent is null we are back to the root object |
| // the last object that will hit the afterUnmarshal method |
| if (parent == null && null != changeSummaries) { |
| XMLUnmarshaller unmarshaller = null; |
| for (int i = 0, changeSummariesSize=changeSummaries.size(); i < changeSummariesSize; i++) { |
| SDOChangeSummary nextCS = changeSummaries.get(i); |
| // Set logging to true until finished building modified list. |
| boolean loggingValue = nextCS.isLoggingMapping(); |
| nextCS.setLogging(true); |
| |
| // CREATES |
| // For each xpath in the create attribute convert it to an sdo path and execute it against the root |
| // dataobject to get the dataobject being pointed to and set that dataobject to be created |
| List xpaths = nextCS.getCreatedXPaths(); |
| for (int j = 0, xpathsSize = xpaths.size(); j < xpathsSize; j++) { |
| String nextXPath = (String)xpaths.get(j); |
| String sdoPath = convertXPathToSDOPath(nextXPath); |
| SDODataObject nextCreatedDO = targetDataObject.getDataObject(sdoPath); |
| |
| if(nextCreatedDO == null) { |
| int nextSlash = sdoPath.indexOf('/'); |
| if(nextSlash != -1) { |
| sdoPath = sdoPath.substring(nextSlash + 1); |
| } else { |
| sdoPath = "/"; |
| } |
| nextCreatedDO = targetDataObject.getDataObject(sdoPath); |
| } |
| |
| if (nextCreatedDO != null) { |
| nextCreatedDO._setCreated(true); |
| nextCS.getOldContainers().remove(nextCreatedDO); |
| } else { |
| throw SDOException.errorProcessingXPath(nextXPath); |
| } |
| } |
| //clear the createxpaths list that was read in from XML |
| nextCS.setCreatedXPaths(null); |
| |
| //MODIFIED |
| List modifiedDoms = nextCS.getModifiedDoms(); |
| for (int j = 0, modifiedDomsSize=modifiedDoms.size(); j < modifiedDomsSize; j++) { |
| Element nextNode = (Element)modifiedDoms.get(j); |
| String refValue = nextNode.getAttributeNS(SDOConstants.SDO_URL, SDOConstants.CHANGESUMMARY_REF); |
| if ((refValue == null) || (refValue.length() == 0)) { |
| throw SDOException.missingRefAttribute(); |
| } |
| //nextModifiedDO is the real modified current data object |
| String sdoPath = convertXPathToSDOPath(refValue); |
| SDODataObject nextModifiedDO = targetDataObject.getDataObject(sdoPath); |
| //if it failed, try peeling off the first fragment (may be the root |
| if(nextModifiedDO == null) { |
| int nextSlash = sdoPath.indexOf('/'); |
| if(nextSlash != -1) { |
| sdoPath = sdoPath.substring(nextSlash + 1); |
| } else { |
| sdoPath = "/"; |
| } |
| nextModifiedDO = targetDataObject.getDataObject(sdoPath); |
| } |
| String unsetValue = nextNode.getAttributeNS(SDOConstants.SDO_URL, SDOConstants.CHANGESUMMARY_UNSET); |
| List unsetValueList = new ArrayList(); |
| if ((unsetValue != null) && (unsetValue.length() > 0)) { |
| XMLConversionManager xmlConversionManager = ((SDOXMLHelper) aHelperContext.getXMLHelper()).getXmlConversionManager(); |
| unsetValueList = xmlConversionManager.convertObject(unsetValue, List.class); |
| } |
| if (nextModifiedDO != null) { |
| nextModifiedDO._setModified(true); |
| SDOCSUnmarshalListener listener = new SDOCSUnmarshalListener(nextModifiedDO.getType().getHelperContext(), true); |
| if(null == unmarshaller) { |
| unmarshaller = ((SDOXMLHelper)aHelperContext.getXMLHelper()).getXmlContext().createUnmarshaller(); |
| } |
| unmarshaller.setUnmarshalListener(listener); |
| unmarshaller.getProperties().put("sdoHelperContext", aHelperContext); |
| unmarshaller.setUnmappedContentHandlerClass(SDOUnmappedContentHandler.class); |
| Object unmarshalledNode = unmarshaller.unmarshal(nextNode, nextModifiedDO.getType().getXmlDescriptor().getJavaClass()); |
| //unmarshalledDO is the modified dataobject from the changesummary xml |
| SDODataObject unmarshalledDO = null; |
| // Assumption: unmarshalledNode should always be either an instance of XMLRoot or DataObject |
| if (unmarshalledNode instanceof XMLRoot) { |
| unmarshalledDO = (SDODataObject)((XMLRoot)unmarshalledNode).getObject(); |
| } else if (unmarshalledNode instanceof DataObject) { |
| unmarshalledDO = (SDODataObject)unmarshalledNode; |
| } |
| List modifiedProps = new ArrayList(); |
| Node n = nextNode.getFirstChild(); |
| TypeHelper typeHelper = aHelperContext.getTypeHelper(); |
| while(n != null) { |
| if (n.getNodeType() == Node.ELEMENT_NODE) { |
| String propName = n.getLocalName(); |
| Property nextProp = unmarshalledDO.getInstanceProperty(propName); |
| if (nextProp == null) { |
| nextProp = typeHelper.getOpenContentProperty(n.getNamespaceURI(), propName); |
| } |
| if (!modifiedProps.contains(nextProp)) { |
| modifiedProps.add(nextProp); |
| } |
| } |
| n = n.getNextSibling(); |
| } |
| //instead of iterating over all props can we just check elements in cs and get appropriate properties from DO |
| for (int k = 0, modifiedPropsSize = modifiedProps.size(); k < modifiedPropsSize; k++) { |
| SDOProperty nextProp = (SDOProperty)modifiedProps.get(k); |
| if (!nextProp.getType().isDataType()) { |
| if (nextProp.isMany()) { |
| //original value is the list from the changesummary xml |
| List originalValue = unmarshalledDO.getList(nextProp); |
| List newList = new ArrayList(); |
| List toDelete = new ArrayList(); |
| List indexsToDelete = new ArrayList(); |
| for (int l = 0, originalValueSize = originalValue.size(); l < originalValueSize; l++) { |
| SDODataObject nextInList = (SDODataObject)originalValue.get(l); |
| String sdoRef = nextInList._getSdoRef(); |
| if (sdoRef != null) { |
| //if sdoRef is not null then object is modified |
| String sdoRefPath = convertXPathToSDOPath(sdoRef); |
| int nextSlash = sdoRefPath.indexOf('/'); |
| if(nextSlash != -1) { |
| sdoRefPath = sdoRefPath.substring(nextSlash + 1); |
| } else { |
| sdoRefPath = "/"; |
| } |
| newList.add(targetDataObject.getDataObject(sdoRefPath)); |
| } else { |
| //if sdo ref is null there is a deleted object |
| toDelete.add(nextInList); |
| indexsToDelete.add(l); |
| newList.add(nextInList); |
| } |
| } |
| //lw is the list from the real current data object |
| ListWrapper lw = ((ListWrapper)nextModifiedDO.getList(nextProp)); |
| int indexsToDeleteSize = indexsToDelete.size(); |
| if (indexsToDeleteSize > 0) { |
| //after this loop, lw will have the entire list when logging was turned on |
| nextCS.pauseLogging(); |
| for (int m = 0; m < indexsToDeleteSize; m++) { |
| int toDeleteIndex = (Integer) indexsToDelete.get(m); |
| SDODataObject nextToDelete = (SDODataObject)toDelete.get(m); |
| lw.add(toDeleteIndex, nextToDelete); |
| } |
| nextCS.setPropertyInternal(nextModifiedDO, nextProp, lw); |
| SDOSequence nextSeq = ((SDOSequence)nextCS.getOriginalSequences().get(nextModifiedDO)); |
| nextCS.resumeLogging(); |
| nextModifiedDO._setModified(true); |
| for (int m = indexsToDeleteSize - 1; m >= 0; m--) { |
| int toDeleteIndex = (Integer) indexsToDelete.get(m); |
| SDODataObject nextToDelete = (SDODataObject)toDelete.get(m); |
| if(nextSeq != null){ |
| nextSeq.addSettingWithoutModifyingDataObject(-1, nextProp, nextToDelete); |
| } |
| nextToDelete.resetChanges(); |
| |
| lw.remove(toDeleteIndex); |
| } |
| } |
| nextCS.getOriginalElements().put(lw, newList); |
| } else { |
| SDODataObject value = unmarshalledDO.getDataObject(nextProp); |
| if (value != null) { |
| String sdoRef = value._getSdoRef(); |
| if (sdoRef != null) { |
| //modified |
| nextModifiedDO._setModified(true); |
| } else { |
| //deleted |
| value._setChangeSummary(nextCS); |
| nextModifiedDO._setModified(true); |
| nextCS.pauseLogging(); |
| boolean wasSet = nextModifiedDO.isSet(nextProp); |
| |
| Object existingValue = nextModifiedDO.get(nextProp); |
| // grab index of nextProp's Setting for use during setting below |
| Sequence nextModifiedDOSequence = nextModifiedDO.getSequence(); |
| int settingIdx = -1; |
| if (nextModifiedDOSequence != null) { |
| settingIdx = ((SDOSequence)nextModifiedDOSequence).getIndexForProperty(nextProp); |
| } |
| value._setContainmentPropertyName(null); |
| value._setContainer(null); |
| nextModifiedDO.set(nextProp, value); |
| nextCS.setPropertyInternal(nextModifiedDO, nextProp, value); |
| SDOSequence nextSeq = ((SDOSequence)nextCS.getOriginalSequences().get(nextModifiedDO)); |
| if(nextSeq != null){ |
| nextSeq.addSettingWithoutModifyingDataObject(-1, nextProp, value); |
| } |
| |
| nextCS.resumeLogging(); |
| nextModifiedDO._setModified(true); |
| |
| value.resetChanges(); |
| value.delete(); |
| if (wasSet) { |
| // need to add at the right pos in the list, not at the end |
| nextModifiedDO.set(nextProp, existingValue, false); |
| if (settingIdx != -1) { |
| nextModifiedDO.getSequence().addSettingWithoutModifyingDataObject(settingIdx, nextProp, existingValue); |
| } |
| } else { |
| nextModifiedDO.unset(nextProp); |
| } |
| } |
| } else { |
| nextModifiedDO._setModified(true); |
| nextCS.setPropertyInternal(nextModifiedDO, nextProp, null); |
| } |
| } |
| } else { |
| nextModifiedDO._setModified(true); |
| Object value = unmarshalledDO.get(nextProp); |
| //lw is the list from the real current data object |
| |
| if(nextProp.isMany()){ |
| |
| Property theProp = nextModifiedDO.getInstanceProperty(nextProp.getName()); |
| if(theProp == null){ |
| Property newProp = nextModifiedDO.defineOpenContentProperty(nextProp.getName(), new ArrayList(), nextProp.getType()); |
| nextModifiedDO.set(newProp, new ArrayList()); |
| theProp = newProp; |
| } |
| List lw = nextModifiedDO.getList(theProp.getName()); |
| nextCS.setPropertyInternal(nextModifiedDO, theProp, lw); |
| nextCS.getOriginalElements().put(lw, ((ListWrapper)value).getCurrentElements()); |
| }else{ |
| nextCS.setPropertyInternal(nextModifiedDO, nextProp, value); |
| } |
| } |
| } |
| |
| for (int k = 0, unsetValueListSize = unsetValueList.size(); k < unsetValueListSize; k++) { |
| SDOProperty nextProp = unmarshalledDO.getInstanceProperty((String)unsetValueList.get(k)); |
| if (nextProp != null) { |
| Object oldValue = null; |
| if (nextProp.getType().isDataType() || nextProp.isMany()) { |
| //to get default |
| oldValue = unmarshalledDO.get(nextProp); |
| } |
| nextModifiedDO._setModified(true); |
| nextCS.setPropertyInternal(nextModifiedDO, nextProp, oldValue); |
| nextCS.unsetPropertyInternal(nextModifiedDO, nextProp); |
| } else { |
| nextProp = nextModifiedDO.getInstanceProperty((String)unsetValueList.get(k)); |
| nextModifiedDO._setModified(true); |
| nextCS.setPropertyInternal(nextModifiedDO, nextProp, null); |
| nextCS.unsetPropertyInternal(nextModifiedDO, nextProp); |
| } |
| } |
| } else { |
| throw SDOException.errorProcessingXPath(refValue); |
| } |
| } |
| |
| //clear modified doms list |
| nextCS.setModifiedDoms(null); |
| |
| //clear deleted xpaths list |
| nextCS.setDeletedXPaths(null); |
| |
| List created = nextCS.getCreated(); |
| for(int j=0, createdSize = created.size(); j<createdSize; j++) { |
| SDODataObject next = (SDODataObject)created.get(j); |
| Property containmentProperty = next.getContainmentProperty(); |
| if(containmentProperty != null && containmentProperty.isMany()) { |
| SDODataObject container = next.getContainer(); |
| ListWrapper list = (ListWrapper)container.get(containmentProperty); |
| if(!(nextCS.getOriginalElements().containsKey(list))) { |
| //if there was an object created as part of a list, and that list is not |
| //already in the original elements map. Add an empty list to the map. |
| nextCS.getOriginalElements().put(list, new ArrayList()); |
| } |
| } |
| } |
| |
| nextCS.setLogging(loggingValue); |
| } |
| // reset changeSummary list - we are done with it |
| changeSummaries = null; |
| } |
| } |
| |
| private List<SDOChangeSummary> getChangeSummaries() { |
| if(null == changeSummaries) { |
| changeSummaries = new ArrayList<SDOChangeSummary>(); |
| } |
| return changeSummaries; |
| } |
| |
| private String convertXPathToSDOPath(String xpath) { |
| if ((xpath == null) || (xpath.length() < SDOConstants.SDO_CHANGESUMMARY_REF_PATH_PREFIX_LENGTH) ||// |
| !xpath.startsWith(SDOConstants.SDO_CHANGESUMMARY_REF_PATH_PREFIX)) { |
| throw SDOException.errorProcessingXPath(xpath); |
| } |
| if (xpath.equals("#/")) { |
| return "/"; |
| } else if (xpath.startsWith("#/")) { |
| return xpath.substring(2, xpath.length()); |
| } else { |
| // remove the sdo ref prefix only "#", leave the root path identifier "/" |
| return xpath.substring(1, xpath.length()); |
| } |
| } |
| |
| } |