001package org.hl7.fhir.convertors.misc;
002
003import java.io.FileNotFoundException;
004import java.io.IOException;
005import java.util.*;
006
007import org.hl7.fhir.exceptions.DefinitionException;
008import org.hl7.fhir.exceptions.FHIRException;
009import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
010import org.hl7.fhir.r5.context.ContextUtilities;
011import org.hl7.fhir.r5.context.SimpleWorkerContext;
012import org.hl7.fhir.r5.extensions.ExtensionDefinitions;
013import org.hl7.fhir.r5.extensions.ExtensionUtilities;
014import org.hl7.fhir.r5.model.*;
015import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType;
016import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules;
017import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
018import org.hl7.fhir.r5.model.Enumerations.FHIRVersion;
019import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionContextComponent;
020import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
021import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
022import org.hl7.fhir.utilities.StandardsStatus;
023import org.hl7.fhir.utilities.Utilities;
024import org.hl7.fhir.utilities.VersionUtilities;
025
026public class ProfileVersionAdaptor {
027  public enum ConversionMessageStatus {
028    ERROR, WARNING, NOTE
029  }
030
031  public static class ConversionMessage {
032    private String message;
033    private ConversionMessageStatus status;
034    public ConversionMessage(String message, ConversionMessageStatus status) {
035      super();
036      this.message = message;
037      this.status = status;
038    }
039    public String getMessage() {
040      return message;
041    }
042    public ConversionMessageStatus getStatus() {
043      return status;
044    } 
045  }
046
047  private SimpleWorkerContext sCtxt;
048  private SimpleWorkerContext tCtxt;
049  private ProfileUtilities tpu;
050  private ContextUtilities tcu;
051  private List<StructureDefinition> snapshotQueue = new ArrayList<>();
052
053  public ProfileVersionAdaptor(SimpleWorkerContext sourceContext, SimpleWorkerContext targetContext) {
054    super();
055    this.sCtxt = sourceContext;
056    this.tCtxt = targetContext;
057    if (VersionUtilities.versionMatches(sourceContext.getVersion(), targetContext.getVersion())) {
058      throw new DefinitionException("Cannot convert profile from "+sourceContext.getVersion()+" to "+targetContext.getVersion());
059    } else if (VersionUtilities.compareVersions(sourceContext.getVersion(), targetContext.getVersion()) < 1) {
060      throw new DefinitionException("Only converts backwards - cannot do "+sourceContext.getVersion()+" to "+targetContext.getVersion());
061    }
062    tcu = new ContextUtilities(tCtxt);
063    tpu = new ProfileUtilities(tCtxt, null, tcu);
064
065  }
066
067  public StructureDefinition convert(StructureDefinition sd, List<ConversionMessage> log) throws FileNotFoundException, IOException {
068    if (sd.getKind() == StructureDefinitionKind.LOGICAL) {
069      return convertLogical(sd, log);
070    }
071    if (sd.getDerivation() != TypeDerivationRule.CONSTRAINT || !"Extension".equals(sd.getType())) {
072      return null; // nothing to say right now
073    }
074    sd = sd.copy();
075    convertContext(sd, log);
076    if (sd.getContext().isEmpty()) {
077      log.clear();
078      log.add(new ConversionMessage("There are no valid contexts for this extension", ConversionMessageStatus.WARNING));
079      return null; // didn't convert successfully
080    }
081    sd.setFhirVersion(FHIRVersion.fromCode(tCtxt.getVersion()));
082    sd.setSnapshot(null);
083
084    // first pass, targetProfiles
085    for (ElementDefinition ed : sd.getDifferential().getElement()) {
086      for (TypeRefComponent td : ed.getType()) {
087        List<CanonicalType> toRemove = new ArrayList<CanonicalType>();
088        for (CanonicalType c : td.getTargetProfile()) {
089          String tp = getCorrectedProfile(c);
090          if (tp == null) {
091            log.add(new ConversionMessage("Remove the target profile " + c.getValue() + " from the element " + ed.getIdOrPath(), ConversionMessageStatus.WARNING));
092            toRemove.add(c);
093          } else if (!tp.equals(c.getValue())) {
094            log.add(new ConversionMessage("Change the target profile " + c.getValue() + " to " + tp + " on the element " + ed.getIdOrPath(), ConversionMessageStatus.WARNING));
095            c.setValue(tp);
096          }
097        }
098        td.getTargetProfile().removeAll(toRemove);
099      }
100    }
101    // second pass, unsupported primitive data types
102    for (ElementDefinition ed : sd.getDifferential().getElement()) {
103      for (TypeRefComponent tr : ed.getType()) {
104        String mappedDT = getMappedDT(tr.getCode());
105        if (mappedDT != null) {
106          log.add(new ConversionMessage("Map the type " + tr.getCode() + " to " + mappedDT + " on the element " + ed.getIdOrPath(), ConversionMessageStatus.WARNING));
107          tr.setCode(mappedDT);
108        }
109      }
110    }
111
112    // third pass, unsupported complex data types
113    ElementDefinition lastExt = null;
114    ElementDefinition group = null;
115
116    int i = 0;
117    while (i < sd.getDifferential().getElement().size()) {
118      ElementDefinition ed = sd.getDifferential().getElement().get(i);
119      if (ed.getPath().contains(".value")) {
120        Map<String, ElementDefinition> children = loadValueChildren(sd.getDifferential(), ed, i);
121        if (ed.getType().size() > 1) {
122          if (ed.getType().removeIf(tr -> !tcu.isDatatype(tr.getWorkingCode()))) {
123            log.add(new ConversionMessage("Remove types from the element " + ed.getIdOrPath(), ConversionMessageStatus.WARNING));
124          }
125        } else if (ed.getType().size() == 1) {
126          TypeRefComponent tr = ed.getTypeFirstRep();
127          if (!tcu.isDatatype(tr.getWorkingCode()) || !isValidExtensionType(tr.getWorkingCode())) {
128            if (ed.hasBinding()) {
129              if (!"CodeableReference".equals(tr.getWorkingCode())) {
130                throw new DefinitionException("not handled: Unknown type " + tr.getWorkingCode() + " has a binding");
131              }
132            }
133            ed.getType().clear();
134            ed.setMin(0);
135            ed.setMax("0");
136            lastExt.setDefinition(ed.getDefinition());
137            lastExt.setShort(ed.getShort());
138            lastExt.setMax("*");
139            lastExt.getSlicing().setRules(SlicingRules.OPEN).setOrdered(false).addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url");
140            StructureDefinition type = sCtxt.fetchTypeDefinition(tr.getCode());
141            if (type == null) {
142              throw new DefinitionException("unable to find definition for " + tr.getCode());
143            }
144            log.add(new ConversionMessage("Replace the type " + tr.getCode() + " with a set of extensions for the content of the type along with the _datatype extension", ConversionMessageStatus.WARNING));
145            int insPoint = sd.getDifferential().getElement().indexOf(lastExt);
146            int offset = 1;
147
148            // a slice extension for _datatype
149            offset = addDatatypeSlice(sd, offset, insPoint, lastExt, tr.getCode());
150
151            // now, a slice extension for each thing in the data type differential
152            for (ElementDefinition elementFromType : type.getDifferential().getElement()) {
153              if (elementFromType.getPath().contains(".")) { // skip the root
154                ElementDefinition constraintFromExtension = children.get(elementFromType.getPath().substring(elementFromType.getPath().indexOf(".") + 1));
155                ElementDefinition base = lastExt;
156                int bo = 0;
157                int cc = Utilities.charCount(elementFromType.getPath(), '.');
158                if (cc > 2) {
159                  throw new DefinitionException("type is deeper than 2?");
160                } else if (cc == 2) {
161                  base = group;
162                  bo = 2;
163                } else {
164                  // nothing
165                }
166                ElementDefinition newBaseElementDefinition = new ElementDefinition(base.getPath());
167                newBaseElementDefinition.setSliceName(elementFromType.getName());
168                if (constraintFromExtension != null) {
169                  newBaseElementDefinition.setShortElement(constraintFromExtension.hasShort() ? constraintFromExtension.getShortElement() : elementFromType.getShortElement());
170                  newBaseElementDefinition.setDefinitionElement(constraintFromExtension.hasDefinition() ? constraintFromExtension.getDefinitionElement() : elementFromType.getDefinitionElement());
171                  newBaseElementDefinition.setCommentElement(constraintFromExtension.hasComment() ? constraintFromExtension.getCommentElement() : elementFromType.getCommentElement());
172                  newBaseElementDefinition.setMinElement(constraintFromExtension.hasMin() ? constraintFromExtension.getMinElement() : elementFromType.getMinElement());
173                  newBaseElementDefinition.setMaxElement(constraintFromExtension.hasMax() ? constraintFromExtension.getMaxElement() : elementFromType.getMaxElement());
174                } else {
175                  newBaseElementDefinition.setShortElement(elementFromType.getShortElement());
176                  newBaseElementDefinition.setDefinitionElement(elementFromType.getDefinitionElement());
177                  newBaseElementDefinition.setCommentElement(elementFromType.getCommentElement());
178                  newBaseElementDefinition.setMinElement(elementFromType.getMinElement());
179                  newBaseElementDefinition.setMaxElement(elementFromType.getMaxElement());
180                }
181
182                offset = addDiffElement(sd, insPoint - bo, offset, newBaseElementDefinition);
183                // set the extensions to 0
184                ElementDefinition newExtensionElementDefinition = new ElementDefinition(base.getPath() + ".extension");
185                newExtensionElementDefinition.setMax("0");
186                offset = addDiffElement(sd, insPoint - bo, offset, newExtensionElementDefinition);
187                // fix the url 
188                ElementDefinition newUrlElementDefinition = new ElementDefinition(base.getPath() + ".url");
189                newUrlElementDefinition.setFixed(new UriType(elementFromType.getName()));
190                offset = addDiffElement(sd, insPoint - bo, offset, newUrlElementDefinition);
191                // set the value 
192                ElementDefinition newValueElementDefinition = new ElementDefinition(base.getPath() + ".value[x]");
193                newValueElementDefinition.setMin(1);
194                offset = addDiffElement(sd, insPoint - bo, offset, newValueElementDefinition);
195                if (elementFromType.getType().size() == 1 && Utilities.existsInList(elementFromType.getTypeFirstRep().getWorkingCode(), "Element", "BackboneElement")) {
196                  newExtensionElementDefinition.setMax("*");
197                  newValueElementDefinition.setMin(0);
198                  newValueElementDefinition.setMax("0");
199                  newValueElementDefinition.getType().clear();
200                  group = newExtensionElementDefinition;
201                  group.getSlicing().setRules(SlicingRules.OPEN).setOrdered(false).addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url");
202                } else {
203                  Set<String> types = new HashSet<>();
204                  for (TypeRefComponent ttr : (constraintFromExtension != null && constraintFromExtension.hasType() ? constraintFromExtension : elementFromType).getType()) {
205                    TypeRefComponent ntr = checkTypeReference(ttr, types);
206                    if (ntr != null) {
207                      types.add(ntr.getWorkingCode());
208                      newValueElementDefinition.addType(ntr);
209                    }
210                  }
211                  if (newValueElementDefinition.getType().isEmpty()) {
212                    throw new DefinitionException("No types?");
213                  }
214                  if (ed.hasBinding() && "concept".equals(elementFromType.getName())) { // codeablereference, we have to move the binding down one
215                    newValueElementDefinition.setBinding(ed.getBinding());
216                  } else {
217                    newValueElementDefinition.setBinding(elementFromType.getBinding());
218                  }
219                }
220              }
221            }
222          }
223        }
224      }
225      if (ed.getPath().endsWith(".extension")) {
226        lastExt = ed;
227      }
228      i++;
229    }
230    if (!log.isEmpty()) {
231      if (!sd.hasExtension(ExtensionDefinitions.EXT_FMM_LEVEL) || ExtensionUtilities.readIntegerExtension(sd, ExtensionDefinitions.EXT_FMM_LEVEL, 0) > 2) {
232        ExtensionUtilities.setCodeExtension(sd, ExtensionDefinitions.EXT_FMM_LEVEL, "2");
233      }
234      StandardsStatus code = ExtensionUtilities.getStandardsStatus(sd);
235      if (code == StandardsStatus.TRIAL_USE) {
236        ExtensionUtilities.setCodeExtension(sd, ExtensionDefinitions.EXT_STANDARDS_STATUS, "draft");
237        ExtensionUtilities.setCodeExtension(sd, ExtensionDefinitions.EXT_STANDARDS_STATUS_REASON, "Extensions that have been modified for " + VersionUtilities.getNameForVersion(tCtxt.getVersion()) + " are still draft while real-world experience is collected");
238        log.add(new ConversionMessage("Note: Extensions that have been modified for " + VersionUtilities.getNameForVersion(tCtxt.getVersion()) + " are still draft while real-world experience is collected", ConversionMessageStatus.NOTE));
239      }
240    }
241    snapshotQueue.add(sd);
242    tCtxt.cacheResource(sd);
243    return sd;
244  }
245
246  private Map<String, ElementDefinition> loadValueChildren(StructureDefinition.StructureDefinitionDifferentialComponent differential, ElementDefinition ved, int i) {
247    Map<String, ElementDefinition> children = new HashMap<>();
248    String pathPrefix = ved.getPath();
249    i++;
250    while (i < differential.getElement().size() && differential.getElement().get(i).getPath().startsWith(pathPrefix)) {
251      ElementDefinition ed = differential.getElement().get(i);
252      children.put(ed.getPath().substring(pathPrefix.length()+1), ed);
253      differential.getElement().remove(i);
254    }
255    return children;
256  }
257
258  public void generateSnapshots(List<ConversionMessage> log) {
259    for (StructureDefinition sd : snapshotQueue) {
260      StructureDefinition base = tCtxt.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
261      if (base == null) {
262        log.add(new ConversionMessage("Unable to create "+VersionUtilities.getNameForVersion(tCtxt.getVersion())+" version of "+sd.getVersionedUrl()+" because cannot find StructureDefinition for "+sd.getBaseDefinition()+" for version "+tCtxt.getVersion(), ConversionMessageStatus.WARNING));
263      } else {
264        tpu.generateSnapshot(base, sd, sd.getUrl(), "http://hl7.org/" + VersionUtilities.getNameForVersion(tCtxt.getVersion()) + "/", sd.getName());
265      }
266    }
267  }
268
269  private StructureDefinition convertLogical(StructureDefinition sdSrc, List<ConversionMessage> log) {
270    StructureDefinition sd = sdSrc.copy();
271    sd.setFhirVersion(FHIRVersion.fromCode(tCtxt.getVersion()));
272    sd.setSnapshot(null);
273
274    // first pass, targetProfiles
275    for (ElementDefinition ed : sd.getDifferential().getElement()) {
276      for (TypeRefComponent td : ed.getType()) {
277        List<CanonicalType> toRemove = new ArrayList<CanonicalType>();
278        for (CanonicalType c : td.getTargetProfile()) {
279          String tp = getCorrectedProfile(c);
280          if (tp == null) {
281            log.add(new ConversionMessage("Remove the target profile "+c.getValue()+" from the element "+ed.getIdOrPath(), ConversionMessageStatus.WARNING));
282            toRemove.add(c);
283          } else if (!tp.equals(c.getValue())) {
284            log.add(new ConversionMessage("Change the target profile "+c.getValue()+" to "+tp+" on the element "+ed.getIdOrPath(), ConversionMessageStatus.WARNING));
285            c.setValue(tp);
286          }
287        }
288        td.getTargetProfile().removeAll(toRemove);
289      }
290    }
291    // second pass, unsupported primitive data types
292    for (ElementDefinition ed : sd.getDifferential().getElement()) {
293      for (TypeRefComponent tr : ed.getType()) {
294        String mappedDT = getMappedDT(tr.getCode());
295        if (mappedDT != null) {
296          log.add(new ConversionMessage("Map the type "+tr.getCode()+" to "+mappedDT+" on the element "+ed.getIdOrPath(), ConversionMessageStatus.WARNING));
297          tr.setCode(mappedDT);
298        }
299      }
300    }
301
302    // third pass, unsupported complex data types
303    for (int i = 0; i < sd.getDifferential().getElement().size(); i++) {
304      ElementDefinition ed = sd.getDifferential().getElement().get(i);
305      if (ed.getType().size() > 1) {
306        if (ed.getType().removeIf(tr -> !tcu.isDatatype(tr.getWorkingCode()))) {
307          log.add(new ConversionMessage("Remove types from the element " + ed.getIdOrPath(), ConversionMessageStatus.WARNING));
308        }
309      } else if (ed.getType().size() == 1) {
310        TypeRefComponent tr = ed.getTypeFirstRep();
311        if (!tcu.isDatatype(tr.getWorkingCode()) && !isValidLogicalType(tr.getWorkingCode())) {
312          log.add(new ConversionMessage("Illegal type "+tr.getWorkingCode(), ConversionMessageStatus.ERROR));
313          return null;
314        }
315      }
316    }
317
318    if (!log.isEmpty()) {
319      if (!sd.hasExtension(ExtensionDefinitions.EXT_FMM_LEVEL) || ExtensionUtilities.readIntegerExtension(sd, ExtensionDefinitions.EXT_FMM_LEVEL, 0) > 2) {
320        ExtensionUtilities.setCodeExtension(sd, ExtensionDefinitions.EXT_FMM_LEVEL, "2");
321      }
322      ExtensionUtilities.setCodeExtension(sd, ExtensionDefinitions.EXT_STANDARDS_STATUS, "draft");
323      ExtensionUtilities.setCodeExtension(sd, ExtensionDefinitions.EXT_STANDARDS_STATUS_REASON, "Logical Models that have been modified for "+VersionUtilities.getNameForVersion(tCtxt.getVersion())+" are still draft while real-world experience is collected");
324      log.add(new ConversionMessage("Note: Logical Models that have been modified for "+VersionUtilities.getNameForVersion(tCtxt.getVersion())+" are still draft while real-world experience is collected", ConversionMessageStatus.NOTE));
325    }
326
327    StructureDefinition base = tCtxt.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
328    if (base == null) {
329      base = sCtxt.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
330    }
331    if (base == null) {
332      throw new FHIRException("Unable to find base for Logical Model from "+sd.getBaseDefinition());
333    }
334    snapshotQueue.add(sd);
335    tCtxt.cacheResource(sd);
336    return sd;
337  }
338
339  private boolean isValidLogicalType(String code) {
340    StructureDefinition sd = tCtxt.fetchTypeDefinition(code);
341    if (sd != null) {
342      return true;
343    }
344    sd = sCtxt.fetchTypeDefinition(code);
345    if (sd != null && !sd.getSourcePackage().isCore()) {
346      return true;
347    }
348    return false;
349  }
350
351  private int addDatatypeSlice(StructureDefinition sd, int offset, int insPoint, ElementDefinition base, String type) {
352    ElementDefinition ned = new ElementDefinition(base.getPath());
353    ned.setSliceName("_datatype");
354    ned.setShort("DataType name '"+type+"' from "+VersionUtilities.getNameForVersion(sCtxt.getVersion()));
355    ned.setDefinition(ned.getShort());
356    ned.setMin(1);
357    ned.setMax("1");
358    ned.addType().setCode("Extension").addProfile("http://hl7.org/fhir/StructureDefinition/_datatype");
359    offset = addDiffElement(sd, insPoint, offset, ned);
360    //    // set the extensions to 0
361    //    ElementDefinition need = new ElementDefinition(base.getPath()+".extension");
362    //    need.setMax("0");
363    //    offset = addDiffElement(sd, insPoint, offset, need);
364    //    // fix the url 
365    //    ned = new ElementDefinition(base.getPath()+".url");
366    //    ned.setFixed(new UriType("http://hl7.org/fhir/StructureDefinition/_datatype"));
367    //    offset = addDiffElement(sd, insPoint, offset, ned);
368    // set the value 
369    ned = new ElementDefinition(base.getPath()+".value[x]");
370    ned.setMin(1);
371    offset = addDiffElement(sd, insPoint, offset, ned);
372    ned.addType().setCode("string");
373    ned.setFixed(new StringType(type));
374    return offset;
375  }
376
377  private int addDiffElement(StructureDefinition sd, int insPoint, int offset, ElementDefinition ned) {
378    sd.getDifferential().getElement().add(insPoint+offset, ned);
379    offset++;
380    return offset;
381  }
382
383  private boolean isValidExtensionType(String type) {
384    StructureDefinition extDef = tCtxt.fetchTypeDefinition("Extension");
385    ElementDefinition ed = extDef.getSnapshot().getElementByPath("Extension.value");
386    for (TypeRefComponent tr : ed.getType()) {
387      if (type.equals(tr.getCode())) {
388        return true;
389      }
390    }
391    return false;
392  }
393
394  private TypeRefComponent checkTypeReference(TypeRefComponent tr, Set<String> types) {
395    String dt = getMappedDT(tr.getCode());
396    if (dt != null) {
397      if (types.contains(dt)) {
398        return null;
399      } else {
400        return tr.copy().setCode(dt);
401      }
402    } else if (tcu.isDatatype(tr.getWorkingCode())) {
403      return tr.copy();
404    } else {
405      return null;
406    }
407  }
408
409  private String getCorrectedProfile(CanonicalType c) {
410    StructureDefinition sd = tCtxt.fetchResource(StructureDefinition.class, c.getValue());
411    if (sd != null) {
412      return c.getValue();
413    }
414    // or it might be something defined in the IG or it's dependencies
415    sd = sCtxt.fetchResource(StructureDefinition.class, c.getValue());
416    if (sd != null && !sd.getSourcePackage().isCore()) {
417      return c.getValue();
418    }
419    return null;
420  }
421
422  private String getMappedDT(String code) {
423    if (VersionUtilities.isR5Plus(tCtxt.getVersion())) {
424      return code;
425    }
426    if (VersionUtilities.isR4Plus(tCtxt.getVersion())) {
427      switch (code) {
428      case "integer64" : return "version";
429      default:
430        return null;
431      }
432    }
433    if (VersionUtilities.isR3Ver(tCtxt.getVersion())) {
434      switch (code) {
435      case "integer64" : return "string";
436      case "canonical" : return "uri";
437      case "url" : return "uri";
438      default:
439        return null;
440      }
441    }
442    return null;
443  }
444
445  public void convertContext(StructureDefinition sd, List<ConversionMessage> log) {
446    List<StructureDefinitionContextComponent> toRemove = new ArrayList<>();
447    for (StructureDefinitionContextComponent ctxt : sd.getContext()) {
448      if (ctxt.getType() != null) {
449        switch (ctxt.getType()) {
450        case ELEMENT:
451          String newPath = adaptPath(ctxt.getExpression());
452          if (newPath == null) {
453            log.add(new ConversionMessage("Remove the extension context "+ctxt.getExpression(), ConversionMessageStatus.WARNING));
454            toRemove.add(ctxt);
455          } else if (!newPath.equals(ctxt.getExpression())) {
456            log.add(new ConversionMessage("Adjust the extension context "+ctxt.getExpression()+" to "+newPath, ConversionMessageStatus.WARNING));
457            ctxt.setExpression(newPath);
458          }
459          break;
460        case EXTENSION:
461          // nothing. for now
462          break;
463        case FHIRPATH:
464          // nothing. for now ?
465          break;
466        case NULL:
467          break;
468        default:
469          break;
470        }
471      }
472    }
473    sd.getContext().removeAll(toRemove);
474  }
475
476  /**
477   * WIP: change a context for an older version, or delete it (= return null)
478   *
479   * ToDo: Use the Cross-Version infrastructure to make this more intelligent
480   *
481   * @param path
482   * @return
483   */
484  private String adaptPath(String path) {
485    String base = path.contains(".") ? path.substring(0, path.indexOf(".")) : path;
486    StructureDefinition sd = tCtxt.fetchTypeDefinition(base);
487    if (sd == null) {
488      StructureDefinition ssd = sCtxt.fetchTypeDefinition(base);
489      if (ssd != null && ssd.getKind() == StructureDefinitionKind.RESOURCE) {
490        if ("CanonicalResource".equals(base)) {
491          return "CanonicalResource";
492        } else {
493          return "Basic";
494        }
495      } else if (ssd != null && ssd.getKind() == StructureDefinitionKind.COMPLEXTYPE) {
496        return null;
497      } else {
498        return null;
499      }
500    } else {
501      ElementDefinition ed = sd.getSnapshot().getElementByPath(base);
502      if (ed == null) {
503        return null;
504      } else {
505        return path;
506      }
507    }
508  }
509
510  public SearchParameter convert(SearchParameter resource, List<ConversionMessage> log) {
511    SearchParameter res = resource.copy();
512    // todo: translate resource types
513    res.getBase().removeIf(t -> { 
514      String rt = t.asStringValue();
515      boolean r = !tcu.isResource(rt);
516      if (r) {
517        log.add(new ConversionMessage("Remove search base "+rt, ConversionMessageStatus.WARNING));
518      }
519      return r;
520    }
521        );
522    return res;
523  }
524
525}