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}