001package org.hl7.fhir.r5.renderers; 002 003import java.io.IOException; 004import java.io.UnsupportedEncodingException; 005 006import java.util.ArrayList; 007import java.util.HashMap; 008import java.util.List; 009import java.util.Map; 010 011import javax.annotation.Nullable; 012 013import org.hl7.fhir.exceptions.DefinitionException; 014import org.hl7.fhir.exceptions.FHIRFormatError; 015import org.hl7.fhir.exceptions.FHIRException; 016import org.hl7.fhir.r5.model.CanonicalResource; 017import org.hl7.fhir.r5.model.CanonicalType; 018import org.hl7.fhir.r5.model.CapabilityStatement; 019import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestComponent; 020import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceComponent; 021import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceOperationComponent; 022import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent; 023import org.hl7.fhir.r5.model.CapabilityStatement.ReferenceHandlingPolicy; 024import org.hl7.fhir.r5.model.CapabilityStatement.ResourceInteractionComponent; 025import org.hl7.fhir.r5.model.CapabilityStatement.SystemInteractionComponent; 026import org.hl7.fhir.r5.model.CapabilityStatement.SystemRestfulInteraction; 027import org.hl7.fhir.r5.model.CapabilityStatement.TypeRestfulInteraction; 028import org.hl7.fhir.r5.model.CodeType; 029import org.hl7.fhir.r5.model.CodeableConcept; 030import org.hl7.fhir.r5.model.Element; 031import org.hl7.fhir.r5.model.Enumeration; 032import org.hl7.fhir.r5.model.Enumerations.FHIRVersion; 033import org.hl7.fhir.r5.model.Extension; 034import org.hl7.fhir.r5.model.OperationDefinition; 035import org.hl7.fhir.r5.model.Resource; 036import org.hl7.fhir.r5.model.StringType; 037import org.hl7.fhir.r5.model.StructureDefinition; 038import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; 039import org.hl7.fhir.r5.renderers.utils.RenderingContext; 040import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; 041import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; 042import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; 043import org.hl7.fhir.r5.utils.ToolingExtensions; 044import org.hl7.fhir.utilities.Utilities; 045import org.hl7.fhir.utilities.xhtml.NodeType; 046import org.hl7.fhir.utilities.xhtml.XhtmlNode; 047 048 049public class CapabilityStatementRenderer extends ResourceRenderer { 050 051 private static final String EXPECTATION = "http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation"; 052 private static final String COMBINED = "http://hl7.org/fhir/StructureDefinition/capabilitystatement-search-parameter-combination"; 053 private static final String SP_BASE = "http://hl7.org/fhir/searchparameter/"; 054 private static final String FHIR_BASE = "http://hl7.org/fhir/"; 055 private static final String VERS_DEF_PREFIX = "FHIR Release "; 056 057 private String currentFhirBase = ""; 058 private String collapseClass = "panel-collapse in"; 059 060 private boolean multExpectationsPresent = false; 061 062 //Private classes for driving the rendering 063 064 private class CombinedSearchParamSet { 065 private Map<Boolean, List<SingleParam>> params; 066 String expectation = ""; 067 068 CombinedSearchParamSet(String expectation) { 069 params = new HashMap<Boolean, List<SingleParam>>(); 070 params.put(true, new ArrayList<SingleParam>()); 071 params.put(false, new ArrayList<SingleParam>()); 072 if (!Utilities.noString(expectation)) this.expectation = expectation; 073 } 074 075 public Map<Boolean, List<SingleParam>> getParams() { 076 return params; 077 } 078 079 public String getExpectation() { 080 return expectation; 081 } 082 083 public void addParam(boolean required, SingleParam param) { 084 params.get(required).add(param); 085 } 086 } 087 private class SingleParam { 088 private String name = ""; 089 private String definition = ""; 090 private String type = ""; 091 private String documentation = ""; 092 private String expectation = ""; 093 private String hostResource = ""; 094 095 public SingleParam(String name) { 096 if (!Utilities.noString(name)) this.name=name; 097 } 098 099 public SingleParam(String name, String definition) { 100 if (!Utilities.noString(name)) this.name=name; 101 if (!Utilities.noString(definition)) this.definition=definition; 102 } 103 104 public SingleParam(String name, String definition, String type) { 105 if (!Utilities.noString(name)) this.name=name; 106 if (!Utilities.noString(definition)) this.definition=definition; 107 if (!Utilities.noString(type)) this.type=type; 108 } 109 110 public SingleParam(String name, String definition, String type, String documentation) { 111 if (!Utilities.noString(name)) this.name=name; 112 if (!Utilities.noString(definition)) this.definition=definition; 113 if (!Utilities.noString(type)) this.type=type; 114 if (!Utilities.noString(documentation)) this.documentation=documentation; 115 } 116 117 public SingleParam(String name, String definition, String type, String documentation, String expectation, String hostResource) { 118 if (!Utilities.noString(name)) this.name=name; 119 if (!Utilities.noString(definition)) this.definition=definition; 120 if (!Utilities.noString(type)) this.type=type; 121 if (!Utilities.noString(documentation)) this.documentation=documentation; 122 if (!Utilities.noString(expectation)) this.expectation=expectation; 123 if (!Utilities.noString(hostResource)) this.hostResource = hostResource; 124 } 125 126 public String getName() { 127 return name; 128 } 129 130 public String getDefinition() { 131 return definition; 132 } 133 134 public String getDocumentation() { 135 return documentation; 136 } 137 138 public String getType() { 139 return type; 140 } 141 142 public String getExpectation() { 143 return expectation; 144 } 145 146 public String getHostResource() { 147 return hostResource; 148 } 149 } 150 private class ResourceSearchParams { 151 private Map<String, List<CombinedSearchParamSet>> combinedParams; 152 private Map<String, SingleParam> individualParamsByName; 153 private Map<String, List<SingleParam>> individualParamsByExp; 154 155 public ResourceSearchParams() { 156 combinedParams = new HashMap<String, List<CombinedSearchParamSet>>(); 157 combinedParams.put("SHALL", new ArrayList<CombinedSearchParamSet>()); 158 combinedParams.put("SHOULD", new ArrayList<CombinedSearchParamSet>()); 159 combinedParams.put("SHOULD-NOT", new ArrayList<CombinedSearchParamSet>()); 160 combinedParams.put("MAY", new ArrayList<CombinedSearchParamSet>()); 161 combinedParams.put("supported", new ArrayList<CombinedSearchParamSet>()); 162 individualParamsByName = new HashMap<String, SingleParam>(); 163 individualParamsByExp = new HashMap<String, List<SingleParam>>(); 164 individualParamsByExp.put("SHALL", new ArrayList<SingleParam>()); 165 individualParamsByExp.put("SHOULD", new ArrayList<SingleParam>()); 166 individualParamsByExp.put("MAY", new ArrayList<SingleParam>()); 167 individualParamsByExp.put("SHOULD-NOT", new ArrayList<SingleParam>()); 168 individualParamsByExp.put("supported", new ArrayList<SingleParam>()); 169 } 170 171 public Map<String, List<CombinedSearchParamSet>> getCombined() { 172 return combinedParams; 173 } 174 175 public Map<String, SingleParam> getInd() { 176 return individualParamsByName; 177 } 178 179 public Map<String, SingleParam> getIndbyName() { 180 return individualParamsByName; 181 } 182 183 public Map<String, List<SingleParam>> getIndbyExp() { 184 return individualParamsByExp; 185 } 186 187 public void addCombinedParamSet(String exp, CombinedSearchParamSet params) { 188 combinedParams.get(exp).add(params); 189 } 190 191 public void addIndividualbyName(String name, SingleParam param) { 192 individualParamsByName.put(name, param); 193 } 194 195 public void addIndividualbyExp(String exp, SingleParam param) { 196 individualParamsByExp.get(exp).add(param); 197 } 198 199 } 200 201 private class SingleOperation { 202 private String name = ""; 203 private String definition = ""; 204 private String documentation = ""; 205 private String expectation = ""; 206 207 public SingleOperation(String name) { 208 if (!Utilities.noString(name)) this.name=name; 209 } 210 211 public SingleOperation(String name, String definition) { 212 if (!Utilities.noString(name)) this.name=name; 213 if (!Utilities.noString(definition)) this.definition=definition; 214 } 215 216 public SingleOperation(String name, String definition, String documentation) { 217 if (!Utilities.noString(name)) this.name=name; 218 if (!Utilities.noString(definition)) this.definition=definition; 219 220 if (!Utilities.noString(documentation)) this.documentation=documentation; 221 } 222 223 public SingleOperation(String name, String definition, String documentation, String expectation) { 224 if (!Utilities.noString(name)) this.name=name; 225 if (!Utilities.noString(definition)) this.definition=definition; 226 if (!Utilities.noString(documentation)) this.documentation=documentation; 227 if (!Utilities.noString(expectation)) this.expectation=expectation; 228 } 229 230 public String getName() { 231 return name; 232 } 233 234 public String getDefinition() { 235 return definition; 236 } 237 238 public String getDocumentation() { 239 return documentation; 240 } 241 242 public String getExpectation() { 243 return expectation; 244 } 245 } 246 247 private class ResourceOperations { 248 private Map<String, List<SingleOperation>> operationsByExp; 249 250 public ResourceOperations() { 251 252 operationsByExp = new HashMap<String, List<SingleOperation>>(); 253 operationsByExp.put("SHALL", new ArrayList<SingleOperation>()); 254 operationsByExp.put("SHOULD", new ArrayList<SingleOperation>()); 255 operationsByExp.put("MAY", new ArrayList<SingleOperation>()); 256 operationsByExp.put("SHOULD-NOT", new ArrayList<SingleOperation>()); 257 operationsByExp.put("supported", new ArrayList<SingleOperation>()); 258 } 259 260 public Map<String, List<SingleOperation>> getOperations() { 261 return operationsByExp; 262 } 263 public void addOperation(String exp, SingleOperation op) { 264 operationsByExp.get(exp).add(op); 265 } 266 267 } 268 269 //Constructors 270 public CapabilityStatementRenderer(RenderingContext context) { 271 super(context); 272 } 273 274 public CapabilityStatementRenderer(RenderingContext context, ResourceContext rcontext) { 275 super(context, rcontext); 276 } 277 278 public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException { 279 return render(x, (CapabilityStatement) dr); 280 } 281 282 public boolean render(XhtmlNode x, CapabilityStatement conf) throws FHIRFormatError, DefinitionException, IOException { 283 boolean igRenderingMode = (context.getRules() == GenerationRules.IG_PUBLISHER); 284 FHIRVersion currentVersion = conf.getFhirVersion(); 285 String versionPathComponent = getVersionPathComponent(currentVersion.getDefinition()); 286 if (!Utilities.noString(versionPathComponent)) { 287 currentFhirBase = FHIR_BASE + versionPathComponent + "/"; 288 } 289 else { 290 currentFhirBase = FHIR_BASE; 291 } 292 293 String igVersion = conf.getVersion(); 294 295 x.h(2,"title").addText(conf.getTitle()); 296 XhtmlNode uList = x.ul(); 297 uList.li().addText(context.formatPhrase(RenderingContext.CAPABILITY_IMP_VER, igVersion) + " "); 298 uList.li().addText(context.formatPhrase(RenderingContext.CAPABILITY_FHIR_VER, currentVersion.toCode()) + " "); 299 addSupportedFormats(uList, conf); 300 301 uList.li().addText(context.formatPhrase(RenderingContext.CAPABILITY_PUB_ON, displayDateTime(conf.getDateElement()) + " ")); 302 uList.li().addText(context.formatPhrase(RenderingContext.CAPABILITY_PUB_BY, conf.getPublisherElement().asStringValue()) + " "); 303 304 305 XhtmlNode block = x.addTag("blockquote").attribute("class","impl-note"); 306 block.addTag("p").addTag("strong").addText(context.formatPhrase(RenderingContext.CAPABILITY_NOTE_CAP)); 307 block.addTag("p").addText(context.formatPhrase(RenderingContext.CAPABILTY_ALLOW_CAP)); 308 309 310 addSupportedCSs(x, conf); 311 addSupportedIGs(x, conf); 312 313 int restNum = conf.getRest().size(); 314 int nextLevel = 3; 315 if (restNum > 0) { 316 x.h(2,"rest").addText((context.formatPhrase(RenderingContext.CAPABILITY_REST_CAPS))); 317 int count=1; 318 for (CapabilityStatementRestComponent rest : conf.getRest()) { 319 if (restNum > 1) { 320 x.h(3,"rest"+Integer.toString(count)).addText(context.formatPhrase(RenderingContext.CAPABILITY_REST_CONFIG, Integer.toString(count)) + " "); 321 nextLevel = 4; 322 } 323 addRestConfigPanel(x, rest, nextLevel, count); 324 325 boolean hasVRead = false; 326 boolean hasPatch = false; 327 boolean hasDelete = false; 328 boolean hasHistory = false; 329 boolean hasUpdates = false; 330 for (CapabilityStatementRestResourceComponent r : rest.getResource()) { 331 hasVRead = hasVRead || hasOp(r, TypeRestfulInteraction.VREAD); 332 hasPatch = hasPatch || hasOp(r, TypeRestfulInteraction.PATCH); 333 hasDelete = hasDelete || hasOp(r, TypeRestfulInteraction.DELETE); 334 hasHistory = hasHistory || hasOp(r, TypeRestfulInteraction.HISTORYTYPE); 335 hasUpdates = hasUpdates || hasOp(r, TypeRestfulInteraction.HISTORYINSTANCE); 336 } 337 if (rest.getResource().size() >0) { 338 x.h(nextLevel,"resourcesCap" + Integer.toString(count)).addText(context.formatPhrase(RenderingContext.CAPABILITY_RES_PRO)); 339 x.h(nextLevel+1,"resourcesSummary" + Integer.toString(count)).addText(context.formatPhrase(RenderingContext.GENERAL_SUMM)); 340 addSummaryIntro(x); 341 addSummaryTable(x, rest, hasVRead, hasPatch, hasDelete, hasHistory, hasUpdates, count); 342 x.addTag("hr"); 343 //Third time for individual resources 344 int resCount = 1; 345 for (CapabilityStatementRestResourceComponent r : rest.getResource()) { 346 addResourceConfigPanel(x, r, nextLevel+1, count, resCount, igRenderingMode); 347 resCount++; 348 } 349 } 350 count++; 351 } 352 } 353 354 if (multExpectationsPresent) { 355 addWarningPanel(x,"⹋⹋ - this mark indicates that there are more than one expectation extensions present"); 356 } 357 358 return true; 359 } 360 361 private String getVersionPathComponent(String definition) { 362 if (Utilities.noString(definition)) return ""; 363 if (!definition.startsWith(VERS_DEF_PREFIX)) return ""; 364 String restOfDef[] = definition.substring(VERS_DEF_PREFIX.length()).split(" "); 365 if (restOfDef[1].startsWith("(")) return "R"+restOfDef[0]; 366 return ""; 367 } 368 369 public void describe(XhtmlNode x, CapabilityStatement cs) { 370 x.tx(display(cs)); 371 } 372 373 public String display(CapabilityStatement cs) { 374 return cs.present(); 375 } 376 377 @Override 378 public String display(Resource r) throws UnsupportedEncodingException, IOException { 379 return ((CapabilityStatement) r).present(); 380 } 381 382 383 private boolean hasOp(CapabilityStatementRestResourceComponent r, TypeRestfulInteraction on) { 384 for (ResourceInteractionComponent op : r.getInteraction()) { 385 if (op.getCode() == on) 386 return true; 387 } 388 return false; 389 } 390 391 private String showOp(CapabilityStatementRestResourceComponent r, TypeRestfulInteraction on) { 392 for (ResourceInteractionComponent op : r.getInteraction()) { 393 if (op.getCode() == on) 394 return "y"; 395 } 396 return ""; 397 } 398 399 private String showOp(CapabilityStatementRestComponent r, SystemRestfulInteraction on) { 400 for (SystemInteractionComponent op : r.getInteraction()) { 401 if (op.getCode() == on) 402 return "y"; 403 } 404 return ""; 405 } 406 407 private XhtmlNode addTableRow(XhtmlNode t, String name) { 408 XhtmlNode tr = t.tr(); 409 tr.td().addText(name); 410 return tr.td(); 411 } 412 413 private void addTableRow(XhtmlNode t, String name, String value) { 414 XhtmlNode tr = t.tr(); 415 tr.td().addText(name); 416 tr.td().addText(value); 417 } 418 419 @Nullable 420 private String getExtValueCode(Extension ext) { 421 if (ext != null) { 422 return ext.getValueCodeType().getCode(); 423 } 424 return null; 425 } 426 427 private void addSupportedCSs(XhtmlNode x, CapabilityStatement cap) { 428 if (cap.hasInstantiates()) { 429 XhtmlNode p = x.para(); 430 p.tx(cap.getInstantiates().size() > 1 ? "This CapabilityStatement instantiates these CapabilityStatements" : "This CapabilityStatement instantiates the CapabilityStatement"); 431 boolean first = true; 432 for (CanonicalType ct : cap.getInstantiates()) { 433 CapabilityStatement cs = context.getContext().fetchResource(CapabilityStatement.class, ct.getValue(), cap); 434 if (first) {first = false;} else {p.tx(", ");}; 435 if (cs == null) { 436 p.code().tx(ct.getValue()); 437 } else { 438 p.ah(cs.getWebPath()).tx(cs.present()); 439 } 440 } 441 } 442 if (cap.hasImports()) { 443 XhtmlNode p = x.para(); 444 p.tx(cap.getImports().size() > 1 ? "This CapabilityStatement imports these CapabilityStatements" : "This CapabilityStatement imports the CapabilityStatement"); 445 boolean first = true; 446 for (CanonicalType ct : cap.getImports()) { 447 CapabilityStatement cs = context.getContext().fetchResource(CapabilityStatement.class, ct.getValue(), cap); 448 if (first) {first = false;} else {p.tx(", ");}; 449 if (cs == null) { 450 p.code().tx(ct.getValue()); 451 } else { 452 p.ah(cs.getWebPath()).tx(cs.present()); 453 } 454 } 455 } 456 } 457 458 private void addSupportedIGs(XhtmlNode x, CapabilityStatement cap) { 459 String capExpectation=null; 460 if (cap.hasImplementationGuide()) { 461 ArrayList<String> igShoulds = new ArrayList<String>(); 462 ArrayList<String> igShalls = new ArrayList<String>(); 463 ArrayList<String> igMays = new ArrayList<String>(); 464 int i=0; 465 for (CanonicalType c : cap.getImplementationGuide()) 466 { 467 capExpectation=getExtValueCode(c.getExtensionByUrl(EXPECTATION)); 468 if (!Utilities.noString(capExpectation)) { 469 if (capExpectation.equals("SHALL")) { 470 igShalls.add(c.asStringValue()); 471 } 472 else if (capExpectation.equals("SHOULD")) { 473 igShoulds.add(c.asStringValue()); 474 } 475 else if (capExpectation.equals("MAY")) { 476 igMays.add(c.asStringValue()); 477 } 478 } 479 else { 480 igShalls.add(c.asStringValue()); //default to SHALL 481 } 482 } 483 XhtmlNode ul = null; 484 if (igShalls.size() > 0) { 485 x.h(3,"shallIGs").addText(context.formatPhrase(RenderingContext.CAPABILTY_SHALL_SUPP)); 486 ul = x.ul(); 487 for (String url : igShalls) { 488 addResourceLink(ul.li(), url, url); 489 //ul.li().ah(url).addText(url); 490 } 491 } 492 if (igShoulds.size() > 0) { 493 x.h(3,"shouldIGs").addText(context.formatPhrase(RenderingContext.CAPABILITY_SHOULD_SUPP)); 494 ul = x.ul(); 495 for (String url : igShoulds) { 496 addResourceLink(ul.li(), url, url); 497 //ul.li().ah(url).addText(url); 498 } 499 } 500 if (igMays.size() > 0) { 501 x.h(3,"shouldIGs").addText(context.formatPhrase(RenderingContext.CAPABILITY_SHOULD_SUPP)); 502 ul = x.ul(); 503 for (String url : igMays) { 504 addResourceLink(ul.li(), url, url); 505 //ul.li().ah(url).addText(url); 506 } 507 } 508 509 } 510 } 511 512 private void addSupportedFormats(XhtmlNode uList, CapabilityStatement conf) { 513 XhtmlNode lItem = uList.li(); 514 lItem.addText(context.formatPhrase(RenderingContext.CAPABILITY_SUPP_FORM) + " "); 515 Boolean first = true; 516 String capExpectation = null; 517 for (CodeType c : conf.getFormat()) { 518 if (!first) { 519 lItem.addText(", "); 520 } 521 capExpectation = getExtValueCode(c.getExtensionByUrl(EXPECTATION)); 522 if (!Utilities.noString(capExpectation)) { 523 lItem.addTag("strong").addText(capExpectation); 524 lItem.addText(" "+ (context.formatPhrase(RenderingContext.CAPABILITY_SUPP) + " ")); 525 } 526 lItem.code().addText(c.getCode()); 527 first = false; 528 } 529 lItem = uList.li(); 530 lItem.addText(context.formatPhrase(RenderingContext.CAPABILITY_SUPP_PATCH_FORM) + " "); 531 first=true; 532 for (CodeType c : conf.getPatchFormat()) { 533 if (!first) { 534 lItem.addText(", "); 535 } 536 capExpectation = getExtValueCode(c.getExtensionByUrl(EXPECTATION)); 537 if (!Utilities.noString(capExpectation)) { 538 lItem.addTag("strong").addText(capExpectation); 539 lItem.addText(context.formatPhrase(RenderingContext.CAPABILITY_SUPP) + " "); 540 } 541 lItem.code().addText(c.getCode()); 542 first = false; 543 } 544 } 545 546 private void addRestConfigPanel(XhtmlNode x, CapabilityStatementRestComponent rest, int nextLevel, int count) throws FHIRFormatError, DefinitionException, IOException { 547 XhtmlNode panel= null; 548 XhtmlNode body = null; 549 XhtmlNode row = null; 550 XhtmlNode cell = null; 551 XhtmlNode heading = null; 552 panel = x.div().attribute("class", "panel panel-default"); 553 heading = panel.div().attribute("class", "panel-heading").h(nextLevel,"mode" + Integer.toString(count)).attribute("class", "panel-title"); 554 heading.addText("Mode: "); 555 heading.code().addText(rest.getMode().toCode()); 556 body = panel.div().attribute("class", "panel-body"); 557 addMarkdown(body, rest.getDocumentation()); 558 //Security info 559 if (rest.hasSecurity()) { 560 body.div().attribute("class","lead").addTag("em").addText("Security"); 561 String mdText = rest.getSecurity().getDescription(); 562 boolean cors = rest.getSecurity().getCors(); 563 List<CodeableConcept> services = rest.getSecurity().getService(); 564 if (cors || services.size() >0) { 565 row = body.div().attribute("class","row"); 566 row.div().attribute("class","col-lg-6").addText(getCorsText(cors)); 567 cell = row.div().attribute("class","col-lg-6"); 568 cell.addText("Security services supported: "); 569 addSeparatedListOfCodes(cell, getSecServices(services), ","); 570 } 571 572 if (!Utilities.noString(mdText)) { 573 addMarkdown(body.blockquote(),mdText); 574 } 575 } 576 body.div().attribute("class","lead").addTag("em").addText(context.formatPhrase(RenderingContext.CAPABILITY_SUMM_SYS_INT)); 577 addSystemInteractions(body, rest.getInteraction()); 578 579 } 580 581 private String getCorsText(boolean on) { 582 if (on) { 583 return context.formatPhrase(RenderingContext.CAPABILITY_CORS_YES); 584 } 585 return context.formatPhrase(RenderingContext.CAPABILITY_CORS_NO); 586 } 587 588 private List<String> getSecServices(List<CodeableConcept> services) 589 { 590 List<String> serviceStrings = new ArrayList<String>(); 591 for (CodeableConcept c: services) { 592 if (c.hasCoding()){ 593 serviceStrings.add(c.getCodingFirstRep().getCode()); 594 } 595 else { 596 serviceStrings.add(c.getText()); 597 } 598 } 599 return serviceStrings; 600 } 601 602 603 private void addSystemInteractions(XhtmlNode body, List<SystemInteractionComponent> interactions) { 604 if (interactions.size()==0) return; 605 XhtmlNode uList = body.ul(); 606 String capExpectation = null; 607 String expName = null; 608 String documentation = null; 609 Map<String,String> expression = null; 610 ArrayList<Map<String,String>> shalls = new ArrayList<Map<String,String>>(); 611 ArrayList<Map<String,String>> shoulds = new ArrayList<Map<String,String>>(); 612 ArrayList<Map<String,String>> mays = new ArrayList<Map<String,String>>(); 613 ArrayList<Map<String,String>> shouldnots = new ArrayList<Map<String,String>>(); 614 ArrayList<Map<String,String>> supports = new ArrayList<Map<String,String>>(); 615 for (SystemInteractionComponent comp : interactions) { 616 capExpectation=getExtValueCode(comp.getExtensionByUrl(EXPECTATION)); 617 expName = comp.getCode().toCode(); 618 documentation = comp.getDocumentation(); 619 if (Utilities.noString(documentation)) { 620 documentation=""; 621 } 622 expression = new HashMap<String,String>(); 623 expression.put(expName,documentation); 624 if (!Utilities.noString(capExpectation)) { 625 if (capExpectation.equals("SHALL")) { 626 shalls.add(expression); 627 } 628 else if (capExpectation.equals("SHOULD")) { 629 shoulds.add(expression); 630 } 631 else if (capExpectation.equals("MAY")) { 632 mays.add(expression); 633 } 634 else if (capExpectation.equals("SHOULD-NOT")) { 635 shouldnots.add(expression); 636 } 637 } 638 else { 639 supports.add(expression); 640 } 641 } 642 addInteractionListItems(uList, "SHALL", shalls); 643 addInteractionListItems(uList, "SHOULD", shoulds); 644 addInteractionListItems(uList, "MAY", mays); 645 addInteractionListItems(uList, "SHOULD NOT", shouldnots); 646 addInteractionListItems(uList, null, supports); 647 } 648 649 private void addInteractionListItems(XhtmlNode uList, String verb, List<Map<String,String>> interactions) { 650 XhtmlNode item = null; 651 String interaction = null; 652 String documentation = null; 653 for (Map<String,String> interactionMap : interactions) { 654 item = uList.li(); 655 if (Utilities.noString(verb)) { 656 item.addText(context.formatPhrase(RenderingContext.CAPABILITY_SUPP_THE) + " "); 657 } 658 else { 659 item.addTag("strong").addText(verb); 660 item.addText(context.formatPhrase(RenderingContext.CAPABILITY_SUPP_THE) + " "); 661 } 662 interaction = interactionMap.keySet().toArray()[0].toString(); 663 item.code(interaction); 664 documentation = interactionMap.get(interaction); 665 if (Utilities.noString(documentation)) { 666 item.addText(context.formatPhrase(RenderingContext.CAPABILITY_INT)); 667 } 668 else { 669 item.addText(context.formatPhrase(RenderingContext.CAPABILITY_INT_DESC)); 670 try { 671 addMarkdown(item, documentation); 672 } catch (FHIRFormatError e) { 673 // TODO Auto-generated catch block 674 e.printStackTrace(); 675 } catch (DefinitionException e) { 676 // TODO Auto-generated catch block 677 e.printStackTrace(); 678 } catch (IOException e) { 679 // TODO Auto-generated catch block 680 e.printStackTrace(); 681 } 682 } 683 } 684 } 685 686 private void addInteractionSummaryList(XhtmlNode uList, String verb, List<String> interactions) { 687 if (interactions.size() == 0) return; 688 XhtmlNode item = uList.li(); 689 if (Utilities.noString(verb)) { 690 item.addText(context.formatPhrase(RenderingContext.CAPABILITY_SUPPS) + " "); 691 } 692 else { 693 item.addTag("strong").addText(verb); 694 item.addText(context.formatPhrase(RenderingContext.CAPABILITY_SUPP) + " "); 695 } 696 addSeparatedListOfCodes(item, interactions, ","); 697 item.addText("."); 698 } 699 700 private void addSummaryIntro(XhtmlNode x) { 701 XhtmlNode uList = null; 702 XhtmlNode lItem = null; 703 x.para().addText(context.formatPhrase(RenderingContext.CAPABILITY_SUMM_RES)); 704 uList=x.ul(); 705 uList.li().addText(context.formatPhrase(RenderingContext.CAPABILITY_REV_PROF)); 706 lItem = uList.li(); 707 lItem.addText(context.formatPhrase(RenderingContext.CAPABILITY_INTER_SUPP)); 708 lItem.b().addTag("span").attribute("class","bg-info").addText("R"); 709 lItem.addText("ead, "); 710 lItem.b().addTag("span").attribute("class","bg-info").addText("S"); 711 lItem.addText("earch, "); 712 lItem.b().addTag("span").attribute("class","bg-info").addText("U"); 713 lItem.addText("pdate, and "); 714 lItem.b().addTag("span").attribute("class","bg-info").addText("C"); 715 lItem.addText("reate, are always shown, while "); 716 lItem.b().addTag("span").attribute("class","bg-info").addText("VR"); 717 lItem.addText("ead, "); 718 lItem.b().addTag("span").attribute("class","bg-info").addText("P"); 719 lItem.addText("atch, "); 720 lItem.b().addTag("span").attribute("class","bg-info").addText("D"); 721 lItem.addText("elete, "); 722 lItem.b().addTag("span").attribute("class","bg-info").addText("H"); 723 lItem.addText("istory on "); 724 lItem.b().addTag("span").attribute("class","bg-info").addText("I"); 725 lItem.addText("nstance, or "); 726 lItem.b().addTag("span").attribute("class","bg-info").addText("H"); 727 lItem.addText("istory on "); 728 lItem.b().addTag("span").attribute("class","bg-info").addText("T"); 729 lItem.addText(context.formatPhrase(RenderingContext.CAPABILITY_TYP_PRES)); 730 uList.li().addTag("span").addText(context.formatPhrase(RenderingContext.CAPABILITY_SEARCH_PAR) + " "); 731 lItem = uList.li(); 732 lItem.addText(context.formatPhrase(RenderingContext.CAPABILITY_RES_ENB) + " "); 733 lItem.code().addText("_include"); 734 lItem = uList.li(); 735 lItem.addText(context.formatPhrase(RenderingContext.CAPABILITY_OTH_RES_ENB) + " "); 736 lItem.code().addText("_revinclude"); 737 uList.li().addText(context.formatPhrase(RenderingContext.CAPABILITY_RES_OPER)); 738 } 739 740 private void addSummaryTable(XhtmlNode x, CapabilityStatement.CapabilityStatementRestComponent rest, boolean hasVRead, boolean hasPatch, boolean hasDelete, boolean hasHistory, boolean hasUpdates, int count) throws IOException { 741 XhtmlNode t = x.div().attribute("class","table-responsive").table("table table-condensed table-hover"); 742 XhtmlNode tr = t.addTag("thead").tr(); 743 tr.th().b().tx(context.formatPhrase(RenderingContext.CAPABILITY_RES_TYP)); 744 tr.th().b().tx(context.formatPhrase(RenderingContext.GENERAL_PROF)); 745 tr.th().attribute("class", "text-center").b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_READ_INT)).tx("R"); 746 if (hasVRead) 747 tr.th().attribute("class", "text-center").b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_VREAD_INT)).tx("V-R"); 748 tr.th().attribute("class", "text-center").b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_SEARCH_INT)).tx("S"); 749 tr.th().attribute("class", "text-center").b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_UPDATE_INT)).tx("U"); 750 if (hasPatch) 751 tr.th().attribute("class", "text-center").b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_PATCH_INT)).tx("P"); 752 tr.th().attribute("class", "text-center").b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_CREATE_INT)).tx("C"); 753 if (hasDelete) 754 tr.th().attribute("class", "text-center").b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_DELETE_INT)).tx("D"); 755 if (hasUpdates) 756 tr.th().attribute("class", "text-center").b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_HISTORY_INT)).tx("H-I"); 757 if (hasHistory) 758 tr.th().attribute("class", "text-center").b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_HISTORY_TYPE)).tx("H-T"); 759 tr.th().b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_REQ_RECOM)).tx(context.formatPhrase(RenderingContext.CAPABILITY_SEARCHES)); 760 tr.th().code().b().tx("_include"); 761 tr.th().code().b().tx("_revinclude"); 762 tr.th().b().tx(context.formatPhrase(RenderingContext.CAPABILITY_OP)); 763 764 XhtmlNode tbody = t.addTag("tbody"); 765 XhtmlNode profCell = null; 766 boolean hasProf = false; 767 boolean hasSupProf = false; 768 int resCount = 1; 769 String countString = ""; 770 for (CapabilityStatementRestResourceComponent r : rest.getResource()) { 771 tr = tbody.tr(); 772 countString = Integer.toString(count) + "-" + Integer.toString(resCount); 773 tr.td().ah("#" + r.getType() + countString).addText(r.getType()); 774 resCount++; 775 //Show profiles 776 profCell = tr.td(); 777 hasProf = r.hasProfile(); 778 hasSupProf = r.hasSupportedProfile(); 779 if ((!hasProf) && (!hasSupProf)) { 780 profCell.nbsp(); 781 } 782 else if (hasProf) { 783 addResourceLink(profCell, r.getProfile(), r.getProfile()); 784 //profCell.ah(r.getProfile()).addText(r.getProfile()); 785 if (hasSupProf) { 786 profCell.br(); 787 profCell.addTag("em").addText(context.formatPhrase(RenderingContext.CAPABILITY_ADD_SUPP_PROF)); 788 renderSupportedProfiles(profCell, r); 789 } 790 } 791 else { //Case of only supported profiles 792 profCell.addText(context.formatPhrase(RenderingContext.CAPABILITY_SUPP_PROFS)); 793 renderSupportedProfiles(profCell, r); 794 } 795 //Show capabilities 796 tr.td().addText(showOp(r, TypeRestfulInteraction.READ)); 797 if (hasVRead) 798 tr.td().attribute("class", "text-center").addText(showOp(r, TypeRestfulInteraction.VREAD)); 799 tr.td().attribute("class", "text-center").addText(showOp(r, TypeRestfulInteraction.SEARCHTYPE)); 800 tr.td().attribute("class", "text-center").addText(showOp(r, TypeRestfulInteraction.UPDATE)); 801 if (hasPatch) 802 tr.td().attribute("class", "text-center").addText(showOp(r, TypeRestfulInteraction.PATCH)); 803 tr.td().attribute("class", "text-center").addText(showOp(r, TypeRestfulInteraction.CREATE)); 804 if (hasDelete) 805 tr.td().attribute("class", "text-center").addText(showOp(r, TypeRestfulInteraction.DELETE)); 806 if (hasUpdates) 807 tr.td().attribute("class", "text-center").addText(showOp(r, TypeRestfulInteraction.HISTORYINSTANCE)); 808 if (hasHistory) 809 tr.td().attribute("class", "text-center").addText(showOp(r, TypeRestfulInteraction.HISTORYTYPE)); 810 //Show search parameters 811 List<String> stringList = new ArrayList<String>(); 812 getParams(stringList,r.getSearchParam()); 813 getCombinedParams(stringList,r.getExtensionsByUrl(COMBINED)); 814 tr.td().addText(getSeparatedList(stringList,",")); 815 //Show supported includes 816 stringList = getStringListFromStringTypeList(r.getSearchInclude()); 817 addSeparatedListOfCodes(tr.td(), stringList, ","); 818 //Show supported revIncludes 819 stringList = getStringListFromStringTypeList(r.getSearchRevInclude()); 820 addSeparatedListOfCodes(tr.td(), stringList, ","); 821 //Show supported operations 822 stringList = getStringListFromOperations(r.getOperation()); 823 addSeparatedListOfCodes(tr.td(), stringList, ","); 824 } 825 } 826 827 private List<String> getCombinedParams(List<String> paramNames, List<Extension> paramExtensions) { 828 for (Extension e : paramExtensions) { 829 String capExpectation = expectationForDisplay(e,EXPECTATION); 830 if (!Utilities.noString(capExpectation)) { 831 if (capExpectation.equals("SHALL") || capExpectation.equals("SHOULD") || capExpectation.equals("MAY")) { 832 paramNames.add(printCombinedParams(e)); 833 } 834 } 835 } 836 return paramNames; 837 } 838 839 private void renderSupportedProfiles(XhtmlNode profCell, CapabilityStatementRestResourceComponent r) throws IOException { 840 for (CanonicalType sp: r.getSupportedProfile()) { 841 profCell.br(); 842 profCell.nbsp().nbsp(); 843 StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, sp.getValue()); 844 if (sd != null) { 845 profCell.ah(sd.getWebPath()).addText(sd.present()); 846 } else { 847 profCell.ah(sp.getValue()).addText(sp.getValue()); 848 } 849 } 850 if (r.hasExtension(ToolingExtensions.EXT_PROFILE_MAPPING)) { 851 profCell.br(); 852 profCell.b().tx(context.formatPhrase(RenderingContext.CAPABILITY_PROF_MAP)); 853 XhtmlNode tbl = profCell.table("grid"); 854 boolean doco = false; 855 for (Extension ext : r.getExtensionsByUrl(ToolingExtensions.EXT_PROFILE_MAPPING)) { 856 doco = doco || ext.hasExtension("documentation"); 857 } 858 XhtmlNode tr = tbl.tr(); 859 tr.th().tx(context.formatPhrase(RenderingContext.GENERAL_CRIT)); 860 tr.th().tx(context.formatPhrase(RenderingContext.GENERAL_PROF)); 861 if (doco) { 862 tr.th().tx(context.formatPhrase(RenderingContext.GENERAL_CRIT)); 863 } 864 for (Extension ext : r.getExtensionsByUrl(ToolingExtensions.EXT_PROFILE_MAPPING)) { 865 tr = tbl.tr(); 866 tr.td().code().tx(ToolingExtensions.readStringExtension(ext, "search")); 867 String url = ToolingExtensions.readStringExtension(ext, "profile"); 868 StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, url); 869 if (sd != null) { 870 tr.td().code().ah(sd.getWebPath()).tx(sd.present()); 871 } else { 872 tr.td().code().tx(url); 873 } 874 if (doco) { 875 tr.td().code().markdown(ToolingExtensions.readStringExtension(ext, "documentation"), "documentation"); 876 } 877 } 878 } 879 } 880 881 private String printCombinedParams(Extension e) { 882 StringBuilder params = new StringBuilder(); 883 List<Extension> requiredParams = e.getExtensionsByUrl("required"); 884 List<Extension> optionalParams = e.getExtensionsByUrl("optional"); 885 boolean first = true; 886 for (Extension param : requiredParams) { 887 if (!first) { 888 params.append("+"); 889 } 890 first = false; 891 params.append(param.getValueStringType()); 892 } 893 for (Extension param : optionalParams) { 894 params.append("+"); 895 params.append(param.getValueStringType()); 896 } 897 return params.toString(); 898 } 899 900 private List<String> getParams(List<String> paramNames, List<CapabilityStatementRestResourceSearchParamComponent> params) { 901 for (CapabilityStatementRestResourceSearchParamComponent p : params) { 902 String capExpectation = expectationForDisplay(p,EXPECTATION); 903 if (!Utilities.noString(capExpectation)) { 904 if (capExpectation.equals("SHALL") || capExpectation.equals("SHOULD") || capExpectation.equals("MAY")) { 905 paramNames.add(p.getName()); 906 } 907 } 908 else { //When there is not extension, assume parameters are required 909 paramNames.add(p.getName()); 910 } 911 } 912 return paramNames; 913 } 914 915 private String getSeparatedList(List<String> list, String separator) { 916 StringBuilder result = new StringBuilder(); 917 boolean first = true; 918 for (String s : list) { 919 if (!first) { 920 result.append(separator + " "); 921 } 922 first = false; 923 result.append(s); 924 } 925 return result.toString(); 926 } 927 928 private List<String> getStringListFromStringTypeList(List<StringType> list) { 929 List<String> stringList = new ArrayList<String>(); 930 for (StringType st : list) { 931 stringList.add(st.asStringValue()); 932 } 933 return stringList; 934 } 935 936 private void addSeparatedListOfCodes(XhtmlNode parent, List<String> list, String separator) { 937 boolean first = true; 938 for (String s : list) { 939 if (!first) { 940 parent.addText(separator + " "); 941 } 942 first = false; 943 parent.code().addText(s); 944 } 945 } 946 947 private List<String> getStringListFromOperations(List<CapabilityStatementRestResourceOperationComponent> list) { 948 List<String> result = new ArrayList<String>(); 949 for (CapabilityStatementRestResourceOperationComponent op : list) { 950 String capExpectation = expectationForDisplay(op,EXPECTATION); 951 if (!Utilities.noString(capExpectation)) { 952 if (capExpectation.equals("SHALL") || capExpectation.equals("SHOULD") || capExpectation.equals("MAY")) { 953 result.add("$"+op.getName()); 954 } 955 } 956 } 957 return result; 958 } 959 960 private void addResourceConfigPanel(XhtmlNode x, CapabilityStatementRestResourceComponent r, int nextLevel, int count, int resCount, boolean igRenderingMode) throws FHIRFormatError, DefinitionException, IOException { 961 XhtmlNode panel= null; 962 XhtmlNode body = null; 963 XhtmlNode panelHead = null; 964 XhtmlNode panelRef = null; 965 966 String countString = Integer.toString(count) + "-" + Integer.toString(resCount); 967 panel = x.div().attribute("class", "panel panel-default"); 968 if (igRenderingMode) { 969 panelHead = panel.div().attribute("class", "panel-heading").attribute("role", "tab").attribute("id","heading" + countString).h(nextLevel,r.getType() + countString).attribute("class", "panel-title"); 970 panelRef = panelHead.ah("#collapse" + countString).attribute("role","button").attribute("data-toggle", "collapse").attribute("aria-expanded","true").attribute("aria-controls","collapse" + countString); 971 //panelRef = panelHead.addTag("button").attribute("href","#collapse" + countString).attribute("role","button").attribute("data-toggle", "collapse").attribute("aria-expanded","false").attribute("aria-controls","collapse" + countString); 972 //panelRef.span("float: right;","").attribute("class", "lead").addText("Resource Conformance: " + getResourceExpectation(r)); 973 panelRef.span("float: right;","").addText("Resource Conformance: " + getResourceExpectation(r)); 974 panelRef.addText(r.getType()); 975 body = panel.div().attribute("class", collapseClass).attribute("id","collapse" + countString).attribute("role","tabpanel").attribute("aria-labelledby","heading" + countString).div().attribute("class", "panel-body").div().attribute("class", "container"); 976 } 977 else { 978 panelHead = panel.div().attribute("class", "panel-heading").h(nextLevel,r.getType() + countString).attribute("class", "panel-title"); 979 panelHead.span("float: right;","").addText(context.formatPhrase(RenderingContext.CAPABILITY_RES_CONF, getResourceExpectation(r)) + " "); 980 panelHead.addText(r.getType()); 981 body = panel.div().attribute("class", "panel-body").div().attribute("class", "container"); 982 } 983 984 985 //Top part of panel 986 XhtmlNode cell = null; 987 XhtmlNode row = body.div().attribute("class", "row"); 988 String text = r.getProfile(); 989 boolean pullInteraction = false; 990 String refPolicyWidth = "col-lg-3"; 991 if (!Utilities.noString(text)) { 992 cell = row.div().attribute("class", "col-lg-6"); 993 addLead(cell,context.formatPhrase(RenderingContext.CAPABILITY_BASE_SYS)); 994 cell.br(); 995 addResourceLink(cell, text, text); 996 cell=row.div().attribute("class", "col-lg-3"); 997 addLead(cell, context.formatPhrase(RenderingContext.CAPABILITY_PROF_CONF)); 998 cell.br(); 999 cell.b().addText(getProfileExpectation(r.getProfileElement())); 1000 } 1001 else { //No profile, use FHIR Core Resource 1002 cell = row.div().attribute("class", "col-lg-4"); 1003 addLead(cell, context.formatPhrase(RenderingContext.CAPABILITY_FHIR)); 1004 cell.br(); 1005 cell.ah(currentFhirBase + r.getType().toLowerCase() + ".html").addText(r.getType()); 1006 pullInteraction = true; 1007 refPolicyWidth = "col-lg-4"; 1008 } 1009 1010 cell = row.div().attribute("class", refPolicyWidth); 1011 addLead(cell,context.formatPhrase(RenderingContext.CAPABILITY_REF_PROF)); 1012 cell.br(); 1013 addSeparatedListOfCodes(cell, getReferencePolicyStrings(r.getReferencePolicy()) , ","); 1014 if (pullInteraction) { 1015 addInteractions(row, r, 4); 1016 } 1017 body.para(); 1018 List<CanonicalType> supportedProfiles = r.getSupportedProfile(); 1019 if (supportedProfiles.size() > 0) { 1020 row = body.div().attribute("class", "row"); 1021 cell = row.div().attribute("class", "col-6"); 1022 addLead(cell, context.formatPhrase(RenderingContext.CAPABILITY_SUPP_PROFS)); 1023 XhtmlNode para = cell.para(); 1024 boolean first = true; 1025 for (CanonicalType c : supportedProfiles) { 1026 if (!first) { 1027 para.br(); 1028 } 1029 first=false; 1030 addResourceLink(para, c.asStringValue(), c.asStringValue()); 1031 //para.ah(c.asStringValue()).addText(c.asStringValue()); 1032 } 1033 } 1034 if (!pullInteraction) { 1035 if (supportedProfiles.size() < 1) { 1036 row = body.div().attribute("class", "row"); 1037 } 1038 addInteractions(row, r, 6); 1039 } 1040 1041 //Resource Documentation 1042 body.para(); 1043 String mdText = r.getDocumentation(); 1044 if (!Utilities.noString(mdText)) { 1045 row = body.div().attribute("class", "row"); 1046 cell = row.div().attribute("class", "col-12"); 1047 addLead(cell, context.formatPhrase(RenderingContext.GENERAL_DOCUMENTATION)); 1048 addMarkdown(cell.blockquote(), mdText); 1049 } 1050 1051 //Resource search parameters 1052 ResourceSearchParams sParams = collectParams(r); 1053 addSearchParams(body, sParams); 1054 //addSearchParamsDocumentation(body, sParams); 1055 //Resource operations 1056 ResourceOperations ops = collectOperations(r); 1057 addExtendedOperations(body, ops); 1058 } 1059 1060 private void addExtendedOperations(XhtmlNode body, ResourceOperations ops) { 1061 if (ops == null) return; 1062 Map<String, List<SingleOperation>> map = ops.getOperations(); 1063 if (!hasOperations(map)) return; 1064 XhtmlNode row; 1065 XhtmlNode cell; 1066 XhtmlNode table; 1067 XhtmlNode tbody; 1068 XhtmlNode tr; 1069 row = body.div().attribute("class", "row"); 1070 cell = row.div().attribute("class", "col-12"); 1071 addLead(cell, context.formatPhrase(RenderingContext.CAPABILITY_EXT_OP)); 1072 table = cell.table("table table-condensed table-hover"); 1073 tr = table.addTag("thead").tr(); 1074 tr.th().addText(context.formatPhrase(RenderingContext.GENERAL_CONFORMANCE)); 1075 tr.th().addText(context.formatPhrase(RenderingContext.CAPABILITY_OPER)); 1076 tr.th().addText(context.formatPhrase(RenderingContext.GENERAL_DOCUMENTATION)); 1077 tbody = table.addTag("tbody"); 1078 addOps(tbody, map, "supported"); 1079 addOps(tbody, map, "SHALL"); 1080 addOps(tbody, map, "SHOULD"); 1081 addOps(tbody, map, "MAY"); 1082 addOps(tbody, map, "SHOULD-NOT"); 1083 return; 1084 } 1085 1086 private ResourceOperations collectOperations(CapabilityStatementRestResourceComponent r) { 1087 List <CapabilityStatementRestResourceOperationComponent> opList = r.getOperation(); 1088 if (opList.size()==0) return null; 1089 String capExpectation; 1090 SingleOperation operation; 1091 ResourceOperations ops = new ResourceOperations(); 1092 1093 for ( CapabilityStatementRestResourceOperationComponent op : opList) { 1094 capExpectation = expectationForDisplay(op,EXPECTATION); 1095 if (Utilities.noString(capExpectation)) { 1096 capExpectation = "supported"; 1097 } 1098 operation = new SingleOperation(op.getName(),op.getDefinition(),op.getDocumentation(),capExpectation); 1099 ops.addOperation(capExpectation, operation); 1100 } 1101 return ops; 1102 } 1103 1104 private void addInteractions(XhtmlNode row, CapabilityStatementRestResourceComponent r, int width) { 1105 String capExpectation; 1106 String widthString = "col-lg-" + Integer.toString(width); 1107 List<String> shalls = new ArrayList<String>(); 1108 List<String> shoulds = new ArrayList<String>(); 1109 List<String> mays = new ArrayList<String>(); 1110 List<String> shouldnots = new ArrayList<String>(); 1111 List<String> supporteds = new ArrayList<String>(); 1112 1113 for (ResourceInteractionComponent op : r.getInteraction()) { 1114 capExpectation = expectationForDisplay(op,EXPECTATION); 1115 if (!Utilities.noString(capExpectation)) { 1116 switch(capExpectation) { 1117 case "SHALL" : shalls.add(op.getCode().toCode()); 1118 break; 1119 case "SHOULD" : shoulds.add(op.getCode().toCode()); 1120 break; 1121 case "MAY" : mays.add(op.getCode().toCode()); 1122 break; 1123 case "SHOULD-NOT" : shouldnots.add(op.getCode().toCode()); 1124 break; 1125 } 1126 } 1127 else { 1128 supporteds.add(op.getCode().toCode()); 1129 } 1130 } 1131 XhtmlNode cell = row.div().attribute("class", widthString); 1132 addLead(cell, context.formatPhrase(RenderingContext.CAPABILITY_INT_SUMM)); 1133 cell.br(); 1134 XhtmlNode ul = cell.ul(); 1135 addInteractionSummaryList(ul, "SHALL", shalls); 1136 addInteractionSummaryList(ul, "SHOULD", shoulds); 1137 addInteractionSummaryList(ul, "MAY", mays); 1138 addInteractionSummaryList(ul, "SHOULD-NOT", shouldnots); 1139 addInteractionSummaryList(ul, "", supporteds); 1140 } 1141 1142 private ResourceSearchParams collectParams(CapabilityStatementRestResourceComponent r) { 1143 ResourceSearchParams sParams = new ResourceSearchParams(); 1144 String capExpectation; 1145 SingleParam param; 1146 for ( CapabilityStatementRestResourceSearchParamComponent sp : r.getSearchParam()) { 1147 capExpectation = expectationForDisplay(sp,EXPECTATION); 1148 if (Utilities.noString(capExpectation)) { 1149 capExpectation = "supported"; 1150 } 1151 param = new SingleParam(sp.getName(),sp.getDefinition(),sp.getType().toCode(),sp.getDocumentation(),capExpectation, r.getType().toLowerCase()); 1152 sParams.addIndividualbyName(param.getName(), param); 1153 sParams.addIndividualbyExp(capExpectation,param); 1154 } 1155 //CombinedSearchParam component; 1156 CombinedSearchParamSet combinedParams; 1157 String paramName; 1158 for (Extension e : r.getExtensionsByUrl(COMBINED)) { 1159 capExpectation = expectationForDisplay(e,EXPECTATION); 1160 if (Utilities.noString(capExpectation)) { 1161 capExpectation = "supported"; 1162 } 1163 combinedParams = new CombinedSearchParamSet(capExpectation); 1164 for (Extension cmpnt : e.getExtensionsByUrl("required")) { 1165 paramName = cmpnt.getValueStringType().asStringValue(); 1166 param = sParams.getIndbyName().get(paramName); 1167 if (param == null) { 1168 param = new SingleParam(paramName,"","<unknown>"); 1169 } 1170 //component = new CombinedSearchParam(param, true); 1171 combinedParams.addParam(true, param); 1172 } 1173 for (Extension cmpnt : e.getExtensionsByUrl("optional")) { 1174 paramName = cmpnt.getValueStringType().asStringValue(); 1175 param = sParams.getIndbyName().get(paramName); 1176 if (param == null) { 1177 param = new SingleParam(paramName); 1178 } 1179 //component = new CombinedSearchParam(param); 1180 combinedParams.addParam(false, param); 1181 } 1182 sParams.addCombinedParamSet(capExpectation, combinedParams); 1183 } 1184 return sParams; 1185 } 1186 1187 private void addSearchParams(XhtmlNode body, ResourceSearchParams sParams) { 1188 Map<String, List<CombinedSearchParamSet>> comboMap = sParams.getCombined(); 1189 if (isCombinedEmpty(comboMap) && sParams.getIndbyName().size()==0) return; 1190 XhtmlNode row; 1191 XhtmlNode cell; 1192 XhtmlNode table; 1193 XhtmlNode tbody; 1194 XhtmlNode tr; 1195 row = body.div().attribute("class", "row"); 1196 cell = row.div().attribute("class", "col-lg-7"); 1197 addLead(cell, context.formatPhrase(RenderingContext.CAPABILITY_SEARCH_PARS)); 1198 table = cell.table("table table-condensed table-hover"); 1199 tr = table.addTag("thead").tr(); 1200 tr.th().addText(context.formatPhrase(RenderingContext.GENERAL_CONFORMANCE)); 1201 tr.th().addText(context.formatPhrase(RenderingContext.GENERAL_PAR)); 1202 tr.th().addText(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 1203 tr.th().addText(context.formatPhrase(RenderingContext.GENERAL_DOCUMENTATION)); 1204 tbody = table.addTag("tbody"); 1205 Map<String,List<SingleParam>> map = sParams.getIndbyExp(); 1206 addIndRows(tbody, map, "supported"); 1207 addIndRows(tbody, map, "SHALL"); 1208 addIndRows(tbody, map, "SHOULD"); 1209 addIndRows(tbody, map, "MAY"); 1210 addIndRows(tbody, map, "SHOULD-NOT"); 1211 cell = row.div().attribute("class", "col-lg-5"); 1212 if (!isCombinedEmpty(comboMap)) { 1213 addLead(cell, context.formatPhrase(RenderingContext.CAPABILITY_COMB_SEARCH_PAR)); 1214 table = cell.table("table table-condensed table-hover"); 1215 tr = table.addTag("thead").tr(); 1216 tr.th().addText(context.formatPhrase(RenderingContext.GENERAL_CONFORMANCE)); 1217 tr.th().addText(context.formatPhrase(RenderingContext.GENERAL_PARS)); 1218 tr.th().addText(context.formatPhrase(RenderingContext.CAPABILITY_TYPS)); 1219 tbody = table.addTag("tbody"); 1220 addComboRows(tbody, comboMap, "supported"); 1221 addComboRows(tbody, comboMap, "SHALL"); 1222 addComboRows(tbody, comboMap, "SHOULD"); 1223 addComboRows(tbody, comboMap, "MAY"); 1224 addComboRows(tbody, comboMap, "SHOULD-NOT"); 1225 } 1226 else { 1227 cell.nbsp(); 1228 } 1229 } 1230 1231 private void addIndRows(XhtmlNode tbody, Map<String, List<SingleParam>> map, String exp) { 1232 XhtmlNode tr; 1233 for (SingleParam param: map.get(exp)) { 1234 tr=tbody.tr(); 1235 if (!exp.equals("supported")) { 1236 tr.td().b().addText(exp); 1237 } 1238 else { 1239 tr.td().b().addText("SHALL"); 1240 } 1241 addResourceLink(tr.td(), param.getName(), param.getDefinition(),true, param.getHostResource()); 1242 /*if (Utilities.noString(param.getDefinition())) { 1243 tr.td().addText(param.getName()); 1244 } 1245 else { 1246 tr.td().ah(param.getDefinition()).addText(param.getName()); 1247 }*/ 1248 tr.td().code().addText(param.getType()); 1249 try { 1250 addMarkdown(tr.td(), param.getDocumentation()); 1251 } catch (FHIRFormatError e) { 1252 // TODO Auto-generated catch block 1253 e.printStackTrace(); 1254 } catch (DefinitionException e) { 1255 // TODO Auto-generated catch block 1256 e.printStackTrace(); 1257 } catch (IOException e) { 1258 // TODO Auto-generated catch block 1259 e.printStackTrace(); 1260 } 1261 } 1262 } 1263 1264 private void addOps(XhtmlNode tbody, Map<String, List<SingleOperation>> map, String exp) { 1265 XhtmlNode tr; 1266 String name; 1267 String canonicalUri; 1268 for (SingleOperation operation: map.get(exp)) { 1269 tr = tbody.tr(); 1270 name = "$" + operation.getName(); 1271 if (!exp.equals("supported")) { 1272 tr.td().b().addText(exp); 1273 } 1274 else { 1275 tr.td().b().addText("SHALL"); 1276 } 1277 canonicalUri = operation.getDefinition(); 1278 addResourceLink(tr.td(), name, canonicalUri); 1279 /* 1280 if (Utilities.noString(operation.getDefinition())) { 1281 tr.td().addText(name); 1282 } 1283 else { 1284 tr.td().ah(operation.getDefinition()).addText(name); 1285 Resource cr = context.getContext().fetchResource(Resource.class, operation.getDefinition()); 1286 } */ 1287 try { 1288 addMarkdown(tr.td(), operation.getDocumentation()); 1289 } catch (FHIRFormatError e) { 1290 // TODO Auto-generated catch block 1291 e.printStackTrace(); 1292 } catch (DefinitionException e) { 1293 // TODO Auto-generated catch block 1294 e.printStackTrace(); 1295 } catch (IOException e) { 1296 // TODO Auto-generated catch block 1297 e.printStackTrace(); 1298 } 1299 } 1300 } 1301 1302 private void addComboRows(XhtmlNode tbody, Map<String, List<CombinedSearchParamSet>> map, String exp) { 1303 XhtmlNode tr,td; 1304 boolean first; 1305 for (CombinedSearchParamSet paramSet: map.get(exp)) { 1306 tr=tbody.tr(); 1307 if (!exp.equals("supported")) { 1308 tr.td().b().addText(exp); 1309 } 1310 else { 1311 tr.td().nbsp(); 1312 } 1313 //Parameter combination 1314 td = tr.td(); 1315 first = true; 1316 for (SingleParam p : paramSet.getParams().get(true)) { 1317 if (!first) { 1318 td.addText("+"); 1319 } 1320 first=false; 1321 if (Utilities.noString(p.getDefinition())) { 1322 td.addText(p.getName()); 1323 } 1324 else { 1325 addResourceLink(td, p.getName(), p.getDefinition(), true, p.getHostResource()); 1326 //td.ah(p.param.getDefinition()).addText(p.param.getName()); 1327 } 1328 } 1329 if (paramSet.getParams().get(false).size() > 0) { 1330 td.addText("("); 1331 for (SingleParam p : paramSet.getParams().get(false)) { 1332 td.addText("+"); 1333 if (Utilities.noString(p.getDefinition())) { 1334 td.addText(p.getName()); 1335 } 1336 else { 1337 addResourceLink(td, p.getName(), p.getDefinition(), true, p.getHostResource()); 1338 //td.ah(p.param.getDefinition()).addText(p.param.getName()); 1339 } 1340 } 1341 td.addText(")"); 1342 } 1343 //types combination 1344 td = tr.td(); 1345 first = true; 1346 for (SingleParam p : paramSet.getParams().get(true)) { 1347 if (!first) { 1348 td.addText("+"); 1349 } 1350 first=false; 1351 td.code().addText(p.getType()); 1352 } 1353 if (paramSet.getParams().get(false).size() > 0) { 1354 td.addText("("); 1355 for (SingleParam p : paramSet.getParams().get(false)) { 1356 td.addText("+"); 1357 if (!p.getType().equals("<unknown>")) { 1358 td.code().addText(p.getType()); 1359 } 1360 else { 1361 td.addText(p.getType()); 1362 } 1363 } 1364 td.addText(")"); 1365 } 1366 } 1367 } 1368 1369 boolean isCombinedEmpty(Map<String, List<CombinedSearchParamSet>> combinedMap) { 1370 boolean result = true; 1371 for (String s : combinedMap.keySet()) { 1372 if (combinedMap.get(s).size() > 0) { 1373 result=false; 1374 break; 1375 } 1376 } 1377 return result; 1378 } 1379 1380 boolean hasOperations(Map<String, List<SingleOperation>> operationsMap) { 1381 boolean result = false; 1382 for (String s : operationsMap.keySet()) { 1383 if (operationsMap.get(s).size() > 0) { 1384 result=true; 1385 break; 1386 } 1387 } 1388 return result; 1389 } 1390 1391 private List<String> getReferencePolicyStrings(List<Enumeration<ReferenceHandlingPolicy>> list) { 1392 List<String> stringList = new ArrayList<String>(); 1393 for (Enumeration<ReferenceHandlingPolicy> p : list) { 1394 stringList.add(p.getCode()); 1395 } 1396 return stringList; 1397 } 1398 private String getResourceExpectation(CapabilityStatementRestResourceComponent r) { 1399 String capExpectation = expectationForDisplay(r,EXPECTATION); 1400 if (!Utilities.noString(capExpectation)) return capExpectation; 1401 boolean shalls = false; 1402 boolean shoulds = false; 1403 boolean mays = false; 1404 boolean shouldnots = false; 1405 for (ResourceInteractionComponent ric : r.getInteraction()) { 1406 capExpectation = expectationForDisplay(ric,EXPECTATION); 1407 if (!Utilities.noString(capExpectation)) { 1408 switch(capExpectation) { 1409 case "SHALL" : shalls = true; 1410 break; 1411 case "SHOULD" : shoulds = true; 1412 break; 1413 case "MAY" : mays = true; 1414 break; 1415 case "SHOULD-NOT" : shouldnots = true; 1416 break; 1417 } 1418 } 1419 } 1420 if (shalls) return "SHALL"; 1421 //Check search parameters requirements 1422 for ( CapabilityStatementRestResourceSearchParamComponent sp : r.getSearchParam()) { 1423 capExpectation = expectationForDisplay(sp,EXPECTATION); 1424 if (!Utilities.noString(capExpectation)) { 1425 switch(capExpectation) { 1426 case "SHALL" : shalls = true; 1427 break; 1428 case "SHOULD" : shoulds = true; 1429 break; 1430 case "MAY" : mays = true; 1431 break; 1432 case "SHOULD-NOT" : shouldnots = true; 1433 break; 1434 } 1435 } 1436 } 1437 if (shalls) return "SHALL"; 1438 if (shoulds) return "SHOULD"; 1439 if (mays) return "MAY"; 1440 if (shouldnots) return "SHOULD NOT"; 1441 return "supported"; 1442 } 1443 1444 private String getProfileExpectation(CanonicalType r) { 1445 String capExpectation = expectationForDisplay(r,EXPECTATION); 1446 if (!Utilities.noString(capExpectation)) return capExpectation; 1447 return "SHALL"; 1448 } 1449 1450 private void addLead(XhtmlNode node, String text) { 1451 node.addTag("span").attribute("class", "lead").addText(text); 1452 } 1453 1454 public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { 1455 if (r.has("title")) { 1456 return r.children("title").get(0).getBase().primitiveValue(); 1457 } 1458 if (r.has("name")) { 1459 return r.children("name").get(0).getBase().primitiveValue(); 1460 } 1461 return "??"; 1462 } 1463 1464 private void addResourceLink(XhtmlNode node, String name, String canonicalUri) { 1465 addResourceLink(node, name, canonicalUri, false, ""); 1466 } 1467 1468 private void addResourceLink(XhtmlNode node, String name, String canonicalUri, boolean isParam, String hostResource) { 1469 if (Utilities.noString(canonicalUri)) { 1470 node.addText(name); 1471 return; 1472 } 1473 1474 Resource cr = context.getContext().fetchResource(Resource.class, canonicalUri); 1475 if (cr == null) { 1476 node.addText(name); 1477 } 1478 else { 1479 //String path = cr.getUserString("path"); 1480 String path = cr.getWebPath(); 1481 if (Utilities.noString(path)) { 1482 if (isParam && (canonicalUri.toLowerCase().startsWith(SP_BASE)) && (!Utilities.noString(currentFhirBase)) && (!Utilities.noString(hostResource))) { 1483 String resourceName = ""; 1484 if (canonicalUri.substring(SP_BASE.length()).split("-")[0].toLowerCase().equals("resource")) { 1485 resourceName = "resource"; 1486 } 1487 else if (canonicalUri.substring(SP_BASE.length()).split("-")[0].toLowerCase().equals("domainresource")) { 1488 resourceName = "domainresource"; 1489 } 1490 else { 1491 resourceName = hostResource; 1492 } 1493 1494 node.ah(currentFhirBase + resourceName + ".html#search").addText(name); 1495 } 1496 else { 1497 node.addText(name); 1498 } 1499 } 1500 else { 1501 node.ah(path).addText(name); 1502 } 1503 } 1504 } 1505 1506 private String expectationForDisplay(Element e, String url) { 1507 String result; 1508 try { 1509 result = e.getExtensionString(url); 1510 return result; 1511 } 1512 catch (FHIRException fex) { 1513 List<Extension> ext = e.getExtensionsByUrl(url); 1514 if (ext.isEmpty()) 1515 return null; 1516 if (!ext.get(0).hasValue()) 1517 return null; 1518 multExpectationsPresent = true; 1519 return ext.get(0).getValue().primitiveValue() + "-⹋⹋"; 1520 } 1521 1522 } 1523 1524 private void addWarningPanel(XhtmlNode node, String text) { 1525 XhtmlNode panel = node.addTag("div").attribute("class","panel panel-danger").addTag("div").attribute("class","panel-body"); 1526 panel.addTag("span").attribute("class","label label-danger").addText("Error detected"); 1527 panel.addText(" " + text); 1528 } 1529}