| /* |
| * Copyright (c) 2014, 2021 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 v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Iaroslav Savytskyi - 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.math.BigDecimal; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.ArrayDeque; |
| import java.util.Deque; |
| |
| import jakarta.json.Json; |
| import jakarta.json.JsonArrayBuilder; |
| import jakarta.json.JsonException; |
| import jakarta.json.JsonObjectBuilder; |
| import jakarta.json.JsonStructure; |
| import jakarta.json.JsonValue; |
| import jakarta.json.stream.JsonParser; |
| |
| import org.eclipse.persistence.exceptions.XMLMarshalException; |
| import org.eclipse.persistence.internal.oxm.Constants; |
| import org.eclipse.persistence.internal.oxm.ConversionManager; |
| import org.eclipse.persistence.internal.oxm.MediaType; |
| import org.eclipse.persistence.internal.oxm.Unmarshaller; |
| import org.eclipse.persistence.internal.oxm.mappings.Field; |
| import org.eclipse.persistence.internal.oxm.record.AbstractUnmarshalRecord; |
| import org.eclipse.persistence.internal.oxm.record.ExtendedContentHandler; |
| import org.eclipse.persistence.internal.oxm.record.XMLReaderAdapter; |
| import org.eclipse.persistence.oxm.mappings.nullpolicy.AbstractNullPolicy; |
| import org.xml.sax.ContentHandler; |
| import org.xml.sax.ErrorHandler; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| |
| /** |
| * Reader for JSR-353 stream (StAX) parser. |
| * |
| * Could be instantiated with {@link JsonParserReader.JsonParserReaderBuilder#build()}; |
| */ |
| public final class JsonParserReader extends XMLReaderAdapter { |
| |
| private final JsonParser parser; |
| private final JsonStructureReader structureReader; |
| |
| /** |
| * Parsing stack |
| */ |
| private final Deque<JsonStructureBuilder> stack = new ArrayDeque<>(); |
| |
| /** |
| * Private constructor |
| * Use {@link JsonParserReader.JsonParserReaderBuilder} to instantiate the class; |
| */ |
| private JsonParserReader(JsonParserReaderBuilder b) { |
| this.parser = b.parser; |
| if (b.resultClass == null) |
| this.structureReader = new JsonStructureReader(b.um); |
| else |
| this.structureReader = new JsonStructureReader(b.um, b.resultClass); |
| } |
| |
| @Override |
| public void parse(InputSource input) throws IOException, SAXException { |
| if (null == input) { |
| doParsing(parser); |
| return; |
| } |
| |
| if (null != input.getCharacterStream()) { |
| doParsing(Json.createParser(input.getCharacterStream())); |
| return; |
| } |
| |
| InputStream inputStream = null; |
| try { |
| if (null != (inputStream = input.getByteStream())) { |
| doParsing(Json.createParser(inputStream)); |
| return; |
| } |
| |
| try { |
| URL url = new URL(input.getSystemId()); |
| inputStream = url.openStream(); |
| } catch (MalformedURLException malformedURLException) { |
| try { |
| inputStream = new FileInputStream(input.getSystemId()); |
| } catch (FileNotFoundException fileNotFoundException) { |
| throw malformedURLException; |
| } |
| } |
| doParsing(Json.createParser(inputStream)); |
| } catch (JsonException je) { |
| throw XMLMarshalException.unmarshalException(je); |
| } finally { |
| if (null != inputStream) { |
| inputStream.close(); |
| } |
| } |
| } |
| |
| @Override |
| public void parse(String systemId) { |
| try { |
| parse(new InputSource(systemId)); |
| } catch (IOException | SAXException e) { |
| throw XMLMarshalException.unmarshalException(e); |
| } |
| } |
| |
| private void doParsing(JsonParser parser) throws SAXException, IOException { |
| JsonStructureBuilder builder = null; |
| while (parser.hasNext()) { |
| builder = parseEvent(parser); |
| } |
| assert builder != null; |
| JsonStructure jsonStructure = builder.build(); |
| structureReader.parseRoot(jsonStructure); |
| } |
| |
| private JsonStructureBuilder parseEvent(JsonParser jp) throws SAXException { |
| JsonParser.Event e = jp.next(); |
| |
| JsonStructureBuilder top = stack.peek(); |
| switch (e) { |
| case START_ARRAY: { |
| JsonStructureBuilder b = new ArrayBuilder(Json.createArrayBuilder()); |
| stack.push(b); |
| break; |
| } |
| case START_OBJECT: { |
| JsonStructureBuilder b = new ObjectBuilder(Json.createObjectBuilder()); |
| stack.push(b); |
| break; |
| } |
| case KEY_NAME: { |
| top.setKey(jp.getString()); |
| break; |
| } |
| case VALUE_STRING: { |
| top.add(jp.getString()); |
| break; |
| } |
| case VALUE_NUMBER: { |
| top.add(jp.getBigDecimal()); |
| break; |
| } |
| case VALUE_TRUE: { |
| top.add(Boolean.TRUE); |
| break; |
| } |
| case VALUE_FALSE: { |
| top.add(Boolean.FALSE); |
| break; |
| } |
| case VALUE_NULL: { |
| top.addNull(); |
| break; |
| } |
| case END_ARRAY: |
| case END_OBJECT: { // adding build element to the upper one |
| JsonStructureBuilder b = stack.pop(); |
| top = stack.peek(); |
| if (top != null) |
| top.add(b.build()); |
| return b; |
| } |
| default: |
| throw new IllegalStateException("Unhandled event: " + e); |
| } |
| return null; |
| } |
| |
| // ******************************** Redirecting requests to JsonStructureReader ******************************* |
| @Override |
| public boolean isNullRepresentedByXsiNil(AbstractNullPolicy nullPolicy) { |
| return true; |
| } |
| |
| @Override |
| public Object convertValueBasedOnSchemaType(Field xmlField, Object value, ConversionManager conversionManager, AbstractUnmarshalRecord record) { |
| return structureReader.convertValueBasedOnSchemaType(xmlField, value, conversionManager, record); |
| } |
| |
| @Override |
| public char getNamespaceSeparator() { |
| return structureReader.getNamespaceSeparator(); |
| } |
| |
| @Override |
| public ErrorHandler getErrorHandler() { |
| return structureReader.getErrorHandler(); |
| } |
| |
| @Override |
| public ExtendedContentHandler getContentHandler() { |
| return structureReader.getContentHandler(); |
| } |
| |
| @Override |
| public void setContentHandler(ContentHandler contentHandler) { |
| structureReader.setContentHandler(contentHandler); |
| } |
| |
| @Override |
| public boolean isInCollection() { |
| return structureReader.isInCollection(); |
| } |
| |
| @Override |
| public MediaType getMediaType() { |
| return Constants.APPLICATION_JSON; |
| } |
| |
| @Override |
| public boolean isNamespaceAware() { |
| return structureReader.isNamespaceAware(); |
| } |
| // ************************************************************************************************************ |
| |
| /** |
| * JsonStructure builder |
| */ |
| private interface JsonStructureBuilder { |
| JsonStructure build(); |
| |
| void add(JsonValue value); |
| |
| void add(String value); |
| |
| void add(BigDecimal value); |
| |
| void add(boolean value); |
| |
| void addNull(); |
| |
| void setKey(String key); |
| } |
| |
| /** |
| * Builder for JsonParserReader |
| */ |
| public static final class JsonParserReaderBuilder { |
| private final JsonParser parser; |
| private Unmarshaller um; |
| private Class resultClass; |
| |
| public JsonParserReaderBuilder(JsonParser parser) { |
| this.parser = parser; |
| } |
| |
| public JsonParserReaderBuilder setUnmarshaller(Unmarshaller um) { |
| this.um = um; |
| return this; |
| } |
| |
| public JsonParserReaderBuilder setResultClass(Class resultClass) { |
| this.resultClass = resultClass; |
| return this; |
| } |
| |
| public JsonParserReader build() { |
| if (parser == null) |
| throw new NullPointerException("JsonParser can't be null"); |
| return new JsonParserReader(this); |
| } |
| } |
| |
| private static final class ObjectBuilder implements JsonStructureBuilder { |
| private final JsonObjectBuilder b; |
| private String key; |
| |
| public ObjectBuilder(JsonObjectBuilder b) { |
| this.b = b; |
| } |
| |
| @Override |
| public JsonStructure build() { |
| return b.build(); |
| } |
| |
| @Override |
| public void add(JsonValue value) { |
| b.add(key, value); |
| } |
| |
| @Override |
| public void add(String value) { |
| b.add(key, value); |
| } |
| |
| @Override |
| public void add(BigDecimal value) { |
| b.add(key, value); |
| } |
| |
| @Override |
| public void add(boolean value) { |
| b.add(key, value); |
| } |
| |
| @Override |
| public void addNull() { |
| b.addNull(key); |
| } |
| |
| @Override |
| public void setKey(String key) { |
| this.key = key; |
| } |
| } |
| |
| private static final class ArrayBuilder implements JsonStructureBuilder { |
| private final JsonArrayBuilder b; |
| |
| public ArrayBuilder(JsonArrayBuilder b) { |
| this.b = b; |
| } |
| |
| @Override |
| public JsonStructure build() { |
| return b.build(); |
| } |
| |
| @Override |
| public void add(JsonValue value) { |
| b.add(value); |
| } |
| |
| @Override |
| public void add(String value) { |
| b.add(value); |
| } |
| |
| @Override |
| public void add(BigDecimal value) { |
| b.add(value); |
| } |
| |
| @Override |
| public void add(boolean value) { |
| b.add(value); |
| } |
| |
| @Override |
| public void addNull() { |
| b.addNull(); |
| } |
| |
| @Override |
| public void setKey(String key) { |
| // noop |
| } |
| } |
| } |