001package org.hl7.fhir.dstu3.elementmodel; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033 034import java.io.IOException; 035import java.io.InputStream; 036import java.io.OutputStream; 037import java.io.OutputStreamWriter; 038import java.util.HashSet; 039import java.util.List; 040import java.util.Set; 041 042import org.apache.commons.lang3.NotImplementedException; 043import org.hl7.fhir.dstu3.context.IWorkerContext; 044import org.hl7.fhir.dstu3.elementmodel.Element.SpecialElement; 045import org.hl7.fhir.dstu3.formats.IParser.OutputStyle; 046import org.hl7.fhir.dstu3.formats.JsonCreator; 047import org.hl7.fhir.dstu3.formats.JsonCreatorCanonical; 048import org.hl7.fhir.dstu3.formats.JsonCreatorGson; 049import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent; 050import org.hl7.fhir.utilities.Utilities; 051 052public class JsonLDParser extends ParserBase { 053 054 private JsonCreator json; 055 private String base; 056 private String jsonLDBase = "http://build.fhir.org/"; 057 058 public JsonLDParser(IWorkerContext context) { 059 super(context); 060 } 061 062 @Override 063 public Element parse(InputStream stream) { 064 throw new NotImplementedException("not done yet"); 065 } 066 067 068 protected void prop(String name, String value) throws IOException { 069 if (name != null) 070 json.name(name); 071 json.value(value); 072 } 073 074 protected void open(String name) throws IOException { 075 if (name != null) 076 json.name(name); 077 json.beginObject(); 078 } 079 080 protected void close() throws IOException { 081 json.endObject(); 082 } 083 084 protected void openArray(String name) throws IOException { 085 if (name != null) 086 json.name(name); 087 json.beginArray(); 088 } 089 090 protected void closeArray() throws IOException { 091 json.endArray(); 092 } 093 094 095 @Override 096 public void compose(Element e, OutputStream stream, OutputStyle style, String base) throws IOException { 097 this.base = base; 098 OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8"); 099 if (style == OutputStyle.CANONICAL) 100 json = new JsonCreatorCanonical(osw); 101 else 102 json = new JsonCreatorGson(osw); 103 json.setIndent(style == OutputStyle.PRETTY ? " " : ""); 104 json.beginObject(); 105 prop("@type", "fhir:"+e.getType()); 106 prop("@context", jsonLDBase+"fhir.jsonld"); 107 prop("role", "fhir:treeRoot"); 108 String id = e.getChildValue("id"); 109 if (base != null && id != null) { 110 if (base.endsWith("#")) 111 prop("@id", base + e.getType() + "-" + id + ">"); 112 else 113 prop("@id", Utilities.pathURL(base, e.getType(), id)); 114 } 115 Set<String> done = new HashSet<String>(); 116 for (Element child : e.getChildren()) { 117 compose(e.getName(), e, done, child); 118 } 119 json.endObject(); 120 json.finish(); 121 osw.flush(); 122 } 123 124 private void compose(String path, Element e, Set<String> done, Element child) throws IOException { 125 if (!child.isList()) { 126 compose(path, child); 127 } else if (!done.contains(child.getName())) { 128 done.add(child.getName()); 129 List<Element> list = e.getChildrenByName(child.getName()); 130 composeList(path, list); 131 } 132 } 133 134 private void composeList(String path, List<Element> list) throws IOException { 135 // there will be at least one element 136 String en = getFormalName(list.get(0)); 137 138 openArray(en); 139 for (Element item : list) { 140 open(null); 141 json.name("index"); 142 json.value(item.getIndex()); 143 if (item.isPrimitive() || isPrimitive(item.getType())) { 144 if (item.hasValue()) 145 primitiveValue(item); 146 } 147 if (item.getProperty().isResource()) { 148 prop("@type", "fhir:"+item.getType()); 149 } 150 Set<String> done = new HashSet<String>(); 151 for (Element child : item.getChildren()) { 152 compose(path+"."+item.getName(), item, done, child); 153 } 154 if ("Coding".equals(item.getType())) 155 decorateCoding(item); 156 if ("CodeableConcept".equals(item.getType())) 157 decorateCodeableConcept(item); 158 if ("Reference".equals(item.getType())) 159 decorateReference(item); 160 161 close(); 162 } 163 closeArray(); 164 } 165 166 private void primitiveValue(Element item) throws IOException { 167 String type = item.getType(); 168 if (Utilities.existsInList(type, "date", "dateTime", "instant")) { 169 String v = item.getValue(); 170 if (v.length() > 10) { 171 int i = v.substring(10).indexOf("-"); 172 if (i == -1) 173 i = v.substring(10).indexOf("+"); 174 v = i == -1 ? v : v.substring(0, 10+i); 175 } 176 if (v.length() > 10) 177 json.name("dateTime"); 178 else if (v.length() == 10) 179 json.name("date"); 180 else if (v.length() == 7) 181 json.name("gYearMonth"); 182 else if (v.length() == 4) 183 json.name("gYear"); 184 json.value(item.getValue()); 185 } else if (Utilities.existsInList(type, "boolean")) { 186 json.name("boolean"); 187 json.value(item.getValue().equals("true") ? new Boolean(true) : new Boolean(false)); 188 } else if (Utilities.existsInList(type, "integer", "unsignedInt", "positiveInt")) { 189 json.name("integer"); 190 json.value(new Integer(item.getValue())); 191 } else if (Utilities.existsInList(type, "decimal")) { 192 json.name("decimal"); 193 json.value(item.getValue()); 194 } else if (Utilities.existsInList(type, "base64Binary")) { 195 json.name("binary"); 196 json.value(item.getValue()); 197 } else { 198 json.name("value"); 199 json.value(item.getValue()); 200 } 201 } 202 203 private void compose(String path, Element element) throws IOException { 204 Property p = element.hasElementProperty() ? element.getElementProperty() : element.getProperty(); 205 String en = getFormalName(element); 206 207 if (element.fhirType().equals("xhtml")) { 208 json.name(en); 209 json.value(element.getValue()); 210 } else if (element.hasChildren() || element.hasComments() || element.hasValue()) { 211 open(en); 212 if (element.getProperty().isResource()) { 213 prop("@type", "fhir:"+element.getType()); 214// element = element.getChildren().get(0); 215 } 216 if (element.isPrimitive() || isPrimitive(element.getType())) { 217 if (element.hasValue()) 218 primitiveValue(element); 219 } 220 221 Set<String> done = new HashSet<String>(); 222 for (Element child : element.getChildren()) { 223 compose(path+"."+element.getName(), element, done, child); 224 } 225 if ("Coding".equals(element.getType())) 226 decorateCoding(element); 227 if ("CodeableConcept".equals(element.getType())) 228 decorateCodeableConcept(element); 229 if ("Reference".equals(element.getType())) 230 decorateReference(element); 231 232 close(); 233 } 234 } 235 236 private void decorateReference(Element element) throws IOException { 237 String ref = element.getChildValue("reference"); 238 if (ref != null && (ref.startsWith("http://") || ref.startsWith("https://"))) { 239 json.name("link"); 240 json.value(ref); 241 } else if (base != null && ref != null && ref.contains("/")) { 242 json.name("link"); 243 json.value(Utilities.pathURL(base, ref)); 244 } 245 } 246 247 protected void decorateCoding(Element coding) throws IOException { 248 String system = coding.getChildValue("system"); 249 String code = coding.getChildValue("code"); 250 251 if (system == null) 252 return; 253 if ("http://snomed.info/sct".equals(system)) { 254 json.name("concept"); 255 json.value("http://snomed.info/id/"+code); 256 } else if ("http://loinc.org".equals(system)) { 257 json.name("concept"); 258 json.value("http://loinc.org/rdf#"+code); 259 } 260 } 261 262 private void decorateCodeableConcept(Element element) throws IOException { 263 // nothing here; ITS committee decision 264 } 265 266 private String getFormalName(Element element) { 267 String en = null; 268 if (element.getSpecial() == null) { 269 if (element.getProperty().getDefinition().hasBase()) 270 en = element.getProperty().getDefinition().getBase().getPath(); 271 } 272 else if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY) 273 en = "Bundle.entry.resource"; 274 else if (element.getSpecial() == SpecialElement.BUNDLE_OUTCOME) 275 en = "Bundle.entry.response.outcome"; 276 else if (element.getSpecial() == SpecialElement.PARAMETER) 277 en = element.getElementProperty().getDefinition().getPath(); 278 else // CONTAINED 279 en = "DomainResource.contained"; 280 281 if (en == null) 282 en = element.getProperty().getDefinition().getPath(); 283 boolean doType = false; 284 if (en.endsWith("[x]")) { 285 en = en.substring(0, en.length()-3); 286 doType = true; 287 } 288 if (doType || (element.getProperty().getDefinition().getType().size() > 1 && !allReference(element.getProperty().getDefinition().getType()))) 289 en = en + Utilities.capitalize(element.getType()); 290 return en; 291 } 292 293 private boolean allReference(List<TypeRefComponent> types) { 294 for (TypeRefComponent t : types) { 295 if (!t.getCode().equals("Reference")) 296 return false; 297 } 298 return true; 299 } 300 301}