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