001package org.hl7.fhir.dstu2016may.validation;
002
003import static org.apache.commons.lang3.StringUtils.isBlank;
004import static org.apache.commons.lang3.StringUtils.isNotBlank;
005
006import java.io.InputStream;
007import java.util.ArrayList;
008import java.util.HashMap;
009import java.util.List;
010import java.util.Map;
011
012import org.hl7.fhir.dstu2016may.formats.FormatUtilities;
013import org.hl7.fhir.dstu2016may.metamodel.Element;
014import org.hl7.fhir.dstu2016may.metamodel.Element.SpecialElement;
015import org.hl7.fhir.dstu2016may.metamodel.JsonParser;
016import org.hl7.fhir.dstu2016may.metamodel.Manager;
017import org.hl7.fhir.dstu2016may.metamodel.Manager.FhirFormat;
018import org.hl7.fhir.dstu2016may.metamodel.ParserBase;
019import org.hl7.fhir.dstu2016may.metamodel.ParserBase.ValidationPolicy;
020import org.hl7.fhir.dstu2016may.metamodel.XmlParser;
021import org.hl7.fhir.dstu2016may.model.Address;
022import org.hl7.fhir.dstu2016may.model.Attachment;
023import org.hl7.fhir.dstu2016may.model.Bundle;
024import org.hl7.fhir.dstu2016may.model.Bundle.BundleEntryComponent;
025import org.hl7.fhir.dstu2016may.model.CodeSystem;
026import org.hl7.fhir.dstu2016may.model.CodeSystem.ConceptDefinitionComponent;
027import org.hl7.fhir.dstu2016may.model.CodeableConcept;
028import org.hl7.fhir.dstu2016may.model.Coding;
029import org.hl7.fhir.dstu2016may.model.ContactPoint;
030import org.hl7.fhir.dstu2016may.model.DateType;
031import org.hl7.fhir.dstu2016may.model.DomainResource;
032import org.hl7.fhir.dstu2016may.model.ElementDefinition;
033import org.hl7.fhir.dstu2016may.model.ElementDefinition.ConstraintSeverity;
034import org.hl7.fhir.dstu2016may.model.ElementDefinition.ElementDefinitionBindingComponent;
035import org.hl7.fhir.dstu2016may.model.ElementDefinition.ElementDefinitionConstraintComponent;
036import org.hl7.fhir.dstu2016may.model.ElementDefinition.TypeRefComponent;
037import org.hl7.fhir.dstu2016may.model.Enumerations.BindingStrength;
038import org.hl7.fhir.dstu2016may.model.ExpressionNode;
039import org.hl7.fhir.dstu2016may.model.Extension;
040import org.hl7.fhir.dstu2016may.model.HumanName;
041import org.hl7.fhir.dstu2016may.model.Identifier;
042import org.hl7.fhir.dstu2016may.model.IntegerType;
043import org.hl7.fhir.dstu2016may.model.Period;
044import org.hl7.fhir.dstu2016may.model.Quantity;
045import org.hl7.fhir.dstu2016may.model.Questionnaire;
046import org.hl7.fhir.dstu2016may.model.Questionnaire.QuestionnaireItemComponent;
047import org.hl7.fhir.dstu2016may.model.Questionnaire.QuestionnaireItemOptionComponent;
048import org.hl7.fhir.dstu2016may.model.Questionnaire.QuestionnaireItemType;
049import org.hl7.fhir.dstu2016may.model.Range;
050import org.hl7.fhir.dstu2016may.model.Ratio;
051import org.hl7.fhir.dstu2016may.model.Reference;
052import org.hl7.fhir.dstu2016may.model.Resource;
053import org.hl7.fhir.dstu2016may.model.SampledData;
054import org.hl7.fhir.dstu2016may.model.StringType;
055import org.hl7.fhir.dstu2016may.model.StructureDefinition;
056import org.hl7.fhir.dstu2016may.model.StructureDefinition.ExtensionContext;
057import org.hl7.fhir.dstu2016may.model.StructureDefinition.StructureDefinitionKind;
058import org.hl7.fhir.dstu2016may.model.StructureDefinition.StructureDefinitionSnapshotComponent;
059import org.hl7.fhir.dstu2016may.model.StructureDefinition.TypeDerivationRule;
060import org.hl7.fhir.dstu2016may.model.TimeType;
061import org.hl7.fhir.dstu2016may.model.Timing;
062import org.hl7.fhir.dstu2016may.model.Type;
063import org.hl7.fhir.dstu2016may.model.UriType;
064import org.hl7.fhir.dstu2016may.model.ValueSet;
065import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionContainsComponent;
066import org.hl7.fhir.dstu2016may.utils.FHIRPathEngine;
067import org.hl7.fhir.dstu2016may.utils.IWorkerContext;
068import org.hl7.fhir.dstu2016may.utils.IWorkerContext.ValidationResult;
069import org.hl7.fhir.dstu2016may.utils.ProfileUtilities;
070import org.hl7.fhir.exceptions.DefinitionException;
071import org.hl7.fhir.exceptions.FHIRException;
072import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
073import org.hl7.fhir.utilities.Utilities;
074import org.hl7.fhir.utilities.xhtml.NodeType;
075import org.hl7.fhir.utilities.xhtml.XhtmlNode;
076import org.w3c.dom.Document;
077import org.w3c.dom.Node;
078
079import com.google.gson.JsonObject;
080
081import ca.uhn.fhir.util.ObjectUtil;
082import org.hl7.fhir.utilities.validation.ValidationMessage;
083import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
084import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
085import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
086
087
088/* 
089 * todo:
090 * check urn's don't start oid: or uuid: 
091 */
092public class InstanceValidator extends BaseValidator implements IResourceValidator {
093
094
095        private boolean anyExtensionsAllowed;
096
097        private BestPracticeWarningLevel bpWarnings;
098        // configuration items
099        private CheckDisplayOption checkDisplay;
100        private IWorkerContext context;
101        private FHIRPathEngine fpe; 
102
103        private List<String> extensionDomains = new ArrayList<String>();
104
105        private IdStatus resourceIdRule;
106
107        // used during the build process to keep the overall volume of messages down
108        private boolean suppressLoincSnomedMessages;
109
110        private Bundle logical;
111
112        // time tracking
113        private long overall = 0;
114        private long txTime = 0;
115        private long sdTime = 0;
116        private long loadTime = 0;
117        private long fpeTime = 0;
118
119
120        public InstanceValidator(IWorkerContext theContext) {
121                super();
122                this.context = theContext;
123                fpe = new FHIRPathEngine(context);
124                source = Source.InstanceValidator;
125        }
126
127        private boolean allowUnknownExtension(String url) {
128                if (url.contains("example.org") || url.contains("acme.com") || url.contains("nema.org"))
129                        return true;
130                for (String s : extensionDomains)
131                        if (url.startsWith(s))
132                                return true;
133                return anyExtensionsAllowed;
134        }
135
136        private void bpCheck(List<ValidationMessage> errors, IssueType invalid, int line, int col, String literalPath, boolean test, String message) {
137                if (bpWarnings != null) {
138                        switch (bpWarnings) {
139                        case Error:
140                                rule(errors, invalid, line, col, literalPath, test, message);
141                                break;
142                        case Warning:
143                                warning(errors, invalid, line, col, literalPath, test, message);
144                                break;
145                        case Hint:
146                                hint(errors, invalid, line, col, literalPath, test, message);
147                                break;
148                        default: // do nothing
149                        }
150                }
151        }
152
153
154        @Override
155        public void validate(List<ValidationMessage> errors, InputStream stream, FhirFormat format) throws Exception {
156                validate(errors, stream, format, (StructureDefinition) null);
157        }
158
159        @Override
160        public void validate(List<ValidationMessage> errors, InputStream stream, FhirFormat format, String profile) throws Exception {
161                long t = System.nanoTime();
162                StructureDefinition p = context.fetchResource(StructureDefinition.class, profile);
163                sdTime = sdTime + (System.nanoTime() - t);
164                if (p == null)
165                        throw new DefinitionException("StructureDefinition '" + profile + "' not found");
166                validate(errors, stream, format, p);
167        }
168
169        @Override
170        public void validate(List<ValidationMessage> errors, InputStream stream, FhirFormat format, StructureDefinition profile) throws Exception {
171                ParserBase parser = Manager.makeParser(context, format);
172                parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 
173                long t = System.nanoTime();
174                Element e = parser.parse(stream);
175                loadTime = System.nanoTime() - t;
176                if (e != null)
177                        validate(errors, e, profile);
178        }
179
180        @Override
181        public void validate(List<ValidationMessage> errors, Resource resource) throws Exception {
182                validate(errors, resource, (StructureDefinition) null);
183        }
184
185        @Override
186        public void validate(List<ValidationMessage> errors, Resource resource, String profile) throws Exception {
187                long t = System.nanoTime();
188                StructureDefinition p = context.fetchResource(StructureDefinition.class, profile);
189                sdTime = sdTime + (System.nanoTime() - t);
190                if (p == null)
191                        throw new DefinitionException("StructureDefinition '" + profile + "' not found");
192                validate(errors, resource, p);
193        }
194
195        @Override
196        public void validate(List<ValidationMessage> errors, Resource resource, StructureDefinition profile) throws Exception {
197                throw new Exception("Not done yet");
198                //    ParserBase parser = new ObjectParser(context);
199                //    parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 
200                //    long t = System.nanoTime();
201                //    Element e = parser.parse(resource);
202                //    loadTime = System.nanoTime() - t;
203                //    validate(errors, e, profile);    
204        }
205
206        @Override
207        public void validate(List<ValidationMessage> errors, Element element) throws Exception {
208                validate(errors, element, (StructureDefinition) null);
209        }
210
211        @Override
212        public void validate(List<ValidationMessage> errors, org.w3c.dom.Element element) throws Exception {
213                validate(errors, element, (StructureDefinition) null);
214        }
215
216        @Override
217        public void validate(List<ValidationMessage> errors, Element element, String profile) throws Exception {
218                long t = System.nanoTime();
219                StructureDefinition p = context.fetchResource(StructureDefinition.class, profile);
220                sdTime = sdTime + (System.nanoTime() - t);
221                if (p == null)
222                        throw new DefinitionException("StructureDefinition '" + profile + "' not found");
223                validate(errors, element, p);
224        }
225
226        @Override
227        public void validate(List<ValidationMessage> errors, org.w3c.dom.Element element, String profile) throws Exception {
228                long t = System.nanoTime();
229                StructureDefinition p = context.fetchResource(StructureDefinition.class, profile);
230                sdTime = sdTime + (System.nanoTime() - t);
231                if (p == null)
232                        throw new DefinitionException("StructureDefinition '" + profile + "' not found");
233                validate(errors, element, p);
234        }
235
236        @Override
237        public void validate(List<ValidationMessage> errors, org.w3c.dom.Element element, StructureDefinition profile) throws Exception {
238                XmlParser parser = new XmlParser(context);
239                parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 
240                long t = System.nanoTime();
241                Element e = parser.parse(element);
242                loadTime = System.nanoTime() - t;
243                validate(errors, e, profile);
244        }
245
246        @Override
247        public void validate(List<ValidationMessage> errors, Document document) throws Exception {
248                validate(errors, document, (StructureDefinition) null);
249        }
250
251        @Override
252        public void validate(List<ValidationMessage> errors, Document document, String profile) throws Exception {
253                long t = System.nanoTime();
254                StructureDefinition p = context.fetchResource(StructureDefinition.class, profile);
255                sdTime = sdTime + (System.nanoTime() - t);
256                if (p == null)
257                        throw new DefinitionException("StructureDefinition '" + profile + "' not found");
258                validate(errors, document, p);
259        }
260
261        @Override
262        public void validate(List<ValidationMessage> errors, Document document, StructureDefinition profile) throws Exception {
263                XmlParser parser = new XmlParser(context);
264                parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 
265                long t = System.nanoTime();
266                Element e = parser.parse(document);
267                loadTime = System.nanoTime() - t;
268                validate(errors, e, profile);
269        }
270
271        @Override
272        public void validate(List<ValidationMessage> errors, JsonObject object) throws Exception {
273                // validate(errors, object, (StructureDefinition) null);
274        }
275
276        @Override
277        public void validate(List<ValidationMessage> errors, JsonObject object, String profile) throws Exception {
278                long t = System.nanoTime();
279                StructureDefinition p = context.fetchResource(StructureDefinition.class, profile);
280                sdTime = sdTime + (System.nanoTime() - t);
281                if (p == null)
282                        throw new DefinitionException("StructureDefinition '" + profile + "' not found");
283                validate(errors, object, p);
284        }
285
286        @Override
287        public void validate(List<ValidationMessage> errors, JsonObject object, StructureDefinition profile) throws Exception {
288                JsonParser parser = new JsonParser(context);
289                parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 
290                long t = System.nanoTime();
291                Element e = parser.parse(object);
292                loadTime = System.nanoTime() - t;
293                validate(errors, e, profile);
294        }
295
296        @Override
297        public void validate(List<ValidationMessage> errors, Element element, StructureDefinition profile) throws Exception {
298                // this is the main entry point; all the other entry points end up here coming here...
299                long t = System.nanoTime();
300                validateResource(errors, element, element, profile, resourceIdRule, new NodeStack(element));
301                overall = System.nanoTime() - t;
302        }
303
304
305        private boolean check(String v1, String v2) {
306                return v1 == null ? Utilities.noString(v1) : v1.equals(v2);
307        }
308
309        private void checkAddress(List<ValidationMessage> errors, String path, Element focus, Address fixed) {
310                checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use");
311                checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text");
312                checkFixedValue(errors, path + ".city", focus.getNamedChild("city"), fixed.getCityElement(), "city");
313                checkFixedValue(errors, path + ".state", focus.getNamedChild("state"), fixed.getStateElement(), "state");
314                checkFixedValue(errors, path + ".country", focus.getNamedChild("country"), fixed.getCountryElement(), "country");
315                checkFixedValue(errors, path + ".zip", focus.getNamedChild("zip"), fixed.getPostalCodeElement(), "postalCode");
316
317                List<Element> lines = new ArrayList<Element>();
318                focus.getNamedChildren("line", lines);
319                if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, lines.size() == fixed.getLine().size(),
320                                "Expected " + Integer.toString(fixed.getLine().size()) + " but found " + Integer.toString(lines.size()) + " line elements")) {
321                        for (int i = 0; i < lines.size(); i++)
322                                checkFixedValue(errors, path + ".coding", lines.get(i), fixed.getLine().get(i), "coding");
323                }
324        }
325
326        private void checkAttachment(List<ValidationMessage> errors, String path, Element focus, Attachment fixed) {
327                checkFixedValue(errors, path + ".contentType", focus.getNamedChild("contentType"), fixed.getContentTypeElement(), "contentType");
328                checkFixedValue(errors, path + ".language", focus.getNamedChild("language"), fixed.getLanguageElement(), "language");
329                checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), "data");
330                checkFixedValue(errors, path + ".url", focus.getNamedChild("url"), fixed.getUrlElement(), "url");
331                checkFixedValue(errors, path + ".size", focus.getNamedChild("size"), fixed.getSizeElement(), "size");
332                checkFixedValue(errors, path + ".hash", focus.getNamedChild("hash"), fixed.getHashElement(), "hash");
333                checkFixedValue(errors, path + ".title", focus.getNamedChild("title"), fixed.getTitleElement(), "title");
334        }
335
336        // public API
337
338        private boolean checkCode(List<ValidationMessage> errors, Element element, String path, String code, String system, String display) {
339                long t = System.nanoTime();
340                boolean ss = context.supportsSystem(system);
341                txTime = txTime + (System.nanoTime() - t);
342                if (ss) {
343                        t = System.nanoTime();
344                        ValidationResult s = context.validateCode(system, code, display);
345                        txTime = txTime + (System.nanoTime() - t);
346                        if (s == null || s.isOk())
347                                return true;
348                        if (s.getSeverity() == IssueSeverity.INFORMATION)
349                                hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
350                        else if (s.getSeverity() == IssueSeverity.WARNING)
351                                warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
352                        else
353                                return rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
354                        return true;
355                } else if (system.startsWith("http://hl7.org/fhir")) {
356                        if (system.equals("http://hl7.org/fhir/sid/icd-10"))
357                                return true; // else don't check ICD-10 (for now)
358                        else {
359                                CodeSystem cs = getCodeSystem(system);
360                                if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, cs != null, "Unknown Code System " + system)) {
361                                        ConceptDefinitionComponent def = getCodeDefinition(cs, code);
362                                        if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, def != null, "Unknown Code (" + system + "#" + code + ")"))
363                                                return warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, display == null || display.equals(def.getDisplay()), "Display should be '" + def.getDisplay() + "'");
364                                }
365                                return false;
366                        }
367                } else if (system.startsWith("http://loinc.org")) {
368                        return true;
369                } else if (system.startsWith("http://unitsofmeasure.org")) {
370                        return true;
371                } else
372                        return true;
373        }
374
375        private void checkCodeableConcept(List<ValidationMessage> errors, String path, Element focus, CodeableConcept fixed) {
376                checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text");
377                List<Element> codings = new ArrayList<Element>();
378                focus.getNamedChildren("coding", codings);
379                if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, codings.size() == fixed.getCoding().size(),
380                                "Expected " + Integer.toString(fixed.getCoding().size()) + " but found " + Integer.toString(codings.size()) + " coding elements")) {
381                        for (int i = 0; i < codings.size(); i++)
382                                checkFixedValue(errors, path + ".coding", codings.get(i), fixed.getCoding().get(i), "coding");
383                }
384        }
385
386        private void checkCodeableConcept(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext)  {
387                if (theElementCntext != null && theElementCntext.hasBinding()) {
388                        ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
389                        if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing (cc)")) {
390                                if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) {
391                                        ValueSet valueset = resolveBindingReference(profile, binding.getValueSet());
392                                        if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found")) {
393                                                try {
394                                                        CodeableConcept cc = readAsCodeableConcept(element);
395                                                        if (!cc.hasCoding()) {
396                                                                if (binding.getStrength() == BindingStrength.REQUIRED)
397                                                                        rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "No code provided, and a code is required from the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl());
398                                                                else if (binding.getStrength() == BindingStrength.EXTENSIBLE)
399                                                                        warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "No code provided, and a code should be provided from the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl());
400                                                        } else {
401                                                                long t = System.nanoTime();
402                                                                ValidationResult vr = context.validateCode(cc, valueset);
403                                                                txTime = txTime + (System.nanoTime() - t);
404                                                                if (!vr.isOk()) {
405                                                                        if (binding.getStrength() == BindingStrength.REQUIRED)
406                                                                                rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl()+", and a code from this value set is required");
407                                                                        else if (binding.getStrength() == BindingStrength.EXTENSIBLE)
408                                                                                warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code should come from this value set unless it has no suitable code");
409                                                                        else if (binding.getStrength() == BindingStrength.PREFERRED)
410                                                                                hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,  "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is recommended to come from this value set");
411                                                                }
412                                                        }
413                                                } catch (Exception e) {
414                                                        warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating CodeableConcept");
415                                                }
416                                        }
417                                } else if (binding.hasValueSet()) {
418                                        hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding by URI reference cannot be checked");
419                                } else {
420                                        hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding for path " + path + " has no source, so can't be checked");
421                                }
422                        }
423                }
424        }
425
426        private CodeableConcept readAsCodeableConcept(Element element) {
427                CodeableConcept cc = new CodeableConcept();
428                List<Element> list = new ArrayList<Element>();
429                element.getNamedChildren("coding", list);
430                for (Element item : list)
431                        cc.addCoding(readAsCoding(item));
432                cc.setText(element.getNamedChildValue("text"));
433                return cc;
434        }
435
436        private Coding readAsCoding(Element item) {
437                Coding c = new Coding();
438                c.setSystem(item.getNamedChildValue("system"));
439                c.setVersion(item.getNamedChildValue("version"));
440                c.setCode(item.getNamedChildValue("code"));
441                c.setDisplay(item.getNamedChildValue("display"));
442                return c;
443        }
444
445        private void checkCoding(List<ValidationMessage> errors, String path, Element focus, Coding fixed) {
446                checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system");
447                checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), "code");
448                checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), "display");
449                checkFixedValue(errors, path + ".userSelected", focus.getNamedChild("userSelected"), fixed.getUserSelectedElement(), "userSelected");
450        }
451
452        private void checkCoding(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept)  {
453                String code = element.getNamedChildValue("code");
454                String system = element.getNamedChildValue("system");
455                String display = element.getNamedChildValue("display");
456                rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system), "Coding.system must be an absolute reference, not a local reference");
457
458                if (system != null && code != null) {
459                        if (checkCode(errors, element, path, code, system, display))
460                                if (theElementCntext != null && theElementCntext.getBinding() != null) {
461                                        ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
462                                        if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing")) {
463                                                if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) {
464                                                        ValueSet valueset = resolveBindingReference(profile, binding.getValueSet());
465                                                        if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found")) {
466                                                                try {
467                                                                        Coding c = readAsCoding(element);
468                                                                        long t = System.nanoTime();
469                                                                        ValidationResult vr = context.validateCode(c, valueset);
470                                                                        txTime = txTime + (System.nanoTime() - t);
471                                                                        if (!vr.isOk()) {
472                                                                                if (binding.getStrength() == BindingStrength.REQUIRED)
473                                                                                        rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided is not in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is required from this value set");
474                                                                                else if (binding.getStrength() == BindingStrength.EXTENSIBLE)
475                                                                                        warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided is not in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code should come from this value set unless it has no suitable code");
476                                                                                else if (binding.getStrength() == BindingStrength.PREFERRED)
477                                                                                        hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,  "The value provided is not in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is recommended to come from this value set");
478                                                                        }
479                                                                } catch (Exception e) {
480                                                                        warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating CodeableConcept");
481                                                                }
482                                                        }
483                                                } else if (binding.hasValueSet()) {
484                                                        hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding by URI reference cannot be checked");
485                                                } else if (!inCodeableConcept) {
486                                                        hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding for path " + path + " has no source, so can't be checked");
487                                                }
488                                        }
489                                }
490                }
491        }
492
493        private void checkContactPoint(List<ValidationMessage> errors, String path, Element focus, ContactPoint fixed) {
494                checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system");
495                checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value");
496                checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use");
497                checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), "period");
498
499        }
500
501        private void checkDeclaredProfiles(List<ValidationMessage> errors, Element resource, Element element, NodeStack stack) throws FHIRException {
502                Element meta = element.getNamedChild("meta");
503                if (meta != null) {
504                        List<Element> profiles = new ArrayList<Element>();
505                        meta.getNamedChildren("profile", profiles);
506                        int i = 0;
507                        for (Element profile : profiles) {
508                                String ref = profile.primitiveValue();
509                                String p = stack.addToLiteralPath("meta", "profile", ":" + Integer.toString(i));
510                                if (rule(errors, IssueType.INVALID, element.line(), element.col(), p, !Utilities.noString(ref), "StructureDefinition reference invalid")) {
511                                        long t = System.nanoTime();
512                                        StructureDefinition pr = context.fetchResource(StructureDefinition.class, ref);
513                                        sdTime = sdTime + (System.nanoTime() - t);
514                                        if (warning(errors, IssueType.INVALID, element.line(), element.col(), p, pr != null, "StructureDefinition reference \"{0}\" could not be resolved", ref)) {
515                                                if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), p, pr.hasSnapshot(),
516                                                                "StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided")) {
517                                                        validateElement(errors, pr, pr.getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false);
518                                                }
519                                        }
520                                        i++;
521                                }
522                        }
523                }
524        }
525
526        private StructureDefinition checkExtension(List<ValidationMessage> errors, String path, Element element, ElementDefinition def, StructureDefinition profile, NodeStack stack) {
527                String url = element.getNamedChildValue("url");
528                boolean isModifier = element.getName().equals("modifierExtension");
529
530                long t = System.nanoTime();
531                StructureDefinition ex = context.fetchResource(StructureDefinition.class, url);
532                sdTime = sdTime + (System.nanoTime() - t);
533                if (ex == null) {
534                        if (!rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowUnknownExtension(url), "The extension " + url + " is unknown, and not allowed here"))
535                                warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowUnknownExtension(url), "Unknown extension " + url);
536                } else {
537                        if (def.getIsModifier())
538                                rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(),
539                                                "Extension modifier mismatch: the extension element is labelled as a modifier, but the underlying extension is not");
540                        else
541                                rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(),
542                                                "Extension modifier mismatch: the extension element is not labelled as a modifier, but the underlying extension is");
543
544                        // two questions
545                        // 1. can this extension be used here?
546                        checkExtensionContext(errors, element, /* path+"[url='"+url+"']", */ ex, stack, ex.getUrl());
547
548                        if (isModifier)
549                                rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(),
550                                                "The Extension '" + url + "' must be used as a modifierExtension");
551                        else
552                                rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(),
553                                                "The Extension '" + url + "' must not be used as an extension (it's a modifierExtension)");
554
555                        // 2. is the content of the extension valid?
556
557                }
558                return ex;
559        }
560
561        private boolean checkExtensionContext(List<ValidationMessage> errors, Element element, StructureDefinition definition, NodeStack stack, String extensionParent) {
562                String extUrl = definition.getUrl();
563                CommaSeparatedStringBuilder p = new CommaSeparatedStringBuilder();
564                for (String lp : stack.getLogicalPaths())
565                        p.append(lp);
566                if (definition.getContextType() == ExtensionContext.DATATYPE) {
567                        boolean ok = false;
568                        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
569                        for (StringType ct : definition.getContext()) {
570                                b.append(ct.getValue());
571                                if (ct.getValue().equals("*") || stack.getLogicalPaths().contains(ct.getValue() + ".extension"))
572                                        ok = true;
573                        }
574                        return rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), ok,
575                                        "The extension " + extUrl + " is not allowed to be used on the logical path set [" + p.toString() + "] (allowed: datatype=" + b.toString() + ")");
576                } else if (definition.getContextType() == ExtensionContext.EXTENSION) {
577                        boolean ok = false;
578                        for (StringType ct : definition.getContext())
579                                if (ct.getValue().equals("*") || ct.getValue().equals(extensionParent))
580                                        ok = true;
581                        return rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), ok,
582                                        "The extension " + extUrl + " is not allowed to be used with the extension '" + extensionParent + "'");
583                } else if (definition.getContextType() == ExtensionContext.RESOURCE) {
584                        boolean ok = false;
585                        // String simplePath = container.getPath();
586                        // System.out.println(simplePath);
587                        // if (effetive.endsWith(".extension") || simplePath.endsWith(".modifierExtension"))
588                        // simplePath = simplePath.substring(0, simplePath.lastIndexOf('.'));
589                        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
590                        for (StringType ct : definition.getContext()) {
591                                String c = ct.getValue();
592                                b.append(c);
593                                if (c.equals("*") || stack.getLogicalPaths().contains(c + ".extension") || (c.startsWith("@") && stack.getLogicalPaths().contains(c.substring(1) + ".extension")))
594                                        ;
595                                ok = true;
596                        }
597                        return rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), ok,
598                                        "The extension " + extUrl + " is not allowed to be used on the logical path set " + p.toString() + " (allowed: resource=" + b.toString() + ")");
599                } else
600                        throw new Error("Unknown context type");
601        }
602        //
603        // private String simplifyPath(String path) {
604        // String s = path.replace("/f:", ".");
605        // while (s.contains("["))
606        // s = s.substring(0, s.indexOf("["))+s.substring(s.indexOf("]")+1);
607        // String[] parts = s.split("\\.");
608        // int i = 0;
609        // while (i < parts.length && !context.getProfiles().containsKey(parts[i].toLowerCase()))
610        // i++;
611        // if (i >= parts.length)
612        // throw new Error("Unable to process part "+path);
613        // int j = parts.length - 1;
614        // while (j > 0 && (parts[j].equals("extension") || parts[j].equals("modifierExtension")))
615        // j--;
616        // StringBuilder b = new StringBuilder();
617        // boolean first = true;
618        // for (int k = i; k <= j; k++) {
619        // if (k == j || !parts[k].equals(parts[k+1])) {
620        // if (first)
621        // first = false;
622        // else
623        // b.append(".");
624        // b.append(parts[k]);
625        // }
626        // }
627        // return b.toString();
628        // }
629        //
630
631        private void checkFixedValue(List<ValidationMessage> errors, String path, Element focus, org.hl7.fhir.dstu2016may.model.Element fixed, String propName) {
632                if (fixed == null && focus == null)
633                        ; // this is all good
634                else if (fixed == null && focus != null)
635                        rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, false, "Unexpected element " + focus.getName());
636                else if (fixed != null && focus == null)
637                        rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, false, "Mising element " + propName);
638                else {
639                        String value = focus.primitiveValue();
640                        if (fixed instanceof org.hl7.fhir.dstu2016may.model.BooleanType)
641                                rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.BooleanType) fixed).asStringValue(), value),
642                                                "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.BooleanType) fixed).asStringValue() + "'");
643                        else if (fixed instanceof org.hl7.fhir.dstu2016may.model.IntegerType)
644                                rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.IntegerType) fixed).asStringValue(), value),
645                                                "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.IntegerType) fixed).asStringValue() + "'");
646                        else if (fixed instanceof org.hl7.fhir.dstu2016may.model.DecimalType)
647                                rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.DecimalType) fixed).asStringValue(), value),
648                                                "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.DecimalType) fixed).asStringValue() + "'");
649                        else if (fixed instanceof org.hl7.fhir.dstu2016may.model.Base64BinaryType)
650                                rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.Base64BinaryType) fixed).asStringValue(), value),
651                                                "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.Base64BinaryType) fixed).asStringValue() + "'");
652                        else if (fixed instanceof org.hl7.fhir.dstu2016may.model.InstantType)
653                                rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.InstantType) fixed).getValue().toString(), value),
654                                                "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.InstantType) fixed).asStringValue() + "'");
655                        else if (fixed instanceof org.hl7.fhir.dstu2016may.model.StringType)
656                                rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.StringType) fixed).getValue(), value),
657                                                "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.StringType) fixed).getValue() + "'");
658                        else if (fixed instanceof org.hl7.fhir.dstu2016may.model.UriType)
659                                rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.UriType) fixed).getValue(), value),
660                                                "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.UriType) fixed).getValue() + "'");
661                        else if (fixed instanceof org.hl7.fhir.dstu2016may.model.DateType)
662                                rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.DateType) fixed).getValue().toString(), value),
663                                                "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.DateType) fixed).getValue() + "'");
664                        else if (fixed instanceof org.hl7.fhir.dstu2016may.model.DateTimeType)
665                                rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.DateTimeType) fixed).getValue().toString(), value),
666                                                "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.DateTimeType) fixed).getValue() + "'");
667                        else if (fixed instanceof org.hl7.fhir.dstu2016may.model.OidType)
668                                rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.OidType) fixed).getValue(), value),
669                                                "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.OidType) fixed).getValue() + "'");
670                        else if (fixed instanceof org.hl7.fhir.dstu2016may.model.UuidType)
671                                rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.UuidType) fixed).getValue(), value),
672                                                "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.UuidType) fixed).getValue() + "'");
673                        else if (fixed instanceof org.hl7.fhir.dstu2016may.model.CodeType)
674                                rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.CodeType) fixed).getValue(), value),
675                                                "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.CodeType) fixed).getValue() + "'");
676                        else if (fixed instanceof org.hl7.fhir.dstu2016may.model.IdType)
677                                rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.IdType) fixed).getValue(), value),
678                                                "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.IdType) fixed).getValue() + "'");
679                        else if (fixed instanceof Quantity)
680                                checkQuantity(errors, path, focus, (Quantity) fixed);
681                        else if (fixed instanceof Address)
682                                checkAddress(errors, path, focus, (Address) fixed);
683                        else if (fixed instanceof ContactPoint)
684                                checkContactPoint(errors, path, focus, (ContactPoint) fixed);
685                        else if (fixed instanceof Attachment)
686                                checkAttachment(errors, path, focus, (Attachment) fixed);
687                        else if (fixed instanceof Identifier)
688                                checkIdentifier(errors, path, focus, (Identifier) fixed);
689                        else if (fixed instanceof Coding)
690                                checkCoding(errors, path, focus, (Coding) fixed);
691                        else if (fixed instanceof HumanName)
692                                checkHumanName(errors, path, focus, (HumanName) fixed);
693                        else if (fixed instanceof CodeableConcept)
694                                checkCodeableConcept(errors, path, focus, (CodeableConcept) fixed);
695                        else if (fixed instanceof Timing)
696                                checkTiming(errors, path, focus, (Timing) fixed);
697                        else if (fixed instanceof Period)
698                                checkPeriod(errors, path, focus, (Period) fixed);
699                        else if (fixed instanceof Range)
700                                checkRange(errors, path, focus, (Range) fixed);
701                        else if (fixed instanceof Ratio)
702                                checkRatio(errors, path, focus, (Ratio) fixed);
703                        else if (fixed instanceof SampledData)
704                                checkSampledData(errors, path, focus, (SampledData) fixed);
705
706                        else
707                                rule(errors, IssueType.EXCEPTION, focus.line(), focus.col(), path, false, "Unhandled fixed value type " + fixed.getClass().getName());
708                        List<Element> extensions = new ArrayList<Element>();
709                        focus.getNamedChildren("extension", extensions);
710                        if (fixed.getExtension().size() == 0) {
711                                rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == 0, "No extensions allowed");
712                        } else if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == fixed.getExtension().size(),
713                                        "Extensions count mismatch: expected " + Integer.toString(fixed.getExtension().size()) + " but found " + Integer.toString(extensions.size()))) {
714                                for (Extension e : fixed.getExtension()) {
715                                        Element ex = getExtensionByUrl(extensions, e.getUrl());
716                                        if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, ex != null, "Extension count mismatch: unable to find extension: " + e.getUrl())) {
717                                                checkFixedValue(errors, path, ex.getNamedChild("extension").getNamedChild("value"), e.getValue(), "extension.value");
718                                        }
719                                }
720                        }
721                }
722        }
723
724        private void checkHumanName(List<ValidationMessage> errors, String path, Element focus, HumanName fixed) {
725                checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use");
726                checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text");
727                checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), "period");
728
729                List<Element> parts = new ArrayList<Element>();
730                focus.getNamedChildren("family", parts);
731                if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getFamily().size(),
732                                "Expected " + Integer.toString(fixed.getFamily().size()) + " but found " + Integer.toString(parts.size()) + " family elements")) {
733                        for (int i = 0; i < parts.size(); i++)
734                                checkFixedValue(errors, path + ".family", parts.get(i), fixed.getFamily().get(i), "family");
735                }
736                focus.getNamedChildren("given", parts);
737                if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getGiven().size(),
738                                "Expected " + Integer.toString(fixed.getGiven().size()) + " but found " + Integer.toString(parts.size()) + " given elements")) {
739                        for (int i = 0; i < parts.size(); i++)
740                                checkFixedValue(errors, path + ".given", parts.get(i), fixed.getGiven().get(i), "given");
741                }
742                focus.getNamedChildren("prefix", parts);
743                if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getPrefix().size(),
744                                "Expected " + Integer.toString(fixed.getPrefix().size()) + " but found " + Integer.toString(parts.size()) + " prefix elements")) {
745                        for (int i = 0; i < parts.size(); i++)
746                                checkFixedValue(errors, path + ".prefix", parts.get(i), fixed.getPrefix().get(i), "prefix");
747                }
748                focus.getNamedChildren("suffix", parts);
749                if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getSuffix().size(),
750                                "Expected " + Integer.toString(fixed.getSuffix().size()) + " but found " + Integer.toString(parts.size()) + " suffix elements")) {
751                        for (int i = 0; i < parts.size(); i++)
752                                checkFixedValue(errors, path + ".suffix", parts.get(i), fixed.getSuffix().get(i), "suffix");
753                }
754        }
755
756        private void checkIdentifier(List<ValidationMessage> errors, String path, Element element, ElementDefinition context) {
757                String system = element.getNamedChildValue("system");
758                rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system), "Identifier.system must be an absolute reference, not a local reference");
759        }
760
761        private void checkIdentifier(List<ValidationMessage> errors, String path, Element focus, Identifier fixed) {
762                checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use");
763                checkFixedValue(errors, path + ".type", focus.getNamedChild("type"), fixed.getType(), "type");
764                checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system");
765                checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value");
766                checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), "period");
767                checkFixedValue(errors, path + ".assigner", focus.getNamedChild("assigner"), fixed.getAssigner(), "assigner");
768        }
769
770        private void checkPeriod(List<ValidationMessage> errors, String path, Element focus, Period fixed) {
771                checkFixedValue(errors, path + ".start", focus.getNamedChild("start"), fixed.getStartElement(), "start");
772                checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), "end");
773        }
774
775        private void checkPrimitive(List<ValidationMessage> errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile) {
776                if (type.equals("boolean")) {
777                        rule(errors, IssueType.INVALID, e.line(), e.col(), path, "true".equals(e.primitiveValue()) || "false".equals(e.primitiveValue()), "boolean values must be 'true' or 'false'");
778                }
779                if (type.equals("uri")) {
780                        rule(errors, IssueType.INVALID, e.line(), e.col(), path, !e.primitiveValue().startsWith("oid:"), "URI values cannot start with oid:");
781                        rule(errors, IssueType.INVALID, e.line(), e.col(), path, !e.primitiveValue().startsWith("uuid:"), "URI values cannot start with uuid:");
782                        rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().equals(e.primitiveValue().trim()), "URI values cannot have leading or trailing whitespace");
783                }
784                if (!type.equalsIgnoreCase("string") && e.hasPrimitiveValue()) {
785                        if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().length() > 0, "@value cannot be empty")) {
786                                warning(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().trim().equals(e.primitiveValue()), "value should not start or finish with whitespace");
787                        }
788                }
789                if (type.equals("dateTime")) {
790                        rule(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), "The value '" + e.primitiveValue() + "' does not have a valid year");
791                        rule(errors, IssueType.INVALID, e.line(), e.col(), path,
792                                        e.primitiveValue()
793                                        .matches("-?[0-9]{4}(-(0[1-9]|1[0-2])(-(0[0-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"),
794                                        "Not a valid date time");
795                        rule(errors, IssueType.INVALID, e.line(), e.col(), path, !hasTime(e.primitiveValue()) || hasTimeZone(e.primitiveValue()), "if a date has a time, it must have a timezone");
796
797                }
798                if (type.equals("instant")) {
799                        rule(errors, IssueType.INVALID, e.line(), e.col(), path,
800                                        e.primitiveValue().matches("-?[0-9]{4}-(0[1-9]|1[0-2])-(0[0-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))"),
801                                        "The instant '" + e.primitiveValue() + "' is not valid (by regex)");
802                        rule(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), "The value '" + e.primitiveValue() + "' does not have a valid year");
803                }
804
805                if (type.equals("code") && e.primitiveValue() != null) {
806                        // Technically, a code is restricted to string which has at least one character and no leading or trailing whitespace, and where there is no whitespace
807                        // other than single spaces in the contents
808                        rule(errors, IssueType.INVALID, e.line(), e.col(), path, passesCodeWhitespaceRules(e.primitiveValue()), "The code '" + e.primitiveValue() + "' is not valid (whitespace rules)");
809                }
810
811                if (context.hasBinding() && e.primitiveValue() != null) {
812                        checkPrimitiveBinding(errors, path, type, context, e, profile);
813                }
814
815                if (type.equals("xhtml")) {
816                        XhtmlNode xhtml = e.getXhtml();
817                        if (xhtml != null) { // if it is null, this is an error already noted in the parsers
818                                // check that the namespace is there and correct.
819                                String ns = xhtml.getNsDecl();
820                                rule(errors, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.XHTML_NS.equals(ns), "Wrong namespace on the XHTML ('"+ns+"')");
821                                // check that inner namespaces are all correct 
822                                checkInnerNS(errors, e, path, xhtml.getChildNodes());
823                                rule(errors, IssueType.INVALID, e.line(), e.col(), path, "div".equals(xhtml.getName()), "Wrong name on the XHTML ('"+ns+"') - must start with div");
824                                // check that no illegal elements and attributes have been used
825                                checkInnerNames(errors, e, path, xhtml.getChildNodes());
826                        } 
827                }
828                // for nothing to check
829        }
830
831        private void checkInnerNames(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) {
832                for (XhtmlNode node : list) {
833                        if (node.getNodeType() == NodeType.Element) {
834                                rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(node.getName(), 
835                                                "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong",
836                                                "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup",
837                                                "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td",
838                                                "code", "samp", "img", "map", "area"
839
840                                                ), "Illegal element name in the XHTML ('"+node.getName()+"')");
841                                for (String an : node.getAttributes().keySet()) {
842                                        boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an, 
843                                                        "title", "style", "class", "id", "lang", "xml:lang", "dir", "accesskey", "tabindex",
844                                                        // tables
845                                                        "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") ||
846
847                                                        Utilities.existsInList(node.getName()+"."+an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite",
848                                                                        "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src",
849                                                                        "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape",
850                                                                        "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border",
851                                                                        "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space"
852                                                                        );
853                                        if (!ok)
854                                                rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, "Illegal attribute name in the XHTML ('"+an+"' on '"+node.getName()+"')");
855                                }
856                                checkInnerNames(errors, e, path, node.getChildNodes());
857                        }
858                }       
859        }
860
861        private void checkInnerNS(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) {
862                for (XhtmlNode node : list) {
863                        if (node.getNodeType() == NodeType.Element) {
864                                String ns = node.getNsDecl();
865                                rule(errors, IssueType.INVALID, e.line(), e.col(), path, ns == null || FormatUtilities.XHTML_NS.equals(ns), "Wrong namespace on the XHTML ('"+ns+"')");
866                                checkInnerNS(errors, e, path, node.getChildNodes());
867                        }
868                }       
869        }
870
871        // note that we don't check the type here; it could be string, uri or code.
872        private void checkPrimitiveBinding(List<ValidationMessage> errors, String path, String type, ElementDefinition elementContext, Element element, StructureDefinition profile) {
873                if (!element.hasPrimitiveValue())
874                        return;
875
876                String value = element.primitiveValue();
877                // System.out.println("check "+value+" in "+path);
878
879                // firstly, resolve the value set
880                ElementDefinitionBindingComponent binding = elementContext.getBinding();
881                if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) {
882                        ValueSet vs = resolveBindingReference(profile, binding.getValueSet());
883                        if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, "ValueSet {0} not found", describeReference(binding.getValueSet()))) {
884                                long t = System.nanoTime();
885                                ValidationResult vr = context.validateCode(null, value, null, vs);
886                                txTime = txTime + (System.nanoTime() - t);
887                                if (vr != null && !vr.isOk()) {
888                                        if (binding.getStrength() == BindingStrength.REQUIRED)
889                                                rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided ('"+value+"') is not in the value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ", and a code is required from this value set");
890                                        else if (binding.getStrength() == BindingStrength.EXTENSIBLE)
891                                                warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided ('"+value+"') is not in the value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ", and a code should come from this value set unless it has no suitable code");
892                                        else if (binding.getStrength() == BindingStrength.PREFERRED)
893                                                hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,  "The value provided ('"+value+"') is not in the value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ", and a code is recommended to come from this value set");
894                                }
895                        }
896                } else
897                        hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !type.equals("code"), "Binding has no source, so can't be checked");
898        }
899
900        private void checkQuantity(List<ValidationMessage> errors, String path, Element focus, Quantity fixed) {
901                checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value");
902                checkFixedValue(errors, path + ".comparator", focus.getNamedChild("comparator"), fixed.getComparatorElement(), "comparator");
903                checkFixedValue(errors, path + ".units", focus.getNamedChild("unit"), fixed.getUnitElement(), "units");
904                checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system");
905                checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), "code");
906        }
907
908        // implementation
909
910        private void checkRange(List<ValidationMessage> errors, String path, Element focus, Range fixed) {
911                checkFixedValue(errors, path + ".low", focus.getNamedChild("low"), fixed.getLow(), "low");
912                checkFixedValue(errors, path + ".high", focus.getNamedChild("high"), fixed.getHigh(), "high");
913
914        }
915
916        private void checkRatio(List<ValidationMessage> errors, String path, Element focus, Ratio fixed) {
917                checkFixedValue(errors, path + ".numerator", focus.getNamedChild("numerator"), fixed.getNumerator(), "numerator");
918                checkFixedValue(errors, path + ".denominator", focus.getNamedChild("denominator"), fixed.getDenominator(), "denominator");
919        }
920
921        private void checkReference(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition container, String parentType, NodeStack stack) {
922                String ref = element.getNamedChildValue("reference");
923                if (Utilities.noString(ref)) {
924                        // todo - what should we do in this case?
925                        warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, !Utilities.noString(element.getNamedChildValue("display")), "A Reference without an actual reference should have a display");
926                        return;
927                }
928
929                Element we = resolve(ref, stack);
930                String ft;
931                if (we != null)
932                        ft = we.getType();
933                else
934                        ft = tryParse(ref);
935                if (hint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ft != null, "Unable to determine type of target resource")) {
936                        boolean ok = false;
937                        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
938                        for (TypeRefComponent type : container.getType()) {
939                                if (!ok && type.getCode().equals("Reference")) {
940                                        // we validate as much as we can. First, can we infer a type from the profile?
941                                        if (!type.hasProfile() || type.getProfile().get(0).getValue().equals("http://hl7.org/fhir/StructureDefinition/Resource"))
942                                                ok = true;
943                                        else {
944                                                String pr = type.getProfile().get(0).getValue();
945
946                                                String bt = getBaseType(profile, pr);
947                                                if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, bt != null, "Unable to resolve the profile reference '" + pr + "'")) {
948                                                        b.append(bt);
949                                                        ok = bt.equals(ft);
950                                                } else
951                                                        ok = true; // suppress following check
952                                        }
953                                }
954                                if (!ok && type.getCode().equals("*")) {
955                                        ok = true; // can refer to anything
956                                }
957                        }
958                        rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok, "Invalid Resource target type. Found " + ft + ", but expected one of (" + b.toString() + ")");
959                }
960        }
961
962        private String checkResourceType(String type)  {
963                long t = System.nanoTime();
964                try {
965                        if (context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + type) != null)
966                                return type;
967                        else
968                                return null;
969                } finally {
970                        sdTime = sdTime + (System.nanoTime() - t);
971                }
972        }
973
974        private void checkSampledData(List<ValidationMessage> errors, String path, Element focus, SampledData fixed) {
975                checkFixedValue(errors, path + ".origin", focus.getNamedChild("origin"), fixed.getOrigin(), "origin");
976                checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriodElement(), "period");
977                checkFixedValue(errors, path + ".factor", focus.getNamedChild("factor"), fixed.getFactorElement(), "factor");
978                checkFixedValue(errors, path + ".lowerLimit", focus.getNamedChild("lowerLimit"), fixed.getLowerLimitElement(), "lowerLimit");
979                checkFixedValue(errors, path + ".upperLimit", focus.getNamedChild("upperLimit"), fixed.getUpperLimitElement(), "upperLimit");
980                checkFixedValue(errors, path + ".dimensions", focus.getNamedChild("dimensions"), fixed.getDimensionsElement(), "dimensions");
981                checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), "data");
982        }
983
984        private void checkTiming(List<ValidationMessage> errors, String path, Element focus, Timing fixed) {
985                checkFixedValue(errors, path + ".repeat", focus.getNamedChild("repeat"), fixed.getRepeat(), "value");
986
987                List<Element> events = new ArrayList<Element>();
988                focus.getNamedChildren("event", events);
989                if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, events.size() == fixed.getEvent().size(),
990                                "Expected " + Integer.toString(fixed.getEvent().size()) + " but found " + Integer.toString(events.size()) + " event elements")) {
991                        for (int i = 0; i < events.size(); i++)
992                                checkFixedValue(errors, path + ".event", events.get(i), fixed.getEvent().get(i), "event");
993                }
994        }
995
996        private boolean codeinExpansion(ValueSetExpansionContainsComponent cnt, String system, String code) {
997                for (ValueSetExpansionContainsComponent c : cnt.getContains()) {
998                        if (code.equals(c.getCode()) && system.equals(c.getSystem().toString()))
999                                return true;
1000                        if (codeinExpansion(c, system, code))
1001                                return true;
1002                }    
1003                return false;
1004        }
1005
1006        private boolean codeInExpansion(ValueSet vs, String system, String code) {
1007                for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) {
1008                        if (code.equals(c.getCode()) && (system == null || system.equals(c.getSystem())))
1009                                return true;
1010                        if (codeinExpansion(c, system, code))
1011                                return true;
1012                }
1013                return false;
1014        }
1015
1016        private String describeReference(Type reference) {
1017                if (reference == null)
1018                        return "null";
1019                if (reference instanceof UriType)
1020                        return ((UriType) reference).getValue();
1021                if (reference instanceof Reference)
1022                        return ((Reference) reference).getReference();
1023                return "??";
1024        }
1025
1026        private String describeTypes(List<TypeRefComponent> types) {
1027                CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1028                for (TypeRefComponent t : types) {
1029                        b.append(t.getCode());
1030                }
1031                return b.toString();
1032        }
1033
1034        private ElementDefinition findElement(StructureDefinition profile, String name) {
1035                for (ElementDefinition c : profile.getSnapshot().getElement()) {
1036                        if (c.getPath().equals(name)) {
1037                                return c;
1038                        }
1039                }
1040                return null;
1041        }
1042
1043        private String genFullUrl(String bundleBase, String entryBase, String type, String id) {
1044                String base = Utilities.noString(entryBase) ? bundleBase : entryBase;
1045                if (Utilities.noString(base)) {
1046                        return type + "/" + id;
1047                } else if ("urn:uuid".equals(base) || "urn:oid".equals(base))
1048                        return base + id;
1049                else
1050                        return Utilities.appendSlash(base) + type + "/" + id;
1051        }  
1052
1053        public BestPracticeWarningLevel getBasePracticeWarningLevel() {
1054                return bpWarnings;
1055        }  
1056
1057        private String getBaseType(StructureDefinition profile, String pr)  {
1058                // if (pr.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
1059                // // this just has to be a base type
1060                // return pr.substring(40);
1061                // } else {
1062                StructureDefinition p = resolveProfile(profile, pr);
1063                if (p == null)
1064                        return null;
1065                else if (p.getKind() == StructureDefinitionKind.RESOURCE)
1066                        return p.getSnapshot().getElement().get(0).getPath();
1067                else
1068                        return p.getSnapshot().getElement().get(0).getType().get(0).getCode();
1069                // }
1070        }
1071
1072        @Override
1073        public CheckDisplayOption getCheckDisplay() {
1074                return checkDisplay;
1075        }
1076
1077        //      private String findProfileTag(Element element) {
1078        //      String uri = null;
1079        //        List<Element> list = new ArrayList<Element>();
1080        //        element.getNamedChildren("category", list);
1081        //        for (Element c : list) {
1082        //              if ("http://hl7.org/fhir/tag/profile".equals(c.getAttribute("scheme"))) {
1083        //                      uri = c.getAttribute("term");
1084        //              }
1085        //        }
1086        //        return uri;
1087        //  }
1088
1089        private ConceptDefinitionComponent getCodeDefinition(ConceptDefinitionComponent c, String code) {
1090                if (code.equals(c.getCode()))
1091                        return c;
1092                for (ConceptDefinitionComponent g : c.getConcept()) {
1093                        ConceptDefinitionComponent r = getCodeDefinition(g, code);
1094                        if (r != null)
1095                                return r;
1096                }
1097                return null;
1098        }
1099
1100        private ConceptDefinitionComponent getCodeDefinition(CodeSystem cs, String code) {
1101                for (ConceptDefinitionComponent c : cs.getConcept()) {
1102                        ConceptDefinitionComponent r = getCodeDefinition(c, code);
1103                        if (r != null)
1104                                return r;
1105                }
1106                return null;
1107        }
1108
1109        private Element getContainedById(Element container, String id) {
1110                List<Element> contained = new ArrayList<Element>();
1111                container.getNamedChildren("contained", contained);
1112                for (Element we : contained) {
1113                        if (id.equals(we.getNamedChildValue("id")))
1114                                return we;
1115                }   
1116                return null;
1117        }
1118
1119        public IWorkerContext getContext() {
1120                return context;
1121        }
1122
1123        private ElementDefinition getCriteriaForDiscriminator(String path, ElementDefinition ed, String discriminator, StructureDefinition profile) throws DefinitionException, org.hl7.fhir.exceptions.DefinitionException {
1124                List<ElementDefinition> childDefinitions = ProfileUtilities.getChildMap(profile, ed);
1125                List<ElementDefinition> snapshot = null;
1126                int index;
1127                if (childDefinitions.isEmpty()) {
1128                        // going to look at the type
1129                        if (ed.getType().size() == 0)
1130                                throw new DefinitionException("Error in profile for " + path + " no children, no type");
1131                        if (ed.getType().size() > 1)
1132                                throw new DefinitionException("Error in profile for " + path + " multiple types defined in slice discriminator");
1133                        StructureDefinition type;
1134                        if (ed.getType().get(0).hasProfile()) {
1135                                // need to do some special processing for reference here...
1136                                if (ed.getType().get(0).getCode().equals("Reference"))
1137                                        discriminator = discriminator.substring(discriminator.indexOf(".")+1);
1138                                long t = System.nanoTime();
1139                                type = context.fetchResource(StructureDefinition.class, ed.getType().get(0).getProfile().get(0).getValue());
1140                                sdTime = sdTime + (System.nanoTime() - t);
1141                        } else {
1142                                long t = System.nanoTime();
1143                                type = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + ed.getType().get(0).getCode());
1144                                sdTime = sdTime + (System.nanoTime() - t);
1145                        }
1146                        snapshot = type.getSnapshot().getElement();
1147                        ed = snapshot.get(0);
1148                        index = 0;
1149                } else {
1150                        snapshot = childDefinitions;
1151                        index = -1;
1152                }
1153                String originalPath = ed.getPath();
1154                String goal = originalPath + "." + discriminator;
1155
1156                index++;
1157                while (index < snapshot.size() && !snapshot.get(index).getPath().equals(originalPath)) {
1158                        if (snapshot.get(index).getPath().equals(goal))
1159                                return snapshot.get(index);
1160                        index++;
1161                }
1162                throw new Error("Unable to find discriminator definition for " + goal + " in " + discriminator + " at " + path);
1163        }
1164
1165        private Element getExtensionByUrl(List<Element> extensions, String urlSimple) {
1166                for (Element e : extensions) {
1167                        if (urlSimple.equals(e.getNamedChildValue("url")))
1168                                return e;
1169                }
1170                return null;
1171        }
1172
1173        public List<String> getExtensionDomains() {
1174                return extensionDomains;
1175        }
1176
1177        private Element getFromBundle(Element bundle, String ref, String fullUrl) {
1178                List<Element> entries = new ArrayList<Element>();
1179                bundle.getNamedChildren("entry", entries);
1180                for (Element we : entries) {
1181                        Element res = we.getNamedChild("resource");
1182                        if (res != null) {
1183                                String url = genFullUrl(bundle.getNamedChildValue("base"), we.getNamedChildValue("base"), res.getName(), res.getNamedChildValue("id"));
1184                                if (url.endsWith(ref))
1185                                        return res;
1186                        }
1187                }
1188                return null;
1189        }  
1190
1191        private StructureDefinition getProfileForType(String type) {
1192                if (logical != null)
1193                        for (BundleEntryComponent be : logical.getEntry()) {
1194                                if (be.hasResource() && be.getResource() instanceof StructureDefinition) {
1195                                        StructureDefinition sd = (StructureDefinition) be.getResource();
1196                                        if (sd.getId().equals(type))
1197                                                return sd;
1198                                }
1199                        }
1200
1201                long t = System.nanoTime();
1202                try {
1203                        return context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + type);
1204                } finally {
1205                        sdTime = sdTime + (System.nanoTime() - t);
1206                }
1207        }
1208
1209        private Element getValueForDiscriminator(Element element, String discriminator, ElementDefinition criteria) {
1210                // throw new Error("validation of slices not done yet");
1211                return null;
1212        }
1213
1214        private CodeSystem getCodeSystem(String system) {
1215                long t = System.nanoTime();
1216                try {
1217                        return context.fetchCodeSystem(system);
1218                } finally {
1219                        txTime = txTime + (System.nanoTime() - t);
1220                }
1221        }
1222
1223        private boolean hasTime(String fmt) {
1224                return fmt.contains("T");
1225        }
1226
1227        private boolean hasTimeZone(String fmt) {
1228                return fmt.length() > 10 && (fmt.substring(10).contains("-") || fmt.substring(10).contains("+") || fmt.substring(10).contains("Z"));
1229        }
1230
1231        private boolean isAbsolute(String uri) {
1232                return Utilities.noString(uri) || uri.startsWith("http:") || uri.startsWith("https:") || uri.startsWith("urn:uuid:") || uri.startsWith("urn:oid:") || uri.startsWith("urn:ietf:")
1233                                || uri.startsWith("urn:iso:") || isValidFHIRUrn(uri);
1234        }
1235
1236        private boolean isValidFHIRUrn(String uri) {
1237                return (uri.equals("urn:x-fhir:uk:id:nhs-number"));
1238        }
1239
1240        public boolean isAnyExtensionsAllowed() {
1241                return anyExtensionsAllowed;
1242        }
1243
1244        private boolean isParametersEntry(String path) {
1245                String[] parts = path.split("\\.");
1246                return parts.length > 2 && parts[parts.length - 1].equals("resource") && (parts[parts.length - 2].startsWith("parameter[") || parts[parts.length - 2].startsWith("part["));
1247        }
1248
1249        private boolean isBundleEntry(String path) {
1250                String[] parts = path.split("\\.");
1251                return parts.length > 2 && parts[parts.length - 1].equals("resource") && parts[parts.length - 2].startsWith("entry[");
1252        }
1253
1254        private boolean isPrimitiveType(String type) {
1255                return type.equalsIgnoreCase("boolean") || type.equalsIgnoreCase("integer") || type.equalsIgnoreCase("string") || type.equalsIgnoreCase("decimal") || type.equalsIgnoreCase("uri")
1256                                || type.equalsIgnoreCase("base64Binary") || type.equalsIgnoreCase("instant") || type.equalsIgnoreCase("date") || type.equalsIgnoreCase("uuid") || type.equalsIgnoreCase("id")
1257                                || type.equalsIgnoreCase("xhtml") || type.equalsIgnoreCase("markdown") || type.equalsIgnoreCase("dateTime") || type.equalsIgnoreCase("time") || type.equalsIgnoreCase("code")
1258                                || type.equalsIgnoreCase("oid") || type.equalsIgnoreCase("id");
1259        }
1260
1261
1262
1263        public boolean isSuppressLoincSnomedMessages() {
1264                return suppressLoincSnomedMessages;
1265        }
1266
1267        private boolean nameMatches(String name, String tail) {
1268                if (tail.endsWith("[x]"))
1269                        return name.startsWith(tail.substring(0, tail.length() - 3));
1270                else
1271                        return (name.equals(tail));
1272        }
1273
1274        // private String mergePath(String path1, String path2) {
1275        // // path1 is xpath path
1276        // // path2 is dotted path
1277        // String[] parts = path2.split("\\.");
1278        // StringBuilder b = new StringBuilder(path1);
1279        // for (int i = 1; i < parts.length -1; i++)
1280        // b.append("/f:"+parts[i]);
1281        // return b.toString();
1282        // }
1283
1284        private boolean passesCodeWhitespaceRules(String v) {
1285                if (!v.trim().equals(v))
1286                        return false;
1287                boolean lastWasSpace = true;
1288                for (char c : v.toCharArray()) {
1289                        if (c == ' ') {
1290                                if (lastWasSpace)
1291                                        return false;
1292                                else
1293                                        lastWasSpace = true;
1294                        } else if (Character.isWhitespace(c))
1295                                return false;
1296                        else
1297                                lastWasSpace = false;
1298                }
1299                return true;
1300        }
1301
1302        private Element resolve(String ref, NodeStack stack) {
1303                if (ref.startsWith("#")) {
1304                        // work back through the contained list.
1305                        // really, there should only be one level for this (contained resources cannot contain
1306                        // contained resources), but we'll leave that to some other code to worry about
1307                        while (stack != null && stack.getElement() != null) {
1308                                if (stack.getElement().getProperty().isResource()) {
1309                                        // ok, we'll try to find the contained reference
1310                                        Element res = getContainedById(stack.getElement(), ref.substring(1));
1311                                        if (res != null)
1312                                                return res;
1313                                }
1314                                if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY) {
1315                                        return null; // we don't try to resolve contained references across this boundary
1316                                }
1317                                stack = stack.parent;
1318                        }
1319                        return null;
1320                } else {
1321                        // work back through the contained list - if any of them are bundles, try to resolve
1322                        // the resource in the bundle
1323                        String fullUrl = null; // we're going to try to work this out as we go up
1324                        while (stack != null && stack.getElement() != null) {
1325                                if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY) {
1326                                        fullUrl = "test"; // we don't try to resolve contained references across this boundary
1327                                }
1328                                if ("Bundle".equals(stack.getElement().getType())) {
1329                                        Element res = getFromBundle(stack.getElement(), ref, fullUrl);
1330                                        return res;
1331                                }
1332                                stack = stack.parent;
1333                        }
1334
1335                        // todo: consult the external host for resolution
1336                        return null;
1337
1338                }
1339        }
1340
1341        private ValueSet resolveBindingReference(DomainResource ctxt, Type reference) {
1342                if (reference instanceof UriType) {
1343                        long t = System.nanoTime();
1344                        ValueSet fr = context.fetchResource(ValueSet.class, ((UriType) reference).getValue().toString());
1345                        txTime = txTime + (System.nanoTime() - t);
1346                        return fr;
1347                }
1348                else if (reference instanceof Reference) {
1349                        String s = ((Reference) reference).getReference();
1350                        if (s.startsWith("#")) {
1351                                for (Resource c : ctxt.getContained()) {
1352                                        if (c.getId().equals(s.substring(1)) && (c instanceof ValueSet))
1353                                                return (ValueSet) c;
1354                                }
1355                                return null;
1356                        } else {
1357                                long t = System.nanoTime();
1358                                ValueSet fr = context.fetchResource(ValueSet.class, ((Reference) reference).getReference());
1359                                txTime = txTime + (System.nanoTime() - t);
1360                                return fr;
1361                        }
1362                }
1363                else
1364                        return null;
1365        }
1366
1367        private Element resolveInBundle(List<Element> entries, String ref, String fullUrl, String type, String id) {
1368                if (Utilities.isAbsoluteUrl(ref)) {
1369                        // if the reference is absolute, then you resolve by fullUrl. No other thinking is required. 
1370                        for (Element entry : entries) {
1371                                String fu = entry.getNamedChildValue("fullUrl");
1372                                if (ref.equals(fu))
1373                                        return entry;
1374                        }
1375                        return null;
1376                } else {
1377                        // split into base, type, and id
1378                        String u = null;
1379                        if (fullUrl != null && fullUrl.endsWith(type+"/"+id))
1380                                // fullUrl = complex
1381                                u = fullUrl.substring((type+"/"+id).length())+ref;
1382                        String[] parts = ref.split("\\/");
1383                        if (parts.length >= 2) {
1384                                String t = parts[0];
1385                                String i = parts[1];
1386                                for (Element entry : entries) {
1387                                        String fu = entry.getNamedChildValue("fullUrl");
1388                                        if (u != null && fullUrl.equals(u))
1389                                                return entry;
1390                                        if (u == null) {
1391                                                Element resource = entry.getNamedChild("resource");
1392                                                String et = resource.getType();
1393                                                String eid = resource.getNamedChildValue("id");
1394                                                if (t.equals(et) && i.equals(eid))
1395                                                        return entry;
1396                                        }
1397                                }
1398                        }
1399                        return null;
1400                }
1401        }
1402
1403        private ElementDefinition resolveNameReference(StructureDefinitionSnapshotComponent snapshot, String contentReference) {
1404                for (ElementDefinition ed : snapshot.getElement())
1405                        if (contentReference.equals("#"+ed.getId()))
1406                                return ed;
1407                return null;
1408        }
1409
1410        private StructureDefinition resolveProfile(StructureDefinition profile, String pr)  {
1411                if (pr.startsWith("#")) {
1412                        for (Resource r : profile.getContained()) {
1413                                if (r.getId().equals(pr.substring(1)) && r instanceof StructureDefinition)
1414                                        return (StructureDefinition) r;
1415                        }
1416                        return null;
1417                } else {
1418                        long t = System.nanoTime();
1419                        StructureDefinition fr = context.fetchResource(StructureDefinition.class, pr);
1420                        sdTime = sdTime + (System.nanoTime() - t);
1421                        return fr;
1422                }
1423        }
1424
1425        private ElementDefinition resolveType(String type)  {
1426                if (logical != null)
1427                        for (BundleEntryComponent be : logical.getEntry()) {
1428                                if (be.hasResource() && be.getResource() instanceof StructureDefinition) {
1429                                        StructureDefinition sd = (StructureDefinition) be.getResource();
1430                                        if (sd.getId().equals(type))
1431                                                return sd.getSnapshot().getElement().get(0);
1432                                }
1433                        }
1434                String url = "http://hl7.org/fhir/StructureDefinition/" + type;
1435                long t = System.nanoTime();
1436                StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
1437                sdTime = sdTime + (System.nanoTime() - t);
1438                if (sd == null || !sd.hasSnapshot())
1439                        return null;
1440                else
1441                        return sd.getSnapshot().getElement().get(0);
1442        }
1443
1444        public void setAnyExtensionsAllowed(boolean anyExtensionsAllowed) {
1445                this.anyExtensionsAllowed = anyExtensionsAllowed;
1446        }
1447
1448        public void setBestPracticeWarningLevel(BestPracticeWarningLevel value) {
1449                bpWarnings = value;
1450        }
1451
1452        @Override
1453        public void setCheckDisplay(CheckDisplayOption checkDisplay) {
1454                this.checkDisplay = checkDisplay;
1455        }
1456
1457        public void setSuppressLoincSnomedMessages(boolean suppressLoincSnomedMessages) {
1458                this.suppressLoincSnomedMessages = suppressLoincSnomedMessages;
1459        }
1460
1461        public IdStatus getResourceIdRule() {
1462                return resourceIdRule;
1463        }
1464
1465        public void setResourceIdRule(IdStatus resourceIdRule) {
1466                this.resourceIdRule = resourceIdRule;
1467        }
1468
1469        /**
1470         * 
1471         * @param element
1472         *          - the candidate that might be in the slice
1473         * @param path
1474         *          - for reporting any errors. the XPath for the element
1475         * @param slice
1476         *          - the definition of how slicing is determined
1477         * @param ed
1478         *          - the slice for which to test membership
1479         * @return
1480         * @throws DefinitionException 
1481         * @throws DefinitionException 
1482         * @throws Exception
1483         */
1484        private boolean sliceMatches(Element element, String path, ElementDefinition slice, ElementDefinition ed, StructureDefinition profile) throws DefinitionException, DefinitionException {
1485                if (!slice.getSlicing().hasDiscriminator())
1486                        return false; // cannot validate in this case
1487                for (StringType s : slice.getSlicing().getDiscriminator()) {
1488                        String discriminator = s.getValue();
1489                        ElementDefinition criteria = getCriteriaForDiscriminator(path, ed, discriminator, profile);
1490                        if (discriminator.equals("url") && criteria.getPath().equals("Extension.url")) {
1491                                if (!element.getNamedChildValue("url").equals(((UriType) criteria.getFixed()).asStringValue()))
1492                                        return false;
1493                        } else {
1494                                Element value = getValueForDiscriminator(element, discriminator, criteria);
1495                                if (!valueMatchesCriteria(value, criteria))
1496                                        return false;
1497                        }
1498                }
1499                return true;
1500        }
1501
1502        // we assume that the following things are true:
1503        // the instance at root is valid against the schema and schematron
1504        // the instance validator had no issues against the base resource profile
1505        private void start(List<ValidationMessage> errors, Element resource, Element element, StructureDefinition profile, NodeStack stack) throws FHIRException, FHIRException {
1506                // profile is valid, and matches the resource name
1507                if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), profile.hasSnapshot(),
1508                                "StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided")) {
1509                        validateElement(errors, profile, profile.getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false);
1510
1511                        checkDeclaredProfiles(errors, resource, element, stack);
1512
1513                        // specific known special validations
1514                        if (element.getType().equals("Bundle"))
1515                                validateBundle(errors, element, stack);
1516                        if (element.getType().equals("Observation"))
1517                                validateObservation(errors, element, stack);
1518                        if (element.getType().equals("QuestionnaireResponse"))
1519                                validateQuestionannaireResponse(errors, element, stack);
1520                }
1521        }
1522
1523        private void validateQuestionannaireResponse(List<ValidationMessage> errors, Element element, NodeStack stack) {
1524                Element q = element.getNamedChild("questionnaire");
1525                if (hint(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), q != null, "No questionnaire is identified, so no validation can be performed against the base questionnaire")) {
1526                        long t = System.nanoTime();
1527                        Questionnaire qsrc = context.fetchResource(Questionnaire.class, q.getNamedChildValue("reference"));
1528                        sdTime = sdTime + (System.nanoTime() - t);
1529                        if (warning(errors, IssueType.REQUIRED, q.line(), q.col(), stack.getLiteralPath(), qsrc != null, "The questionnaire could not be resolved, so no validation can be performed against the base questionnaire")) {
1530                                boolean inProgress = "in-progress".equals(element.getNamedChildValue("status"));
1531                                validateQuestionannaireResponseItems(qsrc, qsrc.getItem(), errors, element, stack, inProgress);        
1532                        }
1533                }
1534        }
1535
1536        private void validateQuestionannaireResponseItem(Questionnaire qsrc, QuestionnaireItemComponent qItem, List<ValidationMessage> errors, Element element, NodeStack stack, boolean inProgress) {
1537                String text = element.getNamedChildValue("text");
1538                rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), Utilities.noString(text) || text.equals(qItem.getText()), "If text exists, it must match the questionnaire definition for linkId "+qItem.getLinkId());
1539
1540                List<Element> answers = new ArrayList<Element>();
1541                element.getNamedChildren("answer", answers);
1542                if (inProgress)
1543                        warning(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), (answers.size() > 0) || !qItem.getRequired(), "No response answer found for required item "+qItem.getLinkId());
1544                else
1545                        rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), (answers.size() > 0) || !qItem.getRequired(), "No response answer found for required item "+qItem.getLinkId());
1546                if (answers.size() > 1)
1547                        rule(errors, IssueType.INVALID, answers.get(1).line(), answers.get(1).col(), stack.getLiteralPath(), qItem.getRepeats(), "Only one response answer item with this linkId allowed");
1548
1549                for (Element answer : answers) {
1550                        NodeStack ns = stack.push(answer, -1, null, null);
1551                        switch (qItem.getType()) {
1552                        case GROUP: 
1553                                rule(errors, IssueType.STRUCTURE, answer.line(), answer.col(), stack.getLiteralPath(), false, "Items of type group should not have answers"); 
1554                                break;
1555                        case DISPLAY:  // nothing
1556                                break;
1557                        case BOOLEAN:       
1558                                validateQuestionnaireResponseItemType(errors, answer, ns, "boolean");
1559                                break;
1560                        case DECIMAL:       
1561                                validateQuestionnaireResponseItemType(errors, answer, ns, "decimal");
1562                                break;
1563                        case INTEGER:       
1564                                validateQuestionnaireResponseItemType(errors, answer, ns, "integer");
1565                                break;
1566                        case DATE:          
1567                                validateQuestionnaireResponseItemType(errors, answer, ns, "date");
1568                                break;
1569                        case DATETIME:      
1570                                validateQuestionnaireResponseItemType(errors, answer, ns, "dateTime");
1571                                break;
1572                        case INSTANT:       
1573                                validateQuestionnaireResponseItemType(errors, answer, ns, "instant");
1574                                break;
1575                        case TIME:          
1576                                validateQuestionnaireResponseItemType(errors, answer, ns, "time");
1577                                break;
1578                        case STRING:        
1579                                validateQuestionnaireResponseItemType(errors, answer, ns, "string");
1580                                break;
1581                        case TEXT:          
1582                                validateQuestionnaireResponseItemType(errors, answer, ns, "text");
1583                                break;
1584                        case URL:           
1585                                validateQuestionnaireResponseItemType(errors, answer, ns, "uri");
1586                                break;
1587                        case ATTACHMENT:    
1588                                validateQuestionnaireResponseItemType(errors, answer, ns, "Attachment");
1589                                break;
1590                        case REFERENCE:     
1591                                validateQuestionnaireResponseItemType(errors, answer, ns, "Reference");
1592                                break;
1593                        case QUANTITY:   
1594                                if (validateQuestionnaireResponseItemType(errors, answer, ns, "Quantity").equals("Quantity"))
1595                                        if (qItem.hasExtension("???"))
1596                                                validateQuestionnaireResponseItemQuantity(errors, answer, ns);
1597                                break;
1598                        case CHOICE:     
1599                                String itemType=validateQuestionnaireResponseItemType(errors, answer, ns, "Coding", "date", "time", "integer", "string");
1600                                if (itemType.equals("Coding")) validateAnswerCode(errors, answer, ns, qsrc, qItem, false);
1601                                else if (itemType.equals("date")) checkOption(errors, answer, ns, qsrc, qItem, "date");
1602                                else if (itemType.equals("time")) checkOption(errors, answer, ns, qsrc, qItem, "time");
1603                                else if (itemType.equals("integer")) checkOption(errors, answer, ns, qsrc, qItem, "integer");
1604                                else if (itemType.equals("string")) checkOption(errors, answer, ns, qsrc, qItem, "string");
1605                                break;
1606                        case OPENCHOICE: 
1607                                itemType=validateQuestionnaireResponseItemType(errors, answer, ns, "Coding", "date", "time", "integer", "string");
1608                                if (itemType.equals("Coding")) validateAnswerCode(errors, answer, ns, qsrc, qItem, true);
1609                                else if (itemType.equals("date")) checkOption(errors, answer, ns, qsrc, qItem, "date");
1610                                else if (itemType.equals("time")) checkOption(errors, answer, ns, qsrc, qItem, "time");
1611                                else if (itemType.equals("integer")) checkOption(errors, answer, ns, qsrc, qItem, "integer");
1612                                else if (itemType.equals("string")) checkOption(errors, answer, ns, qsrc, qItem, "string", true);
1613                                break;
1614                        default:
1615                                break;
1616                        }
1617                        validateQuestionannaireResponseItems(qsrc, qItem.getItem(), errors, answer, stack, inProgress);
1618                }
1619                if (qItem.getType() == null) {
1620                        fail(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), false, "Definition for item "+qItem.getLinkId() + " does not contain a type");
1621                } else if (qItem.getType() == QuestionnaireItemType.GROUP) {
1622                        validateQuestionannaireResponseItems(qsrc, qItem.getItem(), errors, element, stack, inProgress);
1623                } else {
1624                        List<Element> items = new ArrayList<Element>();
1625                        element.getNamedChildren("item", items);
1626                        for (Element item : items) {
1627                                NodeStack ns = stack.push(item, -1, null, null);
1628                                rule(errors, IssueType.STRUCTURE, answers.get(0).line(), answers.get(0).col(), stack.getLiteralPath(), false, "Items not of type group should not have items - Item with linkId {0} of type {1} has {2} item(s)", qItem.getLinkId(), qItem.getType(), items.size());
1629                        }
1630                }
1631        }
1632
1633        private void validateQuestionannaireResponseItem(Questionnaire qsrc, QuestionnaireItemComponent qItem, List<ValidationMessage> errors, List<Element> elements, NodeStack stack, boolean inProgress) {
1634                if (elements.size() > 1)
1635                        rule(errors, IssueType.INVALID, elements.get(1).line(), elements.get(1).col(), stack.getLiteralPath(), qItem.getRepeats(), "Only one response item with this linkId allowed");
1636                for (Element element : elements) {
1637                        NodeStack ns = stack.push(element, -1, null, null);
1638                        validateQuestionannaireResponseItem(qsrc, qItem, errors, element, ns, inProgress);
1639                }
1640        }
1641
1642        private int getLinkIdIndex(List<QuestionnaireItemComponent> qItems, String linkId) {
1643                for (int i = 0; i < qItems.size(); i++) {
1644                        if (linkId.equals(qItems.get(i).getLinkId()))
1645                                return i;
1646                }
1647                return -1;
1648        }
1649
1650        private void validateQuestionannaireResponseItems(Questionnaire qsrc, List<QuestionnaireItemComponent> qItems, List<ValidationMessage> errors, Element element, NodeStack stack, boolean inProgress) {
1651                List<Element> items = new ArrayList<Element>();
1652                element.getNamedChildren("item", items);
1653                // now, sort into stacks
1654                Map<String, List<Element>> map = new HashMap<String, List<Element>>();
1655                int lastIndex = -1;
1656                for (Element item : items) {
1657                        String linkId = item.getNamedChildValue("linkId");
1658                        if (rule(errors, IssueType.REQUIRED, item.line(), item.col(), stack.getLiteralPath(), !Utilities.noString(linkId), "No LinkId, so can't be validated")) {
1659                                int index = getLinkIdIndex(qItems, linkId);
1660                                if (index == -1) {
1661                                        QuestionnaireItemComponent qItem = findQuestionnaireItem(qsrc, linkId);
1662                                        if (qItem != null) {
1663                                                rule(errors, IssueType.STRUCTURE, item.line(), item.col(), stack.getLiteralPath(), index > -1, "Structural Error: item is in the wrong place");
1664                                                NodeStack ns = stack.push(item, -1, null, null);
1665                                                validateQuestionannaireResponseItem(qsrc, qItem, errors, element, ns, inProgress);
1666                                        }
1667                                        else
1668                                                rule(errors, IssueType.NOTFOUND, item.line(), item.col(), stack.getLiteralPath(), index > -1, "LinkId \""+linkId+"\" not found in questionnaire");
1669                                }
1670                                else
1671                                {
1672                                        rule(errors, IssueType.STRUCTURE, item.line(), item.col(), stack.getLiteralPath(), index >= lastIndex, "Structural Error: items are out of order");
1673                                        lastIndex = index;
1674                                        List<Element> mapItem = map.get(linkId);
1675                                        if (mapItem == null) {
1676                                                mapItem = new ArrayList<Element>();
1677                                                map.put(linkId, mapItem);
1678                                        }
1679                                        mapItem.add(item);
1680                                }
1681                        }
1682                }
1683
1684                // ok, now we have a list of known items, grouped by linkId. We"ve made an error for anything out of order
1685                for (QuestionnaireItemComponent qItem : qItems) {
1686                        List<Element> mapItem = map.get(qItem.getLinkId());
1687                        if (mapItem != null)
1688                                validateQuestionannaireResponseItem(qsrc, qItem, errors, mapItem, stack, inProgress);
1689                        else
1690                                rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), !qItem.getRequired(), "No response found for required item "+qItem.getLinkId());
1691                }
1692        }
1693
1694        private void validateQuestionnaireResponseItemQuantity( List<ValidationMessage> errors, Element answer, NodeStack stack)        {
1695
1696        }
1697
1698        private String validateQuestionnaireResponseItemType(List<ValidationMessage> errors, Element element, NodeStack stack, String... types) {
1699                List<Element> values = new ArrayList<Element>();
1700                element.getNamedChildrenWithWildcard("value[x]", values);
1701                if (values.size() > 0) {
1702                        NodeStack ns = stack.push(values.get(0), -1, null, null);
1703                        CommaSeparatedStringBuilder l = new CommaSeparatedStringBuilder();
1704                        for (String s : types)  {
1705                                l.append(s);
1706                                if (values.get(0).getName().equals("value"+Utilities.capitalize(s))) 
1707                                        return(s);
1708                        }
1709                        if (types.length == 1)
1710                                rule(errors, IssueType.STRUCTURE, values.get(0).line(), values.get(0).col(), ns.getLiteralPath(), false, "Answer value must be of type "+types[0]);
1711                        else
1712                                rule(errors, IssueType.STRUCTURE, values.get(0).line(), values.get(0).col(), ns.getLiteralPath(), false, "Answer value must be one of the types "+l.toString());
1713                }
1714                return null;
1715        }
1716
1717        private QuestionnaireItemComponent findQuestionnaireItem(Questionnaire qSrc, String linkId) {
1718                return findItem(qSrc.getItem(), linkId);
1719        }
1720
1721        private QuestionnaireItemComponent findItem(List<QuestionnaireItemComponent> list, String linkId) {
1722                for (QuestionnaireItemComponent item : list) {
1723                        if (linkId.equals(item.getLinkId()))
1724                                return item;
1725                        QuestionnaireItemComponent result = findItem(item.getItem(), linkId);
1726                        if (result != null)
1727                                return result;
1728                }
1729                return null;
1730        }
1731
1732        /*      private void validateAnswerCode(List<ValidationMessage> errors, Element value, NodeStack stack, List<Coding> optionList) {
1733          String system = value.getNamedChildValue("system");
1734          String code = value.getNamedChildValue("code");
1735          boolean found = false;
1736          for (Coding c : optionList) {
1737      if (ObjectUtil.equals(c.getSystem(), system) && ObjectUtil.equals(c.getCode(), code)) {
1738              found = true;
1739              break;
1740            }
1741          }
1742          rule(errors, IssueType.STRUCTURE, value.line(), value.col(), stack.getLiteralPath(), found, "The code "+system+"::"+code+" is not a valid option");
1743        }*/
1744
1745        private void validateAnswerCode(List<ValidationMessage> errors, Element value, NodeStack stack, Questionnaire qSrc, Reference ref, boolean theOpenChoice) {
1746                ValueSet vs = resolveBindingReference(qSrc, ref);
1747                if (warning(errors, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), vs != null, "ValueSet " + describeReference(ref) + " not found"))  {
1748                        try {
1749                                Coding c = readAsCoding(value);
1750                                if (isBlank(c.getCode()) && isBlank(c.getSystem()) && isNotBlank(c.getDisplay())) {
1751                                        if (theOpenChoice) {
1752                                                return;
1753                                        }
1754                                }
1755
1756                                long t = System.nanoTime();
1757                                ValidationResult res = context.validateCode(c, vs);
1758                                txTime = txTime + (System.nanoTime() - t);
1759                                if (!res.isOk())
1760                                        rule(errors, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), false, "The value provided ("+c.getSystem()+"::"+c.getCode()+") is not in the options value set in the questionnaire");
1761                        } catch (Exception e) {
1762                                warning(errors, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), false, "Error " + e.getMessage() + " validating Coding against Questionnaire Options");
1763                        }
1764                }
1765        }
1766
1767        private void validateAnswerCode( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean theOpenChoice) {
1768                Element v = answer.getNamedChild("valueCoding");
1769                NodeStack ns = stack.push(v, -1, null, null);
1770                if (qItem.getOption().size() > 0)
1771                        checkCodingOption(errors, answer, stack, qSrc, qItem, theOpenChoice);
1772                //          validateAnswerCode(errors, v, stack, qItem.getOption());
1773                else if (qItem.hasOptions())
1774                        validateAnswerCode(errors, v, stack, qSrc, qItem.getOptions(), theOpenChoice);
1775                else
1776                        hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate options because no option or options are provided");
1777        }
1778
1779        private void checkOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, String type) {
1780                checkOption(errors, answer, stack, qSrc,  qItem, type, false);
1781        }
1782
1783        private void checkOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, String type, boolean openChoice) {
1784                if (type.equals("integer"))     checkIntegerOption(errors, answer, stack, qSrc, qItem, openChoice);
1785                else if (type.equals("date"))   checkDateOption(errors, answer, stack, qSrc, qItem, openChoice);
1786                else if (type.equals("time"))   checkTimeOption(errors, answer, stack, qSrc, qItem, openChoice);
1787                else if (type.equals("string")) checkStringOption(errors, answer, stack, qSrc, qItem, openChoice);
1788                else if (type.equals("Coding")) checkCodingOption(errors, answer, stack, qSrc, qItem, openChoice);
1789        }
1790
1791        private void checkIntegerOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) {
1792                Element v = answer.getNamedChild("valueInteger");
1793                NodeStack ns = stack.push(v, -1, null, null);
1794                if (qItem.getOption().size() > 0) {
1795                        List<IntegerType> list = new ArrayList<IntegerType>();
1796                        for (QuestionnaireItemOptionComponent components : qItem.getOption())  {
1797                                try {
1798                                        list.add(components.getValueIntegerType());
1799                                } catch (FHIRException e) {
1800                                        // If it's the wrong type, just keep going
1801                                }
1802                        }
1803                        if (list.isEmpty() && !openChoice) {
1804                                rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type integer");
1805                        } else {
1806                                boolean found = false;
1807                                for (IntegerType item : list) {
1808                                        if (item.getValue() == Integer.parseInt(v.primitiveValue())) {
1809                                                found = true;
1810                                                break;
1811                                        }
1812                                }
1813                                if (!found) {
1814                                        rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The integer "+v.primitiveValue()+" is not a valid option");
1815                                }
1816                        }
1817                } else
1818                        hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate integer answer option because no option list is provided");
1819        }
1820
1821        private void checkDateOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) {
1822                Element v = answer.getNamedChild("valueDate");
1823                NodeStack ns = stack.push(v, -1, null, null);
1824                if (qItem.getOption().size() > 0) {
1825                        List<DateType> list = new ArrayList<DateType>();
1826                        for (QuestionnaireItemOptionComponent components : qItem.getOption())  {
1827                                try {
1828                                        list.add(components.getValueDateType());
1829                                } catch (FHIRException e) {
1830                                        // If it's the wrong type, just keep going
1831                                }
1832                        }
1833                        if (list.isEmpty() && !openChoice) {
1834                                rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type date");
1835                        } else {
1836                                boolean found = false;
1837                                for (DateType item : list) {
1838                                        if (item.getValue().equals(v.primitiveValue())) {
1839                                                found = true;
1840                                                break;
1841                                        }
1842                                }
1843                                if (!found) {
1844                                        rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The date "+v.primitiveValue()+" is not a valid option");
1845                                }
1846                        }
1847                } else
1848                        hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate date answer option because no option list is provided");
1849        }
1850
1851        private void checkTimeOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) {
1852                Element v = answer.getNamedChild("valueTime");
1853                NodeStack ns = stack.push(v, -1, null, null);
1854                if (qItem.getOption().size() > 0) {
1855                        List<TimeType> list = new ArrayList<TimeType>();
1856                        for (QuestionnaireItemOptionComponent components : qItem.getOption())  {
1857                                try {
1858                                        list.add(components.getValueTimeType());
1859                                } catch (FHIRException e) {
1860                                        // If it's the wrong type, just keep going
1861                                }
1862                        }
1863                        if (list.isEmpty() && !openChoice) {
1864                                rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type time");
1865                        } else {
1866                                boolean found = false;
1867                                for (TimeType item : list) {
1868                                        if (item.getValue().equals(v.primitiveValue())) {
1869                                                found = true;
1870                                                break;
1871                                        }
1872                                }
1873                                if (!found) {
1874                                        rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The time "+v.primitiveValue()+" is not a valid option");
1875                                }
1876                        }
1877                } else
1878                        hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate time answer option because no option list is provided");
1879        }
1880
1881        private void checkStringOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) {
1882                Element v = answer.getNamedChild("valueString");
1883                NodeStack ns = stack.push(v, -1, null, null);
1884                if (qItem.getOption().size() > 0) {
1885                        List<StringType> list = new ArrayList<StringType>();
1886                        for (QuestionnaireItemOptionComponent components : qItem.getOption())  {
1887                                try {
1888                                        if (components.getValue() != null) {
1889                                                list.add(components.getValueStringType());
1890                                        }
1891                                } catch (FHIRException e) {
1892                                        // If it's the wrong type, just keep going
1893                                }
1894                        }
1895                        if (list.isEmpty() && !openChoice) {
1896                                rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type string");
1897                        } else {
1898                                boolean found = false;
1899                                for (StringType item : list) {
1900                                        if (item.getValue().equals((v.primitiveValue()))) {
1901                                                found = true;
1902                                                break;
1903                                        }
1904                                }
1905                                if (!found) {
1906                                        rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The string "+v.primitiveValue()+" is not a valid option");
1907                                }
1908                        }
1909                } else {
1910                        hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate string answer option because no option list is provided");
1911                }
1912        }
1913
1914        private void checkCodingOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) {
1915                Element v = answer.getNamedChild("valueCoding");
1916                String system = v.getNamedChildValue("system");
1917                String code = v.getNamedChildValue("code");
1918                NodeStack ns = stack.push(v, -1, null, null);
1919                if (qItem.getOption().size() > 0) {
1920                        List<Coding> list = new ArrayList<Coding>();
1921                        for (QuestionnaireItemOptionComponent components : qItem.getOption())  {
1922                                try {
1923                                        if (components.getValue() != null) {
1924                                                list.add(components.getValueCoding());
1925                                        }
1926                                } catch (FHIRException e) {
1927                                        // If it's the wrong type, just keep going
1928                                }
1929                        }
1930                        if (list.isEmpty() && !openChoice) {
1931                                rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type coding");
1932                        } else {
1933                                boolean found = false;
1934                                for (Coding item : list) {
1935                                        if (ObjectUtil.equals(item.getSystem(), system) && ObjectUtil.equals(item.getCode(), code)) {
1936                                                found = true;
1937                                                break;
1938                                        }
1939                                }
1940                                if (!found) {
1941                                        rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The code "+system+"::"+code+" is not a valid option");
1942                                }
1943                        }
1944                } else
1945                        hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate Coding option because no option list is provided");
1946        }
1947
1948        private String tail(String path) {
1949                return path.substring(path.lastIndexOf(".") + 1);
1950        }
1951
1952        private String tryParse(String ref)  {
1953                String[] parts = ref.split("\\/");
1954                switch (parts.length) {
1955                case 1:
1956                        return null;
1957                case 2:
1958                        return checkResourceType(parts[0]);
1959                default:
1960                        if (parts[parts.length - 2].equals("_history"))
1961                                return checkResourceType(parts[parts.length - 4]);
1962                        else
1963                                return checkResourceType(parts[parts.length - 2]);
1964                }
1965        }
1966
1967        private boolean typesAreAllReference(List<TypeRefComponent> theType) {
1968                for (TypeRefComponent typeRefComponent : theType) {
1969                        if (typeRefComponent.getCode().equals("Reference") == false) {
1970                                return false;
1971                        }
1972                }
1973                return true;
1974        }
1975
1976        private void validateBundle(List<ValidationMessage> errors, Element bundle, NodeStack stack) {
1977                List<Element> entries = new ArrayList<Element>();
1978                bundle.getNamedChildren("entry", entries);
1979                String type = bundle.getNamedChildValue("type");
1980                if (entries.size() == 0) {
1981                        rule(errors, IssueType.INVALID, stack.getLiteralPath(), !(type.equals("document") || type.equals("message")), "Documents or Messages must contain at least one entry");
1982                } else {
1983                        Element firstEntry = entries.get(0);
1984                        NodeStack firstStack = stack.push(firstEntry, 0, null, null);
1985                        String fullUrl = firstEntry.getNamedChildValue("fullUrl");
1986
1987                        if (type.equals("document")) {
1988                                Element resource = firstEntry.getNamedChild("resource");
1989                                NodeStack localStack = firstStack.push(resource, -1, null, null);
1990                                String id = resource.getNamedChildValue("id");
1991                                if (rule(errors, IssueType.INVALID, firstEntry.line(), firstEntry.col(), stack.addToLiteralPath("entry", ":0"), resource != null, "No resource on first entry")) {
1992                                        validateDocument(errors, entries, resource, localStack.push(resource, -1, null, null), fullUrl, id);
1993                                }
1994                        }
1995                        if (type.equals("message"))
1996                                validateMessage(errors, bundle);
1997                }
1998        }
1999
2000        private void validateBundleReference(List<ValidationMessage> errors, List<Element> entries, Element ref, String name, NodeStack stack, String fullUrl, String type, String id) {
2001                if (ref != null && !Utilities.noString(ref.getNamedChildValue("reference"))) {
2002                        Element target = resolveInBundle(entries, ref.getNamedChildValue("reference"), fullUrl, type, id);
2003                        rule(errors, IssueType.INVALID, target.line(), target.col(), stack.addToLiteralPath("reference"), target != null, "Unable to resolve the target of the reference in the bundle (" + name + ")");
2004                }
2005        }
2006
2007        private void validateContains(List<ValidationMessage> errors, String path, ElementDefinition child, ElementDefinition context, Element resource, Element element, NodeStack stack, IdStatus idstatus) throws FHIRException, FHIRException {
2008                String resourceName = element.getType();
2009                long t = System.nanoTime();
2010                StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName);
2011                sdTime = sdTime + (System.nanoTime() - t);
2012                // special case: resource wrapper is reset if we're crossing a bundle boundary, but not otherwise
2013                if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY) 
2014                        resource = element;
2015                if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), profile != null, "No profile found for contained resource of type '" + resourceName + "'"))
2016                        validateResource(errors, resource, element, profile, idstatus, stack);
2017        }
2018
2019        private void validateDocument(List<ValidationMessage> errors, List<Element> entries, Element composition, NodeStack stack, String fullUrl, String id) {
2020                // first entry must be a composition
2021                if (rule(errors, IssueType.INVALID, composition.line(), composition.col(), stack.getLiteralPath(), composition.getType().equals("Composition"),
2022                                "The first entry in a document must be a composition")) {
2023                        // the composition subject and section references must resolve in the bundle
2024                        Element elem = composition.getNamedChild("subject");
2025                        if (rule(errors, IssueType.INVALID, composition.line(), composition.col(), stack.getLiteralPath(), elem != null, "A document composition must have a subject"))
2026                                validateBundleReference(errors, entries, elem, "Composition Subject", stack.push(elem, -1, null, null), fullUrl, "Composition", id);
2027                        validateSections(errors, entries, composition, stack, fullUrl, id);
2028                }
2029        }
2030        // rule(errors, IssueType.INVALID, bundle.line(), bundle.col(), "Bundle", !"urn:guid:".equals(base), "The base 'urn:guid:' is not valid (use urn:uuid:)");
2031        // rule(errors, IssueType.INVALID, entry.line(), entry.col(), localStack.getLiteralPath(), !"urn:guid:".equals(ebase), "The base 'urn:guid:' is not valid");
2032        // rule(errors, IssueType.INVALID, entry.line(), entry.col(), localStack.getLiteralPath(), !Utilities.noString(base) || !Utilities.noString(ebase), "entry
2033        // does not have a base");
2034        // String firstBase = null;
2035        // firstBase = ebase == null ? base : ebase;
2036
2037        long time = 0;
2038        private void validateElement(List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, StructureDefinition cprofile, ElementDefinition context,
2039                        Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept) throws FHIRException, FHIRException {
2040//              element.markValidation(profile, definition);
2041
2042                //              System.out.println("  "+stack.getLiteralPath()+" "+Long.toString((System.nanoTime() - time) / 1000000));
2043                //              time = System.nanoTime();
2044                checkInvariants(errors, stack.getLiteralPath(), profile, definition, null, null, resource, element);
2045
2046                // get the list of direct defined children, including slices
2047                List<ElementDefinition> childDefinitions = ProfileUtilities.getChildMap(profile, definition.getName(), definition.getPath(), definition.getContentReference());
2048
2049                // 1. List the children, and remember their exact path (convenience)
2050                List<ElementInfo> children = new ArrayList<InstanceValidator.ElementInfo>();
2051                ChildIterator iter = new ChildIterator(stack.getLiteralPath(), element);
2052                while (iter.next())
2053                        children.add(new ElementInfo(iter.name(), iter.element(), iter.path(), iter.count()));
2054
2055                // 2. assign children to a definition
2056                // for each definition, for each child, check whether it belongs in the slice
2057                ElementDefinition slice = null;
2058                for (int i = 0; i < childDefinitions.size(); i++) {
2059                        ElementDefinition ed = childDefinitions.get(i);
2060                        boolean process = true;
2061                        // where are we with slicing
2062                        if (ed.hasSlicing()) {
2063                                if (slice != null && slice.getPath().equals(ed.getPath()))
2064                                        throw new DefinitionException("Slice encountered midway through path on " + slice.getPath());
2065                                slice = ed;
2066                                process = false;
2067                        } else if (slice != null && !slice.getPath().equals(ed.getPath()))
2068                                slice = null;
2069
2070                        if (process) {
2071                                for (ElementInfo ei : children) {
2072                                        boolean match = false;
2073                                        if (slice == null) {
2074                                                match = nameMatches(ei.name, tail(ed.getPath()));
2075                                        } else {
2076                                                if (nameMatches(ei.name, tail(ed.getPath())))
2077                                                        match = sliceMatches(ei.element, ei.path, slice, ed, profile);
2078                                        }
2079                                        if (match) {
2080                                                if (rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, ei.definition == null, "Element matches more than one slice")) {
2081                                                        ei.definition = ed;
2082                                                        ei.index = i;
2083                                                }
2084                                        }
2085                                }
2086                        }
2087                }
2088                int last = -1;
2089                for (ElementInfo ei : children) {
2090                        if (ei.path.endsWith(".extension"))
2091                                rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, ei.definition != null, "Element is unknown or does not match any slice (url=\"" + ei.element.getNamedChildValue("url") + "\")");
2092                        else
2093                                rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, (ei.definition != null), "Element is unknown or does not match any slice");
2094                        rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, (ei.definition == null) || (ei.index >= last), "Element is out of order");
2095                        last = ei.index;
2096                }
2097
2098                // 3. report any definitions that have a cardinality problem
2099                for (ElementDefinition ed : childDefinitions) {
2100                        if (ed.getRepresentation().isEmpty()) { // ignore xml attributes
2101                                int count = 0;
2102                                for (ElementInfo ei : children)
2103                                        if (ei.definition == ed)
2104                                                count++;
2105                                if (ed.getMin() > 0) {
2106                                        rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), count >= ed.getMin(),
2107                                                        "Element '" + stack.getLiteralPath() + "." + tail(ed.getPath()) + "': minimum required = " + Integer.toString(ed.getMin()) + ", but only found " + Integer.toString(count));
2108                                }
2109                                if (ed.hasMax() && !ed.getMax().equals("*")) {
2110                                        rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), count <= Integer.parseInt(ed.getMax()),
2111                                                        "Element " + tail(ed.getPath()) + " @ " + stack.getLiteralPath() + ": max allowed = " + ed.getMax() + ", but found " + Integer.toString(count));
2112                                }
2113
2114                        }
2115                }
2116                // 4. check order if any slices are orderd. (todo)
2117
2118                // 5. inspect each child for validity
2119                for (ElementInfo ei : children) {
2120                        if (ei.definition != null) {
2121                                String type = null;
2122                                ElementDefinition typeDefn = null;
2123                                if (ei.definition.getType().size() == 1 && !ei.definition.getType().get(0).getCode().equals("*") && !ei.definition.getType().get(0).getCode().equals("Element")
2124                                                && !ei.definition.getType().get(0).getCode().equals("BackboneElement"))
2125                                        type = ei.definition.getType().get(0).getCode();
2126                                else if (ei.definition.getType().size() == 1 && ei.definition.getType().get(0).getCode().equals("*")) {
2127                                        String prefix = tail(ei.definition.getPath());
2128                                        assert prefix.endsWith("[x]");
2129                                        type = ei.name.substring(prefix.length() - 3);
2130                                        if (isPrimitiveType(type))
2131                                                type = Utilities.uncapitalize(type);
2132                                } else if (ei.definition.getType().size() > 1) {
2133
2134                                        String prefix = tail(ei.definition.getPath());
2135                                        assert typesAreAllReference(ei.definition.getType()) || prefix.endsWith("[x]") : prefix;
2136
2137                                        prefix = prefix.substring(0, prefix.length() - 3);
2138                                        for (TypeRefComponent t : ei.definition.getType())
2139                                                if ((prefix + Utilities.capitalize(t.getCode())).equals(ei.name))
2140                                                        type = t.getCode();
2141                                        if (type == null) {
2142                                                TypeRefComponent trc = ei.definition.getType().get(0);
2143                                                if (trc.getCode().equals("Reference"))
2144                                                        type = "Reference";
2145                                                else 
2146                                                        rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), false,
2147                                                                        "The element " + ei.name + " is illegal. Valid types at this point are " + describeTypes(ei.definition.getType()));
2148                                        }
2149                                } else if (ei.definition.getContentReference() != null) {
2150                                        typeDefn = resolveNameReference(profile.getSnapshot(), ei.definition.getContentReference());
2151                                }
2152
2153                                if (type != null) {
2154                                        if (type.startsWith("@")) {
2155                                                ei.definition = findElement(profile, type.substring(1));
2156                                                type = null;
2157                                        }
2158                                }
2159                                NodeStack localStack = stack.push(ei.element, ei.count, ei.definition, type == null ? typeDefn : resolveType(type));
2160                                String localStackLiterapPath = localStack.getLiteralPath();
2161                                String eiPath = ei.path;
2162                                assert(eiPath.equals(localStackLiterapPath)) : "ei.path: " + ei.path + "  -  localStack.getLiteralPath: " + localStackLiterapPath;
2163                                boolean thisIsCodeableConcept = false;
2164
2165                                if (type != null) {
2166                                        if (isPrimitiveType(type))
2167                                                checkPrimitive(errors, ei.path, type, ei.definition, ei.element, profile);
2168                                        else {
2169                                                if (type.equals("Identifier"))
2170                                                        checkIdentifier(errors, ei.path, ei.element, ei.definition);
2171                                                else if (type.equals("Coding"))
2172                                                        checkCoding(errors, ei.path, ei.element, profile, ei.definition, inCodeableConcept);
2173                                                else if (type.equals("CodeableConcept")) {
2174                                                        checkCodeableConcept(errors, ei.path, ei.element, profile, ei.definition);
2175                                                        thisIsCodeableConcept = true;
2176                                                } else if (type.equals("Reference"))
2177                                                        checkReference(errors, ei.path, ei.element, profile, ei.definition, actualType, localStack);
2178
2179                                                if (type.equals("Extension"))
2180                                                        checkExtension(errors, ei.path, ei.element, ei.definition, profile, localStack);
2181                                                else if (type.equals("Resource"))
2182                                                        validateContains(errors, ei.path, ei.definition, definition, resource, ei.element, localStack, idStatusForEntry(element, ei)); // if
2183                                                // (str.matches(".*([.,/])work\\1$"))
2184                                                else {
2185                                                        StructureDefinition p = getProfileForType(type);
2186                                                        if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, p != null, "Unknown type " + type)) {
2187                                                                validateElement(errors, p, p.getSnapshot().getElement().get(0), profile, ei.definition, resource, ei.element, type, localStack, thisIsCodeableConcept);
2188                                                        }
2189                                                }
2190                                        }
2191                                } else {
2192                                        if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), ei.definition != null, "Unrecognised Content " + ei.name))
2193                                                validateElement(errors, profile, ei.definition, null, null, resource, ei.element, type, localStack, false);
2194                                }
2195                        }
2196                }
2197        }
2198
2199        private IdStatus idStatusForEntry(Element ep, ElementInfo ei) {
2200                if (isBundleEntry(ei.path)) {
2201                        Element req = ep.getNamedChild("request");
2202                        Element resp = ep.getNamedChild("response");
2203                        Element fullUrl = ep.getNamedChild("fullUrl");
2204                        Element method = null;
2205                        Element url = null;
2206                        if (req != null) {
2207                                method = req.getNamedChild("method");
2208                                url = req.getNamedChild("url");
2209                        }
2210                        if (resp != null) {
2211                                return IdStatus.OPTIONAL;
2212                        } if (method == null) {
2213                                if (fullUrl == null)
2214                                        return IdStatus.REQUIRED;
2215                                else if (fullUrl.primitiveValue().startsWith("urn:uuid:"))
2216                                        return IdStatus.OPTIONAL;
2217                                else
2218                                        return IdStatus.REQUIRED;
2219                        } else {
2220                                String s = method.primitiveValue();
2221                                if (s.equals("PUT")) {
2222                                        if (url == null)
2223                                                return IdStatus.REQUIRED;
2224                                        else
2225                                                return IdStatus.OPTIONAL; // or maybe prohibited? not clear
2226                                } else if (s.equals("POST"))
2227                                        return IdStatus.OPTIONAL; // this should be prohibited, but see task 9102
2228                                else // actually, we should never get to here; a bundle entry with method get/delete should not have a resource
2229                                        return IdStatus.OPTIONAL;                                       
2230                        }
2231                } else if (isParametersEntry(ei.path))
2232                        return IdStatus.OPTIONAL; 
2233                else
2234                        return IdStatus.REQUIRED; 
2235        }
2236
2237        private void checkInvariants(List<ValidationMessage> errors, String path, StructureDefinition profile, ElementDefinition ed, String typename, String typeProfile, Element resource, Element element) throws FHIRException, FHIRException {
2238                for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
2239                        if (inv.hasExpression()) {
2240                                ExpressionNode n = (ExpressionNode) inv.getUserData("validator.expression.cache");
2241                                if (n == null) {
2242                                        long t = System.nanoTime();
2243                                        n = fpe.parse(inv.getExpression());
2244                                        fpeTime = fpeTime + (System.nanoTime() - t);
2245                                        inv.setUserData("validator.expression.cache", n);
2246                                }
2247
2248                                String msg;
2249                                boolean ok;
2250                                try {
2251                                        long t = System.nanoTime();
2252                                        ok = fpe.evaluateToBoolean(resource, element, n);
2253                                        fpeTime = fpeTime + (System.nanoTime() - t);
2254                                        msg = fpe.forLog();
2255                                } catch (Exception ex) {
2256                                        ok = false;
2257                                        msg = ex.getMessage(); 
2258                                }
2259                                if (!ok) {
2260                                        if (inv.getSeverity() == ConstraintSeverity.ERROR)
2261                                                rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getHuman()+" ("+msg+") ["+inv.getExpression()+"]");
2262                                        else if (inv.getSeverity() == ConstraintSeverity.WARNING)
2263                                                warning(errors, IssueType.INVARIANT, element.line(), element.line(), path, ok, inv.getHuman()+" ("+msg+") ["+inv.getExpression()+"]");
2264                                }
2265                        }
2266                }
2267        }
2268
2269        private void validateMessage(List<ValidationMessage> errors, Element bundle) {
2270                // TODO Auto-generated method stub
2271
2272        }
2273
2274        private void validateObservation(List<ValidationMessage> errors, Element element, NodeStack stack) {
2275                // all observations should have a subject, a performer, and a time
2276
2277                bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("subject") != null, "All observations should have a subject");
2278                bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("performer") != null, "All observations should have a performer");
2279                bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("effectiveDateTime") != null || element.getNamedChild("effectivePeriod") != null,
2280                                "All observations should have an effectiveDateTime or an effectivePeriod");
2281        }
2282
2283        /*
2284         * The actual base entry point
2285         */
2286        private void validateResource(List<ValidationMessage> errors, Element resource, Element element, StructureDefinition profile, IdStatus idstatus, NodeStack stack) throws FHIRException, FHIRException {
2287                assert stack != null;
2288                assert resource != null;
2289
2290                // getting going - either we got a profile, or not.
2291                boolean ok = true;
2292                if (ok) {
2293                        String resourceName = element.getType();
2294                        if (profile == null) {
2295                                long t = System.nanoTime();
2296                                profile = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName);
2297                                sdTime = sdTime + (System.nanoTime() - t);
2298                                ok = rule(errors, IssueType.INVALID, element.line(), element.col(), stack.addToLiteralPath(resourceName), profile != null, "No profile found for resource type '" + resourceName + "'");
2299                        } else {
2300                                String type = profile.getKind() == StructureDefinitionKind.LOGICAL ? profile.getId() : profile.hasBaseType() && profile.getDerivation() == TypeDerivationRule.CONSTRAINT ? profile.getBaseType() : profile.getName();
2301                                // special case: we have a bundle, and the profile is not for a bundle. We'll try the first entry instead 
2302                                if (!type.equals(resourceName) && resourceName.equals("Bundle")) {
2303                                        Element first = getFirstEntry(element);
2304                                        if (first != null && first.getType().equals(type)) {
2305                                                element = first;
2306                                                resourceName = element.getType();
2307                                                idstatus = IdStatus.OPTIONAL; // why?
2308                                        }
2309                                }
2310                                ok = rule(errors, IssueType.INVALID, -1, -1, stack.getLiteralPath(), type.equals(resourceName),
2311                                                "Specified profile type was '" + type + "', but found type '" + resourceName + "'");
2312                        }
2313                }
2314
2315                if (ok) {
2316                        if (idstatus == IdStatus.REQUIRED && (element.getNamedChild("id") == null))
2317                                rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, "Resource requires an id, but none is present");
2318                        else if (idstatus == IdStatus.PROHIBITED && (element.getNamedChild("id") != null))
2319                                rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, "Resource has an id, but none is allowed");
2320                        start(errors, resource, element, profile, stack); // root is both definition and type
2321                }
2322        }
2323
2324        private Element getFirstEntry(Element bundle) {
2325                List<Element> list = new ArrayList<Element>();
2326                bundle.getNamedChildren("entry", list);
2327                if (list.isEmpty())
2328                        return null;
2329                Element resource = list.get(0).getNamedChild("resource");
2330                if (resource == null)
2331                        return null;
2332                else
2333                        return resource;
2334        }
2335
2336        private void validateSections(List<ValidationMessage> errors, List<Element> entries, Element focus, NodeStack stack, String fullUrl, String id) {
2337                List<Element> sections = new ArrayList<Element>();
2338                focus.getNamedChildren("entry", sections);
2339                int i = 0;
2340                for (Element section : sections) {
2341                        NodeStack localStack = stack.push(section, 1, null, null);
2342                        validateBundleReference(errors, entries, section.getNamedChild("content"), "Section Content", localStack, fullUrl, "Composition", id);
2343                        validateSections(errors, entries, section, localStack, fullUrl, id);
2344                        i++;
2345                }
2346        }
2347
2348        private boolean valueMatchesCriteria(Element value, ElementDefinition criteria) {
2349                // throw new Error("validation of slices not done yet");
2350                return false;
2351        }
2352
2353        private boolean yearIsValid(String v) {
2354                if (v == null) {
2355                        return false;
2356                }
2357                try {
2358                        int i = Integer.parseInt(v.substring(0, Math.min(4, v.length())));
2359                        return i >= 1800 && i <= 2100;
2360                } catch (NumberFormatException e) {
2361                        return false;
2362                }
2363        }
2364
2365        public class ChildIterator {
2366                private String basePath;
2367                private Element parent;
2368                private int cursor;
2369                private int lastCount;
2370
2371                public ChildIterator(String path, Element element) {
2372                        parent = element;
2373                        basePath = path;
2374                        cursor = -1;
2375                }
2376
2377                public int count() {
2378                        String nb = cursor == 0 ? "--" : parent.getChildren().get(cursor-1).getName();
2379                        String na = cursor >= parent.getChildren().size() - 1 ? "--" : parent.getChildren().get(cursor+1).getName();
2380                        if (name().equals(nb) || name().equals(na) ) {
2381                                return lastCount + 1;
2382                        } else
2383                                return -1;
2384                }
2385
2386                public Element element() {
2387                        return parent.getChildren().get(cursor);
2388                }
2389
2390                public String name() {
2391                        return element().getName();
2392                }
2393
2394                public boolean next() {
2395                        if (cursor == -1) {
2396                                cursor++;
2397                                lastCount = 0;
2398                        } else {
2399                                String lastName = name();
2400                                cursor++;
2401                                if (cursor < parent.getChildren().size() && name().equals(lastName))
2402                                        lastCount++;
2403                                else
2404                                        lastCount = 0;
2405                        }
2406                        return cursor < parent.getChildren().size();
2407                }
2408
2409                public String path() {
2410                        int i = count();
2411                        String sfx = "";
2412                        if (i > -1) {
2413                                sfx = "[" + Integer.toString(lastCount + 1) + "]";
2414                        }
2415                        return basePath + "." + name() + sfx;
2416                }
2417        }
2418
2419        private class NodeStack {
2420                private ElementDefinition definition;
2421                private Element element;
2422                private ElementDefinition extension;
2423                private String literalPath; // xpath format
2424                private List<String> logicalPaths; // dotted format, various entry points
2425                private NodeStack parent;
2426                private ElementDefinition type;
2427
2428                public NodeStack() {
2429                }         
2430
2431                public NodeStack(Element element) {
2432                        this.element = element;
2433                        literalPath = element.getName();
2434                }         
2435
2436                public String addToLiteralPath(String... path) {
2437                        StringBuilder b = new StringBuilder();
2438                        b.append(getLiteralPath());
2439                        for (String p : path) {
2440                                if (p.startsWith(":")) {
2441                                        b.append("[");
2442                                        b.append(p.substring(1));
2443                                        b.append("]");
2444                                } else {
2445                                        b.append(".");
2446                                        b.append(p);
2447                                }         
2448                        }         
2449                        return b.toString();
2450                }
2451
2452                private ElementDefinition getDefinition() {
2453                        return definition;
2454                }
2455
2456                private Element getElement() {
2457                        return element;
2458                }
2459
2460                private String getLiteralPath() {
2461                        return literalPath == null ? "" : literalPath;
2462                }
2463
2464                private List<String> getLogicalPaths() {
2465                        return logicalPaths == null ? new ArrayList<String>() : logicalPaths;
2466                }
2467
2468                private ElementDefinition getType() {
2469                        return type;
2470                }
2471
2472                private NodeStack push(Element element, int count, ElementDefinition definition, ElementDefinition type) {
2473                        NodeStack res = new NodeStack();
2474                        res.parent = this;
2475                        res.element = element;
2476                        res.definition = definition;
2477                        res.literalPath = getLiteralPath() + "." + element.getName();
2478                        if (count > -1)
2479                                res.literalPath = res.literalPath + "[" + Integer.toString(count) + "]";
2480                        res.logicalPaths = new ArrayList<String>();
2481                        if (type != null) {
2482                                // type will be bull if we on a stitching point of a contained resource, or if....
2483                                res.type = type;
2484                                String t = tail(definition.getPath());
2485                                for (String lp : getLogicalPaths()) {
2486                                        res.logicalPaths.add(lp + "." + t);
2487                                        if (t.endsWith("[x]"))
2488                                                res.logicalPaths.add(lp + "." + t.substring(0, t.length() - 3) + type.getPath());
2489                                }
2490                                res.logicalPaths.add(type.getPath());
2491                        } else if (definition != null) {
2492                                for (String lp : getLogicalPaths())
2493                                        res.logicalPaths.add(lp + "." + element.getName());
2494                        } else
2495                                res.logicalPaths.addAll(getLogicalPaths());
2496                        // CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
2497                        // for (String lp : res.logicalPaths)
2498                        // b.append(lp);
2499                        // System.out.println(res.literalPath+" : "+b.toString());
2500                        return res;
2501                }
2502
2503                private void setType(ElementDefinition type) {
2504                        this.type = type;
2505                }
2506        }
2507
2508        private void checkForProcessingInstruction(List<ValidationMessage> errors, Document document) {
2509                Node node = document.getFirstChild();
2510                while (node != null) {
2511                        rule(errors, IssueType.INVALID, -1, -1, "(document)", node.getNodeType() != Node.PROCESSING_INSTRUCTION_NODE, "No processing instructions allowed in resources");
2512                        node = node.getNextSibling();
2513                }
2514        }
2515
2516        public class ElementInfo {
2517
2518                public int index;
2519                public int count;
2520                public ElementDefinition definition;
2521                private Element element;
2522                private String name;
2523                private String path;
2524
2525                public ElementInfo(String name, Element element, String path, int count) {
2526                        this.name = name;
2527                        this.element = element;
2528                        this.path = path;
2529                        this.count = count;
2530                }
2531
2532                public int col() {
2533                        return element.col();
2534                }
2535
2536                public int line() {
2537                        return element.line();
2538                }
2539
2540        }
2541
2542        public String reportTimes() {
2543                String s = String.format("Times: overall = %d, tx = %d, sd = %d, load = %d, fpe = %d", overall, txTime, sdTime, loadTime, fpeTime);
2544                overall = 0;
2545                txTime = 0;
2546                sdTime = 0;
2547                loadTime = 0;
2548                fpeTime = 0;
2549                return s;
2550        }
2551}