diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml index dcc2cb3f956..6e01dce40bc 100644 --- a/hapi-fhir-base/pom.xml +++ b/hapi-fhir-base/pom.xml @@ -20,8 +20,8 @@ - com.google.code.gson - gson + com.fasterxml.jackson.core + jackson-databind diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java index 8cc1a40c4ff..10b8609356a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java @@ -22,20 +22,29 @@ package ca.uhn.fhir.parser; import ca.uhn.fhir.context.*; import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum; -import ca.uhn.fhir.model.api.*; +import ca.uhn.fhir.model.api.ExtensionDt; +import ca.uhn.fhir.model.api.IPrimitiveDatatype; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.api.Tag; +import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.base.composite.BaseCodingDt; import ca.uhn.fhir.model.base.composite.BaseContainedDt; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.narrative.INarrativeGenerator; -import ca.uhn.fhir.parser.json.*; +import ca.uhn.fhir.parser.json.JsonLikeArray; +import ca.uhn.fhir.parser.json.JsonLikeObject; +import ca.uhn.fhir.parser.json.JsonLikeStructure; +import ca.uhn.fhir.parser.json.JsonLikeValue; import ca.uhn.fhir.parser.json.JsonLikeValue.ScalarType; import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType; +import ca.uhn.fhir.parser.json.JsonLikeWriter; +import ca.uhn.fhir.parser.json.jackson.JacksonStructure; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.util.ElementUtil; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.text.WordUtils; @@ -45,11 +54,17 @@ import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.math.BigDecimal; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; import static ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum.ID_DATATYPE; import static ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum.PRIMITIVE_DATATYPE; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; /** * This class is the FHIR JSON parser/encoder. Users should not interact with this class directly, but should use @@ -147,10 +162,9 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { theEventWriter.beginObject(arrayName); } - private JsonLikeWriter createJsonWriter(Writer theWriter) { - JsonLikeStructure jsonStructure = new GsonStructure(); - JsonLikeWriter retVal = jsonStructure.getJsonLikeWriter(theWriter); - return retVal; + private JsonLikeWriter createJsonWriter(Writer theWriter) throws IOException { + JsonLikeStructure jsonStructure = new JacksonStructure(); + return jsonStructure.getJsonLikeWriter(theWriter); } public void doEncodeResourceToJsonLikeWriter(IBaseResource theResource, JsonLikeWriter theEventWriter, EncodeContext theEncodeContext) throws IOException { @@ -168,11 +182,12 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { protected void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws IOException { JsonLikeWriter eventWriter = createJsonWriter(theWriter); doEncodeResourceToJsonLikeWriter(theResource, eventWriter, theEncodeContext); + eventWriter.close(); } @Override public T doParseResource(Class theResourceType, Reader theReader) { - JsonLikeStructure jsonStructure = new GsonStructure(); + JsonLikeStructure jsonStructure = new JacksonStructure(); jsonStructure.load(theReader); T retVal = doParseResource(theResourceType, jsonStructure); @@ -418,10 +433,10 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { String currentChildName = null; boolean inArray = false; - ArrayList> extensions = new ArrayList>(0); - ArrayList> modifierExtensions = new ArrayList>(0); - ArrayList> comments = new ArrayList>(0); - ArrayList ids = new ArrayList(0); + ArrayList> extensions = new ArrayList<>(0); + ArrayList> modifierExtensions = new ArrayList<>(0); + ArrayList> comments = new ArrayList<>(0); + ArrayList ids = new ArrayList<>(0); int valueIdx = 0; for (IBase nextValue : values) { @@ -1107,7 +1122,8 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } else { // must be a SCALAR theState.enteringNewElement(null, theName); - theState.attributeValue("value", theJsonVal.getAsString()); + String asString = theJsonVal.getAsString(); + theState.attributeValue("value", asString); parseAlternates(theAlternateVal, theState, theAlternateName, theAlternateName); theState.endingElement(); } @@ -1376,11 +1392,6 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } } - public static Gson newGson() { - Gson gson = new GsonBuilder().disableHtmlEscaping().create(); - return gson; - } - private static void write(JsonLikeWriter theWriter, String theName, String theValue) throws IOException { theWriter.write(theName, theValue); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/GsonStructure.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/GsonStructure.java deleted file mode 100644 index fcf96fbbfca..00000000000 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/GsonStructure.java +++ /dev/null @@ -1,379 +0,0 @@ -package ca.uhn.fhir.parser.json; -/* - * #%L - * HAPI FHIR - Core Library - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * 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. - * #L% - */ - -import java.io.PushbackReader; -import java.io.Reader; -import java.io.Writer; -import java.util.AbstractSet; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import ca.uhn.fhir.parser.DataFormatException; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSyntaxException; - -public class GsonStructure implements JsonLikeStructure { - - private enum ROOT_TYPE {OBJECT, ARRAY}; - private ROOT_TYPE rootType = null; - private JsonElement nativeRoot = null; - private JsonLikeValue jsonLikeRoot = null; - private GsonWriter jsonLikeWriter = null; - - public GsonStructure() { - super(); - } - - public void setNativeObject (JsonObject json) { - this.rootType = ROOT_TYPE.OBJECT; - this.nativeRoot = json; - } - public void setNativeArray (JsonArray json) { - this.rootType = ROOT_TYPE.ARRAY; - this.nativeRoot = json; - } - - @Override - public JsonLikeStructure getInstance() { - return new GsonStructure(); - } - - @Override - public void load(Reader theReader) throws DataFormatException { - this.load(theReader, false); - } - - @Override - public void load(Reader theReader, boolean allowArray) throws DataFormatException { - PushbackReader pbr = new PushbackReader(theReader); - int nextInt; - try { - while(true) { - nextInt = pbr.read(); - if (nextInt == -1) { - throw new DataFormatException("Did not find any content to parse"); - } - if (nextInt == '{') { - pbr.unread(nextInt); - break; - } - if (Character.isWhitespace(nextInt)) { - continue; - } - if (allowArray) { - if (nextInt == '[') { - pbr.unread(nextInt); - break; - } - throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char)nextInt + "' (must be '{' or '[')"); - } - throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char)nextInt + "' (must be '{')"); - } - - Gson gson = new GsonBuilder().disableHtmlEscaping().create(); - if (nextInt == '{') { - JsonObject root = gson.fromJson(pbr, JsonObject.class); - setNativeObject(root); - } else if (nextInt == '[') { - JsonArray root = gson.fromJson(pbr, JsonArray.class); - setNativeArray(root); - } - } catch (JsonSyntaxException e) { - if (e.getMessage().startsWith("Unexpected char 39")) { - throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage() + " - This may indicate that single quotes are being used as JSON escapes where double quotes are required", e); - } - throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage(), e); - } catch (Exception e) { - throw new DataFormatException("Failed to parse JSON content, error was: " + e.getMessage(), e); - } - } - - @Override - public JsonLikeWriter getJsonLikeWriter (Writer writer) { - if (null == jsonLikeWriter) { - jsonLikeWriter = new GsonWriter(writer); - } - return jsonLikeWriter; - } - - @Override - public JsonLikeWriter getJsonLikeWriter () { - if (null == jsonLikeWriter) { - jsonLikeWriter = new GsonWriter(); - } - return jsonLikeWriter; - } - - @Override - public JsonLikeObject getRootObject() throws DataFormatException { - if (rootType == ROOT_TYPE.OBJECT) { - if (null == jsonLikeRoot) { - jsonLikeRoot = new GsonJsonObject((JsonObject)nativeRoot); - } - return jsonLikeRoot.getAsObject(); - } - throw new DataFormatException("Content must be a valid JSON Object. It must start with '{'."); - } - - @Override - public JsonLikeArray getRootArray() throws DataFormatException { - if (rootType == ROOT_TYPE.ARRAY) { - if (null == jsonLikeRoot) { - jsonLikeRoot = new GsonJsonArray((JsonArray)nativeRoot); - } - return jsonLikeRoot.getAsArray(); - } - throw new DataFormatException("Content must be a valid JSON Array. It must start with '['."); - } - - private static class GsonJsonObject extends JsonLikeObject { - private JsonObject nativeObject; - private Set keySet = null; - private Map jsonLikeMap = new LinkedHashMap(); - - public GsonJsonObject (JsonObject json) { - this.nativeObject = json; - } - - @Override - public Object getValue() { - return null; - } - - @Override - public Set keySet() { - if (null == keySet) { - Set> entrySet = nativeObject.entrySet(); - keySet = new EntryOrderedSet(entrySet.size()); - for (Entry entry : entrySet) { - keySet.add(entry.getKey()); - } - } - return keySet; - } - - @Override - public JsonLikeValue get(String key) { - JsonLikeValue result = null; - if (jsonLikeMap.containsKey(key)) { - result = jsonLikeMap.get(key); - } else { - JsonElement child = nativeObject.get(key); - if (child != null) { - result = new GsonJsonValue(child); - } - jsonLikeMap.put(key, result); - } - return result; - } - } - - private static class GsonJsonArray extends JsonLikeArray { - private JsonArray nativeArray; - private Map jsonLikeMap = new LinkedHashMap(); - - public GsonJsonArray (JsonArray json) { - this.nativeArray = json; - } - - @Override - public Object getValue() { - return null; - } - - @Override - public int size() { - return nativeArray.size(); - } - - @Override - public JsonLikeValue get(int index) { - Integer key = Integer.valueOf(index); - JsonLikeValue result = null; - if (jsonLikeMap.containsKey(key)) { - result = jsonLikeMap.get(key); - } else { - JsonElement child = nativeArray.get(index); - if (child != null) { - result = new GsonJsonValue(child); - } - jsonLikeMap.put(key, result); - } - return result; - } - } - - private static class GsonJsonValue extends JsonLikeValue { - private JsonElement nativeValue; - private JsonLikeObject jsonLikeObject = null; - private JsonLikeArray jsonLikeArray = null; - - public GsonJsonValue (JsonElement json) { - this.nativeValue = json; - } - - @Override - public Object getValue() { - if (nativeValue != null && nativeValue.isJsonPrimitive()) { - if (((JsonPrimitive)nativeValue).isNumber()) { - return nativeValue.getAsNumber(); - } - if (((JsonPrimitive)nativeValue).isBoolean()) { - return Boolean.valueOf(nativeValue.getAsBoolean()); - } - return nativeValue.getAsString(); - } - return null; - } - - @Override - public ValueType getJsonType() { - if (null == nativeValue || nativeValue.isJsonNull()) { - return ValueType.NULL; - } - if (nativeValue.isJsonObject()) { - return ValueType.OBJECT; - } - if (nativeValue.isJsonArray()) { - return ValueType.ARRAY; - } - if (nativeValue.isJsonPrimitive()) { - return ValueType.SCALAR; - } - return null; - } - - @Override - public ScalarType getDataType() { - if (nativeValue != null && nativeValue.isJsonPrimitive()) { - if (((JsonPrimitive)nativeValue).isNumber()) { - return ScalarType.NUMBER; - } - if (((JsonPrimitive)nativeValue).isString()) { - return ScalarType.STRING; - } - if (((JsonPrimitive)nativeValue).isBoolean()) { - return ScalarType.BOOLEAN; - } - } - return null; - } - - @Override - public JsonLikeArray getAsArray() { - if (nativeValue != null && nativeValue.isJsonArray()) { - if (null == jsonLikeArray) { - jsonLikeArray = new GsonJsonArray((JsonArray)nativeValue); - } - } - return jsonLikeArray; - } - - @Override - public JsonLikeObject getAsObject() { - if (nativeValue != null && nativeValue.isJsonObject()) { - if (null == jsonLikeObject) { - jsonLikeObject = new GsonJsonObject((JsonObject)nativeValue); - } - } - return jsonLikeObject; - } - - @Override - public Number getAsNumber() { - return nativeValue != null ? nativeValue.getAsNumber() : null; - } - - @Override - public String getAsString() { - return nativeValue != null ? nativeValue.getAsString() : null; - } - - @Override - public boolean getAsBoolean() { - if (nativeValue != null && nativeValue.isJsonPrimitive() && ((JsonPrimitive)nativeValue).isBoolean()) { - return nativeValue.getAsBoolean(); - } - return super.getAsBoolean(); - } - } - - private static class EntryOrderedSet extends AbstractSet { - private transient ArrayList data = null; - - public EntryOrderedSet (int initialCapacity) { - data = new ArrayList(initialCapacity); - } - @SuppressWarnings("unused") - public EntryOrderedSet () { - data = new ArrayList(); - } - - @Override - public int size() { - return data.size(); - } - - @Override - public boolean contains(Object o) { - return data.contains(o); - } - - @SuppressWarnings("unused") // not really.. just not here - public T get(int index) { - return data.get(index); - } - - @Override - public boolean add(T element) { - if (data.contains(element)) { - return false; - } - return data.add(element); - } - - @Override - public boolean remove(Object o) { - return data.remove(o); - } - - @Override - public void clear() { - data.clear(); - } - - @Override - public Iterator iterator() { - return data.iterator(); - } - - } -} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/GsonWriter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/GsonWriter.java deleted file mode 100644 index 1442f40c9c3..00000000000 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/GsonWriter.java +++ /dev/null @@ -1,263 +0,0 @@ -package ca.uhn.fhir.parser.json; - -/* - * #%L - * HAPI FHIR - Core Library - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * 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. - * #L% - */ - -import java.io.IOException; -import java.io.Writer; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.Stack; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.stream.JsonWriter; - -public class GsonWriter extends JsonLikeWriter { - private static final Logger log = LoggerFactory.getLogger(GsonWriter.class); - - private JsonWriter eventWriter; - private enum BlockType { - NONE, OBJECT, ARRAY - } - private BlockType blockType = BlockType.NONE; - private Stack blockStack = new Stack(); - - public GsonWriter () { - super(); - } - public GsonWriter (Writer writer) { - setWriter(writer); - } - - @Override - public JsonLikeWriter init() throws IOException { - eventWriter = new JsonWriter(getWriter()); - eventWriter.setSerializeNulls(true); - if (isPrettyPrint()) { - eventWriter.setIndent(" "); - } - blockType = BlockType.NONE; - blockStack.clear(); - return this; - } - - @Override - public JsonLikeWriter flush() throws IOException { - if (blockType != BlockType.NONE) { - log.error("JsonLikeStreamWriter.flush() called but JSON document is not finished"); - } - eventWriter.flush(); - getWriter().flush(); - return this; - } - - @Override - public void close() throws IOException { - eventWriter.close(); - getWriter().close(); - } - - @Override - public JsonLikeWriter beginObject() throws IOException { - blockStack.push(blockType); - blockType = BlockType.OBJECT; - eventWriter.beginObject(); - return this; - } - - @Override - public JsonLikeWriter beginArray() throws IOException { - blockStack.push(blockType); - blockType = BlockType.ARRAY; - eventWriter.beginArray(); - return this; - } - - @Override - public JsonLikeWriter beginObject(String name) throws IOException { - blockStack.push(blockType); - blockType = BlockType.OBJECT; - eventWriter.name(name); - eventWriter.beginObject(); - return this; - } - - @Override - public JsonLikeWriter beginArray(String name) throws IOException { - blockStack.push(blockType); - blockType = BlockType.ARRAY; - eventWriter.name(name); - eventWriter.beginArray(); - return this; - } - - @Override - public JsonLikeWriter write(String value) throws IOException { - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(BigInteger value) throws IOException { - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(BigDecimal value) throws IOException { - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(long value) throws IOException { - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(double value) throws IOException { - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(Boolean value) throws IOException { - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(boolean value) throws IOException { - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter writeNull() throws IOException { - eventWriter.nullValue(); - return this; - } - - @Override - public JsonLikeWriter write(String name, String value) throws IOException { - eventWriter.name(name); - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(String name, BigInteger value) throws IOException { - eventWriter.name(name); - eventWriter.value(value); - return this; - } - @Override - public JsonLikeWriter write(String name, BigDecimal value) throws IOException { - eventWriter.name(name); - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(String name, long value) throws IOException { - eventWriter.name(name); - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(String name, double value) throws IOException { - eventWriter.name(name); - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(String name, Boolean value) throws IOException { - eventWriter.name(name); - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(String name, boolean value) throws IOException { - eventWriter.name(name); - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter writeNull(String name) throws IOException { - eventWriter.name(name); - eventWriter.nullValue(); - return this; - } - - @Override - public JsonLikeWriter endObject() throws IOException { - if (blockType == BlockType.NONE) { - log.error("JsonLikeStreamWriter.endObject(); called with no active JSON document"); - } else { - if (blockType != BlockType.OBJECT) { - log.error("JsonLikeStreamWriter.endObject(); called outside a JSON object. (Use endArray() instead?)"); - eventWriter.endArray(); - } else { - eventWriter.endObject(); - } - blockType = blockStack.pop(); - } - return this; - } - - @Override - public JsonLikeWriter endArray() throws IOException { - if (blockType == BlockType.NONE) { - log.error("JsonLikeStreamWriter.endArray(); called with no active JSON document"); - } else { - if (blockType != BlockType.ARRAY) { - log.error("JsonLikeStreamWriter.endArray(); called outside a JSON array. (Use endObject() instead?)"); - eventWriter.endObject(); - } else { - eventWriter.endArray(); - } - blockType = blockStack.pop(); - } - return this; - } - - @Override - public JsonLikeWriter endBlock() throws IOException { - if (blockType == BlockType.NONE) { - log.error("JsonLikeStreamWriter.endBlock(); called with no active JSON document"); - } else { - if (blockType == BlockType.ARRAY) { - eventWriter.endArray(); - } else { - eventWriter.endObject(); - } - blockType = blockStack.pop(); - } - return this; - } - -} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeObject.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeObject.java index dd5e785a387..fd3d3bea64b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeObject.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeObject.java @@ -53,20 +53,4 @@ public abstract class JsonLikeObject extends JsonLikeValue { public abstract JsonLikeValue get (String key); - public String getString (String key) { - JsonLikeValue value = this.get(key); - if (null == value) { - throw new NullPointerException("Json object missing element named \""+key+"\""); - } - return value.getAsString(); - } - - public String getString (String key, String defaultValue) { - String result = defaultValue; - JsonLikeValue value = this.get(key); - if (value != null) { - result = value.getAsString(); - } - return result; - } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeStructure.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeStructure.java index 5d7004c9cfd..a171d0c75bb 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeStructure.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeStructure.java @@ -19,34 +19,38 @@ */ package ca.uhn.fhir.parser.json; +import ca.uhn.fhir.parser.DataFormatException; + +import java.io.IOException; import java.io.Reader; import java.io.Writer; -import ca.uhn.fhir.parser.DataFormatException; - /** - * This interface is the generic representation of any sort of data + * This interface is the generic representation of any sort of data * structure that looks and smells like JSON. These data structures * can be abstractly viewed as a or List * whose members are other Maps, Lists, or scalars (Strings, Numbers, Boolean) - * + * * @author Bill.Denton */ public interface JsonLikeStructure { - public JsonLikeStructure getInstance(); - + JsonLikeStructure getInstance(); + /** * Parse the JSON document into the Json-like structure * so that it can be navigated. - * + * * @param theReader a Reader that will - * process the JSON input stream + * process the JSON input stream * @throws DataFormatException when invalid JSON is received */ - public void load (Reader theReader) throws DataFormatException; - public void load (Reader theReader, boolean allowArray) throws DataFormatException; - public JsonLikeObject getRootObject () throws DataFormatException; - public JsonLikeArray getRootArray () throws DataFormatException; - public JsonLikeWriter getJsonLikeWriter (); - public JsonLikeWriter getJsonLikeWriter (Writer writer); + void load(Reader theReader) throws DataFormatException; + + void load(Reader theReader, boolean allowArray) throws DataFormatException; + + JsonLikeObject getRootObject() throws DataFormatException; + + JsonLikeWriter getJsonLikeWriter(); + + JsonLikeWriter getJsonLikeWriter(Writer writer) throws IOException; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeWriter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeWriter.java index aebcdd80a74..9e4f8576746 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeWriter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeWriter.java @@ -29,55 +29,73 @@ public abstract class JsonLikeWriter { private boolean prettyPrint; private Writer writer; - - public void setPrettyPrint (boolean tf) { - prettyPrint = tf; - } - public boolean isPrettyPrint () { - return prettyPrint; - } - - public void setWriter (Writer writer) { - this.writer = writer; - } - public Writer getWriter () { - return writer; - } - - public abstract JsonLikeWriter init () throws IOException; - public abstract JsonLikeWriter flush () throws IOException; - public abstract void close () throws IOException; - - public abstract JsonLikeWriter beginObject () throws IOException; - public abstract JsonLikeWriter beginArray () throws IOException; - public abstract JsonLikeWriter beginObject (String name) throws IOException; - public abstract JsonLikeWriter beginArray (String name) throws IOException; - - public abstract JsonLikeWriter write (String value) throws IOException; - public abstract JsonLikeWriter write (BigInteger value) throws IOException; - public abstract JsonLikeWriter write (BigDecimal value) throws IOException; - public abstract JsonLikeWriter write (long value) throws IOException; - public abstract JsonLikeWriter write (double value) throws IOException; - public abstract JsonLikeWriter write (Boolean value) throws IOException; - public abstract JsonLikeWriter write (boolean value) throws IOException; - public abstract JsonLikeWriter writeNull () throws IOException; - - public abstract JsonLikeWriter write (String name, String value) throws IOException; - public abstract JsonLikeWriter write (String name, BigInteger value) throws IOException; - public abstract JsonLikeWriter write (String name, BigDecimal value) throws IOException; - public abstract JsonLikeWriter write (String name, long value) throws IOException; - public abstract JsonLikeWriter write (String name, double value) throws IOException; - public abstract JsonLikeWriter write (String name, Boolean value) throws IOException; - public abstract JsonLikeWriter write (String name, boolean value) throws IOException; - public abstract JsonLikeWriter writeNull (String name) throws IOException; - - public abstract JsonLikeWriter endObject () throws IOException; - public abstract JsonLikeWriter endArray () throws IOException; - public abstract JsonLikeWriter endBlock () throws IOException; - public JsonLikeWriter() { super(); } + public boolean isPrettyPrint() { + return prettyPrint; + } + + public void setPrettyPrint(boolean tf) { + prettyPrint = tf; + } + + public Writer getWriter() { + return writer; + } + + public void setWriter(Writer writer) { + this.writer = writer; + } + + public abstract JsonLikeWriter init() throws IOException; + + public abstract JsonLikeWriter flush() throws IOException; + + public abstract void close() throws IOException; + + public abstract JsonLikeWriter beginObject() throws IOException; + + public abstract JsonLikeWriter beginObject(String name) throws IOException; + + public abstract JsonLikeWriter beginArray(String name) throws IOException; + + public abstract JsonLikeWriter write(String value) throws IOException; + + public abstract JsonLikeWriter write(BigInteger value) throws IOException; + + public abstract JsonLikeWriter write(BigDecimal value) throws IOException; + + public abstract JsonLikeWriter write(long value) throws IOException; + + public abstract JsonLikeWriter write(double value) throws IOException; + + public abstract JsonLikeWriter write(Boolean value) throws IOException; + + public abstract JsonLikeWriter write(boolean value) throws IOException; + + public abstract JsonLikeWriter writeNull() throws IOException; + + public abstract JsonLikeWriter write(String name, String value) throws IOException; + + public abstract JsonLikeWriter write(String name, BigInteger value) throws IOException; + + public abstract JsonLikeWriter write(String name, BigDecimal value) throws IOException; + + public abstract JsonLikeWriter write(String name, long value) throws IOException; + + public abstract JsonLikeWriter write(String name, double value) throws IOException; + + public abstract JsonLikeWriter write(String name, Boolean value) throws IOException; + + public abstract JsonLikeWriter write(String name, boolean value) throws IOException; + + public abstract JsonLikeWriter endObject() throws IOException; + + public abstract JsonLikeWriter endArray() throws IOException; + + public abstract JsonLikeWriter endBlock() throws IOException; + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonStructure.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonStructure.java new file mode 100644 index 00000000000..d22ac026796 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonStructure.java @@ -0,0 +1,372 @@ +package ca.uhn.fhir.parser.json.jackson; + +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.parser.json.JsonLikeArray; +import ca.uhn.fhir.parser.json.JsonLikeObject; +import ca.uhn.fhir.parser.json.JsonLikeStructure; +import ca.uhn.fhir.parser.json.JsonLikeValue; +import ca.uhn.fhir.parser.json.JsonLikeWriter; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.DecimalNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.jena.tdb.setup.BuilderStdDB; + +import java.io.IOException; +import java.io.PushbackReader; +import java.io.Reader; +import java.io.Writer; +import java.math.BigDecimal; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class JacksonStructure implements JsonLikeStructure { + + private static final ObjectMapper OBJECT_MAPPER = createObjectMapper(); + private JacksonWriter jacksonWriter; + private ROOT_TYPE rootType = null; + private JsonNode nativeRoot = null; + private JsonNode jsonLikeRoot = null; + + public void setNativeObject(ObjectNode objectNode) { + this.rootType = ROOT_TYPE.OBJECT; + this.nativeRoot = objectNode; + } + + public void setNativeArray(ArrayNode arrayNode) { + this.rootType = ROOT_TYPE.ARRAY; + this.nativeRoot = arrayNode; + } + + @Override + public JsonLikeStructure getInstance() { + return new JacksonStructure(); + } + + @Override + public void load(Reader theReader) throws DataFormatException { + this.load(theReader, false); + } + + @Override + public void load(Reader theReader, boolean allowArray) throws DataFormatException { + PushbackReader pbr = new PushbackReader(theReader); + int nextInt; + try { + while (true) { + nextInt = pbr.read(); + if (nextInt == -1) { + throw new DataFormatException("Did not find any content to parse"); + } + if (nextInt == '{') { + pbr.unread(nextInt); + break; + } + if (Character.isWhitespace(nextInt)) { + continue; + } + if (allowArray) { + if (nextInt == '[') { + pbr.unread(nextInt); + break; + } + throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char) nextInt + "' (must be '{' or '[')"); + } + throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char) nextInt + "' (must be '{')"); + } + + if (nextInt == '{') { + setNativeObject((ObjectNode) OBJECT_MAPPER.readTree(pbr)); + } else { + setNativeArray((ArrayNode) OBJECT_MAPPER.readTree(pbr)); + } + } catch (Exception e) { + if (e.getMessage().startsWith("Unexpected char 39")) { + throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage() + " - " + + "This may indicate that single quotes are being used as JSON escapes where double quotes are required", e); + } + throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage(), e); + } + } + + @Override + public JsonLikeWriter getJsonLikeWriter(Writer writer) throws IOException { + if (null == jacksonWriter) { + jacksonWriter = new JacksonWriter(OBJECT_MAPPER.getFactory(), writer); + } + + return jacksonWriter; + } + + @Override + public JsonLikeWriter getJsonLikeWriter() { + if (null == jacksonWriter) { + jacksonWriter = new JacksonWriter(); + } + return jacksonWriter; + } + + @Override + public JsonLikeObject getRootObject() throws DataFormatException { + if (rootType == ROOT_TYPE.OBJECT) { + if (null == jsonLikeRoot) { + jsonLikeRoot = nativeRoot; + } + + return new JacksonJsonObject((ObjectNode) jsonLikeRoot); + } + + throw new DataFormatException("Content must be a valid JSON Object. It must start with '{'."); + } + + private enum ROOT_TYPE {OBJECT, ARRAY} + + private static class JacksonJsonObject extends JsonLikeObject { + private final ObjectNode nativeObject; + private final Map jsonLikeMap = new LinkedHashMap<>(); + private Set keySet = null; + + public JacksonJsonObject(ObjectNode json) { + this.nativeObject = json; + } + + @Override + public Object getValue() { + return null; + } + + @Override + public Set keySet() { + if (null == keySet) { + final Iterable> iterable = nativeObject::fields; + keySet = StreamSupport.stream(iterable.spliterator(), false) + .map(Map.Entry::getKey) + .collect(Collectors.toCollection(EntryOrderedSet::new)); + } + + return keySet; + } + + @Override + public JsonLikeValue get(String key) { + JsonLikeValue result = null; + if (jsonLikeMap.containsKey(key)) { + result = jsonLikeMap.get(key); + } else { + JsonNode child = nativeObject.get(key); + if (child != null) { + result = new JacksonJsonValue(child); + } + jsonLikeMap.put(key, result); + } + return result; + } + } + + private static class EntryOrderedSet extends AbstractSet { + private final transient ArrayList data; + + public EntryOrderedSet() { + data = new ArrayList<>(); + } + + @Override + public int size() { + return data.size(); + } + + @Override + public boolean contains(Object o) { + return data.contains(o); + } + + public T get(int index) { + return data.get(index); + } + + @Override + public boolean add(T element) { + if (data.contains(element)) { + return false; + } + return data.add(element); + } + + @Override + public boolean remove(Object o) { + return data.remove(o); + } + + @Override + public void clear() { + data.clear(); + } + + @Override + public Iterator iterator() { + return data.iterator(); + } + } + + private static class JacksonJsonArray extends JsonLikeArray { + private final ArrayNode nativeArray; + private final Map jsonLikeMap = new LinkedHashMap(); + + public JacksonJsonArray(ArrayNode json) { + this.nativeArray = json; + } + + @Override + public Object getValue() { + return null; + } + + @Override + public int size() { + return nativeArray.size(); + } + + @Override + public JsonLikeValue get(int index) { + Integer key = index; + JsonLikeValue result = null; + if (jsonLikeMap.containsKey(key)) { + result = jsonLikeMap.get(key); + } else { + JsonNode child = nativeArray.get(index); + if (child != null) { + result = new JacksonJsonValue(child); + } + jsonLikeMap.put(key, result); + } + return result; + } + } + + private static class JacksonJsonValue extends JsonLikeValue { + private final JsonNode nativeValue; + private JsonLikeObject jsonLikeObject = null; + private JsonLikeArray jsonLikeArray = null; + + public JacksonJsonValue(JsonNode jsonNode) { + this.nativeValue = jsonNode; + } + + @Override + public Object getValue() { + if (nativeValue != null && nativeValue.isValueNode()) { + if (nativeValue.isNumber()) { + return nativeValue.numberValue(); + } + + if (nativeValue.isBoolean()) { + return nativeValue.booleanValue(); + } + + return nativeValue.asText(); + } + return null; + } + + @Override + public ValueType getJsonType() { + if (null == nativeValue || nativeValue.isNull()) { + return ValueType.NULL; + } + if (nativeValue.isObject()) { + return ValueType.OBJECT; + } + if (nativeValue.isArray()) { + return ValueType.ARRAY; + } + if (nativeValue.isValueNode()) { + return ValueType.SCALAR; + } + return null; + } + + @Override + public ScalarType getDataType() { + if (nativeValue != null && nativeValue.isValueNode()) { + if (nativeValue.isNumber()) { + return ScalarType.NUMBER; + } + if (nativeValue.isTextual()) { + return ScalarType.STRING; + } + if (nativeValue.isBoolean()) { + return ScalarType.BOOLEAN; + } + } + return null; + } + + @Override + public JsonLikeArray getAsArray() { + if (nativeValue != null && nativeValue.isArray()) { + if (null == jsonLikeArray) { + jsonLikeArray = new JacksonJsonArray((ArrayNode) nativeValue); + } + } + return jsonLikeArray; + } + + @Override + public JsonLikeObject getAsObject() { + if (nativeValue != null && nativeValue.isObject()) { + if (null == jsonLikeObject) { + jsonLikeObject = new JacksonJsonObject((ObjectNode) nativeValue); + } + } + return jsonLikeObject; + } + + @Override + public Number getAsNumber() { + return nativeValue != null ? nativeValue.numberValue() : null; + } + + @Override + public String getAsString() { + if (nativeValue != null) { + if (nativeValue instanceof DecimalNode) { + BigDecimal value = nativeValue.decimalValue(); + return value.toPlainString(); + } + return nativeValue.asText(); + } + return null; + } + + @Override + public boolean getAsBoolean() { + if (nativeValue != null && nativeValue.isValueNode() && nativeValue.isBoolean()) { + return nativeValue.asBoolean(); + } + return super.getAsBoolean(); + } + } + + private static ObjectMapper createObjectMapper() { + ObjectMapper retVal = new ObjectMapper(); + retVal = retVal.setNodeFactory(new JsonNodeFactory(true)); + retVal = retVal.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS); + retVal = retVal.enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS); + retVal = retVal.disable(JsonParser.Feature.INCLUDE_SOURCE_IN_LOCATION); + retVal = retVal.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET); + retVal = retVal.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE); + retVal = retVal.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); + return retVal; + } +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonWriter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonWriter.java new file mode 100644 index 00000000000..54c40e36e9c --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonWriter.java @@ -0,0 +1,197 @@ +package ca.uhn.fhir.parser.json.jackson; + +import ca.uhn.fhir.parser.json.JsonLikeWriter; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.PrettyPrinter; +import com.fasterxml.jackson.core.util.DefaultIndenter; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.core.util.Separators; + +import java.io.IOException; +import java.io.Writer; +import java.math.BigDecimal; +import java.math.BigInteger; + +public class JacksonWriter extends JsonLikeWriter { + + private JsonGenerator myJsonGenerator; + + public JacksonWriter(JsonFactory theJsonFactory, Writer theWriter) throws IOException { + myJsonGenerator = theJsonFactory.createGenerator(theWriter); + setWriter(theWriter); + } + + public JacksonWriter() { + } + + @Override + public JsonLikeWriter init() { + if (isPrettyPrint()) { + DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter() { + + /** + * Objects should serialize as + *
+				 * {
+				 *    "key": "value"
+				 * }
+				 * 
+ * in order to be consistent with Gson behaviour, instead of the jackson default + *
+				 * {
+				 *    "key" : "value"
+				 * }
+				 * 
+ */ + @Override + public DefaultPrettyPrinter withSeparators(Separators separators) { + _separators = separators; + _objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " "; + return this; + } + + }; + prettyPrinter = prettyPrinter.withObjectIndenter(new DefaultIndenter(" ", "\n")); + + myJsonGenerator.setPrettyPrinter(prettyPrinter); + } + return this; + } + + @Override + public JsonLikeWriter flush() { + return this; + } + + @Override + public void close() throws IOException { + myJsonGenerator.close(); + } + + @Override + public JsonLikeWriter beginObject() throws IOException { + myJsonGenerator.writeStartObject(); + return this; + } + + @Override + public JsonLikeWriter beginObject(String name) throws IOException { + myJsonGenerator.writeObjectFieldStart(name); + return this; + } + + @Override + public JsonLikeWriter beginArray(String name) throws IOException { + myJsonGenerator.writeArrayFieldStart(name); + return this; + } + + @Override + public JsonLikeWriter write(String value) throws IOException { + myJsonGenerator.writeObject(value); + return this; + } + + @Override + public JsonLikeWriter write(BigInteger value) throws IOException { + myJsonGenerator.writeObject(value); + return this; + } + + @Override + public JsonLikeWriter write(BigDecimal value) throws IOException { + myJsonGenerator.writeObject(value); + return this; + } + + @Override + public JsonLikeWriter write(long value) throws IOException { + myJsonGenerator.writeObject(value); + return this; + } + + @Override + public JsonLikeWriter write(double value) throws IOException { + myJsonGenerator.writeObject(value); + return this; + } + + @Override + public JsonLikeWriter write(Boolean value) throws IOException { + myJsonGenerator.writeObject(value); + return this; + } + + @Override + public JsonLikeWriter write(boolean value) throws IOException { + myJsonGenerator.writeObject(value); + return this; + } + + @Override + public JsonLikeWriter writeNull() throws IOException { + myJsonGenerator.writeNull(); + return this; + } + + @Override + public JsonLikeWriter write(String name, String value) throws IOException { + myJsonGenerator.writeObjectField(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, BigInteger value) throws IOException { + myJsonGenerator.writeObjectField(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, BigDecimal value) throws IOException { + myJsonGenerator.writeObjectField(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, long value) throws IOException { + myJsonGenerator.writeObjectField(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, double value) throws IOException { + myJsonGenerator.writeObjectField(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, Boolean value) throws IOException { + myJsonGenerator.writeObjectField(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, boolean value) throws IOException { + myJsonGenerator.writeObjectField(name, value); + return this; + } + + @Override + public JsonLikeWriter endObject() throws IOException { + myJsonGenerator.writeEndObject(); + return this; + } + + @Override + public JsonLikeWriter endArray() throws IOException { + myJsonGenerator.writeEndArray(); + return this; + } + + @Override + public JsonLikeWriter endBlock() throws IOException { + myJsonGenerator.writeEndObject(); + return this; + } +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/json/JsonLikeStructureTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/json/JsonLikeStructureTest.java index bc120a0e2c6..3b71f8a8840 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/json/JsonLikeStructureTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/json/JsonLikeStructureTest.java @@ -5,6 +5,7 @@ import static org.junit.Assert.assertNotNull; import java.io.StringReader; +import ca.uhn.fhir.parser.json.jackson.JacksonStructure; import org.junit.Test; public class JsonLikeStructureTest { @@ -39,7 +40,7 @@ public class JsonLikeStructureTest { @Test public void testStructureLoading() { StringReader reader = new StringReader(TEST_STRUCTURELOADING_DATA); - JsonLikeStructure jsonStructure = new GsonStructure(); + JsonLikeStructure jsonStructure = new JacksonStructure(); jsonStructure.load(reader); JsonLikeObject rootObject = jsonStructure.getRootObject(); @@ -70,7 +71,7 @@ public class JsonLikeStructureTest { @Test public void testJsonAndDataTypes() { StringReader reader = new StringReader(TEST_JSONTYPES_DATA); - JsonLikeStructure jsonStructure = new GsonStructure(); + JsonLikeStructure jsonStructure = new JacksonStructure(); jsonStructure.load(reader); JsonLikeObject rootObject = jsonStructure.getRootObject(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index f110b447a96..56c98b94cfd 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -5368,7 +5368,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { @Test public void testValidateJsonWithDuplicateKey() throws IOException { - String inputStr = "{\"resourceType\":\"Patient\", \"name\":[{\"text\":foo\"}], name:[{\"text\":\"foo\"}] }"; + String inputStr = "{\"resourceType\":\"Patient\", \"name\":[{\"text\":\"foo\"}], \"name\":[{\"text\":\"foo\"}] }"; HttpPost post = new HttpPost(ourServerBase + "/Patient/$validate"); post.setEntity(new StringEntity(inputStr, ContentType.create(Constants.CT_FHIR_JSON_NEW, "UTF-8"))); @@ -5378,7 +5378,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info(resp); assertEquals(412, response.getStatusLine().getStatusCode()); - assertThat(resp, stringContainsInOrder("Error parsing JSON source: Syntax error in json reading special word false at Line 1")); + assertThat(resp, stringContainsInOrder("Duplicated property name: name")); } finally { response.getEntity().getContent().close(); response.close(); diff --git a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2_1Test.java b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2_1Test.java index b611feb0055..f8a232a60a4 100644 --- a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2_1Test.java +++ b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2_1Test.java @@ -1518,13 +1518,13 @@ public class JsonParserDstu2_1Test { ourCtx.newJsonParser().parseResource("FOO"); fail(); } catch (DataFormatException e) { - assertEquals("Failed to parse JSON content, error was: Content does not appear to be FHIR JSON, first non-whitespace character was: 'F' (must be '{')", e.getMessage()); + assertEquals("Failed to parse JSON encoded FHIR content: Content does not appear to be FHIR JSON, first non-whitespace character was: 'F' (must be '{')", e.getMessage()); } try { ourCtx.newJsonParser().parseResource("[\"aaa\"]"); fail(); } catch (DataFormatException e) { - assertEquals("Failed to parse JSON content, error was: Content does not appear to be FHIR JSON, first non-whitespace character was: '[' (must be '{')", e.getMessage()); + assertEquals("Failed to parse JSON encoded FHIR content: Content does not appear to be FHIR JSON, first non-whitespace character was: '[' (must be '{')", e.getMessage()); } assertEquals(Bundle.class, ourCtx.newJsonParser().parseResource(" {\"resourceType\" : \"Bundle\"}").getClass()); diff --git a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2_1Test.java b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2_1Test.java index be084ff6cf4..2e810637a11 100644 --- a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2_1Test.java +++ b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2_1Test.java @@ -1996,41 +1996,27 @@ public class XmlParserDstu2_1Test { assertThat(output, stringContainsInOrder( "{", - " \"resourceType\": \"Patient\",", - " \"id\": \"someid\",", - " \"_id\": {", - " \"fhir_comments\": [", - " \" comment 1 \"", - " ]", - " },", - " \"extension\": [", - " {", - " \"fhir_comments\": [", - " \" comment 2 \",", - " \" comment 7 \"", - " ],", - " \"url\": \"urn:patientext:att\",", - " \"valueAttachment\": {", - " \"fhir_comments\": [", - " \" comment 3 \",", - " \" comment 6 \"", - " ],", - " \"contentType\": \"aaaa\",", - " \"_contentType\": {", - " \"fhir_comments\": [", - " \" comment 4 \"", - " ]", - " },", - " \"data\": \"AAAA\",", - " \"_data\": {", - " \"fhir_comments\": [", - " \" comment 5 \"", - " ]", - " }", - " }", - " }", - " ]", - "}" + " \"resourceType\": \"Patient\",", + " \"id\": \"someid\",", + " \"_id\": {", + " \"fhir_comments\": [ \" comment 1 \" ]", + " },", + " \"extension\": [ {", + " \"fhir_comments\": [ \" comment 2 \", \" comment 7 \" ],", + " \"url\": \"urn:patientext:att\",", + " \"valueAttachment\": {", + " \"fhir_comments\": [ \" comment 3 \", \" comment 6 \" ],", + " \"contentType\": \"aaaa\",", + " \"_contentType\": {", + " \"fhir_comments\": [ \" comment 4 \" ]", + " },", + " \"data\": \"AAAA\",", + " \"_data\": {", + " \"fhir_comments\": [ \" comment 5 \" ]", + " }", + " }", + " } ]", + "}" )); //@formatter:on diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java index f57d0d49993..bef8cddd281 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java @@ -498,37 +498,28 @@ public class JsonParserDstu2Test { String enc = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(p); ourLog.info(enc); - //@formatter:off String actual = enc.trim(); ourLog.info("Actual:\n{}", actual); - assertEquals("{\n" + - " \"resourceType\": \"Patient\",\n" + - " \"meta\": {\n" + - " \"security\": [\n" + - " {\n" + - " \"system\": \"SYSTEM1\",\n" + - " \"version\": \"VERSION1\",\n" + - " \"code\": \"CODE1\",\n" + - " \"display\": \"DISPLAY1\"\n" + - " },\n" + - " {\n" + - " \"system\": \"SYSTEM2\",\n" + - " \"version\": \"VERSION2\",\n" + - " \"code\": \"CODE2\",\n" + - " \"display\": \"DISPLAY2\"\n" + - " }\n" + - " ]\n" + - " },\n" + - " \"name\": [\n" + - " {\n" + - " \"family\": [\n" + - " \"FAMILY\"\n" + - " ]\n" + - " }\n" + - " ]\n" + - "}", actual); - //@formatter:on + assertThat(actual, stringContainsInOrder("{", + " \"resourceType\": \"Patient\",", + " \"meta\": {", + " \"security\": [ {", + " \"system\": \"SYSTEM1\",", + " \"version\": \"VERSION1\",", + " \"code\": \"CODE1\",", + " \"display\": \"DISPLAY1\"", + " }, {", + " \"system\": \"SYSTEM2\",", + " \"version\": \"VERSION2\",", + " \"code\": \"CODE2\",", + " \"display\": \"DISPLAY2\"", + " } ]", + " },", + " \"name\": [ {", + " \"family\": [ \"FAMILY\" ]", + " } ]", + "}")); Patient parsed = ourCtx.newJsonParser().parseResource(Patient.class, enc); List gotLabels = ResourceMetadataKeyEnum.SECURITY_LABELS.get(parsed); diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserDstu2Test.java index 3f800d809ce..f577cc8502b 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserDstu2Test.java @@ -1,16 +1,15 @@ package ca.uhn.fhir.parser.jsonlike; -import java.io.StringReader; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.IJsonLikeParser; +import ca.uhn.fhir.parser.json.JsonLikeStructure; +import ca.uhn.fhir.parser.json.jackson.JacksonStructure; +import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.junit.AfterClass; import org.junit.Test; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.parser.IJsonLikeParser; -import ca.uhn.fhir.parser.json.GsonStructure; -import ca.uhn.fhir.parser.json.JsonLikeStructure; -import ca.uhn.fhir.util.TestUtil; +import java.io.StringReader; public class JsonLikeParserDstu2Test { private static FhirContext ourCtx = FhirContext.forDstu2(); @@ -28,7 +27,7 @@ public class JsonLikeParserDstu2Test { String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(parsed); ourLog.info(encoded); - JsonLikeStructure jsonLikeStructure = new GsonStructure(); + JsonLikeStructure jsonLikeStructure = new JacksonStructure(); jsonLikeStructure.load(new StringReader(encoded)); IJsonLikeParser jsonLikeparser = (IJsonLikeParser)ourCtx.newJsonParser(); diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java index 3b58f877a14..b87a3408961 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java @@ -72,7 +72,8 @@ public class JsonParserDstu3Test { p.parseResource(input); fail(); } catch (DataFormatException e) { - assertEquals("Found incorrect type for element subject - Expected OBJECT and found SCALAR (STRING)", e.getMessage()); + assertEquals("Failed to parse JSON encoded FHIR content: Unexpected character ('=' (code 61)): was expecting a colon to separate field name and value\n" + + " at [Source: UNKNOWN; line: 4, column: 18]", e.getMessage()); } } @@ -488,32 +489,25 @@ public class JsonParserDstu3Test { String enc = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(p); ourLog.info(enc); - //@formatter:off - assertEquals("{\n" + - " \"resourceType\": \"Patient\",\n" + - " \"meta\": {\n" + - " \"security\": [\n" + - " {\n" + - " \"system\": \"SYSTEM1\",\n" + - " \"version\": \"VERSION1\",\n" + - " \"code\": \"CODE1\",\n" + - " \"display\": \"DISPLAY1\"\n" + - " },\n" + - " {\n" + - " \"system\": \"SYSTEM2\",\n" + - " \"version\": \"VERSION2\",\n" + - " \"code\": \"CODE2\",\n" + - " \"display\": \"DISPLAY2\"\n" + - " }\n" + - " ]\n" + - " },\n" + - " \"name\": [\n" + - " {\n" + - " \"family\": \"FAMILY\"\n" + - " }\n" + - " ]\n" + - "}", enc.trim()); - //@formatter:on + assertThat(enc.trim(), stringContainsInOrder("{", + " \"resourceType\": \"Patient\",", + " \"meta\": {", + " \"security\": [ {", + " \"system\": \"SYSTEM1\",", + " \"version\": \"VERSION1\",", + " \"code\": \"CODE1\",", + " \"display\": \"DISPLAY1\"", + " }, {", + " \"system\": \"SYSTEM2\",", + " \"version\": \"VERSION2\",", + " \"code\": \"CODE2\",", + " \"display\": \"DISPLAY2\"", + " } ]", + " },", + " \"name\": [ {", + " \"family\": \"FAMILY\"", + " } ]", + "}")); Patient parsed = ourCtx.newJsonParser().parseResource(Patient.class, enc); List gotLabels = parsed.getMeta().getSecurity(); @@ -1401,7 +1395,8 @@ public class JsonParserDstu3Test { String input = "{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.0000000000000001}}"; Observation obs = ourCtx.newJsonParser().parseResource(Observation.class, input); - assertEquals("0.0000000000000001", ((Quantity) obs.getValue()).getValueElement().getValueAsString()); + DecimalType valueElement = ((Quantity) obs.getValue()).getValueElement(); + assertEquals("0.0000000000000001", valueElement.getValueAsString()); String str = ourCtx.newJsonParser().encodeResourceToString(obs); ourLog.info(str); @@ -1993,13 +1988,13 @@ public class JsonParserDstu3Test { ourCtx.newJsonParser().parseResource("FOO"); fail(); } catch (DataFormatException e) { - assertEquals("Failed to parse JSON content, error was: Content does not appear to be FHIR JSON, first non-whitespace character was: 'F' (must be '{')", e.getMessage()); + assertEquals("Failed to parse JSON encoded FHIR content: Content does not appear to be FHIR JSON, first non-whitespace character was: 'F' (must be '{')", e.getMessage()); } try { ourCtx.newJsonParser().parseResource("[\"aaa\"]"); fail(); } catch (DataFormatException e) { - assertEquals("Failed to parse JSON content, error was: Content does not appear to be FHIR JSON, first non-whitespace character was: '[' (must be '{')", e.getMessage()); + assertEquals("Failed to parse JSON encoded FHIR content: Content does not appear to be FHIR JSON, first non-whitespace character was: '[' (must be '{')", e.getMessage()); } assertEquals(Bundle.class, ourCtx.newJsonParser().parseResource(" {\"resourceType\" : \"Bundle\"}").getClass()); @@ -2221,25 +2216,47 @@ public class JsonParserDstu3Test { @Test public void testParseWithPrecision() { - String input = "{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.000000000000000100}}"; - Observation obs = ourCtx.newJsonParser().parseResource(Observation.class, input); - DecimalType valueElement = ((Quantity) obs.getValue()).getValueElement(); - assertEquals("0.000000000000000100", valueElement.getValueAsString()); +// BigDecimal d0 = new BigDecimal("0.1"); +// BigDecimal d1 = new BigDecimal("0.1000"); +// +// ourLog.info("Value: {}", d0); +// ourLog.info("Value: {}", d1); - String str = ourCtx.newJsonParser().encodeResourceToString(obs); - ourLog.info(str); - assertEquals("{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.000000000000000100}}", str); + { + String input = "{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.0100}}"; + Observation obs = ourCtx.newJsonParser().parseResource(Observation.class, input); + DecimalType valueElement = ((Quantity) obs.getValue()).getValueElement(); + assertEquals("0.0100", valueElement.getValueAsString()); + String str = ourCtx.newJsonParser().encodeResourceToString(obs); + ourLog.info(str); + assertEquals("{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.0100}}", str); + } + { + String input = "{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.000000000000000100}}"; + Observation obs = ourCtx.newJsonParser().parseResource(Observation.class, input); + DecimalType valueElement = ((Quantity) obs.getValue()).getValueElement(); + assertEquals("0.000000000000000100", valueElement.getValueAsString()); + String str = ourCtx.newJsonParser().encodeResourceToString(obs); + ourLog.info(str); + assertEquals("{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.000000000000000100}}", str); + } } - @Test(expected = DataFormatException.class) + @Test public void testParseWithTrailingContent() { String bundle = "{\n" + - " \"resourceType\" : \"Bundle\",\n" + - " \"total\" : 1\n" + + " \"resourceType\": \"Bundle\",\n" + + " \"total\": 1\n" + "}}"; - ourCtx.newJsonParser().parseResource(Bundle.class, bundle); + try { + ourCtx.newJsonParser().parseResource(Bundle.class, bundle); + fail(); + } catch (DataFormatException e) { + assertEquals("Failed to parse JSON encoded FHIR content: Unexpected close marker '}': expected ']' (for root starting at [Source: UNKNOWN; line: 1, column: 0])\n" + + " at [Source: UNKNOWN; line: 4, column: 3]", e.getMessage()); + } } @Test diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java index 8023417d1de..98fdae848b5 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java @@ -2508,42 +2508,27 @@ public class XmlParserDstu3Test { output = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(pat); ourLog.info(output); - assertThat(output, stringContainsInOrder( - "{", + assertThat(output, stringContainsInOrder("{", " \"resourceType\": \"Patient\",", " \"id\": \"someid\",", " \"_id\": {", - " \"fhir_comments\": [", - " \" comment 1 \"", - " ]", + " \"fhir_comments\": [ \" comment 1 \" ]", " },", - " \"extension\": [", - " {", - " \"fhir_comments\": [", - " \" comment 2 \",", - " \" comment 7 \"", - " ],", - " \"url\": \"urn:patientext:att\",", - " \"valueAttachment\": {", - " \"fhir_comments\": [", - " \" comment 3 \",", - " \" comment 6 \"", - " ],", - " \"contentType\": \"aaaa\",", - " \"_contentType\": {", - " \"fhir_comments\": [", - " \" comment 4 \"", - " ]", - " },", - " \"data\": \"AAAA\",", - " \"_data\": {", - " \"fhir_comments\": [", - " \" comment 5 \"", - " ]", - " }", + " \"extension\": [ {", + " \"fhir_comments\": [ \" comment 2 \", \" comment 7 \" ],", + " \"url\": \"urn:patientext:att\",", + " \"valueAttachment\": {", + " \"fhir_comments\": [ \" comment 3 \", \" comment 6 \" ],", + " \"contentType\": \"aaaa\",", + " \"_contentType\": {", + " \"fhir_comments\": [ \" comment 4 \" ]", + " },", + " \"data\": \"AAAA\",", + " \"_data\": {", + " \"fhir_comments\": [ \" comment 5 \" ]", " }", " }", - " ]", + " } ]", "}")); } diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserDstu3Test.java index 082a737d845..d63d4dcb8d9 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserDstu3Test.java @@ -1,426 +1,404 @@ -package ca.uhn.fhir.parser.jsonlike; - -import java.io.IOException; -import java.io.StringReader; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Stack; - -import org.apache.commons.io.IOUtils; -import org.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.dstu3.model.Extension; -import org.hl7.fhir.dstu3.model.Patient; -import org.hl7.fhir.dstu3.model.Reference; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Test; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.parser.IJsonLikeParser; -import ca.uhn.fhir.parser.json.GsonStructure; -import ca.uhn.fhir.parser.json.JsonLikeStructure; -import ca.uhn.fhir.parser.json.JsonLikeWriter; -import ca.uhn.fhir.util.TestUtil; - -public class JsonLikeParserDstu3Test { - private static FhirContext ourCtx = FhirContext.forDstu3(); - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonLikeParserDstu3Test.class); - - /** - * Test for JSON Parser with user-supplied JSON-like structure (use default GSON) - */ - @Test - public void testJsonLikeParseAndEncodeBundleFromXmlToJson() throws Exception { - String content = IOUtils.toString(JsonLikeParserDstu3Test.class.getResourceAsStream("/bundle_with_woven_obs.xml")); - - Bundle parsed = ourCtx.newXmlParser().parseResource(Bundle.class, content); - - String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(parsed); - ourLog.info(encoded); - - JsonLikeStructure jsonLikeStructure = new GsonStructure(); - jsonLikeStructure.load(new StringReader(encoded)); - - IJsonLikeParser jsonLikeparser = (IJsonLikeParser)ourCtx.newJsonParser(); - - Bundle bundle = jsonLikeparser.parseResource(Bundle.class, jsonLikeStructure); - - } - - /** - * Test JSON-Like writer using custom stream writer - * - */ - @Test - public void testJsonLikeParseWithCustomJSONStreamWriter() throws Exception { - String refVal = "http://my.org/FooBar"; - - Patient fhirPat = new Patient(); - fhirPat.addExtension().setUrl("x1").setValue(new Reference(refVal)); - - IJsonLikeParser jsonLikeParser = (IJsonLikeParser)ourCtx.newJsonParser(); - JsonLikeMapWriter jsonLikeWriter = new JsonLikeMapWriter(); - - jsonLikeParser.encodeResourceToJsonLikeWriter(fhirPat, jsonLikeWriter); - Map jsonLikeMap = jsonLikeWriter.getResultMap(); - - System.out.println("encoded map: " + jsonLikeMap.toString()); - - Assert.assertNotNull("Encoded resource missing 'resourceType' element", jsonLikeMap.get("resourceType")); - Assert.assertEquals("Expecting 'resourceType'='Patient'; found '"+jsonLikeMap.get("resourceType")+"'", jsonLikeMap.get("resourceType"), "Patient"); - - Assert.assertNotNull("Encoded resource missing 'extension' element", jsonLikeMap.get("extension")); - Assert.assertTrue("'extension' element is not a List", (jsonLikeMap.get("extension") instanceof List)); - - List extensions = (List)jsonLikeMap.get("extension"); - Assert.assertEquals("'extnesion' array has more than one entry", 1, extensions.size()); - Assert.assertTrue("'extension' array entry is not a Map", (extensions.get(0) instanceof Map)); - - Map extension = (Map)extensions.get(0); - Assert.assertNotNull("'extension' entry missing 'url' member", extension.get("url")); - Assert.assertTrue("'extension' entry 'url' member is not a String", (extension.get("url") instanceof String)); - Assert.assertEquals("Expecting '/extension[]/url' = 'x1'; found '"+extension.get("url")+"'", "x1", (String)extension.get("url")); - - } - - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); - } - - - - public static class JsonLikeMapWriter extends JsonLikeWriter { - - private Map target; - - private static class Block { - private BlockType type; - private String name; - private Map object; - private List array; - public Block(BlockType type) { - this.type = type; - } - public BlockType getType() { - return type; - } - public String getName() { - return name; - } - public void setName(String currentName) { - this.name = currentName; - } - public Map getObject() { - return object; - } - public void setObject(Map currentObject) { - this.object = currentObject; - } - public List getArray() { - return array; - } - public void setArray(List currentArray) { - this.array = currentArray; - } - } - private enum BlockType { - NONE, OBJECT, ARRAY - } - private Block currentBlock = new Block(BlockType.NONE); - private Stack blockStack = new Stack(); - - public JsonLikeMapWriter () { - super(); - } - - public Map getResultMap() { - return target; - } - public void setResultMap(Map target) { - this.target = target; - } - - @Override - public JsonLikeWriter init() throws IOException { - if (target != null) { - target.clear(); - } - currentBlock = new Block(BlockType.NONE); - blockStack.clear(); - return this; - } - - @Override - public JsonLikeWriter flush() throws IOException { - if (currentBlock.getType() != BlockType.NONE) { - throw new IOException("JsonLikeStreamWriter.flush() called but JSON document is not finished"); - } - return this; - } - - @Override - public void close() { - // nothing to do - } - - @Override - public JsonLikeWriter beginObject() throws IOException { - if (currentBlock.getType() == BlockType.OBJECT) { - throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); - } - Map newObject = null; - if (currentBlock.getType() == BlockType.NONE) { - if (null == target) { - // for this test, we don't care about ordering of map elements - // target = new EntryOrderedMap(); - target = new HashMap(); - } - newObject = target; - } else { - // for this test, we don't care about ordering of map elements - // newObject = new EntryOrderedMap(); - newObject = new HashMap(); - } - blockStack.push(currentBlock); - currentBlock = new Block(BlockType.OBJECT); - currentBlock.setObject(newObject); - return this; - } - - @Override - public JsonLikeWriter beginArray() throws IOException { - if (currentBlock.getType() == BlockType.NONE) { - throw new IOException("JsonLikeStreamWriter.beginArray() called but only beginObject() is allowed here."); - } - blockStack.push(currentBlock); - currentBlock = new Block(BlockType.ARRAY); - currentBlock.setArray(new ArrayList()); - return this; - } - - @Override - public JsonLikeWriter beginObject(String name) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - blockStack.push(currentBlock); - currentBlock = new Block(BlockType.OBJECT); - currentBlock.setName(name); - // for this test, we don't care about ordering of map elements - // currentBlock.setObject(new EntryOrderedMap()); - currentBlock.setObject(new HashMap()); - return this; - } - - @Override - public JsonLikeWriter beginArray(String name) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - blockStack.push(currentBlock); - currentBlock = new Block(BlockType.ARRAY); - currentBlock.setName(name); - currentBlock.setArray(new ArrayList()); - return this; - } - - @Override - public JsonLikeWriter write(String value) throws IOException { - if (currentBlock.getType() == BlockType.OBJECT) { - throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); - } - currentBlock.getArray().add(value); - return this; - } - - @Override - public JsonLikeWriter write(BigInteger value) throws IOException { - if (currentBlock.getType() == BlockType.OBJECT) { - throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); - } - currentBlock.getArray().add(value); - return this; - } - - @Override - public JsonLikeWriter write(BigDecimal value) throws IOException { - if (currentBlock.getType() == BlockType.OBJECT) { - throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); - } - currentBlock.getArray().add(value); - return this; - } - - @Override - public JsonLikeWriter write(long value) throws IOException { - if (currentBlock.getType() == BlockType.OBJECT) { - throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); - } - currentBlock.getArray().add(Long.valueOf(value)); - return this; - } - - @Override - public JsonLikeWriter write(double value) throws IOException { - if (currentBlock.getType() == BlockType.OBJECT) { - throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); - } - currentBlock.getArray().add(Double.valueOf(value)); - return this; - } - - @Override - public JsonLikeWriter write(Boolean value) throws IOException { - if (currentBlock.getType() == BlockType.OBJECT) { - throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); - } - currentBlock.getArray().add(value); - return this; - } - - @Override - public JsonLikeWriter write(boolean value) throws IOException { - if (currentBlock.getType() == BlockType.OBJECT) { - throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); - } - currentBlock.getArray().add(Boolean.valueOf(value)); - return this; - } - - @Override - public JsonLikeWriter writeNull() throws IOException { - if (currentBlock.getType() == BlockType.OBJECT) { - throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); - } - currentBlock.getArray().add(null); - return this; - } - - @Override - public JsonLikeWriter write(String name, String value) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - currentBlock.getObject().put(name, value); - return this; - } - - @Override - public JsonLikeWriter write(String name, BigInteger value) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - currentBlock.getObject().put(name, value); - return this; - } - @Override - public JsonLikeWriter write(String name, BigDecimal value) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - currentBlock.getObject().put(name, value); - return this; - } - - @Override - public JsonLikeWriter write(String name, long value) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - currentBlock.getObject().put(name, Long.valueOf(value)); - return this; - } - - @Override - public JsonLikeWriter write(String name, double value) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - currentBlock.getObject().put(name, Double.valueOf(value)); - return this; - } - - @Override - public JsonLikeWriter write(String name, Boolean value) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - currentBlock.getObject().put(name, value); - return this; - } - - @Override - public JsonLikeWriter write(String name, boolean value) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - currentBlock.getObject().put(name, Boolean.valueOf(value)); - return this; - } - - @Override - public JsonLikeWriter writeNull(String name) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - currentBlock.getObject().put(name, null); - return this; - } - - @Override - public JsonLikeWriter endObject() throws IOException { - if (currentBlock.getType() == BlockType.NONE) { - ourLog.error("JsonLikeStreamWriter.endObject(); called with no active JSON document"); - } else { - if (currentBlock.getType() != BlockType.OBJECT) { - ourLog.error("JsonLikeStreamWriter.endObject(); called outside a JSON object. (Use endArray() instead?)"); - } - endBlock(); - } - return this; - } - - @Override - public JsonLikeWriter endArray() throws IOException { - if (currentBlock.getType() == BlockType.NONE) { - ourLog.error("JsonLikeStreamWriter.endArray(); called with no active JSON document"); - } else { - if (currentBlock.getType() != BlockType.ARRAY) { - ourLog.error("JsonLikeStreamWriter.endArray(); called outside a JSON array. (Use endObject() instead?)"); - } - endBlock(); - } - return this; - } - - @Override - public JsonLikeWriter endBlock() throws IOException { - if (currentBlock.getType() == BlockType.NONE) { - ourLog.error("JsonLikeStreamWriter.endBlock(); called with no active JSON document"); - } else { - Object toPut = null; - if (currentBlock.getType() == BlockType.ARRAY) { - toPut = currentBlock.getArray(); - } else { - toPut = currentBlock.getObject(); - } - Block parentBlock = blockStack.pop(); - if (parentBlock.getType() == BlockType.OBJECT) { - parentBlock.getObject().put(currentBlock.getName(), toPut); - } else - if (parentBlock.getType() == BlockType.ARRAY) { - parentBlock.getArray().add(toPut); - } - currentBlock = parentBlock; - } - return this; - } - - } - -} +package ca.uhn.fhir.parser.jsonlike; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.IJsonLikeParser; +import ca.uhn.fhir.parser.json.JsonLikeStructure; +import ca.uhn.fhir.parser.json.JsonLikeWriter; +import ca.uhn.fhir.parser.json.jackson.JacksonStructure; +import ca.uhn.fhir.util.TestUtil; +import org.apache.commons.io.IOUtils; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.Reference; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.io.StringReader; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; + +public class JsonLikeParserDstu3Test { + private static FhirContext ourCtx = FhirContext.forDstu3(); + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonLikeParserDstu3Test.class); + + /** + * Test for JSON Parser with user-supplied JSON-like structure (use default GSON) + */ + @Test + public void testJsonLikeParseAndEncodeBundleFromXmlToJson() throws Exception { + String content = IOUtils.toString(JsonLikeParserDstu3Test.class.getResourceAsStream("/bundle_with_woven_obs.xml")); + + Bundle parsed = ourCtx.newXmlParser().parseResource(Bundle.class, content); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(parsed); + ourLog.info(encoded); + + JsonLikeStructure jsonLikeStructure = new JacksonStructure(); + jsonLikeStructure.load(new StringReader(encoded)); + + IJsonLikeParser jsonLikeparser = (IJsonLikeParser)ourCtx.newJsonParser(); + + Bundle bundle = jsonLikeparser.parseResource(Bundle.class, jsonLikeStructure); + + } + + /** + * Test JSON-Like writer using custom stream writer + * + */ + @Test + public void testJsonLikeParseWithCustomJSONStreamWriter() throws Exception { + String refVal = "http://my.org/FooBar"; + + Patient fhirPat = new Patient(); + fhirPat.addExtension().setUrl("x1").setValue(new Reference(refVal)); + + IJsonLikeParser jsonLikeParser = (IJsonLikeParser)ourCtx.newJsonParser(); + JsonLikeMapWriter jsonLikeWriter = new JsonLikeMapWriter(); + + jsonLikeParser.encodeResourceToJsonLikeWriter(fhirPat, jsonLikeWriter); + Map jsonLikeMap = jsonLikeWriter.getResultMap(); + + System.out.println("encoded map: " + jsonLikeMap.toString()); + + Assert.assertNotNull("Encoded resource missing 'resourceType' element", jsonLikeMap.get("resourceType")); + Assert.assertEquals("Expecting 'resourceType'='Patient'; found '"+jsonLikeMap.get("resourceType")+"'", jsonLikeMap.get("resourceType"), "Patient"); + + Assert.assertNotNull("Encoded resource missing 'extension' element", jsonLikeMap.get("extension")); + Assert.assertTrue("'extension' element is not a List", (jsonLikeMap.get("extension") instanceof List)); + + List extensions = (List)jsonLikeMap.get("extension"); + Assert.assertEquals("'extnesion' array has more than one entry", 1, extensions.size()); + Assert.assertTrue("'extension' array entry is not a Map", (extensions.get(0) instanceof Map)); + + Map extension = (Map)extensions.get(0); + Assert.assertNotNull("'extension' entry missing 'url' member", extension.get("url")); + Assert.assertTrue("'extension' entry 'url' member is not a String", (extension.get("url") instanceof String)); + Assert.assertEquals("Expecting '/extension[]/url' = 'x1'; found '"+extension.get("url")+"'", "x1", (String)extension.get("url")); + + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + + public static class JsonLikeMapWriter extends JsonLikeWriter { + + private Map target; + + private static class Block { + private BlockType type; + private String name; + private Map object; + private List array; + public Block(BlockType type) { + this.type = type; + } + public BlockType getType() { + return type; + } + public String getName() { + return name; + } + public void setName(String currentName) { + this.name = currentName; + } + public Map getObject() { + return object; + } + public void setObject(Map currentObject) { + this.object = currentObject; + } + public List getArray() { + return array; + } + public void setArray(List currentArray) { + this.array = currentArray; + } + } + private enum BlockType { + NONE, OBJECT, ARRAY + } + private Block currentBlock = new Block(BlockType.NONE); + private Stack blockStack = new Stack(); + + public JsonLikeMapWriter () { + super(); + } + + public Map getResultMap() { + return target; + } + public void setResultMap(Map target) { + this.target = target; + } + + @Override + public JsonLikeWriter init() throws IOException { + if (target != null) { + target.clear(); + } + currentBlock = new Block(BlockType.NONE); + blockStack.clear(); + return this; + } + + @Override + public JsonLikeWriter flush() throws IOException { + if (currentBlock.getType() != BlockType.NONE) { + throw new IOException("JsonLikeStreamWriter.flush() called but JSON document is not finished"); + } + return this; + } + + @Override + public void close() { + // nothing to do + } + + @Override + public JsonLikeWriter beginObject() throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + Map newObject = null; + if (currentBlock.getType() == BlockType.NONE) { + if (null == target) { + // for this test, we don't care about ordering of map elements + // target = new EntryOrderedMap(); + target = new HashMap(); + } + newObject = target; + } else { + // for this test, we don't care about ordering of map elements + // newObject = new EntryOrderedMap(); + newObject = new HashMap(); + } + blockStack.push(currentBlock); + currentBlock = new Block(BlockType.OBJECT); + currentBlock.setObject(newObject); + return this; + } + + @Override + public JsonLikeWriter beginObject(String name) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + blockStack.push(currentBlock); + currentBlock = new Block(BlockType.OBJECT); + currentBlock.setName(name); + // for this test, we don't care about ordering of map elements + // currentBlock.setObject(new EntryOrderedMap()); + currentBlock.setObject(new HashMap()); + return this; + } + + @Override + public JsonLikeWriter beginArray(String name) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + blockStack.push(currentBlock); + currentBlock = new Block(BlockType.ARRAY); + currentBlock.setName(name); + currentBlock.setArray(new ArrayList()); + return this; + } + + @Override + public JsonLikeWriter write(String value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(value); + return this; + } + + @Override + public JsonLikeWriter write(BigInteger value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(value); + return this; + } + + @Override + public JsonLikeWriter write(BigDecimal value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(value); + return this; + } + + @Override + public JsonLikeWriter write(long value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(Long.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter write(double value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(Double.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter write(Boolean value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(value); + return this; + } + + @Override + public JsonLikeWriter write(boolean value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(Boolean.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter writeNull() throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(null); + return this; + } + + @Override + public JsonLikeWriter write(String name, String value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, BigInteger value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, value); + return this; + } + @Override + public JsonLikeWriter write(String name, BigDecimal value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, long value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, Long.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter write(String name, double value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, Double.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter write(String name, Boolean value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, boolean value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, Boolean.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter endObject() throws IOException { + if (currentBlock.getType() == BlockType.NONE) { + ourLog.error("JsonLikeStreamWriter.endObject(); called with no active JSON document"); + } else { + if (currentBlock.getType() != BlockType.OBJECT) { + ourLog.error("JsonLikeStreamWriter.endObject(); called outside a JSON object. (Use endArray() instead?)"); + } + endBlock(); + } + return this; + } + + @Override + public JsonLikeWriter endArray() throws IOException { + if (currentBlock.getType() == BlockType.NONE) { + ourLog.error("JsonLikeStreamWriter.endArray(); called with no active JSON document"); + } else { + if (currentBlock.getType() != BlockType.ARRAY) { + ourLog.error("JsonLikeStreamWriter.endArray(); called outside a JSON array. (Use endObject() instead?)"); + } + endBlock(); + } + return this; + } + + @Override + public JsonLikeWriter endBlock() { + if (currentBlock.getType() == BlockType.NONE) { + ourLog.error("JsonLikeStreamWriter.endBlock(); called with no active JSON document"); + } else { + Object toPut = null; + if (currentBlock.getType() == BlockType.ARRAY) { + toPut = currentBlock.getArray(); + } else { + toPut = currentBlock.getObject(); + } + Block parentBlock = blockStack.pop(); + if (parentBlock.getType() == BlockType.OBJECT) { + parentBlock.getObject().put(currentBlock.getName(), toPut); + } else + if (parentBlock.getType() == BlockType.ARRAY) { + parentBlock.getArray().add(toPut); + } + currentBlock = parentBlock; + } + return this; + } + + } + +} diff --git a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserHl7OrgDstu2Test.java b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserHl7OrgDstu2Test.java index e26ffe9f5cc..08615623b2a 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserHl7OrgDstu2Test.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserHl7OrgDstu2Test.java @@ -1097,12 +1097,6 @@ public class JsonParserHl7OrgDstu2Test { assertEquals("idsystem", p.getIdentifier().get(0).getSystem()); } - @Test - public void testParseSingleQuotes() { - ourCtx.newJsonParser().parseResource(Bundle.class, "{ \"resourceType\": \"Bundle\" }"); - ourCtx.newJsonParser().parseResource(Bundle.class, "{ 'resourceType': 'Bundle' }"); - } - /** * HAPI FHIR < 0.6 incorrectly used "resource" instead of "reference" */ diff --git a/hapi-fhir-structures-r4/pom.xml b/hapi-fhir-structures-r4/pom.xml index fdb10ac261e..4f16839bcf0 100644 --- a/hapi-fhir-structures-r4/pom.xml +++ b/hapi-fhir-structures-r4/pom.xml @@ -175,6 +175,11 @@ ${project.version} test + + org.awaitility + awaitility + test + @@ -215,9 +220,8 @@ - org.apache.jena - apache-jena-libs - pom + com.google.code.gson + gson test diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java index 5527e7602b6..e7783c95f7d 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java @@ -248,6 +248,12 @@ public class JsonParserR4Test extends BaseTest { } + @Test + public void testParseSingleQuotes() { + Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, "{ 'resourceType': 'Bundle', 'id': '123' }"); + assertEquals("123", bundle.getIdElement().getIdPart()); + } + @Test public void testEncodeBinary() { @@ -260,6 +266,18 @@ public class JsonParserR4Test extends BaseTest { assertEquals("{\"resourceType\":\"Binary\",\"contentType\":\"application/octet-stream\",\"data\":\"AAECAwQ=\"}", output); } + + @Test + public void testAlwaysUseUnixNewlines() { + Patient p = new Patient(); + p.setId("1"); + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(p); + assertEquals("{\n" + + " \"resourceType\": \"Patient\",\n" + + " \"id\": \"1\"\n" + + "}", encoded); + } + @Test public void testEncodeWithInvalidExtensionMissingUrl() { @@ -586,6 +604,14 @@ public class JsonParserR4Test extends BaseTest { * 15:20:41.708 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:574] - Encoded 1200 passes - 28ms / pass - 34.5 / second * 15:20:44.722 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:574] - Encoded 1300 passes - 29ms / pass - 34.4 / second * 15:20:47.716 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:574] - Encoded 1400 passes - 29ms / pass - 34.4 / second + * + * 2020-02-27 - Post #1673 + * 21:27:25.817 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:609] - Encoded 1100 passes - 28ms / pass - 35.5 / second + * 21:27:28.598 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:609] - Encoded 1200 passes - 28ms / pass - 35.5 / second + * 21:27:31.436 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:609] - Encoded 1300 passes - 28ms / pass - 35.5 / second + * 21:27:34.246 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:609] - Encoded 1400 passes - 28ms / pass - 35.5 / second + * 21:27:37.013 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:609] - Encoded 1500 passes - 28ms / pass - 35.6 / second + * 21:27:39.874 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:609] - Encoded 1600 passes - 28ms / pass - 35.5 / second */ @Test @Ignore @@ -655,6 +681,12 @@ public class JsonParserR4Test extends BaseTest { * 15:22:40.699 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:638] - Parsed 2500 passes - 12ms / pass - 79.7 / second * 15:22:42.135 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:638] - Parsed 2600 passes - 12ms / pass - 79.3 / second * + * 2020-02-27 - Post #1673 + * 21:29:38.157 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:687] - Parsed 2200 passes - 11ms / pass - 83.4 / second + * 21:29:39.374 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:687] - Parsed 2300 passes - 12ms / pass - 83.3 / second + * 21:29:40.576 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:687] - Parsed 2400 passes - 12ms / pass - 83.3 / second + * 21:29:41.778 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:687] - Parsed 2500 passes - 12ms / pass - 83.3 / second + * 21:29:42.999 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:687] - Parsed 2600 passes - 12ms / pass - 83.3 / second * */ @Test diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserTest.java index 72841e5450a..dcb523ac20e 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserTest.java @@ -1,5 +1,26 @@ package ca.uhn.fhir.parser.jsonlike; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.parser.IJsonLikeParser; +import ca.uhn.fhir.parser.json.JsonLikeArray; +import ca.uhn.fhir.parser.json.JsonLikeObject; +import ca.uhn.fhir.parser.json.JsonLikeStructure; +import ca.uhn.fhir.parser.json.JsonLikeValue; +import ca.uhn.fhir.parser.json.JsonLikeWriter; +import ca.uhn.fhir.parser.json.jackson.JacksonStructure; +import ca.uhn.fhir.parser.view.ExtPatient; +import ca.uhn.fhir.util.TestUtil; +import org.apache.commons.io.IOUtils; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Reference; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Test; + import java.io.IOException; import java.io.Reader; import java.io.StringReader; @@ -14,31 +35,6 @@ import java.util.Map; import java.util.Set; import java.util.Stack; -import org.apache.commons.io.IOUtils; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.IntegerType; -import org.hl7.fhir.r4.model.Patient; -import org.hl7.fhir.r4.model.Reference; -import org.hl7.fhir.r4.model.Extension; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Test; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.parser.IJsonLikeParser; -import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.parser.json.GsonStructure; -import ca.uhn.fhir.parser.json.JsonLikeArray; -import ca.uhn.fhir.parser.json.JsonLikeObject; -import ca.uhn.fhir.parser.json.JsonLikeStructure; -import ca.uhn.fhir.parser.json.JsonLikeValue; -import ca.uhn.fhir.parser.json.JsonLikeWriter; -import ca.uhn.fhir.parser.json.JsonLikeValue.ScalarType; -import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType; -import ca.uhn.fhir.parser.view.ExtPatient; -import ca.uhn.fhir.util.TestUtil; - public class JsonLikeParserTest { private static FhirContext ourCtx = FhirContext.forR4(); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonLikeParserTest.class); @@ -55,7 +51,7 @@ public class JsonLikeParserTest { String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(parsed); ourLog.info(encoded); - JsonLikeStructure jsonLikeStructure = new GsonStructure(); + JsonLikeStructure jsonLikeStructure = new JacksonStructure(); jsonLikeStructure.load(new StringReader(encoded)); IJsonLikeParser jsonLikeparser = (IJsonLikeParser)ourCtx.newJsonParser(); @@ -258,17 +254,6 @@ public class JsonLikeParserTest { return this; } - @Override - public JsonLikeWriter beginArray() throws IOException { - if (currentBlock.getType() == BlockType.NONE) { - throw new IOException("JsonLikeStreamWriter.beginArray() called but only beginObject() is allowed here."); - } - blockStack.push(currentBlock); - currentBlock = new Block(BlockType.ARRAY); - currentBlock.setArray(new ArrayList()); - return this; - } - @Override public JsonLikeWriter beginObject(String name) throws IOException { if (currentBlock.getType() == BlockType.ARRAY) { @@ -429,15 +414,6 @@ public class JsonLikeParserTest { return this; } - @Override - public JsonLikeWriter writeNull(String name) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - currentBlock.getObject().put(name, null); - return this; - } - @Override public JsonLikeWriter endObject() throws IOException { if (currentBlock.getType() == BlockType.NONE) { @@ -452,7 +428,7 @@ public class JsonLikeParserTest { } @Override - public JsonLikeWriter endArray() throws IOException { + public JsonLikeWriter endArray() { if (currentBlock.getType() == BlockType.NONE) { ourLog.error("JsonLikeStreamWriter.endArray(); called with no active JSON document"); } else { @@ -465,11 +441,11 @@ public class JsonLikeParserTest { } @Override - public JsonLikeWriter endBlock() throws IOException { + public JsonLikeWriter endBlock() { if (currentBlock.getType() == BlockType.NONE) { ourLog.error("JsonLikeStreamWriter.endBlock(); called with no active JSON document"); } else { - Object toPut = null; + Object toPut; if (currentBlock.getType() == BlockType.ARRAY) { toPut = currentBlock.getArray(); } else { @@ -544,14 +520,9 @@ public class JsonLikeParserTest { return jsonLikeObject; } - @Override - public JsonLikeArray getRootArray() throws DataFormatException { - throw new DataFormatException("JSON document must be an object not an array for native Java Map structures"); - } - private class JsonMapObject extends JsonLikeObject { private Map nativeObject; - private Map jsonLikeMap = new LinkedHashMap(); + private Map jsonLikeMap = new LinkedHashMap<>(); public JsonMapObject (Map json) { this.nativeObject = json; @@ -585,7 +556,7 @@ public class JsonLikeParserTest { private class JsonMapArray extends JsonLikeArray { private List nativeArray; - private Map jsonLikeMap = new LinkedHashMap(); + private Map jsonLikeMap = new LinkedHashMap<>(); public JsonMapArray (List json) { this.nativeArray = json; @@ -603,7 +574,7 @@ public class JsonLikeParserTest { @Override public JsonLikeValue get(int index) { - Integer key = Integer.valueOf(index); + Integer key = index; JsonLikeValue result = null; if (jsonLikeMap.containsKey(key)) { result = jsonLikeMap.get(key); @@ -694,7 +665,7 @@ public class JsonLikeParserTest { @Override public boolean getAsBoolean() { if (nativeValue != null && isBoolean()) { - return ((Boolean)nativeValue).booleanValue(); + return (Boolean) nativeValue; } return super.getAsBoolean(); } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/BlockingContentR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/BlockingContentR4Test.java index dd6793ec4df..a646e95cb5b 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/BlockingContentR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/BlockingContentR4Test.java @@ -91,7 +91,7 @@ public class BlockingContentR4Test { if (myByteCount++ == 10) { ourLog.info("About to block..."); try { - Thread.sleep(30000); + Thread.sleep(3000); } catch (InterruptedException e) { ourLog.warn("Interrupted", e); } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ConsentInterceptorTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ConsentInterceptorTest.java index 20be8fee5bc..ab8ee8a1838 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ConsentInterceptorTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ConsentInterceptorTest.java @@ -35,12 +35,16 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.invocation.Invocation; import org.mockito.junit.MockitoJUnitRunner; import java.io.IOException; +import java.util.Collection; import java.util.List; import java.util.concurrent.TimeUnit; +import static org.awaitility.Awaitility.await; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.Matchers.not; import static org.junit.Assert.*; @@ -201,12 +205,13 @@ public class ConsentInterceptorTest { assertThat(responseContent, containsString("A DIAG")); } - verify(myConsentSvc, times(1)).startOperation(any(), any()); - verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any()); - verify(myConsentSvc, times(3)).willSeeResource(any(), any(), any()); - verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any()); - verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any()); + verify(myConsentSvc, timeout(10000).times(1)).startOperation(any(), any()); + verify(myConsentSvc, timeout(10000).times(2)).canSeeResource(any(), any(), any()); + verify(myConsentSvc, timeout(10000).times(3)).willSeeResource(any(), any(), any()); + verify(myConsentSvc, timeout(10000).times(1)).completeOperationSuccess(any(), any()); + verify(myConsentSvc, timeout(10000).times(0)).completeOperationFailure(any(), any(), any()); verifyNoMoreInteractions(myConsentSvc); + } @Test @@ -241,6 +246,7 @@ public class ConsentInterceptorTest { ourPatientProvider.store((Patient) new Patient().setActive(true).setId("PTA")); ourPatientProvider.store((Patient) new Patient().setActive(false).setId("PTB")); + reset(myConsentSvc); when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.canSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.willSeeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t->{ @@ -266,10 +272,10 @@ public class ConsentInterceptorTest { assertEquals("PTB", response.getEntry().get(1).getResource().getIdElement().getIdPart()); } - verify(myConsentSvc, times(1)).startOperation(any(), any()); - verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any()); - verify(myConsentSvc, times(3)).willSeeResource(any(), any(), any()); - verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any()); + verify(myConsentSvc, timeout(1000).times(1)).startOperation(any(), any()); + verify(myConsentSvc, timeout(1000).times(2)).canSeeResource(any(), any(), any()); + verify(myConsentSvc, timeout(1000).times(3)).willSeeResource(any(), any(), any()); + verify(myConsentSvc, timeout(1000).times(1)).completeOperationSuccess(any(), any()); verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any()); verifyNoMoreInteractions(myConsentSvc); } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/InterceptorThrowingExceptionR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/InterceptorThrowingExceptionR4Test.java index 25a9e5177ca..72bfe7f35fc 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/InterceptorThrowingExceptionR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/InterceptorThrowingExceptionR4Test.java @@ -7,6 +7,7 @@ import ca.uhn.fhir.rest.annotation.Read; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.test.utilities.JettyUtil; import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; @@ -21,7 +22,11 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Resource; -import org.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -30,11 +35,12 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; +import static org.awaitility.Awaitility.await; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.Matchers.contains; -import static org.junit.Assert.*; - -import ca.uhn.fhir.test.utilities.JettyUtil; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; public class InterceptorThrowingExceptionR4Test { @@ -57,7 +63,7 @@ public class InterceptorThrowingExceptionR4Test { } @After - public void after(){ + public void after() { ourServlet.getInterceptorService().unregisterAllInterceptors(); } @@ -84,26 +90,26 @@ public class InterceptorThrowingExceptionR4Test { @Test public void testFailureInProcessingCompletedNormally() throws Exception { - final List hit = new ArrayList<>(); + final List hit = Collections.synchronizedList(new ArrayList<>()); ourServlet.getInterceptorService().registerInterceptor(new InterceptorAdapter() { @Override public void processingCompletedNormally(ServletRequestDetails theRequestDetails) { hit.add(1); - throw new NullPointerException(); + throw new NullPointerException("Hit 1"); } }); ourServlet.getInterceptorService().registerInterceptor(new InterceptorAdapter() { @Override public void processingCompletedNormally(ServletRequestDetails theRequestDetails) { hit.add(2); - throw new NullPointerException(); + throw new NullPointerException("Hit 2"); } }); ourServlet.getInterceptorService().registerInterceptor(new InterceptorAdapter() { @Override public void processingCompletedNormally(ServletRequestDetails theRequestDetails) { hit.add(3); - throw new NullPointerException(); + throw new NullPointerException("Hit 3"); } }); @@ -119,6 +125,9 @@ public class InterceptorThrowingExceptionR4Test { assertEquals(200, status.getStatusLine().getStatusCode()); assertThat(response, containsString("FAM")); assertTrue(ourHitMethod); + + await().until(() -> hit.size() == 3); + ourLog.info("Hit: {}", hit); assertThat("Hits: " + hit.toString(), hit, contains(1, 2, 3)); @@ -165,7 +174,7 @@ public class InterceptorThrowingExceptionR4Test { proxyHandler.addServletWithMapping(servletHolder, "/*"); ourServer.setHandler(proxyHandler); JettyUtil.startServer(ourServer); - ourPort = JettyUtil.getPortForStartedServer(ourServer); + ourPort = JettyUtil.getPortForStartedServer(ourServer); PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); HttpClientBuilder builder = HttpClientBuilder.create(); diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml index 696b0b9c2f6..58ca47b8214 100644 --- a/hapi-fhir-testpage-overlay/pom.xml +++ b/hapi-fhir-testpage-overlay/pom.xml @@ -66,6 +66,10 @@ ${project.version} + + com.google.code.gson + gson + org.thymeleaf thymeleaf diff --git a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java index 9ed40191388..c0af8e3ea98 100644 --- a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java +++ b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java @@ -1,42 +1,59 @@ package ca.uhn.fhir.to; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; +import ca.uhn.fhir.model.primitive.BoundCodeDt; +import ca.uhn.fhir.model.primitive.DateTimeDt; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.client.impl.GenericClient; +import ca.uhn.fhir.rest.gclient.ICreateTyped; +import ca.uhn.fhir.rest.gclient.IHistory; +import ca.uhn.fhir.rest.gclient.IHistoryTyped; +import ca.uhn.fhir.rest.gclient.IHistoryUntyped; +import ca.uhn.fhir.rest.gclient.IQuery; +import ca.uhn.fhir.rest.gclient.IUntypedQuery; +import ca.uhn.fhir.rest.gclient.NumberClientParam.IMatches; +import ca.uhn.fhir.rest.gclient.QuantityClientParam; +import ca.uhn.fhir.rest.gclient.QuantityClientParam.IAndUnits; +import ca.uhn.fhir.rest.gclient.StringClientParam; +import ca.uhn.fhir.rest.gclient.TokenClientParam; +import ca.uhn.fhir.to.model.HomeRequest; +import ca.uhn.fhir.to.model.ResourceRequest; +import ca.uhn.fhir.to.model.TransactionRequest; +import com.google.gson.stream.JsonWriter; +import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.dstu3.model.CapabilityStatement; +import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestComponent; +import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestResourceComponent; +import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent; +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.instance.model.api.IBaseConformance; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.TreeSet; + import static org.apache.commons.lang3.StringUtils.defaultIfEmpty; import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; -import java.io.IOException; -import java.io.StringWriter; -import java.util.*; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.lang3.StringUtils; -import org.hl7.fhir.dstu3.model.CapabilityStatement; -import org.hl7.fhir.dstu3.model.CapabilityStatement.*; -import org.hl7.fhir.dstu3.model.StringType; -import org.hl7.fhir.instance.model.api.*; -import org.springframework.ui.ModelMap; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.RequestMapping; - -import com.google.gson.stream.JsonWriter; - -import ca.uhn.fhir.context.*; -import ca.uhn.fhir.model.api.Include; -import ca.uhn.fhir.model.dstu2.resource.Conformance; -import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; -import ca.uhn.fhir.model.primitive.*; -import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.client.impl.GenericClient; -import ca.uhn.fhir.rest.gclient.*; -import ca.uhn.fhir.rest.gclient.NumberClientParam.IMatches; -import ca.uhn.fhir.rest.gclient.QuantityClientParam.IAndUnits; -import ca.uhn.fhir.to.model.*; - @org.springframework.stereotype.Controller() public class Controller extends BaseController { static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(Controller.class); diff --git a/hapi-fhir-validation/pom.xml b/hapi-fhir-validation/pom.xml index 46a12b9b6b3..8fe9898edcb 100644 --- a/hapi-fhir-validation/pom.xml +++ b/hapi-fhir-validation/pom.xml @@ -46,6 +46,15 @@ jsr305 + + + com.google.code.gson + gson + +