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}