/**
*
* 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.helper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.stream.XMLStreamReader;
import org.apache.tuscany.sdo.SimpleAnyTypeDataObject;
import org.apache.tuscany.sdo.util.DataObjectUtil;
import org.apache.tuscany.sdo.util.SDOUtil;
import org.apache.tuscany.sdo.util.resource.SDOXMLResourceImpl;
import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.ResourceSet;
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.xmi.XMLResource;
import org.eclipse.emf.ecore.xml.type.XMLTypePackage;
import org.xml.sax.InputSource;
import commonj.sdo.DataObject;
import commonj.sdo.Type;
import commonj.sdo.helper.XMLDocument;
/**
* Represents an XML Document containing a tree of DataObjects.
*
* An example XMLDocument fragment is:
* <?xml version="1.0"?>
* <purchaseOrder orderDate="1999-10-20">
*
* created from this XML Schema fragment:
* <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
* <xsd:element name="purchaseOrder" type="PurchaseOrderType"/>
* <xsd:complexType name="PurchaseOrderType">
*
* Upon loading this XMLDocument:
* DataObject is an instance of Type PurchaseOrderType.
* RootElementURI is null because the XSD has no targetNamespace URI.
* RootElementName is purchaseOrder.
* Encoding is null because the document did not specify an encoding.
* XMLDeclaration is true because the document contained an XML declaration.
* XMLVersion is 1.0
* SchemaLocation and noNamespaceSchemaLocation are null because they are
* not specified in the document.
*
* When saving the root element, if the type of the root dataObject is not the
* type of global element specified by rootElementURI and rootElementName,
* or if a global element does not exist for rootElementURI and rootElementName,
* then an xsi:type declaration is written to record the root DataObject's Type.
*
* When loading the root element and an xsi:type declaration is found
* it is used as the type of the root DataObject. In this case,
* if validation is not being performed, it is not an error if the
* rootElementName is not a global element.
*/
public class XMLDocumentImpl implements XMLDocument
{
protected ExtendedMetaData extendedMetaData;
protected EObject rootObject;
protected XMLResource resource;
protected EStructuralFeature rootElement;
protected EObject documentRoot;
protected final static String WHITESPACE_REGEX = "\\s";
//TODO clean up the options thing
protected XMLDocumentImpl(ExtendedMetaData extendedMetaData, Object options)
{
this.extendedMetaData = extendedMetaData;
ResourceSet resourceSet = DataObjectUtil.createResourceSet();
if (options instanceof Map)
{
Class resourceFactoryClass = (Class)((Map)options).get("GENERATED_LOADER");
if (resourceFactoryClass != null)
{
try
{
Object resourceFactory = resourceFactoryClass.newInstance();
resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("*", resourceFactory);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
resource = (XMLResource)resourceSet.createResource(URI.createURI("http:///temp.xml"));
DataObjectUtil.configureXMLResource(resource, extendedMetaData);
}
protected XMLDocumentImpl(ExtendedMetaData extendedMetaData)
{
this(extendedMetaData, null);
}
protected XMLDocumentImpl(ExtendedMetaData extendedMetaData, DataObject dataObject, String rootElementURI, String rootElementName)
{
this(extendedMetaData);
rootObject = (EObject)dataObject;
rootElement = extendedMetaData.getElement(rootElementURI, rootElementName);
if (rootElement == null)
{
rootElement = ExtendedMetaData.INSTANCE.demandFeature(rootElementURI, rootElementName, true);
}
EClass documentRootClass = rootElement.getEContainingClass();
documentRoot = EcoreUtil.create(documentRootClass);
resource.getContents().add(documentRoot);
}
protected void save(OutputStream outputStream, Object options) throws IOException
{
EObject oldContainer = null;
EReference oldContainmentReference = null;
int oldContainmentIndex = -1;
if (documentRoot != null)
{
//TODO also check if rootObject is directly contained in a resource
oldContainer = rootObject.eContainer();
if (oldContainer != null)
{
oldContainmentReference = rootObject.eContainmentFeature();
}
if (oldContainer != documentRoot || oldContainmentReference != rootElement)
{
if (oldContainmentReference != null && oldContainmentReference.isMany())
{
oldContainmentIndex = ((List)oldContainer.eGet(oldContainmentReference)).indexOf(rootObject);
}
Object rootValue =
rootElement instanceof EAttribute && rootObject instanceof SimpleAnyTypeDataObject ?
((SimpleAnyTypeDataObject)rootObject).getValue() : rootObject;
documentRoot.eSet(rootElement, rootValue);
}
}
resource.save(outputStream, (Map)options);
if (oldContainer != null)
{
if (oldContainer != documentRoot || oldContainmentReference != rootElement)
{
if (oldContainmentReference.isMany())
{
((List)oldContainer.eGet(oldContainmentReference)).add(oldContainmentIndex, rootObject);
}
else
{
oldContainer.eSet(oldContainmentReference, rootObject);
}
}
}
else if (documentRoot != null)
{
documentRoot.eSet(rootElement, null);
}
}
protected void save(Writer outputWriter, Object options) throws IOException
{
// TODO temporary brute-force implementation ... to be replaced
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
save(outputStream, options);
outputWriter.write(new String(outputStream.toByteArray()));
}
protected void load(InputStream inputStream, String locationURI, Object options) throws IOException
{
InputSource inputSource = new InputSource(inputStream);
load(inputSource, locationURI, options);
}
protected void load(Reader inputReader, String locationURI, Object options) throws IOException
{
InputSource inputSource = new InputSource(inputReader);
load(inputSource, locationURI, options);
}
protected void load(XMLStreamReader reader) throws IOException
{
((SDOXMLResourceImpl)resource).load(reader, null);
initLoadedRoot();
}
protected void load(InputSource inputSource, String locationURI, Object options) throws IOException
{
if (locationURI != null)
{
inputSource.setSystemId(locationURI);
resource.setURI(URI.createURI(locationURI));
}
resource.load(inputSource, (Map)options);
initLoadedRoot();
}
private void initLoadedRoot()
{
rootObject = null;
rootElement = null;
documentRoot = null;
if (!resource.getContents().isEmpty())
{
documentRoot = (EObject)resource.getContents().get(0);
EClass documentRootClass = documentRoot.eClass();
if ("".equals(extendedMetaData.getName(documentRootClass))) //TODO efficient way to check this? Maybe DataObject.getContainer should also check this?
{
FeatureMap featureMap = (FeatureMap)documentRoot.eGet(documentRootClass.getEStructuralFeature(0)); // get mixed feature
int size = featureMap.size();
for (int index = 0; index < size; index++)
{
EStructuralFeature feature = featureMap.getEStructuralFeature(index);
boolean isText =
feature == XMLTypePackage.Literals.XML_TYPE_DOCUMENT_ROOT__TEXT ||
feature == XMLTypePackage.Literals.XML_TYPE_DOCUMENT_ROOT__CDATA ||
feature == XMLTypePackage.Literals.XML_TYPE_DOCUMENT_ROOT__COMMENT;
if (!isText)
{
if (feature instanceof EReference)
{
rootObject = (EObject)featureMap.getValue(index);
documentRoot.eUnset(feature);
}
else //EAttribute
{
rootObject = (EObject)SDOUtil.createDataTypeWrapper((Type)feature.getEType(), featureMap.getValue(index));
}
rootElement = feature;
break;
}
} //for
}
else
{
rootObject = documentRoot;
documentRoot = null;
}
}
}
public DataObject getRootObject()
{
return (DataObject)rootObject;
}
public String getRootElementURI()
{
if (rootElement != null)
{
return extendedMetaData.getNamespace(rootElement);
}
else if (rootObject != null)
{
return extendedMetaData.getNamespace(rootObject.eClass());
}
return null;
}
public String getRootElementName()
{
if (rootElement != null)
{
return extendedMetaData.getName(rootElement);
}
else if (rootObject != null)
{
return extendedMetaData.getName(rootObject.eClass());
}
return null;
}
public String getEncoding()
{
return resource.getEncoding();
}
public void setEncoding(String encoding)
{
resource.setEncoding(encoding);
}
public boolean isXMLDeclaration()
{
return Boolean.FALSE.equals(resource.getDefaultSaveOptions().get(XMLResource.OPTION_DECLARE_XML));
}
public void setXMLDeclaration(boolean xmlDeclaration)
{
resource.getDefaultSaveOptions().put(XMLResource.OPTION_DECLARE_XML, xmlDeclaration ? Boolean.TRUE : Boolean.FALSE);
}
public String getXMLVersion()
{
return resource.getXMLVersion();
}
public void setXMLVersion(String xmlVersion)
{
resource.setXMLVersion(xmlVersion);
}
/**
* @return an EMap containing the schema locations or null when no map
*/
protected EMap getSchemaLocationMap()
{
EMap result = null;
if ((documentRoot != null) && (extendedMetaData != null))
{
EReference xsiSchemaLocationMapFeature = extendedMetaData
.getXSISchemaLocationMapFeature(documentRoot.eClass());
if (xsiSchemaLocationMapFeature != null)
{
result = (EMap) documentRoot.eGet(xsiSchemaLocationMapFeature);
}
}
return result;
}
/**
* @param value
* from schema location map.
* @return string form of URI from provided value, deresolved if appropriate.
*/
protected String deresolve(String value)
{
URI location = URI.createURI(value);
URI resourceURI = resource.getURI();
boolean shouldDeresolve = resourceURI != null && !resourceURI.isRelative()
&& resourceURI.isHierarchical();
if (shouldDeresolve && !location.isRelative())
{
URI deresolvedURI = location.deresolve(resourceURI, true, true, false);
if (deresolvedURI.hasRelativePath())
{
location = deresolvedURI;
}
}
return location.toString();
}
/**
* @param value
* for schema location from input parameter.
* @return string form of URI from provided value, resolved if appropriate.
*/
protected String resolve(String value)
{
URI location = URI.createURI(value);
URI resourceURI = resource.getURI();
boolean shouldResolve = resourceURI != null && resourceURI.isHierarchical()
&& !resourceURI.isRelative();
if (shouldResolve && location.isRelative() && location.hasRelativePath())
{
location = location.resolve(resourceURI, false);
}
return location.toString();
}
public String getSchemaLocation()
{
EMap xsiSchemaLocationMap = getSchemaLocationMap();
if (xsiSchemaLocationMap != null)
{
if (!xsiSchemaLocationMap.isEmpty())
{
StringBuffer xsiSchemaLocation = new StringBuffer();
for (Iterator i = xsiSchemaLocationMap.entrySet().iterator(); i
.hasNext();)
{
Map.Entry entry = (Map.Entry) i.next();
String namespace = (String) entry.getKey();
if (namespace != null)
{
if (xsiSchemaLocation.length() > 0)
{
xsiSchemaLocation.append(' ');
}
xsiSchemaLocation.append(namespace);
xsiSchemaLocation.append(' ');
String value = entry.getValue().toString();
xsiSchemaLocation.append(deresolve(value));
}
}
return xsiSchemaLocation.toString().equals("") ? null
: xsiSchemaLocation.toString();
}
}
return null;
}
public void setSchemaLocation(String schemaLocation)
{
EMap xsiSchemaLocationMap = getSchemaLocationMap();
if (xsiSchemaLocationMap != null)
{
// only remove the entries from xsiSchemaLocationMap that contain a
// non-null key
for (Iterator i = xsiSchemaLocationMap.entrySet().iterator(); i.hasNext();)
{
Map.Entry entry = (Map.Entry) i.next();
if (entry.getKey() != null)
{
i.remove();
}
}
if (xsiSchemaLocationMap.size() == 0)
{
resource.getDefaultSaveOptions().put(
XMLResource.OPTION_SCHEMA_LOCATION, Boolean.FALSE);
}
if (schemaLocation != null)
{
String[] values = schemaLocation.split(WHITESPACE_REGEX);
for (int i = 0; i < values.length; i++) // note: also incremented in
// loop
{
String key = values[i++];
if (i < values.length)
{
xsiSchemaLocationMap.put(key, resolve(values[i]));
}
}
if (xsiSchemaLocationMap.size() != 0)
{
resource.getDefaultSaveOptions().put(
XMLResource.OPTION_SCHEMA_LOCATION, Boolean.TRUE);
}
}
}
}
public String getNoNamespaceSchemaLocation()
{
EMap xsiSchemaLocationMap = getSchemaLocationMap();
if (xsiSchemaLocationMap != null)
{
StringBuffer xsiSchemaLocation = new StringBuffer();
if (!xsiSchemaLocationMap.isEmpty())
{
Object valueObject = xsiSchemaLocationMap.get(null);
if (valueObject != null)
{
String valueString = (String) valueObject;
String[] values = valueString.split(WHITESPACE_REGEX);
for (int i = 0; i < values.length; i++)
{
if (xsiSchemaLocation.length() > 0)
{
xsiSchemaLocation.append(' ');
}
xsiSchemaLocation.append(deresolve(values[i]));
}
}
String result = xsiSchemaLocation.toString();
return result.equals("") ? null : result;
}
}
return null;
}
public void setNoNamespaceSchemaLocation(String schemaLocation)
{
EMap xsiSchemaLocationMap = getSchemaLocationMap();
if (xsiSchemaLocationMap != null)
{
// only remove the entries from xsiSchemaLocationMap that contain a null
// key
xsiSchemaLocationMap.removeKey(null);
if (xsiSchemaLocationMap.size() == 0)
{
resource.getDefaultSaveOptions().put(
XMLResource.OPTION_SCHEMA_LOCATION, Boolean.FALSE);
}
if (schemaLocation != null)
{
String[] values = schemaLocation.split(WHITESPACE_REGEX);
for (int i = 0; i < values.length; i++)
{
xsiSchemaLocationMap.put(null, resolve(values[i]));
}
if (xsiSchemaLocationMap.size() != 0)
{
resource.getDefaultSaveOptions().put(
XMLResource.OPTION_SCHEMA_LOCATION, Boolean.TRUE);
}
}
}
}
}