001package org.hl7.fhir.dstu2.utils;
002
003/*-
004 * #%L
005 * org.hl7.fhir.dstu2
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.util.ArrayList;
025import java.util.List;
026
027import org.hl7.fhir.dstu2.model.Base;
028import org.hl7.fhir.dstu2.model.Bundle;
029import org.hl7.fhir.dstu2.model.Bundle.BundleEntryComponent;
030import org.hl7.fhir.dstu2.model.Bundle.BundleLinkComponent;
031import org.hl7.fhir.dstu2.model.CodeableConcept;
032import org.hl7.fhir.dstu2.model.Coding;
033import org.hl7.fhir.dstu2.model.ContactPoint;
034import org.hl7.fhir.dstu2.model.ContactPoint.ContactPointSystem;
035import org.hl7.fhir.dstu2.model.DataElement;
036import org.hl7.fhir.dstu2.model.DataElement.DataElementContactComponent;
037import org.hl7.fhir.dstu2.model.ElementDefinition;
038import org.hl7.fhir.dstu2.model.ElementDefinition.ElementDefinitionBindingComponent;
039import org.hl7.fhir.dstu2.model.ElementDefinition.TypeRefComponent;
040import org.hl7.fhir.dstu2.model.Meta;
041import org.hl7.fhir.dstu2.model.OperationOutcome;
042import org.hl7.fhir.dstu2.model.OperationOutcome.IssueSeverity;
043import org.hl7.fhir.dstu2.model.OperationOutcome.OperationOutcomeIssueComponent;
044import org.hl7.fhir.dstu2.model.Reference;
045import org.hl7.fhir.dstu2.model.Resource;
046import org.hl7.fhir.dstu2.model.ResourceType;
047import org.hl7.fhir.dstu2.model.Type;
048import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
049import org.hl7.fhir.utilities.Utilities;
050import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
051
052/**
053 * Decoration utilities for various resource types
054 * @author Grahame
055 *
056 */
057public class ResourceUtilities {
058
059  public final static String FHIR_LANGUAGE = "urn:ietf:bcp:47";
060
061        public static boolean isAnError(OperationOutcome error) {
062                for (OperationOutcomeIssueComponent t : error.getIssue())
063                        if (t.getSeverity() == IssueSeverity.ERROR)
064                                return true;
065                        else if (t.getSeverity() == IssueSeverity.FATAL)
066                                return true;
067                return false;
068        }
069        
070        public static String getErrorDescription(OperationOutcome error) {  
071                if (error.hasText() && error.getText().hasDiv())
072                        return new XhtmlComposer(true, false).composePlainText(error.getText().getDiv());
073                
074                StringBuilder b = new StringBuilder();
075                for (OperationOutcomeIssueComponent t : error.getIssue())
076                        if (t.getSeverity() == IssueSeverity.ERROR)
077                                b.append("Error:" +t.getDetails()+"\r\n");
078                        else if (t.getSeverity() == IssueSeverity.FATAL)
079                                b.append("Fatal:" +t.getDetails()+"\r\n");
080                        else if (t.getSeverity() == IssueSeverity.WARNING)
081                                b.append("Warning:" +t.getDetails()+"\r\n");
082                        else if (t.getSeverity() == IssueSeverity.INFORMATION)
083                                b.append("Information:" +t.getDetails()+"\r\n");
084                return b.toString();
085  }
086
087  public static Resource getById(Bundle feed, ResourceType type, String reference) {
088    for (BundleEntryComponent item : feed.getEntry()) {
089      if (item.getResource().getId().equals(reference) && item.getResource().getResourceType() == type)
090        return item.getResource();
091    }
092    return null;
093  }
094
095  public static BundleEntryComponent getEntryById(Bundle feed, ResourceType type, String reference) {
096    for (BundleEntryComponent item : feed.getEntry()) {
097      if (item.getResource().getId().equals(reference) && item.getResource().getResourceType() == type)
098        return item;
099    }
100    return null;
101  }
102
103        public static String getLink(Bundle feed, String rel) {
104                for (BundleLinkComponent link : feed.getLink()) {
105                        if (link.getRelation().equals(rel))
106                                return link.getUrl();
107                }
108          return null;
109  }
110
111  public static Meta meta(Resource resource) {
112    if (!resource.hasMeta())
113      resource.setMeta(new Meta());
114    return resource.getMeta();
115  }
116
117  public static String representDataElementCollection(IWorkerContext context, Bundle bundle, boolean profileLink, String linkBase) {
118    StringBuilder b = new StringBuilder();
119    DataElement common = showDECHeader(b, bundle);
120    b.append("<table class=\"grid\">\r\n"); 
121    List<String> cols = chooseColumns(bundle, common, b, profileLink);
122    for (BundleEntryComponent e : bundle.getEntry()) {
123      DataElement de = (DataElement) e.getResource();
124      renderDE(de, cols, b, profileLink, linkBase);
125    }
126    b.append("</table>\r\n");
127    return b.toString();
128  }
129
130  
131  private static void renderDE(DataElement de, List<String> cols, StringBuilder b, boolean profileLink, String linkBase) {
132    b.append("<tr>");
133    for (String col : cols) {
134      String v;
135      ElementDefinition dee = de.getElement().get(0);
136      if (col.equals("DataElement.name")) {
137        v = de.hasName() ? Utilities.escapeXml(de.getName()) : "";
138      } else if (col.equals("DataElement.status")) {
139        v = de.hasStatusElement() ? de.getStatusElement().asStringValue() : "";
140      } else if (col.equals("DataElement.code")) {
141        v = renderCoding(dee.getCode());
142      } else if (col.equals("DataElement.type")) {
143        v = dee.hasType() ? Utilities.escapeXml(dee.getType().get(0).getCode()) : "";
144      } else if (col.equals("DataElement.units")) {
145        v = renderDEUnits(ToolingExtensions.getAllowedUnits(dee));
146      } else if (col.equals("DataElement.binding")) {
147        v = renderBinding(dee.getBinding());
148      } else if (col.equals("DataElement.minValue")) {
149        v = ToolingExtensions.hasExtension(de, "http://hl7.org/fhir/StructureDefinition/minValue") ? Utilities.escapeXml(ToolingExtensions.readPrimitiveExtension(de, "http://hl7.org/fhir/StructureDefinition/minValue").asStringValue()) : "";
150      } else if (col.equals("DataElement.maxValue")) {
151        v = ToolingExtensions.hasExtension(de, "http://hl7.org/fhir/StructureDefinition/maxValue") ? Utilities.escapeXml(ToolingExtensions.readPrimitiveExtension(de, "http://hl7.org/fhir/StructureDefinition/maxValue").asStringValue()) : "";
152      } else if (col.equals("DataElement.maxLength")) {
153        v = ToolingExtensions.hasExtension(de, "http://hl7.org/fhir/StructureDefinition/maxLength") ? Utilities.escapeXml(ToolingExtensions.readPrimitiveExtension(de, "http://hl7.org/fhir/StructureDefinition/maxLength").asStringValue()) : "";
154      } else if (col.equals("DataElement.mask")) {
155        v = ToolingExtensions.hasExtension(de, "http://hl7.org/fhir/StructureDefinition/mask") ? Utilities.escapeXml(ToolingExtensions.readPrimitiveExtension(de, "http://hl7.org/fhir/StructureDefinition/mask").asStringValue()) : "";
156      } else 
157        throw new Error("Unknown column name: "+col);
158
159      b.append("<td>"+v+"</td>");
160    }
161    if (profileLink) {
162      b.append("<td><a href=\""+linkBase+"-"+de.getId()+".html\">Profile</a>, <a href=\"http://www.opencem.org/#/20140917/Intermountain/"+de.getId()+"\">CEM</a>");
163      if (ToolingExtensions.hasExtension(de, ToolingExtensions.EXT_CIMI_REFERENCE)) 
164        b.append(", <a href=\""+ToolingExtensions.readStringExtension(de, ToolingExtensions.EXT_CIMI_REFERENCE)+"\">CIMI</a>");
165      b.append("</td>");
166    }
167    b.append("</tr>\r\n");
168  }
169
170  
171
172  private static String renderBinding(ElementDefinitionBindingComponent binding) {
173    // TODO Auto-generated method stub
174    return null;
175  }
176
177  private static String renderDEUnits(Type units) {
178    if (units == null || units.isEmpty())
179      return "";
180    if (units instanceof CodeableConcept)
181      return renderCodeable((CodeableConcept) units);
182    else
183      return "<a href=\""+Utilities.escapeXml(((Reference) units).getReference())+"\">"+Utilities.escapeXml(((Reference) units).getReference())+"</a>";
184      
185  }
186
187  private static String renderCodeable(CodeableConcept units) {
188    if (units == null || units.isEmpty())
189      return "";
190    String v = renderCoding(units.getCoding());
191    if (units.hasText())
192      v = v + " " +Utilities.escapeXml(units.getText());
193    return v;
194  }
195
196  private static String renderCoding(List<Coding> codes) {
197    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
198    for (Coding c : codes)
199      b.append(renderCoding(c));
200    return b.toString();
201  }
202
203  private static String renderCoding(Coding code) {
204    if (code == null || code.isEmpty())
205      return "";
206    else
207      return "<span title=\""+Utilities.escapeXml(code.getSystem())+"\">"+Utilities.escapeXml(code.getCode())+"</span>";
208  }
209
210  private static List<String> chooseColumns(Bundle bundle, DataElement common, StringBuilder b, boolean profileLink) {
211    b.append("<tr>");
212    List<String> results = new ArrayList<String>();
213    results.add("DataElement.name");
214    b.append("<td width=\"250\"><b>Name</b></td>");
215    if (!common.hasStatus()) {
216      results.add("DataElement.status");
217      b.append("<td><b>Status</b></td>");
218    }
219    if (hasCode(bundle)) {
220      results.add("DataElement.code");
221      b.append("<td><b>Code</b></td>");
222    }
223    if (!common.getElement().get(0).hasType() && hasType(bundle)) {
224      results.add("DataElement.type");
225      b.append("<td><b>Type</b></td>");
226    }
227    if (hasUnits(bundle)) {
228      results.add("DataElement.units");
229      b.append("<td><b>Units</b></td>");
230    }
231    if (hasBinding(bundle)) {
232      results.add("DataElement.binding");
233      b.append("<td><b>Binding</b></td>");
234    }
235    if (hasExtension(bundle, "http://hl7.org/fhir/StructureDefinition/minValue")) {
236      results.add("DataElement.minValue");
237      b.append("<td><b>Min Value</b></td>");
238    }
239    if (hasExtension(bundle, "http://hl7.org/fhir/StructureDefinition/maxValue")) {
240      results.add("DataElement.maxValue");
241      b.append("<td><b>Max Value</b></td>");
242    }
243    if (hasExtension(bundle, "http://hl7.org/fhir/StructureDefinition/maxLength")) {
244      results.add("DataElement.maxLength");
245      b.append("<td><b>Max Length</b></td>");
246    }
247    if (hasExtension(bundle, "http://hl7.org/fhir/StructureDefinition/mask")) {
248      results.add("DataElement.mask");
249      b.append("<td><b>Mask</b></td>");
250    }
251    if (profileLink)
252      b.append("<td><b>Links</b></td>");
253    b.append("</tr>\r\n");
254    return results;
255  }
256
257  private static boolean hasExtension(Bundle bundle, String url) {
258    for (BundleEntryComponent e : bundle.getEntry()) {
259      DataElement de = (DataElement) e.getResource();
260      if (ToolingExtensions.hasExtension(de, url))
261        return true;
262    }
263    return false;
264  }
265
266  private static boolean hasBinding(Bundle bundle) {
267    for (BundleEntryComponent e : bundle.getEntry()) {
268      DataElement de = (DataElement) e.getResource();
269      if (de.getElement().get(0).hasBinding())
270        return true;
271    }
272    return false;
273  }
274
275  private static boolean hasCode(Bundle bundle) {
276    for (BundleEntryComponent e : bundle.getEntry()) {
277      DataElement de = (DataElement) e.getResource();
278      if (de.getElement().get(0).hasCode())
279        return true;
280    }
281    return false;
282  }
283
284  private static boolean hasType(Bundle bundle) {
285    for (BundleEntryComponent e : bundle.getEntry()) {
286      DataElement de = (DataElement) e.getResource();
287      if (de.getElement().get(0).hasType())
288        return true;
289    }
290    return false;
291  }
292
293  private static boolean hasUnits(Bundle bundle) {
294    for (BundleEntryComponent e : bundle.getEntry()) {
295      DataElement de = (DataElement) e.getResource();
296      if (ToolingExtensions.getAllowedUnits(de.getElement().get(0)) != null)
297        return true;
298    }
299    return false;
300  }
301
302  private static DataElement showDECHeader(StringBuilder b, Bundle bundle) {
303    DataElement meta = new DataElement();
304    DataElement prototype = (DataElement) bundle.getEntry().get(0).getResource();
305    meta.setPublisher(prototype.getPublisher());
306    meta.getContact().addAll(prototype.getContact());
307    meta.setStatus(prototype.getStatus());
308    meta.setDate(prototype.getDate());
309    meta.addElement().getType().addAll(prototype.getElement().get(0).getType());
310
311    for (BundleEntryComponent e : bundle.getEntry()) {
312      DataElement de = (DataElement) e.getResource();
313      if (!Base.compareDeep(de.getPublisherElement(), meta.getPublisherElement(), false))
314        meta.setPublisherElement(null);
315      if (!Base.compareDeep(de.getContact(), meta.getContact(), false))
316        meta.getContact().clear();
317      if (!Base.compareDeep(de.getStatusElement(), meta.getStatusElement(), false))
318        meta.setStatusElement(null);
319      if (!Base.compareDeep(de.getDateElement(), meta.getDateElement(), false))
320        meta.setDateElement(null);
321      if (!Base.compareDeep(de.getElement().get(0).getType(), meta.getElement().get(0).getType(), false))
322        meta.getElement().get(0).getType().clear();
323    }
324    if (meta.hasPublisher() || meta.hasContact() || meta.hasStatus() || meta.hasDate() /* || meta.hasType() */) {
325      b.append("<table class=\"grid\">\r\n"); 
326      if (meta.hasPublisher())
327        b.append("<tr><td>Publisher:</td><td>"+meta.getPublisher()+"</td></tr>\r\n");
328      if (meta.hasContact()) {
329        b.append("<tr><td>Contacts:</td><td>");
330        boolean firsti = true;
331        for (DataElementContactComponent c : meta.getContact()) {
332          if (firsti)
333            firsti = false;
334          else
335            b.append("<br/>");
336          if (c.hasName())
337            b.append(Utilities.escapeXml(c.getName())+": ");
338          boolean first = true;
339          for (ContactPoint cp : c.getTelecom()) {
340            if (first)
341              first = false;
342            else
343              b.append(", ");
344            renderContactPoint(b, cp);
345          }
346        }
347        b.append("</td></tr>\r\n");
348      }
349      if (meta.hasStatus())
350        b.append("<tr><td>Status:</td><td>"+meta.getStatus().toString()+"</td></tr>\r\n");
351      if (meta.hasDate())
352        b.append("<tr><td>Date:</td><td>"+meta.getDateElement().asStringValue()+"</td></tr>\r\n");
353      if (meta.getElement().get(0).hasType())
354        b.append("<tr><td>Type:</td><td>"+renderType(meta.getElement().get(0).getType())+"</td></tr>\r\n");
355      b.append("</table>\r\n"); 
356    }  
357    return meta;
358  }
359
360  private static String renderType(List<TypeRefComponent> type) {
361    if (type == null || type.isEmpty())
362      return "";
363    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
364    for (TypeRefComponent c : type)
365      b.append(renderType(c));
366    return b.toString();
367  }
368
369  private static String renderType(TypeRefComponent type) {
370    if (type == null || type.isEmpty())
371      return "";
372    return type.getCode();
373  }
374
375  public static void renderContactPoint(StringBuilder b, ContactPoint cp) {
376    if (cp != null && !cp.isEmpty()) {
377      if (cp.getSystem() == ContactPointSystem.EMAIL)
378        b.append("<a href=\"mailto:"+cp.getValue()+"\">"+cp.getValue()+"</a>");
379      else if (cp.getSystem() == ContactPointSystem.FAX) 
380        b.append("Fax: "+cp.getValue());
381      else if (cp.getSystem() == ContactPointSystem.OTHER) 
382        b.append("<a href=\""+cp.getValue()+"\">"+cp.getValue()+"</a>");
383      else
384        b.append(cp.getValue());
385    }
386  }
387}