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