/******************************************************************************* * Copyright (c) 2013, 2015 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 v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Denise Smith - 2.6 - initial implementation ******************************************************************************/ package org.eclipse.persistence.internal.oxm.record.json; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.json.Json; import javax.json.JsonArray; import javax.json.JsonNumber; import javax.json.JsonException; import javax.json.JsonObject; import javax.json.JsonReader; import javax.json.JsonString; import javax.json.JsonStructure; import javax.json.JsonValue; import javax.json.JsonValue.ValueType; import javax.xml.namespace.QName; import org.eclipse.persistence.exceptions.XMLMarshalException; import org.eclipse.persistence.internal.oxm.CollectionGroupingElementNodeValue; import org.eclipse.persistence.internal.oxm.ConversionManager; import org.eclipse.persistence.internal.oxm.Constants; import org.eclipse.persistence.internal.oxm.ContainerValue; import org.eclipse.persistence.internal.oxm.MediaType; import org.eclipse.persistence.internal.oxm.NamespaceResolver; import org.eclipse.persistence.internal.oxm.NodeValue; import org.eclipse.persistence.internal.oxm.OXMSystemProperties; import org.eclipse.persistence.internal.oxm.Root; import org.eclipse.persistence.internal.oxm.Unmarshaller; import org.eclipse.persistence.internal.oxm.XPathFragment; import org.eclipse.persistence.internal.oxm.mappings.Field; import org.eclipse.persistence.internal.oxm.record.AbstractUnmarshalRecord; import org.eclipse.persistence.internal.oxm.record.SAXUnmarshallerHandler; import org.eclipse.persistence.internal.oxm.record.UnmarshalRecord; import org.eclipse.persistence.internal.oxm.XPathNode; import org.eclipse.persistence.internal.oxm.record.XMLReaderAdapter; import org.eclipse.persistence.internal.oxm.record.deferred.DeferredContentHandler; import org.eclipse.persistence.oxm.mappings.nullpolicy.AbstractNullPolicy; import org.eclipse.persistence.oxm.record.XMLRootRecord; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; public class JsonStructureReader extends XMLReaderAdapter { private static final String TRUE = "true"; private static final String FALSE = "false"; private String attributePrefix = null; private NamespaceResolver namespaces = null; private boolean includeRoot; private String textWrapper; private Class unmarshalClass; private boolean isInCollection; private JsonStructure jsonStructure; private JsonAttributes attributes = new JsonAttributes(); /** * If we should treat unqualified type property in JSON as MOXy type discriminator. */ private boolean jsonTypeCompatibility; public JsonStructureReader(Unmarshaller u) { this(u, null); } public JsonStructureReader(Unmarshaller u, Class clazz) { this.attributePrefix = u.getAttributePrefix(); if (Constants.EMPTY_STRING.equals(attributePrefix)) { attributePrefix = null; } namespaces = u.getNamespaceResolver(); setNamespaceAware(u.getNamespaceResolver() != null); setNamespaceSeparator(u.getNamespaceSeparator()); this.includeRoot = u.isIncludeRoot(); this.setErrorHandler(u.getErrorHandler()); this.textWrapper = u.getValueWrapper(); this.unmarshalClass = clazz; this.jsonTypeCompatibility = u.getJsonTypeConfiguration().useJsonTypeCompatibility(); } public void setJsonStructure(JsonStructure jsonStructure) { this.jsonStructure = jsonStructure; } @Override public void parse(InputSource input) throws IOException, SAXException, JsonException { if (input == null) { if (jsonStructure != null) { parseRoot(jsonStructure); } return; } try { InputStream inputStream = null; JsonReader jsonReader; if (null != input.getByteStream()) { inputStream = input.getByteStream(); jsonReader = Json.createReader(inputStream); } else if (null != input.getCharacterStream()) { jsonReader = Json.createReader(input.getCharacterStream()); } else { try { URL url = new URL(input.getSystemId()); inputStream = url.openStream(); } catch (MalformedURLException malformedURLException) { try { inputStream = new FileInputStream(input.getSystemId()); } catch (FileNotFoundException fileNotFoundException) { throw malformedURLException; } } jsonReader = Json.createReader(inputStream); } if (jsonReader != null) { JsonStructure structure = jsonReader.read(); parseRoot(structure); } if (null != inputStream) { inputStream.close(); } } catch (JsonException je) { throw XMLMarshalException.unmarshalException(je); } } @Override public void parse(String systemId) { try { parse(new InputSource(systemId)); } catch (IOException | SAXException e) { throw XMLMarshalException.unmarshalException(e); } } public void parseRoot(JsonValue jsonValue) throws SAXException { if (namespaces != null) { Map<String, String> namespacePairs = namespaces.getPrefixesToNamespaces(); for (Entry<String, String> namespacePair : namespacePairs.entrySet()) { contentHandler.startPrefixMapping(namespacePair.getKey(), namespacePair.getValue()); } } if (jsonValue.getValueType() == ValueType.OBJECT) { contentHandler.startDocument(); JsonObject jsonObject = (JsonObject) jsonValue; Set<Entry<String, JsonValue>> children = jsonObject.entrySet(); if (children.size() == 0 && unmarshalClass == null) { return; } Iterator<Entry<String, JsonValue>> iter = children.iterator(); if (includeRoot) { if (children.size() > 0) { Entry<String, JsonValue> nextEntry = iter.next(); parsePair(nextEntry.getKey(), nextEntry.getValue()); } } else { contentHandler.startElement(Constants.EMPTY_STRING, Constants.EMPTY_STRING, null, attributes.setValue(jsonValue, attributePrefix, namespaces, getNamespaceSeparator(), isNamespaceAware())); while (iter.hasNext()) { Entry<String, JsonValue> nextEntry = iter.next(); parsePair(nextEntry.getKey(), nextEntry.getValue()); } contentHandler.endElement(Constants.EMPTY_STRING, Constants.EMPTY_STRING, null); } contentHandler.endDocument(); } else if (jsonValue.getValueType() == ValueType.ARRAY) { SAXUnmarshallerHandler rootContentHandler = null; if (getContentHandler() instanceof SAXUnmarshallerHandler) { rootContentHandler = (SAXUnmarshallerHandler) getContentHandler(); } JsonArray jsonArray = (JsonArray) jsonValue; List<Object> list = new ArrayList<>(jsonArray.size()); for (JsonValue aJsonArray : jsonArray) { parseRoot(aJsonArray); if (getContentHandler() instanceof SAXUnmarshallerHandler) { SAXUnmarshallerHandler saxUnmarshallerHandler = (SAXUnmarshallerHandler) contentHandler; list.add(saxUnmarshallerHandler.getObject()); saxUnmarshallerHandler.setObject(null); } else if (getContentHandler() instanceof UnmarshalRecord) { UnmarshalRecord unmarshalRecord = (UnmarshalRecord) contentHandler; Object unmarshalledObject = unmarshalRecord.getCurrentObject(); if (includeRoot && unmarshalClass != null) { if (!(unmarshalledObject instanceof Root)) { Root xmlRoot = unmarshalRecord.createRoot(); xmlRoot.setNamespaceURI(unmarshalRecord.getRootElementNamespaceUri()); xmlRoot.setLocalName(unmarshalRecord.getLocalName()); xmlRoot.setObject(unmarshalledObject); unmarshalledObject = xmlRoot; } } list.add(unmarshalledObject); unmarshalRecord.setCurrentObject(null); unmarshalRecord.setRootElementName(null); unmarshalRecord.setLocalName(null); } } if (getContentHandler() instanceof SAXUnmarshallerHandler) { ((SAXUnmarshallerHandler) getContentHandler()).setObject(list); } else if (getContentHandler() instanceof UnmarshalRecord) { ((UnmarshalRecord) getContentHandler()).setCurrentObject(list); ((UnmarshalRecord) getContentHandler()).setRootElementName(Constants.EMPTY_STRING); ((UnmarshalRecord) getContentHandler()).setLocalName(Constants.EMPTY_STRING); if (rootContentHandler != null) { rootContentHandler.setObject(list); } } } else { getContentHandler().startDocument(); parseValue(jsonValue); } } private void parseValue(JsonValue jsonValue) throws SAXException { switch (jsonValue.getValueType()) { case STRING: { String string = ((JsonString) jsonValue).getString(); contentHandler.characters(string); break; } case FALSE: { contentHandler.characters(FALSE); break; } case TRUE: { contentHandler.characters(TRUE); break; } case NUMBER: { JsonNumber number = ((JsonNumber) jsonValue); contentHandler.characters(number.toString()); break; } case OBJECT: { for (Entry<String, JsonValue> nextEntry : ((JsonObject) jsonValue).entrySet()) { parsePair(nextEntry.getKey(), nextEntry.getValue()); } break; } case ARRAY: { for (JsonValue value : (JsonArray) jsonValue) { parseValue(value); } break; } case NULL: { break; // noop } default: throw new IllegalStateException("Unhandled valueType: " + jsonValue.getValueType()); } } private void parsePair(String name, JsonValue jsonValue) throws SAXException { if (jsonValue == null) { return; } ValueType valueType = jsonValue.getValueType(); if (valueType == ValueType.ARRAY) { JsonArray jsonArray = (JsonArray) jsonValue; String parentLocalName = name; if (attributePrefix != null && parentLocalName.startsWith(attributePrefix)) { // do nothing; return; } String uri = Constants.EMPTY_STRING; if (isNamespaceAware() && namespaces != null) { if (parentLocalName.length() > 2) { int nsIndex = parentLocalName.indexOf(getNamespaceSeparator(), 1); if (nsIndex > -1) { String prefix = parentLocalName.substring(0, nsIndex); uri = namespaces.resolveNamespacePrefix(prefix); } if (uri == null) { uri = namespaces.getDefaultNamespaceURI(); } else { parentLocalName = parentLocalName.substring(nsIndex + 1); } } else { uri = namespaces.getDefaultNamespaceURI(); } } boolean isTextValue; int arraySize = jsonArray.size(); if (arraySize == 0) { if (contentHandler instanceof UnmarshalRecord) { UnmarshalRecord ur = (UnmarshalRecord) contentHandler; XPathNode node = ur.getNonAttributeXPathNode(uri, parentLocalName, parentLocalName, null); if (node != null) { NodeValue nv = node.getNodeValue(); if (nv == null && node.getTextNode() != null) { nv = node.getTextNode().getUnmarshalNodeValue(); } if (nv != null && nv.isContainerValue()) { ur.getContainerInstance(((ContainerValue) nv)); } } } } startCollection(); XPathFragment groupingXPathFragment = null; XPathFragment itemXPathFragment = null; if (contentHandler instanceof UnmarshalRecord) { isTextValue = isTextValue(parentLocalName); UnmarshalRecord unmarshalRecord = (UnmarshalRecord) contentHandler; if (unmarshalRecord.getUnmarshaller().isWrapperAsCollectionName()) { XPathNode unmarshalRecordXPathNode = unmarshalRecord.getXPathNode(); if (null != unmarshalRecordXPathNode) { XPathFragment currentFragment = new XPathFragment(); currentFragment.setLocalName(parentLocalName); currentFragment.setNamespaceURI(uri); currentFragment.setNamespaceAware(isNamespaceAware()); XPathNode groupingXPathNode = unmarshalRecordXPathNode.getNonAttributeChildrenMap().get(currentFragment); if (groupingXPathNode != null) { if (groupingXPathNode.getUnmarshalNodeValue() instanceof CollectionGroupingElementNodeValue) { groupingXPathFragment = groupingXPathNode.getXPathFragment(); contentHandler.startElement(uri, parentLocalName, parentLocalName, new AttributesImpl()); XPathNode itemXPathNode = groupingXPathNode.getNonAttributeChildren().get(0); itemXPathFragment = itemXPathNode.getXPathFragment(); } else if (groupingXPathNode.getUnmarshalNodeValue() == null) { XPathNode itemXPathNode = groupingXPathNode.getNonAttributeChildren().get(0); if (itemXPathNode != null) { if ((itemXPathNode.getUnmarshalNodeValue()).isContainerValue()) { groupingXPathFragment = groupingXPathNode.getXPathFragment(); contentHandler.startElement(uri, parentLocalName, parentLocalName, new AttributesImpl()); itemXPathFragment = itemXPathNode.getXPathFragment(); } } } } } } for (JsonValue nextArrayValue : jsonArray) { if (nextArrayValue.getValueType() == ValueType.NULL) { contentHandler.setNil(true); } if (!isTextValue) { if (null != itemXPathFragment) { contentHandler.startElement(itemXPathFragment.getNamespaceURI(), itemXPathFragment.getLocalName(), itemXPathFragment.getLocalName(), attributes.setValue(nextArrayValue, attributePrefix,namespaces, getNamespaceSeparator(), isNamespaceAware())); } else { contentHandler.startElement(uri, parentLocalName,parentLocalName, attributes.setValue(nextArrayValue, attributePrefix,namespaces, getNamespaceSeparator(), isNamespaceAware())); } } parseValue(nextArrayValue); if (!isTextValue) { if (null != itemXPathFragment) { contentHandler.endElement(itemXPathFragment.getNamespaceURI(),itemXPathFragment.getLocalName(),itemXPathFragment.getLocalName()); } else { contentHandler.endElement(uri, parentLocalName,parentLocalName); } } } } if (null != groupingXPathFragment) { contentHandler.endElement(uri,groupingXPathFragment.getLocalName(),groupingXPathFragment.getLocalName()); } endCollection(); } else { if (attributePrefix != null && name.startsWith(attributePrefix)) { return; } String localName = name; String uri = Constants.EMPTY_STRING; if (isNamespaceAware() && namespaces != null) { if (localName.length() > 2) { int nsIndex = localName.indexOf(getNamespaceSeparator(), 1); String prefix = Constants.EMPTY_STRING; if (nsIndex > -1) { prefix = localName.substring(0, nsIndex); } uri = namespaces.resolveNamespacePrefix(prefix); if (uri == null) { uri = namespaces.getDefaultNamespaceURI(); } else { localName = localName.substring(nsIndex + 1); } if (localName.equals(Constants.SCHEMA_TYPE_ATTRIBUTE) && uri != null && uri.equals(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI)) { return; } } else { uri = namespaces.getDefaultNamespaceURI(); } } if (contentHandler instanceof XMLRootRecord || contentHandler instanceof DeferredContentHandler) { if (jsonTypeCompatibility) { // if its not namespaceAware don't report the "type" child as it // is will be read by the xsi:type lookup if (!isNamespaceAware() && localName.equals(Constants.SCHEMA_TYPE_ATTRIBUTE)) { return; } } if (textWrapper != null && textWrapper.equals(localName)) { parseValue(jsonValue); return; } } else if (contentHandler instanceof UnmarshalRecord && ((UnmarshalRecord) contentHandler).getXPathNode() != null) { if (jsonTypeCompatibility) { if (!isNamespaceAware() && localName.equals(Constants.SCHEMA_TYPE_ATTRIBUTE) && !((UnmarshalRecord) contentHandler).getXPathNode().hasTypeChild()) { return; } } boolean isTextValue = isTextValue(localName); if (isTextValue) { parseValue(jsonValue); return; } NodeValue nv = ((UnmarshalRecord)contentHandler).getAttributeChildNodeValue(uri, localName); if(attributePrefix == null && nv !=null ){ return; } } if (jsonValue.getValueType() == ValueType.NULL) { contentHandler.setNil(true); } contentHandler.startElement(uri, localName, localName, attributes.setValue(jsonValue, attributePrefix, namespaces, getNamespaceSeparator(), isNamespaceAware())); parseValue(jsonValue); contentHandler.endElement(uri, localName, localName); } } public boolean isNullRepresentedByXsiNil(AbstractNullPolicy nullPolicy) { return true; } private void startCollection() { isInCollection = true; } private void endCollection() { isInCollection = false; } public boolean isInCollection() { return isInCollection; } private boolean isTextValue(String localName) { XPathNode currentNode = ((UnmarshalRecord) contentHandler).getXPathNode(); if (currentNode == null) { return textWrapper != null && textWrapper.equals(localName); } return ((currentNode.getNonAttributeChildrenMap() == null || currentNode.getNonAttributeChildrenMap().size() == 0 || (currentNode.getNonAttributeChildrenMap().size() == 1 && currentNode.getTextNode() != null) ) && textWrapper != null && textWrapper.equals(localName) ); } @Override public Object convertValueBasedOnSchemaType(Field xmlField, Object value, ConversionManager conversionManager, AbstractUnmarshalRecord record) { if (xmlField.getSchemaType() != null) { if (Constants.QNAME_QNAME.equals(xmlField.getSchemaType())) { String stringValue = (String) value; int indexOpen = stringValue.indexOf('{'); int indexClose = stringValue.indexOf('}'); String uri; String localName; if (indexOpen > -1 && indexClose > -1) { uri = stringValue.substring(indexOpen + 1, indexClose); localName = stringValue.substring(indexClose + 1); } else { QName obj = (QName) xmlField.convertValueBasedOnSchemaType(stringValue, conversionManager, record); localName = obj.getLocalPart(); uri = obj.getNamespaceURI(); } if (uri != null) { return new QName(uri, localName); } else { return new QName(localName); } } else { Class fieldType = xmlField.getType(); if (fieldType == null) { fieldType = xmlField.getJavaClass(xmlField.getSchemaType(), conversionManager); } return conversionManager.convertObject(value, fieldType, xmlField.getSchemaType()); } } return value; } /** * INTERNAL: The MediaType associated with this reader */ @Override public MediaType getMediaType() { return Constants.APPLICATION_JSON; } private static class JsonAttributes extends IndexedAttributeList { private JsonValue value; private String attributePrefix; private char namespaceSeparator; private NamespaceResolver namespaces; private boolean namespaceAware; public JsonAttributes setValue(JsonValue value, String attributePrefix, NamespaceResolver nr, char namespaceSeparator, boolean namespaceAware) { reset(); this.value = value; this.attributePrefix = attributePrefix; this.namespaces = nr; this.namespaceSeparator = namespaceSeparator; this.namespaceAware = namespaceAware; return this; } private void addSimpleAttribute(List<Attribute> attributes, String uri, String attributeLocalName, JsonValue childValue) { switch (childValue.getValueType()) { case STRING: { String stringValue = ((JsonString) childValue).getString(); attributes.add(new Attribute(uri, attributeLocalName, attributeLocalName, stringValue)); break; } case NUMBER: { attributes.add(new Attribute(uri, attributeLocalName, attributeLocalName, childValue.toString())); break; } case FALSE: { attributes.add(new Attribute(uri, attributeLocalName, attributeLocalName, FALSE)); break; } case TRUE: { attributes.add(new Attribute(uri, attributeLocalName, attributeLocalName, TRUE)); break; } case ARRAY: case OBJECT: case NULL: break; // noop default: throw new IllegalStateException("Unhandled valueType: " + childValue.getValueType()); } } public int getIndex(String uri, String localName) { if (null == localName) { return -1; } int index = 0; for (Attribute attribute : attributes()) { if (namespaceAware) { if (localName.equals(attribute.getLocalName()) && uri.equals(attribute.getUri())) { return index; } } else { if (attribute.getName().equals(localName)) { return index; } } index++; } return -1; } @Override protected Attribute[] attributes() { if (null == attributes) { switch (value.getValueType()) { case NULL: { return NO_ATTRIBUTES; } case OBJECT: { JsonObject jsonObject = (JsonObject) value; ArrayList<Attribute> attributesList = new ArrayList<>(jsonObject.values().size()); for (Entry<String, JsonValue> nextEntry : jsonObject.entrySet()) { String attributeLocalName = nextEntry.getKey(); if (attributePrefix != null) { if (attributeLocalName.startsWith(attributePrefix)) { attributeLocalName = attributeLocalName.substring(attributePrefix.length()); } else { break; } } String uri = Constants.EMPTY_STRING; if (namespaceAware && namespaces != null) { if (attributeLocalName.length() > 2) { String prefix = Constants.EMPTY_STRING; int nsIndex = attributeLocalName.indexOf(namespaceSeparator, 1); if (nsIndex > -1) { prefix = attributeLocalName.substring(0, nsIndex); } uri = namespaces.resolveNamespacePrefix(prefix); if (uri == null) { uri = namespaces.getDefaultNamespaceURI(); } else { attributeLocalName = attributeLocalName.substring(nsIndex + 1); } } else { uri = namespaces.getDefaultNamespaceURI(); } } JsonValue nextValue = nextEntry.getValue(); if (nextValue.getValueType() == ValueType.ARRAY) { JsonArray jsonArray = (JsonArray) nextValue; if (jsonArray.size() == 0) { attributesList.add(new Attribute(uri, attributeLocalName, attributeLocalName, "")); } for (JsonValue nextChildValue : jsonArray) { addSimpleAttribute(attributesList, uri, attributeLocalName, nextChildValue); } } else { addSimpleAttribute(attributesList, uri, attributeLocalName, nextValue); } } attributes = attributesList.toArray(new Attribute[attributesList.size()]); break; } default: { attributes = NO_ATTRIBUTES; } } } return attributes; } } }