001package org.hl7.fhir.dstu3.elementmodel; 002 003/*- 004 * #%L 005 * org.hl7.fhir.dstu3 006 * %% 007 * Copyright (C) 2014 - 2019 Health Level 7 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023 024import java.io.IOException; 025import java.io.InputStream; 026import java.io.OutputStream; 027import java.io.OutputStreamWriter; 028import java.util.HashSet; 029import java.util.List; 030import java.util.Set; 031 032import org.apache.commons.lang3.NotImplementedException; 033import org.hl7.fhir.dstu3.context.IWorkerContext; 034import org.hl7.fhir.dstu3.elementmodel.Element.SpecialElement; 035import org.hl7.fhir.dstu3.formats.IParser.OutputStyle; 036import org.hl7.fhir.dstu3.formats.JsonCreator; 037import org.hl7.fhir.dstu3.formats.JsonCreatorCanonical; 038import org.hl7.fhir.dstu3.formats.JsonCreatorGson; 039import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent; 040import org.hl7.fhir.utilities.Utilities; 041 042public class JsonLDParser extends ParserBase { 043 044 private JsonCreator json; 045 private String base; 046 private String jsonLDBase = "http://build.fhir.org/"; 047 048 public JsonLDParser(IWorkerContext context) { 049 super(context); 050 } 051 052 @Override 053 public Element parse(InputStream stream) { 054 throw new NotImplementedException("not done yet"); 055 } 056 057 058 protected void prop(String name, String value) throws IOException { 059 if (name != null) 060 json.name(name); 061 json.value(value); 062 } 063 064 protected void open(String name) throws IOException { 065 if (name != null) 066 json.name(name); 067 json.beginObject(); 068 } 069 070 protected void close() throws IOException { 071 json.endObject(); 072 } 073 074 protected void openArray(String name) throws IOException { 075 if (name != null) 076 json.name(name); 077 json.beginArray(); 078 } 079 080 protected void closeArray() throws IOException { 081 json.endArray(); 082 } 083 084 085 @Override 086 public void compose(Element e, OutputStream stream, OutputStyle style, String base) throws IOException { 087 this.base = base; 088 OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8"); 089 if (style == OutputStyle.CANONICAL) 090 json = new JsonCreatorCanonical(osw); 091 else 092 json = new JsonCreatorGson(osw); 093 json.setIndent(style == OutputStyle.PRETTY ? " " : ""); 094 json.beginObject(); 095 prop("@type", "fhir:"+e.getType()); 096 prop("@context", jsonLDBase+"fhir.jsonld"); 097 prop("role", "fhir:treeRoot"); 098 String id = e.getChildValue("id"); 099 if (base != null && id != null) { 100 if (base.endsWith("#")) 101 prop("@id", base + e.getType() + "-" + id + ">"); 102 else 103 prop("@id", Utilities.pathURL(base, e.getType(), id)); 104 } 105 Set<String> done = new HashSet<String>(); 106 for (Element child : e.getChildren()) { 107 compose(e.getName(), e, done, child); 108 } 109 json.endObject(); 110 json.finish(); 111 osw.flush(); 112 } 113 114 private void compose(String path, Element e, Set<String> done, Element child) throws IOException { 115 if (!child.isList()) { 116 compose(path, child); 117 } else if (!done.contains(child.getName())) { 118 done.add(child.getName()); 119 List<Element> list = e.getChildrenByName(child.getName()); 120 composeList(path, list); 121 } 122 } 123 124 private void composeList(String path, List<Element> list) throws IOException { 125 // there will be at least one element 126 String en = getFormalName(list.get(0)); 127 128 openArray(en); 129 for (Element item : list) { 130 open(null); 131 json.name("index"); 132 json.value(item.getIndex()); 133 if (item.isPrimitive() || isPrimitive(item.getType())) { 134 if (item.hasValue()) 135 primitiveValue(item); 136 } 137 if (item.getProperty().isResource()) { 138 prop("@type", "fhir:"+item.getType()); 139 } 140 Set<String> done = new HashSet<String>(); 141 for (Element child : item.getChildren()) { 142 compose(path+"."+item.getName(), item, done, child); 143 } 144 if ("Coding".equals(item.getType())) 145 decorateCoding(item); 146 if ("CodeableConcept".equals(item.getType())) 147 decorateCodeableConcept(item); 148 if ("Reference".equals(item.getType())) 149 decorateReference(item); 150 151 close(); 152 } 153 closeArray(); 154 } 155 156 private void primitiveValue(Element item) throws IOException { 157 String type = item.getType(); 158 if (Utilities.existsInList(type, "date", "dateTime", "instant")) { 159 String v = item.getValue(); 160 if (v.length() > 10) { 161 int i = v.substring(10).indexOf("-"); 162 if (i == -1) 163 i = v.substring(10).indexOf("+"); 164 v = i == -1 ? v : v.substring(0, 10+i); 165 } 166 if (v.length() > 10) 167 json.name("dateTime"); 168 else if (v.length() == 10) 169 json.name("date"); 170 else if (v.length() == 7) 171 json.name("gYearMonth"); 172 else if (v.length() == 4) 173 json.name("gYear"); 174 json.value(item.getValue()); 175 } else if (Utilities.existsInList(type, "boolean")) { 176 json.name("boolean"); 177 json.value(item.getValue().equals("true") ? new Boolean(true) : new Boolean(false)); 178 } else if (Utilities.existsInList(type, "integer", "unsignedInt", "positiveInt")) { 179 json.name("integer"); 180 json.value(new Integer(item.getValue())); 181 } else if (Utilities.existsInList(type, "decimal")) { 182 json.name("decimal"); 183 json.value(item.getValue()); 184 } else if (Utilities.existsInList(type, "base64Binary")) { 185 json.name("binary"); 186 json.value(item.getValue()); 187 } else { 188 json.name("value"); 189 json.value(item.getValue()); 190 } 191 } 192 193 private void compose(String path, Element element) throws IOException { 194 Property p = element.hasElementProperty() ? element.getElementProperty() : element.getProperty(); 195 String en = getFormalName(element); 196 197 if (element.fhirType().equals("xhtml")) { 198 json.name(en); 199 json.value(element.getValue()); 200 } else if (element.hasChildren() || element.hasComments() || element.hasValue()) { 201 open(en); 202 if (element.getProperty().isResource()) { 203 prop("@type", "fhir:"+element.getType()); 204// element = element.getChildren().get(0); 205 } 206 if (element.isPrimitive() || isPrimitive(element.getType())) { 207 if (element.hasValue()) 208 primitiveValue(element); 209 } 210 211 Set<String> done = new HashSet<String>(); 212 for (Element child : element.getChildren()) { 213 compose(path+"."+element.getName(), element, done, child); 214 } 215 if ("Coding".equals(element.getType())) 216 decorateCoding(element); 217 if ("CodeableConcept".equals(element.getType())) 218 decorateCodeableConcept(element); 219 if ("Reference".equals(element.getType())) 220 decorateReference(element); 221 222 close(); 223 } 224 } 225 226 private void decorateReference(Element element) throws IOException { 227 String ref = element.getChildValue("reference"); 228 if (ref != null && (ref.startsWith("http://") || ref.startsWith("https://"))) { 229 json.name("link"); 230 json.value(ref); 231 } else if (base != null && ref != null && ref.contains("/")) { 232 json.name("link"); 233 json.value(Utilities.pathURL(base, ref)); 234 } 235 } 236 237 protected void decorateCoding(Element coding) throws IOException { 238 String system = coding.getChildValue("system"); 239 String code = coding.getChildValue("code"); 240 241 if (system == null) 242 return; 243 if ("http://snomed.info/sct".equals(system)) { 244 json.name("concept"); 245 json.value("http://snomed.info/id/"+code); 246 } else if ("http://loinc.org".equals(system)) { 247 json.name("concept"); 248 json.value("http://loinc.org/rdf#"+code); 249 } 250 } 251 252 private void decorateCodeableConcept(Element element) throws IOException { 253 // nothing here; ITS committee decision 254 } 255 256 private String getFormalName(Element element) { 257 String en = null; 258 if (element.getSpecial() == null) { 259 if (element.getProperty().getDefinition().hasBase()) 260 en = element.getProperty().getDefinition().getBase().getPath(); 261 } 262 else if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY) 263 en = "Bundle.entry.resource"; 264 else if (element.getSpecial() == SpecialElement.BUNDLE_OUTCOME) 265 en = "Bundle.entry.response.outcome"; 266 else if (element.getSpecial() == SpecialElement.PARAMETER) 267 en = element.getElementProperty().getDefinition().getPath(); 268 else // CONTAINED 269 en = "DomainResource.contained"; 270 271 if (en == null) 272 en = element.getProperty().getDefinition().getPath(); 273 boolean doType = false; 274 if (en.endsWith("[x]")) { 275 en = en.substring(0, en.length()-3); 276 doType = true; 277 } 278 if (doType || (element.getProperty().getDefinition().getType().size() > 1 && !allReference(element.getProperty().getDefinition().getType()))) 279 en = en + Utilities.capitalize(element.getType()); 280 return en; 281 } 282 283 private boolean allReference(List<TypeRefComponent> types) { 284 for (TypeRefComponent t : types) { 285 if (!t.getCode().equals("Reference")) 286 return false; 287 } 288 return true; 289 } 290 291}