| /* |
| * 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.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import commonj.sdo.ChangeSummary; |
| import commonj.sdo.DataGraph; |
| import commonj.sdo.DataObject; |
| import commonj.sdo.Property; |
| import commonj.sdo.Sequence; |
| import commonj.sdo.helper.HelperContext; |
| import org.eclipse.persistence.sdo.helper.ListWrapper; |
| import org.eclipse.persistence.sdo.helper.SDOCopyHelper; |
| |
| /** |
| * <p><b>Purpose</b>:A change summary is used to record changes to DataObjects. |
| * <p><b>Responsibilities</b>:<ul> |
| * <li> Track changes to DataObjects that are within the scope of this ChangeSummary (based on the root object of the Changesummary). |
| * <li> Track if those DataObjects are created, modified or deleted. |
| * <li> Return the values at the time that ChangeSummary logging was turned on for DataObjects in scope. |
| * </ul> |
| * <p>This class is implemented as a Memento (283) [GOF - Gamma, Helm, Johnson, Vlissides] Design Pattern.<br> |
| * (Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.)<br> |
| * <p>The class also realizes some aspects of the Command (233) Pattern - the undo-able operation part (from the first change).<br> |
| * (Encapsulate a request as an object, thereby letting you parameterize clients with different requests, |
| * queue or log requests, and support undo-able operations.) |
| */ |
| public class SDOChangeSummary implements ChangeSummary { |
| private SDODataObject rootDataObject; |
| private boolean logging; |
| |
| //loggingMapping is the boolean that's mapped in OX, we don't really want to turn |
| //on logging until after the XML has all been processed at which time we set the |
| //real logging boolean |
| private boolean loggingMapping; |
| private DataGraph dataGraph; |
| private List createdList; |
| private List deletedList; |
| private Map deepCopies; |
| private List createdXPaths;//for ox mapping |
| |
| /** The deletedXPaths field is picked up reflectively during marshal/unmarshal operations. */ |
| @SuppressWarnings("unused") private List deletedXPaths; //for ox mapping |
| private List modifiedDoms; |
| private Map unsetPropsMap; |
| |
| /** To implement ChangeSummary undo we require a copy of the original state of our model. |
| * The originalValueStores will be populated when we start logging - it will then point to the |
| * currentValueStore while the currentValueStore will be shallow copied (its child ValueStores |
| * are shared between both ValueStores) |
| */ |
| private Map originalValueStores;// HashMap<DataObject, ValueStore> |
| private Map originalElements; |
| |
| // old values |
| private Map oldContainer;// Key is DataObject, value is DataObject |
| private Map oldContainmentProperty;// Key is DataObject, value is Property |
| |
| /** Cache map of originalSequences {@code Map<DataObject, Sequence>} */ |
| private Map oldSequences;// Key is DataObject, value is Sequence |
| |
| /** Map of originalSequence {@code Map<DataObject, Sequence>} */ |
| private Map originalSequences;// Key is DataObject, value is Sequence |
| private Map unsetOCPropsMap; |
| |
| /** A HashMap of List objects keyed off SDODataObject, null key:value permitted */ |
| private Map oldSettings;// List of SDOSettings |
| |
| /** */ |
| private Map reverseDeletedMap; |
| |
| /** Hold the context containing all helpers so that we can preserve inter-helper relationships */ |
| private HelperContext aHelperContext; |
| |
| public SDOChangeSummary() { |
| // HelperContext is set during unmarshalling in SDOUnmarshalListener |
| createdList = new ArrayList(); |
| deepCopies = new HashMap(); |
| deletedList = new ArrayList(); |
| oldSettings = new HashMap(); |
| oldContainer = new HashMap(); |
| oldContainmentProperty = new HashMap(); |
| unsetPropsMap = new HashMap(); |
| unsetOCPropsMap = new HashMap(); |
| originalValueStores = new HashMap(); |
| originalElements = new HashMap(); |
| reverseDeletedMap = new HashMap(); |
| } |
| |
| public SDOChangeSummary(SDODataObject dataObject, HelperContext aContext) { |
| this(); |
| aHelperContext = aContext; |
| rootDataObject = dataObject; |
| } |
| |
| public SDOChangeSummary(SDODataGraph dataGraph, HelperContext aContext) { |
| this(); |
| aHelperContext = aContext; |
| this.dataGraph = dataGraph; |
| } |
| |
| /** |
| * Indicates whether change logging is on (<code>true</code>) or off (<code>false</code>). |
| * @return <code>true</code> if change logging is on. |
| * @see #beginLogging |
| * @see #endLogging |
| */ |
| @Override |
| public boolean isLogging() { |
| return logging; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set flag created value. |
| * @param created flag created's new value. |
| */ |
| public void setCreated(DataObject anObject, boolean created) { |
| if (getRootObject() == anObject) { |
| return; |
| } |
| |
| // Explicitly clear the flag |
| if (isLogging() && !created) { |
| createdList.remove(anObject); |
| } |
| |
| if (isLogging() && !isCreated(anObject)) { |
| if (created) { |
| // remove from other sets |
| deletedList.remove(anObject); |
| // add to set |
| createdList.add(anObject); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Set flag modified value. |
| * @param deleted flag modified's new value. |
| */ |
| public boolean setDeleted(DataObject anObject, boolean deleted) { |
| if (getRootObject() == anObject) { |
| return false; |
| } |
| |
| // Explicitly clear the flag |
| if (isLogging() && !deleted) { |
| deletedList.remove(anObject); |
| } |
| |
| if (isLogging() && !this.isDeleted(anObject)) { |
| if (deleted) { |
| // remove from other sets |
| if(isCreated(anObject)){ |
| createdList.remove(anObject); |
| |
| oldSettings.remove(anObject); |
| originalValueStores.remove(anObject); |
| originalElements.remove(anObject); |
| return false; |
| }else { |
| pauseLogging(); |
| deletedList.add(anObject); |
| resumeLogging(); |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * @param aKey |
| * @param aValue |
| * void |
| */ |
| public void setOldContainer(SDODataObject aKey, DataObject aValue) { |
| oldContainer.put(aKey, aValue); |
| } |
| |
| /** |
| * INTERNAL: |
| * |
| * @return |
| */ |
| public Map getOldContainers() { |
| return oldContainer; |
| } |
| |
| /** |
| * INTERNAL: |
| * @param aKey DataObject |
| * @param aValue Property |
| * void |
| * |
| */ |
| public void setOldContainmentProperty(SDODataObject aKey, Property aValue) { |
| oldContainmentProperty.put(aKey, aValue); |
| } |
| |
| /** |
| * INTERNAL: |
| * @param aKey DataObject |
| * @param aValue Property |
| * void |
| * |
| */ |
| public void setOldSequence(SDODataObject aKey, Sequence aValue) { |
| getOldSequences().put(aKey, aValue); |
| } |
| |
| /** |
| * Returns the {@link DataGraph data graph} associated with this change summary or null. |
| * @return the data graph. |
| * @see DataGraph#getChangeSummary |
| */ |
| @Override |
| public DataGraph getDataGraph() { |
| return dataGraph; |
| } |
| |
| /** |
| * Returns a list consisting of all the {@link DataObject data objects} that have been changed while {@link #isLogging logging}. |
| * <p> |
| * The {@link #isCreated new} and {@link #isModified modified} objects in the List are references to objects |
| * associated with this ChangeSummary. |
| * The {@link #isDeleted deleted} objects in the List are references to objects |
| * at the time that event logging was enabled; |
| * <p> Each changed object must have exactly one of the following methods return true: |
| * {@link #isCreated isCreated}, |
| * {@link #isDeleted isDeleted}, or |
| * {@link #isModified isModified}. |
| * @return a list of changed data objects. |
| * @see #isCreated(DataObject) |
| * @see #isDeleted(DataObject) |
| * @see #isModified(DataObject) |
| */ |
| @Override |
| public List getChangedDataObjects() { |
| // merge all the sets |
| ArrayList aList = new ArrayList(); |
| aList.addAll(getModified()); |
| if (deletedList != null) { |
| aList.addAll(deletedList); |
| } |
| if (createdList != null) { |
| aList.addAll(createdList); |
| } |
| return aList; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return all modified objects |
| * @return |
| * Set |
| */ |
| public List getModified() { |
| ArrayList modifiedList = new ArrayList(); |
| getModified(rootDataObject, modifiedList); |
| return modifiedList; |
| } |
| |
| private void getModified(SDODataObject sdoDataObject, List modifiedList) { |
| if(null == sdoDataObject) { |
| return; |
| } |
| |
| if(isModified(sdoDataObject)) { |
| modifiedList.add(sdoDataObject); |
| } |
| List<Property> properties = sdoDataObject.getInstanceProperties(); |
| for(int x=0; x<properties.size(); x++) { |
| Property property = properties.get(x); |
| if(property.isContainment()) { |
| if(property.isMany()) { |
| List<SDODataObject> dataObjects = sdoDataObject.getList(property); |
| for(int y=0; y<dataObjects.size(); y++) { |
| getModified(dataObjects.get(y), modifiedList); |
| } |
| } else { |
| if ((property.getType() != null) && !(((SDOType)property.getType()).isChangeSummaryType())) { |
| getModified(sdoDataObject.getDataObject(property), modifiedList); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return all deleted objects |
| * @return |
| * Set |
| */ |
| public List getDeleted() { |
| return deletedList; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return all created objects |
| * @return |
| * Set |
| */ |
| public List getCreated() { |
| return createdList; |
| } |
| |
| /** |
| * Returns whether or not the specified data object was created while {@link #isLogging logging}. |
| * Any object that was added to the scope |
| * but was not in the scope when logging began, |
| * will be considered created. |
| * @param dataObject the data object in question. |
| * @return <code>true</code> if the specified data object was created. |
| * @see #getChangedDataObjects |
| */ |
| @Override |
| public boolean isCreated(DataObject dataObject) { |
| return (createdList != null) && createdList.contains(dataObject); |
| } |
| |
| /** |
| * Returns whether or not the specified data object was deleted while {@link #isLogging logging}. |
| * Any object that is not in scope but was in scope when logging began |
| * will be considered deleted. |
| * @param dataObject the data object in question. |
| * @return <code>true</code> if the specified data object was deleted. |
| * @see #getChangedDataObjects |
| */ |
| @Override |
| public boolean isDeleted(DataObject dataObject) { |
| return (deletedList != null) && deletedList.contains(dataObject); |
| } |
| |
| /** |
| * Returns whether or not the specified data object was updated while {@link #isLogging logging}. |
| * An object that was contained in the scope when logging began |
| * and remains in the scope when logging ends will be considered potentially modified. |
| * <p> An object considered modified must have at least one old value setting. |
| * @param dataObject the data object in question. |
| * @return <code>true</code> if the specified data object was modified. |
| * @see #getChangedDataObjects |
| */ |
| @Override |
| public boolean isModified(DataObject dataObject) { |
| // a modified data object is present in the original value |
| // stores list and has not been deleted |
| if (this.originalValueStores.get(dataObject) == null || isDeleted(dataObject)) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Returns a list of {@link ChangeSummary.Setting settings} |
| * that represent the property values of the given <code>dataObject</code> |
| * at the point when logging {@link #beginLogging() began}. |
| * <p>In the case of a {@link #isDeleted(DataObject) deleted} object, |
| * the List will include settings for all the Properties. |
| * <p> An old value setting indicates the value at the |
| * point logging begins. A setting is only produced for |
| * {@link #isModified modified} objects if |
| * either the old value differs from the current value or |
| * if the isSet differs from the current value. |
| * <p> No settings are produced for {@link #isCreated created} objects. |
| * @param dataObject the object in question. |
| * @return a list of settings. |
| * @see #getChangedDataObjects |
| */ |
| @Override |
| public List getOldValues(DataObject dataObject) { |
| if ((dataObject == null) || (!isDeleted(dataObject) && ((((SDODataObject)dataObject).getChangeSummary() != null) && (((SDODataObject)dataObject).getChangeSummary() != this)))) { |
| return new ArrayList(); |
| } |
| if (!isCreated(dataObject) && isDirty(dataObject)) { |
| List oldSettingsList = new ArrayList(); |
| for (int i = 0; i < dataObject.getInstanceProperties().size(); i++) { |
| SDOProperty nextProp = (SDOProperty)dataObject.getInstanceProperties().get(i); |
| Setting setting = getOldValueForChangedDataObject(dataObject, nextProp); |
| if (setting != null) { |
| oldSettingsList.add(setting); |
| } |
| } |
| |
| List openProps = (List)getUnsetOCPropertiesMap().get(dataObject); |
| if(openProps != null){ |
| for(int i=0; i< openProps.size(); i++){ |
| SDOProperty nextProp = (SDOProperty)openProps.get(i); |
| Setting setting = getOldValueForChangedDataObject(dataObject, nextProp); |
| if (setting != null) { |
| oldSettingsList.add(setting); |
| } |
| } |
| } |
| |
| return oldSettingsList; |
| } |
| |
| return new ArrayList();// Note: spec did not mention null value case. |
| } |
| |
| /** |
| * INTERNAL: |
| * |
| * @param dataObject |
| * @return |
| */ |
| public List getUnsetProps(DataObject dataObject) { |
| //call getOldValues to populate oldSettings which will populate unsetPropsMap |
| getOldValues(dataObject); |
| Object value = unsetPropsMap.get(dataObject); |
| if (value == null) { |
| return new ArrayList(); |
| } |
| return (List)value; |
| |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the entire HashMap of lists of open content properties that were unset |
| * keyed on dataObject |
| * @return |
| */ |
| public Map getUnsetOCPropertiesMap() { |
| return unsetOCPropsMap; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return a List containing all open content properties that were unset |
| * @param dataObject |
| * @return |
| */ |
| public List getUnsetOCProperties(DataObject dataObject) { |
| Object value = unsetOCPropsMap.get(dataObject); |
| if (null == value) { |
| return new ArrayList(); |
| } |
| return (List)value; |
| } |
| |
| /** |
| * INTERNAL: |
| * Add an open content property that has been unset to the list keyed on dataObject |
| * @param dataObject |
| * @param ocKey |
| */ |
| public void setUnsetOCProperty(DataObject dataObject, Property ocKey) { |
| Object value = unsetOCPropsMap.get(dataObject); |
| if (null == value) { |
| // create a new list and populate |
| List aList = new ArrayList(); |
| aList.add(ocKey); |
| unsetOCPropsMap.put(dataObject, aList); |
| } else { |
| // dont replace existing key (ie we readd a previously unset oc property) |
| if (!((List)value).contains(ocKey)) { |
| ((List)value).add(ocKey); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Delete an open content property from the list of unset oc properties keyed on dataObject |
| * @param dataObject |
| * @param ocKey |
| */ |
| public void removeUnsetOCProperty(DataObject dataObject, Property ocKey) { |
| Object value = unsetOCPropsMap.get(dataObject); |
| if (value != null) { |
| // if we referenced the property - check if it is the only one left in the map value |
| if (((List)value).remove(ocKey)) { |
| if(!(((List)value).size() > 0)) { |
| unsetOCPropsMap.remove(dataObject); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Clears the List of {@link #getChangedDataObjects changes} and turns change logging on. |
| * No operation occurs if logging is already on. |
| * @see #endLogging |
| * @see #isLogging |
| */ |
| @Override |
| public void beginLogging() { |
| if (!logging) { |
| logging = true; |
| loggingMapping = true; |
| resetChanges(); |
| rootDataObject.resetChanges(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Turn both logging flags back on. |
| */ |
| public void resumeLogging() { |
| if (!logging) { |
| logging = true; |
| loggingMapping = true; |
| } |
| } |
| |
| /** |
| * An implementation that requires logging may throw an UnsupportedOperationException. |
| * Turns change logging off. No operation occurs if logging is already off. |
| * @see #beginLogging |
| * @see #isLogging |
| */ |
| @Override |
| public void endLogging() { |
| logging = false; |
| loggingMapping = false; |
| //October 12, 1007 - as per the spec do not clear anything on Changesummary on endLogging |
| } |
| |
| /** |
| * INTERNAL: |
| * Turn both logging flags on. |
| */ |
| public void pauseLogging() { |
| logging = false; |
| loggingMapping = false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Called from beginLogging and undoChanges |
| */ |
| private void resetChanges() { |
| createdList.clear(); |
| deletedList.clear(); |
| // See spec. p.30 "List of changed DataObjects cleared" |
| oldSettings.clear(); |
| deepCopies.clear(); |
| oldContainer.clear(); |
| oldContainmentProperty.clear(); |
| unsetPropsMap.clear(); |
| unsetOCPropsMap.clear(); |
| originalValueStores.clear(); |
| originalElements.clear(); |
| reverseDeletedMap.clear(); |
| getOldSequences().clear(); |
| getOriginalSequences().clear(); |
| } |
| |
| /** |
| * Returns the ChangeSummary root DataObject - the object from which |
| * changes are tracked. |
| * When a DataGraph is used, this is the same as getDataGraph().getRootObject(). |
| * @return the ChangeSummary root DataObject |
| */ |
| @Override |
| public SDODataObject getRootObject() { |
| return rootDataObject; |
| } |
| |
| /** |
| * Returns a {@link ChangeSummary.Setting setting} for the specified property |
| * representing the property value of the given <code>dataObject</code> |
| * at the point when logging {@link #beginLogging() began}. |
| * <p>Returns null if the property was not modified and |
| * has not been {@link #isDeleted(DataObject) deleted}. |
| * @param dataObject the object in question. |
| * @param property the property of the object. |
| * @return the Setting for the specified property. |
| * @see #getChangedDataObjects |
| */ |
| @Override |
| public SDOChangeSummary.Setting getOldValue(DataObject dataObject, Property property) { |
| if ((dataObject == null) || (!isDeleted(dataObject) && ((((SDODataObject)dataObject).getChangeSummary() != null) && (((SDODataObject)dataObject).getChangeSummary() != this)))) { |
| return null; |
| } |
| |
| if (!isCreated(dataObject) && isDirty(dataObject)) { |
| return getOldValueForChangedDataObject(dataObject, (SDOProperty) property); |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * @param dataObject which is not null and not created and is dirty in the scope of this changesummary |
| * @param property |
| * @return new or already existing Setting |
| */ |
| private ChangeSummary.Setting getOldValueForChangedDataObject(DataObject dataObject, SDOProperty property) { |
| if ((null == property) || property.getType().isChangeSummaryType()) { |
| return null; |
| } |
| Setting setting = getPropertyInOldSettings(dataObject, property); |
| if (setting == null) { |
| SDODataObject sdoDataObject = ((SDODataObject)dataObject); |
| boolean isDeleted = isDeleted(dataObject); |
| Object oldValue = getPropertyInternal(sdoDataObject, property); |
| Object currentValue = sdoDataObject.getPropertyInternal(property); |
| boolean isSet = sdoDataObject.isSetInternal(property); |
| boolean wasSet = wasSet(sdoDataObject, property); |
| |
| if (property.isMany()) { |
| currentValue = ((ListWrapper)currentValue).getCurrentElements(); |
| if (isDirty(((ListWrapper)oldValue))) { |
| List elements = (List)getOriginalElements().get(oldValue); |
| oldValue = new ArrayList(elements); |
| |
| if (!property.getType().isDataType()) { |
| for (int i = 0; i < ((List)oldValue).size(); i++) { |
| Object next = ((List)oldValue).get(i); |
| |
| Object deepCopy = getOrCreateDeepCopy((DataObject)next); |
| ((List)oldValue).set(i, deepCopy); |
| } |
| } |
| } else { |
| oldValue = currentValue; |
| } |
| } |
| if (isDeleted || ((wasSet != isSet) || (oldValue != currentValue))) { |
| if ((oldValue != null) && !property.getType().isDataType()) { |
| if (oldValue instanceof DataObject) { |
| oldValue = getOrCreateDeepCopy((DataObject)oldValue); |
| } |
| } |
| setting = buildAndAddOldSetting((SDODataObject)dataObject, property, oldValue, wasSet); |
| } |
| } |
| return setting; |
| } |
| |
| /** |
| * INTERNAL: |
| * |
| * @param dataObject |
| * @param property |
| * @param value |
| * @param isSet |
| * @return |
| */ |
| private Setting buildAndAddOldSetting(SDODataObject dataObject, Property property, Object value, boolean isSet) { |
| SDOSetting setting = new SDOSetting(property, value); |
| setting.setIsSet(isSet); |
| |
| if (oldSettings.get(dataObject) == null) { |
| List aList = new ArrayList(); |
| aList.add(setting); |
| oldSettings.put(dataObject, aList); |
| } else { |
| List theList = (List)oldSettings.get(dataObject); |
| theList.add(setting); |
| } |
| |
| if (!setting.isSet()) { |
| List theList = (List)unsetPropsMap.get(dataObject); |
| if (theList == null) { |
| List aList = new ArrayList(); |
| aList.add(setting.getProperty().getName()); |
| unsetPropsMap.put(dataObject, aList); |
| } else { |
| if (!theList.contains(setting.getProperty().getName())) { |
| theList.add(setting.getProperty().getName()); |
| } |
| } |
| } |
| |
| return setting; |
| } |
| |
| /** |
| * Returns the value of the {@link DataObject#getContainer container} data object |
| * at the point when logging {@link #beginLogging() began}. |
| * @param dataObject the object in question. |
| * @return the old container data object. |
| */ |
| @Override |
| public SDODataObject getOldContainer(DataObject dataObject) { |
| return (SDODataObject) oldContainer.get(dataObject); |
| } |
| |
| /** |
| * Returns the value of the {@link DataObject#getContainmentProperty containment property} data object property |
| * at the point when logging {@link #beginLogging() began}. |
| * @param dataObject the object in question. |
| * @return the old containment property. |
| */ |
| @Override |
| public SDOProperty getOldContainmentProperty(DataObject dataObject) { |
| return (SDOProperty) oldContainmentProperty.get(dataObject); |
| } |
| |
| /** |
| * Returns the value of the {@link DataObject#getSequence sequence} for the data object |
| * at the point when logging {@link #beginLogging() began}. |
| * @param dataObject the object in question. |
| * @return the old containment property. |
| */ |
| @Override |
| public SDOSequence getOldSequence(DataObject dataObject) { |
| if ((dataObject == null) || (!isDeleted(dataObject) && ((((SDODataObject)dataObject).getChangeSummary() != null) && (((SDODataObject)dataObject).getChangeSummary() != this)))) { |
| return null; |
| } |
| if (!isCreated(dataObject) && dataObject.getType().isSequenced()) { |
| // check cache first |
| if (getOldSequences().containsKey(dataObject)) { |
| return (SDOSequence) getOldSequences().get(dataObject); |
| } |
| |
| // no sequence - get from the original sequence map |
| SDOSequence originalSeq = (SDOSequence) getOriginalSequences().get(dataObject); |
| if (originalSeq == null) { |
| originalSeq = (SDOSequence) dataObject.getSequence(); |
| } |
| |
| SDOSequence seqWithDeepCopies = new SDOSequence((SDODataObject) dataObject); |
| for (int i = 0; i < originalSeq.size(); i++) { |
| // setting/value may be null in some cases |
| Object nextOriginalSettingValue = originalSeq.getValue(i); |
| if (nextOriginalSettingValue == null) { |
| continue; |
| } |
| |
| // property may be null if the setting contains unstructured text |
| SDOProperty nextOriginalSettingProp = originalSeq.getProperty(i); |
| if (nextOriginalSettingProp == null) { |
| // handle unstructured text |
| seqWithDeepCopies.addText(nextOriginalSettingValue.toString()); |
| } else if (nextOriginalSettingProp.getType().isDataType()) { |
| // handle simple types |
| seqWithDeepCopies.addSettingWithoutModifyingDataObject(nextOriginalSettingProp, nextOriginalSettingValue, false); |
| } else { |
| // handle complex types |
| seqWithDeepCopies.addSettingWithoutModifyingDataObject(nextOriginalSettingProp, getOrCreateDeepCopy((DataObject) nextOriginalSettingValue), false); |
| } |
| } |
| // store deep copy of old sequence in cache |
| getOldSequences().put(dataObject, seqWithDeepCopies); |
| return seqWithDeepCopies; |
| } |
| return null; |
| } |
| |
| /** |
| * This method is intended for use by service implementations only. |
| * Undoes all changes in the log to restore the tree of |
| * DataObjects to its original state when logging began. |
| * isLogging() is unchanged. The log is cleared. |
| * @see #beginLogging |
| * @see #endLogging |
| * @see #isLogging |
| */ |
| @Override |
| public void undoChanges() { |
| |
| /** |
| * See Jira SDO-109/107 and 125/225 for open issues with move optimization |
| * See bug#5882923 for smart local undo via set()/unset() |
| */ |
| Property oldProp = getOldContainmentProperty(rootDataObject); |
| String oldName = null; |
| if (oldProp != null) { |
| oldName = oldProp.getName(); |
| } |
| rootDataObject.undoChanges(true, this, getOldContainer(rootDataObject), oldName); |
| resetChanges(); |
| rootDataObject.resetChanges(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the root DataObject for this ChangeSummary. |
| * @param dataObject the root of DataObject tree this ChangeSummary belongs to |
| */ |
| public void setRootDataObject(DataObject dataObject) { |
| rootDataObject = (SDODataObject)dataObject; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used by CopyHelper to set logging when creating a copy of a changesummary |
| * @param logging logging status |
| */ |
| public void setLogging(boolean logging) { |
| if (logging) { |
| beginLogging(); |
| } else { |
| endLogging(); |
| } |
| |
| this.loggingMapping = logging; |
| } |
| |
| /** |
| * INTERNAL: |
| * Check if a property is in its DataObject's oldsetting list |
| * @param dataObject property's owner |
| * @param property property to be checked |
| * @return property's Setting if this property is in list |
| */ |
| private ChangeSummary.Setting getPropertyInOldSettings(DataObject dataObject, Property property) { |
| Iterator iterOldSettings = null; |
| List aList = (List)oldSettings.get(dataObject); |
| if (aList != null) { |
| iterOldSettings = aList.iterator(); |
| ChangeSummary.Setting curSetting; |
| while (iterOldSettings.hasNext()) { |
| curSetting = (ChangeSummary.Setting)iterOldSettings.next(); |
| if (curSetting.getProperty().equals(property)) { |
| return curSetting; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the helperContext if the default SDOChangeSummary constructor was used |
| * @param helperContext |
| */ |
| public void setHelperContext(HelperContext helperContext) { |
| aHelperContext = helperContext; |
| } |
| |
| /** |
| * INTERNAL: |
| * |
| * @param createdXPathsList |
| */ |
| public void setCreatedXPaths(List createdXPathsList) { |
| createdXPaths = createdXPathsList; |
| } |
| |
| /** |
| * INTERNAL: |
| * |
| * @return |
| */ |
| public List getCreatedXPaths() { |
| return createdXPaths; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the logging state during mapping operations |
| * @return logging state |
| */ |
| public boolean isLoggingMapping() { |
| return loggingMapping; |
| } |
| |
| /** |
| * INTERNAL: |
| * |
| * @param modifiedDomsList |
| */ |
| public void setModifiedDoms(List modifiedDomsList) { |
| modifiedDoms = modifiedDomsList; |
| } |
| |
| /** |
| * INTERNAL: |
| * |
| * @return |
| */ |
| public List getModifiedDoms() { |
| return modifiedDoms; |
| } |
| |
| /** |
| * INTERNAL: |
| * The deletedXPaths field is picked up reflectively during marshal/unmarshal operations. |
| * @param deletedXPathsList |
| */ |
| public void setDeletedXPaths(List deletedXPathsList) { |
| deletedXPaths = deletedXPathsList; |
| } |
| |
| public Map getOldContainmentProperty() { |
| return oldContainmentProperty; |
| } |
| |
| public Map getOldContainer() { |
| return oldContainer; |
| } |
| |
| /** |
| * INTERNAL: |
| * |
| * @param dataObject |
| * @param property |
| * @return |
| */ |
| public boolean wasSet(DataObject dataObject, Property property) { |
| ValueStore vs = (ValueStore)originalValueStores.get(dataObject); |
| if (null == vs) { |
| vs = ((SDODataObject)dataObject)._getCurrentValueStore(); |
| } |
| if (property.isOpenContent()) { |
| return vs.isSetOpenContentProperty(property); |
| } else { |
| return vs.isSetDeclaredProperty(((SDOProperty)property).getIndexInType()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * |
| * @param dataObject |
| * @param property |
| * @return |
| */ |
| public Object getPropertyInternal(DataObject dataObject, Property property) { |
| ValueStore vs = (ValueStore)originalValueStores.get(dataObject); |
| |
| if (null == vs) { |
| vs = ((SDODataObject)dataObject)._getCurrentValueStore(); |
| } |
| |
| if (property.isOpenContent()) { |
| return vs.getOpenContentProperty(property); |
| } else { |
| return vs.getDeclaredProperty(((SDOProperty)property).getIndexInType()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * |
| * @param dataObject |
| * @param property |
| * @param value |
| */ |
| public void setPropertyInternal(DataObject dataObject, Property property, Object value) { |
| ValueStore vs = (ValueStore)originalValueStores.get(dataObject); |
| if (property.isOpenContent()) { |
| vs.setOpenContentProperty(property, value); |
| } else { |
| vs.setDeclaredProperty(((SDOProperty)property).getIndexInType(), value); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the map of original ValueStores keyed on |
| * @return |
| */ |
| public Map getOriginalValueStores() { |
| return originalValueStores; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return whether the <code>dataObject</code> has been modified. |
| * @param dataObject |
| * @return |
| */ |
| public boolean isDirty(DataObject dataObject) { |
| ValueStore vs = (ValueStore)originalValueStores.get(dataObject); |
| return vs != null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return whether the <code>aListWrapper</code> has been modified. |
| * @param aListWrapper |
| * @return |
| */ |
| public boolean isDirty(ListWrapper aListWrapper) { |
| Object originalList = getOriginalElements().get(aListWrapper); |
| return originalList != null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return whether the <code>aSequence</code> has been modified. |
| * @param aSequence |
| * @return |
| */ |
| public boolean isDirty(SDOSequence aSequence) { |
| Object originalSequence = getOriginalSequences().get(aSequence.getDataObject()); |
| return originalSequence != null; |
| } |
| |
| /** |
| * INTERNAL: |
| * |
| * @param dataObject |
| * @param property |
| */ |
| public void unsetPropertyInternal(DataObject dataObject, Property property) { |
| ValueStore vs = (ValueStore)originalValueStores.get(dataObject); |
| if (property.isMany()) { |
| ListWrapper currentValue = (ListWrapper)dataObject.getList(property); |
| originalElements.put(currentValue, new ArrayList()); |
| if (property.isOpenContent()) { |
| vs.unsetOpenContentProperty(property); |
| } else { |
| vs.unsetDeclaredProperty(((SDOProperty)property).getIndexInType()); |
| } |
| } else { |
| if (property.isOpenContent()) { |
| vs.unsetOpenContentProperty(property); |
| } else { |
| vs.unsetDeclaredProperty(((SDOProperty)property).getIndexInType()); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * @return Map of original elements, key and value are both listwrappers |
| */ |
| public Map getOriginalElements() { |
| return originalElements; |
| } |
| |
| /** |
| * INTERNAL: |
| * @param original |
| * @return |
| */ |
| private DataObject getOrCreateDeepCopy(DataObject original) { |
| DataObject value = (DataObject)getDeepCopies().get(original); |
| if (null == value) { |
| DataObject undoneCopy = ((SDOCopyHelper)aHelperContext.getCopyHelper()).copy(original, this); |
| |
| // if original is null we will create only 1 key:value null:null pair |
| getDeepCopies().put(original, undoneCopy); |
| getReverseDeletedMap().put(undoneCopy, original); |
| return undoneCopy; |
| } else { |
| return value; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * @return Map of deep copies of DataObjects key is original dataobject |
| */ |
| public Map getDeepCopies() { |
| return deepCopies; |
| } |
| |
| /** |
| * INTERNAL: |
| * @return Map of deep copies of DataObjects key is copy of dataobject |
| */ |
| public Map getReverseDeletedMap() { |
| return reverseDeletedMap; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return a map of original sequences keyed on DataObject. |
| * @return Map of old Sequences |
| */ |
| public Map getOriginalSequences() { |
| if (null == originalSequences) { |
| originalSequences = new HashMap(); |
| } |
| return originalSequences; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return a map of original sequences keyed on DataObject (cached values). |
| * @return Map of old Sequences that have deep copies of all DataObjects |
| */ |
| public Map getOldSequences() { |
| if (null == oldSequences) { |
| oldSequences = new HashMap(); |
| } |
| return oldSequences; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the string representation of the receiver. |
| */ |
| @Override |
| public String toString() { |
| StringBuffer aBuffer = new StringBuffer(); |
| aBuffer.append("ChangeSummary@"); |
| aBuffer.append(getClass().hashCode()); |
| aBuffer.append(" [logging: "); |
| aBuffer.append(logging); |
| aBuffer.append(", root: "); |
| aBuffer.append(rootDataObject); |
| List aList = getChangedDataObjects(); |
| if (aList != null) { |
| aBuffer.append(", "); |
| aBuffer.append(aList.size()); |
| aBuffer.append(" changes: <"); |
| boolean first = true; |
| for (Iterator i = aList.iterator(); i.hasNext();) { |
| if (first) { |
| first = false; |
| } else { |
| aBuffer.append(", "); |
| } |
| aBuffer.append(i.next());// null values are handled |
| } |
| } |
| aBuffer.append(">]"); |
| return aBuffer.toString(); |
| } |
| |
| } |