/******************************************************************************* * Copyright (c) 1998, 2015 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 v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * 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); } 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 */ 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 = (List)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)).intValue(); 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)).intValue(); 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()); } } }