/**
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.tuscany.sdo.util.resource;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import org.apache.tuscany.sdo.SDOPackage;
import org.apache.tuscany.sdo.helper.SDOAnnotations;
import org.apache.tuscany.sdo.helper.XSDHelperImpl;
import org.apache.tuscany.sdo.impl.ChangeSummaryImpl;
import org.apache.tuscany.sdo.model.ModelFactory;
import org.apache.tuscany.sdo.model.impl.ModelFactoryImpl;
import org.apache.tuscany.sdo.util.SDOUtil;
import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.change.ChangeDescription;
import org.eclipse.emf.ecore.change.ChangeKind;
import org.eclipse.emf.ecore.change.FeatureChange;
import org.eclipse.emf.ecore.change.ListChange;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.ExtendedMetaData;
import org.eclipse.emf.ecore.util.FeatureMap;
import org.eclipse.emf.ecore.util.FeatureMapUtil;
import org.eclipse.emf.ecore.xmi.XMLResource;
import commonj.sdo.ChangeSummary;
import commonj.sdo.DataObject;
import commonj.sdo.Property;
import commonj.sdo.helper.XSDHelper;
/**
* ChangeSummary StAX Serializer whose output conforms to the SDO Java/C++/PHP specifications. The instance isn't thread-safe, however it's safe to
* use the instance any times on the same thread.
*/
public class ChangeSummaryStreamSerializer {
private XMLStreamWriter writer;
private String writeNamespace(String prefix, String nameSpace) throws XMLStreamException {
writer.writeNamespace(prefix, nameSpace);
writer.setPrefix(prefix, nameSpace);
return prefix;
}
private int nsPrefixSuffix;
private String prefix(String nameSpace, String preference) throws XMLStreamException {
if (preference != null) {
String bound = writer.getNamespaceContext().getNamespaceURI(preference);
if (bound == null) {
String prefix = writer.getPrefix(nameSpace);
return prefix == null ? writeNamespace(preference, nameSpace) : prefix/* or null */;
}
if (bound.equals(nameSpace))
return preference;
}
Object automaticNsPrefix = writer.getProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES);
if (automaticNsPrefix != null && automaticNsPrefix.getClass() == Boolean.class // faster than instanceof since Boolean is final
&& ((Boolean) automaticNsPrefix).booleanValue())
return null;
String prefix = writer.getPrefix(nameSpace);
if (prefix != null)
return prefix; // or null
NamespaceContext nameSpaces = writer.getNamespaceContext();
do
prefix = "CS" + nsPrefixSuffix++;
while (nameSpaces.getNamespaceURI(prefix) != null);
return writeNamespace(prefix, nameSpace);
}
void writeGlobalAttribute(String prefix, String nameSpace, String name, String value) throws XMLStreamException {
prefix = prefix(nameSpace, prefix);
if (prefix == null)
writer.writeAttribute(nameSpace, name, value);
else
writer.writeAttribute(prefix, nameSpace, name, value);
}
XSDHelper xsdHelper;
protected final void writeAttribute(Property property, String value) throws XMLStreamException {
String name = xsdHelper.getLocalName(property), nameSpace = xsdHelper.getNamespaceURI(property);
// TODO "" for no-NameSpace global attribute
if (nameSpace == null)
writer.writeAttribute(name, value);
else
writeGlobalAttribute(null, nameSpace, name, value);
}
private String lineBreak, indent, margin;
private int nest;
private void breakLine() throws XMLStreamException {
writer.writeCharacters(lineBreak);
if (margin != null)
writer.writeCharacters(margin);
if (indent != null)
for (int count = nest; count != 0; --count)
writer.writeCharacters(indent);
}
private Map options;
static private final String STRING_OPTION = "String option";
void startElement() throws XMLStreamException {
if (options == null)
return;
if (lineBreak == STRING_OPTION)
lineBreak = (String) options.get(SDOUtil.XML_SAVE_LineBreak);
if (lineBreak == null)
return;
if (margin == STRING_OPTION)
margin = (String) options.get(SDOUtil.XML_SAVE_MARGIN);
if (indent == STRING_OPTION)
indent = (String) options.get(SDOUtil.XML_SAVE_INDENT);
breakLine();
}
void writeStartElement(String prefix, String nameSpace, String name) throws XMLStreamException {
startElement();
if (nameSpace == null)
writer.writeStartElement(name);
else {
prefix = prefix(nameSpace, prefix);
if (prefix == null)
writer.writeStartElement(nameSpace, name);
else
writer.writeStartElement(prefix, name, nameSpace);
}
}
void writeStartElement(Property property) throws XMLStreamException {
++nest;
writeStartElement(null, xsdHelper.getNamespaceURI(property),// TODO "" for no-NameSpace global element
xsdHelper.getLocalName(property));
}
static protected final String CREATE_ATTRIBUTE = "create", DELETE_ATTRIBUTE = "delete", LOGGING_ATTRIBUTE = "logging", REF_ATTRIBUTE = "ref", UNSET = "unset";
private StringBuffer step(String nameSpace, String name, StringBuffer path) throws XMLStreamException {
if (nameSpace != null) {
nameSpace = writer.getPrefix(nameSpace);
if (nameSpace != null && nameSpace.length() != 0)
return path.append(nameSpace).append(':').append(name); // *:name[namespace-uri()='nameSpace']
}
return path.append(name);
}
private StringBuffer step(Property containmentProperty, StringBuffer path) throws XMLStreamException {
return step(xsdHelper.getNamespaceURI(containmentProperty),// TODO "" for no-NameSpace global element
xsdHelper.getLocalName(containmentProperty), path);
}
private StringBuffer step(Property containmentProperty) throws XMLStreamException {
return step(containmentProperty, new StringBuffer());
}
private DataObject dataObject;
private StringBuffer step(Object container) throws XMLStreamException {
Property containmentProperty = dataObject.getContainmentProperty();
StringBuffer step = step(containmentProperty);
if (containmentProperty.isMany() || ((EObject) dataObject).eContainingFeature() != containmentProperty)
step.append('[').append(((DataObject) container).getList(containmentProperty).indexOf(dataObject) + 1).append(']');
return step;
}
private String pathRootObject;
private DataObject rootObject;
private EObject container(EObject object) {
final EObject container = object.eContainer();
if (!(container instanceof DataObject))
return null;
String name = extendedMetaData.getName(container.eClass());
return name != null && name.length() == 0 // DocumentRoot
? null : container;
}
private String path() throws XMLStreamException {
if (pathRootObject == STRING_OPTION)
pathRootObject = options == null ? null : (String) options.get(OPTION_RootObject_PATH);
if (pathRootObject != null && dataObject == rootObject)
return null;
EObject container = container((EObject) dataObject);
if (container == null)
return null;
StringBuffer step = step(container);
while (true) {
String path = step.toString();
dataObject = (DataObject) container;
if (pathRootObject != null && container == rootObject)
return path;
container = container(container);
if (container == null)
return path;
step = step(container).append('/').append(path);
}
}
/*
* not to support DataGraph 3-1 private org.eclipse.emf.ecore.resource.Resource rootResource;
*/
protected String rootElementNS;
String rootElementName;
ExtendedMetaData extendedMetaData;
protected final String rootElementName() {
if (rootElementNS != null)
return rootElementName;
QName rootElement = (QName) options.get(OPTION_ROOT_ELEMENT);
if (rootElement != null) {
rootElementNS = rootElement.getNamespaceURI();
return rootElementName = rootElement.getLocalPart();
}
EStructuralFeature element = ((EObject) rootObject).eContainingFeature();
if (element == null) {
rootElementNS = "";
return rootElementName = "descendant-or-self::node()";
}
rootElementNS = extendedMetaData.getNamespace(element);
if (rootElementNS == null)
rootElementNS = "";
return rootElementName = extendedMetaData.getName(element);
}
String ref() throws XMLStreamException {
/*
* not to support DataGraph 3-2 if (rootResource != null) return rootResource.getURIFragment((EObject) dataObject);
*/
String id = EcoreUtil.getID((EObject) dataObject);
if (id != null)
return id;
id = path();
if (pathRootObject == null)
return id == null ? "#/" + rootElementName() // descendant-or-self::node()
: "#//" + id;
return id == null ? pathRootObject/* + "."*/ : pathRootObject + id;
}
void writeRef(String ref) throws XMLStreamException {
writer.writeAttribute(SDOAnnotations.COMMONJ_SDO_NS, REF_ATTRIBUTE, ref);
}
void writeRef() throws XMLStreamException {
writeRef(ref());
}
void writeEndElement(String lineBreak) throws XMLStreamException {
if (lineBreak != null)
breakLine();
writer.writeEndElement();
--nest;
}
private StringBuffer pathDeleted/* = null */;
private Collection modifiedDataObjects;
private int lengthDeleted;
private String changeSummaryElementNS, changeSummaryElementName;
private ChangeSummary changeSummary;
protected boolean skipDeletedModification(DataObject modifiedDataObject) {
return changeSummary.isDeleted(modifiedDataObject);
}
String refDeleted() throws XMLStreamException {
String id = EcoreUtil.getID((EObject) dataObject);
if (id != null)
return id;
id = path(); // "dataObject" is updated too!!
DataObject deletedDataObject = dataObject;
/*
* construct "#//...changeSummary/"
* output: pathDeleted
*/
if (lengthDeleted == -1) {
String path = pathRootObject == null ? "#//" : pathRootObject;
if (pathDeleted == null)
pathDeleted = new StringBuffer(path);
else
pathDeleted.replace(0, pathDeleted.length(), path);
dataObject = rootObject;
path = path();
if (path != null)
pathDeleted.append(path).append('/');
step(changeSummaryElementNS, changeSummaryElementName, pathDeleted).append('/');
lengthDeleted = pathDeleted.length();
} else
pathDeleted.delete(lengthDeleted, pathDeleted.length());
dataObject = changeSummary.getOldContainer(deletedDataObject);
Property containmentProperty = dataObject.getContainmentProperty();
String name = containmentProperty == null ? rootElementName() : xsdHelper.getLocalName(containmentProperty);
int index = 1;
for (Iterator iterator = modifiedDataObjects.iterator(); iterator.hasNext();) {
DataObject modifiedDataObject = (DataObject) iterator.next();
if (skipDeletedModification(modifiedDataObject))
continue;
if (modifiedDataObject == dataObject)
break;
Property property = modifiedDataObject.getContainmentProperty();
if (property == containmentProperty || name.equals(property == null ? rootElementName() : xsdHelper.getLocalName(property)))
++index;
}
pathDeleted/*.append("*:")*/.append(name).append('[').append(index).append("]/");
containmentProperty = changeSummary.getOldContainmentProperty(deletedDataObject);
// assert containmentProperty != null;
step(containmentProperty, pathDeleted);
Object f;
if (containmentProperty.isMany()
|| (f = extendedMetaData.getAffiliation(((EObject) dataObject).eClass(), (EStructuralFeature) containmentProperty)) != null
&& f != containmentProperty)
pathDeleted.append('[').append(
((List) changeSummary.getOldValue(dataObject, containmentProperty).getValue()).indexOf(deletedDataObject) + 1).append(']');
if (id != null)
pathDeleted.append('/').append(id);
return pathDeleted.toString();
}
static String convertToString(Property property, Object value) {
return EcoreUtil.convertToString((EDataType) property.getType(), value);
}
void writeRefDeleted() throws XMLStreamException {
writeRef(refDeleted());
}
protected final void writeDeletedObject(Property property) throws XMLStreamException {
++nest;
startElement();
--nest;
DataObject oldDataObject = ((ChangeSummaryImpl)changeSummary).getOldDataObject(dataObject);
XMLStreamReader reader = new DataObjectXMLStreamReader(property, oldDataObject, null, xsdHelper);
new XMLStreamSerializer().serialize(new XMLDocumentStreamReader(reader), writer);
}
static public final Object ChangeSummary_TYPE = ((ModelFactoryImpl) ModelFactory.INSTANCE).getChangeSummaryType();
Collection deletedDataObjects;
protected final void writeElement(Object value, Property property) throws XMLStreamException {
if (value == null) {
writeStartElement(property);
writeGlobalAttribute(ExtendedMetaData.XSI_PREFIX, ExtendedMetaData.XSI_URI, XMLResource.NIL, "true");
writeEndElement(null);
} else if (value instanceof DataObject) {
dataObject = (DataObject) value;
if (!changeSummary.isDeleted(dataObject)) {
writeStartElement(property);
writeRef();
writeEndElement(null);
} else if (property.isContainment() && deletedDataObjects.contains(dataObject)) {
writeDeletedObject(property);
} else {
writeStartElement(property);
writeRefDeleted();
writeEndElement(null);
}
} else {
Object type = property.getType();
if (type == ChangeSummary_TYPE)
return;
writeStartElement(property);
writer.writeCharacters(EcoreUtil.convertToString((EDataType) type, value));
writeEndElement(null);
}
}
protected final void writeElement(Object value) throws XMLStreamException {
FeatureMap.Entry entry = (FeatureMap.Entry) value;
writeElement(entry.getValue(), (Property)entry.getEStructuralFeature());
}
static protected List optimize(List values, Object featureChange, int size) {
int fromIndex = size, toIndex = 0;
for (Iterator changes = ((FeatureChange) featureChange).getListChanges().iterator(); changes.hasNext();) {
ListChange change = (ListChange) changes.next();
Object kind = change.getKind();
if (kind == ChangeKind.MOVE_LITERAL)
return values;
int index = change.getIndex();
if (kind == ChangeKind.ADD_LITERAL) {
if (index == 0) {
fromIndex = 0;
if (toIndex == 0)
toIndex = 1;
} else {
int to = index;
if (--index < fromIndex)
fromIndex = index;
if (++to > toIndex)
toIndex = to;
else if (to < toIndex)
++toIndex;
}
++size;
} else {
--size;
if (index < fromIndex)
fromIndex = index;
if (index < toIndex)
--toIndex;
else if (index > toIndex && index != size)
toIndex = index;
}
}
return values.subList(fromIndex, toIndex);
}
static protected final Object CHANGE_SUMMARY = SDOPackage.eINSTANCE.getChangeSummary();
/**
* Root Object path String such as "#", etc. Absent/null is the default (automatic computation)
*/
static public final String OPTION_RootObject_PATH = "RootObject path",
/**
* Boolean to optimize sequence/list/array. Absent/null/Boolean.FALSE is the default (no optimization)
*/
OPTION_OPTIMIZE_LIST = "optimize sequence/list/array",
/**
* Element QName if the changeSummary Root Object is a XML root; the NameSpace can be empty, never null; the prefix is ignored.
* Absent/null is the default (automatic computation from DocumentRoot if any)
*/
OPTION_ROOT_ELEMENT = "Root Element";
/**
* Exports ChangeSummary
*
* @param changeSummary
* Never null
* @param changeSummaryElement
* changeSummary element; the NameSpace can be empty if no NameSpace, or null if local element; the prefix can be null(no preference)
* @param writer
* Never null
* @param options
* {@link SDOUtil#XML_SAVE_LineBreak} (absence/null is the default i.e. no Line Breaking), {@link SDOUtil#XML_SAVE_INDENT} (absence/null is the default i.e. no indentation), {@link SDOUtil#XML_SAVE_MARGIN}, {@link #OPTION_RootObject_PATH}, {@link #OPTION_OPTIMIZE_LIST} and XMLResource.OPTION_EXTENDED_META_DATA; can be null or empty
*/
public final void saveChangeSummary(ChangeSummary changeSummary, QName changeSummaryElement, XMLStreamWriter writer, Map options)
throws XMLStreamException {
/*
* 6-1. Group created, deleted and modified DataObjects
* input: changeSummary output: createdDataObjects, deletedDataObjects & modifiedDataObjects
* implement: careful if compute from changeSummary.getChangedDataObjects() since it also includes children of deleted objects (thank Frank)
*/
if (changeSummary.isLogging())
((ChangeSummaryImpl) changeSummary).summarize();
ChangeDescription changeDescription = (ChangeDescription) changeSummary;
Iterator createdDataObjects = changeDescription.getObjectsToDetach().iterator();
deletedDataObjects = changeDescription.getObjectsToAttach();
EMap objectChanges = changeDescription.getObjectChanges();
modifiedDataObjects = objectChanges.keySet(); // may contain DO(s) from createdDataObjects and/or deletedDataObjects
/*
* 6-2. Prepare to compute (X)Path
* input: changeSummary
* output: rootResource
*/
/*not to support DataGraph 3-3
Object dataGraph = changeSummary.getDataGraph();
if (dataGraph == null) {
DataObject rootObject = changeSummary.getRootObject();
// assert rootObject != null;
rootResource = rootObject.getContainer() == null ? ((EObject) rootObject).eResource() // Can be null since this *StAX* writer does NOT
// require rootObject contained by an *EMF* Resource
: null; // eResource() direct content may not necessarily always be the XML document
} else
// assert dataGraph instanceof DataGraphImpl;
rootResource = ((org.apache.tuscany.sdo.impl.DataGraphImpl) dataGraph).getRootResource(); */
/*
* 6-2. Start ChangeSummary element
* input: writer, options, elementCS, changeSummary & changeDescription (6-1)
*/
nsPrefixSuffix = 0;
this.writer = writer;
this.options = options;
lineBreak = "";
indent = margin = pathRootObject = STRING_OPTION;
nest = 0;
changeSummaryElementNS = changeSummaryElement.getNamespaceURI();
changeSummaryElementName = changeSummaryElement.getLocalPart();
writeStartElement(changeSummaryElement.getPrefix(), changeSummaryElementNS, changeSummaryElementName);
lineBreak = STRING_OPTION;
rootObject = changeSummary.getRootObject();
extendedMetaData = (ExtendedMetaData) options.get(XMLResource.OPTION_EXTENDED_META_DATA);
if (extendedMetaData == null)
{
extendedMetaData = ExtendedMetaData.INSTANCE;
xsdHelper = XSDHelper.INSTANCE;
}
else
xsdHelper = new XSDHelperImpl(extendedMetaData, null);
Property declaration = changeSummaryElementNS == null
? rootObject.getType().getProperty(changeSummaryElementName)
: xsdHelper.getGlobalProperty(changeSummaryElementNS, changeSummaryElementName, true);
if (declaration != null)
{
EClassifier type = changeDescription.eClass();
if (type != declaration.getType() && type != CHANGE_SUMMARY)
writeGlobalAttribute(ExtendedMetaData.XSI_PREFIX, ExtendedMetaData.XSI_URI, XMLResource.TYPE, new StringBuffer(prefix(extendedMetaData.getNamespace(type), null))
.append(':').append(extendedMetaData.getName(type)).toString());
}
/*
* 6-3. "create" attribute
* input: createdDataObjects (6-1), rootResource (6-2), changeSummary & writer
*/
rootElementNS = null;
this.changeSummary = changeSummary;
if (createdDataObjects.hasNext()) {
StringBuffer buffer = new StringBuffer();
while (true) {
dataObject = (DataObject) createdDataObjects.next();
buffer.append(ref());
if (!createdDataObjects.hasNext())
break;
buffer.append(' ');
}
writer.writeAttribute(CREATE_ATTRIBUTE, buffer.toString());
}
/*
* 6-4. "delete" attribute
* input: deletedDataObjects (6-1), modifiedDataObjects (6-1) & writer
*/
Iterator iterator = deletedDataObjects.iterator();
if (iterator.hasNext()) {
lengthDeleted = -1;
StringBuffer buffer = null;
do {
dataObject = (DataObject) iterator.next();
if (skipDeletedModification(changeSummary.getOldContainer(dataObject)))
continue;
if (buffer == null)
buffer = new StringBuffer();
else
buffer.append(' ');
buffer.append(refDeleted());
} while (iterator.hasNext());
writer.writeAttribute(DELETE_ATTRIBUTE, buffer.toString());
}
/*
* 6-5. "logging" attribute
* input: changeSummary & writer
*/
writer.writeAttribute(LOGGING_ATTRIBUTE, changeSummary.isLogging() ? "true" : "false");
/*
* 6-6. Modified DataObjects
* input: modifiedDataObjects (6-1), rootResource (6-2), changeSummary & writer
*/
iterator = modifiedDataObjects.iterator();
if (iterator.hasNext()) {
boolean optimizeList;
if (options == null)
optimizeList = false;
else
{
Object option = options.get(OPTION_OPTIMIZE_LIST);
optimizeList = option == null ? false : ((Boolean)option).booleanValue();
}
prefix(SDOAnnotations.COMMONJ_SDO_NS, SDOPackage.eNS_PREFIX);
do {
DataObject dataObject = (DataObject) iterator.next();
if (skipDeletedModification(dataObject))
continue;
Property containmentProperty = dataObject.getContainmentProperty();
if (containmentProperty == null) {
++nest;
startElement();
rootElementName();
writer.writeStartElement(rootElementNS, rootElementName);
} else
writeStartElement(containmentProperty);
this.dataObject = dataObject;
writeRef();
String lineBreak = null;
Collection oldValues = (Collection) objectChanges.get(dataObject); // changeSummary.getOldValues repeats Sequence changes
Iterator settings = oldValues.iterator();
if (settings.hasNext()) {
do {
ChangeSummary.Setting oldValue = (ChangeSummary.Setting) settings.next();
if (oldValue.isSet())
continue;
StringBuffer unset = step(oldValue.getProperty());
while (settings.hasNext()) {
oldValue = (ChangeSummary.Setting) settings.next();
if (!oldValue.isSet())
step(oldValue.getProperty(), unset.append(' '));
}
writer.writeAttribute(SDOAnnotations.COMMONJ_SDO_NS, UNSET, unset.toString());
break;
} while (settings.hasNext());
for (settings = oldValues.iterator(); settings.hasNext();) {
ChangeSummary.Setting oldValue = (ChangeSummary.Setting) settings.next();
Property property = oldValue.getProperty();
if (oldValue.isSet() && xsdHelper.isAttribute(property))
// assert ! property.isMany();
writeAttribute(property, convertToString(property, oldValue.getValue()));
}
for (settings = oldValues.iterator(); settings.hasNext();) {
ChangeSummary.Setting oldValue = (ChangeSummary.Setting) settings.next();
Property property = oldValue.getProperty();
if (!xsdHelper.isAttribute(property))
if (property.isMany()) {
Object value = oldValue.getValue();
List list = (List) value;
if (FeatureMapUtil.isFeatureMap((EStructuralFeature) property)) {
if (optimizeList)
list = optimize(list, oldValue, dataObject.getSequence(property).size());
Iterator values = list.iterator();
if (!values.hasNext())
continue;
do
writeElement(values.next());
while (values.hasNext());
} else {
if (optimizeList)
list = optimize(list, oldValue, dataObject.getList(property).size());
Iterator values = list.iterator();
if (!values.hasNext())
continue;
do
writeElement(values.next(), property);
while (values.hasNext());
}
lineBreak = this.lineBreak;
} else if (oldValue.isSet()) {
Object value = oldValue.getValue();
if (value instanceof FeatureMap.Entry)
writeElement(value);
else
writeElement(value, property);
lineBreak = this.lineBreak;
}
}
}
writeEndElement(lineBreak);
} while (iterator.hasNext());
writeEndElement(lineBreak);
} else
writeEndElement(null);
writer.flush();
}
}