org.openmdx.application.xml.spi.ImportHandler Maven / Gradle / Ivy
/*
* ====================================================================
* Project: openMDX/Core, http://www.openmdx.org/
* Description: Import Handler
* Owner: OMEX AG, Switzerland, http://www.omex.ch
* ====================================================================
*
* This software is published under the BSD license as listed below.
*
* Copyright (c) 2004-2014, OMEX AG, Switzerland
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of the openMDX team nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* ------------------
*
* This product includes software developed by other organizations as
* listed in the NOTICE file.
*/
package org.openmdx.application.xml.spi;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.logging.Level;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.parsers.DocumentBuilderFactory;
import org.openmdx.base.exception.ServiceException;
import org.openmdx.base.mof.cci.Multiplicity;
import org.openmdx.base.naming.Path;
import org.openmdx.base.rest.cci.ObjectRecord;
import org.openmdx.base.rest.spi.Facades;
import org.openmdx.base.rest.spi.Object_2Facade;
import org.openmdx.base.text.conversion.Base64;
import org.openmdx.kernel.exception.BasicException;
import org.openmdx.kernel.exception.Throwables;
import org.openmdx.kernel.loading.Resources;
import org.openmdx.kernel.log.SysLog;
import org.w3c.spi2.Datatypes;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Import Handler
*/
public class ImportHandler extends DefaultHandler {
/**
* Constructor
*
* @param target
* @param documentURL
* @param defaultImportMode
*/
public ImportHandler(
ImportTarget target,
InputSource source,
ImportMode defaultImportMode
) {
this.target = target;
this.url = getDocumentURL(source);
this.binary = isBinary(source);
this.defaultImportMode = defaultImportMode;
}
/**
* Be aware, SimpleDateFormat
instances are not thread safe.
*/
private final SimpleDateFormat localSecondFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
/**
* Be aware, SimpleDateFormat
instances are not thread safe.
*/
private final SimpleDateFormat localMillisecondFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
// stack of objects currently open
private Stack objectStack = null;
// Path length of the request collection's first element
private int pendingAt = -1;
/**
* set of XML elements which denote object elements, i.e. elements with a
* qualifier This set is required to determine on endElement operations
* whether an attribute/ struct/reference is closed or an object.
*/
private Map> objectElements = null;
// state/context of current object
private ObjectRecord currentObject = null;
/**
* The current object's operation
*/
private ImportMode currentObjectOperation;
private Path currentPath = null;
private StringBuilder currentAttributeValue = null;
private String currentAttributeName = null;
private String currentLocalpartObject = null;
private int nextTemporaryId = -1;
private int currentAttributeOffset = 0;
private int currentAttributePosition = -1;
private String currentAttributeKey = null;
private Multiplicity currentAttributeMultiplicity = null;
private String currentAttributeOperation = null;
// true when the last call was endElement(). Is set to false by
// startElement()
private boolean previousElementEnded = true;
private final ImportTarget target;
private final URL url;
private final ImportMode defaultImportMode;
/**
* true
in case of a WBXML input
*/
private final boolean binary;
// cached type definitions (global in classloader)
private static final Set loadedSchemas = new HashSet();
// attributes types as (, attributeTypes = new HashMap();
// attribute multiplicities
private static final Map attributeMultiplicities = new HashMap();
// qualifier names of a complex schema type.
private static final Map qualifierNames = new HashMap();
private static final List cachedParentPaths = new ArrayList();
/**
* Suffix marking date/time values as UTC based
*/
private static String[] UTC_IDS = {
"Z", // canonical form
"+00", "-00",
"+00:00", "-00:00"
};
private static final String LEGACY_RESOURCE_RPEFIX = "xri:+resource/";
private static final String STANDARD_RESOURCE_PREFIX = "xri://+resource/";
private static final String FALLBACK_ATTRIBUTE_TYPE = "org.w3c.string";
/**
* Determine the document URL
*
* @param source
* the InputSource
*
* @return the document URL
*/
private static URL getDocumentURL(
InputSource source
) {
String uri = source.getSystemId();
if (uri == null) {
return null;
} else {
try {
return new URL(uri);
} catch (MalformedURLException exception) {
try {
return new File(uri).toURI().toURL();
} catch (MalformedURLException exception1) {
return null;
}
}
}
}
/**
* Determine the document is binary
*
* @param source
* the InputSource
*
* @return true
in case of WBXML input
*/
private static boolean isBinary(
InputSource source
) {
if (source.getCharacterStream() != null) {
return false;
}
String uri = source.getSystemId();
if (uri != null) {
uri = uri.toLowerCase();
if (uri.endsWith(".wbxml")) {
return true;
}
if (uri.endsWith(".xml")) {
return false;
}
}
String encoding = source.getEncoding();
return encoding == null;
}
/**
* Tells whether we are reading an XML or a WBXML source
*
* @return true
in case of a WBXML source
*/
public boolean isBinary() {
return this.binary;
}
// -----------------------------------------------------------------------
// Implements EntityResolver
// -----------------------------------------------------------------------
/*
* (non-Javadoc)
*
* @see org.xml.sax.helpers.DefaultHandler#resolveEntity(java.lang.String, java.lang.String)
*/
@Override
public InputSource resolveEntity(
String publicId,
String systemId
)
throws SAXException {
return this.getSchemaInputSource(systemId);
}
/**
* Initialize qualifierNames, attributeTypes, attributeMultiplicities
*
* @param schemaDocument
*
* @throws ServiceException
*/
private void initTypes(
org.w3c.dom.Document schemaDocument
)
throws ServiceException {
org.w3c.dom.Element docElement = schemaDocument.getDocumentElement();
org.w3c.dom.NodeList complexTypeNodes = docElement.getElementsByTagName("xsd:complexType");
// iterate all xsd:complexType
int complexTypeNodesLength = complexTypeNodes.getLength();
for (int i = 0; i < complexTypeNodesLength; i++) {
org.w3c.dom.Node complexType = complexTypeNodes.item(i);
org.w3c.dom.NamedNodeMap complexTypeAttributes = complexType.getAttributes();
org.w3c.dom.Attr complexTypeName = (org.w3c.dom.Attr) complexTypeAttributes.getNamedItem("name");
if (complexTypeName != null) {
// get qualifierName of complex type
org.w3c.dom.NodeList attributeNodes = ((org.w3c.dom.Element) complexType).getElementsByTagName("xsd:attribute");
int attributeNodesLength = attributeNodes.getLength();
for (int j = 0; j < attributeNodesLength; j++) {
org.w3c.dom.Node attributeNode = attributeNodes.item(j);
if ("_qualifier".equals(attributeNode.getAttributes().getNamedItem("name").getNodeValue())) {
ImportHandler.qualifierNames.put(
complexTypeName
.getNodeValue(), attributeNode
.getAttributes()
.getNamedItem("fixed")
.getNodeValue()
);
}
}
// get qualifierTypes, qualifierMultiplicities
org.w3c.dom.NodeList elementNodes = ((org.w3c.dom.Element) complexType).getElementsByTagName("xsd:element");
int elementNodesLength = elementNodes.getLength();
for (int j = 0; j < elementNodesLength; j++) {
org.w3c.dom.Node element = elementNodes.item(j);
org.w3c.dom.NamedNodeMap elementAttributes = element.getAttributes();
org.w3c.dom.Attr attributeName = (org.w3c.dom.Attr) elementAttributes.getNamedItem("name");
org.w3c.dom.Attr attributeType = (org.w3c.dom.Attr) elementAttributes.getNamedItem("type");
// default multiplicity Multiplicities.SINGLE_VALUE
if (attributeName != null &&
!"_content".equals(attributeName.getNodeValue()) &&
!"_object".equals(attributeName.getNodeValue()) &&
!"_item".equals(attributeName.getNodeValue()) &&
attributeName.getNodeValue().indexOf('.') < 0) {
String attributeTypeName = null;
// multi-valued attribute defined as complexType
if (attributeType == null) {
// find xsd:attribute name=""_multiplicity"" of
// complexType
org.w3c.dom.NodeList l0 = ((org.w3c.dom.Element) element).getElementsByTagName("xsd:complexType");
for (int i0 = 0; i0 < l0.getLength(); i0++) {
org.w3c.dom.Node n0 = l0.item(i0);
org.w3c.dom.NodeList l1 = ((org.w3c.dom.Element) n0).getElementsByTagName("xsd:attribute");
for (int i1 = 0; i1 < l1.getLength(); i1++) {
org.w3c.dom.Node n1 = l1.item(i1);
if ("_multiplicity".equals(n1.getAttributes().getNamedItem("name").getNodeValue())) {
ImportHandler.attributeMultiplicities.put(
complexTypeName.getNodeValue() + ":" + attributeName.getNodeValue(),
n1.getAttributes().getNamedItem("fixed").getNodeValue()
);
}
}
}
// xsd:extension base=
org.w3c.dom.Node extension = element;
while (extension != null &&
!"xsd:extension".equals(extension.getNodeName())) {
org.w3c.dom.NodeList children = extension.getChildNodes();
extension = null;
for (int k = 0; k < children.getLength(); k++) {
if (children.item(k).getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
extension = children.item(k);
break;
}
}
}
if (extension != null) {
org.w3c.dom.NamedNodeMap extensionAttributes = extension.getAttributes();
org.w3c.dom.Attr extensionBase = (org.w3c.dom.Attr) extensionAttributes.getNamedItem("base");
if (extensionBase != null) {
attributeTypeName = extensionBase.getValue();
}
}
} else {
//
// A simple type is defined as:
//
//
// <xsd:element name="org.omg.model1.Element.annotation" type="org.w3c.string" minOccurs="0"/>
//
//
attributeTypeName = attributeType.getValue();
}
if (attributeTypeName == null) {
// SysLog.warning("type for attribute " +
// attributeName +
// " not defined. Assuming org:w3c:string");
attributeTypeName = FALLBACK_ATTRIBUTE_TYPE;
}
ImportHandler.attributeTypes.put(
complexTypeName.getNodeValue().replace('.', ':') + ":" + attributeName.getNodeValue(),
attributeTypeName
);
}
}
}
}
}
/**
* Load the meta data
*
* @param uriSchema
*
* @throws ServiceException
*/
private void loadMetaData(
String uriSchema
)
throws ServiceException {
if ("xri://+resource/org/omg/model1/xmi1/model1.xsd".equals(uriSchema)) {
Model1MetaData.amendAttributeTypes(attributeTypes);
Model1MetaData.amendAttributeMultiplicities(attributeMultiplicities);
Model1MetaData.amendQualifierNames(qualifierNames);
} else
try {
org.w3c.dom.Document schemaDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
this.getSchemaInputSource(uriSchema)
);
this.initTypes(schemaDocument);
} catch (Exception exception) {
throw new ServiceException(
exception,
BasicException.Code.DEFAULT_DOMAIN,
BasicException.Code.INVALID_CONFIGURATION,
"Could not retrieve meta data from schema",
new BasicException.Parameter("schema", uriSchema)
);
}
ImportHandler.loadedSchemas.add(uriSchema);
}
/**
* Retrieve the meta data if necessary
*
* @param _uriSchema
* @throws ServiceException
*/
private void retrieveMetaData(
String uriSchema
)
throws SAXException {
try {
if (uriSchema.indexOf("/xmi/") > 0)
throw new ServiceException(
BasicException.Code.DEFAULT_DOMAIN,
BasicException.Code.NOT_SUPPORTED,
"\"/xmi/\" directory no longer supported",
new BasicException.Parameter("uri", uriSchema)
);
synchronized (ImportHandler.loadedSchemas) {
if (!ImportHandler.loadedSchemas.contains(uriSchema)) {
loadMetaData(uriSchema);
}
}
} catch (ServiceException exception) {
throw new SAXException(exception.log());
}
}
// ------------------------------------------------------------------------
// Implements ContentHandler
// ------------------------------------------------------------------------
/*
* (non-Javadoc)
*
* @see org.xml.sax.helpers.DefaultHandler#startDocument()
*/
@Override
public void startDocument()
throws SAXException {
this.currentPath = new Path("");
this.objectStack = new Stack();
this.objectElements = new HashMap>();
this.currentObject = null;
this.currentAttributeValue = null;
this.previousElementEnded = true;
}
/**
* Start an XML element. Start and end look like: ... embedded
* elements
*/
@Override
public void startElement(
String uri,
String localpart,
String rawname,
Attributes attributes
)
throws SAXException {
this.previousElementEnded = false;
this.currentAttributeValue = null;
// check whether to load schema
for (int i = 0; i < attributes.getLength(); i++) {
if ("noNamespaceSchemaLocation".equals(attributes.getLocalName(i))) {
this.retrieveMetaData(attributes.getValue(i));
}
}
List element = parseElement(localpart);
String qualifierName = ImportHandler.qualifierNames.get(localpart);
final Multiplicity attributeMultiplicity = getCurrentAttributeMultiplicity(localpart);
//
// element has a qualifier --> new object
//
if (qualifierName != null) {
try {
this.currentLocalpartObject = localpart;
this.objectStack.push(this.currentObject);
//
// Remember attribute values of element. These are: - localpart
// - - "_qualifier" (fixed) - "_operation"
// (optional)
//
Map attributeValues = new HashMap();
attributeValues.put("_qualifier", qualifierName);
for (int i = 0; i < attributes.getLength(); i++) {
String attributeName = attributes.getLocalName(i);
if (!"".equals(attributeName)
&& !"_qualifier".equals(attributeName)
&& !"noNamespaceSchemaLocation".equals(attributeName)
&& !"xsi".equals(attributeName)) {
attributeValues.put(
attributeName, attributes
.getValue(i)
);
}
}
if (this.currentPath.size() < 4) {
// "null" fix for Authority|Provider
attributeValues.put("_operation", "null");
} else if (attributeValues.size() < 3 ||
"".equals(attributeValues.get("_operation"))) {
// "set" is the default operation
attributeValues.put("_operation", this.defaultImportMode.name().toLowerCase());
}
String qualifier = attributeValues.get(qualifierName);
if (qualifier == null) {
throw new ServiceException(
BasicException.Code.DEFAULT_DOMAIN,
BasicException.Code.ASSERTION_FAILURE,
"required attribute " + qualifierName + " not found.",
new BasicException.Parameter("localpart", localpart),
new BasicException.Parameter(
"attributes",
attributeValues
)
);
}
this.objectElements.put(localpart, attributeValues);
this.currentPath = this.currentPath.getChild(
"".equals(qualifier) ? ":" + this.nextTemporaryId-- : qualifier
);
this.currentObject = Facades.newObject(
this.currentPath,
this.toNameComponent(element)
).getDelegate();
String operation = attributeValues.get("_operation");
if (!"null".equals(operation)) {
Path objectId = Object_2Facade.getPath(this.currentObject);
// An object and its sub-objects are processed as a single
// unit of work
if (this.pendingAt == -1) {
this.pendingAt = objectId.size();
}
if ("create".equals(operation)) {
this.currentObjectOperation = ImportMode.CREATE;
} else if ("set".equals(operation)) {
this.currentObjectOperation = ImportMode.SET;
} else if ("update".equals(operation)) {
this.currentObjectOperation = ImportMode.UPDATE;
} else if ("operation".equals(operation) ||
"remove".equals(operation)) {
//
// unsupported request
//
throw new ServiceException(
BasicException.Code.DEFAULT_DOMAIN,
BasicException.Code.NOT_SUPPORTED,
"No longer supported _operation argument",
new BasicException.Parameter(BasicException.Parameter.XRI, objectId),
new BasicException.Parameter("unsupported", "operation", "remove"),
new BasicException.Parameter("requested", operation)
);
} else {
//
// illegal request
//
throw new ServiceException(
BasicException.Code.DEFAULT_DOMAIN,
BasicException.Code.NOT_SUPPORTED,
"Unsupported _operation argument",
new BasicException.Parameter(BasicException.Parameter.XRI, objectId),
new BasicException.Parameter("supported", "", "null", "set", "create", "update"),
new BasicException.Parameter("requested", operation)
);
}
}
} catch (ServiceException e) {
throw new SAXException(e);
}
} else if ("_item".equals(localpart)) {
//
// <"_item"> start element of value of a multi-valued attribute
//
this.currentAttributeOperation = attributes.getValue("_operation");
final String _position = attributes.getValue("_position");
int position = _position == null ? 0 : Integer.parseInt(_position);
this.currentAttributePosition = position == -1 ? this.currentAttributePosition + 1 : position;
this.currentAttributeKey = attributes.getValue("_key");
} else if (attributeMultiplicity != null && attributeMultiplicity.isMultiValued()) {
// multi-valued attribute with attributes 'offset', 'multiplicity' last
// component of the element is the unqualified element name
this.currentAttributeName = localpart;
this.currentAttributeMultiplicity = attributeMultiplicity;
try {
if (attributeMultiplicity.isCollection()) {
// LIST, SET, SPARSEARRAY
this.currentPath = this.currentPath.getChild(
element.get(element.size() - 1)
);
this.currentAttributeOffset = attributes.getValue("_offset") == null ? 0
: Integer.parseInt(
attributes.getValue("_offset")
);
this.currentAttributePosition = -1;
Facades.asObject(this.currentObject).clearAttributeValuesAsList(this.currentAttributeName);
} else {
// MAP
String featureName = element.get(element.size() - 1);
this.currentPath = this.currentPath.getChild(featureName);
this.currentAttributeOffset = 0; // no offset for maps
this.currentAttributePosition = -1; // no position for maps
Facades.asObject(
this.currentObject
).attributeValuesAsMap(
this.currentAttributeName
).clear();
}
} catch (ServiceException exception) {
throw new SAXException(exception);
}
} else {
//
// no attribute --> no qualifier -> element is
// reference|attribute|struct last component of the element is the
// unqualified element name
//
if ("_object".equals(localpart)) {
this.currentAttributeName = null;
} else if ("_content".equals(localpart)) {
this.currentObject = null;
} else {
this.currentPath = currentPath.getChild(element.get(element.size() - 1));
this.currentAttributeName = localpart;
this.currentAttributeOffset = 0;
this.currentAttributePosition = -1;
}
}
}
private Multiplicity getCurrentAttributeMultiplicity(String localpart) {
final String multiplicityKey = this.currentLocalpartObject + ":" + localpart;
final String attributeMultiplicity = ImportHandler.attributeMultiplicities.get(
multiplicityKey
);
return attributeMultiplicity == null ? null : Multiplicity.parse(attributeMultiplicity);
}
/**
*
*/
@Override
public void characters(
char[] ch,
int offset,
int length
)
throws SAXException {
if (this.currentAttributeValue == null) {
this.currentAttributeValue = new StringBuilder();
}
this.currentAttributeValue.append(ch, offset, length);
}
/*
* (non-Javadoc)
*
* @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public void endElement(
String uri,
String localName,
String qName
)
throws SAXException {
try {
//
// The end element corresponds to an Object. Process the
// currentObject and get next from stack
//
if ("_object".equals(localName) ||
this.objectElements.get(localName) != null) {
if (this.currentObject != null) {
if (this.currentObjectOperation != null) {
this.target.importObject(this.currentObjectOperation, this.currentObject);
this.currentObjectOperation = null;
}
//
// If object was already closed by <"_object"> it can not be
// closed again by the object end tag. Skip endObject() in
// this case
//
try {
if (this.pendingAt == Object_2Facade.getPath(this.currentObject).size()) {
this.pendingAt = -1;
}
} catch (RuntimeException e) {
throw new SAXException(e);
}
this.currentObject = this.objectStack.pop();
}
this.currentLocalpartObject = null;
//
// In v3 format there are never pending objects.
//
if ("_object".equals(localName) &&
(this.currentObject != null || !this.objectStack.isEmpty())) {
throw new ServiceException(
BasicException.Code.DEFAULT_DOMAIN,
BasicException.Code.ASSERTION_FAILURE,
"pending object found. There must be no pending objects in v3 schema format.",
new BasicException.Parameter("object", this.currentObject)
);
}
}
// it is an end element of a Attribute|Reference|StructureType
else if (this.currentObject != null) {
/**
* store attribute value the attribute value between two closing
* elements is a sequence of blanks. Skip this value
*/
if (!this.previousElementEnded) {
Object_2Facade facade = Facades.asObject(this.currentObject);
// attribute name
String attributeName = this.currentPath.getLastSegment().toClassicRepresentation();
java.lang.Object value = getCurrentAttributeValue();
//
// Set values according to multiplicity and operation
//
boolean supported = true;
int absolutePosition = this.currentAttributeOffset + this.currentAttributePosition;
if (this.currentAttributeMultiplicity == null) {
// SPARSEARRAY
// In case of v2 format the multiplicity is not set
if (this.currentAttributeOperation == null ||
"".equals(this.currentAttributeOperation) ||
"set".equals(this.currentAttributeOperation)) {
if (facade.isTypeSafe()) {
if (facade.isCollection(attributeName)) {
if (absolutePosition < 0) {
Collection
© 2015 - 2024 Weber Informatics LLC | Privacy Policy