Remove old comparison code

This commit is contained in:
Grahame Grieve 2020-08-18 17:58:52 +10:00
parent 17d9e61baf
commit 97e6330c58
3 changed files with 29 additions and 1023 deletions

View File

@ -1,923 +1,6 @@
package org.hl7.fhir.r5.conformance;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.conformance.ProfileComparer.ProfileComparison;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.context.SimpleWorkerContext;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.CapabilityStatement;
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceOperationComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestSecurityComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.ResourceInteractionComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.SystemInteractionComponent;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.DomainResource;
import org.hl7.fhir.r5.model.Element;
import org.hl7.fhir.r5.model.Enumeration;
import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
import org.hl7.fhir.r5.model.Enumerations.RestfulCapabilityMode;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.Property;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.utils.KeyGenerator;
import org.hl7.fhir.r5.utils.OperationOutcomeUtilities;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
import org.hl7.fhir.utilities.xhtml.XhtmlDocument;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.hl7.fhir.utilities.xhtml.XhtmlParser;
public class CapabilityStatementUtilities {
public class CapabilityStatementComparisonOutput {
public CapabilityStatementComparisonOutput() {
subset = new CapabilityStatement();
keygen.genId(subset);
subset.setDate(new Date());
subset.setName("intersection of "+selfName+" and "+otherName);
subset.setStatus(PublicationStatus.DRAFT);
superset = new CapabilityStatement();
keygen.genId(superset);
superset.setDate(subset.getDate());
superset.setName("union of "+selfName+" and "+otherName);
superset.setStatus(PublicationStatus.DRAFT);
}
private CapabilityStatement superset;
private CapabilityStatement subset;
private OperationOutcome outcome;
private List<ValidationMessage> messages = new ArrayList<>();
public CapabilityStatement getSuperset() {
return superset;
}
public CapabilityStatement getSubset() {
return subset;
}
public OperationOutcome getOutcome() {
return outcome;
}
public List<ValidationMessage> getMessages() {
return messages;
}
}
private IWorkerContext context;
private String selfName;
private String otherName;
private CapabilityStatementComparisonOutput output;
private XhtmlDocument html;
private MarkDownProcessor markdown = new MarkDownProcessor(Dialect.COMMON_MARK);
private String folder;
private KeyGenerator keygen;
// private String css;
public CapabilityStatementUtilities(SimpleWorkerContext context, String folder, KeyGenerator keygen) throws IOException {
super();
this.context = context;
this.folder = folder;
this.keygen = keygen;
if (!new File(Utilities.path(folder, "conparison-zip-marker.bin")).exists()) {
String f = Utilities.path(folder, "comparison.zip");
download("https://www.fhir.org/archive/comparison.zip", f);
unzip(f, folder);
}
}
/**
* Size of the buffer to read/write data
*/
private static final int BUFFER_SIZE = 4096;
/**
* Extracts a zip file specified by the zipFilePath to a directory specified by
* destDirectory (will be created if does not exists)
* @param zipFilePath
* @param destDirectory
* @throws IOException
*/
public void unzip(String zipFilePath, String destDirectory) throws IOException {
File destDir = new File(destDirectory);
if (!destDir.exists()) {
destDir.mkdir();
}
ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath));
ZipEntry entry = zipIn.getNextEntry();
// iterates over entries in the zip file
while (entry != null) {
String filePath = destDirectory + File.separator + entry.getName();
if (!entry.isDirectory()) {
// if the entry is a file, extracts it
extractFile(zipIn, filePath);
} else {
// if the entry is a directory, make the directory
File dir = new File(filePath);
dir.mkdir();
}
zipIn.closeEntry();
entry = zipIn.getNextEntry();
}
zipIn.close();
}
/**
* Extracts a zip entry (file entry)
* @param zipIn
* @param filePath
* @throws IOException
*/
private void extractFile(ZipInputStream zipIn, String filePath) throws IOException {
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
byte[] bytesIn = new byte[BUFFER_SIZE];
int read = 0;
while ((read = zipIn.read(bytesIn)) != -1) {
bos.write(bytesIn, 0, read);
}
bos.close();
}
public void saveToFile() throws IOException {
String s = new XhtmlComposer(true, true).compose(html);
TextFile.stringToFile(s, Utilities.path(folder, "index.html"));
}
/**
* Compares 2 capability statements to see if self is a subset of other
*
* e.g. is a system implementing "+otherName+" also an implementation of self?
*
* the output is a series of messages identifying ways in which it is not,
* or warning messages where the algorithm is unable to determine the
* relationship
*
* Note that there are aspects of this question that are not computable.
* the
*
* @param self
* @param other
* @return
* @throws IOException
* @throws FHIRFormatError
* @throws DefinitionException
*/
public CapabilityStatementComparisonOutput isCompatible(String selfName, String otherName, CapabilityStatement self, CapabilityStatement other) throws DefinitionException, FHIRFormatError, IOException {
this.selfName = selfName;
this.otherName = otherName;
this.output = new CapabilityStatementComparisonOutput();
XhtmlNode x = startHtml();
information(x, IssueType.INVARIANT, self.getUrl(), "Comparing "+selfName+" to "+otherName+", to see if a server that implements "+otherName+" also implements "+selfName+"");
information(x, IssueType.INVARIANT, self.getUrl(), " "+selfName+": "+self.getUrl()+"|"+self.getVersion());
information(x, IssueType.INVARIANT, self.getUrl(), " "+otherName+": "+other.getUrl()+"|"+other.getVersion());
if (self.getRest().size() != 1 || self.getRestFirstRep().getMode() != RestfulCapabilityMode.SERVER)
fatal(x, IssueType.INVARIANT, self.getUrl()+"#rest", "The CapabilityStatement Comparison tool can only compare CapabilityStatements with a single server component");
if (other.getRest().size() != 1 || other.getRestFirstRep().getMode() != RestfulCapabilityMode.SERVER)
fatal(x, IssueType.INVARIANT, other.getUrl()+"#rest", "The CapabilityStatement Comparison tool can only compare CapabilityStatements with a single server component");
if (self.getRest().size() == 1 && other.getRest().size() == 1) {
XhtmlNode tbl = startTable(x, self, other);
compareRest(tbl, self.getUrl(), self.getRest().get(0), other.getRest().get(0), output.subset.addRest().setMode(RestfulCapabilityMode.SERVER), output.superset.addRest().setMode(RestfulCapabilityMode.SERVER));
}
if (folder != null)
saveToFile();
output.outcome = OperationOutcomeUtilities.createOutcome(output.messages);
sortCapabilityStatement(output.subset);
sortCapabilityStatement(output.superset);
return output;
}
private void compareRest(XhtmlNode tbl, String path, CapabilityStatementRestComponent self, CapabilityStatementRestComponent other, CapabilityStatementRestComponent intersection, CapabilityStatementRestComponent union) throws DefinitionException, FHIRFormatError, IOException {
compareSecurity(tbl, path, self, other, intersection, union);
// check resources
List<CapabilityStatementRestResourceComponent> ol = new ArrayList<>();
List<CapabilityStatementRestResourceComponent> olr = new ArrayList<>();
ol.addAll(other.getResource());
for (CapabilityStatementRestResourceComponent r : self.getResource()) {
CapabilityStatementRestResourceComponent o = null;
for (CapabilityStatementRestResourceComponent t : ol) {
if (t.getType().equals(r.getType())) {
o = t;
break;
}
}
XhtmlNode tr = tbl.tr();
tr.style("background-color: #dddddd");
tr.td().b().addText(r.getType());
tr.td().tx("Present");
if (o == null) {
union.addResource(r);
XhtmlNode p = tr.td().para("Absent");
XhtmlNode td = tr.td();
String s = getConfStatus(r);
if (Utilities.existsInList(s, "SHALL", "SHOULD")) {
error(td, IssueType.NOTFOUND, path+".resource.where(type = '"+r.getType()+"')", selfName+" specifies the resource "+r.getType()+" as "+s+" but "+otherName+" does not cover it");
p.style("background-color: #ffe6e6; border: 1px solid #ff1a1a; margin-width: 10px");
}
tr = tbl.tr();
tr.td().para().tx(XhtmlNode.NBSP+" - Conformance");
genConf(tr.td().para(), r, null);
tr.td().style("background-color: #eeeeee");
tr.td();
tr = tbl.tr();
tr.td().para().tx(XhtmlNode.NBSP+" - Profile");
genProfile(tr.td().para(), r, null);
tr.td().style("background-color: #eeeeee");
tr.td();
tr = tbl.tr();
tr.td().para().tx(XhtmlNode.NBSP+" - Interactions");
genInt(tr.td(), r, null, false);
tr.td().style("background-color: #eeeeee");
tr.td();
tr = tbl.tr();
tr.td().para().tx(XhtmlNode.NBSP+" - Search Parameters");
genSP(tr.td(), r, null, false);
tr.td().style("background-color: #eeeeee");
tr.td();
} else {
olr.add(o);
tr.td().tx("Present");
tr.td().nbsp();
compareResource(path+".resource.where(type = '"+r.getType()+"')", r, o, tbl, intersection.addResource().setType(r.getType()), union.addResource().setType(r.getType()));
}
}
for (CapabilityStatementRestResourceComponent t : ol) {
XhtmlNode tr = tbl.tr();
if (!olr.contains(t)) {
union.addResource(t);
tr.td().addText(t.getType());
XhtmlNode td = tr.td();
td.style("background-color: #eeeeee").para("Absent");
td = tr.td();
genConf(td, t, null);
genProfile(td, t, null);
genInt(td, t, null, false);
genSP(td, t, null, false);
if (isProhibited(t)) {
error(td, IssueType.INVARIANT, path+".resource", selfName+" does not specify the resource "+t.getType()+" but "+otherName+" prohibits it");
}
}
}
// check interactions
// check search parameters
// check operation
// check compartments
}
private void genConf(XhtmlNode x, CapabilityStatementRestResourceComponent r, CapabilityStatementRestResourceComponent other) {
String s = getConfStatus(r);
String so = other != null ? getConfStatus(other) : null;
x.add(same(s == null ? "(not specified)" : s, (s == null && so == null) || (s != null && s.equals(so))));
}
private void genProfile(XhtmlNode x, CapabilityStatementRestResourceComponent r, CapabilityStatementRestResourceComponent other) {
String s = getProfile(r);
if (s == null)
s = "(not specified)";
String so = other == null ? null : getProfile(other) == null ? getProfile(other) : "(not specified)";
x.add(same(s, (s == null && so == null) || (s != null && s.equals(so))));
}
private String getProfile(CapabilityStatementRestResourceComponent r) {
if (r.hasSupportedProfile() && r.getSupportedProfile().size() == 1)
return r.getSupportedProfile().get(0).asStringValue();
return r.getProfile();
}
private void genInt(XhtmlNode td, CapabilityStatementRestResourceComponent r, CapabilityStatementRestResourceComponent other, boolean errorIfNoMatch) {
boolean first = true;
for (ResourceInteractionComponent i : r.getInteraction()) {
if (first) first = false; else td.tx(", ");
if (exists(other, i)) {
td.code().span("background-color: #bbff99; border: 1px solid #44cc00; margin-width: 10px", null).tx(i.getCode().toCode());
} else if (errorIfNoMatch){
td.code().span("background-color: #ffe6e6; border: 1px solid #ff1a1a; margin-width: 10px", null).tx(i.getCode().toCode());
} else {
td.code(i.getCode().toCode());
}
}
}
private boolean exists(CapabilityStatementRestResourceComponent other, ResourceInteractionComponent i) {
if (other == null)
return false;
for (ResourceInteractionComponent t : other.getInteraction())
if (t.getCode().equals(i.getCode()))
return true;
return false;
}
private void genSP(XhtmlNode td, CapabilityStatementRestResourceComponent r, CapabilityStatementRestResourceComponent other, boolean errorIfNoMatch) {
boolean first = true;
for (CapabilityStatementRestResourceSearchParamComponent i : r.getSearchParam()) {
if (first) first = false; else td.tx(", ");
if (exists(other, i)) {
td.code().span("background-color: #bbff99; border: 1px solid #44cc00; margin-width: 10px", null).tx(i.getName());
} else if (errorIfNoMatch){
td.code().span("background-color: #ffe6e6; border: 1px solid #ff1a1a; margin-width: 10px", null).tx(i.getName());
} else {
td.code(i.getName());
}
}
}
private boolean exists(CapabilityStatementRestResourceComponent other, CapabilityStatementRestResourceSearchParamComponent i) {
if (other == null)
return false;
for (CapabilityStatementRestResourceSearchParamComponent t : other.getSearchParam())
if (t.getName().equals(i.getName()))
return true;
return false;
}
public void compareSecurity(XhtmlNode tbl, String path, CapabilityStatementRestComponent self, CapabilityStatementRestComponent other, CapabilityStatementRestComponent intersection, CapabilityStatementRestComponent union) {
XhtmlNode tr = tbl.tr();
tr.td().b().addText("Security");
tr.td().para(gen(self.getSecurity()));
tr.td().para(gen(other.getSecurity()));
XhtmlNode td = tr.td();
if (self.hasSecurity() && !other.hasSecurity())
error(td, IssueType.CONFLICT, path+".security", selfName+" specifies some security requirements ("+gen(self.getSecurity())+") but "+otherName+" doesn't");
else if (!self.hasSecurity() && other.hasSecurity())
error(td, IssueType.CONFLICT, path+".security", selfName+" does not specify security requirements but "+otherName+" does ("+gen(self.getSecurity())+")");
else if (self.hasSecurity() && other.hasSecurity())
compareSecurity(td, path+".security", self.getSecurity(), other.getSecurity(), intersection.getSecurity(), union.getSecurity());
}
private void compareResource(String path, CapabilityStatementRestResourceComponent self, CapabilityStatementRestResourceComponent other, XhtmlNode tbl,
CapabilityStatementRestResourceComponent intersection, CapabilityStatementRestResourceComponent union) throws DefinitionException, FHIRFormatError, IOException {
XhtmlNode tr = tbl.tr();
tr.td().para().tx(XhtmlNode.NBSP+" - Conformance");
genConf(tr.td(), self, other);
genConf(tr.td(), other, self);
tr.td().nbsp();
tr = tbl.tr();
tr.td().para().tx(XhtmlNode.NBSP+" - Profile");
genProfile(tr.td(), self, other);
genProfile(tr.td(), other, self);
compareProfiles(tr.td(), path, getProfile(self), getProfile(other), self.getType(), intersection, union);
// compare the interactions
compareResourceInteractions(path, self, other, tbl, intersection, union);
compareResourceSearchParams(path, self, other, tbl, intersection, union);
// compare the search parameters
// compare the operations
// compare the profile?
}
private void compareProfiles(XhtmlNode td, String path, String urlL, String urlR, String type,
CapabilityStatementRestResourceComponent intersection, CapabilityStatementRestResourceComponent union) throws DefinitionException, FHIRFormatError, IOException {
if (urlL == null) {
urlL = "http://hl7.org/fhir/StructureDefinition/"+type;
}
if (urlR == null) {
urlR = "http://hl7.org/fhir/StructureDefinition/"+type;
}
StructureDefinition sdL = context.fetchResource(StructureDefinition.class, urlL);
StructureDefinition sdR = context.fetchResource(StructureDefinition.class, urlR);
if (sdL == null)
error(td, IssueType.NOTFOUND, path, "Unable to resolve "+urlL);
if (sdR == null)
error(td, IssueType.NOTFOUND, path, "Unable to resolve "+urlR);
if (sdL != null && sdR != null && sdL != sdR) {
// ok they are different...
if (sdR.getUrl().equals(sdL.getBaseDefinition())) {
information(td, null, path, "The profile specified by "+selfName+" is inherited from the profile specified by "+otherName);
intersection.setProfile(sdL.getUrl());
union.setProfile(sdR.getUrl());
} else if (sdL.getUrl().equals(sdR.getBaseDefinition())) {
information(td, null, path, "The profile specified by "+otherName+" is inherited from the profile specified by "+selfName);
intersection.setProfile(sdR.getUrl());
union.setProfile(sdL.getUrl());
} else if (folder != null) {
try {
ProfileComparer pc = new ProfileComparer(context, keygen, folder);
pc.setId("api-ep."+type);
pc.setTitle("Comparison - "+selfName+" vs "+otherName);
pc.setLeftName(selfName+": "+sdL.present());
pc.setLeftLink(sdL.getUserString("path"));
pc.setRightName(otherName+": "+sdR.present());
pc.setRightLink(sdR.getUserString("path"));
pc.compareProfiles(sdL, sdR);
System.out.println("Generate Comparison between "+pc.getLeftName()+" and "+pc.getRightName());
pc.generate();
for (ProfileComparison cmp : pc.getComparisons()) {
new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, cmp.getSubset().fhirType()+"-"+cmp.getSubset().getId()+".xml")), cmp.getSubset());
new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, cmp.getSubset().fhirType()+"-"+cmp.getSubset().getId()+".json")), cmp.getSubset());
new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, cmp.getSuperset().fhirType()+"-"+cmp.getSuperset().getId()+".xml")), cmp.getSuperset());
new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, cmp.getSuperset().fhirType()+"-"+cmp.getSuperset().getId()+".json")), cmp.getSuperset());
}
for (ValueSet vs : pc.getValuesets()) {
new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, vs.fhirType()+"-"+vs.getId()+".xml")), vs);
new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, vs.fhirType()+"-"+vs.getId()+".json")), vs);
}
intersection.setProfile(pc.getComparisons().get(0).getSubset().getUrl());
union.setProfile(pc.getComparisons().get(0).getSuperset().getUrl());
td.ah(pc.getId()+".html").tx("Comparison...");
td.tx(pc.getErrCount()+" "+Utilities.pluralize("error", pc.getErrCount()));
} catch (Exception e) {
e.printStackTrace();
error(td, IssueType.EXCEPTION, path, "Error comparing profiles: "+e.getMessage());
}
} else {
information(td, null, path, "Use the validator to compare the profiles");
}
}
}
private void compareResourceInteractions(String path, CapabilityStatementRestResourceComponent self, CapabilityStatementRestResourceComponent other, XhtmlNode tbl,
CapabilityStatementRestResourceComponent intersection, CapabilityStatementRestResourceComponent union) {
XhtmlNode tr = tbl.tr();
tr.td().para().tx(XhtmlNode.NBSP+" - Interactions");
genInt(tr.td(), self, other, true);
genInt(tr.td(), other, self, false);
XhtmlNode td = tr.td();
List<ResourceInteractionComponent> ol = new ArrayList<>();
List<ResourceInteractionComponent> olr = new ArrayList<>();
ol.addAll(other.getInteraction());
for (ResourceInteractionComponent r : self.getInteraction()) {
ResourceInteractionComponent o = null;
for (ResourceInteractionComponent t : ol) {
if (t.getCode().equals(r.getCode())) {
o = t;
break;
}
}
union.addInteraction(r);
if (o == null) {
error(td, IssueType.NOTFOUND, path+".interaction.where(code = '"+r.getCode()+"')", selfName+" specifies the interaction "+r.getCode()+" but "+otherName+" does not");
} else {
intersection.addInteraction(r);
olr.add(o);
}
}
for (ResourceInteractionComponent t : ol) {
union.addInteraction(t);
if (!olr.contains(t) && isProhibited(t))
error(td, IssueType.CONFLICT, path+".interaction", selfName+" does not specify the interaction "+t.getCode()+" but "+otherName+" prohibits it");
}
}
private void compareResourceSearchParams(String path, CapabilityStatementRestResourceComponent self, CapabilityStatementRestResourceComponent other, XhtmlNode tbl,
CapabilityStatementRestResourceComponent intersection, CapabilityStatementRestResourceComponent union) {
XhtmlNode tr = tbl.tr();
tr.td().para().tx(XhtmlNode.NBSP+" - Search Params");
genSP(tr.td(), self, other, true);
genSP(tr.td(), other, self, false);
XhtmlNode td = tr.td();
List<CapabilityStatementRestResourceSearchParamComponent> ol = new ArrayList<>();
List<CapabilityStatementRestResourceSearchParamComponent> olr = new ArrayList<>();
ol.addAll(other.getSearchParam());
for (CapabilityStatementRestResourceSearchParamComponent r : self.getSearchParam()) {
CapabilityStatementRestResourceSearchParamComponent o = null;
for (CapabilityStatementRestResourceSearchParamComponent t : ol) {
if (t.getName().equals(r.getName())) {
o = t;
break;
}
}
union.addSearchParam(r);
if (o == null) {
error(td, IssueType.NOTFOUND, path+".searchParam.where(name = '"+r.getName()+"')", selfName+" specifies the search parameter "+r.getName()+" but "+otherName+" does not");
} else {
intersection.addSearchParam(r);
olr.add(o);
}
}
for (CapabilityStatementRestResourceSearchParamComponent t : ol) {
if (!olr.contains(t))
union.addSearchParam(t);
if (!olr.contains(t) && isProhibited(t))
error(td, IssueType.CONFLICT, path+"", selfName+" does not specify the search parameter "+t.getName()+" but "+otherName+" prohibits it");
}
}
private String getConfStatus(Element t) {
return t.hasExtension("http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation") ?
t.getExtensionString("http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation") : null;
}
private boolean isShouldOrShall(Element t) {
return t.hasExtension("http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation") &&
("SHALL".equals(t.getExtensionString("http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation")) || "SHOULD".equals(t.getExtensionString("http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation")));
}
private boolean isProhibited(Element t) {
return t.hasExtension("http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation") && "SHALL NOT".equals(t.getExtensionString("http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation"));
}
private void compareSecurity(XhtmlNode td, String path, CapabilityStatementRestSecurityComponent self, CapabilityStatementRestSecurityComponent other,
CapabilityStatementRestSecurityComponent intersection, CapabilityStatementRestSecurityComponent union) {
if (self.getCors() && !other.getCors())
error(td, IssueType.CONFLICT, path+".security.cors", selfName+" specifies CORS but "+otherName+" doesn't");
else if (!self.getCors() && other.getCors())
error(td, IssueType.CONFLICT, path+".security.cors", selfName+" does not specify CORS but "+otherName+" does");
if (self.getCors() || other.getCors())
union.setCors(true);
if (self.getCors() && other.getCors())
union.setCors(true);
List<CodeableConcept> ol = new ArrayList<>();
List<CodeableConcept> olr = new ArrayList<>();
ol.addAll(other.getService());
for (CodeableConcept cc : self.getService()) {
CodeableConcept o = null;
for (CodeableConcept t : ol) {
if (isMatch(t, cc)) {
o = t;
break;
}
}
union.getService().add(cc);
if (o == null) {
error(td, IssueType.CONFLICT, path+".security.cors", selfName+" specifies the security option "+gen(cc)+" but "+otherName+" does not");
} else {
intersection.getService().add(cc);
olr.add(o);
}
}
for (CodeableConcept cc : ol) {
if (!olr.contains(cc)) {
union.getService().add(cc);
error(td, IssueType.CONFLICT, path+".security.cors", selfName+" does not specify the security option "+gen(cc)+" but "+otherName+" does");
}
}
}
private boolean isMatch(CodeableConcept self, CodeableConcept other) {
for (Coding s : self.getCoding())
for (Coding o : other.getCoding())
if (isMatch(s, o))
return true;
return false;
}
private boolean isMatch(Coding s, Coding o) {
return s.hasCode() && s.getCode().equals(o.getCode()) && s.hasSystem() && s.getSystem().equals(o.getSystem());
}
private String gen(CapabilityStatementRestSecurityComponent security) {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (CodeableConcept cc : security.getService())
b.append(gen(cc));
if (security.getCors())
b.append("(CORS)");
if (Utilities.noString(b.toString()))
return "(none specified)";
return b.toString();
}
private String gen(CodeableConcept cc) {
if (cc.hasText())
return cc.getText();
if (cc.hasCoding())
return gen(cc.getCoding().get(0));
return "?gen-cc?";
}
private String gen(Coding coding) {
if (coding.hasDisplay())
return coding.getDisplay();
if (coding.hasCode())
return coding.getCode();
return "?gen-c?";
}
private XhtmlNode startHtml() {
html = new XhtmlDocument();
XhtmlNode doc = html.addTag("html");
XhtmlNode head = doc.addTag("head");
head.addTag("title").addText("Comparison of "+selfName+" to "+otherName);
head.addTag("link").setAttribute("rel", "stylesheet").setAttribute("href", "fhir.css");
XhtmlNode body = doc.addTag("body").style("background-color: white");
body.h1().addText("Comparison of "+selfName+" to "+otherName);
return body;
}
private void addMarkdown(XhtmlNode x, String text) throws FHIRFormatError, IOException, DefinitionException {
if (text != null) {
// 1. custom FHIR extensions
while (text.contains("[[[")) {
String left = text.substring(0, text.indexOf("[[["));
String link = text.substring(text.indexOf("[[[")+3, text.indexOf("]]]"));
String right = text.substring(text.indexOf("]]]")+3);
String url = link;
String[] parts = link.split("\\#");
StructureDefinition p = context.fetchResource(StructureDefinition.class, parts[0]);
if (p == null)
p = context.fetchTypeDefinition(parts[0]);
if (p == null)
p = context.fetchResource(StructureDefinition.class, link);
if (p != null) {
url = p.getUserString("path");
if (url == null)
url = p.getUserString("filename");
} else
throw new DefinitionException("Unable to resolve markdown link "+link);
text = left+"["+link+"]("+url+")"+right;
}
// 2. markdown
String s = markdown.process(Utilities.escapeXml(text), "narrative generator");
XhtmlParser p = new XhtmlParser();
XhtmlNode m;
try {
m = p.parse("<div>"+s+"</div>", "div");
} catch (org.hl7.fhir.exceptions.FHIRFormatError e) {
throw new FHIRFormatError(e.getMessage(), e);
}
x.getChildNodes().addAll(m.getChildNodes());
}
}
private XhtmlNode startTable(XhtmlNode x, CapabilityStatement self, CapabilityStatement other) {
XhtmlNode tbl = x.table("grid");
XhtmlNode tr = tbl.tr();
tr.td().b().nbsp();
tr.td().b().addText(selfName);
tr.td().b().addText(otherName);
tr.td().b().addText("Comparison");
return tbl;
}
private void download(String address, String filename) throws IOException {
URL url = new URL(address);
URLConnection c = url.openConnection();
InputStream s = c.getInputStream();
FileOutputStream f = new FileOutputStream(filename);
transfer(s, f, 1024);
f.close();
}
public static void transfer(InputStream in, OutputStream out, int buffer) throws IOException {
byte[] read = new byte[buffer]; // Your buffer size.
while (0 < (buffer = in.read(read)))
out.write(read, 0, buffer);
}
private void fatal(XhtmlNode x, IssueType type, String path, String message) {
output.messages.add(new ValidationMessage(Source.ProfileComparer, type, path, message, IssueSeverity.FATAL));
XhtmlNode ul;
if ("ul".equals(x.getName())) {
ul = x;
} else {
ul = null;
for (XhtmlNode c : x.getChildNodes()) {
if ("ul".equals(c.getName())) {
ul = c;
}
}
if (ul == null) {
ul = x.ul();
}
}
ul.li().b().style("color: maroon").addText(message);
}
private void error(XhtmlNode x, IssueType type, String path, String message) {
output.messages.add(new ValidationMessage(Source.ProfileComparer, type, path, message, IssueSeverity.ERROR));
XhtmlNode ul;
if ("ul".equals(x.getName())) {
ul = x;
} else {
ul = null;
for (XhtmlNode c : x.getChildNodes()) {
if ("ul".equals(c.getName())) {
ul = c;
}
}
if (ul == null) {
ul = x.ul();
}
}
ul.li().b().style("color: maroon").addText(message);
}
private void information(XhtmlNode x, IssueType type, String path, String message) {
if (type != null)
output.messages.add(new ValidationMessage(Source.ProfileComparer, type, path, message, IssueSeverity.INFORMATION));
XhtmlNode ul;
if ("ul".equals(x.getName())) {
ul = x;
} else {
ul = null;
for (XhtmlNode c : x.getChildNodes()) {
if ("ul".equals(c.getName())) {
ul = c;
}
}
if (ul == null) {
ul = x.ul();
}
}
ul.li().addText(message);
}
private XhtmlNode same(String text, boolean test) {
XhtmlNode span = new XhtmlNode(NodeType.Element, "span");
if (test)
span.style("background-color: #bbff99; border: 1px solid #44cc00; margin-width: 10px");
span.tx(text);
return span;
}
private int compareStr(String l, String r) {
if (l == null) {
if (r == null)
return (r == null) ? 0 : -1;
}
if (r == null)
return 1;
return l.compareTo(r);
}
public class CapabilityStatementRestSorter implements Comparator<CapabilityStatementRestComponent> {
@Override
public int compare(CapabilityStatementRestComponent l, CapabilityStatementRestComponent r) {
return compareStr(l.hasMode() ? l.getMode().toCode() : null, r.hasMode() ? r.getMode().toCode() : null);
}
}
public class CapabilityStatementRestResourceSorter implements Comparator<CapabilityStatementRestResourceComponent> {
@Override
public int compare(CapabilityStatementRestResourceComponent l, CapabilityStatementRestResourceComponent r) {
return compareStr(l.getType(), r.getType());
}
}
public class CapabilityStatementRestResourceInteractionSorter implements Comparator<ResourceInteractionComponent> {
@Override
public int compare(ResourceInteractionComponent l, ResourceInteractionComponent r) {
return compareStr(l.hasCode() ? l.getCode().toCode() : null, r.hasCode() ? r.getCode().toCode() : null);
}
}
public class SystemInteractionComponentSorter implements Comparator<SystemInteractionComponent> {
@Override
public int compare(SystemInteractionComponent l, SystemInteractionComponent r) {
return compareStr(l.hasCode() ? l.getCode().toCode() : null, r.hasCode() ? r.getCode().toCode() : null);
}
}
public class SearchParamSorter implements Comparator<CapabilityStatementRestResourceSearchParamComponent> {
@Override
public int compare(CapabilityStatementRestResourceSearchParamComponent l, CapabilityStatementRestResourceSearchParamComponent r) {
return compareStr(l.getName(), r.getName());
}
}
public class OperationSorter implements Comparator<CapabilityStatementRestResourceOperationComponent> {
@Override
public int compare(CapabilityStatementRestResourceOperationComponent l, CapabilityStatementRestResourceOperationComponent r) {
return compareStr(l.getName(), r.getName());
}
}
public class CanonicalTypeSorter implements Comparator<CanonicalType> {
@Override
public int compare(CanonicalType l, CanonicalType r) {
return compareStr(l.getValue(), r.getValue());
}
}
public class StringTypeSorter implements Comparator<StringType> {
@Override
public int compare(StringType l, StringType r) {
return compareStr(l.getValue(), r.getValue());
}
}
public class EnumSorter implements Comparator<Enumeration> {
@Override
public int compare(Enumeration l, Enumeration r) {
return compareStr(l.getCode(), r.getCode());
}
}
public class ExtensionSorter implements Comparator<Extension> {
@Override
public int compare(Extension l, Extension r) {
return compareStr(l.getUrl(), r.getUrl());
}
}
public class CodingSorter implements Comparator<Coding> {
@Override
public int compare(Coding l, Coding r) {
return compareStr(""+l.getSystem()+"#"+l.getCode(), ""+r.getSystem()+"#"+r.getCode());
}
}
public class CodeableConceptSorter implements Comparator<CodeableConcept> {
@Override
public int compare(CodeableConcept l, CodeableConcept r) {
Collections.sort(l.getCoding(), new CodingSorter());
Collections.sort(r.getCoding(), new CodingSorter());
return compareStr(cgen(l), cgen(r));
}
private String cgen(CodeableConcept cc) {
if (cc.hasCoding())
return ""+cc.getCodingFirstRep().getSystem()+"#"+cc.getCodingFirstRep().getCode();
return "~"+cc.getText();
}
}
private void sortCapabilityStatement(CapabilityStatement cs) {
sortExtensions(cs);
Collections.sort(cs.getRest(), new CapabilityStatementRestSorter());
for (CapabilityStatementRestComponent csr : cs.getRest()) {
Collections.sort(csr.getSecurity().getService(), new CodeableConceptSorter());
Collections.sort(csr.getCompartment(), new CanonicalTypeSorter());
Collections.sort(csr.getResource(), new CapabilityStatementRestResourceSorter());
for (CapabilityStatementRestResourceComponent r : csr.getResource()) {
Collections.sort(r.getSupportedProfile(), new CanonicalTypeSorter());
Collections.sort(r.getInteraction(), new CapabilityStatementRestResourceInteractionSorter());
Collections.sort(r.getReferencePolicy(), new EnumSorter());
Collections.sort(r.getSearchInclude(), new StringTypeSorter());
Collections.sort(r.getSearchRevInclude(), new StringTypeSorter());
Collections.sort(r.getSearchParam(), new SearchParamSorter());
Collections.sort(r.getOperation(), new OperationSorter());
}
Collections.sort(csr.getInteraction(), new SystemInteractionComponentSorter());
Collections.sort(csr.getSearchParam(), new SearchParamSorter());
Collections.sort(csr.getOperation(), new OperationSorter());
}
}
private void sortExtensions(DomainResource dr) {
Collections.sort(dr.getExtension(), new ExtensionSorter());
for (Property p : dr.children()) {
for (Base b : p.getValues()) {
if (b instanceof Element)
sortExtensions((Element) b);
}
}
}
private void sortExtensions(Element e) {
Collections.sort(e.getExtension(), new ExtensionSorter());
for (Property p : e.children()) {
for (Base b : p.getValues()) {
if (b instanceof Element)
sortExtensions((Element) b);
}
}
}
}

View File

@ -1,75 +0,0 @@
package org.hl7.fhir.r5.utils;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.DomainResource;
import org.hl7.fhir.utilities.Utilities;
public class KeyGenerator {
private String canonical;
int i = 0;
public KeyGenerator(String canonical) {
super();
this.canonical = canonical;
}
public void genId(DomainResource dr) {
i++;
dr.setId(getAcronym(dr.fhirType())+Integer.toString(i));
if (dr instanceof CanonicalResource) {
CanonicalResource mr = (CanonicalResource) dr;
mr.setUrl(Utilities.pathURL(canonical, mr.fhirType(), mr.getId()));
}
}
private String getAcronym(String rt) {
if ("CapabilityStatement".equals(rt)) {
return "cs";
}
if ("ValueSet".equals(rt)) {
return "vs";
}
if ("CodeSystem".equals(rt)) {
return "cs";
}
if ("StructureDefinition".equals(rt)) {
return "sd";
}
if ("StructureMap".equals(rt)) {
return "sm";
}
if ("ConceptMap".equals(rt)) {
return "cm";
}
if ("ImplementationGuide".equals(rt)) {
return "";
}
if ("SearchParameter".equals(rt)) {
return "sp";
}
if ("MessageDefinition".equals(rt)) {
return "md";
}
if ("OperationDefinition".equals(rt)) {
return "od";
}
if ("CompartmentDefinition".equals(rt)) {
return "cd";
}
if ("GraphDefinition".equals(rt)) {
return "gd";
}
if ("ExampleScenario".equals(rt)) {
return "es";
}
if ("NamingSystem".equals(rt)) {
return "ns";
}
if ("TerminologyCapabilities".equals(rt)) {
return "tc";
}
return "r";
}
}

View File

@ -2,7 +2,6 @@ package org.hl7.fhir.validation.cli.services;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.conformance.CapabilityStatementUtilities;
import org.hl7.fhir.r5.conformance.ProfileComparer;
import org.hl7.fhir.r5.formats.IParser;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.formats.XmlParser;
@ -11,7 +10,6 @@ import org.hl7.fhir.r5.model.CapabilityStatement;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.utils.EOperationOutcome;
import org.hl7.fhir.r5.utils.KeyGenerator;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationMessage;
@ -54,26 +52,26 @@ public class ComparisonService {
String nameLeft = chooseName(args, "leftName", resLeft);
String nameRight = chooseName(args, "rightName", resRight);
System.out.println("Comparing CapabilityStatements " + left + " to " + right);
CapabilityStatementUtilities pc = new CapabilityStatementUtilities(validator.getContext(), dest, new KeyGenerator("http://fhir.org/temp/" + UUID.randomUUID().toString().toLowerCase()));
CapabilityStatement capL = (CapabilityStatement) resLeft;
CapabilityStatement capR = (CapabilityStatement) resRight;
CapabilityStatementUtilities.CapabilityStatementComparisonOutput output = pc.isCompatible(nameLeft, nameRight, capL, capR);
String destTxt = Utilities.path(dest, "output.txt");
System.out.println("Generating output to " + destTxt + "...");
StringBuilder b = new StringBuilder();
for (ValidationMessage msg : output.getMessages()) {
b.append(msg.summary());
b.append("\r\n");
}
TextFile.stringToFile(b.toString(), destTxt);
new XmlParser().setOutputStyle(IParser.OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(dest, "CapabilityStatement-union.xml")), output.getSuperset());
new XmlParser().setOutputStyle(IParser.OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(dest, "CapabilityStatement-intersection.xml")), output.getSubset());
new XmlParser().setOutputStyle(IParser.OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(dest, "OperationOutcome-issues.xml")), output.getOutcome());
new JsonParser().setOutputStyle(IParser.OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(dest, "CapabilityStatement-union.json")), output.getSuperset());
new JsonParser().setOutputStyle(IParser.OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(dest, "CapabilityStatement-intersection.json")), output.getSubset());
new JsonParser().setOutputStyle(IParser.OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(dest, "OperationOutcome-issues.json")), output.getOutcome());
// CapabilityStatementUtilities pc = new CapabilityStatementUtilities(validator.getContext(), dest, new KeyGenerator("http://fhir.org/temp/" + UUID.randomUUID().toString().toLowerCase()));
// CapabilityStatement capL = (CapabilityStatement) resLeft;
// CapabilityStatement capR = (CapabilityStatement) resRight;
// CapabilityStatementUtilities.CapabilityStatementComparisonOutput output = pc.isCompatible(nameLeft, nameRight, capL, capR);
//
// String destTxt = Utilities.path(dest, "output.txt");
// System.out.println("Generating output to " + destTxt + "...");
// StringBuilder b = new StringBuilder();
// for (ValidationMessage msg : output.getMessages()) {
// b.append(msg.summary());
// b.append("\r\n");
// }
// TextFile.stringToFile(b.toString(), destTxt);
// new XmlParser().setOutputStyle(IParser.OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(dest, "CapabilityStatement-union.xml")), output.getSuperset());
// new XmlParser().setOutputStyle(IParser.OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(dest, "CapabilityStatement-intersection.xml")), output.getSubset());
// new XmlParser().setOutputStyle(IParser.OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(dest, "OperationOutcome-issues.xml")), output.getOutcome());
// new JsonParser().setOutputStyle(IParser.OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(dest, "CapabilityStatement-union.json")), output.getSuperset());
// new JsonParser().setOutputStyle(IParser.OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(dest, "CapabilityStatement-intersection.json")), output.getSubset());
// new JsonParser().setOutputStyle(IParser.OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(dest, "OperationOutcome-issues.json")), output.getOutcome());
//
String destHtml = Utilities.path(dest, "public/index.html");
File htmlFile = new File(destHtml);
Desktop.getDesktop().browse(htmlFile.toURI());
@ -81,15 +79,15 @@ public class ComparisonService {
}
public static void compareStructureDefinitions(String dest, ValidationEngine validator, String left, String right, StructureDefinition resLeft, StructureDefinition resRight) throws IOException, FHIRException, EOperationOutcome {
System.out.println("Comparing StructureDefinitions " + left + " to " + right);
ProfileComparer pc = new ProfileComparer(validator.getContext(), dest);
StructureDefinition sdL = resLeft;
StructureDefinition sdR = resRight;
pc.compareProfiles(sdL, sdR);
System.out.println("Generating output to " + dest + "...");
File htmlFile = new File(pc.generate());
Desktop.getDesktop().browse(htmlFile.toURI());
System.out.println("Done");
// System.out.println("Comparing StructureDefinitions " + left + " to " + right);
// ProfileComparer pc = new ProfileComparer(validator.getContext(), dest);
// StructureDefinition sdL = resLeft;
// StructureDefinition sdR = resRight;
// pc.compareProfiles(sdL, sdR);
// System.out.println("Generating output to " + dest + "...");
// File htmlFile = new File(pc.generate());
// Desktop.getDesktop().browse(htmlFile.toURI());
// System.out.println("Done");
}
private static String chooseName(String[] args, String name, CanonicalResource mr) {