Remove old comparison code
This commit is contained in:
parent
17d9e61baf
commit
97e6330c58
|
@ -1,923 +1,6 @@
|
||||||
package org.hl7.fhir.r5.conformance;
|
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 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -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";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -2,7 +2,6 @@ package org.hl7.fhir.validation.cli.services;
|
||||||
|
|
||||||
import org.hl7.fhir.exceptions.FHIRException;
|
import org.hl7.fhir.exceptions.FHIRException;
|
||||||
import org.hl7.fhir.r5.conformance.CapabilityStatementUtilities;
|
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.IParser;
|
||||||
import org.hl7.fhir.r5.formats.JsonParser;
|
import org.hl7.fhir.r5.formats.JsonParser;
|
||||||
import org.hl7.fhir.r5.formats.XmlParser;
|
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.Resource;
|
||||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||||
import org.hl7.fhir.r5.utils.EOperationOutcome;
|
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.TextFile;
|
||||||
import org.hl7.fhir.utilities.Utilities;
|
import org.hl7.fhir.utilities.Utilities;
|
||||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||||
|
@ -54,26 +52,26 @@ public class ComparisonService {
|
||||||
String nameLeft = chooseName(args, "leftName", resLeft);
|
String nameLeft = chooseName(args, "leftName", resLeft);
|
||||||
String nameRight = chooseName(args, "rightName", resRight);
|
String nameRight = chooseName(args, "rightName", resRight);
|
||||||
System.out.println("Comparing CapabilityStatements " + left + " to " + right);
|
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()));
|
// CapabilityStatementUtilities pc = new CapabilityStatementUtilities(validator.getContext(), dest, new KeyGenerator("http://fhir.org/temp/" + UUID.randomUUID().toString().toLowerCase()));
|
||||||
CapabilityStatement capL = (CapabilityStatement) resLeft;
|
// CapabilityStatement capL = (CapabilityStatement) resLeft;
|
||||||
CapabilityStatement capR = (CapabilityStatement) resRight;
|
// CapabilityStatement capR = (CapabilityStatement) resRight;
|
||||||
CapabilityStatementUtilities.CapabilityStatementComparisonOutput output = pc.isCompatible(nameLeft, nameRight, capL, capR);
|
// CapabilityStatementUtilities.CapabilityStatementComparisonOutput output = pc.isCompatible(nameLeft, nameRight, capL, capR);
|
||||||
|
//
|
||||||
String destTxt = Utilities.path(dest, "output.txt");
|
// String destTxt = Utilities.path(dest, "output.txt");
|
||||||
System.out.println("Generating output to " + destTxt + "...");
|
// System.out.println("Generating output to " + destTxt + "...");
|
||||||
StringBuilder b = new StringBuilder();
|
// StringBuilder b = new StringBuilder();
|
||||||
for (ValidationMessage msg : output.getMessages()) {
|
// for (ValidationMessage msg : output.getMessages()) {
|
||||||
b.append(msg.summary());
|
// b.append(msg.summary());
|
||||||
b.append("\r\n");
|
// b.append("\r\n");
|
||||||
}
|
// }
|
||||||
TextFile.stringToFile(b.toString(), destTxt);
|
// 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-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, "CapabilityStatement-intersection.xml")), output.getSubset());
|
||||||
new XmlParser().setOutputStyle(IParser.OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(dest, "OperationOutcome-issues.xml")), output.getOutcome());
|
// 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-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, "CapabilityStatement-intersection.json")), output.getSubset());
|
||||||
new JsonParser().setOutputStyle(IParser.OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(dest, "OperationOutcome-issues.json")), output.getOutcome());
|
// 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");
|
String destHtml = Utilities.path(dest, "public/index.html");
|
||||||
File htmlFile = new File(destHtml);
|
File htmlFile = new File(destHtml);
|
||||||
Desktop.getDesktop().browse(htmlFile.toURI());
|
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 {
|
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);
|
// System.out.println("Comparing StructureDefinitions " + left + " to " + right);
|
||||||
ProfileComparer pc = new ProfileComparer(validator.getContext(), dest);
|
// ProfileComparer pc = new ProfileComparer(validator.getContext(), dest);
|
||||||
StructureDefinition sdL = resLeft;
|
// StructureDefinition sdL = resLeft;
|
||||||
StructureDefinition sdR = resRight;
|
// StructureDefinition sdR = resRight;
|
||||||
pc.compareProfiles(sdL, sdR);
|
// pc.compareProfiles(sdL, sdR);
|
||||||
System.out.println("Generating output to " + dest + "...");
|
// System.out.println("Generating output to " + dest + "...");
|
||||||
File htmlFile = new File(pc.generate());
|
// File htmlFile = new File(pc.generate());
|
||||||
Desktop.getDesktop().browse(htmlFile.toURI());
|
// Desktop.getDesktop().browse(htmlFile.toURI());
|
||||||
System.out.println("Done");
|
// System.out.println("Done");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String chooseName(String[] args, String name, CanonicalResource mr) {
|
private static String chooseName(String[] args, String name, CanonicalResource mr) {
|
||||||
|
|
Loading…
Reference in New Issue