All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.cloudevents.xml.XMLDeserializer Maven / Gradle / Ivy

/*
 * Copyright 2018-Present The CloudEvents Authors
 * 

* Licensed 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 io.cloudevents.xml; import io.cloudevents.CloudEventData; import io.cloudevents.SpecVersion; import io.cloudevents.core.data.BytesCloudEventData; import io.cloudevents.rw.*; import io.cloudevents.types.Time; import org.w3c.dom.*; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.net.URI; import java.util.Base64; class XMLDeserializer implements CloudEventReader { private final Document xmlDocument; private final OccurrenceTracker ceAttributeTracker = new OccurrenceTracker(); XMLDeserializer(Document doc) { this.xmlDocument = doc; } // CloudEventReader ------------------------------------------------------- @Override public , R> R read( CloudEventWriterFactory writerFactory, CloudEventDataMapper mapper) throws CloudEventRWException { // Grab the Root and ensure it's what we expect. final Element root = xmlDocument.getDocumentElement(); checkValidRootElement(root); // Get the specversion and build the CE Writer String specVer = root.getAttribute("specversion"); if (specVer == null) { throw CloudEventRWException.newInvalidSpecVersion("null - Missing XML attribute"); } final SpecVersion specVersion = SpecVersion.parse(specVer); final CloudEventWriter writer = writerFactory.create(specVersion); // Now iterate through the elements NodeList nodes = root.getChildNodes(); Element dataElement = null; for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { Element e = (Element) node; // Sanity ensureValidContextAttribute(e); // Grab all the useful markers. final String attrName = e.getLocalName(); final String attrType = extractAttributeType(e); final String attrValue = e.getTextContent(); // Check if this is a Required or Optional attribute if (specVersion.getAllAttributes().contains(attrName)) { // Yep .. Just write it out. writer.withContextAttribute(attrName, attrValue); } else { if (XMLConstants.XML_DATA_ELEMENT.equals(attrName)) { // Just remember the data node for now. dataElement = e; } else { // Handle the extension attributes switch (attrType) { case XMLConstants.CE_ATTR_STRING: writer.withContextAttribute(attrName, attrValue); break; case XMLConstants.CE_ATTR_INTEGER: writer.withContextAttribute(attrName, Integer.valueOf(attrValue)); break; case XMLConstants.CE_ATTR_TIMESTAMP: writer.withContextAttribute(attrName, Time.parseTime(attrValue)); break; case XMLConstants.CE_ATTR_BOOLEAN: writer.withContextAttribute(attrName, Boolean.valueOf(attrValue)); break; case XMLConstants.CE_ATTR_URI: writer.withContextAttribute(attrName, URI.create(attrValue)); break; case XMLConstants.CE_ATTR_URI_REF: writer.withContextAttribute(attrName, URI.create(attrValue)); break; case XMLConstants.CE_ATTR_BINARY: writer.withContextAttribute(attrName, Base64.getDecoder().decode(attrValue)); break; } } } } } // And handle any data if (dataElement != null) { return writer.end(processData(dataElement)); } else { return writer.end(); } } // Private Methods -------------------------------------------------------- /** * Get the first child Element of an Element * * @param e * @return The first child, or NULL if there isn't one. */ private static Element findFirstElement(Element e) { NodeList nodeList = e.getChildNodes(); for (int i = 0; i < nodeList.getLength(); i++) { Node n = nodeList.item(i); if (n.getNodeType() == Node.ELEMENT_NODE) { return (Element) n; } } return null; } /** * Process the business event data of the XML Formatted * event. *

* This may result in an XML specific data wrapper being returned * depending on payload. * * @param data * @return {@link CloudEventData} The data wrapper. * @throws CloudEventRWException */ private CloudEventData processData(Element data) throws CloudEventRWException { CloudEventData retVal = null; final String attrType = extractAttributeType(data); // Process based on the defined `xsi:type` of the data element. switch (attrType) { case XMLConstants.CE_DATA_ATTR_TEXT: retVal = new TextCloudEventData(data.getTextContent()); break; case XMLConstants.CE_DATA_ATTR_BINARY: String eData = data.getTextContent(); retVal = BytesCloudEventData.wrap(Base64.getDecoder().decode(eData)); break; case XMLConstants.CE_DATA_ATTR_XML: try { // Ensure it's acceptable before we move forward. ensureValidDataElement(data); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); Document newDoc = dbf.newDocumentBuilder().newDocument(); Element eventData = findFirstElement(data); Element newRoot = newDoc.createElementNS(eventData.getNamespaceURI(), eventData.getLocalName()); newDoc.appendChild(newRoot); // Copy the children... NodeList nodesToCopy = eventData.getChildNodes(); for (int i = 0; i < nodesToCopy.getLength(); i++) { Node n = nodesToCopy.item(i); if (n.getNodeType() == Node.ELEMENT_NODE) { Node newNode = newDoc.importNode(n, true); newRoot.appendChild(newNode); } } newDoc.normalizeDocument(); retVal = XMLCloudEventData.wrap(newDoc); } catch (ParserConfigurationException e) { throw CloudEventRWException.newDataConversion(e, null, null); } break; default: // I don't believe this is reachable break; } return retVal; } /** * Ensure that the root element of the received XML document is valid * in our context. * * @param e The root Element * @throws CloudEventRWException */ private void checkValidRootElement(Element e) throws CloudEventRWException { // It must be the name we expect. if (!XMLConstants.XML_ROOT_ELEMENT.equals(e.getLocalName())) { throw CloudEventRWException.newInvalidDataType(e.getLocalName(), XMLConstants.XML_ROOT_ELEMENT); } // It must be in the CE namespace. if (!XMLConstants.CE_NAMESPACE.equalsIgnoreCase(e.getNamespaceURI())) { throw CloudEventRWException.newInvalidDataType(e.getNamespaceURI(), "Namespace: " + XMLConstants.CE_NAMESPACE); } } /** * Ensure the XML `data` element is well-formed. * * @param dataEl * @throws CloudEventRWException */ private void ensureValidDataElement(Element dataEl) throws CloudEventRWException { // It must have a single child final int childCount = XMLUtils.countOfChildElements(dataEl); if (childCount != 1) { throw CloudEventRWException.newInvalidDataType("data has " + childCount + " children", "1 expected"); } // And there must be a valid type discriminator final String xsiType = dataEl.getAttribute(XMLConstants.XSI_TYPE); if (xsiType == null) { throw CloudEventRWException.newInvalidDataType("NULL", "xsi:type oneOf [xs:base64Binary, xs:string, xs:any]"); } } /** * Ensure a CloudEvent context attribute representation is as expected. * * @param el * @throws CloudEventRWException */ private void ensureValidContextAttribute(Element el) throws CloudEventRWException { final String localName = el.getLocalName(); // It must be in our namespace if (!XMLConstants.CE_NAMESPACE.equals(el.getNamespaceURI())) { final String allowedTxt = el.getLocalName() + " Expected namespace: " + XMLConstants.CE_NAMESPACE; throw CloudEventRWException.newInvalidDataType(el.getNamespaceURI(), allowedTxt); } // It must be all lowercase if (!allLowerCase(localName)) { throw CloudEventRWException.newInvalidDataType(localName, " context atttribute names MUST be lowercase"); } // A bit of a kludge, not relevant for 'data' - should refactor if (!XMLConstants.XML_DATA_ELEMENT.equals(localName)) { // It must not have any children if (XMLUtils.countOfChildElements(el) != 0) { throw CloudEventRWException.newInvalidDataType(el.getLocalName(), "Unexpected child element(s)"); } } // Finally, ensure we only see each CE Attribute once... if ( ! ceAttributeTracker.trackOccurrence(localName)) { throw CloudEventRWException.newOther(localName + ": Attribute appeared more than once"); } } private String extractAttributeType(Element e) { final Attr a = e.getAttributeNodeNS(XMLConstants.XSI_NAMESPACE, "type"); if (a != null) { return a.getValue(); } else { return null; } } private boolean allLowerCase(String s) { if (s == null) { return false; } for (int i = 0; i < s.length(); i++) { if (Character.isUpperCase(s.charAt(i))) { return false; } } return true; } // DataWrapper Inner Classes public class TextCloudEventData implements CloudEventData { private final String text; TextCloudEventData(String text) { this.text = text; } @Override public byte[] toBytes() { return text.getBytes(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy