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}